Assembly and fastening (bt.assembly)¶
Bolted joints as a drawing states them, the screwdriver's program, and what a bake says about every screw. See Assembly and fastening.
screw = bt.assembly.iso4762(5, 20)
joint = bt.assembly.joint(scene, "cover_joint", a="housing", b="cover", pattern_a=threads,
pattern_b=clearances, fastener=screw, seat=seat, thickness=0.010,
torque_nm=(4.0, 5.0), min_engagement_mm=10)
driver = bt.assembly.driver(scene, "driver", robot="arm", bit="drv_bit", shank="drv_shank",
stroke=0.055, fastener=screw, torque_nm=4.5, cycles=7)
fastening = bt.assembly.fasten(sq, joint, driver, feeder, motions={...})
assembly
¶
Assembly: fit and fasten (design/design-assembly.md).
A mechanical assembly cell does two things with a part: it fits it —
carries it onto its mate, over pins, into a bore — and fastens it, one
screw at a time. botrail says both in the vocabulary it already has. A
fit is an attach, a move and a detach, plus a report of where the
part landed against where the joint says it goes. A fastening is that
same carry for the screw, then the screwdriver's own stroke running it
down at the thread's feed, while a driver program — one more PLC
program scanned beside the robot's, the way a machine tool's is — times
the rundown from the screw's pitch and answers OK or NOK. Threads, torque
curves and cam-out are not simulated: the numbers are data with a source,
the motion is geometry, the result is a signal.
fastener = bt.assembly.iso4762(5, 20) # M5×20, 8.8
holes = [bt.assembly.Hole(f"h{i}", xy, "threaded", depth_mm=13) ...]
joint = bt.assembly.joint(scene, "cover", a="housing", b="cover",
pattern_a=bt.assembly.BoltPattern(holes), pattern_b=...,
fastener=fastener, seat=scene.frame("housing/top"),
thickness=0.010, torque_nm=(4.0, 5.0), min_engagement_mm=10)
driver = bt.assembly.driver(scene, "driver", robot="arm", bit="drv_bit",
shank="drv_shank", stroke=0.055, fastener=fastener,
torque_nm=4.5, cycles=len(joint.order) + 1)
sq = scene.sequence("assemble")
fastening = bt.assembly.fasten(sq, joint, driver, feeder, motions={...})
tl = scene.simulate_sequences(["assemble", driver.program])
rows = bt.assembly.fastening_report(tl, fastening)
A fitted part meets what it is fitted into, and the collision check has
to know that is intended: fasten and place declare it
(Scene.allow_object_obstacle_contact) — the cover may meet its housing
and dowels anywhere, a screw may be inside the cover and the housing only
within its hole's window (the hole's axis, the hole's tolerance), so
a screw taught off its hole is still a collision the bake refuses.
What a joint knows is what a drawing states: the holes of each part
(threaded or clearance, depth, grip), the locators (pins and their holes),
the screw, the torque the joint is designed for and the engagement it
needs. From that come the hole frames a teach aims at, the order the
screws go in (a star pattern, opposite pairs from the centre out), the
engagement (length − grip − washer − thread start), the tip's depth
against the hole's bottom, and the checks a driver has to pass — its
torque range, its bit, its stroke. Fastener figures for ISO 4762 socket
head cap screws are tabled (iso4762), with the ISO 898-1 reference
torque at μ = 0.14 as a ceiling, never as the joint's own value.
ISO_4762
module-attribute
¶
ISO_4762 = {
3: (0.5, 5.5, 3.0, 2.5),
4: (0.7, 7.0, 4.0, 3.0),
5: (0.8, 8.5, 5.0, 4.0),
6: (1.0, 10.0, 6.0, 5.0),
8: (1.25, 13.0, 8.0, 6.0),
10: (1.5, 16.0, 10.0, 8.0),
12: (1.75, 18.0, 12.0, 10.0),
}
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)
TORQUE_REF_NM
module-attribute
¶
TORQUE_REF_NM = {
"8.8": {
3: 1.37,
4: 3.1,
5: 6.15,
6: 10.5,
8: 26.0,
10: 51.0,
12: 89.0,
},
"10.9": {
3: 1.92,
4: 4.4,
5: 8.65,
6: 15.0,
8: 36.0,
10: 72.0,
12: 125.0,
},
}
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)
BoltPattern
¶
The holes and locators one part shows on a mating plane, in the plane's frame (millimetres, +Z out of the part).
Driver
¶
What driver authored: the screwdriver's program (program, on the
I/O node node), the links it drives with (bit, the shank joint
and its stroke_m), the screw and torque it is set up for, its
phase timing, and its signals by role — signal("start") is the
output the robot's program raises, signal("ok") / signal("nok")
the inputs it waits on. tool is what the tool's datasheet says
(torque_nm range, screw_length_mm, bit_mm) for check.
cycles
class-attribute
¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.int(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by '+' or '-' and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal.
int('0b100', base=0) 4
engage_m
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
final_turn
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
rpm_final
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
rpm_run
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
run_m
property
¶
The stroke one screw runs down: its length less the depth the engage move already put its tip into the hole.
t_find_s
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
t_pre_s
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
Fastener
¶
One screw, by its figures (millimetres): the thread and its pitch,
the length under the head, the head's diameter head_dk_mm and
height head_k_mm, the drive's across-flats drive_s_mm (the bit it
takes), the head standard and property class, and — where a table
gives one — the reference torque torque_ref_nm it must not be
tightened beyond. model is the part number it goes on the bill as.
head_standard
class-attribute
¶
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'.
property_class
class-attribute
¶
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'.
from_catalog
classmethod
¶
from_catalog(
ref,
*,
length_mm: Optional[float] = None,
property_class: Optional[str] = None,
) -> Fastener
A screw from a fastener spec pack — the id, or a package
directory: the thread, pitch, head and drive the pack's screw
component states, the length_mm and property_class it sells
(its defaults when omitted), the article number, the mass and the
class's reference torque (torque_ref_<class>_nm in its specs).
place
¶
place(
scene,
name: str,
position: tuple,
quaternion: Optional[tuple] = None,
*,
manufacturer: Optional[str] = None,
**attributes,
) -> str
The screw as an obstacle (bt.parts.bolt): its tip at position,
+Z up the shank, pinned as a fastener with these figures — and to
its pack, for a screw read from_catalog.
Fastening
¶
What fasten appended: which screw went to which hole, in order
(pairs), the driver and the feeder it used, the joint, the halted
lane, and the step names per screw (steps[k]["run"]…).
Finding
¶
Finding(severity: 'str', code: 'str', message: 'str', target: 'Optional[str]' = None)
Hole
¶
A hole of a bolt pattern at xy_mm on the mating plane: threaded
(with a usable depth_mm from the plane and an unthreaded
thread_start_mm) or clearance (through a part grip_mm thick
under the head — the part's thickness when omitted).
tolerance_mm is the position a placed screw is judged against.
kind
class-attribute
¶
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'.
thread_start_mm
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
tolerance_mm
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
Joint
¶
A bolted joint the cell makes: part a (the threaded side) and part
b (the clearance side) with their patterns, the screw, the joint's
torque range and required engagement, the pose of the mating plane
(seat: its centre, +Z out of a, where b's underside lands),
b's thickness, and what joint() derived — the tightening order
and one frame per hole (frames[id] = <name>/hole/<id>, on b's
top face, +Z out, where a screw's head seats).
seat_frame
class-attribute
¶
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'.
washer_mm
class-attribute
¶
Convert a string or number to a floating-point number, if possible.
check
¶
What the drawing and the tool say against each other — see
check().
engagement_mm
¶
Thread engaged in a: length − grip − washer − thread start.
grip_mm
¶
Under the head to the mating plane: the clearance hole's grip,
else b's thickness.
tip_depth_mm
¶
How deep the tip goes into a from the mating plane.
Locator
¶
A locating feature at xy_mm on the mating plane: a pin or
boss standing depth_mm proud of one part, a hole or recess
that deep in the other.
kind
class-attribute
¶
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'.
Placement
¶
What place appended: the part carried, its expected seat, the
step names (steps["release"] is where it was let go) and the
seated signal the release raises — what a fastening of the part
waits on.
seated
class-attribute
¶
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'.
check
¶
The joint against its screw, and — given the driver — against the
tool: hole_mismatch (a hole in one part not over its mate),
hole_kinds (a clearance hole where a thread should be),
engagement_short (thread engaged under what the joint needs),
hole_bottom (the tip through the usable depth), torque_over_table
(the joint's torque above the class's reference), torque_off_joint
(the driver set outside the joint's range), torque_over_tool (the
joint's torque outside what the tool delivers), bit_mismatch,
screw_too_long and stroke_short. Errors are what makes a joint
impossible as drawn; warnings are what a reviewer would flag.
driver
¶
driver(
scene,
name: str,
*,
robot: str,
bit: str,
fastener: Fastener,
torque_nm: float,
cycles: int,
shank: Optional[str] = None,
stroke: Optional[float] = None,
engage: float = 0.003,
rpm_run: float = 340.0,
rpm_final: float = 40.0,
t_find_s: float = 0.2,
t_pre_s: float = 0.2,
final_turn: float = 0.3333333333333333,
program: Optional[str] = None,
node: bool = True,
estop: Optional[str] = None,
finish: Optional[str] = None,
tool: Optional[Mapping[str, object]] = None,
channels: Optional[list] = None,
) -> Driver
The screwdriver's controller as a program of its own, scanned
beside the robot's — the way a tightening controller runs: on
<name>/start it finds the drive (t_find_s), runs the screw
down at rpm_run for as long as the thread takes (the length less
engage, the depth the engage move already put the tip into the hole,
at pitch × rpm), pre-tightens (t_pre_s), final-tightens
a final_turn at rpm_final, then answers <name>/ok or
<name>/nok and holds <name>/busy until the start drops. <name>/run
is on while the bit turns (what a spin effect binds to). The result
is OK unless a scenario says otherwise: <name>/fault_first fails the
next run, <name>/fault_retry the one after — the two faults a
fastening's retry branch is tested with.
cycles is how many starts the program can serve (screws plus the
retries allowed): the program is that many rundowns nested so it ends
on <name>/finish (or finish, a signal the robot's program raises
when it is done) from whichever cycle it is waiting in. With estop
(an input lane) no run starts while it is pressed. shank and
stroke name the tool's own feed joint and its travel, for the
ramp fasten runs the screw down with; tool carries the datasheet
figures check compares against (torque_nm=(min, max),
screw_length_mm, bit_mm). node (default) hosts the program on
the I/O node <name>/controller, so the handshake lands on the I/O
list as wires between the two controllers; channels
(bt.io.channels) are that controller's terminals, what
Scene.auto_assign_io gives the wires addresses on.
export_sheet
¶
export_sheet(
path: Union[str, Path],
fastening: Fastening,
*,
rows: Optional[list[dict]] = None,
format: Optional[str] = None,
title: Optional[str] = None,
) -> None
The tightening sheet: one line per screw in the order it is driven
— hole, screw, thread, length, class, head, engagement, torque, the
driver's rundown time and the step that runs it — as Markdown or CSV
(by format or the file's suffix). With rows (a fastening_report)
the baked result columns are added and the Markdown states the
driver's timing per screw and how many screws were driven.
fasten
¶
fasten(
sq,
joint: Joint,
driver: Driver,
feeder: ScrewFeeder,
*,
motions: Mapping[str, str],
retry: int = 1,
robot: Optional[str] = None,
prefix: str = "",
retract_s: float = 0.4,
tilt_deg: float = 1.0,
after: Sequence[str] = (),
) -> Fastening
Appends the fastening of joint to the sequence sq, one screw per
hole in the joint's order, each from the feeder: request a screw
(start the feeder, wait for present), the taught to_pick,
pick (the bit into the head), attach the screw to the bit, lift,
over the hole, engage (the tip into the hole by the driver's engage
depth — motions named with {i} for the screw's index and {hole} for
its hole id), then
raise the driver's start; while the driver finds the drive and runs,
the tool's shank ramps the screw down its length at the same
thread-derived time, and the step waits for ok or nok. On OK the
screw is released and the shank retracted; on NOK the shank backs out
and the run is repeated up to retry times — a last NOK releases the
screw where it stands, raises <program>/halted and stops the
program there (a refused cycle, the FAT row). A driver without its
own feed runs the screw down with the taught motion drive
instead.
Each screw is allowed to meet the joint's two parts only within its
hole's window (Scene.allow_object_obstacle_contact): its tip within
the hole's tolerance_mm of the hole's axis, leaning at most
tilt_deg — so the engage move, which is collision-checked, refuses a
screw taught off its hole, and the clearance measure does not count a
screw in its hole as contact.
Two interlocks are written in, rows on the interlock table rather
than conventions: the first screw is picked only once every signal in
after is on (the part's seated from place) and a screw is
present, and no start is raised while the driver's E-stop lane is
pressed (the engage move completes only with it released).
The driver must have a cycle per possible start: len(order) ×
(1 + retry) at most.
fastening_report
¶
fastening_report(
tl, fastening: Fastening, *, tilt_deg: float = 1.0
) -> list[dict]
One row per screw of a baked fastening, facts then checks: hole,
screw, order, t_engage / t_done, the screw's offset_mm from
the hole's axis and tilt_deg when it engaged, seated_mm (how far
it went down, against the run the driver was set for), rundown_s,
attempts and result (ok, retry — OK after a NOK — or nok),
the data-derived engagement_mm and tip_mm, the torque_nm, and
checks — align, tilt, seated, engagement, depth, torque
— each pass / fail / n/a (grasp_report's form).
fit_report
¶
fit_report(
tl,
placement: Placement,
*,
tolerance_mm: float = 0.5,
tilt_deg: float = 0.5,
) -> dict
Where a placed part landed against where it should (expected),
at the moment it was released: offset_mm in the seat's plane,
height_mm along its normal (positive = proud of the seat),
tilt_deg, t_release, and checks — position within
tolerance_mm, tilt within tilt_deg, seated (within the
tolerance along the normal).
iso4762
¶
iso4762(
thread_mm: int,
length_mm: float,
property_class: str = "8.8",
model: Optional[str] = None,
) -> Fastener
An ISO 4762 (DIN 912) hexagon socket head cap screw of the tabled
sizes (M3–M12), length_mm under the head. The mass is the steel of
its envelope; the reference torque is the class's at μ = 0.14 where
the table has the class.
joint
¶
joint(
scene,
name: str,
*,
a: str,
b: str,
seat: tuple,
pattern_a: Optional[BoltPattern] = None,
pattern_b: Optional[BoltPattern] = None,
fastener: Optional[Fastener] = None,
thickness: Optional[float] = None,
torque_nm: Union[
float, tuple[float, float], None
] = None,
min_engagement_mm: Optional[float] = None,
washer_mm: float = 0.0,
order: Union[str, Sequence[str]] = "star",
strict: bool = True,
catalog=None,
) -> Joint
Declares the bolted joint between obstacles a (threaded holes)
and b (clearance holes, thickness metres thick) and puts its
frames in the scene: <name>/seat at the mating plane's centre
(seat = its world pose, +Z out of a — where b's underside
lands), <name>/hole/<id> on b's top face over each hole (+Z out,
the head's seat — a screw is driven by aiming the tool tip's +Z
along it) and <name>/locator/<id> on the plane. The patterns must
agree hole for hole (same ids, same positions).
torque_nm is the joint's design value (a range, or one figure);
min_engagement_mm the thread engagement it needs (a drawing's
figure — 2×d in aluminium is the usual rule of thumb, but the joint
states it, botrail does not guess). order is "star" (derived) or
the hole ids in the order to tighten. With strict (the default) a
joint the screw cannot make — too little engagement, a tip through
the hole's bottom — is refused here rather than found on the line.
With catalog= — a workpiece spec pack's id or package directory
(what bt.parts.workpiece stood the parts from) — the patterns, the
screw, the torque range, the engagement and the thickness are read
from the pack's mounting: its flange face is a's threaded holes
and locators, its mount face b's clearance holes, its fasteners
the screw and the torque; nothing is typed in. Arguments given
alongside override what the pack states.
place
¶
place(
sq,
part: str,
*,
motions: Mapping[str, str],
close: Mapping[str, float],
open: Mapping[str, float],
expected: tuple,
touch: Sequence[str] = (),
grip_s: float = 0.4,
robot: Optional[str] = None,
prefix: str = "",
) -> Placement
Appends the fit of part to the sequence sq: the taught motions
approach (over the part), down (onto it), up, carry (over its
seat), seat (down onto the mate) and clear, with the gripper ramped
to close before the grasp and open after the release. expected
is the pose the part should stand at when seated — what fit_report
measures the bake against. touch names what the carried part is
meant to meet — its mate, the dowels — and declares that contact
allowed (Scene.allow_object_obstacle_contact), so setting it down
is not a collision. The release raises the internal signal
<sequence>/<part>_seated (Placement.seated): what a fastening of
the part waits on (fasten(after=[placement.seated])) — a row on the
interlock table, not a convention.
report_section
¶
report_section(
fastening: Fastening,
rows: Optional[list[dict]] = None,
fit: Optional[dict] = None,
*,
title: str = "Assembly",
) -> dict
The assembly section of a cell report (scene.cell_report(...,
sections=[...])): the joint — its parts, screw, torque, engagement
and order — the driver's timing, the fit of the placed part (a
fit_report) and the baked fastening rows (a fastening_report),
as Markdown and as JSON. What a reviewer reads before the interlock
table and the FAT rows.
rundown_s
¶
Seconds to run length_m of thread down at rpm: the feed is the
pitch per turn.
star_order
¶
star_order(
holes: Union[Sequence[Hole], Mapping[str, tuple]],
) -> list[str]
The tightening order a flange is brought down flat by: opposite pairs through the pattern's centroid, the innermost pair first (the middle of a rectangular cover), then the pairs spread around the circle rather than walked round it — 1, 5, 3, 7, 2, 6, 4, 8 on a ring of eight, the star pattern flange assembly guidance (ASME PCC-1) tightens in. Circular passes at rising torque are a matter of repeating the sequence.