Skip to content

Standard parts (bt.parts)

Generators for the structures every cell has — fences, walls, tables, pedestals, aluminium frame units cut from a maker's profile system (frame_unit, FrameUnit), racks, conveyor bodies, pallets, light curtains, stairs, a machining centre with its door and panel, a screw presenter with its magazine, screws and compound solids, the shape library (appearance, shaped_box) and the props drawn from it (trays, stages, cartons, bins, roll cages, the racks a goods-to-person drive unit carries, stock on pallets, floor markings, a person, a gantry), and catalog objects set down as they ship (prop — the scanned YCB objects) — 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.

The forms a box cannot draw but every cell has — a carton with its lids and tape, a pressed tray, a perforated basket, an adjuster foot — are a small shape library shipped with botrail (SHAPES, unit-box USD layers under botrail/_shapes/, authored in botrail-assets): appearance draws an obstacle as one of them, scaled to its box, so the collision stays the box the cell was taught against and the picture travels with the resident; shaped_box places one as decoration. The props built on it — tray, stage, carton, unit_load, marking, person, gantry — are the generic things a cell is full of and nobody orders by part number, generated from their dimensions like everything else here.

ALUMINIUM module-attribute

ALUMINIUM = (0.72, 0.74, 0.76)

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.

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.

BLACK_ANODIZED module-attribute

BLACK_ANODIZED = (0.09, 0.09, 0.1)

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.

CAP_BLACK module-attribute

CAP_BLACK = (0.05, 0.05, 0.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.

CARDBOARD module-attribute

CARDBOARD = (0.62, 0.45, 0.26)

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.

CLEAR_SHEET module-attribute

CLEAR_SHEET = (0.78, 0.85, 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.

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.

CONTROLLER_MOUNTS module-attribute

CONTROLLER_MOUNTS = (
    "floor",
    "wall",
    "rack",
    "cabinet",
    "under_table",
    "stacked",
)

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.

FACES module-attribute

FACES = ('front', 'back', 'left', 'right', 'top')

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

FOAM module-attribute

FOAM = (0.82, 0.8, 0.74)

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.

FRAME_TEMPLATES module-attribute

FRAME_TEMPLATES = ('table', 'enclosure')

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.

HI_VIS module-attribute

HI_VIS = (0.85, 0.75, 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.

KLT_BLUE module-attribute

KLT_BLUE = (0.1, 0.25, 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.

LABEL_PAPER module-attribute

LABEL_PAPER = (0.86, 0.84, 0.76)

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.

LINE_GREEN module-attribute

LINE_GREEN = (0.1, 0.45, 0.2)

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.

LINE_YELLOW module-attribute

LINE_YELLOW = (0.85, 0.62, 0.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.

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.

PACKING_TAPE module-attribute

PACKING_TAPE = (0.34, 0.22, 0.1)

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.

POD_YELLOW module-attribute

POD_YELLOW = (0.36, 0.46, 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.

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.

SHAPES module-attribute

SHAPES = (
    "adjuster",
    "basket",
    "bracket",
    "carton",
    "handle",
    "highbay",
    "hose",
    "orikon",
    "panel",
    "person",
    "person_pick",
    "person_reach",
    "pod",
    "rim",
    "roll_cage",
    "tote",
    "tray",
    "tslot",
    "tslot_2",
    "waste_bin",
    "workpiece",
)

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.

STRETCH_FILM module-attribute

STRETCH_FILM = (0.78, 0.8, 0.84)

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.

TRAY_STEEL module-attribute

TRAY_STEEL = (0.55, 0.57, 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.

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.

ZINC module-attribute

ZINC = (0.62, 0.63, 0.66)

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.

Box

A box size = (x, y, z) centred at at, in the compound's frame.

at class-attribute

at = (0.0, 0.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.

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

Cylinder

A cylinder of radius and length standing on at — the centre of its bottom face — and rising along the compound's +Z.

at class-attribute

at = (0.0, 0.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.

FramePlan

A template's cut list before anything is built: the members, the pairs joined with a bracket, the member ends that take a cap, the members that stand on a foot, the board on top (size, centre) if there is one, and an enclosure's panels (tag, size, centre) and door leaf (size, centre, the face it closes). Pure data — frame_unit_plan makes it, frame_unit builds it, tests read it without a scene.

panels class-attribute

panels = ()

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.

lengths

lengths() -> dict[float, int]

How many members there are of each length (metres, to 0.1 mm).

FrameUnit

A frame built member by member from a system pack — for the shapes no template has (an L-shaped bench, a camera post with an outrigger, a cover with an opening). Place members in the unit's own frame (metres: floor centre, x along the width, before yaw), say which pairs are joined, where a cap or a foot goes, then build():

F = bt.parts.FrameUnit(scene, "rig", catalog="misumi/hfs/6-series", finish="black")
leg = F.member("leg", (0.0, 0.0, 0.0), (0.0, 0.0, 0.72))
beam = F.member("beam", (0.0, 0.0, 0.72), (1.14, 0.0, 0.72), section="3060")
F.joint(leg, beam)          # one bracket, and the bolts and nuts it takes
F.cap(beam, "to")
built = F.build()

Every member is a box the robot can hit, cut to a length the pack sells (matched against the profile's length_mm range and step — 1140.5 is a length on a 0.5 mm grid, 1140.3 is refused with the nearest) and weighed by the pack's kg/m. A rectangular profile (w x d) stands on edge when horizontal and turns its d side along x when vertical, unless wide= says which axis across the member the d side takes. With detail="full" (the default with a catalog) the members are drawn as the pack's own profile where it ships one (a visual authored at the real cross-section, visual_scale: length) and as the shape library's generic T-slot extrusion otherwise, tinted for clear or black anodising; the brackets and caps are drawn as the pack's own articles where it ships a visual for them (a bracket authored with its fold corner at the origin, a cap as a plate with +z outward) and as the library's bracket and a plain plate otherwise, the feet as the library's adjuster — all out of collision — and counted; bolts and nuts sit inside the slots and are not drawn at all, but they are still counted, on one hidden resident each (<name>/hardware/bolts/lot), since a BOM line needs something in the scene to stand for. The BOM is the cut list: the unit on the group line, one line per profile length, one per hardware article — a cap line per section where the pack sells them that way (cap_3030, cap_3060 …). Changing detail changes none of it. panel sets a sheet into a bay and door hangs a leaf over an opening; they collide, and they are counted on lines of their own (by size, without a part number) in either mode.

Without a catalog give section= (metres, a square side or (w, d)): the members are one line (structure.frame, model= / manufacturer=) and nothing is counted.

cap

cap(tag: str, end: str) -> None

A frame cap on the frm or to end of a member.

door

door(
    size: tuple,
    at: tuple,
    *,
    material: str = "PC",
    model: Optional[str] = None,
) -> str

The leaf that closes an opening: a box (local size and centre) that collides, <name>/door, drawn like a panel of material, with a frame of the same name at its centre and a BOM line of its own (structure.door, model or one made from its size). It stands closed; how it opens is the cell's — scene.add_linear_axis on <name>/door makes it a sliding or a lifting door.

foot

foot(tag: str) -> None

An adjuster foot under the lower end of a vertical member. The member should already stop a foot's height above the floor.

frame

frame(tag: str, at: tuple) -> None

A named frame <name>/<tag> at a local point (the unit's yaw applies).

joint

joint(a: str, b: str) -> None

A bracketed butt joint between two members — one bracket (rules.brackets_per_joint), with its bolts and nuts.

member

member(
    tag: str,
    frm: tuple,
    to: tuple,
    *,
    section: Optional[str] = None,
    wide: Optional[int] = None,
) -> str

A profile from frm to to (local metres, along one axis), cut from the pack's profile section (default: its first). For a rectangular profile wide is the axis across the member its d side runs along (default: x for a vertical member, z for a horizontal one — on edge). Returns tag.

panel

panel(
    tag: str,
    size: tuple,
    at: tuple,
    *,
    material: str = "PC",
) -> str

A sheet set into the slots of the members around a bay: a box (local size and centre, its thinnest side the thickness) that collides, drawn see-through for a clear material (PC, acrylic, PET). Panels are counted by size — one BOM line per material and width x height (<name>/panels/PC-1040x690) — and carry no part number: the pack does not sell them.

plan

plan(
    plan: FramePlan,
    *,
    panel_material: str = "PC",
    door_model: Optional[str] = None,
) -> FrameUnit

Everything a template planned, in one go — an enclosure's panels and door in panel_material.

profile_role

profile_role(section: Optional[str] = None) -> str

The pack's profile component for section — its role (profile_3030), the role without the profile_ prefix (3030), or, left out, the first profile the pack declares.

section

section(role: Optional[str] = None) -> tuple

A profile's cross-section (w, d), metres — from the pack's dimensions_mm.w / .d (d defaults to w), or section= without a catalog.

slab

slab(
    tag: str,
    size: tuple,
    at: tuple,
    *,
    role: Optional[str] = None,
) -> str

A board or a panel: a box (local size and centre) that collides. With role, the pack's component of that role is its BOM line.

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.

Member

One profile of a frame unit: the pack's profile component it is cut from (role) and its two end centres in the unit's own frame — metres, floor centre, x along the width, before yaw. Members run along one axis. wide is the axis across the member that the section's d side runs along, which only matters for a rectangular profile: left out, a vertical member turns it along x and a horizontal member stands on edge (along z).

axis property

axis

0 / 1 / 2 for a member running along x / y / z.

across

across() -> tuple[int, int]

The two axes across the member: (the one the section's w side runs along, the one its d side runs along).

bounds

bounds(section: Union[float, tuple]) -> tuple[tuple, tuple]

The box the member fills, for a square side or a (w, d) section (metres).

size

size(section: tuple) -> tuple

The box the member fills for a section (w, d) in metres.

ScrewFeeder

Bases: botrail.parts.Built

What screw_feeder built, plus what a fastening program addresses: the device (an indexing source — start it for the next screw), the present lane (a zone sensor over the pick point, on while a screw stands there), the pick frame at the presented screw's tip (+Z up the screw) and the screws in the order the feeder hands them out.

device class-attribute

device = ''

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

pick class-attribute

pick = ''

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

present class-attribute

present = ''

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

Workpiece

Bases: botrail.parts.Built

What workpiece built: the housing and cover obstacles, the dowels on the housing, the seat (the mating plane's centre, +Z out of the housing — where the cover's underside lands) and the pack's mounting data the joint is read from.

cover class-attribute

cover = ''

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

housing class-attribute

housing = ''

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

appearance

appearance(
    scene,
    name: str,
    shape: str,
    size: tuple,
    *,
    offset: tuple = (0.0, 0.0, 0.0),
    tint: bool = False,
) -> str

Draws the obstacle name as the library shape shape, scaled to size — the box the shape fills, normally the obstacle's own — so the picture moves, attaches, saves and exports with the resident while its collision stays the box the cell was taught against. offset shifts the picture inside that box (a basket floor sitting on an insert); tint paints the whole shape the obstacle's colour instead of the layer's finishes (a laminate panel in the bench's colour). The layer's finishes replace any set_obstacle_material override. Returns name.

bin

bin(
    scene,
    name: str,
    size: Optional[tuple] = None,
    position=(0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    wall: float | tuple[float, float] | None = None,
    floor: Optional[float] = None,
    yaw: float = 0.0,
    detail: str | None = None,
    color: tuple | None = None,
    sleeve: str | None = None,
    mass_kg: float | None = None,
    model: str | None = None,
    manufacturer: str | None = None,
    **attributes,
) -> Built

A small-load container — a KLT, a tote, a folding container: size = (length, width, height) outside, standing at position (the centre of its underside; a bench top) turned by yaw. Four walls wall thick (one figure, or (across the length, across the width)) stand under <name>/wall0..3 with the floor floor thick between them as <name>/floor, every one a box the planner and the engine see — a thing set down inside lands on the floor and stops at a wall — and the frame <name>/floor is the floor's top centre, where a part sets down. Pinned as one part (bin) on the group, so a physics bake keeps the five boxes one rigid unit that rests, slides and is carried whole; mass_kg is what that unit weighs. Full detail draws the library's sleeve round the boxes under <name>/trim/, out of collision, in the bin's colour: sleeve names it — "tote" (a KLT's ribs, stacking rim, grips and card pocket, the default) or "orikon" (a folding container's walls with their hinge bead, rib frame and hand holes) — or the pack does.

With catalog= — a bin spec pack such as botrail/bin/klt-vda4500 — a container you can order: size (or length_mm / width_mm / height_mm) is matched against the sizes sold and a combination nobody sells is refused, the walls and floor are as thick as the pack's inside dimensions say, and the BOM row carries the type number and the mass.

bolt

bolt(
    scene,
    name: str,
    *,
    position: tuple,
    thread: Optional[float] = None,
    length: Optional[float] = None,
    head_diameter: Optional[float] = None,
    head_height: Optional[float] = None,
    catalog: Optional[CatalogRef] = None,
    quaternion=None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    standard: Optional[str] = None,
    property_class: Optional[str] = None,
    mass_kg: Optional[float] = None,
    color: tuple = (0.62, 0.63, 0.66),
    **attributes,
) -> str

One screw as one obstacle: a shank thread wide and length long with its tip at the origin and +Z up the shank, and a cylindrical head head_diameter × head_height on top of it — the envelope a bit picks, a feeder presents and a hole receives. Pinned as a fastener with its figures, so a magazine of the same screw lands on the bill as one line with a count. bt.assembly.Fastener.place calls this with a standard's figures.

With catalog= — the id of a fastener spec pack, or a package directory — a screw you can order: the thread, pitch, head and drive come from the pack, length (metres) and property_class are matched against what it sells (its defaults when omitted), and the row carries the article number and the mass the pack states.

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

carton

carton(
    scene,
    name: str,
    size: tuple,
    position,
    *,
    yaw: float = 0.0,
    color: tuple = (0.62, 0.45, 0.26),
    mass_kg: float | None = None,
    model: str | None = None,
    manufacturer: str | None = None,
    detail: str | None = None,
    **attributes,
) -> str

A shipping carton size = (length, width, height) standing at position — the centre of its bottom face — turned by yaw: one obstacle, so it attaches, tracks and saves as one thing, drawn in full detail as the library's folded, taped and labelled box. Pins one part (workpiece), its model the RSC size in millimetres unless given, with mass_kg when known — what a payload check reads. Stock that is only scenery: scene.remove_part(name) after. Returns the name.

charging_station

charging_station(
    scene,
    name: str,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    yaw: float = 0.0,
    length: Optional[float] = None,
    width: Optional[float] = None,
    height: Optional[float] = None,
    plate: Optional[float] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.42, 0.44, 0.47),
    **attributes,
) -> Built

A vehicle charging station: the housing, and the charging plate on the floor in front of it that the vehicle backs onto. position is the housing's centre on the floor; the plate extends along local +X (rotated by yaw), which is the side the vehicle arrives from. Adds the frame <name>/dock on the floor at the plate's outer edge with +X pointing away from the housing — the vehicle's charging face stands here, so a vehicle whose contacts are at the back parks with its body centred half a body length along that +X. Pins the charger (vehicle.charger).

With catalog= — the id of a charger spec pack, or a package directory — the housing and plate come from the pack (omit the sizes) and the supply the site provides (supply_vac=) is checked against what it takes.

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.

compound

compound(
    scene,
    name: str,
    solids: Sequence[Union],
    position: tuple,
    *,
    quaternion=None,
    color: tuple = (0.42, 0.44, 0.47),
    finish: Optional[tuple] = None,
    segments: int = 32,
) -> str

One obstacle made of several primitives — a plate with a boss, a screw's shank and head — where add_box and add_cylinder would make one obstacle each and a thing the cell hands around (attaches, feeds, tracks) has to be one resident.

The solids (Box, Cylinder) are written as a mesh into botrail's cache (~/.cache/botrail/parts, or BOTRAIL_CACHE_DIR), keyed by their figures so the same shape is one file, and loaded with add_mesh at position / quaternion — collision is the mesh's convex decomposition, like any mesh. Returns the obstacle's name.

controller

controller(
    scene,
    name: str,
    robots: Sequence[str],
    position: tuple | tuple = (0.0, 0.0),
    *,
    size: Optional[tuple] = None,
    catalog: Optional[CatalogRef] = None,
    variant: Optional[str] = None,
    detail: Optional[str] = None,
    yaw: float = 0.0,
    mount: str = "floor",
    channels: Optional[list] = None,
    programs: Optional[list] = None,
    uplink=None,
    cable_m: Optional[float] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.58, 0.56, 0.49),
    **attributes,
) -> Built

A robot controller box — the cabinet an arm cannot run without — as the three things it is at once: the box <name>/body (size = (width, depth, height), standing on position — its footprint centre, x, y[, the level it sits at] — door face on -Y before yaw), the robot_controller I/O node <name> driving robots (its place is the box; channels=, programs=, uplink= as add_io_node takes them), and one BOM line (robot_controller) carrying mount, the robot cable length cable_m and whatever identity is given. The frame <name>/front sits at the centre of the door face at the box's level — where somebody stands to service it. Declaring the node here takes the arm's derived controller line off the bill: the box, the node and the line are one thing.

A cell that wired the node first (add_io_node(name, ...) with its channels and bindings) keeps that wiring: the box adopts the node, which must drive the same robots; channels= / programs= / uplink= given here override what it had.

With catalog= — the id of a controller spec pack, or a package directory — a cabinet you can order: variant= picks the enclosure the maker sells it in (A-cabinet, Mate, ...), the box takes that variant's size (rules.size_mm_by_variant, else the body's fixed dimensions, else the pack's footprint and height), the line its part number and mass, cable_m defaults to the standard robot cable (electrical.cable.standard_m; an option length must be one the pack lists), channels to the pack's own I/O table (electrical.io), mount is checked against rules.mount, and the service clearance the maker asks for in front (rules.clearance_mm.front) rides on the line as service_clearance_mm.

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

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,
    stand_span: Optional[float] = None,
    rollers: Optional[tuple[float, float]] = None,
    roller_color: Optional[tuple] = 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.

rollers=(pitch, diameter) makes it a roller conveyor: the same slab collides, but full detail draws the rollers across the frame — tops at the conveying height, roller_color (zinc by default) — as one mesh out of collision, and the part is a conveyor.roller. stand_span spaces the stands at most that far apart (a pair at each end without it).

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.

duct

duct(
    scene,
    name: str,
    path,
    *,
    size: Optional[tuple[float, float]] = None,
    z: float = 0.0,
    wall: float = 0.0015,
    catalog: Optional[CatalogRef] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.42, 0.44, 0.47),
    **attributes,
) -> Built

A floor duct or cable tray along path — floor points (x, y) (or (x, y, z)) of its centerline, in order — as a box per straight run under <name>/run{i} (runs overlap by half a width at the corners), size = (width, height) of the outer section, standing on z. The routing support <name> (bt.routing) has its centerline at mid height inside the duct and the inner section less the wall; a route passes it with via=[name] (or bt.routing.through(name)). The part (cable.duct) carries the run length and the section.

Nothing is sized here: the duct is as wide as you say (100 x 60 mm when you say nothing), and a bundle that will not fit is the routing report's duct_fill to tell. With catalog= — a cable.duct spec pack, or a package directory — the inner section the pack states sets the size (plus the wall), and its maker and part number the part.

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.

frame_unit

frame_unit(
    scene,
    name: str,
    size: Optional[tuple] = None,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    template: str = "table",
    detail: Optional[str] = None,
    section: Optional[Union[str, float, tuple]] = None,
    legs: Optional[str] = None,
    rails: Optional[str] = None,
    lower_rails: Optional[float] = 0.0,
    feet: Optional[bool] = None,
    top: Optional[float] = None,
    panels: Optional[Sequence[str]] = None,
    openings: Optional[dict] = None,
    door: Optional[str] = None,
    panel: float = 0.005,
    panel_material: str = "PC",
    door_model: Optional[str] = None,
    yaw: float = 0.0,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: Optional[tuple] = None,
    **attributes,
) -> Built

An aluminium-frame unit from a template — table: four legs under a ring of rails, size = (width, depth, height) the frame's envelope, standing on the floor at position (its centre, x, y[, floor z]), turned by yaw. Adds the frame <name>/top at the centre of the top face (the board's, if top gives one a thickness) and pins the unit (structure.frame) on the group.

enclosure: a machine guard — the same box of twelve members, its lower ring flush with the bottom so it stands on a base (position with the base's top as its z; no feet unless asked), clad with panel-thick sheets of panel_material in the slots of the members on the faces panels names (default all five: front -y, back, left, right, top). openings={face: ((lo, hi), (lo, hi))} cuts one opening in a face — the unit's own coordinates along the face's two axes (x, z for front and back; y, z for the sides; x, y for the top), None for a side that runs to the edge — framed by mid-posts and mid-rails that join the cut list; door=face hangs a leaf over that face's opening, <name>/door, standing closed just proud of the face (drive it with scene.add_linear_axis). Panels are BOM lines by size and the door one line, without part numbers (door_model names it).

With catalog= — the id of a frame system pack (a maker's profile series: profiles by the millimetre, brackets, bolts, nuts, caps, feet), or a package directory — the members are cut to lengths the pack sells, from the profile section chosen ("3030"; default the pack's first; legs= / rails= give the legs and the rails a profile of their own — a rectangular rail stands on edge, a rectangular leg turns its wide side along the width), and the BOM is the cut list: one line per profile length (HFS6-3030-1140 x2 …), one per hardware article, counted from the joints the way the makers' own standard units are (see frame_unit_plan). lower_rails is the lower ring's height above the legs' bottom (None for none); feet puts the pack's adjusters under the legs (default: when the pack sells them); a pack that sizes width_mm / depth_mm / height_mm may leave size out. Without a catalog section is the profile's side in metres (or (w, d)) and the unit is one line.

detail="full" (the default with a catalog) draws the members as T-slot extrusions (the pack's own profile, or the shape library's tslot, tinted for the finish) and adds the brackets, caps and feet — decoration that never collides. The members are what a robot can hit in either mode, and the BOM is the same in either mode.

frame_unit_plan

frame_unit_plan(
    template: str,
    size: tuple,
    section: Union[float, tuple],
    *,
    lower_rails: Optional[float] = 0.0,
    foot: float = 0.0,
    top: float = 0.0,
    role: str = "profile",
    legs: Optional[Union[float, tuple]] = None,
    rails: Optional[Union[float, tuple]] = None,
    legs_role: Optional[str] = None,
    rails_role: Optional[str] = None,
    panels: Optional[Sequence[str]] = None,
    openings: Optional[dict] = None,
    door: Optional[str] = None,
    panel: float = 0.005,
) -> FramePlan

The members a template is cut into — arithmetic only, no scene.

table: four legs under a ring of rails, size = (width, depth, height) the frame's envelope (the height is to the top of the ring, a board goes above it), section the profile's cross-section — a square side, or (w, d) for a rectangular one; legs / rails give the legs and the rails a section of their own (and legs_role / rails_role the pack component each is cut from). The rule is the one the makers' standard units follow (MISUMI's HAU-BA set contents): the two width rails on top run the full width and sit on the legs; every other rail is butt-jointed between them (a depth rail between the width rails, a lower rail between the legs); the legs are a rail's height shorter than the frame, less the foot under them. A rectangular rail stands on edge (its d side vertical) and a rectangular leg turns its d side along the width. lower_rails is the height of the lower ring's underside above the legs' bottom (0 = flush with the floor, the standard unit; None = no lower ring). Every 3-member corner is three bracketed joints, the four exposed ends of the top width rails get a cap, each leg a foot when foot > 0, and top > 0 puts a board that thick on the ring.

For W 620 x D 480 x H 570 on a 30 mm section this is 620 x2, 560 x2, 420 x4, 540 x4 — MISUMI's published set for HAUBA6-3030-W620-D480-H570.

enclosure: the same box of twelve members — the lower ring flush with the bottom, standing on whatever is under it — clad with panel-thick sheets set into the slots of the members around each bay (6 mm into each slot, the sheet on the members' centre line) on the faces panels names (front -y, back +y, left -x, right +x, top; default all five). openings maps a face to the one opening it has, ((lo, hi), (lo, hi)) along the face's two axes in the unit's frame (x then z for front / back, y then z for left / right, x then y for the top; None runs that side to the face's edge): a mid-post or mid-rail frames each side that stops short of the edge, butt-jointed between the members around it, and the bays left beside, below and above the opening take panels. door names a side face whose opening gets a leaf: a sheet lapping half a member over every edge, standing just proud of the face so it can slide over the panels beside it.

gantry

gantry(
    scene,
    name: str,
    span: float,
    height: float,
    position,
    *,
    yaw: float = 0.0,
    section: float = 0.05,
    overhang: float = 0.03,
    color: tuple = (0.2, 0.21, 0.23),
    model: str | None = None,
    manufacturer: str | None = None,
    **attributes,
) -> Built

A portal over a station — the beam a camera or a light hangs from: two square posts section wide, span apart along the local X (turned by yaw) either side of position (the floor point between them), height tall, and the beam across their tops overhanging each post by overhang — <name>/post_l, <name>/post_r, <name>/beam, all in collision. The frame <name>/beam is the beam's centre underside, the mount. Pins one part (structure.gantry) on the group.

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 0x7f7eafce55a0>, 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 0x7f7eafce55a0>, door_side: Union[str, object] = <object object at 0x7f7eafce55a0>, door_travel: Optional[float] = None, door_speed: Optional[float] = None, front_door: Union[tuple, None, object] = <object object at 0x7f7eafce55a0>, 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 framed transparent windows, the enclosure skirt, the accent band and the stack light. These are authored visual details, not measured manufacturer CAD. Door leaves keep their solid collision envelopes behind the drawn panels and glazing. 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.

marking

marking(
    scene,
    name: str,
    *,
    rect: tuple[float, float, float, float] | None = None,
    line: tuple[tuple, tuple] | None = None,
    dash: tuple[float, float] | None = None,
    width: float = 0.08,
    color: tuple = (0.85, 0.62, 0.05),
    floor: float = 0.0,
    thickness: float = 0.003,
) -> Built

Paint on the floor. rect = (x0, y0, x1, y1) outlines an area with four strips <name>/0 … <name>/3; line = ((ax, ay), (bx, by)) is one stripe <name>, or the dashes <name>/0 … with dash = (on, off). Each is width wide and thickness thick, lying on floor (a slab's top). Out of collision and under the layout sheet's ground_z, so the drawing shows them on its ground layer and nothing else notices them.

mobile_rack

mobile_rack(
    scene,
    name: str,
    size: tuple,
    position=(0.0, 0.0),
    *,
    clearance: float = 0.4,
    style: str = "bins",
    lift: float = 0.0,
    yaw: float = 0.0,
    collide: bool = True,
    detail: str | None = None,
    color: tuple | None = None,
    model: str | None = None,
    manufacturer: str | None = None,
    **attributes,
) -> Built

A rack a drive unit carries — the shelf of a goods-to-person (GTP) system: size = (width, depth, height) on four corner uprights, standing at position (the floor point under its centre) turned by yaw, its underside clearance above the floor — the room a drive unit drives into to lift it. lift raises the whole rack that far: one a drive unit carries, its feet off the floor.

style="bins" is the inventory pod: the storage from the base frame to the top collides as one block (<name>/storage) and is drawn as the library's fabric bins on all four faces. style="open" is a case rack: a deck with a tray on it (<name>/deck), a rail round the top of three sides (<name>/rail_*) and the face at +x open — what stands on the deck comes out that way; the frame <name>/deck is the tray's top centre, where a load sits. The uprights (<name>/upright0..3) collide in both, floor to top.

Full detail draws the base frame and the top frame (or the deck's frame, plate and tray, and a mid rail), out of collision under <name>/trim/; collide=False takes the whole rack out of collision (a field of pods nothing drives past). Pins one part (structure.rack) on the group.

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),
    detail: Optional[str] = None,
    **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. An operator the pack sells no article for comes with the box (a complete station) and is listed as included, not bought again; a pack's box trim draws the enclosure in place of the plate.

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), laid out like an EPAL 1: three bottom boards along the length, nine blocks, three stringers and deck_boards deck boards, so a fork — or a pallet truck's arms — enters from the short side between the bottom boards. Adds the frame <name>/top at the centre of the deck and pins one part (pallet).

pallet_rack

pallet_rack(
    scene,
    name: str,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    yaw: float = 0.0,
    bays: int = 1,
    width: Optional[float] = None,
    depth: Optional[float] = None,
    height: Optional[float] = None,
    levels: Optional[int] = None,
    beam_heights: Optional[Sequence[float]] = None,
    upright: Optional[float] = None,
    upright_depth: Optional[float] = None,
    beam: Optional[float] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.42, 0.44, 0.47),
    **attributes,
) -> Built

Pallet racking: bays bays of clear width in a row along local +X (rotated by yaw), depth deep, height tall, with levels beam levels — the floor is level 0 and is not counted. position is the centre of the run on the floor; the aisle faces are ±Y. An upright frame (two posts, upright by upright_depth) stands between every bay and at both ends, and each level is a pair of beam-tall beams the pallets rest on, their tops at beam_heights (evenly spaced by default, the top one 100 mm under the upright top).

Adds the frame <name>/bay{i}/level{j} at the centre of every pallet seat — j = 0 on the floor, then each beam top upward — which is where a pallet is put and what a pick targets. Pins the racking: without a catalog one structure.rack line for the run; with catalog= — the id of a pallet rack spec pack, or a package directory — the dimensions and beam count are matched against what is sold, beam tops snap to the maker's adjustment pitch, and the run is bought as one 単体 unit plus (bays − 1) 連結 extensions, each on the BOM under its own part number.

pallet_stand

pallet_stand(
    scene,
    name: str,
    position: tuple | tuple = (0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    yaw: float = 0.0,
    length: Optional[float] = None,
    width: Optional[float] = None,
    inner: Optional[float] = None,
    support: Optional[float] = None,
    height: Optional[float] = None,
    leg: Optional[float] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.2, 0.21, 0.23),
    **attributes,
) -> Built

A pallet stand: the floor-standing rack a pallet-lift AMR drives under to pick a pallet up or set it down. Two sections stand either side of the vehicle's path, each two posts under a support rail with a stop at the far end; the pallet rests across both rails, support over the floor.

position is the stand's centre on the floor; the open end faces local +X (rotated by yaw), so a vehicle drives in along -X and backs out the way it came. length runs along the path, width is the outside of the two sections, inner the gap between them the vehicle passes through. Adds the frame <name>/pallet at the centre of the pallet seat (+Z up) and <name>/entry on the floor at the open end with +X pointing into the stand — the vehicle's heading when it docks. Pins the stand (structure.pallet_stand).

With catalog= — the id of a pallet stand spec pack, or a package directory — every dimension comes from the pack (omit them all), the pallet it takes is recorded, and the stand lands on the BOM under the article the maker sells it as.

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.

person

person(
    scene,
    name: str,
    position,
    *,
    height: float = 1.7,
    footprint: tuple = (0.4, 0.4),
    yaw: float = 0.0,
    color: tuple = (0.85, 0.75, 0.3),
    pose: str = "stand",
    detail: str | None = None,
) -> str

A person, as the safety scenarios need one: a box footprint by height standing at position (the floor point between the feet), facing yaw, in collision — a walk into it is refused, a light curtain or a zone reads it, a scenario moves it into the gate. Full detail draws the library's figure in it, scaled to height by its own proportions: pose "stand" (at ease), "reach" (handling something before the hips with both hands) or "pick" (reaching into a bin at chest height half a metre ahead — the arm out of the box, which stays the body). color is the box's, for what draws no figure (a colour picture, a USD without materials). Nothing is pinned. Returns the name.

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 — the rating scene.requirements() checks against the current_a of the parts at its voltage.

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.

prop

prop(
    scene,
    name: str,
    position,
    *,
    catalog: CatalogRef,
    yaw: float = 0.0,
    dynamic: bool = True,
    mass_kg: float | None = None,
    friction: float | None = None,
    color: Optional[tuple] = None,
    **attributes,
) -> str

A catalog object — a scanned mug, a cracker box, a clamp — standing at position (x, y[, the z of the face it stands on]) turned by yaw: the package's model as one obstacle, drawn as it ships (its textures included) and colliding as the convex decomposition of that mesh, a dynamic body of the pack's mass (mass_kg overrides) under physics (dynamic=False for scenery). The model's frame need not sit in its footprint — a scan's origin is wherever the scanner put it: the mesh is placed so the middle of its footprint lands on (x, y) and its underside on the face. catalog is a catalog id, an (id, revision) pair or a package directory (a local build).

Pins one part (the catalog id and revision, the pack's category, name and maker, the mass) and colours the obstacle with the pack's albedo_rgb unless color is given — the flat colour the consumers that draw no texture use (the rollout's colour pictures, a USD without materials); the studio keeps the texture. Returns the name.

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.

roll_container

roll_container(
    scene,
    name: str,
    size: Optional[tuple] = None,
    position=(0.0, 0.0),
    *,
    catalog: Optional[CatalogRef] = None,
    yaw: float = 0.0,
    detail: str | None = None,
    color: tuple | None = None,
    mass_kg: float | None = None,
    model: str | None = None,
    manufacturer: str | None = None,
    **attributes,
) -> Built

A roll cage — the two-sided roll box pallet (カゴ台車) cartons are loaded into by hand: size = (width, depth, height) outside, standing at position (the floor point under its centre) turned by yaw. Its two long faces (along x, the width) are open; a mesh frame stands at each end. What collides: the base <name>/base from the floor to the deck's top (casters and frame as one block) and the two end frames <name>/side0 / <name>/side1, thin slabs up to the full height. The frame <name>/deck is the deck's top centre — where the first layer sets down. Pinned as one part (vehicle.cart) on the group. Full detail hides the boxes inside the library's zinc-plated cage (<name>/trim/cage).

With catalog= — a roll-cage spec pack such as makitech/roll-box-pallet/mrc-s — a cart you can order: size (or width_mm / depth_mm) is matched against the sizes sold and a size nobody sells is refused, the deck's height comes from the pack, and the BOM row carries the type number, the mass and the load. Without one the deck is at the library cage's proportion (0.243 of a 1.70 cage).

screw_feeder

screw_feeder(
    scene,
    name: str,
    position: tuple | tuple,
    *,
    screws: Union[Sequence[str], int],
    fastener=None,
    yaw: float = 0.0,
    size: Optional[tuple] = None,
    pick: Optional[tuple] = None,
    proud: float = 0.001,
    catalog: Optional[CatalogRef] = None,
    detail: Optional[str] = None,
    model: Optional[str] = None,
    manufacturer: Optional[str] = None,
    color: tuple = (0.2, 0.21, 0.23),
    **attributes,
) -> ScrewFeeder

A screw presenter: a box size = (length, width, height) standing at position (x, y[, the z of its foot]), turned by yaw, that hands out the screws — obstacles made with bt.parts.bolt — one at a time at a pick point on its top face.

The feeder is an indexing Source: the screws are moved into a magazine row inside the body (its parking slots), and each bt.seq.start(<name>) releases the next one to the pick point, standing on its tip proud of the top face — so a program that names the screw each step takes gets exactly one per request. The zone sensor <name>/present over the pick point reads while a screw stands there (the input a program waits on before picking, and that drops when the bit lifts it away). pick is the pick point on the top face in the body's frame (x along its length, y across), by default centred and toward the -Y edge. The frame <name>/pick marks the presented screw's tip, +Z up the screw.

Pinned on the device as a feeder.screw. A vibratory bowl's mechanics, the rail, the escapement are not modelled: supply is a request and a place.

screws may also be a count: the feeder then makes that many screws itself (<name>/screw<i>) from fastener — a bt.assembly.Fastener, or the pack's own thread as an ISO 4762 M×20 when none is given.

With catalog= — the id of a screw-feeder spec pack, or a package directory — a presenter you can order: the rail size (thread_mm) is matched against the ones sold, the body and the pick point come from the pack, its present_s is what the bill records, and the row carries the article number. detail="full" (the default with a catalog) draws the body from the pack's feeder trim where it ships one; the box underneath stays the collision.

shape_path

shape_path(shape: str) -> Path

The USD layer a library shape is drawn from — its mesh is the prim /Shapes/<shape> — for the names in SHAPES.

shaped_box

shaped_box(
    scene,
    name: str,
    shape: str,
    size: tuple,
    position: tuple,
    *,
    quaternion=None,
    color: tuple | None = None,
    offset: tuple = (0.0, 0.0, 0.0),
) -> str

A decoration drawn as a library shape — the picture of a handle, a hose, an adjuster foot beside a resident that keeps its own massing: add_box out of collision, then appearance. With color the shape is tinted that colour; without, it keeps the layer's finishes. Returns the obstacle's name.

stage

stage(
    scene,
    name: str,
    size: tuple,
    position,
    *,
    yaw: float = 0.0,
    seat: float = 0.018,
    plate: float = 0.016,
    leg: float = 0.012,
    inset: float = 0.03,
    detail: str | None = None,
    color: tuple = (0.2, 0.21, 0.23),
    plate_color: tuple = (0.12, 0.15, 0.16),
    insert_color: tuple = (0.82, 0.8, 0.74),
    model: str | None = None,
    manufacturer: str | None = None,
    **attributes,
) -> Built

A presentation stage — the fixture a part is set on under a camera: a top plate plate thick on four round legs of radius leg, inset in from the corners, the whole size = (length, width, height) at position (its underside centre) turned by yaw, with a foam insert seat thick on the plate. The block collides as one box; in full detail it is hidden and the plate and legs are drawn under <name>/trim/. The frame <name>/seat is the insert's top centre. Pins one part (fixture) on the group.

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),
    top_color: Optional[tuple] = None,
    **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. top_color paints the board apart from the legs (a laminate top on a painted frame); color is both.

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. A pack may put the legs in from the board's edge the way the maker does (dimensions_mm.inset_length / inset_width, to the leg centre) and draw the whole frame, board included, from its frame trim.

tray

tray(
    scene,
    name: str,
    size: tuple,
    position,
    *,
    yaw: float = 0.0,
    seat: float = 0.018,
    grips: bool = True,
    detail: str | None = None,
    color: tuple = (0.55, 0.57, 0.6),
    insert_color: tuple = (0.82, 0.8, 0.74),
    model: str | None = None,
    manufacturer: str | None = None,
    **attributes,
) -> Built

A parts tray: a pressed steel tray size = (length, width, height) standing at position — the centre of its underside, a bench top — turned by yaw, with a foam insert seat thick on it. The tray is the box the planner checks; the insert is a picture (a set-down is refused closer than a few millimetres to a thing the planner sees, and the insert is what the part visibly rests on); the frame <name>/seat is the insert's top centre, where a part sets down. Full detail draws the tray and the insert from the shape library and a bent grip on each side (grips), out of collision, under <name>/trim/. Pins one part (tray) on the group.

unit_load

unit_load(
    scene,
    name: str,
    position,
    *,
    yaw: float = 0.0,
    pallet: tuple = (1.2, 0.8, 0.144),
    height: float = 0.9,
    collide: bool = True,
    detail: str | None = None,
) -> Built

Stock on a pallet: pallet = (length, width, height) at position (the floor point under its centre) turned by yaw, a load height tall on it. Two envelopes are what the aisle check meets — the pallet's and the load's, both hidden in full detail; collide=False for stock on a level nothing drives past. Full detail draws the timber, the courses of cartons with their tape, a stretch-film skin and a label with its barcode on each long side, all out of collision and inside the envelope; the courses and the lot's colour follow from name, so a replay draws the same stock. Stock is not a purchase: nothing is pinned.

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

workpiece

workpiece(
    scene,
    name: str,
    position: tuple | tuple,
    *,
    catalog: CatalogRef,
    yaw: float = 0.0,
    cover_at: Optional[tuple | tuple] = None,
    detail: Optional[str] = None,
    color: tuple = (0.5, 0.51, 0.53),
    cover_color: tuple = (0.62, 0.63, 0.66),
    **attributes,
) -> Workpiece

A two-part assembly from a workpiece spec pack — a housing on its fixture at position (x, y[, the z of its underside]) turned by yaw, and its cover waiting at cover_at (the same point when omitted: seated). The pack's components size them (housing: length, width, height; cover: length, width, thickness, boss_diameter, boss_height; dowel: diameter, proud) and its mounting declares the joint: the housing's flange face with its threaded holes and dowel pins, the cover's mount face with its clearance holes, the screws. The pins stand on the housing where the flange face's locators say. Frames: <name>/seat (the housing's top face centre, +Z up — the mating plane) and <name>/stock (where the cover waits). Everything is pinned to the pack, dowels included (two lines with a count). bt.assembly.joint(catalog=...) reads the same pack for the holes. detail="full" (the default) draws the housing and the cover as the prims the pack ships for them (components[].visual) — the casting and the machined cover — bound to the parts, so they travel with them; the boxes underneath stay what the cell checks.