Skip to content

Focus Test: default the Method to "Z axis steps" when the machine has a Z axis #550

Open

Description

@switch87

Split out from @atkaper's suggestion on #539 — the comment there asks for the "Z axis steps" method to become the default when the machine definition indicates it has a Z axis. Same idea, tracked separately so #539 stays about the Focus Test itself.

Would it be an idea to make the Method "Z- axis steps" the default if the machine definition indicates it supports a z-axis?

Most of this already exists

available_focus_modes() already does the capability filtering, and already orders the result by preference:

modes = [FocusMode.RAMP]
if machine is None or step_registry.get("CommandStep") is None:
    return modes
if not _driver_uses_gcode(machine):
    return modes
modes.insert(0, FocusMode.MANUAL)
if machine.has_z_axis:
    modes.insert(0, FocusMode.Z_AXIS)
return modes

So Z_AXIS is already first whenever it is available. has_z_axis is the right indicator too — it is Machine.axes.get(Axis.Z) is not None, i.e. a 2-axis laser simply omits Z.

The gap is only the dialog's initial selection

FocusTestDialog.__init__ seeds itself from the dataclass default instead of from that ordered list:

d = FocusTestParams()          # mode defaults to FocusMode.MANUAL
...
if d.mode in modes:
    self.mode_row.set_selected(modes.index(d.mode))

FocusTestParams.mode defaults to FocusMode.MANUAL, and MANUAL is in modes on every path that also reaches Z_AXIS, so the condition is always true and the dialog always opens on Manual — even on a Z-axis machine where Z_AXIS is offered and listed first. That is the whole of @atkaper's ask.

It also works by accident in the one case it handles correctly: with no machine, modes is [RAMP], MANUAL is not in it, nothing is selected, and the ComboRow falls back to index 0, which is RAMP.

Suggested shape

Default to the first available mode rather than to the dataclass value:

self.mode_row.set_selected(0)

since modes is already preference-ordered. That falls out correctly in all three cases:

machine modes dialog opens on
none, or no CommandStep, or non-G-code driver [RAMP] Ramp (unchanged)
G-code machine, no Z [MANUAL, RAMP] Manual (unchanged)
G-code machine, has Z [Z_AXIS, MANUAL, RAMP] Z axis steps (new)

Two notes

Leave the dataclass default alone. FocusTestParams() has no machine reference — it cannot — and it doubles as the source for every other field in that dialog (start_offset, step, count, …). Changing mode there would change the default for headless and scripted creation too, where the safe answer is still Manual. The capability decision belongs in the dialog, next to the modes list that already knows the answer.

Worth considering but out of scope here: the method could be greyed out with an explanatory row rather than merely absent when there is no Z axis, so a user on a 2-axis machine learns why it is not offered. available_focus_modes() already returns without it, and the command rejects a Z_AXIS params object with ValueError, so the current behaviour is safe — it is just not self-explanatory.

UI tests for the dialog live in builtin_addons/rayforge-addon-laser/tests/ui_gtk/test_calibration_test_dialogs.py; the backend behaviour of available_focus_modes() is already covered in tests/doceditor/test_calibration_tests.py.

Activity

  1. knipknap commented on Oct 12, 2026

    @knipknap
    Contributor

    Thanks for the very clear write-up, and for splitting this out so #539 can stay focused on the Focus Test itself — that was the right call.

    I agree with the suggestion. When a machine has a Z axis, there is no good reason the Focus Test should open on the manual method; the z-axis method is the more accurate and more convenient one, and it is exactly what a Z-axis machine is capable of. On machines without Z, nothing should change, and neither should the behaviour when no machine is set up at all.

    Your analysis matches what I see in the pull request: the list of offered methods is already sorted so the best-capable one comes first, and the dialog simply does not use that ordering for its initial selection. Picking the first entry of that list instead of a fixed default gives exactly the behaviour you describe: Ramp for machines that can't do better, Manual for G-code machines without Z, and Z axis steps as soon as a Z axis is present. I'd also keep the general default untouched so anything created headless or by scripts keeps the safe Manual fallback — as you say, that decision belongs in the dialog.

    I'll fold this into #539 before it merges, since that is where the dialog is being introduced. The greying-out idea with an explanatory hint for 2-axis machines is a nice touch; I'll consider it as a follow-up rather than holding up the test generators for it.

  2. added
    rca-doneA root cause analysis was attached
    and removed on Oct 12, 2026
  3. switch87 commented on Oct 13, 2026

    @switch87
    ContributorAuthor

    This is in #539 as 73ca5b7: the dialog now starts on the first entry of the preference-ordered method list, so it opens on Z axis steps on a G-code machine with a Z axis, Manual without Z, and Ramp without a machine. FocusTestParams keeps Manual as its default. A UI test covers the Z axis case, and #539 now closes this issue.

  4. switch87 commented on Oct 17, 2026

    @switch87
    ContributorAuthor

    Reminder: this one stays parked on #539 (the Focus Test PR itself). #539 is rebased on current main with Lint, UI and Stress green, so this picks up as soon as it merges.

    Nothing else is missing for the change: available_focus_modes() already returns [Z_AXIS, MANUAL, RAMP] on a G-code machine with a Z axis, so the fix stays inside FocusTestDialog.__init__ — seed from that ordered list instead of from FocusTestParams().mode — plus a test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    rca-doneA root cause analysis was attached

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions