Skip to content

Standard parts (bt.parts)

Generators for the structures every cell has — fences, walls, tables, pedestals, racks, conveyor bodies, pallets, light curtains, stairs, a machining centre with its door and panel — built from ordinary residents (boxes, frames, a device or a sensor) with their part identity pinned, so the BOM counts them and the layout sheet labels them. See Standard parts and CAD geometry.

bt.parts.fence(scene, "fence", path=[(-2, -2), (2, -2), (2, 2), (-2, 2)],
               height=2.0, panel_pitch=1.0, door=(0, 2), model="ST20")
ped = bt.parts.pedestal(scene, "pedestal", height=0.5, position=(0, 0))
scene.set_robot_base_pose(*scene.frame(ped.frames[0]))

parts

Standard structures, generated from parameters: fences, walls, tables, pedestals, racks, conveyor bodies, pallets, light curtains, stairs, control cabinets, a machining centre with its door, its panel and a vise — the scenery every cell has and nobody wants to model.

Each generator composes the ordinary scene API — add_box, add_frame, add_conveyor, add_beam_sensor, set_part — so what it builds is plain residents: boxes under a name prefix (fence/panels/n0, table/top), a frame where the next thing mounts, a device or a sensor where one belongs, and a part on the group with the quantity, so the BOM counts panels and posts and the layout sheet labels the assembly once. Change a parameter and the geometry, the BOM line and the sheet change together.

bt.parts.fence(scene, "fence", path=[(-2, -2), (2, -2), (2, 2), (-2, 2)],
               height=2.0, panel_pitch=1.0, door=(1, 1), model="ST20")
ped = bt.parts.pedestal(scene, "pedestal", height=0.5, position=(0, 0))
scene.set_robot_base_pose(*scene.frame(ped.frames[0]))

Pass catalog= instead of a model string and the parameters come from the catalog: the height is checked against the ones that are sold, the panels are laid out in widths that exist, and every line of the BOM carries the part number you would order it by.

bt.parts.fence(scene, "fence", path=[...], catalog="botrail/fence/mesh-guard",
               height=2.0, door=(0, 1))

botrail does not model shapes. These are boxes arranged by parameters, and that is the point: a fence is panels of a pitch along a path, a table is a top on legs — the meaning is what the BOM and the sheet need, and the few centimetres a real profile differs by change nothing a cell verifies. Anything with a shape of its own comes in from CAD as a mesh (see the Geometry Provider pattern in the standard-parts guide) and gets its identity the same way, with set_part.

BELT module-attribute

BELT = (0.1, 0.1, 0.11)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

BUTTON_BY_NAME module-attribute

BUTTON_BY_NAME = {
    "cycle_start": "green",
    "start": "green",
    "feed_hold": "red",
    "stop": "red",
    "estop": "red",
    "reset": "blue",
    "clamp": "yellow",
    "unclamp": "yellow",
    "door": "white",
}

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

BUTTON_CAP module-attribute

BUTTON_CAP = 0.0285

Convert a string or number to a floating-point number, if possible.

BUTTON_COLORS module-attribute

BUTTON_COLORS = {
    "green": (0.02, 0.35, 0.06),
    "red": (0.55, 0.02, 0.02),
    "yellow": (0.75, 0.55, 0.02),
    "blue": (0.02, 0.1, 0.45),
    "white": (0.8, 0.8, 0.78),
    "black": (0.02, 0.02, 0.02),
}

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

BUTTON_FORCE_N module-attribute

BUTTON_FORCE_N = 3.8

Convert a string or number to a floating-point number, if possible.

BUTTON_TRAVEL module-attribute

BUTTON_TRAVEL = 0.0026

Convert a string or number to a floating-point number, if possible.

CABINET module-attribute

CABINET = (0.58, 0.56, 0.49)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

CHECKER_PLATE module-attribute

CHECKER_PLATE = (0.5, 0.52, 0.53)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

CONCRETE module-attribute

CONCRETE = (0.4, 0.4, 0.39)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

DARK_STEEL module-attribute

DARK_STEEL = (0.2, 0.21, 0.23)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

DETAIL_MODES module-attribute

DETAIL_MODES = ('plain', 'full')

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

DOOR_DRIVES module-attribute

DOOR_DRIVES = ('manual', 'air', 'servo')

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

DOOR_SPEED module-attribute

DOOR_SPEED = {'servo': 1.0, 'air': 0.4}

dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

ESTOP_CAP module-attribute

ESTOP_CAP = 0.04

Convert a string or number to a floating-point number, if possible.

ESTOP_FORCE_N module-attribute

ESTOP_FORCE_N = 44.0

Convert a string or number to a floating-point number, if possible.

FENCE_FRAME module-attribute

FENCE_FRAME = (0.38, 0.4, 0.42)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

FENCE_PANEL module-attribute

FENCE_PANEL = (0.55, 0.58, 0.6)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

FENCE_POST module-attribute

FENCE_POST = (0.16, 0.17, 0.19)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

LATHE_APERTURE module-attribute

LATHE_APERTURE = (0.9, 0.7, 0.8)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

LATHE_CHAMBER module-attribute

LATHE_CHAMBER = 1.0

Convert a string or number to a floating-point number, if possible.

LATHE_CHUCK module-attribute

LATHE_CHUCK = 0.165

Convert a string or number to a floating-point number, if possible.

LATHE_SIZE module-attribute

LATHE_SIZE = (3.2, 1.78, 2.06)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

LATHE_SPINDLE module-attribute

LATHE_SPINDLE = (-0.55, 0.5, 1.05)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

LATHE_TURRET module-attribute

LATHE_TURRET = (0.4, 0.4, 0.45)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

MACHINE_ACCENT module-attribute

MACHINE_ACCENT = (0.8, 0.55, 0.03)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

MACHINE_BED module-attribute

MACHINE_BED = (0.24, 0.25, 0.27)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

MACHINE_SHELL module-attribute

MACHINE_SHELL = (0.62, 0.63, 0.62)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

MACHINE_WINDOW module-attribute

MACHINE_WINDOW = (0.05, 0.06, 0.07)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

MOTOR module-attribute

MOTOR = (0.24, 0.26, 0.3)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

PLASTER module-attribute

PLASTER = (0.62, 0.6, 0.56)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

REFLECTOR module-attribute

REFLECTOR = (0.86, 0.86, 0.8)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

SAFETY_ORANGE module-attribute

SAFETY_ORANGE = (0.91, 0.36, 0.02)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

STEEL module-attribute

STEEL = (0.42, 0.44, 0.47)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

TABLE_STEEL module-attribute

TABLE_STEEL = (0.46, 0.47, 0.49)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

TYPE_CHECKING module-attribute

TYPE_CHECKING = False

bool(x) -> bool

Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.

VMC_APERTURE module-attribute

VMC_APERTURE = (0.705, 0.869, 0.827)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

VMC_CHAMBER module-attribute

VMC_CHAMBER = 1.3

Convert a string or number to a floating-point number, if possible.

VMC_EXCHANGE module-attribute

VMC_EXCHANGE = (0.25, 0.0)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

VMC_FRONT_DOOR module-attribute

VMC_FRONT_DOOR = (0.73, 0.869, 0.827)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

VMC_HEAD_CLEARANCE module-attribute

VMC_HEAD_CLEARANCE = 0.58

Convert a string or number to a floating-point number, if possible.

VMC_SIZE module-attribute

VMC_SIZE = (1.615, 2.108, 2.137)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

VMC_TABLE module-attribute

VMC_TABLE = (0.65, 0.4, 0.9)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

WOOD module-attribute

WOOD = (0.52, 0.36, 0.18)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

Built

What a generator put into the scene, by name — the obstacles, and the frames, devices and sensors that came with them — so the caller can mount on the frame, drive the device, or take the whole thing down.

remove

remove(scene) -> None

Takes everything this generator added out of the scene (parts go with their residents).

MachineTool

Bases: botrail.parts.Built

What machine_tool built, plus the names a tending program addresses: the side door's axis (door, None for a manual door or none), what rides on that door (door_objects — the leaf and its trim, for a robot that slides it by hand), its end-of-travel lanes (door_lanes = closed, open — the axis's stop lanes, or two zone sensors on a loose leaf), the stroke and the world direction it opens along (door_travel, door_axis), the front door's closed switch and the E-stop lane the machine's program is guarded by (front_door_lane, estop), the operator panel's Built and the button sensors on it.

door_axis class-attribute

door_axis = (0.0, 1.0, 0.0)

Built-in immutable sequence.

If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.

If the argument is a tuple, the return value is the same object.

door_travel class-attribute

door_travel = 0.0

Convert a string or number to a floating-point number, if possible.

buildable_lengths

buildable_lengths(
    length_mm: float,
    widths_mm: Sequence[int],
    post_mm: float,
) -> list[int]

The edge lengths nearest length_mm that these panels can actually make — what to move a corner to when a run does not come out.

cabinet

cabinet(
    scene,
    name: str,
    size: Optional[tuple] = None,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    base: Optional[bool] = None,
    plate: Optional[bool] = None,
    base_height: Optional[float] = None,
    yaw: float = 0.0,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.58, 0.56, 0.49),
    **attributes,
) -> Built

A control cabinet: size = (width, depth, height) standing at position (its centre, x, y[, floor z]), door face on -Y before yaw. Adds the frame <name>/front at the centre of the door face at floor level — where an operator stands, and what a maintenance-space check will measure from — and pins the enclosure (structure.cabinet).

The panel builder's customisation is what this generator carries: the enclosure is the article (what is inside it is other people's BOM lines), and the plinth base and the mounting plate are articles of their own. base= stands the body on its plinth (<name>/base), plate= stands the mounting plate inside (<name>/plate) — each is one more line on the BOM when a catalog names it.

With catalog= — the id of a cabinet spec pack, or a package directory — an enclosure you can order: width, depth and height are matched against the sizes that are sold, the BOM row carries the article number they compose into, and base and plate default to whatever the pack sells (pass base=False / plate=False to leave them out). A combination nobody sells is refused by the pack's mass table.

detail="full" (the default with a catalog) draws the door leaves and their handles — or the pack's own drawing (trim:) — as decoration that never collides. The massing stays the body (and its plinth).

chuck

chuck(
    scene,
    name: str,
    position: tuple,
    quaternion: Optional[
        tuple[float, float, float, float]
    ] = None,
    *,
    diameter: float = 0.165,
    length: float = 0.085,
    jaws: int = 3,
    jaw_height: float = 0.03,
    jaw_width: float = 0.025,
    opening: float = 0.05,
    max_opening: Optional[float] = None,
    catalog: Optional[CatalogRef] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.2, 0.21, 0.23),
    **attributes,
) -> Built

A lathe chuck: a diameter body length long with its face at position, its axis the +Z of quaternion (pass a lathe's <name>/spindle frame — bt.parts.chuck(scene, "chuck", *scene.frame("lathe/spindle"))), and jaws jaw blocks standing jaw_height off the face around a part of opening diameter — the gripping diameter, so a robot loading a part along the axis meets the jaws where they are. Frame <name>/face: the face centre, +Z out along the spindle axis (a load comes in along -Z). Part: fixture.chuck with the diameter, the opening and the jaw count; with catalog= the diameter is matched against the ones sold and the maximum opening comes from the pack.

conveyor

conveyor(
    scene,
    name: str,
    length: Optional[float] = None,
    width: Optional[float] = None,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    direction: tuple = (1.0, 0.0),
    speed: Optional[float] = None,
    running: bool = False,
    zone_height: float = 0.15,
    belt_thickness: Optional[float] = None,
    rail: Optional[float] = None,
    legs: bool = True,
    leg: Optional[float] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.2, 0.21, 0.23),
    **attributes,
) -> Built

A belt conveyor: length along direction, width across, its belt surface centred at position (x, y, z of the surface). Builds the body — belt slab, two side rails, legs — as obstacles under <name>/, and the conveyor device <name> whose transport zone sits on the belt (zone_height tall, speed along direction). The part is pinned on the device (conveyor): the body is its geometry, not a second product. Adds the frames <name>/infeed and <name>/outfeed at the belt ends.

With catalog= — the id of a conveyor spec pack, or a package directory — a conveyor you can order: the length, belt width and stand height are matched against the ones that are sold (omit them and the catalog's defaults apply, so position may be given as (x, y)), the speed is checked against the range the drive covers, and the mass follows the length. The stands are spaced by the catalog's maximum span and land on the BOM as their own line.

fence

fence(
    scene,
    name: str,
    path: Sequence[tuple],
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    height: Optional[float] = None,
    panel_pitch: Optional[float] = None,
    post: Optional[float] = None,
    panel_thickness: Optional[float] = None,
    closed: bool = True,
    door: Optional[tuple[int, int]] = None,
    door_model: Optional[str] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    post_model: Optional[str] = None,
    panel_color: tuple = (0.55, 0.58, 0.6),
    post_color: tuple = (0.16, 0.17, 0.19),
    **attributes,
) -> Built

A safety fence along path (floor corners, metres), with a post at every corner and between panels. closed joins the last corner back to the first. door=(edge, slot) makes that slot the door — its own obstacle <name>/door and its own BOM line.

Without a catalog each edge is split into panels of about panel_pitch (the pitch is stretched so an edge takes a whole number), and two parts are pinned: <name> (the panels, qty = panels, with the model / manufacturer / mass_kg you passed) and <name>/posts.

With catalog= — the id of a fence spec pack, or a package directory — the fence is built out of panels that exist. height is checked against the heights that are sold, the widths come from the catalog and each edge is filled with the fewest of them that reach its length, and any catalog parameter can be set by name (mesh_mm="20x20"). The parts pinned are then <name> (the fence as one product, so the layout sheet still labels it once), one group per panel width carrying that width's part number and count, <name>/posts and <name>/door — a bill you can order from.

detail="full" (the default with a catalog) draws each panel the way it looks — a tube frame with a grid of wire in it, posts of the section the catalog sells, a plate under each — as decoration that never collides; the panel slab underneath still does, so nothing about the verification changes. detail="plain" is the bare massing. Returns the names it made.

lathe

lathe(scene, name: str, size: Optional[tuple] = None, position: tuple | tuple = (0.0, 0.0), *, yaw: float = 0.0, aperture: Optional[tuple] = None, door: Union[str, None, object] = <object object at 0x7f18d36928c0>, door_travel: Optional[float] = None, door_speed: Optional[float] = None, spindle: Optional[tuple] = None, chamber: Optional[float] = None, turret: Optional[tuple] = None, tailstock: bool = False, panel: Optional[str] = 'front', buttons: Optional[Sequence[str]] = None, panel_pitch: float = 0.045, wall: float = 0.06, catalog: Optional[CatalogRef] = None, detail: Optional[str] = None, model: Optional[str] = None, manufacturer: Optional[str] = None, color: tuple = (0.62, 0.63, 0.62), **attributes) -> MachineTool

A CNC lathe as the envelopes a tending cell verifies against — the turning counterpart of machine_tool. size = (length, depth, height) stands at position, front face on -Y before yaw, the spindle axis along the length (+X toward the tailstock). Without arguments it is the Haas ST-10 of the public spec pages (LATHE_* above); the front opening, the spindle's height and depth are design values, the first to replace when the drawing is at hand.

What it puts in the scene, all of it collision-checked:

  • the enclosure — bed, the rear block behind the chamber, roof, end walls, and the front wall around the door opening aperture = (width, height, sill), centred on the spindle;
  • the headstock and the spindle nose at spindle = (x from the body centre, depth behind the front wall's inner face, height), the turret envelope turret = (x, y, z size) right of it at spindle height, and a tailstock block at the far end when tailstock=True — the chuck is a part of its own (bt.parts.chuck(scene, "chuck", *scene.frame("<name>/spindle")));
  • the front door: a leaf that slides toward the tailstock end by door_travel. door="servo" / "air" make it a linear axis <name>/front_door with the stops closed and open, checked against every robot each tick; door="manual" (the default) leaves the leaf loose for a robot that takes the handle; door=None builds a solid front. Either way the lanes <name>/front_door/closed and /open read the leaf at its ends of travel;
  • an operator panel on the front face, right of the opening (panel="front"; None leaves it off).

Frames: <name>/spindle (the spindle nose centre, +Z out along the axis toward the tailstock — a load comes in along -Z), <name>/entry (the opening's centre, 150 mm outside the leaf), <name>/door/front/handle (+Z into the leaf), the panel's.

The returned MachineTool names the door axis, its lanes, the stroke and its world direction, the panel and its buttons, the E-stop lane — what bt.tending and a teach read. A lathe has the one door, so its front_door_lane is None and no door-exclusivity guard applies. Refused rather than clipped: an opening that does not fit the front, a stroke that runs the leaf off the body, a spindle outside the chamber.

With catalog= a lathe spec pack's mechanical.envelope (doors.front, spindle, turret, chamber_depth_mm) and the door drive it sells (front_door) stand in for the figures, its articles land on the bill, and its interface rides on the result.

light_curtain

light_curtain(
    scene,
    name: str,
    frm: tuple,
    to: tuple,
    *,
    height: Optional[float] = None,
    beam_height: Optional[float] = None,
    column: float = 0.04,
    watch_robot: bool = True,
    watch: Optional[list[str]] = None,
    catalog: Optional[CatalogRef] = None,
    resolution: Optional[float] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.16, 0.17, 0.19),
    **attributes,
) -> Built

A light curtain between two floor points: two mounting columns <name>/column_a|b of height (1.2 m unless given), and a beam sensor <name> at beam_height (half the height by default) spanning the gap between their lens faces — pulled in off the column centres, so the curtain is not born tripped by its own housings. With the defaults it trips on anything that enters the field, robot links and objects alike; watch=[...] with watch_robot=False narrows it to the named objects, watch=[] to robot links alone. The part (sensor.light_curtain) is pinned on the sensor; the columns are its mounting geometry.

With catalog= — the id of a light-curtain spec pack, or a package directory — a curtain you can order: height is the protective height and is matched against the ones sold, resolution (mm — the smallest object it must catch: 14 for a finger, 25 for a hand) picks the type, the columns take the maker's section, and the BOM row carries the model number of the emitter/receiver pair and its mass. A beam longer than the curtain's operating range is refused with the numbers — the same range_mm a requirement check would ask of it.

machine_tool

machine_tool(scene, name: str, size: Optional[tuple] = None, position: tuple | tuple = (0.0, 0.0), *, yaw: float = 0.0, aperture: Optional[tuple] = None, door: Union[str, None, object] = <object object at 0x7f18d36928c0>, door_side: Union[str, object] = <object object at 0x7f18d36928c0>, door_travel: Optional[float] = None, door_speed: Optional[float] = None, front_door: Union[tuple, None, object] = <object object at 0x7f18d36928c0>, chamber: Optional[float] = None, table: Optional[tuple] = None, exchange: Optional[tuple] = None, head_clearance: Optional[float] = None, panel: Optional[str] = 'front', buttons: Optional[Sequence[str]] = None, panel_pitch: float = 0.045, wall: float = 0.06, catalog: Optional[CatalogRef] = None, detail: Optional[str] = None, model: Optional[str] = None, manufacturer: Optional[str] = None, color: tuple = (0.62, 0.63, 0.62), **attributes) -> MachineTool

A vertical machining centre as the envelopes a tending cell verifies against — not its shape. size = (width, depth, height) stands at position (its centre, x, y[, floor z]), front face on -Y before yaw. Without arguments it is the FANUC ROBODRILL α-D21MiB5 Plus of the public catalogue (VMC_* above): change any figure and the envelopes, the frames and the BOM line change together.

What it puts in the scene, all of it collision-checked:

  • the enclosure — bed, side walls, roof, the rear column block (the last depth - chamber of the body), and a front wall around the front door opening (front_door = (width, height, sill)), with its leaf standing closed;
  • the table table = (width, depth, top height) at the exchange position (exchange = (x, y) offset from the chamber centre, x toward the door side — a table that traverses to the door is what a tending robot reaches), and the spindle head above it from head_clearance (nose to table at Z max) to the roof;
  • the side door: an opening aperture = (width, height, sill) in the door_side wall and a leaf that slides toward the rear by door_travel. door="servo" / "air" make it a linear axis <name>/side_door with the stops closed and open (bt.seq.move_to(door, "open") opens it, move_to(door, "closed") closes; the speed comes from the drive — door_speed overrides), and the rollout checks the leaf against every robot each tick: a door closing on an arm is a DeviceCollision by name. door="manual" leaves the leaf loose, for a robot that takes the handle (bt.seq.attach the door_objects and run a cartesian_line); door=None builds a plain wall. Either way the lanes <name>/side_door/closed and /open read the leaf at its ends of travel — the axis's stop lanes, or two zone sensors on a loose leaf — the limit switches a door interlock is written from;
  • an operator panel (operator_panel) with buttons at panel_pitch, on the front face (panel="front") or on the door-side wall ahead of the opening (panel="door", where a robot at the door reaches it); panel=None leaves it off.

Frames: <name>/table (centre of the table top), <name>/entry (the side opening's centre, 150 mm outside the door leaf — where a robot waits), <name>/door/side/handle (the leaf's handle, +Z into the leaf), and the panel's <name>/panel/<button>[/press].

Refused rather than clipped, like a wall plan that does not close: an opening that does not fit its wall, a leaf whose stroke runs off the body, a spindle head that would stand through the roof.

detail="full" adds the windows, the door rails, the accent band and the stack light — drawn, never collided. The part is pinned on the group (machine_tool.vmc), the side door as <name>/side_door (machine_tool.door, with its drive and stroke), the panel and its buttons by operator_panel.

With catalog= — the id of a machine-tool spec pack, or a package directory — a machine you can order: the body, the openings, the table and the head come from the pack's mechanical.envelope, the options it sells (column_mm, side_door, door_side) are chosen by name and refused when nobody sells them, the door's speed follows the drive's published time, every article lands on the bill with its number, and the pack's interface (its handshake template and signal table) rides on the returned MachineTool for bt.tending.

operator_panel

operator_panel(
    scene,
    name: str,
    position: tuple,
    *,
    yaw: float = 0.0,
    tilt: float = 0.0,
    size: tuple = (0.3, 0.22),
    thickness: float = 0.03,
    buttons: Sequence[str] = (
        "cycle_start",
        "feed_hold",
        "reset",
        "estop",
    ),
    columns: Optional[int] = None,
    pitch: float = 0.045,
    cap: float = 0.0285,
    travel: float = 0.0026,
    proud: float = 0.01,
    watch_robots: Optional[list[str]] = None,
    catalog: Optional[CatalogRef] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    button_model: Union[
        str, Mapping[str, str], None
    ] = None,
    color: tuple = (0.2, 0.21, 0.23),
    **attributes,
) -> Built

An operator panel: a plate size = (width, height) centred at position, its face toward -Y before yaw, tilted up toward the operator by tilt, with a grid of 22 mm pushbuttons on it.

A button is three things. A cap (decoration — drawn, never collided), a zone sensor <name>/<button> the size of the cap and as deep as the button's operating travel, sitting inside the cap face — so a tool that touches the cap reads nothing and one that pushes it in the 2.6 mm a 22 mm actuator travels turns the input on, for as long as it is held — and two frames: <name>/<button> on the cap face and <name>/<button>/press the travel below it, both with +Z pointing into the panel, which is where a pressing tool aims its approach axis. Nothing moves: the stroke is a depth, and the input is the meaning. A neighbouring button's zone is the check that a wide tool did not press two.

Cap colours follow the name (cycle_start green, feed_hold red, reset blue, …) and estop is drawn as the ø40 mushroom head with its collar; each button's sensor is pinned as an hmi.button with the head size, travel and actuating force, the panel itself as an hmi.panel. By default any robot link trips a button; watch_robots=[...] narrows it to the arms named.

With catalog= — the id of a pushbutton-box spec pack, or a package directory — a box you can order: the number of buttons is matched against the sizes sold (the box's face follows), the pitch, the cap and the travel come from the pack, and the box, its buttons and the E-stop land on the bill with their article numbers.

pallet

pallet(
    scene,
    name: str,
    position: tuple | tuple,
    *,
    size: tuple = (1.2, 1.0, 0.144),
    deck_boards: int = 5,
    yaw: float = 0.0,
    model: Optional[str] = "EPAL 1",
    manufacturer: Optional[str] = None,
    color: tuple = (0.52, 0.36, 0.18),
    **attributes,
) -> Built

A wooden pallet size = (length, width, height) on the floor at position (centre): three bottom boards, nine blocks, deck_boards top boards. Adds the frame <name>/top at the centre of the deck and pins one part (pallet).

pedestal

pedestal(
    scene,
    name: str,
    height: Optional[float] = None,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    top: Optional[tuple] = None,
    base: Optional[tuple] = None,
    column: Optional[float] = None,
    plate: Optional[float] = None,
    yaw: float = 0.0,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.2, 0.21, 0.23),
    **attributes,
) -> Built

A robot pedestal: base plate, column, top plate, height from floor to the top face at position. Adds the frame <name>/mount at the top centre — the robot's base pose (scene.set_robot_base_pose(*scene.frame( "<name>/mount"))) — and pins one part (structure.pedestal).

With catalog= — the id of a pedestal spec pack, or a package directory — a stand you can order: the height is matched against the ones that are sold (omit it for the pack's default) and the column, plates and their footprints come from the pack, so the BOM names the stand a robot is actually bolted to.

detail="full" (the default with a catalog) adds the gussets between the column and the base — decoration that never collides.

photoelectric

photoelectric(
    scene,
    name: str,
    frm: tuple,
    to: tuple,
    *,
    body: Optional[tuple] = None,
    watch_robot: bool = False,
    watch: Optional[list[str]] = None,
    catalog: Optional[CatalogRef] = None,
    sensing: Optional[str] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.2, 0.21, 0.23),
    **attributes,
) -> Built

A photoelectric sensor: a beam <name> from the lens at frm to to (both in metres, 3D) that trips on the named objects (watch — a workpiece arriving on the belt) and/or on any robot link (watch_robot), and the sensor body <name>/body behind the lens — body = (depth, width, height), the amplifier-in-head block sold by the million (20 x 11 x 31 mm unless given). The part (sensor.photoelectric) is pinned on the sensor.

What stands at to follows the sensing method: a through-beam pair puts the receiver <name>/receiver there, a retroreflective sensor its reflector <name>/reflector, a diffuse one nothing — the beam ends on the target itself.

With catalog= — the id of a photoelectric spec pack, or a package directory — a sensor you can order: sensing picks the method the pack sells (through_beam / retroreflective / diffuse / ...), the other axes (sensing_range_mm, output, ...) are chosen by name, the body takes the maker's dimensions, the BOM row carries the model number and mass, and a reflector the maker sells separately is a line of its own. A beam longer than the sensing range is refused with the numbers — the same sensing_range_mm a requirement check would ask of it.

power_supply

power_supply(
    scene,
    name: str,
    position: tuple,
    *,
    size: Optional[tuple] = None,
    yaw: float = 0.0,
    catalog: Optional[CatalogRef] = None,
    output_a: Optional[float] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.42, 0.44, 0.47),
    **attributes,
) -> Built

A DIN-rail power supply: the box <name>/body, size = (width, depth, height), standing on position (the centre of its foot — on a rail inside a cabinet), turned by yaw. The part (power_supply) carries output_v / output_a. Declare supply/load ports with bt.connections to check the connected loads against this rating.

With catalog= — the id of a power-supply spec pack, or a package directory — a unit you can order: output_a is matched against the ratings sold, the box takes the size the pack lists for that rating (rules.size_mm_by_output_a, width / depth / height), and the BOM row carries the model number, its mass and its rating.

proximity

proximity(
    scene,
    name: str,
    frm: tuple,
    direction: tuple = (1.0, 0.0, 0.0),
    *,
    sensing_range: Optional[float] = None,
    body: Optional[tuple[float, float]] = None,
    watch: Optional[list[str]] = None,
    watch_robot: bool = False,
    catalog: Optional[CatalogRef] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.42, 0.44, 0.47),
    **attributes,
) -> Built

An inductive proximity switch: a beam <name> from the sensing face at frm, sensing_range along direction — the few millimetres a metal target must come within (4 mm unless given) — and the threaded barrel <name>/body behind the face, body = (diameter, length) in metres (an M12 x 47 mm barrel unless given). The beam trips on the named objects (watch) and/or on any robot link. The part (sensor.proximity) is pinned on the sensor.

With catalog= — the id of a proximity-switch spec pack, or a package directory — a switch you can order: the pack's axes (size M8/M12/M18/ M30, shield, output, contact, connection …) are chosen by name, the sensing range is the model's (sensing_range_mm), the barrel takes the size the pack lists for the thread (rules.body_mm_by_size), and the BOM row carries the model number and mass.

rack

rack(
    scene,
    name: str,
    size: Optional[tuple] = None,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    levels: Optional[int] = None,
    upright: Optional[float] = None,
    shelf_thickness: Optional[float] = None,
    yaw: float = 0.0,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    shelf_model: Optional[str] = None,
    color: tuple = (0.42, 0.44, 0.47),
    **attributes,
) -> Built

Shelving: size = (width, depth, height) standing on the floor at position (its centre, x, y[, floor z]), with levels shelves evenly spaced and the top one at height, on four corner uprights.

Adds a frame at the centre of every shelf's top face — <name>/level0 at the bottom, upwards — which is where the parts on that shelf sit and what a pick targets. Pins the bay (structure.rack) on the group.

With catalog= — the id of a rack spec pack, or a package directory — the bay is one you can order: the width, depth, height and number of levels are matched against what is sold (omit them for the catalog's defaults), the shelves are a line of their own on the BOM with their own part number, and a level spacing the catalog does not allow is refused.

Shelving sold as posts and shelves rather than as a bay works the same way: a pack with an upright component and no bay puts the series on the group line and the posts on their own, four of them, counted in the packs the maker sells them in (rules.uprights_per_pack).

detail="full" (the default with a catalog) adds the beams under each deck, the diagonal braces on the sides and the foot plates — decoration that never collides, so the uprights and decks stay the only thing a robot can hit.

remote_io

remote_io(
    scene,
    name: str,
    position: tuple,
    *,
    catalog: Optional[CatalogRef] = None,
    di_units: Optional[int] = None,
    do_units: Optional[int] = None,
    points_per_unit: int = 16,
    uplink=None,
    place: Optional[str] = None,
    yaw: float = 0.0,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.2, 0.21, 0.23),
    **attributes,
) -> Built

A remote I/O station on a DIN rail: the bus coupler <name>/coupler and its DI / DO terminal units <name>/di{i} / <name>/do{i} side by side from position (the centre of the coupler's foot; the units run along local +X, turned by yaw), and the I/O node <name> (kind="remote_io", hung off uplink the way add_io_node takes it) with a channel per point — DI0… and DO0…. The coupler is the part (io.remote); each unit is a BOM line of its own.

With catalog= — the id of a remote-I/O spec pack, or a package directory — a station you can order: di_units / do_units are matched against what the pack sells, logic (PNP / NPN) picks the unit models, the coupler and the units take the maker's widths and point counts, and every line carries its model number and mass.

stairs

stairs(
    scene,
    name: str,
    *,
    steps: Optional[int] = None,
    rise: Optional[float] = None,
    tread: Optional[float] = None,
    width: Optional[float] = None,
    position: tuple | tuple = (0.0, 0.0),
    yaw: float = 0.0,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    nosing: Optional[float] = None,
    rail_height: Optional[float] = None,
    rails: bool = True,
    legs: bool = True,
    model: Optional[str] = None,
    rail_model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.42, 0.44, 0.47),
    tread_color: tuple = (0.5, 0.52, 0.53),
    rail_color: tuple = (0.91, 0.36, 0.02),
    **attributes,
) -> Built

A steel stair flight, the kind bolted against a mezzanine: steps checker-plate treads climbing rise per step along local +x from position (rotated by yaw), carried on a plate stringer each side and handed by a tubular rail in safety orange.

Every tread is a walkable box, so a legged machine's footfalls snap onto it (see the legged guide); everything else — stringers, support legs, the handrail — is an ordinary obstacle, so an AGV driven into the flight fails its aisle check and an arm sweeping through the rail collides. Adds the frames <name>/foot (on the floor at the bottom) and <name>/top (the landing edge) — author the vehicle path's z between them — and pins the flight (structure.stairs).

Each tread overhangs the one below by nosing, the way a real one does. That overlap is what a walking machine needs at the seam: keep it at least twice the foot radius, or a foothold lands in the gap between two treads and the bake refuses it by name.

With catalog= — the id of a stair spec pack, or a package directory — the flight is one you can order: the rise, tread, width and number of steps are matched against what is sold, the sections come from the pack, the handrails are a line of their own on the BOM (one per side), and a combination the maker does not sell — too steep, too shallow for the walking rule 2 x rise + tread — is refused with the numbers.

rails=False drops the handrail (a flight against a wall); legs=False drops the support leg under the high end, which a flight slung between two landings — a storey of a building stair — does not have.

table

table(
    scene,
    name: str,
    size: Optional[tuple] = None,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    top_thickness: Optional[float] = None,
    leg: Optional[float] = None,
    yaw: float = 0.0,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    top_model: Optional[str] = None,
    color: tuple = (0.42, 0.44, 0.47),
    **attributes,
) -> Built

A table size = (length, width, height) standing on the floor at position (its centre, x, y[, floor z]): a top of top_thickness on four legs. Adds the frame <name>/top at the centre of the top face — where a fixture or a workpiece sits — and pins one part (structure.table) on the group.

With catalog= — the id of a table spec pack, or a package directory — a stand you can order: the sides are matched against the ones that are sold (omit size for the pack's defaults, so position alone will do), the profile section and the board thickness come from the pack, and where the maker sells the board separately it lands on the BOM as its own line.

detail="full" (the default with a catalog) adds the rails under the board and a pad under each foot — decoration that never collides, so the legs and the board stay the only thing a robot can hit.

vise

vise(
    scene,
    name: str,
    position: tuple | tuple,
    *,
    yaw: float = 0.0,
    jaw_width: float = 0.125,
    opening: float = 0.06,
    max_opening: float = 0.15,
    jaw_height: float = 0.04,
    jaw_thickness: float = 0.03,
    body_height: float = 0.06,
    body_length: float = 0.36,
    catalog: Optional[CatalogRef] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.46, 0.47, 0.49),
    **attributes,
) -> Built

A machine vise standing on a table top at position (x, y[, the table's top z]): the body, a fixed jaw and a moving jaw set opening apart, the jaws clamping along local Y (the fixed jaw on +Y, the screw end trailing off to -Y) before yaw. Adds the frame <name>/jaw at the centre of the jaw floor between the jaws — where the workpiece sits, jaw_width wide along X and opening across — and pins the vise (fixture.vise).

Clamping is a signal, not a motion: the jaws stand where the part goes and the cell's program says when it is held (a machine-tending handshake's clamp — see bt.tending). An opening beyond max_opening is refused with the numbers, the way a size nobody sells is.

With catalog= — the id of a vise spec pack, or a package directory — a vise you can order: jaw_width is matched against the ones sold, the jaw and body figures and the maximum opening come from the pack, and the BOM row carries its article number and mass.

wall

wall(
    scene,
    name: str,
    path: Sequence[tuple],
    *,
    height: float = 2.7,
    thickness: float = 0.12,
    base_z: float = 0.0,
    closed: bool = False,
    openings: Sequence[Sequence[float]] = (),
    head: float = 2.1,
    detail: Optional[str] = None,
    color: tuple = (0.62, 0.6, 0.56),
    trim_color: Optional[tuple] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    **attributes,
) -> Built

A partition along path (floor corners, metres): thickness thick, height tall, standing off base_z. closed joins the last corner back to the first, so a four-corner path is a room.

openings=[(edge, centre, width), ...] cuts a doorway width wide, centred centre metres along that edge, and spans the wall over it — the pier each side and the head above are ordinary obstacles, so a machine driven at the pier fails its aisle check while one sent through the opening passes. A fourth element sets that opening's clear height (head otherwise); at or above the wall's own height it is a gap through it, with nothing over. Each one adds the frame <name>/opening{edge}_{i} on the floor at its centre, facing along the wall — that is where a route is authored through it.

Corners get a thickness square column so two runs meet square, and detail="full" adds a skirting to each face and a lining round each opening as decoration (drawn, never collided). Pins one part (structure.wall) carrying the run's length, height and thickness.

bt.parts.wall(scene, "corridor/north", path=[(0, 2.4), (18, 2.4)],
              height=2.7, openings=[(0, 6.0, 0.9), (0, 11.0, 0.9)])