Snippets in afterPrintCancelled and afterPrintDone can't find bundled snippets

What is the problem?

I moved the start GCode from CURA to OctoPrint and commented it out so I could put it back and also to keep as a reference. I did the same for the end GCode, altering it to use a snippet, and also modified the afterPrintCancelled script with part of the same end code. The start GCode executes, as it draws the same purge line it always did, but when I cancel a print, the printer just stops and doesn't move the hotend out of the way. The same happens with afterPrintCancelled. I came down this morning and the hotend was stopped where he last move had left it - fortunately there was no damage to the model.

afterPrintDone:

G91 ;Relative positioning
G1 E-2 F2700 ;Retract a bit
G1 E-2 Z0.2 F2400 ;Retract and raise Z
G1 X5 Y5 F3000 ;Wipe out
G1 Z10 ;Raise Z more
G90 ;Absolute positioning

G1 X0 Y235 ;Present print

; disable motors, heaters and fan
{% snippet 'DisableMotorsAndHeaters' %}

afterPrintCancelled:

G91 ;Relative positioning
G1 E-2 F2700 ;Retract a bit
G1 E-2 Z0.5 F2400 ;Retract and raise Z
G1 X5 Y5 F3000 ;Wipe out
G1 Z10 ;Raise Z more
; disable motors, heaters and fan
{% snippet 'DisableMotorsAndHeaters' %}

And snippets/DisableMotorsAndHeaters

; disable motors
M84 X Y E

;disable all heaters
{% snippet 'disable_hotends' %}
{% snippet 'disable_bed' %}
;disable fan
M106 S0

Looking in the log, it seems that OctoPrint is looking for 'gcode/snippets/snippets/disable_hotend'.

I thought the disable_hotend and disable_bed snippets were predefined as Builtin Scripts? In any event, I can't find any files with those names on the disk. Am I wron? Do I need to create them?

Also if I create them, do I put them in 'gcode/snippets' or 'gcode/snippets/snippets'? What happens if I use further low level snippets? Will I need 'gcode/snippets/snippets/snippets' etc? Would I need multiple copies of snippets at different levels? This would defeat the main purpose of having them which is to have a single common piece of code in one location.

By the way, I realise that, for now, I could just put the GCode commands M104 T0 S0 and M140 S0 in the files, but if I ever change printers...

What did you already try to solve it?

Checked log and script syntax.

I also tried creating 'snippets/disable_hotends' and 'snippets/disable_bed', even though they are supposed to be built in, but it still failed looking for 'gcode/snippets/snippets/disable_hotends'.

Having created the supposedly builtin snippets, I then created a soft link

ln -s snippets/snippets snippet

This then failed with the error:

IOError: [Errno 40] Too many levels of symbolic links: u'/home/pi/.octoprint/scripts/gcode/snippets/snippets/disable_hotends'

For now, the only workaround I've come up with is to put the "builtin" snippet code inline:

; disable motors
M84 X Y E

;disable all heaters
;;;--- snippet 'disable_hotends' ---

{% if printer_profile.extruder.sharedNozzle %}
M104 T0 S0
{% else %}
{% for tool in range(printer_profile.extruder.count) %}
M104 T{{ tool }} S0
{% endfor %}
{% endif %}

;;;--- snippet 'disable_bed' ---

{% if printer_profile.heatedBed %}
M140 S0
{% endif %}

;disable fan
M106 S0

Logs - octoprint.log

2020-06-17 13:06:17,761 - octoprint.events - INFO - Executing a system command
2020-06-17 13:06:17,851 - octoprint.settings - ERROR - Exception while trying to render script gcode:afterPrintCancelled
Traceback (most recent call last):
  File "/home/pi/oprint/local/lib/python2.7/site-packages/octoprint/settings.py", line 1632, in loadScript
    script = template.render(**context)
  File "/home/pi/oprint/local/lib/python2.7/site-packages/jinja2/environment.py", line 989, in render
    return self.environment.handle_exception(exc_info, True)
  File "/home/pi/oprint/local/lib/python2.7/site-packages/jinja2/environment.py", line 754, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/pi/.octoprint/scripts/gcode/afterPrintCancelled", line 7, in top-level template code
    {% snippet 'DisableMotorsAndHeaters' %}
  File "/home/pi/.octoprint/scripts/gcode/snippets/DisableMotorsAndHeaters", line 5, in top-level template code
    {% snippet 'disable_hotends' %}
  File "/home/pi/oprint/local/lib/python2.7/site-packages/octoprint/settings.py", line 760, in get_source
    return self._default.get_source(environment, template)
  File "/home/pi/oprint/local/lib/python2.7/site-packages/jinja2/loaders.py", line 399, in get_source
    raise TemplateNotFound(template)
TemplateNotFound: gcode/snippets/snippets/disable_hotends
2020-06-17 13:06:17,883 - octoprint.plugins.DisplayLayerProgress - INFO - Printing stopped. Detailed progress stopped.
2020-06-17 13:06:17,900 - octoprint.util.comm - INFO - Changing monitoring state from "Cancelling" to "Operational"
2020-06-17 13:06:18,220 - octoprint.plugins.detailedprogress - INFO - Printing stopped. Detailed progress stopped.

Additional information about your setup

OctoPrint version: 1.4.0
OctoPi version: 0.17.0
Printer: CReality Ender-3
Printer firmware: Marlin 2.0.5.3
Browser: Chrome (83.0 at time of log)
Operating system: Windows 10 2004 Build 19645

This should be (in gcode/snippets)

ln -s . snippets

With this any number of "snippets" with in gcode/snippets are ignored. gcode/snippets/snippets/snippets/snippets/snippets is gcode/snippets.

Thanks. I always have trouble with the ln command, especially where trying to fix recursion errors.

I still think this indicates a bug in Octoprint. Surely it should always be looking in the same snippets folder?

Also, I don't know why the supposedly builtin command snippets were missing.

ln (without -s) is really simple, it is like cp but only make a new reference to the same file.

ln -s path-a path-b means that in the directory of path-b the filename of path-b is replaced by path-a as seen from this directory.

E.g. after ln -s a/b c/d/e an access of c/d/e/x is translated into c/d/a/b/x

Thanks, but I've been working with Linux systems for 26 years. ln alone is usually wrong as creates a hard link - on ext4 filesystems, this means 2 inodes point to the same file, which is likely to cause issues - hence in normal situations ln -s is used.

i worded it wrong, but what I meant to say was that I always and only have troulbe with symbolic links when I'm trying to create a recursive structure, i.e a link in a directory which points at the directory itself. I usually end up doing it as an absolute link:

ln -s /home/pi/.octoprint/scripts/gcode/snippets /home/pi/.octoprint/scripts/gcode/snippets/snippets

However, my skill (or lack of it) with `ln doesn't change the fact that needing to create a recursive symbolic link should be unnecessary and indicates a bug in OctoPrint! Most people would expect all snippets to be on the same folder.

I try to avoid absolute symlinks wherever possible. They break if you mount the filesystem at another mountpoint or if you restore a backup temporarily at another place or it's accessed in a chroot.

Especially if a symlink is used to ignore some directory, like tftproot inside of /tftproot or this snippets, a relative link is the only sane way because among other things someone looking at it see the purpose instantly.

That's not without exception, if $HOME/bin/dummy is symlinked to /bin/true a relative links doesn't make much sense.

I wasn't disputing or even discussing the existence of a bug (in my eyes to seems to be a bug) but to show a functional usage of ln -s.