Skip to content

Add probe-length compensation to tool-change offset math - #1

Draft
WhiteRott wants to merge 23 commits into
masterfrom
feature/tool-library
Draft

Add probe-length compensation to tool-change offset math#1
WhiteRott wants to merge 23 commits into
masterfrom
feature/tool-library

Conversation

@WhiteRott

@WhiteRott WhiteRott commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Summary

Three-part effort on top of this fork's existing Probe/Autolevel/Tool widgets:

  1. Tool library — a full data model + UI for managing a table of tools (number, name,
    shape type, diameter, flute length, overall length, flutes, notes, active-tool flag).
    Server: src/server/api/api.toolLibrary.js (CRUD + paging). Client: ToolLibrary widget
    with add/edit modals.
  2. Tool length offsets — global probe-length compensation is done (toolProbeLength,
    wired into Grbl WCS/TLO offset math and the Tool widget's G-code preview). Per-tool Z
    offsets, now that the tool library exists to back them, are still to come.
  3. Probing cycles — a new ProbingCycles widget with Edge/Skew Probe and Corner Probe
    cycles (2-touch fast-find/slow-confirm technique, least-squares line/circle fitting,
    probe-tip diameter compensation, configurable safety limits, retry-on-miss for
    spring-loaded styli, Z-lift on edge transitions to avoid cutting across stock). The
    original Probe widget was renamed to Zero Probe to disambiguate from this.

Context

Builds a tool library, tool length offset support, and extended probing cycles on top of
this fork's existing Probe/Autolevel/Tool widgets. Tool library and probing cycles are
largely complete; per-tool Z offsets are the remaining piece.

Test plan

  • eslint passes clean on all changed files
  • yarn dev builds and serves cleanly under Node 22
  • Probing cycles (Edge/Skew, Corner) dry-run tested on real grblHAL hardware, including
    the retry-on-miss and Z-lift fixes that came out of that testing
  • Per-tool Z offset math not yet implemented or tested
  • Global probe-length compensation (toolProbeLength) still not dry-run tested in
    isolation on real hardware

🤖 Generated with Claude Code

WhiteRott and others added 23 commits August 16, 2026 14:21
Backing field for a new global probe-stylus-length setting that
compensates for touch probes whose contact point sits below the
tool tip.
Adds the stylus stickout compensation to the existing touch-off
formulas: the probe's contact point sits below the tool tip, so
the true tool-tip Z at trigger is touch_plate_height +
tool_probe_length above the reported position.
Mirrors touchPlateHeight's load/save/unit-conversion path: action,
controller:state sync, loadToolConfig, and componentDidUpdate
persistence.
Adds an input next to Touch Plate Height for the touch probe's
stylus/stickout length, and updates the generated G-code preview
(GRBL/Smoothie/Marlin/TinyG branches) plus the custom-commands
example templates to include tool_probe_length in the Z offset math.
- Environment section now reflects the Node 22 migration (nvm default,
  yarn reinstalled under 22, the nvm auto-use PATH fix, gh CLI auth)
- Current-task section reflects actual progress: widget review done,
  toolProbeLength shipped on this branch with a draft PR open, tool
  library and probing cycles still not started
- Records the widget review findings and grblHAL capability notes
  that were previously only in conversation, not the repo
Auto-generated by the i18n tooling picking up the two new i18n._()
calls added to Tool.jsx (label + explanatory text). English has the
real values; other locales get empty placeholders for translators.
Port 8080 was occupied by a Docker container on this machine
(docker-proxy, part of the OpenClaw stack) — already documented in
CLAUDE.md, just never committed.
New CRUD API for a tools table (number, name, type, diameter,
fluteLength, length, flutes, notes), modeled on api.macros.js's
list-record pattern rather than api.tool.js's single-flat-config
pattern -- this stores many tool records, not one global config.

Routes: GET/POST /api/tool-library, GET/PUT/DELETE
/api/tool-library/:id. Verified end-to-end (create, fetch, read,
update with partial-field merge, delete, 404/400 error paths)
against a live dev server.
Mirrors the macros client wrapper's list-CRUD pattern (fetch/
create/read/update/delete) against the new /api/tool-library routes.
New widget (list view + add/edit modals) for managing a table of
tools: number, name, type, diameter, flute length, overall length,
flute count, notes. Modeled on the Macro widget's UI structure
(closest existing analog -- a record-list widget with CRUD), backed
by the new /api/tool-library endpoints.

Registered at the standard 3 touchpoints every widget needs:
Widget.jsx (id -> component map), WidgetManager.jsx (on/off toggle
list), defaultState.js (secondary-container placement + per-widget
config slot). Verified end-to-end against a live dev server under
Node 22 -- builds clean, widget code ships in the bundle, API works
through the full stack.

Deliberately out of scope for this commit: per-tool Z offsets (item
2) and probing-cycle integration (item 3) both depend on this
existing first. The top-of-window "Windows" dropdown menu is also
still pending, per plan, until last.
New POST /api/tool-library/:id/activate endpoint sets one tool's
active flag true and clears it on all others (atomic, single
config.set call). Tool Library widget gets a per-row select control
(green checkmark) and publishes the active tool over pubsub
('tool:active') on every fetch, so other widgets can react to
whichever tool is currently marked as mounted.
Replaces the free-text "type" field with a dropdown of 15 shape
types (Endmill, Ball End, Bullnose, Chamfer, V-Bit, Tapered Ball
Nose, Drill, Reamer, Dovetail, Radius, Tap, Thread Mill, Slitting
Saw, Probe, Custom), matching FreeCAD's CAM workbench tool-bit
naming for consistent terminology. Feeds directly into the
Visualizer's procedural per-shape 3D geometry (next commit).
tool geometry, and always-on camera rotation

This is one connected debugging session against real grblHAL
hardware -- each finding below builds on the last, so it's committed
together rather than as artificially separated pieces.

Coordinate-frame fix (the core bug):
- The grid/limits box was built in raw MACHINE coordinates
  (rebuildCoordinateSystems), but the cutting tool/pointer were
  positioned from WORK coordinates. These only agree when work-zero
  happens to sit near machine-zero -- with a real WCS offset (this
  machine's is 240/-130/-60mm), the tool rendered far outside the
  drawn work area. Switched updateCuttingToolPosition/
  updateCuttingPointerPosition to use machinePosition, matching the
  grid's own convention.
- Added a work-zero origin marker (small RGB axis flag) that stays
  behind at wherever X0/Y0/Z0 currently is, computed from
  machinePosition - workPosition (= WCO) minus the pivot -- unlike
  the tool, it doesn't track live position, so it's a persistent
  reference distinct from the moving tool indicator.
- The loaded G-code toolpath had the same class of bug: it was
  self-centering on its own bounding box instead of anchoring to
  work-zero like everything else. Added updateGCodePosition() using
  the same formula, so a loaded file now overlays the stock/marker
  correctly instead of floating near machine-home.
- Also flagged (not a code fix, a data fix applied via the API): the
  stored machine profile had zmin/zmax with the wrong sign relative
  to this machine's actual negative-going Z travel, which is what
  made the fixed code still look wrong until corrected.

Stock/material visualization (StockSize.jsx, PrimaryToolbar.jsx):
- New translucent stock box anchored the same way as the origin
  marker (front-left-top corner at work-zero), sized via a new
  "Edit Stock Size" dialog (Width/Length/Thickness), with a
  Show/Hide toggle alongside the other display-object toggles.

Procedural cutting-tool geometry (CuttingToolGeometry.js):
- Replaces the single fixed-shape bit.stl (uniformly scaled
  regardless of tool type) with type-aware Three.js primitives
  (cylinder/cone/rounded-tip combinations) sized to the active
  tool's actual diameter/length, dispatched by the new shape-type
  field. Removed the now-unused STL/texture loading path from
  helpers.js.

Always-on free camera rotation (SecondaryToolbar.jsx, constants.js):
- Removed the Pan/Rotate mode toggle -- TrackballControls already
  defaults to left=rotate, right=pan, wheel=zoom simultaneously
  (matching Candle's scheme) when nothing overrides the mouse button
  state. The toggle was actively fighting that default. Touch/tablet
  input is handled by a separate code path and is unaffected.
Auto-generated by the i18n tooling for the active-tool selection,
shape-type dropdown, stock size dialog, and stock visibility toggle
strings added this session. English has real values; other locales
get empty placeholders for translators.
Same theory throughout: compute known sample points in JS, probe
them one at a time via G38.2 (staying in G90, absolute targets =
point + signed search distance), accumulate contacts as GrblController
already does for the existing PRB report handling, fit geometry once
all points are in. No dependence on grblHAL macros/subroutines --
checked grblHAL's own changelog, M98/M99 support was only added in
build 20250202, and this machine's firmware (1.1f, 2023-01-29) predates
it by two years, so this had to be done in JS regardless.

- edgeprobe.js: N-point line fit (createEdgeProbePoints, fitLine) --
  the foundation. A single edge, 2+ points, least-squares angle.
- probecycles.js: fitCircle (Kasa algebraic least-squares, 3+ points,
  shared by Bore/Boss), createCirclePoints, fitRectangle (4-wall
  center/width/length, shared by Rectangular Pocket/Solid), and
  intersectLines (two fitted edge lines -> a corner point without
  assuming a perfect 90°).
- GrblController.js: edgeProbeState/cornerProbeState/circleProbeState/
  rectProbeState (mirroring the existing autolevel probeState shape),
  a shared trackProbeResult() helper factoring the "accumulate PRB
  report, check completion, emit :update" bookkeeping common to all
  of them, and edgeprobe/cornerprobe/circleprobe/rectprobe
  start/stop/getProbeState socket handlers.

Corner Probe specifically: reuses edgeprobe's point-generation and
line-fit twice (once per edge, 2+ points each) rather than a single
touch per edge, then intersects the two fitted lines -- handles a
non-90° corner correctly instead of assuming one.

Server-side only in this commit; only Edge/Skew Probe has a client
widget so far (next commit). Corner/Bore/Boss/Rectangular Pocket/
Rectangular Solid are probe-able via the socket API but have no UI yet.
First cycle with a UI, on top of the server-side infrastructure from
the previous commit: pick a probe direction (+X/-X/+Y/-Y, which
determines the line/probe axis split), a start and end point (with
pin-current-position buttons), point count, probe distance/feedrate,
and retract distance. Shows live progress while probing and, on
completion, the computed skew angle plus a table of the individual
contact points -- flags a warning if the edge isn't aligned to the
machine axis.

Registered at the standard 3 widget touchpoints (Widget.jsx,
WidgetManager.jsx, defaultState.js), placed in the secondary
container next to Probe/Autolevel.
Ball-tip stylus contact points are reported at the ball center, not the
true surface, so edge/corner probes correct each point by the stylus
radius along its probe axis before fitting lines/corners. Also add a
global Max Probe Deflection setting (alongside the existing Probe
Length stylus stickout) so Probing Cycles can warn about reduced Z
clearance and block a run whose configured Probe Distance would exceed
the machine's safe deflection limit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ng it

Each sample point is now probed twice: a fast approach to find the edge,
then -- after backing off and a short settle delay -- a slower touch to
the same target for an accurate, repeatable reading. Only the slow touch
feeds the line fit. Both touches are acknowledged to the client (live
phase status + a running touch log) so an in-progress cycle is never
ambiguous with a stalled one.

The real bug behind tonight's stalls: cncjs's periodic $G parser-state
poll writes to the serial port on its own timer, independent of the
feeder's send/wait-for-ok sequencing. A $G query already in flight when
a probe cycle starts has its [GC:...]+ok reply land mid-cycle, and since
that reply is consumed by matching a flag rather than by content, it can
swallow the probe's own 'ok' -- the machine goes idle but the next
queued line (e.g. the retract right after a touch) never gets sent, with
no error reported. Fixed by giving an active probe cycle unconditional
priority in the 'ok' handler, checked before the $G-reply tracking, plus
suppressing new $G polls entirely while a cycle is running.

Also surfaces the live probe pin state (Pn:) in the widget so a stuck or
disconnected probe input is visible at a glance, and handles a probe
that fails to trigger (G38.2 reaches its target with no contact) instead
of leaving the cycle hanging with no feedback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The generic "Probe" name was ambiguous once Probing Cycles existed
alongside it. This widget's job is specifically single-axis touch-off
to set a work/tool zero, so "Zero Probe" describes it more precisely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds Corner as a selectable probe type alongside Edge/Skew, with UI for
configuring both edges (start/end points, direction, point count, probe
distance, retract distance) plus the shared feed/backoff/settle/tip
fields. Server-side, corner probe is reworked to the same 2-touch
technique as edge probe (fast find, back off, settle, slow confirm),
sharing the pendingAcks-based 'ok' priority fix so it can't stall the
same way edge probe did earlier tonight.

Also fixes a real safety issue found during hardware testing: the move
to each probe point's approach position was a single diagonal G0,
which can cut straight across the corner when transitioning between
edges whose points are far apart in both axes. Both edge and corner
probe now always move one axis at a time -- line axis first, probe
axis last, so the final approach leg is always a straight move along
the direction about to be probed.

Not yet fully validated on hardware: an in-progress test hit a probe
that failed to find the edge at one sample point (likely a real
geometry/configuration mismatch, not a code issue -- the failure was
caught and reported cleanly, no stall). Machine left in Alarm state,
untouched, pending the operator's inspection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two hardware-driven fixes from today's testing:

Retry on missed contact: a G38.2 that reaches its target without
triggering raises a hard alarm in grblHAL, which previously ended the
cycle and left the machine locked for manual inspection. Spring-loaded
styli miss intermittently at slow feeds, so a single miss shouldn't end
an otherwise-good run. Failed touches now retry the whole point --
unlock, requeue, same search distance first, then +1mm, +2mm... up to
+5mm total before giving up and leaving it alarmed as before. The
feeder queue is explicitly cleared before requeuing so stale motion
from the failed attempt can't leak out after the unlock.

Z-lift on edge transitions: the move from the last X-edge point to the
first Y-edge point is the one point-to-point travel that crosses a real,
unpredictable distance across the part -- yesterday's axis-by-axis fix
only removed the diagonal, it couldn't know whether the resulting path
was clear, and it wasn't (bent a probe stylus). That transition now
retracts to a configurable safe Z, travels, and plunges back down.
Within an edge, consecutive points stay axis-by-axis at probing depth.

Verified on hardware: full 6-point corner probe completed end-to-end,
including two retries that recovered correctly and a clean edge
transition. Corner repeatable to ~0.01mm across runs.

Known issue, not yet fixed: the widget's Run button stays disabled after
a cycle completes normally until Stop is pressed. Tried clearing probe
state on completion; that reopened the ok-routing race that stalls the
final retract, so it was reverted -- the real cause is still open.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both sections had drifted badly out of date -- tool library and
probing cycles were still marked "not started" despite being
substantially complete. Rewrites the current-task and widget-map
sections to match actual repo state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant