Tool registry¶
Every end-effector tool used by code should have one versioned datasheet under
config/tools/. Simulation geometry, URDF links, calibration, and dataset
metadata must derive from that record instead of repeating dimensions.
Datasheet contract¶
Identify the schema version and coordinate origin.
State units, mounting frame, tip geometry, and contact assumptions.
Validate the fitted tool before generating derived geometry.
Record the datasheet revision with every run or placement artifact.
A datasheet’s calibration: block declares how the station probe may touch
the tool: face_kind: halo, a tube’s end, with the ring’s outside
(wall_radius_m), its hole (face_radius_m), where a rim touch puts the ball
(rim_touch_radius_m) and how far up the wall a side touch meets it
(side_touch_height_m). The laser’s nose and the ballpoint cartridge’s tube
declare one; the probe calibration refuses a tool whose datasheet does not
(ros/README.md §7). The block’s other fields (contact_reference,
accuracy_profile, feature) belong to the retired one-arm mechanical recipe,
and nothing reads them; they stay because a datasheet’s bytes are its tool
identity, which the URDF and every recorded dataset carry. The touch probe on
the installed palette replaces that recipe’s pits (docs/palette.md).
A fitted mechanical point is stored apart from tcp_z_m: a standoff tool
keeps its nominal working point, and a contact tool’s seated point carries an
unresolved writing-contact correction.
config/workspace.yaml retains mechanical_contact_* as calibration records:
coordinates, contact reference, accuracy profile, fit status and source session.
tatbot ros calib apply updates them with the adopted fit; a tool change clears
them. A pivot touch-off without a separate contact reference leaves those keys
null. Motion consumers use pen_tip_offset_* and the resolved working TCP;
the historical contact record alone grants no motion or fit qualification.
Resolved geometry¶
The datasheet body profile, a planted touch-off, and the working TCP are
different facts. scripts/lib/tool_spec.py resolves them once for the real
URDF, simulation URDF, FK, and dataset metadata:
mount_from_bodyplaces the unchanged physical body profile.body_tip_offset_mis where rendered/contact material ends.tip_offset_mis what the calibration physically planted.tcp_offset_mis the working point used by FK and planning.
For contact tools those three endpoints must agree within 0.5 mm. A
well-conditioned fixed-point/pivot touch-off qualifies the contact vector. For
an axisymmetric profile whose mount origin lies on its centreline, the vector
also determines the contact-relevant axis; roll is unobservable but does not
change the geometry. Metadata therefore separates
contact_geometry_status: pivot-calibrated from
body_pose_status: axis-inferred instead of calling the
whole result provisional. Optional independent body evidence can promote the
body envelope to independent-qualified for asymmetric clearance work.
Coordinates or a hand-edited status are not evidence, and a new touch-off
clears prior independent body evidence because the seat may have changed.
Non-contact tools may separate material and TCP only by their explicit
datasheet standoff.
Dataset metadata always records the concrete geometry that ran, including a nominal fallback and its source; null offsets are not a geometry contract.
The drawn line¶
contact_radius_m is collision and TCP geometry and tip_detail is render
geometry; neither says how wide a line the tool lays. That is the optional
line: mapping, recorded like measured: and validated at load:
line:
width_mm: 0.5 # in (0.05, 2.0]
status: assumed # measured | assumed
utc: 2026-09-15
method: "datasheet ball diameter; swatch not yet measured"
substrate: paper_pad
It is a deposition measurement on one substrate at one working speed, exposed
as ToolSpec.line_width_m; a partial block refuses. The DBV3 recipe records its
acquisition pen width. ROS preparation binds the fitted tool’s physical footprint
without editing the acquired geometry. The fill planner’s source/fixture tooling
can use the footprint for deduplication; native DBV3 paths retain repeated passes.
A status: assumed value is a placeholder for a measurement: draw a short swatch
at the working speed on the substrate, measure the line against a known pitch
(or a loupe), then record measured with the UTC and method.
The stroke: press or ride¶
A rotary machine’s tip reciprocates through its stroke. A page touch trips
only once the tip is pushed up to the top of that stroke, where it stops, so
the touched page is where the tip meets the paper at the top of its stroke,
whether the machine runs or not. Two optional fields place the stroke, and
ToolSpec.stroke_m and ToolSpec.tip_out_at_top_m validate them at load:
stroke_mm: 3.5 # the machine's stroke with this cartridge, in (0, 6]
tip_out_at_top_mm: 2.0 # the tip out of the cartridge's tube at the top of the stroke; negative: inside it
The collar that sets the tip’s protrusion moves the TCP, so a change needs a
new touch-off. How the drawing path uses these is motion tuning, motion.yaml
pen.mode, read at every draw against the fitted tool’s datasheet:
press: the machine stays off, the tip stays at the top of its stroke, and the path presses
pen.press.lift_minto the page; the arm’s and mount’s give keeps the tip on the paper.ride: the drawing stack runs the machine through its switch and the path rides
pen.ride.fractionofstroke_mmover the touched page, so the running tip meets the paper that far down each stroke and would reach the rest past it.
A tool that records neither field, or whose tip sits inside its tube at the
top of the stroke (a touch would meet the tube), cannot ride, unless the
tube’s end is its contact reference (calibration.contact_reference: tube_end, a needle cartridge). Its touches and wrist gauge then measure the
tube’s end, which is the TCP, so the path is that end’s height and riding
needs only stroke_mm. needle_reach_mm records how far the needles pass the
tube’s end at the bottom of the stroke; the run’s pen down event then says
how deep they go.
Public examples should use a clearly marked fixture tool. Physical dimensions, calibration values, and deployment choices that are not needed to build the software remain private.
Contact qualification is evidence-bound by the touch-off’s tool/frame identity, pose count, condition number, rotation spread, residual, datasheet tip range, and seat-angle range. Held-out and leave-one-out disagreement travel as uncertainty metadata; they never widen the collision/marking band. A named simulation recipe may use that bound to draw one mount-frame tip offset per shard. The draw remains fixed for the shard, like one physical seating, and the visible body, collision endpoint, IK TCP, and metadata all use the same resolved geometry. Different seeds span plausible calibration outcomes without allowing marks away from the simulated surface. Optional explicit body geometry separately revalidates its report digest and current-seat identity, falling back to the inferred axis if that evidence is missing or stale. Physical capture procedures and calibration values stay in the private operator documentation rather than this public contract.
Optional independent body qualification¶
scripts/tool_body_qualify.py validates an independently measured tool-body
axis against the current touch-off. This optional file-based study is useful for
asymmetric bodies and close-clearance work; without --write it is a dry run.
The input is a JSON report containing at least five chronological remove and reseat samples. Each sample identifies its touch-off session and records a body-profile origin, a unit body axis, and an independently planted tip in the tool-mount frame. The selected sample must be the final reseat and must match the current workspace touch-off. Measurements must come from an instrument independent of the planted-tip fit; deriving them from that same fit is not independent evidence.
Dry-run before writing:
python3 scripts/tool_body_qualify.py --ee-tool fixture-pen \
--report /path/to/body-reseat-report.json
python3 scripts/tool_body_qualify.py --ee-tool fixture-pen \
--report /path/to/body-reseat-report.json --write
A validation failure writes nothing. A passing write stores canonical report bytes, binds their digest and computed metrics to the workspace, and revalidates the result. Existing evidence is immutable unless the bytes are identical. A later touch-off clears the independent body evidence because the physical seat may have changed. Site-specific capture procedures, measured coordinates, and qualification records remain in private operator documents.
Ballpoint cartridge¶
The installed ballpoint uses ink.mode: cartridge: it writes from its internal
refill and never dips. There is no cartridge clock or supply declaration; ink
coverage is judged from the drawn marks, not elapsed wall time.
The Inlumino Heart Ink saturated-color ballpoint dot cartridges share the
lutin-ballpoint-dot tool geometry. Each color has a separate pigment identity
in config/inks.yaml: inlumino_saturated_sky_blue,
inlumino_saturated_purple, inlumino_saturated_green,
inlumino_saturated_pink and inlumino_saturated_yellow. Catalog RGB values
are nominal preview colors, not measured deposited color responses.
The ten-cartridge pack contains two of each color; config/inventory.yaml
records the package quantities. Bind each acquired DBV3 pen to a color resource
with tatbot-inks/3. At a color change the arm lands and the operator swaps the
cartridge by hand; the cartridges share the tip geometry, so the resumed run
keeps its tip calibration and measured page, with no new touches. A different
physical geometry needs calibration and new page touches. The set never dips.