Skip to content

Initial draft proposal: ACE_BG_WAIT - Prioritize Cold Pull over Carriage Picker - #186

Draft
docx-tech wants to merge 25 commits into
mainfrom
draft-bg-swap-priority
Draft

docx-tech wants to merge 25 commits into
mainfrom
draft-bg-swap-priority

Conversation

@docx-tech

@docx-tech docx-tech commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Relates to #172

Objective

Allow the toolhead background swap sequence to wait during Phase 1 (cold pull/tip forming) rather than aborting the background swap and restarting inline when parked time is short.

Background Swap Phases (Reference)

  • Phase 1: Hotend Prep & Cold Pull (~70–85s)

    • Process: Heats hotend to temperature, executes pre-prime (if configured in table), forms the tip, and executes the cold pull break.
    • Current Behavior on Pick: Aborts swap completely. Reverts to a full inline swap because old filament is still seated in the hotend melt zone.
    • Proposed Behavior: Park active head over discard chute (MOVE_TO_DISCARD_FILAMENT_POSITION) and wait for Phase 1 to finish so the pre-prime and tip forming are not discarded and repeated.
  • Phase 2: Bulk Retract (~15–20s)

    • Process: Retracts the unloaded filament back to the ACE unit, verified via the filament decoder.
    • Current Behavior on Pick: Does not abort. The tool change already blocks/waits for the retract to complete before continuing.
  • Phase 3: Transport / Pre-Staging (~15–20s)

    • Process: Drives the incoming filament forward from the ACE until it triggers the toolhead sensor.
    • Current Behavior on Pick: Halts the background feed, leaves filament pre-staged at the toolhead sensor, and transitions smoothly into the inline load.
  • Phase 4: Dock Purge & Anti-Ooze (Full Mode Only)

    • Process: Presses against dock lever, grips, primes/purges through the dock, and executes anti-ooze retraction.
    • Current Behavior on Pick: In transport mode, this part naturally executes inline once the head is picked.

Proposed Architecture

  1. G-Code Post-Processor (multiace/tools/post_process_virtual_toolheads.py):

    • Hook into rewrite_head_mode_to_file.
    • Identify arrival T commands where the incoming head is currently performing a background operation.
    • Inject ACE_BG_WAIT HEAD=n immediately prior to the arrival T tool change macro.
  2. Wait Logic & State Engine (multiace/klipper/extras/ace_bg_swap.py & ace.py):

    • Implement handler for ACE_BG_WAIT HEAD=n [TIMEOUT=x].
    • Query is_busy(head):
      • If the target head is in Phase 1 (heat, pre-prime, tip forming, cold pull): block until Phase 1 completes and transitions to Phase 2 (bulk retract), then release execution to the tool change.
      • If wait exceeds timeout: fall back cleanly to current abort behavior.
      • If the head is already in Phase 2/3 or idle: pass through immediately.
  3. Nozzle Safety / Anti-Ooze:

    • Park the currently active printing head over the discard chute using MOVE_TO_DISCARD_FILAMENT_POSITION before entering the wait state.
    • Restore target position once the wait completes.
  4. Invariants Maintained:

    • Background operations remain serialized (one at a time).
    • Bulk retract operations are never interrupted.

@decay71 Opening this draft early as requested to review the hook locations and architecture before diving into full implementation. Does this setup match what you had in mind?

@docx-tech docx-tech added the enhancement New feature or request label Oct 21, 2026
@decay71

decay71 commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Thanks, the phase description matches the code and the architecture is the right shape. Four things to settle before you build:

  1. Stamp rule: the preflight cannot know at rewrite time whether the head is still busy, that is runtime. Stamp ACE_BG_WAIT HEAD=n before every arrival T whose head got an ACE_BG_SWAP stamp earlier; the engine passes through immediately when nothing is running. head_mode_bg_stats must use the same condition so the preview stays truthful.

  2. Phase accessor: is_busy(head) is binary. Add something like phase(head) over self.state[head] in ace_bg_swap.py: FA_STOP / HEAT / PULL = wait, RETRACT or later or not running = pass through.

  3. Position: ace_swap.py already has _bg_wait_park / _bg_wait_unpark (homed guard, position snapshot), but they are written for the wait AFTER the pick, where the following ACE_SWAP_HEAD takes the snapshot as its saved position. Before the T the active head would be picked while parked over the chute, and the swap would then save and later restore the chute position instead of the print position. ACE_BG_WAIT must restore the position before it returns, or hand the snapshot through to the swap. Please pick one explicitly.

  4. When waiting pays: if the op is still at the start of HEAT, waiting (~80 s) plus the bulk retract plus the inline load adds up to about an inline swap, so only the pre-prime is saved, not time. I would wait only while the op is in PULL (or in HEAT with most of the heat-up done), otherwise abort as today. The timeout fallback must just continue, never raise or pause.

One more constraint: please keep ace.py untouched. Put the ACE_BG_WAIT handler and the phase accessor in ace_bg_swap.py and reach the park helpers via self.printer.lookup_object('ace', None) with a getattr fallback, the same way cmd_ACE_BG_SET_HEAD reaches _wt_persist. ace.py is being split into mixins on the dev branch right now, so any edit there would have to be re-ported by hand.

@docx-tech

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough feedback and clear constraints @decay71. Here is the settled plan addressing each point:

  1. Stamp Rule: Understood. The post-processor will stamp ACE_BG_WAIT HEAD=n before every arrival T whose head received an earlier ACE_BG_SWAP. The engine will pass through immediately at runtime if the head is idle or not in a wait-eligible phase. I will make sure head_mode_bg_stats mirrors the exact same logic.
  2. Phase Accessor & Gating: I'll implement phase(head) over self.state[head] inside ace_bg_swap.py. We will gate the wait strictly to PULL (or late HEAT where the live nozzle temp is dynamically within a small delta of the configured tip-forming unload_temp). If it is earlier in the thermal ramp, it passes through immediately to abort as today.
  3. Position (Option A): Going with self-contained restoration. ACE_BG_WAIT will snapshot print coordinates, move to the discard position while waiting, and restore back to the original coordinates before returning. This leaves the arrival T macro completely unaffected.
  4. Timeout / Fallback: On timeout or pass-through, the command will return cleanly to let normal inline swap handling take over—never pausing or raising an exception.
  5. Code Isolation: ace.py will remain completely untouched. All wait logic and phase accessors will live in ace_bg_swap.py, using self.printer.lookup_object('ace', None) with getattr fallbacks.

I'm proceeding incrementally on draft-bg-swap-priority: starting with the command registration and phase accessor skeletons, then verifying the park/restore helper, before wiring the wait loop and post-processor.

@docx-tech

docx-tech commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

Implementation & Testing Roadmap

  • Milestone 1: Command Skeleton & Registration

    • 1.1: Register ACE_BG_WAIT command in ace_bg_swap.py.
    • 1.2: Parse and validate the HEAD parameter with error handling.
    • 1.3: Add basic debug console response and verify registration via Fluidd/Mainsail console.
  • Milestone 2: Bench Test Harness Macro

    • 2.1: Implement dry-run test macro (TEST_BG_SWAP_BENCH) in printer config.
    • 2.2: Add steps to pick up an alternate toolhead (e.g., T1) and move to safe resting coordinates.
    • 2.3: Trigger background swap on docked head (HEAD=0) under controlled conditions.
    • 2.4: Verify state inspection and interruption workflow without running live prints.
  • Milestone 3: Phase Accessor & Dynamic Gating Logic

    • 3.1: Implement phase(head) helper exposing self.state[head] (returning 'IDLE' fallback).
    • 3.2: Implement is_wait_eligible(head) helper gating on PULL or late HEAT.
    • 3.3: Add dynamic extruder heater query comparing live temp to configured tip-forming setpoint.
    • 3.4: Verify live phase transitions output correctly to the console via test harness.
  • Milestone 4: Self-Contained Park & Restore (Option A)

    • 4.1: Snapshot current toolhead coordinates prior to park move.
    • 4.2: Move active toolhead to designated discard position upon wait eligibility.
    • 4.3: Restore previous coordinates cleanly upon wait completion or exit.
    • 4.4: Verify incoming arrival T macro remains untouched and receives expected toolhead state.
  • Milestone 5: Wait Loop & Timeout Handling

    • 5.1: Implement reactor polling loop checking for phase transition to RETRACT.
    • 5.2: Add timeout guard sized to cover remaining PULL work (including waittemp/passive cooldown) to prevent premature aborts or indefinite print stalls.
    • 5.3: Implement graceful fallback exit on timeout or pass-through without raising exceptions or pausing.
  • Milestone 6: Post-Processor Integration & Verification

    • 6.1: Update post_process_virtual_toolheads.py to stamp ACE_BG_WAIT HEAD=n prior to arrival T.
    • 6.2: Mirror identical injection condition inside head_mode_bg_stats for consistent statistics.
    • 6.3: Test sliced multi-material G-code to verify command injection and accurate post-processor stats.

I'll be tackling Milestones 1 through 5 on hardware first using the bench harness to verify real timing and motion on the machine. @decay71 if you'd like to collaborate or take on Milestone 6 (the post-processor stamping and stats alignment) in parallel, please feel free! Otherwise, I'm happy to draft that as well once the Klipper command behavior is proven on hardware.

@decay71 decay71 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, milestone 1 looks right: a pass-through skeleton can't stall a print. Four notes:

  1. Timeout: 15-20 s is too short for PULL on the current engine. With a pre-prime configured, PULL contains the pre-prime push and a waittemp down to the pull temperature, which is passive cooling on a parked head without a fan; custom tip-form tables can carry waittemp steps too. Please size the timeout to cover the remaining PULL work rather than a fixed 15-20 s, otherwise the wait mostly expires and aborts as today.
  2. Please move BG_WAIT_SPEC.md into the PR description (or under multiace/); a file in the repo root would end up in the public tree.
  3. Please drop the version bump and the "Oct 22 2026 | Author" comment on BG_SWAP_VERSION; we keep names and dates out of the code and set the version on our side. You will of course be credited in the README.
  4. Small one in the spec: the wait is not about head collisions, it keeps the docked head's cold pull from being aborted.

Milestone 6 is yours too, unless you think it needs me: I am deep in the new driver code right now, which is meant to bring in other devices later. It should be done in time for the next release.

…gle and transition logging

- Implement [gcode_macro TEST_BG_SWAP_BENCH] in ace.cfg supporting ACTIVE_HEAD, TARGET_HEAD, TARGET_SLOT, and SIMULATE=0|1.
- Add centralized _set_state() and phase(head) helpers in ace_bg_swap.py for live auto-printed console phase transitions.
- Fix load_enabled parser in ace_bg_swap.py to support 'transport' alongside boolean representations without config errors.
- Comment out macro_layout_enabled and [ace_macro_layout] in ace.cfg to prevent Klipper startup errors on older firmware.
- Mark Milestone 2 subtasks 2.1-2.4 complete in BG_WAIT_SPEC.md.
…UPT macro

- Register ACE_BG_AWAIT_PHASE command in ace_bg_swap.py for non-blocking phase synchronization.
- Implement [gcode_macro TEST_BG_SWAP_INTERRUPT] in ace.cfg to test interrupting background swaps at arbitrary phases (HEAT, PULL, RETRACT, BOOKKEEP, LD_FEED, LD_PRIME, DONE).
- Support configurable mid-phase DELAY parameter before triggering arrival pickup.
- Add PHASE_ORDER monotonic ranking in AceBgSwap to match target phase or any subsequent phase.
- Prevent 120s timeout on transient phases like BOOKKEEP (<1ms duration).
- Document combiner/splitter clearance math and 1050mm resolution in BG_WAIT_DEV_NOTES.md.
@docx-tech

Copy link
Copy Markdown
Collaborator Author

Hey @decay71

Quick update: Milestone 2 is complete, hardware-verified, and pushed to draft-bg-swap-priority.

If you want to test the new harness on hardware, here is a cool test you can run:

  1. Make sure a docked head (e.g. T0) is loaded with filament from its ACE from a slot other than 2 (or change TARGET_SLOT in the command below).

  2. Run this in the console:

    TEST_BG_SWAP_INTERRUPT ACTIVE_HEAD=1 TARGET_HEAD=0 TARGET_SLOT=2 AT=BOOKKEEP
  3. What you'll see:

    • Active T1 moves to the discard position while T0 does its background cold-pull and retracts into the ACE.
    • The moment retraction finishes at BOOKKEEP, the synchronizer catches it instantly and issues T0.
    • The carriage immediately picks up T0 right after retract while the new feed begins, and the engine catches the pick safely (abort before grip), leaving the filament cleanly STAGED at the toolhead sensor without grinding!

(Note: We also added TEST_BG_SWAP_BENCH SIMULATE=1 for dry runs without heaters/motors, and fixed load_enabled to accept transport).

I will go back through your comments next, try to address them in the next commit, and then I will dive into Milestone 3 (is_wait_eligible + dynamic thermal delta)!

@decay71 decay71 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the harness, the phase ladder makes the interruption cases easy to reproduce. A few things before this goes further:

  1. load_enabled: transport now maps to a full background load. Transport mode only exists in the 1.30b engine (your printer runs the 1.30b beta); 1.27b does not know it, which is why Klipper halted on your config. Treating it as true gives everyone who set transport the full dock purge again, which is exactly what transport mode avoids. Please drop the parser change and use true/false on this base; the transport case comes with the next release.
  2. ace.cfg: your local macro layout setting slipped in (macro_layout_enabled and [ace_macro_layout] are commented out, which would switch the layout off for everyone). The test macros were also inserted between "[gcode_macro ACEG__List] gcode:" and its body, so ACEG__List is now empty and its ACE_LIST line ends up at the end of TEST_BG_SWAP_INTERRUPT. And macros that pick heads and force a background swap should not ship in the default config; please keep them in a separate file for testing.
  3. _set_state sends every phase transition to the console. That adds 8 to 10 lines per background swap, and the console pipe already overflows in swap bursts; please log the transitions to klippy.log only (logging.info).
  4. BG_WAIT_DEV_NOTES.md reads as a working log ("next session pickup"); please keep that in your fork. The spec can stay in multiace/docs/.

… feedback

- Revert BG_SWAP_VERSION to 'v0.9' and remove date/author attribution comment.
- Clarify in BG_WAIT_SPEC.md that wait prevents premature cold pull aborts.
- Update Milestone 5.2 in spec to dynamically size timeout for remaining PULL work.
…d test macros

- Restore multiace/config/extended/ace.cfg to upstream defaults (re-enable macro_layout, restore ACEG__List body).
- Relocate TEST_BG_SWAP_BENCH and TEST_BG_SWAP_INTERRUPT into dedicated test_bg_swap.cfg.
- Revert load_enabled in ace_bg_swap.py to config.getboolean('load_enabled', True).
- Log state transitions to logging.info only to avoid console pipe flooding.
- Untrack working dev notes from git tree while preserving on disk via .git/info/exclude.
…etpoint query, and telemetry tracking

- Implement is_wait_eligible(head) gating helper: True on PULL or late HEAT ((tgt - cur) <= 15.0C).
- Add dynamic extruder heater query without hardcoded temperatures.
- Track operation, heat, and pull elapsed durations in AceBgSwap.
- Integrate DEBUG=1 diagnostic telemetry in ACE_BG_WAIT command.
- Add TEST_BG_WAIT_GATING test macro in test_bg_swap.cfg.
- Mark Milestone 3 subtasks 3.1-3.4 complete in BG_WAIT_SPEC.md.
@docx-tech

Copy link
Copy Markdown
Collaborator Author

@decay71 integrated your comments from milestones 1 and 2 in the two latest commits. I did make a test harness debugging mode which writes to the console during live testing so I can see how things line up. This option is off by default and everything is still written to the logs. Please let me know if anything else stands out.

Might be able to test Milestone 3 which is the big one (i.e. waiting during the late heating/tip forming) 🤞

@decay71

decay71 commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Hi, now printing yourtest file on 4 Aces and color copies enabled with my dev version, still 125 swaps.

@docx-tech

Copy link
Copy Markdown
Collaborator Author

Hi, now printing yourtest file on 4 Aces and color copies enabled with my dev version, still 125 swaps.

🥳 The swaps will be the same but the time savings could change. For my test file I have 3 colors swapping on one ACE.

…d restore

- Implement _snapshot_active_position() capturing toolhead XYZ, speed, coordinate modes, and E baseline.
- Implement _park_at_discard() with +2mm Z-hop and MOVE_TO_DISCARD_FILAMENT_POSITION.
- Implement _restore_active_position() restoring XY approach, Z landing, and speed.
- Hook parking into cmd_ACE_BG_WAIT when eligible (with FORCE_PARK=1 and DWELL parameters for bench testing).
- Add TEST_BG_WAIT_PARK_RESTORE macro in test_bg_swap.cfg.
- Mark Milestone 4 subtasks 4.1-4.4 complete in BG_WAIT_SPEC.md.
@decay71

decay71 commented Oct 24, 2026

Copy link
Copy Markdown
Owner

I don‘t think so, these are color swaps not toolchanges, and with 4 instead of 2 aces there should be less. But depends on the model. Stats tomorrow!

…with dynamic timeout and target RETRACT release

- Implement reactor polling loop in cmd_ACE_BG_WAIT holding arrival head at discard position.
- Sized dynamic timeout budget to cover remaining PULL work without premature aborts.
- Release hold immediately once background op reaches RETRACT (or subsequent/terminal phases).
- Graceful fail-open fallback: restores coordinates cleanly on timeout or failure.
- Add TEST_BG_WAIT_HOLD end-to-end test macro in test_bg_swap.cfg.
- Mark Milestone 5 subtasks 5.1-5.3 complete in BG_WAIT_SPEC.md.
…abort synchronization on ineligibility

- Add self._abort_requested signaling to immediately kill background greenlets when ineligible.
- Zero nozzle heater immediately upon ineligibility and await clean exit before returning.
- Tune dynamic thermal gating threshold to <= 25.0C (covering ~4-5s of remaining heat).
- Update BG_WAIT_SPEC.md with 25.0C setpoint.
@docx-tech

Copy link
Copy Markdown
Collaborator Author

Success on ABORTING to inline swap if T0 is issued during early heating stage (~<25s)

  • Log 10-24 17:07:17.267:[hold-test] Starting TEST_BG_WAIT_HOLD: ACTIVE_HEAD=2, TARGET_HEAD=0, TARGET_SLOT=2, WAIT_POINT_S=20.0s, DEBUG=1

Success on WAITING for bg swap to finish and parking active head before T0 can execute (vid below)

  • Log 10-24 17:15:16.124:[hold-test] Starting TEST_BG_WAIT_HOLD: ACTIVE_HEAD=2, TARGET_HEAD=0, TARGET_SLOT=2, WAIT_POINT_S=30.0s, DEBUG=1

https://github.com/user-attachments/assets/b12f8b99-c5b2-4b62-bd50-559470fe0502
klippy ACE_BG_WAIT.log

- Track background-swapped heads (bg_waiting_heads) in rewrite_head_mode_to_file.
- Stamp ACE_BG_WAIT HEAD=n immediately prior to arrival T<n> line.
- Discard from waiting set once arrival T is reached.
- Mark Milestone 6 subtasks 6.1-6.3 complete in BG_WAIT_SPEC.md.
@decay71

decay71 commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Looks great, congrats! Thanks for all the testing and the clear write-ups, it makes this easy to follow. Curious about the final time compared to your earlier runs.

  1. MIN_WINDOW 0 with ACE_BG_WAIT: yes, in principle. A window that turns out too short either ends in early HEAT (the op aborts and the normal inline swap runs) or in PULL (the arrival waits for the rest of the cold pull, which an inline swap would have to redo anyway). What remains: bg ops are serialized, so a doomed op briefly blocks the engine for another head, and the early abort only costs nothing if it keeps the preheat (see my point 2 on Initial draft proposal: ACE_BG_WAIT - Prioritize Cold Pull over Carriage Picker #186). Your print is the measurement for it.

  2. The 250: that is the nozzle wipe after the load, not your tip-form string. The swap wipes at the heater target the arriving head had before the swap, and when that is below 170 it falls back to the filament database load temperature (250 for Generic PLA). A head coming from a bg op arrives at target 0, because the engine switched it off. The short dip below 200 before that is the end of the load, which sets M104 S0 like stock does.
    This is already fixed on my dev branch: the bg op now hands the heater back to the slicer's own request instead of 0, and during a print a target below 170 is replaced by the last print temperature of that head. With both, the swap stays at your print temperature except for the very first swap of a head.

This is fixed in #196, merged into main now: the bg op hands the heater back to the slicer's own request instead of 0, and during a print a target below 170 is replaced by the last print temperature of that head. With both, the swap stays at your print temperature except for the very first swap of a head. Merge main into your #186 branch to get it. You will get a small conflict in ace_bg_swap.py at the HEAT and LD_FEED state lines: keep your _set_state(...) call and the new _heat_claim(...) / _set_heat(...) lines below it.

@docx-tech
docx-tech force-pushed the draft-bg-swap-priority branch from 2e364e6 to 53acde8 Compare October 25, 2026 07:24
@docx-tech

docx-tech commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator Author

FYI: Still need to test the latest commits (linked below) that merge #196 and passes to inline cleanly etc. (the printer is currently in use).

Untested commits:
53acde8
2c7dd72

@docx-tech

docx-tech commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator Author

Looks great, congrats! Thanks for all the testing and the clear write-ups, it makes this easy to follow. Curious about the final time compared to your earlier runs.

Thank you! Excited to see how the tests go. Thank you for your guidance; it kept the development very focused and fast by premptively solving potenital issues =)

What remains: bg ops are serialized, so a doomed op briefly blocks the engine for another head, and the early abort only costs nothing if it keeps the preheat (see my point 2 on Initial draft proposal: Prioritize cold pull over picker #186). Your print is the measurement for it.

Right. The early-heat abort on arrival takes ~0.1s to release the engine, and with your latest feedback now integrated, it preserves the preheat progress so the arrival inline swap doesn't restart from cold. I haven't parsed the full logs from the current run yet, but it might be in the logs I already uploaded?

Future Work: Engine Serialization

To capture the engine serialization question for future work, I opened Feature Request #197 to track decoupled per-head motion queues (trapq), which is outside the scope of this PR. ACE_BG_WAIT is already parameterized per head, so it will naturally support concurrency whenever that lands (see below).

'ACE_BG_WAIT` per Head Parameterization

In Python, self._busy is a set() of head indices (e.g. {0, 2}).

cmd_ACE_BG_SWAP Global vs. ACE_BG_WAIT Per Head:

  1. How the launcher (cmd_ACE_BG_SWAP) uses it (Global Gate):

    if self._busy:  # <-- Checks if ANY head is in the set! If len > 0, blocks everything.
        return _refuse('another bg op is running')
  2. How ACE_BG_WAIT uses it (Per-Head Membership):

    # In is_wait_eligible(head):
    if head not in self._busy:  # <-- Checks ONLY the arriving head!
        return False, details
    
    # In active abort on early heat:
    if head in self._busy and base_p in ('QUEUED', 'FA_STOP', 'HEAT'):
        self._abort_requested.add(head)
        while head in self._busy:  # <-- Waits ONLY for this specific head to exit!
            self.reactor.pause(...)

@docx-tech docx-tech changed the title Initial draft proposal: Prioritize cold pull over picker Initial draft proposal: ACE_BG_WAIT - Prioritize Cold Pull over Carriage Picker Oct 25, 2026

@decay71 decay71 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read through 53acde8 and the merge, the #196 conflict is resolved the right way. Two small things:

  1. _check_docked now says "ABORTED by arrival wait" for a real pick too, so the log no longer tells the two apart. Please keep the old "was PICKED mid-sequence" text for a pick and use the new one only when _abort_requested fired.
  2. Without DEBUG, ACE_BG_WAIT still sends its verdict to the console on every arrival, right in the swap burst. Please log it to klippy.log only and keep the console line for DEBUG=1, same as _set_state.

@docx-tech

Copy link
Copy Markdown
Collaborator Author

Read through 53acde8 and the merge, the #196 conflict is resolved the right way. Two small things:

  1. _check_docked now says "ABORTED by arrival wait" for a real pick too, so the log no longer tells the two apart. Please keep the old "was PICKED mid-sequence" text for a pick and use the new one only when _abort_requested fired.
  2. Without DEBUG, ACE_BG_WAIT still sends its verdict to the console on every arrival, right in the swap burst. Please log it to klippy.log only and keep the console line for DEBUG=1, same as _set_state.

I addressed both of these issues. See attached log and commits above.
klippy (1).log

I also noticed a bug in #196 which I commented in that PR.

…pick

A full bg swap that is still priming when the T arrives kept extruding
while the stock macro carried the head to the print: the engine's
interlock (_check_docked) only sees the extruder ACTIVATION, which stock
performs ~2.4 s after lifting the head out of the dock, and only between
prime chunks. Field log: pick 13:34:26, activation 13:34:28.6, engine
notice 13:34:29.8 - 3.5 s of prime at 4 mm/s plus no anti-ooze retract,
the blob in front of the wipe tower.

ace.py wraps T0-T3: before the stock handler runs, _notify_bg_pick tells
the engine (request_pick_abort) and waits up to BG_PICK_NOTICE_WAIT for
the handover. The engine stops at its next move boundary while the head
is still docked: the prime loop ends cleanly, sets its anti-ooze cushion
and records the missing purge for the arrival pick-check; every other
polling phase (HEAT, PULL, LD_FEED, LD_GRIP) takes its existing
pick-abort path. The bulk retract stays untouchable, a notice outside
the polling phases is ignored and the T runs as before. An older engine
without the hook, or no engine, is a no-op.

Unit tests for both halves (tests/unit/test_bg_pick_notice.py).
@docx-tech

docx-tech commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

Hey @decay71,

I saw your note and merged fix/bg-pick-notice (PR #198) into this branch.

Here's what's included in these latest commits:

  1. request_pick_abort in ACE_BG_WAIT: In LD_GRIP and LD_PRIME, ACE_BG_WAIT now announces the toolchange via self.request_pick_abort(head) and waits for the engine to cut short the prime chunk, apply the anti-ooze cushion, and hand over before returning to T<n>.
  2. Direct Dock Optimization (RESTORE_POS=0): Added RESTORE_POS to ACE_BG_WAIT (default 0). When the hold releases, the carriage stays at the discard chute so the subsequent T docks the head directly along the rear rail ($Y=332.2$), cutting out the ~3.5s redundant round-trip forward to the bed and back. RESTORE_POS=1 is preserved for bench tests.

It's late, so I haven't deployed/tested these to my machine yet, but I wanted to push the commits here so you have visibility into the implementation in your morning. I'll try to test tmrw!"

@decay71 decay71 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi

RESTORE_POS=0 is right, and my point 3 from the first review was wrong: I assumed the swap after the T saves the position from before the T. The stock pick always ends at the dock line, and your log shows it: the wait returned to X=15 Y=208-236, and the swap still saved X=43.80 Y=250.00 every time. So the trip back was pure time.

Four small things:

  1. With RESTORE_POS=0 please still restore the feedrate and the coordinate mode, only skip the travel. The park leaves F600, and both the stock T and the swap carry the speed they find into the print.
  2. The LD_GRIP/LD_PRIME handover in ACE_BG_WAIT does what #198 already does at the T, two lines later, so it can go. (The "anti-ooze cushion" wording also only fits LD_PRIME; LD_GRIP takes the normal abort path.)
  3. For diagnostics we keep only the config line, without a runtime setter: the debug option and DEBUG=1 are enough, please drop ACE_BG_DEBUG.
  4. BG_WAIT_DEV_NOTES.md is back in the PR, please keep it in your fork.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants