Cables and tubes (bt.routing)¶
Routes between the ports of the connection plan: the line each is made of (a spec), the clamps and ducts it passes, and what is derived from them — centerline, lengths, cable list, BOM rows and the checks. See Cables and tubes.
import botrail as bt
rt = bt.routing
rt.spec(scene, "tu0604", kind="tube", media=["pneumatic"], od_mm=6,
bend_r_fixed_mm=15, bend_r_moving_mm=20, order_step_m=20,
manufacturer="SMC", model="TU0604BU-20")
rt.clamp(scene, "clip", at=("arm", "forearm_link", (-0.2, 0.0, 0.05)), length_m=0.03)
rt.route(scene, "air_hand", carries="air_hand", spec="tu0604", via=["clip"], slack_m=0.15)
rt.geometry(scene, "air_hand")["cut_length_m"]
rt.cable_list(scene).save("cables.csv")
assert rt.report(scene).ok
routing
¶
Cable and tube routes: bt.routing.
A route is the physical run of a connection — which ports it joins (the
connection plan, bt.connections), which line it is made of (a spec:
outer diameter, bend radii, sold lengths), which way points, ducts and
clamps it passes. Everything else is derived every time it is read, from
the residents' current poses: the centerline with its bends, the cut and
purchase length, the BOM rows, the cable list and the checks. Move the
cabinet and the cable follows, and so does its length.
Numbers are declared, never guessed: a spec that states no bend radius
leaves the bend checks unknown (route_spec_unknown), it does not get a
default. A span between a clamp on a robot link and the next anchor is a
moving span: drawn at its time-zero shape here, verified by the rope
replay.
FILL_LIMIT
module-attribute
¶
Convert a string or number to a floating-point number, if possible.
HOLDER_KINDS
module-attribute
¶
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.
CableList
¶
One row per route — the cable list a wiring design, purchasing or an ECAD import starts from. Renders as CSV, Markdown or JSON.
RoutingReport
¶
The checks: findings (severity / code / route /
message and, when located, position, s_m and — on a cycle —
t_s) over the routes of the cable list, and, when a timeline was
given, the spans that move: what the kinematics say of each (the
holders' distance and twist over the cycle, hence the free length and
the stroke) and what the rope replay says (bend, stretch, sag, nearest
body). ok is "no error"; warnings and unknowns stay listed.
adopt
¶
Give route the way of candidate — a row of :func:suggest,
or its index in that list.
along
¶
along(
on,
*,
pitch_m: Optional[float] = None,
face: Optional[str] = None,
offset_m: Optional[float] = None,
inset_m=None,
) -> dict
A way point meaning "run along the body on": an obstacle's name,
or (robot, link) for a robot link.
The route is laid on a face of the body's collision bounds — face
("+x" … "-z", in the body's frame) when given, else the side
face, one along the body's long axis, nearest the way in and the way
out — the line's radius plus the clamp's standoff off the surface, and
held by auto clamps every pitch_m (the route's
clamp_pitch_m, 0.3 m, when unset) — <route>/c0, c1, … in the
route's anchors, counted in the BOM under the route's clamp
article. On a robot link the run rides the link: its spans are
dressed, replayed with the arm but not sized for free length, and
its ends are drawn in until they clear the robot's other links (the
joint housings at either end of an arm link). A
lateral offset between the way in and the way out smaller than a pitch
is taken up by the way in, so the run itself is parallel to the body's
edge; the entry and the exit are the projections of the neighbouring
way points, so the run covers what lies between them. offset_m
moves the run sideways on its face, along the body's axis across the
run (toward that axis's positive end) — two lines tied along one face
side by side, each with its own clips, are offset_m=0.007 and
offset_m=-0.007. inset_m stops the run short at its ends, past
the clearance of the neighbouring links: one figure for both ends or
(way_in, way_out) — inset_m=(0.12, 0) leaves the first 12 cm of
an arm link free for the loop that climbs to it.
animate
¶
animate(
scene,
timeline,
*,
routes=None,
settle_fixed: bool = False,
ground_z=0.0,
step_s: float = 0.0020833333333333333,
spacing_m=None,
axial_hz: float = 500.0,
bending_hz: float = 120.0,
friction: float = 0.5,
margin_m: float = 0.5,
arm_links: bool = True,
settle_s: float = 1.0,
rest: str = "straight",
rod_hz: float = 120.0,
solver: str = "physics",
rigidity_nm2=None,
)
Replay the routes' moving spans as ropes against a baked cycle.
Every moving span (between a clamp on a robot link, or a port on one,
and its neighbour) becomes a bt.rope track named
route:<route>#<span>: held at both ends by what holds it in the
plan (a port's straight_m, half a clamp's length_m; a fixed or
rotating holder holds the line's direction too — one sample spacing at
least, led along its direction, the bisector of its legs, or the
run of an along clip — so the line leaves the holder the way it
points it; a strap holds a point; a port on a moving holder is a
swivel fitting and holds a point). The spans the line slides through
a holder between are one rope, through a ring on the holder's body
at each sliding holder, named after the first of them, with their
slack together. Each starts from its time-zero polyline with its share of the route's slack_m
hung in as a dip, at the line's radius, colliding with the enabled
obstacles within margin_m of it (not the ducts it runs in, not the
residents its holders sit on, not a body its starting polyline already
passes through — the replay would blow apart on it; the static report
names that clash) and with the floor at ground_z (None for
none). A span dressed along a link (both ends on it) is replayed the
same way; the stretch between two holders of a carrier is replayed
once, as the hose — carrier:<name>#<i>, the hose's diameter, its
mass plus the lines inside, the largest slack any of them brings — and
every line in it reads that track. A span held on a robot collides
with that robot's links — it rests on the arm instead of falling
through it — except a holding link whose shape holds the held point
(a clip seated in the casting, which would push its own held particle
away). The slack's dip is hung on the side with the most room (a stiff
line keeps the loop it is laid in), and a link the starting shape still
runs through is left out with a warning (the report then names the
contact). arm_links=False leaves the arm out
altogether. settle_fixed=True replays the
fixed spans too, so they hang under gravity instead of standing on
their polyline. Every rope first settles for settle_s with its
holders parked at time zero — it was laid along a design polyline, not
a hanging shape — before the cycle runs. The ropes are computed side
by side (they do not interact).
The scene's current poses are time zero, as for bt.rope.animate.
Returns the new timeline; :func:report with it judges the spans. A
line rests straight (rest="straight": a tube or a cable off the
reel, bent only by what holds it and by its weight — not the dip it is
laid in) and is a rod, not a string (rod_hz=120: windows of a few
particles each drawn toward their straight rest shape, so a bend
anywhere is resisted — a tube holds a round loop between its holders
instead of hanging in a V; bending_hz=120 is the fold-resisting
spring beside it). The fixed spans, settled only to hang, stay ropes.
The stiffness is the solver's native setting, not the line's, and it
is bounded by the step — past about twice this the replay tears:
shape, clearance and slack are judged, tension is not.
solver="static" draws the spans instead of simulating them (see
:func:botrail.rope.animate): at every frame each span is the rod at
rest between its holders — of least bending and gravitational energy,
its length what the plan gives it, held by every holder as the replay
holds it (a fixed port's stub and a run's clip in direction too; a
moving port, a swivel fitting, and a declared clamp at their points),
clear of every link of the arm and of the obstacles around — a harness
configurator's picture, with no dynamics, settling or spring settings. rigidity_nm2 is the flexural rigidity that balances the
bend against the weight (None: a polyurethane tube's for each
line's diameter). step_s, settle_s, rest, rod_hz and the
spring frequencies do not apply to it; the report judges its frames as
it does the replay's.
attach
¶
Where something sits, as the plan stores it.
at is (obstacle_or_frame, (x, y, z)) — the name of an obstacle
(it follows set_obstacle_pose) or, failing that, of a frame — or
(robot, link, (x, y, z)) for a point on a robot link (it follows the
joints), the offset in the resident's own frame in metres. A dict with
kind (obstacle / link / frame) is taken as is.
cable_list
¶
cable_list(scene) -> CableList
The cable list: every route with its line, ends, lengths and status.
carrier
¶
carrier(
scene,
name: str,
*,
holders=None,
inner_mm=None,
od_mm=None,
bend_r_moving_mm=None,
torsion_deg_m=None,
mass_kg_m: Optional[float] = None,
stroke_m: Optional[float] = None,
manufacturer: Optional[str] = None,
model: Optional[str] = None,
catalog: Optional[str] = None,
robot: Optional[str] = None,
) -> str
A protective hose or 3D energy chain dressed along an arm: the dress pack the lines to the hand run in together.
holders are where it is fixed, in order (each as :func:attach,
typically (robot, link, offset) on the upper arm, the forearm, the
wrist — or dict(at=..., kind=..., direction=..., axis=..., stroke_m=..., force_n=...)
with the holder's kind, direction and rail as for :func:clamp: a
rotating holder lets the hose roll, a sliding one lets it slide
through, a retracting one is the kit's retraction unit); inner_mm
its bore. A route passes it as one way point,
via=[name], and takes the holders as its anchors; every route
through it is one bundle, whose cross-section is checked against the
bore (duct_fill) and whose media are kept apart (route_separation
for power with signal). The hose is a line of the BOM
(cable.carrier) as long as the longest route dressed in it. Over a
cycle, :func:animate replays the stretch between two holders as one
hose — od_mm thick, mass_kg_m of its own plus the lines
inside — and the lines read that replay (carrier:<name>#<i>).
stroke_m is what the kit's retraction takes up, when it has one.
With catalog= (a cable.carrier spec pack: a conduit, or a
robot's dress-pack kit) the bore, the diameter, the bend radius, the
torsion, the mass and the stroke the pack states are the defaults and
its maker and part number the identity; a bore nobody states leaves
the bundle's fill unchecked and the requirement row saying so. A kit
that states where its holders go (holders_at: a link and an offset
in mm per holder) places them on robot when you pass no
holders.
The carrier is a line of scene.requirements(): the cell asks it for
a bore (the bundle at 50 % fill) and, over a baked cycle, the hose
length between the holders, the stroke, the twist per metre and the
bend radius the replay showed — compared with what is declared here
(or, through catalog=, what a kit states), and the figures
bt.catalog.search_for looks for.
clamp
¶
clamp(
scene,
name: str,
*,
at,
length_m: Optional[float] = None,
kind: Optional[str] = None,
direction=None,
axis=None,
stroke_m: Optional[float] = None,
force_n: Optional[float] = None,
manufacturer: Optional[str] = None,
model: Optional[str] = None,
catalog: Optional[str] = None,
) -> str
A clamp: a holder the line is held in (at as :func:attach).
On a robot link it is where a moving span starts or ends; length_m
is the material the holder keeps straight. kind is what the holder
leaves the line free to do — "fixed" (a rigid clip or bracket: the
place, the direction, no roll, no slide; the default), "rotating"
(a rotating holder: the line rolls about its own axis), "sliding"
(a guide the line slides through, and rolls in), "strap" (a
strap: the point, and the line turns and rolls) or "retracting"
(a carriage on a rail, holding like a fixed clamp, pulled toward the
rail's start: a retraction unit, with axis the rail's direction in
the body's frame, stroke_m how far it runs and force_n the
pull) — and direction the way the line runs through it, in the
body's frame (the bisector of its two legs when unset). Every clamp is a line of the BOM
(cable.clamp); identical articles merge. With catalog= (a
cable.clamp spec pack) the article's maker, part number, width (as
the holding length) and kind come from the pack.
duct
¶
duct(
scene,
name: str,
*,
path,
inner_m,
manufacturer: Optional[str] = None,
model: Optional[str] = None,
catalog: Optional[str] = None,
) -> str
A duct or tray as a support only: the centerline a route follows end
to end (path, world metres, two or more points) and its inner
cross-section inner_m = (width, height). bt.parts.duct builds
the boxes on the floor and declares this in one call; call this
directly for a duct whose body is already in the scene (imported
geometry) or needs none.
geometry
¶
One route's derived shape at the current poses: centerline,
s (arc length at each point), anchors (each with its
position, s_m, moving and the tangent the line runs
through it along: a port's stub, an auto clamp's run, a declared
clamp's bisector), spans, bends, length_m,
cut_length_m, purchase_length_m.
remove_spec
¶
Remove a spec; routes made of it report route_endpoint.
remove_support
¶
Remove a clamp or duct; routes that pass it report route_endpoint.
report
¶
report(
scene,
timeline=None,
*,
dt: float = 0.05,
sag_limit_m: float = 0.15,
) -> RoutingReport
Resolve every route at the current poses and check it: bends against the spec's fixed radius, the exit out of each connector, the cut length against the sold lengths and the maximum run, fixed spans against the enabled obstacles and the robots at their current pose, routes against each other, and the connections placed but not yet routed.
With a baked timeline the moving spans are judged over the cycle
too, every dt seconds: the distance between a span's holders gives
the free length it needs (route_tension when the line between them
is shorter) and the stroke a retraction takes up; their whole relative
rotation the twist (route_twist against the spec's
torsion_deg_m — bend and torsion together, the worst case); and,
where :func:animate replayed the span as a
rope, its tightest bend (route_bend_moving), stretch
(route_stretch), sag over sag_limit_m (route_sag) and the
nearest body (route_contact / route_clearance) — each finding
with its time.
restore
¶
Restore a serialized routing plan (specs, supports, routes).
route
¶
route(
scene,
name: str,
*,
carries,
spec: str,
via=(),
orthogonal: bool = False,
slack_m=0.0,
collide: bool = True,
color=None,
clamp_pitch_m: Optional[float] = None,
clamp=None,
) -> str
Declare (or replace) a route.
carries names the connections (bt.connections) this line runs —
one, or several for a multi-core cable; the first decides the two end
ports, which must have geometry (at= / exit= on the port).
spec names the line. via is the ordered way: points (x, y,
z), clamp names, duct names (:func:through), bodies to run along
(:func:along). orthogonal inserts axis-aligned elbows between way
points (z, then x, then y), the way a line runs along a wall and a
floor. slack_m is material beyond the measured run — the service
loop a moving span needs: one figure shared by the route's moving
spans, or a list with one figure per moving span in route order.
collide makes the fixed spans colliders for the planner, the tick
verification and the clearance readout (named route:<name>#<span>);
the static clash check runs either way. clamp_pitch_m is the clamp
pitch of the runs along a body, clamp the article those clamps are
(with kind as for :func:clamp: straps along a cobot's arm are
clamp=dict(kind="strap", ...)) — the article those clamps are
— a dict with manufacturer, model, catalog, length_m
(material held straight in each) and standoff_m (the saddle's
height, 5 mm when unset).
spec
¶
spec(
scene,
name: str,
*,
kind: Optional[str] = None,
media=None,
od_mm=None,
bend_r_fixed_mm=None,
bend_r_moving_mm=None,
mass_kg_m=None,
max_length_m=None,
torsion_deg_m=None,
end_allowance_m: float = 0.0,
sold_lengths_m=None,
order_step_m=None,
manufacturer: Optional[str] = None,
model: Optional[str] = None,
catalog: Optional[str] = None,
color=None,
) -> str
Declare (or replace) a line — the rule every route made of it follows.
kind is cable, tube or hose; media the media it may
carry (power / pneumatic / signal / network; unstated =
unchecked). Dimensions are in millimetres: od_mm the outer diameter,
bend_r_fixed_mm the minimum bend radius laid fixed (the fillet at
every corner), bend_r_moving_mm the one in continuous motion.
sold_lengths_m (6 and 12 m robot cables) or order_step_m (20 m
reels) decide the purchase length; end_allowance_m is added at each
end for stripping and the connector; max_length_m is a product or
standard limit on one run; torsion_deg_m the twist the line takes
per metre of free length (a moving span's holders turning about each
other). Identity (manufacturer / model /
catalog) goes onto the BOM row of every route made of this line.
A value you do not know you leave out: the checks say unknown.
With catalog= — the id of a line's spec pack (cable.* /
pneumatic.tube), or a package directory — the figures the pack
states (od_mm, the bend radii, mass_kg_m, media,
max_length_m, sold_lengths_m / order_step_m) are the
defaults, its maker and part number the identity, and the reference is
recorded; what you pass explicitly wins, and what the pack does not
state stays unknown.
suggest
¶
The ways route could take through the ducts the cell has, best
first. The way points you fixed — points, clamps, carriers, runs along
a body — stay where they are, in order; every ordered choice of up to
three ducts is tried between them, each candidate resolved and checked
at the current poses. A candidate carries its via (what
:func:adopt or :func:route takes), the ducts it passes,
length_m, cut_length_m, bends, supports, the static
errors / warnings with their codes (the ducts' bundle
findings included), its open_m (the run outside any duct), a
score (length + the open run again + 0.3 per bend + 0.2 per
support: a metre in a duct beats a metre on the floor) and current
for the way the route takes now. The order is
by errors, then score, then warnings — the same every time for the
same cell. Free space is not searched: a candidate is a choice of
ducts, not a path around an obstacle.
sweep_holder
¶
sweep_holder(
scene,
timeline,
carrier: str,
holder: int,
*,
x=None,
y=None,
z=None,
dt: float = 0.05,
)
Try places for one holder of a carrier and table what the hose then goes through over the baked cycle.
holder is the holder's index in the carrier's list; x / y /
z are the candidate coordinates in the holder's own frame (the
link's), each a list — the grid is their product, an axis not given
keeps the holder's current value. Nothing is re-baked: the cycle is the
same, only the plan's holder moves, and the kinematic report is read
again at every point. Each row carries the holder's coordinates and
stroke_m (the largest over the spans the holder ends),
free_length_m (what the hose needs between its holders, in total),
twist_deg (the largest over those spans) and errors (the route
findings, static and over the cycle, that are errors on the routes
dressed in the carrier). The replay is not run: contact is not among
the figures. Returns a :class:botrail.study.Sweep — best("stroke_m")
is the place a retraction has least to do — and leaves the plan as it
was.
through
¶
A way point meaning "run through the support name" — the same as
passing the support's name as a string in via.