Scene¶
The cell: one or more robots, the obstacles around them, the frames they mount on, the devices and sensors that give the environment behavior, the named motions, and the sequences that drive it all.
Units are meters, and the world is Z-up. Orientations are quaternions in
(x, y, z, w) order. Wherever a method takes robot=None, it acts on the
scene's first robot.
A scene is also the live link to the studio: state changes made from Python are pushed to connected browsers, and edits made in the browser are visible here.
import botrail as bt
scene = bt.Scene(bt.Robot.from_urdf("arm.urdf"))
scene.add_box("table", size=(0.6, 0.6, 0.05), position=(0.4, 0.0, 0.0))
scene.load_usd("cell.usda", prefix="env")
scene.set_robot_base_pose(*scene.frame("env/World/mount"))
Scene
¶
The cell: one or more robots in a workspace, with the obstacles, frames, sensors, devices, motions, and sequences around them. Shared with the studio server: state changes made here are pushed to connected browsers immediately.
collision_warnings
property
¶
Link shapes skipped for collision checking (e.g. meshes, until the mesh I/O crate lands).
robot_base_pose
property
¶
World pose of the robot root as (position, quaternion_xyzw).
scenario_names
property
¶
Defined scenario names, in authoring order (baseline — the
unmodified scene — is implicit and never listed).
add_beam_sensor
method descriptor
¶
add_beam_sensor(
name,
frm,
to,
radius=0.005,
watch=None,
watch_robot=False,
watch_robots=None,
mount=None,
)
Adds a photoelectric beam sensor between two world points, ON while
the beam is interrupted. Watch semantics as in add_zone_sensor.
add_box
method descriptor
¶
Adds a box obstacle (full extents, meters). Returns the final name, which may be uniquified. Changes are pushed to connected studios.
add_camera
method descriptor
¶
add_camera(
name,
position=Ellipsis,
quaternion=None,
look_at=None,
fov=None,
resolution=None,
near=None,
far=None,
mount=None,
robot=None,
link=None,
from_catalog=None,
revision=None,
)
Adds a camera: a named viewpoint with pinhole optics, drawn as a
frustum in the studio. Presentation only — it publishes no signal
and never affects planning or the cycle. position/quaternion
are in the mount frame (-Z looks, +Y is image-up); look_at aims
the camera at a world point instead of giving a quaternion. Mount
it with mount= (a vehicle device) or robot=/link= (a wrist
camera); default is a world fixture. fov is the horizontal field
of view in degrees (default 60); resolution sets the frustum
aspect and the pixel size of exports (default 1280x720).
from_catalog= names a sensor.camera package: its flat specs
become the optics defaults (fov/resolution and, from the range
specs, near/far), explicit arguments still win, and the package's
identity lands on the BOM (set_part(kind="camera")). With an
explicit pose, position/quaternion place the package's mount
face and the optical axis follows the package's own calibration
(frames.camera_frames); look_at aims the optical axis itself.
add_conveyor
method descriptor
¶
Adds a conveyor: while running, any unattached obstacle whose origin
lies inside the zone box is carried at velocity (m/s). Start/stop
it from sequences with bt.seq.start/bt.seq.stop.
add_cut_trace
method descriptor
¶
Binds an accumulating cut trace to a signal at a robot's TCP:
while the signal is true during playback, the studio draws the
TCP's trail (the cut so far) and spins spin_link if given. Pure
presentation, like add_weld_flash; in USD the toolpath curves
already carry the picture.
add_cylinder
method descriptor
¶
Adds a cylinder obstacle (URDF convention: axis along local +z).
add_field_sensor
method descriptor
¶
add_field_sensor(
name,
lidar,
watch=None,
watch_robot=False,
watch_robots=None,
range=None,
sector=None,
shadowing=True,
)
Adds a laser-scanner field sweeping through lidar: its name
becomes a read-only input signal, ON while a watched body crosses
the scanner's scan-plane sector. range shrinks the field radius
(default: the lidar's max range) and sector=(start, end) narrows
its angular window (degrees in the scan frame, 0 = +X, CCW;
default: the full sweep) — one scanner carries several fields, the
warning/protective field-set shape. shadowing (default on)
ray-tests each candidate's origin against the other obstacles, so
a body hidden behind another does not trip it. Geometry only — no
per-angle rays are cast, robot links (when watched) detect by
overlap alone, and a vehicle-mounted field ignores its own
machine's body.
add_frame
method descriptor
¶
Registers (or updates) a named world frame.
add_io_node
method descriptor
¶
add_io_node(
name,
kind="plc",
robots=None,
programs=None,
uplink=None,
channels=None,
place=None,
model=None,
label=None,
)
Declares a controller / I/O node of the cell's assignment layer:
kind is "plc", "safety_plc", "remote_io",
"robot_controller" (with robots=[...]) or "other".
programs lists the sequences this node runs (unlisted programs
are placed implicitly — see the I/O map guide); uplink is the
parent node ("PLC1" or ("PLC1", "PROFINET")) whose I/O a remote
station or safety module belongs to; channels are the dicts the
bt.io templates build (bt.io.di8(base="%IX0.0") + bt.io.do8(...)
or bt.io.ur_standard()).
add_lidar
method descriptor
¶
add_lidar(
name,
position=Ellipsis,
quaternion=None,
yaw=None,
fov=None,
range=None,
resolution=None,
channels=None,
vfov=None,
mount=None,
robot=None,
link=None,
from_catalog=None,
revision=None,
)
Adds a LiDAR scanner: a named scan origin with a planar sweep,
drawn as a sector in the studio. Presentation only — it publishes
no signal and never affects planning or the cycle (a field sensor
referencing it is the planned signal path). The scan plane is the
local XY plane, angle 0 along +X, counter-clockwise toward +Y (the
ROS laser frame); yaw aims that +X heading in the mount frame
(degrees about +Z) instead of giving a quaternion. Mount it with
mount= (a vehicle device) or robot=/link=; default is a
world fixture. fov is the full scan angle in degrees up to 360
(default 270), range the measuring band [min, max] in meters
(default (0.05, 20.0)), resolution the angular step in degrees
the scan API will default to (default 0.5).
from_catalog= names a sensor.lidar package: its flat specs
become the sweep defaults (fov/resolution and, from the range
specs, the measuring band), explicit arguments still win, and the
package's identity lands on the BOM (set_part(kind="lidar")).
The given pose places the package's mount face and the scan
origin follows the package's own frame (frames.lidar_frames —
ROS laser convention, which is botrail's, so no rotation fix).
add_lift
method descriptor
¶
add_lift(
name,
car,
zone_position,
zone_size,
stops,
speed=0.5,
axis=Ellipsis,
zone_quaternion=None,
start=None,
)
Adds a lift (elevator): the car obstacles ride along axis
between named stops, and whatever the capture zone holds when
the ride is commanded rides too — loose parts by origin, and
vehicles whole (body, deck load, mounted robot). Command it with
bt.seq.move_to(name, "2F") and await bt.seq.device_done(name).
The zone (like the car) is authored where the car stands at
start; a vehicle half out of it refuses to board by name.
Doors are ordinary authoring — an add_linear_axis panel and a
signal — not part of the device. Car entries name obstacles
exactly, or as subtree prefixes.
add_linear_axis
method descriptor
¶
Adds a linear axis (door / lifter / indexer) moving the listed
obstacles along axis at speed, positioned within range by
bt.seq.move_to; await it with bt.seq.device_done.
stops names positions along the axis ({"closed": 0.0, "open":
0.76}): bt.seq.move_to(name, "open") drives to one, and each is
a read-only input lane <name>/<stop> — ON while the axis stands
at it, off between stops — the limit switch an interlock waits on
(bt.seq.signal("door/closed")), and a DI on the I/O list.
What the axis drives is checked against every robot each tick: a
leaf closing on an arm still inside is a DeviceCollision error
naming the device, the obstacle, the robot and the link.
add_mesh
method descriptor
¶
Adds a mesh obstacle from an STL/OBJ file. The collision shape is a VHACD convex decomposition (computed on first load, then cached on disk); the studio renders the original mesh.
add_robot
method descriptor
¶
Adds another robot instance and returns its (possibly uniquified)
scene-unique instance name. name defaults to the model name.
Connected studios pick the new robot up immediately (the handshake
is re-broadcast).
add_scenario
method descriptor
¶
Defines (or replaces) a scenario — a named initial-state delta the
simulate_* calls can run under. Deltas only: signals overrides
declared internal-signal initial values, obstacles maps names to
a position or a (position, quaternion) pair, joints maps robot
instances to start configurations, and faults pins inputs for
the whole run — bt.io.stuck("part_at_pick", False) ignores the
sensor's geometry (or a program's set on an internal signal),
bt.io.open("part_at_pick") is a broken wire (input level low, so
the value follows the binding's invert). "baseline" is the
reserved name of the unmodified scene. Everything is validated when
the scenario is applied (at simulate), so deltas may name things
authored later.
add_segment
method descriptor
¶
add_segment(
motion,
goal=None,
kind="joint",
orientation_cone=None,
position_box=None,
robot=None,
)
Appends a waypoint segment to motion (created when missing).
goal=None captures the current configuration. Constraints:
orientation_cone=(axis_local, axis_world, angle_rad) keeps the tool
axis inside a cone; position_box=(min, max) keeps the TCP inside a
world-aligned box. Both apply along the whole segment.
add_sink
method descriptor
¶
Adds the far end of a line: any unattached carrier reaching the zone
goes back to source's magazine, free to be fed again.
add_source
method descriptor
¶
Adds a feeder: every interval seconds while running it puts the
next waiting member of pool at position.
The pool is finite because a baked timeline holds a fixed set of
named object tracks — an endless line is this plus an add_sink
that returns carriers to the magazine. Member i waits at
park + pitch * i, and a member that does not start on its slot
starts out on the line (an already-loaded belt).
add_spray_cone
method descriptor
¶
Binds a spray-cone effect to a signal at a robot's TCP: while the
signal is true during playback, the studio draws a translucent
cone length long and radius wide at its base along the TCP's
spray direction (its -Z), and USD export carries a beam of the
same size with animated visibility. Bind it to the effective
trigger a timeline writes with with_trigger_signal so it
follows what actually sprayed rather than the enable alone. Pure
presentation, like add_weld_flash.
add_vehicle
method descriptor
¶
add_vehicle(
name,
body,
path,
stations,
speed=0.5,
turn_speed=Ellipsis,
start=None,
ring=False,
allow_reverse=False,
max_grade=None,
drive="differential",
climb_speed=None,
descent_speed=None,
fixed_yaw=None,
tray_position=None,
tray_size=None,
tray_quaternion=None,
)
Adds a guided transport vehicle (an AGV / AMR as the cell sees it):
it drives station to station along path — straight legs at
speed, in-place pivot turns at turn_speed — carrying the body
obstacles rigidly. Dispatch it with bt.seq.goto(name, station) and
await arrival with bt.seq.device_done(name). The arrival heading
is the last leg's direction, so the waypoint before a station sets
how the vehicle docks. Body entries name obstacles exactly, or as
subtree prefixes ("/World/AGV" takes every obstacle under it).
Waypoints are (x, y) or (x, y, z): z is the floor height on the
guidance surface, so a ramp climbs with its waypoints (the body
stays level, and speed is spent along the 3D path). A path that
climbs needs max_grade — the steepest rise over horizontal run
the machine may take (0.10 = 10 %); without it only level paths
pass validation.
drive="aerial" makes the machine a multirotor: z is its own axis
(any climb, no grade rule, vertical legs fly — a ground station
under an overhead waypoint is the takeoff), speed is the
horizontal cruise and each leg's clock is the slower axis,
max(run/speed, rise/climb_speed (or descent_speed)). The nose
faces each leg's course, or holds fixed_yaw the whole flight.
add_vision_sensor
method descriptor
¶
add_vision_sensor(
name,
camera,
watch=None,
watch_robot=False,
watch_robots=None,
detect_range=None,
occlusion=True,
)
Adds a vision presence sensor looking through camera: its name
becomes a read-only input signal, ON while a watched body overlaps
the camera's view frustum. detect_range narrows the detection
band along the view axis (default: the camera's near/far clip);
occlusion (default on) ray-tests each candidate's origin against
the other obstacles, so a body hidden behind another does not trip
it. Geometry only — no pixels are rendered or interpreted, and
robot links (when watched) detect by overlap alone.
add_weld_flash
method descriptor
¶
Binds a weld flash to a signal at a robot's TCP: while the signal
is true during playback, the studio draws an arc flash there and
the USD export blinks an emissive prim. Pure presentation, driven
by the same baked signal a weld controller's "current on" output
would be — declare the signal first (define_signal), author it
from the sequence that owns the weld.
add_zone_sensor
method descriptor
¶
add_zone_sensor(
name,
position,
size,
quaternion=None,
watch=None,
watch_robot=False,
watch_robots=None,
mount=None,
)
Adds a box-shaped presence sensor: its name becomes a read-only
input signal, ON while a watched body overlaps the zone. watch is
a list of obstacle names (default: every obstacle); pass
watch_robot=True to sense robot links too (with watch=[] for a
robot-only light curtain).
allow_inter_robot_collision
method descriptor
¶
Excuses one link pair of two different robots from collision checking — the escape hatch for arms that share a mount plate or are meant to touch. Unlike a robot's own self-collision matrix, which is generated by sampling, inter-robot pairs are never inferred: whether two arms may touch depends on where their bases stand, so it is the author's call.
allow_link_obstacle_contact
method descriptor
¶
Marks contact between link (of robot) and obstacle as
process-intended — a milling cutter in its stock. The pair stops
counting as a collision (checking, planning, min_clearance);
toolpath rapids deliberately ignore the exemption — while not
cutting, any contact is a crash.
animate_carve
method descriptor
¶
animate_carve(
timeline,
stock,
stages=None,
voxel_size=0.001,
cutter_radius=0.004,
cutter_length=0.03,
dt=0.01,
robot=None,
tcp_link=None,
)
Progressive material removal for a baked cycle: carves stock in
stages equal time slices (default: one slice per second of
cycle, capped at 240 — the display lags the tool by at most one
slice, so this keeps the lag around a second), registers one
display-only obstacle per changed slice (grouped under
{stock}_cut/… in the scene tree, cheap AABB colliders — they
never collide), and returns the timeline with the visibility
windows injected: during playback — studio, USD export, and a
replayed recording alike — the stock disappears as it is cut
instead of starting pre-cut. The stock keeps colliding unchanged;
everything here is presentation.
animate_paint
method descriptor
¶
animate_paint(
timeline,
target,
applicator=None,
stages=None,
patch_size=0.01,
dt=0.01,
gate=None,
spec=None,
facing=None,
facing_tolerance=Ellipsis,
occlusion=True,
robot=None,
tcp_link=None,
trigger_signal=None,
style="amount",
paint_color=None,
substrate=None,
)
Progressive film build-up for a baked cycle: re-walks the coat in
stages equal time slices (default: one slice per second of
cycle, capped at 60 — the display lags the gun by at most one
slice), registers one display-only obstacle per changed slice
(grouped under {target}_film/…, cheap AABB colliders — they never
collide) each carrying the film's colour key, and returns the
timeline with the visibility windows injected: during playback —
studio, USD export, and a replayed recording alike — the target's
own colour gives way to the film building up on it. Optionally
writes the effective spray trigger as signal trigger_signal
(declare it first) for a timing lane and a spray-cone effect. The
target keeps colliding unchanged; everything here is presentation.
Stages walk at patch_size — coarser than a spray_coat for the
numbers, since a mesh per stage is what a viewer has to carry —
with the same trigger rules (applicator, gate, brushes) as
spray_coat. Coloured by amount by default (a build-up is about
how much paint is there: the ramp — in paint_color, if given —
runs from a light wash to the full colour at the spec's high edge,
or the final maximum without one), on the target's own colour;
style="spec" colours every stage against the band instead.
attach
method descriptor
¶
Attaches an obstacle to a robot link at its current relative pose —
a grasp. While attached the object follows the link (live, in
planning, and in playback) and collides as part of the robot.
link=None uses the TCP link; touch_links=None allows contact
with the link's subtree (the gripper).
auto_assign_io
method descriptor
¶
Gives every unbound point a channel, deterministically: points in
table order, channels in declaration order, on the point's host
and the stations uplinked to it, first free channel of a compatible
family (safety points prefer safety channels). Existing bindings
are kept; reassign=True first drops the bindings an earlier run
placed (hand bindings keep their channels). Points on an
implicit host (<cell>, <robot>) are not placed — declare the
node that runs their program. Returns the report afterwards.
bind_input
method descriptor
¶
bind_input(
name,
node,
channel,
tag=None,
field=None,
invert=False,
contact=None,
safety=False,
voltage=None,
logic=None,
note=None,
)
Wires an input point ("beam_pick", "line" for a device's
in-position input, "far.done" for a robot's done contact) to a
channel of node. invert=True flips the wire level (NC wiring);
contact ("no" / "nc"), field (the device on the far end),
voltage / logic ("pnp" / "npn") and note document it.
bind_output
method descriptor
¶
bind_output(
name,
node,
channel,
tag=None,
field=None,
invert=False,
contact=None,
safety=False,
voltage=None,
logic=None,
note=None,
)
Wires an output point ("conv" for a run coil, "vacuum" for a
coil, "line.index" for an indexed-transfer start, "far.start"
for a robot start) to a channel of node. Same keywords as
bind_input.
bom
method descriptor
¶
The bill of materials derived from the scene: robots and their tools (catalog identity when loaded from the catalog), conveyors / axes / vehicles, sensors and I/O nodes — each listed whether or not it has been identified — plus every obstacle or group a part was pinned to. Identical products merge into one row with the quantity summed.
cell_report
method descriptor
¶
cell_report(
timelines=None,
*,
scenarios=None,
deliverables=None,
clearance_dt=Ellipsis,
title=None,
ground_z=0.02,
sequences=None,
)
Gathers the cell report: robots, the cycles you pass (timelines
— a SequenceTimeline, a list, or a {name: timeline} dict; each
with its step spans, robot utilization and, unless
clearance_dt=None, the tightest clearance re-scanned against the
scene it was baked from), the I/O map's counts and findings, the
scenario matrix (scenarios= a ScenarioRuns — its runs also
stand in for timelines when none are given), the BOM's totals,
the plan-view footprint, and the SHA-256 of every file in
deliverables (external attachments with unverified provenance).
sequences scopes the report's I/O summary; default all. For a
common snapshot, fresh bakes and verified files, use bt.export_cell.
A reading surface — pytest keeps the asserts.
check
method descriptor
¶
Every static check in one report — the I/O lint, each sequence
walked for dangling references, unidentified equipment lines and
the requirement comparison (spec_short / spec_unknown). Returns
a bt.select.CheckReport (ok, findings, to_json(),
to_markdown()); botrail check prints the same thing.
check_collisions
method descriptor
¶
Colliding pairs at the current configuration, as
((kind, name), (kind, name)) tuples with kind "link"/"obstacle".
check_paint
method descriptor
¶
check_paint(
name,
target,
standoff=None,
max_incidence=Ellipsis,
max_range=None,
step_pos=0.005,
step_rot=0.05,
)
Checks a toolpath as a spray program against obstacle target
before anything is baked: every feed sample (rapids are not
spraying) looks along its spray axis — the TCP's -Z, against
the tool axis — and reports standoff and incidence, judged against
standoff (acceptable band, meters) and max_incidence (steepest
acceptable angle, radians). Pure geometry: no robot is involved,
so this runs before one is chosen. at on the issues is meters
along the path.
Like check_toolpath, the findings are drawn on the path in the
studio until the next check or edit of that path.
check_toolpath
method descriptor
¶
check_toolpath(
name,
robot=None,
tcp_link=None,
step_pos=0.005,
step_rot=0.05,
jump_threshold=0.5,
axis_tolerance=0.0,
spin="greedy",
)
Attempts every sample of a toolpath and reports all failures (unreachable / IK-branch jump / collision) without aborting — the pre-teach "which points can I not reach" face diagnosis.
clear_toolpath_marks
method descriptor
¶
Clears the marks a check left on toolpath name.
declare_io
method descriptor
¶
An exception to the derivation, or an unmodelled point. role is
"input" (an external contact whatever the sequences do),
"output" (a coil — also promotes a magazine to a real feeder),
"internal" (a relay, no I/O) or "exclude" (off the table). A
name the scene does not have becomes a new declared point when
role is input or output. kind overrides the channel type
("safe_di", ...), safety marks the safety class, pair names
the other channel of a two-channel safety input.
define_applicator
method descriptor
¶
Adds or replaces a toolpath (a continuous Cartesian process path,
see bt.toolpath). toolpath is the dict built by
bt.toolpath.builder() / bt.toolpath.from_gcode() — or its JSON
string. Targets live in the part frame named by its frame key
(resolved at bake time, so moving the frame re-solves the path).
Declares a spray applicator under name — the dict
bt.paint.applicator(...) builds — so brushes can refer to it.
Validated now, not at bake time.
define_brush
method descriptor
¶
Declares a brush: a named process setting a toolpath's strokes run
with — applicator (a define_applicator name) at flow times
its calibrated flow, opened lead seconds before each stroke with
this brush begins and closed lag seconds after it ends. The
program's own trigger, per stroke: pass the brush on
ToolpathBuilder.feed(...) (or bt.paint.strokes(brush=...)), and
the film integrator sprays each stroke with it; feed moves that
name no brush in such a path run with the gun off.
define_signal
method descriptor
¶
Declares (or re-initializes) an internal signal — a PLC internal
relay written by bt.seq.set_signal actions and read by
bt.seq.signal transitions.
detach
method descriptor
¶
Detaches an obstacle; its pose freezes where the robot holds it.
disallow_link_obstacle_contact
method descriptor
¶
Removes an allowed-contact entry added by
allow_link_obstacle_contact.
export_bom
method descriptor
¶
Writes the BOM to path; the format follows the extension
(.csv, .md, .json) unless format says otherwise.
export_interlocks
method descriptor
¶
Writes interlocks() to path; the format follows the extension
(.md, .csv, .json).
export_io_list
method descriptor
¶
Writes the I/O list to path; the format follows the extension
(.csv, .md, .json).
export_layout
method descriptor
¶
export_layout(
path,
format=None,
*,
scale=100.0,
units="mm",
ground_z=0.02,
frames=True,
labels=True,
reach=True,
grid=Ellipsis,
title=None,
)
Writes the layout sheet to path; the format follows the extension
(.svg, .dxf, .json) unless format says otherwise. The other
keywords are layout()'s.
export_plcopen
method descriptor
¶
Writes plcopen() to path (.xml).
export_topology
method descriptor
¶
Writes io_topology to path; the format follows the extension
(.mmd / .md Mermaid, .dot / .gv Graphviz, .json).
export_usd
method descriptor
¶
Writes the scene to a USD layer that opens in usdview / Omniverse /
Blender. With a trajectory, an animation: robot link motion as
timeSamples, obstacles as prims, grasped objects riding along —
robot names the instance the trajectory belongs to (required when
the scene has several). Without one, the cell as it stands: robots
at their current joint positions, every visible obstacle at its
pose, toolpaths and cameras — the static layer a layout is handed
around as. The extension picks the serialization — .usda text,
.usdc/.usd binary crate (about half the size). USD-sourced
robots reference their original stage (assets copied to a sibling
<stem>_assets/ directory); URDF robots are authored from the
model's visuals. Returns exporter warnings.
footprint
method descriptor
¶
The plan-view extent of the equipment as a dict — min, max
(x, y in metres), width, depth, area (m²), height (tallest
non-ground item). Ground is anything whose top is at or below
ground_z.
frame
method descriptor
¶
Pose of a named frame as (position, quaternion_xyzw) — e.g.
scene.set_robot_base_pose(*scene.frame("/World/mount")).
generate_python
method descriptor
¶
Generates a Python script that rebuilds this scene with the botrail API (same content as the studio's "Export Python").
grasp_close
method descriptor
¶
Solves the joint values that close the robot's gripper on obstacle
name, to a signed clearance from its surface (the default is
half a millimetre of overtravel — measured to report contact
reliably under physics without disturbing the part). Returns
{joint: value}, ready to hand to bt.seq.ramp.
Pose the grasp first — the solve runs at the current configuration
and the part's current pose, the same contract as attach. Drive
joints default to every actuated joint below the tool mount (mimic
followers close with their drivers); a limitless drive joint needs
its fully-closed value in closed.
interlocks
method descriptor
¶
The interlock table over sequences (every sequence by default):
one row per output a step switches — a signal, a device command, a
robot motion or ramp, a grasp — with the condition that admits the
step (the previous step's transition, an arm's condition, the
cycle's last transition for a first step), the steps it follows and
the inputs the condition reads, each classified (sensor, signal,
device lane, device, robot), a signal traced to the program and
step that writes it, an address where the host has it bound. The
control designer's interlock sheet, derived rather than typed.
io_list
method descriptor
¶
The I/O list as text: format is "csv", "md" (Markdown table)
or "json" (raw fields, step indices included).
io_map
method descriptor
¶
The assignment layer as authored — nodes, bindings, declarations.
Pass it to to_script(io=...) to project a newer assignment onto
a timeline baked earlier.
io_points
method descriptor
¶
The cell's I/O points, derived from how the sequences use the
scene's names (nothing to author): sensors are inputs, coils and
device commands are outputs, signals read or written across
controllers are handshake wires, robots driven from another host
get start/done points. sequences picks the program set (default:
every sequence — pass what you would pass to simulate_sequences
when alternative programs coexist). See docs/guides/io-map.md.
io_report
method descriptor
¶
Lint findings over the derived I/O map: name clashes, unreferenced
definitions, numeric (word/analog) points, programs on the
implicit cell host. assert scene.io_report().errors() == [] is
the CI form.
io_topology
method descriptor
¶
The electrical topology as text: format is "mermaid" (a
flowchart LR for Markdown), "dot" (Graphviz) or "json".
layers filters the edges — any of "functional", "io",
"network", "wiring", "safety" (default: everything).
Magazines stay out unless include_cosmetic=True.
joint_positions_of
method descriptor
¶
Joint configuration of the robot instance named name.
layout
method descriptor
¶
layout(
format="svg",
*,
scale=100.0,
units="mm",
ground_z=0.02,
frames=True,
labels=True,
reach=True,
grid=Ellipsis,
title=None,
)
The plan-view layout sheet as text: format is "svg" (a
self-contained drawing, scale pixels per metre), "dxf" (a
minimal R12 file for 2D CAD, in units — "mm" or "m") or
"json" (the drawn items in world metres). The sheet is derived
from the scene: every visible obstacle as its footprint (convex
hulls of primitives, bounding boxes of meshes), robots as base marks
with the catalog reach as a dashed circle, conveyor / sink zones,
axis travel and vehicle routes, sensor zones and beams, named
frames, labels (pinned parts first, then named groups), a metre
grid and the overall dimensions. Anything whose top sits at or
below ground_z is floor: drawn faint, left out of the extents.
lidar_scan
method descriptor
¶
Simulates one sweep of the named lidar: one ray per beam at the
scanner's angular resolution, against the scene's collision
shapes — obstacles and robot links alike, massing bodies included.
That is the honest complement of capture_depth (the rendered
meshes): what the scan sees is what the cell can hit. Blind-spot
analysis in one call: 0.0 ranges are no-returns, hits names
what each beam struck, points()/save_ply() give the cloud.
Deterministic — two calls return identical data.
Without t the sweep sees the scene as it stands (a parked
vehicle, the current joint pose). t sweeps at that instant of
the last baked cycle instead — joints, moved objects and the
vehicle the scanner rides all follow the timeline's tracks
(clamped to the duration; simulate a sequence first).
noise adds Gaussian range noise, 1σ meters (a datasheet's
±3 cm accuracy reads as noise=0.03) — what a beam hits never
changes, only how far it reports it, clamped to the measuring
band. Still deterministic: the draw is a pure hash of seed,
beam and instant, so the same call repeats bit-for-bit and a
different seed is an independent stream.
link_pose
method descriptor
¶
World pose of a link as (position, quaternion_xyzw).
link_pose_at
method descriptor
¶
World pose of link_name at joint configuration joints — forward
kinematics without moving the robot (its current joints and any
connected studio are untouched). bt.select.requirements measures
taught targets through this.
load_project
staticmethod
¶
Loads a .botrail project file into a fresh scene (robots
included). URDF robots rebuild from the embedded XML; USD robots
re-import from the referenced stage path.
load_urdf
method descriptor
¶
load_urdf(
path,
prefix=None,
position=None,
quaternion=None,
args=None,
geometry="visual",
frames=True,
package_paths=None,
)
Imports a URDF or xacro as scenery: every visual becomes an
obstacle named <prefix>/<link>, posed at the model's zero
configuration and placed at position / quaternion. Links that
carry no geometry become named frames (see frame()), so a file can
name where the next thing mounts. args fills the file's
$(arg …) substitutions, which is what lets one parametric file
draw every size a product is sold in. geometry="collision" reads
the collision shapes instead of the visuals. Returns the obstacle
names it added.
This is furniture, not a machine: joints are taken at zero and
nothing here articulates. A robot is Robot.from_urdf /
Robot.from_xacro and add_robot.
load_usd
method descriptor
¶
Imports the static geometry of a USD stage (usda/usdc/usdz —
references, variants, and instancing are composed) as obstacles,
normalized to meters / Z-up. Leaf Xform/Scope prims become named
frames (see frame()), usable as robot mount points. Obstacle and
frame names are the prim paths, optionally prefixed. Returns the
added obstacle names.
min_obstacle_distance
method descriptor
¶
Minimum robot-obstacle distance (0 when colliding); None without
obstacles.
motion_segments
method descriptor
¶
Segments of a motion as (kind, goal_positions) tuples.
mount_robot
method descriptor
¶
mount_robot(
device,
offset_position=None,
offset_quaternion=None,
robot=None,
gait=None,
spin=None,
)
Puts a robot on a vehicle: from here its base is derived from that
vehicle's frame, offset away, and re-derived every scan tick — an
arm and a chassis become an AMR. Planned motions cannot start while
the vehicle is driving (a plan is baked in world coordinates); ramps
can, which is how an arm stows itself on the move.
With a gait (a bt.Gait) the robot is the vehicle's legs: it
walks whenever the vehicle drives, and stands in the gait's stance
when it does not. The offset then defaults to the one that puts the
stance feet on the vehicle plane, and the robot is set to its stance.
spin is presentation: {joint: rad/s} turned while the vehicle
is off its starting ground or moving — a multirotor's propellers,
signed so counter-rotating pairs read right. Continuous joints
only; no check reads the phase (the collision stays the swept
solid the catalog authors).
obstacle_bounds
method descriptor
¶
World-frame axis-aligned bounds of an obstacle, as (min, max).
A cell that has to sit a workpiece on a pallet asks the geometry
where its underside is instead of hard-coding a measured number
that quietly stops matching when the mesh is rebuilt.
obstacle_color
method descriptor
¶
An obstacle's display colour as linear RGB, or None when it has
none and the viewer picks the shading.
obstacle_material
method descriptor
¶
(metalness, roughness), or None when the obstacle has no
authored material.
obstacle_pose
method descriptor
¶
World pose of an obstacle as (position, quaternion_xyzw).
part
method descriptor
¶
The part pinned to name as a dict (target, kind, catalog,
manufacturer, model, category, description, qty,
attributes), or None.
plan
method descriptor
¶
Plans a collision-free, time-parameterized trajectory from the
current configuration to goal (joint positions in DOF order).
With broadcast=True (default) the result is also pushed to
connected studio clients for preview playback.
plan_motion
method descriptor
¶
Plans every segment of motion from the current configuration into
one trajectory (rest-to-rest at segment boundaries). With
broadcast=True the result is pushed to connected studios.
plan_to_pose
method descriptor
¶
plan_to_pose(
position,
quaternion=None,
link=None,
max_iters=10000,
seed=None,
broadcast=True,
robot=None,
)
IK to the given pose, then plan to the found configuration.
plan_toolpath
method descriptor
¶
plan_toolpath(
name,
robot=None,
tcp_link=None,
step_pos=0.005,
step_rot=0.05,
jump_threshold=0.5,
rapid_speed=None,
axis_tolerance=0.0,
spin="greedy",
)
Bakes a toolpath into one continuous trajectory: seed-continuous IK
along the resampled path (5-DOF axis-aligned where the spin is
free), collision-checked per sample, then time-parameterized in one
piece with the commanded feed as a floor — the TCP holds the feed
and slows only where joint limits force it. segment_ends on the
result marks each move's completion time. The trajectory starts at
the path's first target; author the approach separately.
spin picks how the free rotation about the tool axis is chosen:
"greedy" (seed-continuous, milliseconds) or "optimize"
(Descartes-style global pass over a spin grid — spends spin early
to stay solvable late; seconds). axis_tolerance (rad) permits
lead/tilt deviation from the authored axis on spin-free samples.
play_usd_animation
method descriptor
¶
Plays a baked USD recording (an Isaac Sim capture or a botrail
export) on the scene's robots and broadcasts it to the studio.
Joint playback is used when the layer carries JointStateAPI
samples for every actuated joint; otherwise the recorded body
transforms are replayed directly (force_transforms forces the
latter). With several robots each is located at
/World/<sanitized instance name> (the export convention);
robot_roots maps instance names to prim paths when the recording
placed them elsewhere. Returns {"mode", "duration", "warnings"}.
plcopen
method descriptor
¶
The sequences as PLCopen XML (IEC 61131-10, TC6 v2.01): one SFC
program per sequence (sequences= a subset; default all), steps
with their entry actions and transitions, select as a selection
divergence, and the cycle jump at the end (cycle=False parks the
program in a final step). Conditions are ST expressions; device
coils and commands write the I/O map's variables (declared once as
resource globals, with AT addresses from PLC-side bindings);
robot commands call stub function blocks the control engineer
replaces — or the start / done handshake where the map says the
robot is driven from another host. Opens in Beremiz / OpenPLC
Editor. Deterministic (fixed timestamps).
rename_robot
method descriptor
¶
Renames a robot instance, returning the name it actually got (a
name already taken is uniquified). Sequence actions, robot_done
conditions and a zone sensor's watch list follow the robot, so a
cell can be renamed after it has been authored.
requirements
method descriptor
¶
What every bill-of-materials line must be able to do, derived from
the cell (payload from the tool and the grasped parts, reach from
the taught targets, a beam's span, a conveyor's size and load, ...)
and compared with what the chosen part says. Returns a
bt.select.Requirements (rows, findings(), to_markdown(),
to_json()); botrail derives and compares — it does not choose.
robot_base_pose_of
method descriptor
¶
World base pose of the robot instance named name.
save_project
method descriptor
¶
Saves the whole cell — robots (URDF embedded, USD by reference),
joint state, obstacles, frames, motions, sequences, signals,
sensors, and devices — as a .botrail project file. Plain JSON
when everything is self-contained; a zip archive (project.json +
assets/) when mesh files are referenced, so the file stays
portable across machines.
scan_sweep
method descriptor
¶
One sweep per frame over the whole last baked cycle, on the
export grid (1/fps steps plus the final instant). The corridor
survey for a riding scanner: merge the frames' points() and the
drive's visibility is one cloud. noise/seed as in
lidar_scan — every frame draws its own beams, so the merged
cloud thickens the way a real drive's does.
sequence
method descriptor
¶
Starts (or replaces) a PLC-style sequence and returns a builder:
scene.sequence("pick").step("run", actions=[bt.seq.motion("go")]).
set_gripper_drive
method descriptor
¶
set_gripper_drive(
joints=None,
max_force=None,
stiffness=None,
damping=None,
finger_mass=None,
robot=None,
)
Declares a force-limited drive on the robot's gripper joints
(design-grasping.md G3). Under physics=True the driven fingers
become dynamic bodies moved by force-capped position motors: a
grasped part is held by friction, so a too-weak cap or a
too-fast carry slips for real, and grasp_report() reads the slip
back. Without a physics backend the declaration is inert.
joints— driven actuated joints; default derives every actuated joint below the tool mount (mimics follow either way).max_force— per-joint force cap in N (prismatic) / N·m (revolute); default is each joint's URDF effort limit.stiffness,damping— motor gains; the defaults saturate the cap within ~a millimetre and bound the free speed.finger_mass— mass floor per driven finger body, kg (default 0.2, a finger-plus-carriage moving mass). The engine's contact stiffness scales with the pair's masses, so a mesh-derived few-gram finger cannot develop a newton-scale clamp.
set_link_material
method descriptor
¶
Sets a robot link's contact material for physics bakes — the
name-keyed sibling of set_physics, for the surface the robot
brings to a contact (a fingertip's rubber pad). Inert without a
physics backend; links keep the engine default (friction 0.5,
restitution 0) unless set.
set_obstacle_color
method descriptor
¶
Sets an obstacle's display colour, linear RGB in 0..1. None hands
the shading back to the viewer. Display only — collision and planning
see the same geometry either way.
set_obstacle_enabled
method descriptor
¶
Includes/excludes an obstacle from collision checking (it keeps rendering in the studio either way).
set_obstacle_legend
method descriptor
¶
Attaches a colour key to an obstacle whose colours mean something
— stops is a list of ((r, g, b), label) swatches top to bottom,
linear RGB, empty labels allowed — or clears it with stops=None.
The studio draws it beside the viewport while the obstacle is in
the scene. Presentation only.
set_obstacle_material
method descriptor
¶
Sets how an obstacle's surface takes light. Passing neither knob clears the material, handing the choice back to the viewer.
set_obstacle_visible
method descriptor
¶
Hides or shows an obstacle without touching whether it collides. A hidden obstacle is still a real obstacle: this is how a workpiece carries a display mesh and its convex collision pieces at once.
set_obstacle_walkable
method descriptor
¶
Marks an obstacle's top face as a place a walking machine's feet may stand — a stair tread, a mezzanine slab. Footfalls snap onto it and the walker may touch it (nobody collision-checks a floor against the machine standing on it); everything else still collides with it normally. Only an upright box (yaw rotation is fine) can be walkable.
set_part
method descriptor
¶
set_part(
name,
*,
kind=None,
catalog=None,
manufacturer=None,
model=None,
category=None,
description=None,
qty=1,
attributes=None,
**extra,
)
Pins a part — what the thing is commercially — to a resident or
group by name: a robot, a tool in its stack (by its BOM row name,
arm/tool, arm/tool2 — the identity of a made bracket, or the
last word on a catalog one), a device, a sensor, an I/O node, an
obstacle, or an obstacle group (everything under name/ — an
imported subtree, a generated fence). Identity is optional and
free-form: catalog ("id" or "id@revision" or (id,
revision)), manufacturer, model, category ("conveyor",
"structure.fence", ...), description, qty (how many the
target stands for), and any further keywords or attributes={...}
as free attributes (numbers are summed by bom().total(key),
text is carried). Pass kind= ("robot", "tool", "device",
"sensor", "io_node", "obstacle", "group") when a name lives in several
name spaces. Re-pinning replaces. Returns the kind resolved. The
BOM (bom()) is derived from these plus the catalog identity of
robots and tools.
set_physics
method descriptor
¶
set_physics(
name,
dynamic=None,
mass=None,
friction=None,
restitution=None,
linear_damping=None,
angular_damping=None,
ccd=None,
)
Marks an obstacle for physics: dynamic=True hands its pose to
the engine during a physics bake (simulate_sequence(...,
physics=True)) — it falls, collides, settles. All properties are
inert on a kinematic bake, so authoring them never changes an
existing cell. Repeated calls merge: only the knobs you pass
change. mass in kg (unset derives it from the collision shape's
volume at 1000 kg/m³); ccd enables continuous collision
detection for small fast parts.
set_robot_base_pose
method descriptor
¶
Places the robot root at the world-frame pose and pushes the new state to connected studio clients.
set_tcp_target
method descriptor
¶
Solves IK toward the given pose (seeded from the current
configuration), applies the best-effort result to the scene, and
pushes it to connected studio clients. quaternion=None matches
position only; link defaults to the TCP link.
show_film
method descriptor
¶
Puts a film map in the picture: the coated target's own colour
gives way to film's heatmap mesh, registered as a display-only
obstacle named {target}_film (disabled for collision, cheap
collider) with its micron colour key attached, so the studio draws
the legend beside the viewport. Collision and planning still see
the original target; everything here is presentation. Returns the
obstacle name; remove it and re-show the target to undo.
simulate_scenarios
method descriptor
¶
Rolls the same sequences under a set of scenarios — the cell's
test-case matrix in one call. scenarios=None runs baseline
plus every defined scenario. A scenario that fails (bad delta,
plan failure, timeout) is collected into the result's errors
rather than aborting the sweep — finding the failing scenario is
the point. Each run is deterministic, so coverage and cycle times
off the result are CI-assertable numbers.
simulate_sequence
method descriptor
¶
simulate_sequence(
name,
dt=0.01,
max_duration=120.0,
plan_resolution=None,
scenario=None,
toolpath_spin=None,
physics=None,
)
Rolls out a sequence with the PLC scan loop against a snapshot of this scene (motions plan at their step, grasped objects ride along) and returns the baked timeline. Also broadcasts the result to connected studio clients for playback.
scenario applies a named initial-state delta (add_scenario) to
the snapshot first — the live scene is never touched. None and
"baseline" both mean the scene as it stands.
simulate_sequences
method descriptor
¶
simulate_sequences(
names,
dt=0.01,
max_duration=120.0,
plan_resolution=None,
scenario=None,
toolpath_spin=None,
physics=None,
)
Rolls out several sequences concurrently — the PLC picture of a
line: one program per station plus a transfer program, each a plain
serial SFC, synchronized only through signals and sensors. One scan
tick advances every program in list order, so the bake stays
bit-identical run to run; the result is a single timeline whose
step spans carry program/step names.
Every robot, device, and written signal must be commanded by at
most one of the programs — two programs driving one resource is
rejected up front, like two PLC programs writing one coil.
plan_resolution tightens the planner's edge-validity stride (rad,
joint-space L2). The default 0.05 samples a big arm's sweep every
~10 cm of TCP travel — coarse enough to step across sheet metal, so
cells full of 12 mm flanges pass 0.005.
timeline_from_trajectory
method descriptor
¶
Wraps a planned trajectory as a single-robot SequenceTimeline so
the timeline consumers — studio playback, export_usd,
min_clearance — accept it without authoring a sequence. Other
robots hold their current pose; objects stay static. Script export
is not supported on the result.
unbind_input
method descriptor
¶
Drops the binding of an input point — on node, or everywhere.
unbind_output
method descriptor
¶
Drops the binding of an output point — on node, or everywhere.
IoPoint¶
Returned by Scene.io_points: one derived I/O
point of the cell (see The I/O map).
IoPoint
¶
One derived I/O point of the cell (see Scene.io_points).
aspect
property
¶
The facet for device commands and robot handshakes ("index",
"dispatch", "station", "position", "speed", "start",
"done", "program"), or None.
host
property
¶
The controller that owns the point: "<cell>", "<robot name>"
(implicit placement) or a declared node; None when nothing pins it.
source
property
¶
The derivation rule that produced it: "sensor",
"signal:handshake", "signal:internal", "signal:write-only",
"signal:read-only", "device:run", "device:done",
"device:command", "device:cosmetic", "robot:start",
"robot:done", "robot:program".
status
property
¶
"unbound", "internal" (a relay, no I/O), "cosmetic"
(magazine), "constant" (a coil that is on from t = 0 and never
commanded).
writers
property
¶
(sequence, flat step index, step name) of the steps that write
the point (coil writes, device commands, robot starts).
IoReport¶
Returned by Scene.io_report: the findings over
the derived I/O map, by severity.
IoReport
¶
The findings of Scene.io_report().
IoFinding¶
One entry of an IoReport.
IoFinding
¶
IoMap¶
Returned by Scene.io_map: the assignment layer as
authored (nodes, bindings, declarations) — hand it to to_script(io=...).
IoMap
¶
The assignment layer of a scene's I/O map (see Scene.io_map).
Bom¶
Returned by Scene.bom: the bill of materials derived
from the scene's parts (see Parts and the BOM).
Bom
¶
The bill of materials Scene.bom() derives — one row per distinct
product, in scene order.
rows
property
¶
The rows as dicts: category, names (the residents the row
stands for), manufacturer, model, catalog (id@revision),
qty, description, attributes.
save
method descriptor
¶
Writes the table to path; the format follows the extension
(.csv, .md, .json) unless format says otherwise.
total
method descriptor
¶
Σ qty × key over the rows carrying it as a number, or None
when no row does (a missing figure must not read as zero).
unidentified
method descriptor
¶
Rows nothing identifies yet (no catalog reference, maker or model) — the purchasing to-do list.
InterlockTable¶
Returned by Scene.interlocks: every output a
step switches against the condition that admits the step (see
The interlock table).
InterlockTable
¶
One lint finding of the I/O map.
The interlock table Scene.interlocks() derives: rows are plain
dicts (program, host, step, kind, target, output,
condition, after, inputs); to_markdown() / to_csv() render
the same rows for people and spreadsheets.
io_error
property
¶
Why hosts and addresses are missing, when the I/O map could not be
derived; None when it could.
rows
property
¶
One dict per output: program, host, step, kind, target,
output, condition, after (the predecessor steps) and inputs
(name, kind, written_by, address).
save
method descriptor
¶
Writes the table to path; the format follows the extension
(.md, .csv, .json) unless format says otherwise.
CellReport¶
Returned by Scene.cell_report: cycles, I/O,
scenarios, machines, BOM totals, footprint and deliverable digests in one
page (see Layout sheet and cell report).
CellReport
¶
The cell report Scene.cell_report() gathers: robots, cycles, I/O,
scenarios, machines, BOM totals, footprint, deliverable digests. Every
section is a plain dict / list (JSON-shaped); to_markdown() renders
the same data for people.
cycles
property
¶
The cycles passed in: name, sequences, scenario, duration,
steps (name, sequence, start, end), robots (robot,
busy, utilization), clearance (distance, t, pair) and
branches.
io
property
¶
The I/O summary — points, by_kind, bound, unbound,
internal, safety, nodes, findings — or None when the map
could not be derived (see io_error).
machines
property
¶
The machine tools: name, category, manufacturer, model,
catalog, door (name, drive, stroke_mm, speed, lanes,
driven), buttons, controller, programs.
robots
property
¶
The robots: name, dof, base, and catalog / manufacturer /
model / reach when the catalog knows them.
cycle_time
method descriptor
¶
The cycle time of name (or of the first cycle), or None.
min_clearance
method descriptor
¶
The tightest clearance over every cycle that measured one, or
None.
save
method descriptor
¶
Writes the report to path; the format follows the extension
(.md, .json) unless format says otherwise.