Skip to content

Commit c74ca3f

Browse files
jaysukclaude
andcommitted
feat: tune with the axis loaded, keeping moves inside its travel limits
Closed-loop tuning previously assumed the motor was decoupled from the machine, so tuning moves (G1 H2) never checked position against frame limits. Now that tuning happens on the loaded axis, add a safety layer: before any tuning move, require the axis to be homed, centre it in its travel if needed (with a warning/confirm), and clamp move direction and distance to stay within a configurable margin of min/max. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent dceb7ca commit c74ca3f

3 files changed

Lines changed: 273 additions & 22 deletions

File tree

src/__tests__/limits.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { describe, expect, it } from "vitest";
2+
3+
import { getAxisLimits, midpoint, planSymmetricMove } from "../model/limits";
4+
5+
describe("getAxisLimits", () => {
6+
it("extracts min/max/position/homed from an object-model axis", () => {
7+
const axis = { letter: "X", min: 0, max: 300, machinePosition: 150, homed: true };
8+
expect(getAxisLimits(axis)).toEqual({ letter: "X", min: 0, max: 300, position: 150, homed: true });
9+
});
10+
it("returns null for an extruder (no letter)", () => {
11+
expect(getAxisLimits({ min: 0, max: 300, machinePosition: 0 })).toBeNull();
12+
});
13+
it("returns null when position is unknown", () => {
14+
expect(getAxisLimits({ letter: "X", min: 0, max: 300, machinePosition: null, homed: false })).toBeNull();
15+
});
16+
it("returns null when min/max are missing or degenerate", () => {
17+
expect(getAxisLimits({ letter: "X", machinePosition: 10, homed: true })).toBeNull();
18+
expect(getAxisLimits({ letter: "X", min: 100, max: 100, machinePosition: 10, homed: true })).toBeNull();
19+
});
20+
it("reports homed: false as-is", () => {
21+
const axis = { letter: "X", min: 0, max: 300, machinePosition: 150, homed: false };
22+
expect(getAxisLimits(axis)?.homed).toBe(false);
23+
});
24+
});
25+
26+
describe("midpoint", () => {
27+
it("averages min and max", () => {
28+
expect(midpoint({ letter: "X", min: 0, max: 300, position: 0, homed: true })).toBe(150);
29+
expect(midpoint({ letter: "Z", min: -5, max: 5, position: 0, homed: true })).toBe(0);
30+
});
31+
});
32+
33+
describe("planSymmetricMove", () => {
34+
const centred = { letter: "X", min: 0, max: 300, position: 150, homed: true };
35+
36+
it("picks the positive direction when there's more room that way", () => {
37+
const near = { ...centred, position: 10 };
38+
const plan = planSymmetricMove(near, 50, 2, 0.1);
39+
expect(plan).toEqual({ distance: 50, sign: 1 });
40+
});
41+
it("picks the negative direction when there's more room that way", () => {
42+
const near = { ...centred, position: 290 };
43+
const plan = planSymmetricMove(near, 50, 2, 0.1);
44+
expect(plan).toEqual({ distance: 50, sign: -1 });
45+
});
46+
it("clamps distance to the available headroom minus the margin", () => {
47+
const near = { ...centred, position: 10 };
48+
const plan = planSymmetricMove(near, 50, 2, 0.1);
49+
// headroom positive = 300 - 2 - 10 = 288, headroom negative = 10 - (0 + 2) = 8 -> negative smaller, positive picked
50+
expect(plan).toEqual({ distance: 50, sign: 1 });
51+
52+
const tight = { ...centred, position: 295 };
53+
const tightPlan = planSymmetricMove(tight, 50, 2, 0.1);
54+
// headroom positive = 300 - 2 - 295 = 3, headroom negative = 295 - 2 = 293 -> negative picked, clamps to 50 (< 293)
55+
expect(tightPlan).toEqual({ distance: 50, sign: -1 });
56+
});
57+
it("fails when neither direction has enough clear travel", () => {
58+
const cramped = { letter: "X", min: 0, max: 10, position: 5, homed: true };
59+
const plan = planSymmetricMove(cramped, 50, 2, 5); // only 3 mm clear each way, needs 5
60+
expect(plan).toHaveProperty("error");
61+
expect((plan as { error: string }).error).toContain("X:");
62+
});
63+
it("uses exactly the requested distance when it fits", () => {
64+
const plan = planSymmetricMove(centred, 5, 2, 0.1);
65+
expect(plan).toEqual({ distance: 5, sign: 1 }); // tie goes to positive (>=)
66+
});
67+
});

0 commit comments

Comments
 (0)