Skip to content

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.

applicator_names property

applicator_names

attachments property

attachments

Attached obstacles as (object, link) name pairs.

brush_names property

brush_names

camera_names property

camera_names

collision_warnings property

collision_warnings

Link shapes skipped for collision checking (e.g. meshes, until the mesh I/O crate lands).

device_names property

device_names

frames property

frames

All named frames as {name: (position, quaternion_xyzw)}.

joint_positions property

joint_positions

lidar_names property

lidar_names

motion_names property

motion_names

obstacle_names property

obstacle_names

robot property

robot

robot_base_pose property

robot_base_pose

World pose of the robot root as (position, quaternion_xyzw).

robots property

robots

Instance names of every robot in the scene, in insertion order.

scenario_names property

scenario_names

Defined scenario names, in authoring order (baseline — the unmodified scene — is implicit and never listed).

sensor_names property

sensor_names

sequence_names property

sequence_names

signals property

signals

Declared internal signals as (name, initial) pairs.

toolpath_names property

toolpath_names

add_beam_sensor method descriptor

add_beam_sensor(
    name,
    frm,
    to,
    radius=0.005,
    watch=None,
    watch_robot=False,
    watch_robots=None,
    mount=None,
    watch_groups=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

add_box(name, size, position, quaternion=None, color=None)

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,
    body_visible=True,
)

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

add_conveyor(
    name,
    zone_position,
    zone_size,
    velocity,
    zone_quaternion=None,
    running=True,
)

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_cylinder method descriptor

add_cylinder(
    name,
    radius,
    length,
    position,
    quaternion=None,
    color=None,
)

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

add_frame(name, position, quaternion=None)

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,
    body_visible=True,
)

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

add_linear_axis(
    name,
    objects,
    axis,
    speed,
    range,
    position=0.0,
    stops=None,
)

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

add_mesh(
    name,
    path,
    position,
    scale=None,
    quaternion=None,
    color=None,
)

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

add_robot(
    robot,
    name=None,
    base_position=None,
    base_quaternion=None,
)

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

add_scenario(
    name,
    signals=None,
    obstacles=None,
    joints=None,
    faults=None,
)

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,
    group=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. group names the arm a new motion drives (an existing motion keeps its arm): only that arm's joints move, and goal components outside it are ignored. Omitted, the robot's sole group — or every joint of a robot with several.

add_sink method descriptor

add_sink(
    name,
    zone_position,
    zone_size,
    source,
    zone_quaternion=None,
)

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

add_source(
    name,
    pool,
    park,
    position,
    pitch=None,
    interval=0.0,
    running=False,
)

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_spin method descriptor

add_spin(name, signal, robot, link)

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. Binds a spin effect to a signal: while the signal is true during playback, the studio turns link of robot about its own Z — a screwdriver's bit running a screw down — and draws nothing else. Pure presentation, like add_weld_flash; USD export carries nothing for it.

add_spray_cone method descriptor

add_spray_cone(
    name, signal, robot, length=0.25, radius=0.08
)

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,
    reverse_speed=None,
    prefer="forward",
    arrive=None,
    turn_radius=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

add_weld_flash(name, signal, robot)

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,
    watch_groups=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). watch_groups=[(robot, group), ...] watches the named arms only — the interlock zone that sees one arm of a dual-arm robot and not the other.

allow_inter_robot_collision method descriptor

allow_inter_robot_collision(
    robot_a, link_a, robot_b, link_b
)

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(link, obstacle, robot=None)

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.

allow_object_obstacle_contact method descriptor

allow_object_obstacle_contact(
    object, obstacle, window=None
)

Lets the obstacle object, while a robot carries it, meet obstacle without counting as a collision — the part being fitted: a cover set down on its housing and dowels, a screw in the hole it is driven into. Without window the pair may meet anywhere; with window = (point, axis, radius, angle) only while the carried object's origin lies within radius (m) of the line through point along axis and its own +Z within angle (rad) of that axis — the hole's axis and tolerance, so a screw off its hole is still a collision. Applies to checking, planning and min_clearance; toolpath rapids ignore every allowance. Re-declaring a pair replaces its window.

animate_carve method descriptor

animate_carve(
    timeline,
    stock,
    stages=None,
    chord_tolerance=1e-05,
    max_deviation=1e-05,
    tile_size=None,
    cutter_radius=0.004,
    cutter_length=0.03,
    dt=0.01,
    robot=None,
    tcp_link=None,
    voxel_size=None,
)

Progressive material removal for a baked cycle: re-cuts the sweep 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). The stock is kept as tiles; at the first cut every tile is registered as a display-only obstacle (grouped under {stock}_cut/…, cheap AABB colliders — they never collide), and afterwards only the tiles a slice changed get a new version, so idle stretches and untouched regions cost nothing. 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.

The cut is computed with mesh booleans (cutrail): chord_tolerance (m) is the polygonization of the cutter and max_deviation (m) how far a sampled TCP may stray from the segment it is folded into; tile_size (m) defaults to 2.5 cutter diameters. voxel_size is deprecated and ignored.

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

attach(
    name,
    link=None,
    touch_links=None,
    robot=None,
    group=None,
)

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). group names the arm: link=None is then that arm's tip.

auto_assign_io method descriptor

auto_assign_io(sequences=None, reassign=False)

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

bom()

The bill of materials derived from the scene: robots and their tools (catalog identity when loaded from the catalog), the controller every arm needs (<robot>/controller, until a robot_controller node declares it), 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.

brush method descriptor

brush(name)

{"applicator", "flow", "lead", "lag"} of a declared brush.

camera_fov method descriptor

camera_fov(name)

The horizontal field of view (degrees) of the named camera — what a picture channel's default decimation cell is sized from.

cell_report method descriptor

cell_report(
    timelines=None,
    *,
    scenarios=None,
    deliverables=None,
    clearance_dt=Ellipsis,
    title=None,
    ground_z=0.02,
    sequences=None,
    sections=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. sections are what a domain module contributes — dicts with title, markdown and json (bt.assembly.report_section) — rendered under their titles before the deliverables. For a common snapshot, fresh bakes and verified files, use bt.export_cell. A reading surface — pytest keeps the asserts.

check method descriptor

check(
    *,
    sequences=None,
    timeline=None,
    cable_slack_m=1.0,
    service_clearance_m=0.9,
)

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

check_collisions()

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

clear_toolpath_marks(name)

Clears the marks a check left on toolpath name.

crouch_pose method descriptor

crouch_pose(depth, lean=0.0, robot=None)

The posture a mounted walker takes crouched depth metres below its standing height and leaned lean rad nose-down, where it stands now (its current base pose, taken as standing): (base_pose, joints), with base_pose as (position, quaternion_xyzw). Each leg of its gait is solved so its foot stays where the stance puts it — the same solve bt.seq.crouch runs every scan tick, so an arm pose taught on this posture (set_robot_base_pose(*base_pose), set_joint_positions(joints), then IK) is the one the bake reaches. The scene is not changed. Raises ValueError naming the leg that cannot keep its foot planted.

declare_io method descriptor

declare_io(
    name,
    role=None,
    kind=None,
    safety=False,
    pair=None,
    note=None,
)

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

define_applicator(name, applicator)

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

define_brush(name, applicator, flow=1.0, lead=0.0, lag=0.0)

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

define_signal(name, initial=False)

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

detach(name)

Detaches an obstacle; its pose freezes where the robot holds it.

disallow_link_obstacle_contact(link, obstacle, robot=None)

Removes an allowed-contact entry added by allow_link_obstacle_contact.

disallow_object_obstacle_contact method descriptor

disallow_object_obstacle_contact(object, obstacle)

Removes an allowed object contact added by allow_object_obstacle_contact.

export_bom method descriptor

export_bom(path, format=None)

Writes the BOM to path; the format follows the extension (.csv, .md, .json) unless format says otherwise.

export_interlocks method descriptor

export_interlocks(path, sequences=None)

Writes interlocks() to path; the format follows the extension (.md, .csv, .json).

export_io_list method descriptor

export_io_list(path, sequences=None)

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

export_plcopen(
    path,
    sequences=None,
    *,
    name="cell",
    cycle=True,
    task_interval_ms=10,
)

Writes plcopen() to path (.xml).

export_topology method descriptor

export_topology(
    path,
    sequences=None,
    layers=None,
    include_cosmetic=False,
)

Writes io_topology to path; the format follows the extension (.mmd / .md Mermaid, .dot / .gv Graphviz, .json).

export_usd method descriptor

export_usd(
    path,
    trajectory=None,
    fps=60.0,
    robot=None,
    physics=None,
)

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.

physics= writes the static cell as a simulation stage instead — the world a physics engine can own (Isaac Sim / Isaac Lab), in UsdPhysics: robots as articulations (links with mass and colliders, joints with limits and drives), the dynamic units of physics_plan() as rigid bodies, what a device moves as kinematic bodies, everything else that collides as static colliders, a conveyor's belt with a surface velocity, the ground as a plane. The vocabulary is a bake's: True is the declared scope, bt.Physics(world=True) the whole cell. Collision follows enabled, the picture follows visible (an invisible collision proxy is authored as a guide). It does not combine with a trajectory: an animation and a simulation would fight over the same prims.

footprint method descriptor

footprint(ground_z=0.02)

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

frame(name)

Pose of a named frame as (position, quaternion_xyzw) — e.g. scene.set_robot_base_pose(*scene.frame("/World/mount")).

generate_python method descriptor

generate_python(*, embed_catalog=False)

Generates a Python script that rebuilds this scene with the botrail API (same content as the studio's "Export Python"). embed_catalog=True uses captured catalog sources without Hub access. Mesh/USD paths must exist; load a portable project first to extract them.

grasp_close method descriptor

grasp_close(
    name,
    robot=None,
    joints=None,
    closed=None,
    clearance=None,
)

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

interlocks(sequences=None)

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

io_list(format='csv', sequences=None)

The I/O list as text: format is "csv", "md" (Markdown table) or "json" (raw fields, step indices included).

io_map method descriptor

io_map()

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

io_points(sequences=None)

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

io_report(sequences=None)

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

io_topology(
    format="mermaid",
    sequences=None,
    layers=None,
    include_cosmetic=False,
)

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_positions_of(name)

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

lidar_scan(name, t=None, noise=0.0, seed=0)

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(link_name, robot=None)

World pose of a link as (position, quaternion_xyzw).

link_pose_at(link_name, joints, robot=None)

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

load_project(path)

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

load_usd(path, prefix=None, search_paths=None)

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; Camera prims become world-fixture cameras (see add_camera) with the authored optics, whatever their visibility. Obstacle, frame and camera names are the prim paths, optionally prefixed. Returns the added obstacle names.

min_obstacle_distance method descriptor

min_obstacle_distance(*, to=None)

Minimum robot-obstacle distance (0 when colliding); None without obstacles. to=[...] measures against only those obstacles — each a name or a group it sits in ("guard" for every guard/...); a name that matches nothing raises.

motion_segments method descriptor

motion_segments(name)

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,
    *,
    wheels=None,
    carrier=None,
    flange=None,
    mount=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).

With wheels (a bt.Wheels) the robot is the vehicle's running gear — a humanoid upper body on a wheeled base, modelled whole: its wheel joints turn by exactly what the vehicle drives, the machine is one purchase, and a walkable surface under it is where it rolls, not a collision. The robot is put in the wheels' posture, and the offset defaults to the one that stands its base frame (or its wheels) on the vehicle frame.

With both, the robot is a wheel-legged machine — wheels for feet, one hanging from each foot link of the gait, the wheel radius for its foot_radius. The wheels' mode says which gear takes the route: "roll" (the legs held in the stance with the wheels' posture laid over it, every wheel turned by its hub's travel) or "walk" (the wheels locked, the axles planted like feet).

carrier records the loaded vehicle model's flange frame and catalog provenance for bt.mounting.report(scene). With no offset, the first mount-side allowed_poses entry is used, or the mounting faces coincide if none is declared. An explicit offset is kept and reviewed, not corrected. flange / mount select exact model links; defaults are the carrier flange and arm mount (or arm root). This is a catalog-frame alignment check, not a mechanical fit certification.

obstacle_bounds method descriptor

obstacle_bounds(name)

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

obstacle_color(name)

An obstacle's display colour as linear RGB, or None when it has none and the viewer picks the shading.

obstacle_enabled method descriptor

obstacle_enabled(name)

Whether an obstacle takes part in collision checking and physics (set_obstacle_enabled) — what a pool draw parks things out of.

obstacle_finish method descriptor

obstacle_finish(name)

The surface pattern ("wood", "checker_plate", "plastic") the obstacle's material carries, or None.

obstacle_material method descriptor

obstacle_material(name)

(metalness, roughness), or None when the obstacle has no authored material.

obstacle_opacity method descriptor

obstacle_opacity(name)

Explicit alpha override, or None when the source/viewer supplies it.

obstacle_pose method descriptor

obstacle_pose(name)

World pose of an obstacle as (position, quaternion_xyzw).

obstacle_visible method descriptor

obstacle_visible(name)

Whether an obstacle is drawn (set_obstacle_visible).

open_rollout method descriptor

open_rollout(
    sequences,
    dt=0.01,
    max_duration=120.0,
    plan_resolution=None,
    scenario=None,
    toolpath_spin=None,
    physics=None,
)

Opens the rollout simulate_sequences would bake, stopped at t = 0 with every program in its first step, for Python to advance one scan tick at a time (design-rl.md R0). Between ticks the LiveRollout reads the world — joints, link and object poses, object velocities, signals, this tick's contacts — and drives a robot's joints toward a rate-limited target; the PLC programs, devices, sensors and physics run exactly as in the batch bake. sequences is one name or a list. finish() yields the ordinary timeline (and broadcasts it to connected studios).

part method descriptor

part(name)

The part pinned to name as a dict (target, kind, catalog, manufacturer, model, category, description, qty, attributes), or None.

parts method descriptor

parts()

Every pinned part, in authoring order (see part() for the shape).

physics_plan method descriptor

physics_plan(physics=None)

What a physics bake would hand the engine, before running it: the ground, every rigid unit (its obstacles, fixed or dynamic, the rule that decided it, its mass) and the count of obstacles mirrored individually. The default audits the whole cell (bt.Physics(world=True)); physics=True audits the declared scope. Robots are not listed — their lowering is the declaration's (set_robot_physics).

place_vehicle method descriptor

place_vehicle(name, station)

Places the vehicle name at station as if it had been authored there: its body moves, what rests in its tray rides along, the robots mounted on it stand on it there (holding what they hold), and the station becomes the one a bake starts from. This is how a mounted robot is taught where it works: set_robot_base_pose moves the robot alone and leaves the vehicle's body at the start, so a part in hand meeting the body only shows in the bake. Place it back at the station it started at to undo.

plan method descriptor

plan(
    goal,
    max_iters=10000,
    seed=None,
    broadcast=True,
    robot=None,
    group=None,
)

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. group names the arm to plan: only its joints are searched, every other joint holds. goal may then be given in the group's own joint order. A robot with one group needs no group.

plan_motion method descriptor

plan_motion(motion, seed=None, broadcast=True)

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,
    group=None,
)

IK to the given pose, then plan to the found configuration. group names the arm (its tip is the default link, and only its joints move); a link alone names the arm it hangs off.

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

play_usd_animation(
    path, force_transforms=False, robot_roots=None
)

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

plcopen(
    sequences=None,
    *,
    name="cell",
    cycle=True,
    task_interval_ms=10,
)

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

remove_frame method descriptor

remove_frame(name)

Removes a named frame.

remove_io_node method descriptor

remove_io_node(name)

Removes a node and every binding on it.

remove_motion method descriptor

remove_motion(motion)

Removes the motion itself, name and all (clear_motion leaves an empty motion listed — and in the project and the hand-over set). A sequence that starts it stops validating (unknown motion) until a motion of that name is authored again.

remove_part method descriptor

remove_part(name)

Unpins the part on name.

rename_robot method descriptor

rename_robot(robot, name)

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

requirements(
    *,
    sequences=None,
    margin=0.1,
    timeline=None,
    cable_slack_m=1.0,
)

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

robot_base_pose_of(name)

World base pose of the robot instance named name.

robot_of method descriptor

robot_of(name)

The model of the robot instance named name.

robot_physics method descriptor

robot_physics(robot=None)

Whether the named robot (or the only one) is declared dynamic.

save_project method descriptor

save_project(path)

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

scan_sweep(name, fps=10.0, noise=0.0, seed=0)

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

sequence(name)

Starts (or replaces) a PLC-style sequence and returns a builder: scene.sequence("pick").step("run", actions=[bt.seq.motion("go")]).

set_camera_pose method descriptor

set_camera_pose(name, position, quaternion=None)

Moves a camera: its position and quaternion (xyzw) in its mount frame (the world for a fixture, the link for a wrist camera) — a domain-randomisation hook for the picture channels.

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(
    link, friction=None, restitution=None, robot=None
)

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

set_obstacle_color(name, color)

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

set_obstacle_enabled(name, enabled)

Includes/excludes an obstacle from collision checking (it keeps rendering in the studio either way).

set_obstacle_legend method descriptor

set_obstacle_legend(name, title='', stops=None)

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

set_obstacle_material(
    name,
    metalness=None,
    roughness=None,
    *,
    opacity=None,
    finish=None,
)

Sets how an obstacle's surface takes light. Passing no appearance values clears the material, handing the choice back to the viewer. finish — "wood", "checker_plate" or "plastic" — is a surface pattern the studio draws over the colour (timber grain, the raised bars of tread plate, moulded plastic); on its own it also picks the two knobs that finish reads as. Pictures a rollout renders and a USD export keep the colour and the knobs and ignore the pattern.

set_obstacle_visible method descriptor

set_obstacle_visible(name, visible)

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_visual_asset method descriptor

set_obstacle_visual_asset(
    name, path, prim_path, transform, color_override=False
)

Sets the display gprim independently of the obstacle's collision shape. The column-major transform maps source coordinates into obstacle-local metres. Used by generated Python to reproduce imported USD appearance.

set_obstacle_walkable method descriptor

set_obstacle_walkable(name, walkable=True)

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

set_robot_base_pose(position, quaternion=None, robot=None)

Places the robot root at the world-frame pose and pushes the new state to connected studio clients.

set_robot_physics method descriptor

set_robot_physics(
    robot=None,
    dynamic=True,
    max_force=None,
    damping=None,
    mass_floor=None,
    armature=None,
    floating=None,
)

Declares a robot dynamic under physics bakes (design-rl-dynamics.md): every link with geometry becomes a rigid body weighing what its model states (URDF <inertial>, USD PhysicsMassAPI; else its shape at the default density, at least mass_floor kg), every joint a force-capped servo following the planned motion, and the baked track is the physical joint state read back each scan. The base assembly stays on its stand (or vehicle). Without a physics backend the declaration is inert; dynamic=False removes it.

  • max_force — servo force cap in N·m (revolute) / N (prismatic); default each joint's URDF effort limit, which must then exist.
  • damping — the velocity loop's gain (default the cap per 0.02 rad/s or 2 mm/s of velocity error).
  • mass_floor — least mass of a link without inertials (default 0.2 kg).
  • armature — reflected drive inertia per joint, kg·m² revolute / kg prismatic (default 0.1 / 10): a geared motor's rotor through the gear ratio squared, which also keeps a light wrist from drooping under the impulse-solved motor.

  • floating — the base's kind under physics: True a free rigid body (it falls, tips and collapses with the rest — a walker on its feet, a multirotor), False a mirror on its stand or vehicle whatever the scope, None (default) lets the bake derive it: a world-scope bake (bt.Physics(world=True)) floats walkers and aircraft, the declared scope floats nothing.

A dynamic robot is what botrail.rl.Torque drives.

set_tcp_target method descriptor

set_tcp_target(
    position,
    quaternion=None,
    link=None,
    max_iters=100,
    robot=None,
    group=None,
    centering=None,
)

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. group names the arm (its tip is the default link; only its joints move); a link alone names the arm it hangs off. centering is how strongly a redundant arm's free joints are drawn toward mid-range once the pose is met (default 0.1): 0 leaves them where the current configuration has them, so the answer lands next to it — teaching a pose from a near one.

set_vehicle_wheel method descriptor

set_vehicle_wheel(
    vehicle,
    object,
    *,
    radius,
    axis=Ellipsis,
    pivot=Ellipsis,
    lateral_ratio=0.0,
)

Rotates a disabled wheel visual from the vehicle's travelled distance. Axis and pivot are in the visual's local frame; radius is in metres. A mecanum wheel uses lateral_ratio=-1 or +1 for its roller handedness.

show_film method descriptor

show_film(film, name=None)

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.

show_timeline method descriptor

show_timeline(timeline)

Makes timeline the session's latest bake again — what a studio opened afterwards replays and downloads — after later bakes (a scenario matrix, a sweep) have taken its place. The timeline carries its own scene snapshot, so nothing is re-baked; connected studios receive it as a sequence_result.

simulate_physics method descriptor

simulate_physics(
    duration, dt=0.01, scenario=None, physics=None
)

Bakes duration seconds of the cell with no program at all: devices parked, robots holding their pose, and the physics engine settling, dropping or collapsing whatever it owns. The default is the whole cell (bt.Physics(world=True), ground at z = 0): what is unsupported falls, what rests stays, what is bolted down (set_physics(dynamic=False), device-driven, walkable, an equipment part pin) never moves. Pass physics=bt.Physics(...) to choose otherwise (physics=True is the declared scope). The result is an ordinary timeline — studio playback, USD export and pose queries all work — broadcast to the studio like any bake. scenario applies a named initial-state delta first.

simulate_scenarios method descriptor

simulate_scenarios(
    names,
    scenarios=None,
    dt=0.01,
    max_duration=120.0,
    plan_resolution=None,
)

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,
    policies=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,
    policies=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

timeline_from_trajectory(
    trajectory, robot=None, label="trajectory"
)

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

unbind_input(name, node=None)

Drops the binding of an input point — on node, or everywhere.

unbind_output method descriptor

unbind_output(name, node=None)

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

aspect

The facet for device commands and robot handshakes ("index", "dispatch", "station", "position", "speed", "start", "done", "program"), or None.

direction property

direction

"input" or "output", from the host's side.

host property

host

The controller that owns the point: "<cell>", "<robot name>" (implicit placement) or a declared node; None when nothing pins it.

kind property

kind

Channel type: "DI", "DO", "Word", "AO", ...

label property

label

name or name.aspect — the label the tables use.

name property

name

The scene name the point belongs to (signal, sensor, device, robot).

readers property

readers

(sequence, flat step index, step name) of the steps that read it.

safety property

safety

source property

source

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

status

"unbound", "internal" (a relay, no I/O), "cosmetic" (magazine), "constant" (a coil that is on from t = 0 and never commanded).

writers property

writers

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

findings property

findings

Every finding, most severe first.

ok property

ok

True when there are no errors.

to_json method descriptor

to_json()

The findings as JSON: {"ok": bool, "findings": [{"severity", "code", "message", "at": [[sequence, step index, step name], ...]}]}.

IoFinding

One entry of an IoReport.

IoFinding

at property

at

The steps the finding is attributed to, as (sequence, flat step index, step name).

code property

code

The finding code ("name_clash", "unreferenced", "word_unexpressible", "implicit_host", ...).

message property

message

severity property

severity

"error", "warning" or "info".

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

bindings property

bindings

(label, direction, node, channel) per binding.

decls property

decls

Declared names.

nodes property

nodes

Node names, in declaration order.

to_json method descriptor

to_json()

The layer as JSON — the same form the .botrail project stores.

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

rows

The rows as dicts: category, names (the residents the row stands for), manufacturer, model, catalog (id@revision), qty, description, attributes.

attribute_keys method descriptor

attribute_keys()

Every attribute column any row carries, sorted.

save method descriptor

save(path, format=None)

Writes the table to path; the format follows the extension (.csv, .md, .json) unless format says otherwise.

to_json method descriptor

to_json()

{"rows": [...], "totals": {...}}.

total method descriptor

total(key)

Σ 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

unidentified()

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

io_error

Why hosts and addresses are missing, when the I/O map could not be derived; None when it could.

rows property

rows

One dict per output: program, host, step, kind, target, output, condition, after (the predecessor steps) and inputs (name, kind, written_by, address).

sequences property

sequences

The program set the table was taken over.

save method descriptor

save(path, format=None)

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.

bom property

bom

BOM totals: rows, unidentified, by_category, totals.

cycles property

cycles

The cycles passed in: name, sequences, scenario, duration, steps (name, sequence, start, end), robots (robot, busy, utilization), clearance (distance, t, pair) and branches.

deliverables property

deliverables

The hashed deliverables: path, sha256, bytes.

footprint property

footprint

The plan-view footprint: min, max, width, depth, area, height.

io property

io

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

io_error property

io_error

machines property

machines

The machine tools: name, category, manufacturer, model, catalog, door (name, drive, stroke_mm, speed, lanes, driven), buttons, controller, programs.

robots property

robots

The robots: name, dof, base, and catalog / manufacturer / model / reach when the catalog knows them.

scenarios property

scenarios

The scenario matrix: name, ok, duration, error.

sections property

sections

The contributed sections (sections=): title, markdown, json.

title property

title

cycle_time method descriptor

cycle_time(name=None)

The cycle time of name (or of the first cycle), or None.

min_clearance method descriptor

min_clearance()

The tightest clearance over every cycle that measured one, or None.

save method descriptor

save(path, format=None)

Writes the report to path; the format follows the extension (.md, .json) unless format says otherwise.