Keeping track of position and motor mode(s)


#1

Adding a thread to discuss the topic.

To clarify, I suppose we need to define the scope of this suggestion:

  1. At any time, the nozzle should never move below z=0.
  2. Under manual movements in the Control tab, the nozzle should never move below z=0.

If it's the first, then this would be a good safety measure but it seems like it would need to be inline for all streaming gcode to the printer. The code would need to know what to do if a move wanted to go too low. Although it's a good idea, the work would need to be well-tested.

If it's the second, then this is probably a good idea and without any caveats.


I myself have drilled a hot nozzle into my plastic bed at least once already (although this occurred while resuming from a print job). In my case, the manufacturer's stock pause/resume scripts didn't factor in the possibility that motor movements could be either absolute/relative.

To further describe the problem: imagine pausing a print job, the first OctoPrint gcode script then moves the assembly but doesn't first go into relative movement mode. You then press the resume button and the second script returns to absolute mode and then moves the assembly back. When the original gcode resumes, the motor audit is now totally off and the hotend attempts to move but crashes into your bed.


#2

Baby steps, then...

What if there were a simple plugin which remembers what mode the motors are in (relative/absolute)? It would save M82/M83 state as well as G90/G91. You could query the plugin for this information.

M114 should report back the current position.


Turning my printer on, I run an M114:

Send: M114
Recv: X:0.00 Y:0.00 Z:-15.00 E:0.00 Count X: 0 Y:0 Z:-12000

I then home both X/Y and Z on the Control tab and repeat the M114 to see:

Send: M114
Recv: X:0.00 Y:127.00 Z:145.00 E:0.00 Count X: 0 Y:10160 Z:116000

This now is more trustworthy because it's been homed. None of the OctoPrint gcode scripts have had a chance to run but I include homing commands in my Before print job starts script.


Pressing the button to jog the Z by 10mm, I see the following sandwich of gcode sent to the printer. Note that octoprint itself is making an assumption that I need to go back into absolute mode when this jog is finished.

Send: G91   # set relative mode
Send: G1 Z-10 F200
Send: G90   # set absolute mode

Prevent Negative Z-Axis Movement & Nozzle Crash Into Bed
#3

I've mentioned this before but I think we need a variable/macro to be available in the Gcode scripts within OctoPrint so that it can return to the existing mode.

G91
G90 Z-10
{% snippet 'current_xy_mode' %}   # This would be a new variable
M114
M117 {% snippet 'last_position' %}  # Presumably, this would work now

Similarly, a filament change wizard might query the existing mode of the extruder.

{% snippet 'current_e_mode' %}

In my mind, the contents of the variable should be the command to set it. For example, current_xy_mode would be either "G90" or "G91" (or "; Unknown mode" if unknown).


#4

Well, there's code in Octolapse for this (position+position state+extruder tracking). I've been thinking of splitting it of into a separate plugin for other reasons (reuse).

However, Homing automatically is a problem since the printer might run into something (a non-removed print or something). Running it before the print starts is a really good idea that you suggested (most start gcode does this), but some folks do NOT do this for various reasons. Octolapse requires a home command, and I've fielded support requests where users manually home their prints and run gcode with no home. I actually added a pseudo G28 O command that will tell Octolapse that the current position is the home position to support these situations, though the solution is still in flux.

Anyway, this is a really interesting idea that maybe deserves its own thread? I honestly think this could eventually be a bundled plugin since so many are reinventing the wheel to get an accurate position/axis mode, etc. The 'Display layer progress' plugin immediately comes to mind, as well as the exclude region plugin. I know DisplayLayerProgress has some issues with G91.

The Octolapse code definitely needs improvement/cleanup as well as more gcode implementation (only a small subset is supported), but it is the most advanced position/state tracking code I've seen in a plugin. We should discuss more.


Thanks for starting this thread. I'd like to know if anyone else wants extruder/xyz axis tracking. Here is an example of the position values (dict form) that Octolapse tracks (some aren't really useful outside of the context of Octolapse):

    def to_position_dict(self):
        return {
            "F": self.F,
            "X": self.X,
            "XOffset": self.XOffset,
            "Y": self.Y,
            "YOffset": self.YOffset,
            "Z": self.Z,
            "ZOffset": self.ZOffset,
            "E": self.E,
            "EOffset": self.EOffset,
            "Features": self.Features,
        }

Here are the position state values:

    def to_state_dict(self):
        return {
            "GCode": self.parsed_command.gcode,
            "XHomed": self.XHomed,
            "YHomed": self.YHomed,
            "ZHomed": self.ZHomed,
            "IsLayerChange": self.IsLayerChange,
            "IsHeightChange": self.IsHeightChange,
            "IsZHop": self.IsZHop,
            "IsRelative": self.IsRelative,
            "IsExtruderRelative": self.IsExtruderRelative,
            "IsMetric": self.IsMetric,
            "Layer": self.Layer,
            "Height": self.Height,
            "LastExtrusionHeight": self.LastExtrusionHeight,
            "IsInPosition": self.IsInPosition,
            "HasOneFeatureEnabled": self.HasOneFeatureEnabled,
            "InPathPosition": self.InPathPosition,
            "IsPrimed": self.IsPrimed,
            "HasPositionError": self.HasPositionError,
            "PositionError": self.PositionError,
            "HasReceivedHomeCommand": self.HasReceivedHomeCommand,
            "IsTravelOnly": self.IsTravelOnly
        }

Here is the combined dict (both state and position):

    def to_dict(self):
        return {
            "GCode": self.parsed_command.gcode,
            "F": self.F,
            "X": self.X,
            "XOffset": self.XOffset,
            "XHomed": self.XHomed,
            "Y": self.Y,
            "YOffset": self.YOffset,
            "YHomed": self.YHomed,
            "Z": self.Z,
            "ZOffset": self.ZOffset,
            "ZHomed": self.ZHomed,
            "E": self.E,
            "EOffset": self.EOffset,
            "IsRelative": self.IsRelative,
            "IsExtruderRelative": self.IsExtruderRelative,
            "IsMetric": self.IsMetric,
            "LastExtrusionHeight": self.LastExtrusionHeight,
            "IsLayerChange": self.IsLayerChange,
            "IsZHop": self.IsZHop,
            "IsInPosition": self.IsInPosition,
            "Features": self.Features,
            "InPathPosition": self.InPathPosition,
            "IsPrimed": self.IsPrimed,
            "HasPositionError": self.HasPositionError,
            "PositionError": self.PositionError,
            "HasPositionChanged": self.HasPositionChanged,
            "HasStateChanged": self.HasStateChanged,
            "Layer": self.Layer,
            "Height": self.Height,
            "HasReceivedHomeCommand": self.HasReceivedHomeCommand
        }

Here are the exturder state values that are tracked:

    def to_dict(self):
        return {
            "E": self.E,
            "ExtrusionLength": self.ExtrusionLength,
            "ExtrusionLengthTotal": self.ExtrusionLengthTotal,
            "RetractionLength": self.RetractionLength,
            "DetractionLength": self.DetractionLength,
            "IsExtrudingStart": self.IsExtrudingStart,
            "IsExtruding": self.IsExtruding,
            "IsPrimed": self.IsPrimed,
            "IsRetractingStart": self.IsRetractingStart,
            "IsRetracting": self.IsRetracting,
            "IsRetracted": self.IsRetracted,
            "IsPartiallyRetracted": self.IsPartiallyRetracted,
            "IsDetractingStart": self.IsDetractingStart,
            "IsDetracting": self.IsDetracting,
            "IsDetracted": self.IsDetracted,
            "HasChanged": self.HasChanged
        }

I currently store the previous 5 state values, and the position is calculated ONLY at the queuing phase since I might manually change things by pausing and injecting code. The user would be responsible for submiting an 'Undo position update' if a gcode is altered/suppressed in the queuing phase. It would be trivial to also track the 'Sent' position, which would NOT be alterable and thus less dangerous to use.


#5

Here on line 918 is where the variable injection is taking place for the Gcode scripts in the Settings area. Line 925 is where she would bring in that last_position variable and then it appears a few times later in this file.

It appears to be copied over to the pause_position variable during that event.


#6

It should actually be the printer's responsibility to never try to move past its print volume boundaries (software endstops).

About a variable to return to previous positioning mode - this is sadly not information that I can query from the printer. OctoPrint makes the assumption that the printer starts out in absolute mode and hence it does this sandwiching trick in all its jog moves. It could just as well be wrong, depending on how the firmware has been configured initially, or what kind of codes the user put into some startup gcode file on the printer's SD. Without a GCODE to query the current mode (or for that matter the current tool, the current feedrate, the current feed- and flowrate modifiers, fan speed, ... you get the idea) I can't really track this information reliably.


#7

@foosel, This is pretty much what I've found too. I know you're already aware that my plugin does not assume that the printer starts out in absolute mode, and this has caused me all kinds of grief. At some point you suggested that I could just send a G90 (or G91 depending) when the print starts to force the printer into a known mode, which is an idea I'm still exploring.

Is there a way to reset printers via gcode? If there is some code that mimics a hard reset (power off/power back on) some of these issues could be bypassed. Alternatively, it might be possible to create a printer specific 'reset' gcode script that sets every parameter to a known default. That way we could enter a known state at the start of a print and then start tracking from there. There are some situations where this would not work too well (manual homing), but I think it would work in the average case.

I have 2 motivations for creating a position tracking plugin:

  1. Reuse by me and others
  2. Offload testing and improvement/optimization of position tracking.

Number 2 is a big one. Most of the problems I encounter with my plugin involve differences in how position tracking works from printer to printer. If there were a larger pool of developers more of the issues could be worked out and more printers would eventually be compatible. It's pretty hard to debug issues involving printers you have no access to as I'm sure you are aware!

Anyway, I like this idea and will start development if there is enough interest.


#8

I—for one—like it. I've drilled my hotend into my plastic bed twice now. Granted, they're only $25 with new bed & BuildTak but I'd kind of like it if it just never happened again. It hasn't happened since I've removed all of Robo 3D's software so the incident rate is acceptable now, though.


#9

FYI, I have added an issue to the Marlin github page requesting a new GCode for querying the axis mode. If anyone else feels this would be a good addition, please post a comment there. This would be another step towards accurate and reliable position tracking. It would probably be a good idea to hit up other firmware with this request too. I suggested M145 should be used, but it doesn't really matter which code is used, as long as it is added and then adopted by other firmwares.


#10

It looks like they're going to use M114. Color me excited.


#11

Hi,

just a completely unqualified remark from my side. Is the entire issue not more an issue closed-loop vs none-closed-loop ? I have not experienced that myself cause I don't have one of those printer that raise the bed but I have seen on Youtube that some of those printers drop the bed with gravity if the motors are switched off, unless you have a closed loop system you are never able to track the position reliably. You would always have to assume no step was lost, no external factor has moved anything.

Regards
Jan P.


#12

I think the underlying issue is that a good pause/resume pair of scripts needs to know the state of the movement modes for both the motors and for the extruder. Without this knowledge, the script can't reliably do its work safely. You want to move things relative to the current position and then return; it's necessary to put back the original movement mode before resuming.


#13

@snoozer, the issue is that printer hosts are operating with imperfect information. Imagine running this gcode:

G28 ; Home XYZ to 0,0,0
G0 X10 Y10 Z10 ; Move to 10,10,10
G0 X10 Y10 Z10 ; Do we stay in the same place or go to 20,20,20?

Notice that the effect of the first G0 command will always be to move to 10,10,10, but the result of the second G0 command depends on what axis mode we are in (relative vs absolute).

There are a few possible solutions WITHOUT having a gcode to query the axis mode:

  1. Force the user to send a G90/G91 and refuse to send any commands that are ambiguous to the host (Octolapse has this option in the printer profile settings, and it is the default).

The downside of this is that new users don't know what g90 does and probably don't care. They just want to print. Most printers (maybe all) are set to absolute coordinates by default (or after a hard/soft reset), and lots of start gcode doesn't include G90.

  1. Assume that the printer is in absolute mode for XYZ axis (Octolapse can do this too).

This works great, except when it doesn't. If the printer is, in fact, in relative mode this can be a recipe for disaster. However, 99+% of the time this works and it makes configuration easier.

  1. Send a G90 automatically (perhaps in conjunction with a 'reset to defaults' script)

This is actually a solution I'm considering. However, It would be perfectly valid for a user to change the axis mode to relative, then create gcode that expects the printer to be in relative mode.

The best solution I can think of is to just ask the printer what mode it is in, then get your result and move on. That way there would be no question and no compromises.