Skip to content

Rework PauseAtHeight.py - #21327

Merged
HellAholic merged 12 commits into
Ultimaker:mainfrom
GregValiant:Create_PauseAtLayerOrHeight
Sep 10, 2026
Merged

Rework PauseAtHeight.py#21327
HellAholic merged 12 commits into
Ultimaker:mainfrom
GregValiant:Create_PauseAtLayerOrHeight

Conversation

@GregValiant

@GregValiant GregValiant (GregValiant) commented Jan 22, 2026

Copy link
Copy Markdown
Collaborator

A re-write of the classic Pause script.

Description

Changes and additions:

  1. Added more 'Pause Command' options for alternate firmware flavors.

  2. Added 'Unload' option (prior to pause) and 'Reload' and 'Purge' options (after the pause).

  3. Added 'Reason for Pause' options.
    a. If 'Reason_for_pause' == 'Filament Change' then Unload, Reload, and Purge become available.
    b. If 'Reason for Pause' == 'All Others' then the filament change options are hidden as they are not required for insertions or other pause events.

  4. Added 'Multiple Pause Layers' option.
    a. This works well when all pauses will have the same settings. Delimit the layer numbers with commas.- If pauses require different settings then add another instance of the script.
    b. Multiple messages can be added (also delimited by commas). Each message will be assigned to the equivalent pause layer.
    c. Example: For pause layers 15,23,48 the messages could be: White,Red,Blue

  5. Added support for "Firmware Retraction"

  6. Added support for 'One at a Time' print sequence.
    a. Pauses can be at different layers in different parts.
    b. All pause layers must be listed (use the Cura Preview layer numbers).
    c. A pause at 'Layer:5' will only result in a pause at the first layer:5 encountered whereas pauses at '15,23,67' might be in different models.
    d. Models can be skipped, or have pauses at different layers than other models, and some models could be different colors or a different material.

  7. Added 'Flow Rate' option for 'Redo Layer'.
    a. (Note that "Redo Layer" is disabled when in One-at-a-Time mode.)

  8. Multi-extruder printers now use the Cura settings of the tool that is active when the pause occurs (retraction distance, retract and prime speeds, etc.)

  9. The 'Stepper Timeout' has been de-confused (hopefully).

  10. Obsolete:
    The "Retraction option" is removed. Retractions are now handled in the script.

This fixes... OR This improves... -->

Type of change

  • [ X] New feature (non-breaking change which adds functionality)

How Has This Been Tested?

I have been using it for about 2 years now.

Test Configuration:

  • Operating System:
  • Windows 10 Pro
  • Cura versions 4.13.1 through 5.12.1

Checklist:

  • [ X] My code follows the style guidelines of this project as described in UltiMaker Meta and Cura QML best practices
  • [ X] I have read the Contribution guide
  • [ X] I have commented my code, particularly in hard-to-understand areas
  • [ X] I have uploaded any files required to test this change

NOTE: This version has some overlap with the current "PauseAtHeight". That one could be made obsolete as we did with 'DisplayInfoOnLCD'. The original remains but it just shows a message informing the user that they should use the new one.

A re-write of the classic Pause script.
@github-actions github-actions Bot added the PR: Community Contribution 👑 Community Contribution PR's label Jan 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Test Results

24 518 tests  ±0   24 516 ✅ ±0   50s ⏱️ -1s
     1 suites ±0        2 💤 ±0 
     1 files   ±0        0 ❌ ±0 

Results for commit dfe9c68. ± Comparison against base commit 4f28ea8.

@GregValiant

Copy link
Copy Markdown
Collaborator Author

HellAholic,
Do you think there is a chance of this being added(?) or as a replacement.

I changed the name of this one to try to avoid confusion but there would be the question of what to do with the original "PauseAtHeight".

@HellAholic

Copy link
Copy Markdown
Contributor

This one I would go with replacement since it's extending the Pause At... in my opinion. As what to do with regards to the behavior to support previous workflows, we can have the default values set in such a way that the behavior would be the same as the previous script.

@GregValiant

Copy link
Copy Markdown
Collaborator Author

Crap.
I'll go over it and see what needs to be done. Things like "Disarm Timeout" was in seconds but I changed that to minutes. There might need to be some "conversions" needed.

@GregValiant

Copy link
Copy Markdown
Collaborator Author

I appears to just have needed a couple of variables renamed so they match from old to new.
I have to wring it out some more, but I don't anticipate a lot of work there.

Changed the file name from PauseAtLayerOrHeight to match the existing PauseAtHeight.
Additional debugging to insure that this version is backward compatible with the previous version.
@GregValiant GregValiant (GregValiant) changed the title Create PauseAtLayerOrHeight.py Rework PauseAtHeight.py May 9, 2026
@PartySausage

Copy link
Copy Markdown

I've performed some testing on this as I use pausing for filament swaps quite frequently. So far everything seems to work as it should, however I have a couple of implementation queries, which I was wondering if you could help me with for my understanding

The first is that I think there is a piece of obsolete code as I don't think the condition could ever be achieved

                elif self.redo_layer and self.reason_for_pause == "reason_other":
                    self.pause_lines_list.append(f"M{extrusion_mode_numeric} ; Switch back to {extrusion_mode_string} E values")`

The second query surrounds the switching between Relative/Absolute Extrusion, where I was wondering if there was a reason why the script switches to Relative Extrusion even if Relative Extrusion is already being used then back to Relative Extrusion at the end of the pause.
I was thinking that an easier solution would be to do the check whether Relative Extrusion was being used here`

           # Don't change the extrusion mode if pause method is UM 'griffin'
            if pause_method != "griffin":
                self.pause_lines_list.append("M83 ; Relative extrusion")

Then only switch to Relative Extrusion & back to Absolute Extrusion at the end if Absolute Extrusion was in use before the pause

This would enable this code snipets to be simplified so that the append lines, which use extrusion_mode_numeric would contain an explicit M82 command, which is only appended if Absolute Extrusion was in use before the pause

                extrusion_mode_string = "absolute"
                extrusion_mode_numeric = 82

                relative_extrusion = Application.getInstance().getGlobalContainerStack().getProperty("relative_extrusion", "value")
                if relative_extrusion:
                    extrusion_mode_string = "relative"
                    extrusion_mode_numeric = 83

                if not self.redo_layer:
                    self.pause_lines_list.append(f"M{extrusion_mode_numeric} ; Switch back to {extrusion_mode_string} E values")

@GregValiant

Copy link
Copy Markdown
Collaborator Author
  1. When I started on the revision my incliniation was to simply remove "redo layer". Later I saw a way to keep it in when "All Others" was true. I went back and made the change. As a result of that change, the "elif" statement can never be true ("redo_layer" is not available when "reason_other" is true). I will remove that "If" statement.

  2. That also looks like vestigial code BUT it insures that the extrusion mode is set correctly to "relative" when the print resumes so I'm inclined to leave it. There may have been a condition I came across during debugging (with Firmware Retraction enabled?) where it was needed.

Removed an uneeded IF statement.
changed 2 App.getinstance..." to self.global_stack.
@PartySausage

Copy link
Copy Markdown

Thanks for clearing that up. I only have the one flavour of printer to test this on so never no if there are other printer configurations where the extrusion switching or lack of could cause problems but what you've done is good defensive programming in case something throws a curveball in the future

@HellAholic HellAholic self-assigned this Jul 3, 2026
@HellAholic

Copy link
Copy Markdown
Contributor

This one requires some work. The overall tone would be:

  1. Keeping the old history, adding to it rather than replacing it at the top.
  2. The init function if I remember correctly is for when opening Cura, and the plugin loads, the script pops up, so it's a bit of a structural thing, otherwise you get an exception when you open cura with no printers for example because you initialize the script and the first thing you do is checking for global stack 😝
  3. :param addition is a good choice in terms of direction but it should be used for indicating the variables/parameters that you need to pass to the function, most of the functions get the self or data, so it's not a valid choice to add them to initialize for example. Some of the functions that could use the :param don't have it, for example the _track_tool_nr()
  4. keeping the setting names the same as much as possible (backwards compatibility), labels and descriptions can be updated but the setting name itself should remain the same
  5. Biggest part is the _find_pause; for a single operation of adding pause at height or layer, it's fine to have a for loop and then apply the logic, but as soon as you start extending the functionality, it becomes challenging to check every line of the gcode gainst 300 lines of code. At the very least I would extract each specific section into its own helper function

I'll do some clean up and post a version here as comment.

@HellAholic

Copy link
Copy Markdown
Contributor

PauseAtHeight.py

Still need to test things out yourself and compare, but this is more of a structural guide.

@GregValiant

Copy link
Copy Markdown
Collaborator Author

I'm getting this in a crash report.

Traceback (most recent call last):
  File "C:\Program Files\UltiMaker Cura 5.13.0\share\cura\plugins\PostProcessingPlugin\PostProcessingPlugin.py", line 288, in addScriptToList
    new_script.initialize()
  File "C:\Users\grego\AppData\Roaming\cura\5.13\scripts\PauseAtHeightHH.py", line 59, in initialize
    super().initialize()
  File "C:\Program Files\UltiMaker Cura 5.13.0\share\cura\plugins\PostProcessingPlugin\Script.py", line 50, in initialize
    self._definition.deserialize(json.dumps(setting_data))
  File "UM\Settings\DefinitionContainer.py", line 325, in deserialize
    parsed, is_valid = self.readAndValidateSerialized(serialized)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "UM\Settings\DefinitionContainer.py", line 275, in readAndValidateSerialized
    self._verifyJson(parsed)
  File "UM\Settings\DefinitionContainer.py", line 501, in _verifyJson
    raise IncorrectDefinitionVersionError("Definition uses version {0} but expected version {1}".format(json_dict["version"], self.Version))
UM.Settings.DefinitionContainer.IncorrectDefinitionVersionError: Definition uses version 3 but expected version 2

@HellAholic

Copy link
Copy Markdown
Contributor

It's the setting version, I made a booboo updating it to 3, it should be 2.

    def getSettingDataString(self) -> str:
        return """{
            "name": "Pause at Height",
            "key": "PauseAtHeight",
            "metadata": {},
            "version": 2,
            "settings":
            {

PauseAtHeight.py

@GregValiant

Copy link
Copy Markdown
Collaborator Author

In way of an explanation...
I have had projects that required several pauses (my personal record is 9). It was irksome that 9 separate instances of "Pause" were required and that each instance had to be configured separately. When the pause actually occurred, there was the manual filament handling for each change. Hand "Purging" was required but for some printers was not really an option. The script was also clumsy when used for "Insertions" of nuts or magnets.

So I set out to include the unload / reload / purge option to eliminate most of the manual filament handling, and allow for a slightly different configuration between insertions and filament changes. I also wanted to have the ability to utilize a single instance of the PP even for pauses at multiple layers when they were all of the same configuration.
The "re-do layer" options was raw so now the user can adjust the flow and speed rather than trying to reprint the layer at 100% flow and 100% feedrate. I was thinking that more people might take advantage of it if it didn't cause problems with the printer and/or an over-extruded layer in the print.

So far it seems to run the same with your revisions as it did in my effort. I'll continue to wring it out.

PartySausage if you would care to download and test HellaHolic's latest version it would be much appreciated.

@PartySausage

PartySausage commented Jul 4, 2026

Copy link
Copy Markdown

GregValiant (@GregValiant) I've tested HellAholic's revised script with a several different theoretical scenarios using By Layer & By Height for All Others & Filament Change pauses with multiple pauses for each and haven't seen any hiccups. 👍👍👍

I've been using my own fork of your script with a couple of extra settings so I merged the new script in with that version, which given the number of changes took some doing. Anyway, the resulting G-Code files seem to contain everything they should

;
;TYPE:CUSTOM---------------; Pause at End of Preview Layer: 25 (End of G-Code LAYER: 24)
M83                        ; Relative Extrusion
G0 F300 Z6.2               ; Move Up to Clear Print
G0 F9000 X0 Y110           ; Move to Park Location
G0 F300 Z15                ; Move Up to Minimum Clearance Height
M104 S210                  ; Standby Temperature
M84 S3600                  ; Keep Motors Engaged for 60.0 Minutes
M412 S0                    ; Disable Filament Run-Out Sensor
M300                       ; G-Code Before Pause
M25                        ; PAUSE
M300                       ; G-Code After Pause
M104 S210                  ; Resume Temperature
G0 F9000 X87.701 Y85.097   ; Move to Resume Location
G0 F300 Z5.2               ; Move Down to Resume Height
G1 E0.1 F2400              ; Extra Prime
M83                        ; Switch Back to Relative Extrusion
G92 E0                     ; Reset Extruder Location
;--------------------------; Pause at Layer/Height: End of the Pause G-Code
;

Changes as made and requested by HellaHolic.
@GregValiant

Copy link
Copy Markdown
Collaborator Author

Th this afternoon's commit (on 7/5/2026) includes HellaHolic's suggestions and a couple of minor changes by myself.

Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR rewrites PauseAtHeight.py to significantly expand Cura’s classic “Pause at height” post-processing script, adding firmware-specific pause commands, filament unload/reload/purge sequences, multi-pause support, and improved handling for multi-extruder and One-at-a-Time printing.

Changes:

  • Adds new pause command options (e.g., Klipper, Raise3D, dwell G4, custom command) and richer messaging (LCD + print server).
  • Adds filament-change workflow controls (unload/reload/purge, quick purge, redo-layer flow adjustment) and more parking/clearance controls.
  • Adds support logic for One-at-a-Time sequencing and multi-pause lists (comma-delimited layers/heights + messages).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py Outdated
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py Outdated
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py Outdated
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py Outdated
GregValiant (GregValiant) and others added 2 commits July 6, 2026 21:05
Co-authored-by: HellAholic <alireza.doustdar@gmail.com>
Spelling correction.  Altered 2 variables.
@GregValiant

Copy link
Copy Markdown
Collaborator Author

HellAholic
Please run the last commit through the copilot again. I think I caught all the requested changes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 6 comments.

Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py Outdated
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
Comment on lines +879 to +880
# Reset extruder value to pre pause value
self.pause_lines_list.append(f"G92 E{current_e} ;Reset extruder")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

GregValiant (@GregValiant)
technically valid but is a very low-impact edge case. Whether it's worth fixing is a judgment call — the fix is a one-liner, but the scenario it covers is nearly hypothetical.

Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
changed "value" to "default_value"

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@GregValiant

Copy link
Copy Markdown
Collaborator Author

I'm sorry I got lost on this one.

I had a fiasco involving my water well and I've been busy making the repair. PITA. The original pressure tank lasted 32 years and that was considered the norm. Now it's 3-5 years and they cost 10 times more than they did. Junk.

@HellAholic

Copy link
Copy Markdown
Contributor

No rush, just went through the comments co-pilot made to close the ones out of context.

Nothing is being built to last anymore. Best of luck on the repairs. Hope both you and your wallet survive the fiasco.

@GregValiant

GregValiant (GregValiant) commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

I haven't seen any comments here about the core of the re-design which was to allow a single instance to control multiple pauses, and to add more pause commands, and split off "Insertions" which don't require filament changes. I also like the unload and reload/purge sequence as that was always manual and could be difficult on some machines.

I'll keep my hands out of it and let you work. I have t sit here and provide consolation for my wallet. It's pissed.

@HellAholic

Copy link
Copy Markdown
Contributor

Did some clean up and separated the big block of if else into separate methods. Take a look when things calmed down a bit.

PauseAtHeight.py

@GregValiant

Copy link
Copy Markdown
Collaborator Author

I'm playing with it. So far I haven't been able to break it.

@GregValiant

Copy link
Copy Markdown
Collaborator Author

It works for me.
#21458 is related to this PR. I believe this version addresses the concerns of that PR.

@PartySausage

Copy link
Copy Markdown

Same here no hiccups so far 👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me ! I do have some remarks/questions, but the global quality is good, and from what I understand it already went through a lot of testing, so feel free to reject the suggestions.

Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py Outdated
Comment thread plugins/PostProcessingPlugin/scripts/PauseAtHeight.py Outdated
if is_retracted is None:
is_retracted = False
break
return current_e, is_retracted

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this function working properly with relative extrusion ? Shouldn't it always return 0 in this case ?

is_retracted = True
elif current_e is not None and is_retracted is None:
is_retracted = False
current_e = new_e

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't that the same as getPreviousE ?

Comment on lines +776 to +777
if current_z + move_z > self._machine_height:
move_z = 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
if current_z + move_z > self._machine_height:
move_z = 0
move_z = min(move_z, self._machine_height - current_z)

There are 2 other occurrences below, if you decide to apply it. They could also be moved out the if by the way, so that you would have it only once.

@wawanbreton

Copy link
Copy Markdown
Contributor

Also, can someone explain the "redo layer" principle ? I like the idea of priming the nozzle, but if you replay the entire layer, that is definitely going to over-extrude and make it ugly 🤔

@HellAholic

Copy link
Copy Markdown
Contributor

I made some tweaks but I am also not sure if it's functioning as expected after the changes 😝 So here is the changed version with:

  • Z-clamp logic
  • Potential Duplicate/incorrect E-tracking in _apply_redo_layer
  • Multi-extruder + redo layer (guard)
    PauseAtHeight_V2.py

The idea for keeping the obsolete settings was to prevent any breaking for old 3mf files, but they can be filtered out/removed if needed or if their removal doesn't cause a problem. They are disabled so they should not affect the script itself.

For the redo layer poking GregValiant (@GregValiant)

@GregValiant

Copy link
Copy Markdown
Collaborator Author

The "Redo layer" function is vestigial. I have no idea what scenario would be required for it to be necessary.
99.9% of users are changing filament or making insertions. If those work right, this should be good to go.

A full debugging with all options combinations is going to take a while. I'll ignore "Redo" for now as it is an edge use case anyway. It was working fine in previous testing. I have never had occasion to use it in an actual project.

@GregValiant

Copy link
Copy Markdown
Collaborator Author

The "E Reset" is now broken for the Redo Layer. The "current_e is incorrect. I'm looking.

@HellAholic

Copy link
Copy Markdown
Contributor

Oke, then we can keep the current implementation that is tested and clean things up in a separate later PR.

Simplify the check and move out of condition to calculate once and use where needed.
@HellAholic

Copy link
Copy Markdown
Contributor

Removed the redo layer and obsolete settings.
If there is a complaint (feature is used) during beta we can add the functionality back but at the moment we can't find a usecase for it and maintaining the code with potential issues is less than ideal.

@HellAholic
HellAholic merged commit a5dc293 into Ultimaker:main Sep 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: Community Contribution 👑 Community Contribution PR's

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants