Skip to content

Requirements and the check (bt.select)

What the cell asks of every bill-of-materials line, compared with what the chosen part says — and every static check in one report. botrail derives and compares; it does not choose. See Selecting parts.

req = scene.requirements()                 # bt.select.requirements(scene)
print(req.to_markdown())
req["ur5e/tool"].minimum                   # {"payload_kg": 2.3, "stroke_mm": 150.0}

report = scene.check()                     # bt.select.check(scene)
assert report.ok, report.to_markdown()

select

Requirements derived from the cell, and the selection check.

botrail does not choose parts. It derives what every bill-of-materials line must be able to do — from the cell the part sits in — compares that with what the chosen part says it can do, and reports where the two disagree or where it does not know. Choosing stays with the person, the agent or the vendor; :mod:botrail.catalog finds the real candidates to choose from.

req = scene.requirements()          # one row per BOM line
print(req.to_markdown())
req["tool"].minimum                 # {"payload_kg": 2.3, "stroke_mm": 150.0}
report = scene.check()              # I/O lint + sequences + parts + requirements
assert report.ok, report.to_markdown()

Vocabulary. A requirement names a spec the catalog names too, so a value read from a catalog package (Robot.from_catalog, bt.parts.*(catalog=...), bt.catalog.Product.identify) or typed by hand on set_part(...) lands in the same column. The keys, and the attribute names that answer them (:data:ALIASES):

requirement derived from answered by
payload_kg tool mass + the heaviest part the robot grasps; parts riding a vehicle's deck at start payload_kg
reach_mm the farthest taught target from the base, plus a margin; the table of a machining centre (or the spindle of a lathe) in the cell, through its opening reach_mm
stroke_mm the smallest side of the grasped parts (parallel gripper) stroke_mm, opening_mm
sensing_range_mm a beam sensor's span sensing_range_mm, range_mm, max_range_mm
range_mm a light curtain's span / an area sensor's half-diagonal range_mm, max_range_mm, sensing_range_mm
protective_height_mm a light curtain's post height protective_height_mm, height_mm
scan_fov_deg a lidar's authored sweep angle scan_fov_deg
length_mm, width_mm a conveyor's zone along and across its belt length_mm / width_mm, belt_width_mm
speed_mps a conveyor's belt speed, an axis speed max_speed_mps, speed_max_mps, speed_mps
max_speed_mps a vehicle's travel speed the same
max_climb_mps an aerial vehicle's climb rate the same
max_descent_mps an aerial vehicle's descent rate the same
flight_time_min an aerial vehicle's airborne time per cycle, from the baked timeline (requirements(timeline=tl)) the same
load_kg parts on a conveyor / an axis; robots standing on a pedestal load_kg, capacity_kg, max_load_kg, payload_kg
torque_nm a screwdriver's tightening torque: the joint torque on the screws it drives (bt.assembly.fasten) torque_max_nm, max_torque_nm, torque_nm
screw_length_mm, thread_max_mm (≥) / thread_min_mm (≤) the longest screw, the largest and smallest thread the screwdriver has to take screw_length_mm, thread_max_mm, thread_min_mm
di do ai ao safe_di safe_do points assigned to an I/O node the node's declared channels

Every requirement is a minimum (>=) unless noted. The derivations are geometric and deterministic — no sizing, no safety evaluation; a value that cannot be derived (a grasped part without mass_kg) is reported as a note, never guessed.

ALIASES module-attribute

ALIASES = {
    "payload_kg": ("payload_kg",),
    "reach_mm": ("reach_mm",),
    "arm_count": ("arm_count",),
    "stroke_mm": ("stroke_mm", "opening_mm"),
    "aperture_mm": ("aperture_mm",),
    "grip_force_n": (
        "grip_force_max_n",
        "grip_force_min_n",
    ),
    "sensing_range_mm": (
        "sensing_range_mm",
        "range_mm",
        "max_range_mm",
    ),
    "fov_deg": ("fov_h_deg", "hfov_deg", "fov_deg"),
    "resolution_h_px": ("resolution_h_px",),
    "resolution_v_px": ("resolution_v_px",),
    "max_range_mm": ("max_range_mm",),
    "min_range_mm": ("min_range_mm",),
    "scan_fov_deg": ("scan_fov_deg",),
    "range_mm": (
        "range_mm",
        "max_range_mm",
        "sensing_range_mm",
    ),
    "protective_height_mm": (
        "protective_height_mm",
        "height_mm",
    ),
    "length_mm": ("length_mm",),
    "width_mm": ("width_mm", "belt_width_mm"),
    "speed_mps": (
        "max_speed_mps",
        "speed_max_mps",
        "speed_mps",
    ),
    "max_speed_mps": (
        "max_speed_mps",
        "speed_max_mps",
        "speed_mps",
    ),
    "vertical_reach_min_mm": ("vertical_reach_min_mm",),
    "vertical_reach_max_mm": ("vertical_reach_max_mm",),
    "max_climb_mps": ("max_climb_mps",),
    "max_descent_mps": ("max_descent_mps",),
    "flight_time_min": ("flight_time_min",),
    "load_kg": (
        "load_kg",
        "capacity_kg",
        "max_load_kg",
        "payload_kg",
    ),
    "torque_nm": (
        "torque_max_nm",
        "max_torque_nm",
        "torque_nm",
    ),
    "screw_length_mm": (
        "screw_length_mm",
        "max_screw_length_mm",
        "screw_length_max_mm",
    ),
    "thread_max_mm": ("thread_max_mm",),
    "thread_min_mm": ("thread_min_mm",),
    "output_a": ("output_a", "current_a"),
    "cable_m": ("cable_m", "cable_length_m"),
    "di": ("di",),
    "do": ("do",),
    "ai": ("ai",),
    "ao": ("ao",),
    "safe_di": ("safe_di",),
    "safe_do": ("safe_do",),
}

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)

CheckReport

Every static check of a cell in one list — what botrail check prints.

Finding

Finding(severity: 'str', code: 'str', message: 'str', target: 'Optional[str]' = None)

Requirement

One thing a BOM line must be able to do, and whether its part can.

basis class-attribute

basis = ''

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

lint class-attribute

lint = True

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.

op class-attribute

op = '>='

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

status class-attribute

status = 'unknown'

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

Requirements

What the cell asks of every BOM line — the result of :func:requirements.

ok property

ok

No line falls short of what the cell asks (unknowns do not count).

findings

findings() -> list[Finding]

spec_short (error), spec_unknown (warning) and requirement_incomplete (info), in row order. An I/O node's channel capacity gets no spec finding — the I/O report already lints it — but its robot cable does.

Row

One bill-of-materials line with what the cell asks of it.

minimum property

minimum

The >= requirements as {key: value} — what bt.catalog.search(**row.minimum) takes.

status property

status

ok | short | unknown | unidentified | none (nothing derived).

check

check(
    scene,
    *,
    sequences: Optional[list[str]] = None,
    timeline=None,
    cable_slack_m: float = 1.0,
    service_clearance_m: float = 0.9,
) -> CheckReport

Every static check in one report: the I/O lint, each sequence walked for dangling references, unidentified equipment lines (with what the cell asks of them), the requirement comparison, and the placement of the cabinets — a controller nobody has placed (controller_unplaced), and the service space in front of a control cabinet's or controller's door blocked by something (service_space; service_clearance_m deep, or what the part states as service_clearance_mm). timeline (a baked cycle) adds the cycle-fact requirements — an aerial vehicle's flight time; cable_slack_m is the robot cable allowance beyond the run from arm to box. Errors make ok false; botrail check prints exactly this.

requirements

requirements(
    scene,
    *,
    sequences: Optional[list[str]] = None,
    margin: float = 0.1,
    timeline=None,
    cable_slack_m: float = 1.0,
) -> Requirements

Derive what every BOM line must be able to do from the cell it is in, and compare it with what the chosen part says (its catalog specs or the attributes typed on set_part).

sequences limits the programs whose grasps and I/O points are counted (default: all). margin is added to the reach and flight-time requirements (0.1 = 10 %). timeline is an optional baked simulate_sequences result: cycle facts only it can supply — an aerial vehicle's airborne time — are derived from it, and left as a note when it is absent. cable_slack_m is what a placed controller's robot cable needs beyond the run along the axes from the arm's base to the box (a drop, a loop, the terminations). Nothing is chosen and nothing is sized: a number the cell cannot supply (a grasped part with no mass_kg) becomes a note, not a guess.