Skip to content
botrailPublic

About

Next generation motion planning package. Build robot cells as code.

Topics

Resources

Contributing

Stars

31 stars

Watchers

2 watching

Forks

Repository files navigation

botrail-logo

Beyond motion planning. Build robot cells as code. One source of truth from layout to I/O list — verified deterministically on every change.

Documentation: https://botrail.github.io/botrail/ · Live Demo: https://botrail.github.io/botrail/demo/

Two FANUC palletizers stacking cases off roller conveyors onto pallets, beside a stretch wrapper and a pallet dispenser

A Mitsubishi RV-5AS milling a clamped plate with a spindle, toolpath overlaid
Robot machining — spindle toolpaths with stepwise stock removal
Ceiling-mounted FANUC arms and SCARAs working over red carriers on a linear-motor shuttle loop
Assembly line — a linear-motor shuttle loop serving ceiling-mounted arms and SCARAs
A quadruped carrying a box up a steel stair flight to a mezzanine
Legged mobility — a quadruped climbs a catalog stair flight, payload on board
A G1 humanoid carrying a part along a planned walking path in a fenced cell, its head-camera view inset
Humanoid finishing line — walking is just another sequence step; inset, the robot's head camera
Four FANUC arms welding car bodies along a transfer line inside guarding
Multi-robot weld line — four stations, one deterministic timeline

pip install botrail, a few lines of Python, and you get an interactive 3D studio in your browser for building robot cells — robots, obstacles, conveyors, sensors, and PLC-style sequences. The core is written in Rust — no ROS, no system dependencies, no GPU.

A cell in botrail is text (Python / .botrail JSON / USD): it diffs in git, it bakes into a bit-identical timeline every run, and it regression-tests in CI. Motions are planned, not taught point by point, so moving a pallet or a sensor doesn't break the cell — re-simulate and read the new cycle time.

Highlights

  • Robots from URDF, Xacro, or USD — including Isaac Sim articulations (bt.Robot.from_usd("franka.usd")), rendered at full visual fidelity with three-usd-robot. Mimic joints (URDF <mimic>, USD NewtonMimicAPI / PhysxMimicJointAPI) are followed, so a two-finger gripper costs one DOF, not two. Multiple robots per cell, with tick-checked inter-robot collisions and zone interlocks.
  • USD scene import (usda/usdc/usdz, references, variants, instancing) — stages become obstacles and named mount frames, normalized to meters / Z-up.
  • Environments that behave — a PLC-style step sequencer (entry actions + transition conditions on a fixed scan cycle), zone/beam sensors, conveyors and linear axes, and conveyor tracking: taught poses ride the moving part, so the belt never stops for the pick.
  • Deterministic bake — simulate_sequence() turns Scene + Sequence into a bit-identical SequenceTimeline: cycle time, step spans, signal waveforms, object tracks.
  • Assertable timelines — step_span() / signal() / min_clearance() turn a bake into pytest-able cell checks (cycle budgets, sensor timing, safety margins) that run in CI.
  • Open deliverables — USD animation (plays in usdview / Omniverse / Blender), CSV/JSON, robot programs (URScript), Python code generation — and Isaac Sim recordings play back through the same pipeline.
  • Engineering documents, derived — bill of materials, plan-view layout sheet (SVG / DXF), I/O list and controller topology, and a cell report (cycle times, clearance, I/O counts, scenario matrix, footprint, file digests) all come out of the same script as the simulation, so a layout edit changes exactly the documents it touches — and never lets them disagree.
  • Selection, checked — not chosen — scene.requirements() derives what every BOM line must be able to do (payload from the grasped parts, reach from the taught targets, a beam's span, a conveyor's load...) and compares it with what the chosen part says; bt.catalog.search finds real products that satisfy it. A part that falls short is an error, a part that does not say is a warning, a line nobody has identified becomes the question to ask a vendor.
  • Interactive posing — draggable TCP gizmo with live IK, joint sliders.
  • Collision checking — primitives and STL/OBJ meshes (cached VHACD convex decomposition), live highlighting, clearance readout.
  • Motion planning & authoring — RRT-Connect with time parameterization, waypoint motions with Cartesian-line segments and path constraints, trajectory playback in the studio.
  • Portable projects — save/load .botrail files (meshes and USD stages bundled), regenerate any scene as a Python script.
  • Runs entirely in the browser — the wasm build serves the full studio as a static page, no server; drop a USD file straight into the viewport.

Try it

Run the bundled demos — a Franka Panda in a small USD factory cell whose belt, rack and guarding are ordered from the model catalog (the first run downloads NVIDIA's official Franka asset, ~10 MB, and the catalog packages; pip install botrail[catalog] for those):

python examples/basics/demo.py --studio  # interactive studio: pose, plan, play
python examples/basics/sequence_demo.py --studio  # 13-step cell: conveyor feed → tracked pick
                                  # → pallet; prints the cycle time, exports USD
python examples/multi_robot/dual_cell_demo.py --studio  # two arms sharing one infeed, arbitrated by a
                                  # zone interlock; --clash shows what happens
                                  # without it
python examples/basics/sweep_demo.py --studio  # parameter sweep: belt speed × lane position
                                  # vs cycle time and clearance (no downloads)
python examples/engineering/cell_deliverables_demo.py --studio  # the whole document set from one
                                  # script: layout SVG/DXF, BOM, I/O list, robot
                                  # program, USD, cell report (no downloads)
python examples/engineering/equipment_cell_demo.py --studio  # fence, conveyor and rack ordered from
                                  # the catalog: a bill with real part numbers,
                                  # and each drawn from the package's own file

Or try the browser-only build (deployed from main, or build it locally):

./scripts/build_wasm_demo.sh          # needs wasm-pack + wasm32 target
python -m http.server -d studio/dist-wasm 8899

Quickstart

import botrail as bt

robot = bt.Robot.from_urdf("robot.urdf")   # or from_xacro(...) / from_usd(...)
scene = bt.Scene(robot)

scene.load_usd("cell.usda", prefix="env")                # obstacles + frames
scene.set_robot_base_pose(*scene.frame("env/World/mount"))
scene.add_box("table", size=(0.6, 0.6, 0.05), position=(0.4, 0.0, 0.0))

bt.studio(scene)  # opens the 3D studio in your browser

Everything you do in the studio is mirrored in Python, and vice versa:

scene.set_tcp_target((0.3, 0.1, 0.5))         # live IK, pushed to the browser
scene.in_collision()                          # False
scene.min_obstacle_distance()                 # clearance in meters

traj = scene.plan_to_pose((0.4, 0.1, 0.3))    # IK, then RRT-Connect + time param
traj.export_csv("motion.csv", dt=0.008)

scene.save_project("cell.botrail")            # meshes/USD bundled when needed
print(scene.generate_python())                # script reproducing the scene

Verify the cell, not just the trajectory

Give the environment behavior, write the process as steps, and bake:

scene.add_box("crate", size=(0.04, 0.04, 0.04), position=(-0.5, 0.6, 0.3))
scene.add_conveyor("belt", zone_position=(-0.2, 0.6, 0.3),
                   zone_size=(1.2, 0.3, 0.3), velocity=(0.25, 0.0, 0.0),
                   running=False)
scene.add_beam_sensor("eye", frm=(0.0, 0.4, 0.3), to=(0.0, 0.8, 0.3))
scene.add_segment("approach", goal=[0.6, -0.5, 0.8, 0.0, 0.4, 0.0])

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

tl = scene.simulate_sequence("cycle")   # deterministic: bit-identical every run
print(tl.duration)                      # cycle time in seconds
tl.export_usd("cycle.usda", fps=60)     # replay in usdview / Omniverse / Blender

Because the bake is deterministic, the same numbers are regression tests — the workflow botrail exists for:

def test_cell_cycle():
    tl = build_cell().simulate_sequence("cycle")
    assert tl.duration <= 8.0                # cycle-time budget
    assert tl.step_span("feed").end <= 2.0   # the crate arrives on time
    assert tl.signal("eye").rising_edges()   # the handshake happened
    assert tl.min_clearance() > 0.05         # closest approach, meters

Move the beam sensor 0.25 m downstream and the cycle grows by exactly 1.0 s — a layout edit becomes a failing test instead of a shop-floor surprise. This repository runs such a cell in its own CI (python/tests/test_cell_regression.py), and examples/basics/sweep_demo.py runs the same loop as a parameter study — bt.sweep bakes the cell over a grid and tables it (belt speed moves the cycle; lane position eats the clearance), bt.optimize searches the grid for the fastest cycle that keeps its clearance — with no random number anywhere.

Hand over the cell

The documents a cell is delivered as come out of the same script — none of them is typed in beside the model:

scene.set_part("belt", manufacturer="MISUMI", model="GVL-1200")   # what things *are*
scene.export_bom("bom.csv")               # bill of materials, merged and counted
scene.export_layout("layout.dxf")         # plan-view sheet for the 2D CAD (.svg for the review)
scene.export_io_list("io.csv")            # I/O list for the electrical drawing
tl.export_script("cell.script")           # the robot program, with the same DI/DO numbers

report = scene.cell_report({"cycle": tl}, deliverables=["bom.csv", "layout.dxf", "io.csv"])
report.save("cell_report.md")             # cycle time, clearance, I/O, BOM totals,
                                          # footprint — and the SHA-256 of each file

Because they are derived, they cannot disagree with each other or with the bake, and a layout edit changes exactly the documents it touches: examples/engineering/cell_deliverables_demo.py writes the whole set, and python/tests/test_deliverables.py pins which files a moved sensor or an added fence panel changes — by name.

The same loop runs without writing Python — the entry an agent's iteration and a CI job share:

botrail check cell.py                                  # load, lint, count → JSON (exit 1 on errors)
botrail simulate cell.py --scenarios --report r.json   # bake the matrix → the cell report
botrail export cell.py --out deliverables/ --all       # the whole document set, hashed into the report
botrail schema > project.schema.json                   # the .botrail JSON Schema, from the Rust types

Development

Requirements: Rust (stable), Python >= 3.9, maturin, uv, Node 20+ with pnpm.

./scripts/build_studio.sh                 # build the studio UI into the package
uv venv .venv && source .venv/bin/activate
maturin develop --uv
python examples/basics/demo.py --studio

Tests:

cargo test                                # Rust workspace
python -m pytest python/tests             # Python bindings

Docs (mkdocs, published at the link above):

uv pip install --group docs
mkdocs serve                              # needs `maturin develop` first

Contributor notes are in the Contributing page.

License

botrail is source-available under your choice of the PolyForm Small Business License 1.0.0 or the PolyForm Noncommercial License 1.0.0. Use not permitted by either license requires a separate commercial license from UnRobotics Inc.. See LICENSE for details.

Redistributions must preserve these notices:

Required Notice: Copyright (c) 2026 k-tanaka and botrail contributors.

Required Notice: botrail is licensed by UnRobotics Inc. (https://www.un-robotics.com/).

About

Next generation motion planning package. Build robot cells as code.

Topics

Resources

Contributing

Stars

31 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages