Reviving the dockable_probe

Yes, adding :

activate_gcode:
deactivate_gcode:

to [dockable_probe]

throws an error :

Option 'activate_gcode' is not valid in section 'dockable_probe'

Also tested

deactivate_on_each_sample:

It is also rejected :

Option 'deactivate_on_each_sample' is not valid in section 'dockable_probe'

Looks like activation / deactivation and associated gcodes are not supported.

@cloakedcode. On your GitHub page, you say “This coupling is commonly done with magnets though there is support for a variety of designs including servo and stepper actuated couplings”. I was hoping you may have a link or two where I could find any work that people have done on the mechanical part of server/stepper or other coupling for a dockable probe.

I have a Delta printer with magnetic arms that I would like to use a dockable probe with but I have a strong suspicion that attachment/detachment of a magnetic dockable probe would result in a rapid unscheduled disassembly. Any attachment would have to give a minimal side or downward force to avoid detaching the Delta arm magnets.

Check EUCLID PROBE™️ | Euclid Probe the highly accurate detachable Z-probe for your 3D printer, laser and CNC
Or just use Google / Thingiverse / Printables, there is an endless amount of designs around this, although I have not yet seen a Delta design

Thank you Sineos, but magnetic arms on Delta printers normally operate in a direction that does not tend to separate the magnetic parts. The magnetic coupling to a Euclid probe may exceed the Delta arm coupling.

I have considered using a solenoid, but that needs a very accurate approach_position and dock_position before the solenoid is switched on.

It’s unlikely the Euclid magnets are stronger than the joints ones.
I just measured a pair of random 8x3mm neodymium : 600 gram force (6 N)
Two pairs → 12 N ; simply measure the weight ONE JOINT can wistand. One joint because such a system will fail with a “domino effect”.

Separating magnets by shearing them requires less force.

Thank you YaaJ, I agree that the forces from a magnetically docked probe such as Euclid, Klicky et. al., should be less than the holding force of a delta arm, I do think however that there should be a fairly large amount of latitude; that the delta arm force should be more than twice the dockable probe dismount force - over the years Murphy’s Law and Sod’s Law have punished me frequently for not allowing sufficient tolerance.

I will build my own probe, partly because of space requirements on the effector, and partly because I wish to use a piezo contact probe in preference to a microswitch. The piezo probe is able to discriminate contact with detritus, bubbles under the print surface, etc.

Again, when doing ATTACH_PROBE, Z move is crazy fast while using decent horizontal settings. Why not “homing_speed” from printer.cfg stepper_z ?
(bed slinger, horizontal homings and travels = 150, having forced 150 vertical with leadscews is frightening !)

Ohhh, I’m sorry, I misunderstood your original question. You’re saying the X, Y, and Z movements all happen at the same speed but with a fast setting for X and Y that can be too much speed for Z.

Yes, this can be tricky to get right. In the name of simplicity and ease of use I’ve tried to keep the number of config options/combinations to a minimum and empower users to override the behavior where they want to.

In this particular case, having a separate option for the speed of movement on the Z axis when approaching the dock feels too specific. You asked about using the homing speed from the stepper config, and my argument against that is I don’t want to use a value from another config section that is not explicitly related to the probe as it might be unexpected or alarming to users.

My recommendation is twofold:

  1. As you’ve seen in the code, the MOVE_TO_APPROACH_PROBE gcode is virtually two movements commands. You can define your own MOVE_TO_APPROACH_PROBE with custom speeds to achieve the desired behavior.
  2. I’ve got this module running on a CoreXY and a bed slinger. On the CoreXY I find a travel_speed of 200 to be pleasant. On the bed slinger I only use 100. I’m satisfied with these movement speeds as probing is a very small percentage of the time spent during a complete print.

WRT the activation gcodes, I’ll look into that. That is unexpected. For the first iteration of getting this code into the main branch, I might choose striking that language from the docs and making the options available as an enhancement down the road.

I didn’t understand you didn’t want to use settings from other modules. And on my side I try to use as many dockable_probe settings as possible. Misunderstanding from both sides !

Of course, I use a custom MOVE_TO_APPROACH_PROBE. Was so happy using the untouched plugin, with only one callback ! (just MOVE_TO_APPROACH_PROBE). Everything else works out of the box, with user transparent auto z offset.

This is how I’m using dockable_probe. Not sure it is the way it should be…

[gcode_macro MOVE_TO_APPROACH_PROBE] ; move to (327.5, 0, 15)
; only macro called by ATTACH_PROBE
rename_existing: BASE_MOVE_TO_APPROACH_PROBE
gcode:
  RESPOND MSG="MOVE_TO_APPROACH_PROBE"
  {% set x_approach, y_dummy, z_dummy = printer.configfile.settings.dockable_probe.approach_position.replace(' ', '').split(',') %}
  {% set travel_speed = printer.configfile.settings.dockable_probe.travel_speed | float %} ; mm/s
  {% set z_speed = printer.configfile.settings.dockable_probe.attach_speed | float %} ; mm/s  
  ;{% set z_speed = printer.configfile.settings.stepper_z.homing_speed | float %} ; mm/s
  {% set x_dummy, y_dummy, z_detach = printer.configfile.settings.dockable_probe.detach_position.replace(' ', '').split(',') %}

  G0 X{x_approach} F{60 * travel_speed}
  G0 Z{z_detach} F{60 * z_speed}

But at this point didn’t overload MOVE_TO_INSERT_PROBE. I’s MOVE_TO_INSERT_PROBE that moves the head crazy fast.

Due to the vectors, dockable_probe only calls MOVE_TO_APPROACH_PROBE and MOVE_TO_INSERT_PROBE.

Here’s why ; the dock :

1 = knobprobe ; nozzle probe ; also acts as an endstop, and prevents the head from crashing into the dock if the Klicky is not present ; knobprobe is “responsible” for attaching the probe in the G28 macro : call to “KNOBPROBE” (“PROBE” equivalent)

2 = countersunk screw : register (reference surface) for the Klcky. The difference between Z knobprobe and Z dockable_probe gives the information for the auto Z offset.

The vectors :

approach_position :   327.5, 0, 15 ; z used for extract ?
dock_position:        327.5, 0, 15 ; z ?
detach_position:      290.0, 150.0, 1 ; wipes the Klicky

[homing_override] does a few moves and calculates the gcode Z offset, relative to a reference and saves the results in variables.cfg for future use in start gcode. Always get a brim/skirt within 0.03 mm tolerance.

dockable_probe sees an attached probe most of the time, as knobprobe takes care of it.

I’d like to experiment with the last two features : the probe sensors.

Seems to me that probe_sense_pin makes no sense if the Klicky is made with a NC contact. Can’t see a situation where it could usefull in this setup (???) But I use 3 magnets, so a third contact is available (a pulled up pin and the probe ground). Please tell me if I should experiment with…

Same with dock_sense_pin in this very specific setup, as the “nozzle probe” (knobprobe) acts as an endstop, and stops the move with an error ; then dockable_probe throws an error if the probe is not in the dock after hitting the knobprobe.

Could playing with these features give you some feedback ? It’s one hour or two with CAD software and a couple hours of printing : no big deal ; just fun… Just received nice brand new microswitches (was using salvaged but wonderfull ones, from an old Microsoft Eplorer mouse), and will receive REPRAP endstop modules on saturday…

About the activation gcodes, I had a look to knobprobe, and found that it also inherits from mcu_probe ; I attempted to add dockable_probe as parent class, suspecting the features leave in, but got errors I am unable to fix (initializations)…

Very cool! I like how compact your series of switches and surfaces is, quite tidy. It reminds me of what the Voron 2.4 has with the auto z offset. I’ve wanted to have auto z offset on bed slingers but not got the time and energy to design it. Hmm, I’ve got a stock Ender 3 in parts, maybe it will get some experimental probe parts.

Yes, please play! AFAIK the dock_sense_pin especially hasn’t got much use. It wound be great to see either or both of those optional pins tested in a real world scenario.

About the activation gcodes, I looked at the [probe] implementation yesterday and it would require a significant code change to inherit that feature. Implementing it anew would be less drastic but I think still too much new code (the upstream PR guidelines don’t want the features changing or new, little-tested code going in at the last moment). So I think I’ll strike that from the docs and implement it later. In the meantime, the experienced user (i.e. you) can add the feature through overriding gcode commands.

Looking forward to your next post with additional sensors!

It IS the Voron Z offset device. Easiest and quickiest prototyping before machining. A decapsulated 20 tooth pulley.

I have been using mental’s code for the last 2 years and it worked flawless. because I had a bad SD card I decided to try the new version that might be merged with klipper.

but I have had alot of issues. Internal error on command:“CALIBRATE_Z” Internal error on command:“BASE_CALIBRATE_Z” · Issue #105 · protoloft/klipper_z_calibration (github.com)

could someone help me with correcting what I did wrong? I really like the new code being more efficient and elegant.

this is my current working printer.cfg that works with mental’s old code.

My new attempt for this code is commented out. (please ignore the crap thats in this printer.cfg. I will completely redo it once I get manta m8p + ebb36.)
printer (1).cfg (17.1 KB)

ow and its a voron v2.4

I get internal erros while defining sense pins :

with the ‘probe_sense_pin’ :

'ProbeState' object has no attribute 'probe_sense_pin'

and with the ‘dock_sense_pin’

Internal error during connect: 'NoneType' object has no attribute 'strip'

Here’s my DockableProbe definition :

[dockable_probe]

; not supported :
;deactivate_on_each_sample: True ; ERROR "'deactivate_on_each_sample' not valid in section..."
;activate_gcode: ; ERROR "'activate_gcode' not valid in section..."
;deactivate_gcode: ; ERROR "'deactivate_gcode' not valid in section..."

pin: ^P0.10 ; SKR 1.4 BLTouch probe pin ; NC switch
x_offset: -1.84
y_offset: 20.84
z_offset: 2.55 ; increasing value lowers the nozzle
approach_position: 327.5, 150.0, 15.0 ; z also to lift before probing the coountersunk screw
dock_position:     327.5, 150.0 
detach_position:   290.0, 150.0,  1.0 ; wipe left @ Z = 1 mm
z_hop:             15.0 ; also used before probing countersunk screw reference
speed: 5            ; probing speed  ; /!\ 10 too fast for good repeatability, unlike bltouch
lift_speed:   100.0 ; 50.0 ;15.0 ; default = speed
attach_speed: 150.0 ; speed for MOVE_TO_DOCK_PROBE
detach_speed: 150.0 ; speed for MOVE_TO_DETACH_PROBE
travel_speed: 150.0 ; speed for MOVE_TO_APPROACH_PROBE

; zero retries when setting 'samples_tolerance' to 0.01 -> 1 sample is enough
;samples_tolerance: 0.01 ; 100% success
samples: 1               ; default = 1
sample_retract_dist: 2.0 ; default = 2.0
;samples_result: average
;samples_tolerance_retries: 2
check_open_attach: True
dock_retries: 0 ; not relevant

; Internal error during connect: 'ProbeState' object has no attribute 'probe_sense_pin'
;probe_sense_pin: P2.0 ; SKR 1.4 BLTouch servo out

; Internal error during connect: 'NoneType' object has no attribute 'strip'
;dock_sense_pin: P1.0 ; SKR 1.4 Z+ STOP / PwrDet

[EDIT] :

A couple of typos in two-lines strings macro help definitions :

  • lines 319-320
  • lines 330-331
  • lines 335-336
  • lines 340-341
  • lines 362-363
  • lines 368-369

(missing space before or after the backslash)

[dockable_probe]
pin: ebb36:PB5
z_offset: 6.5
sample_retract_dist: 1.5
approach_position: 32.50, 79, 10
dock_position: 32.50, 110, 10
detach_position: 32.50, 70, 10
check_open_attach: True
speed: 50

trying Z-tilt bed leveling afterwards and it also grabs the probe just fine then when it wants to move the bed ERROR.

Internal error on command:“Z_TILT_ADJUST”
klippy (14).log (1.9 MB)

Internal error on command:“Z_TILT_ADJUST”
Internal Error on WebRequest: gcode/script
Traceback (most recent call last):
File “/home/biqu/klipper/klippy/webhooks.py”, line 256, in _process_request
func(web_request)
File “/home/biqu/klipper/klippy/webhooks.py”, line 431, in _handle_script
self.gcode.run_script(web_request.get_str(‘script’))
File “/home/biqu/klipper/klippy/gcode.py”, line 216, in run_script
self._process_commands(script.split(‘\n’), need_ack=False)
File “/home/biqu/klipper/klippy/gcode.py”, line 198, in _process_commands
handler(gcmd)
File “/home/biqu/klipper/klippy/gcode.py”, line 135, in
func = lambda params: origfunc(self._get_extended_params(params))
File “/home/biqu/klipper/klippy/extras/z_tilt.py”, line 145, in cmd_Z_TILT_ADJUST
self.probe_helper.start_probe(gcmd)
File “/home/biqu/klipper/klippy/extras/probe.py”, line 433, in start_probe
pos = probe.run_probe(gcmd)
File “/home/biqu/klipper/klippy/extras/probe.py”, line 166, in run_probe
pos = self._probe(speed)
File “/home/biqu/klipper/klippy/extras/probe.py”, line 124, in _probe
epos = phoming.probing_move(self.mcu_probe, pos, speed)
File “/home/biqu/klipper/klippy/extras/homing.py”, line 246, in probing_move
epos = hmove.homing_move(pos, speed, probe_pos=True)
File “/home/biqu/klipper/klippy/extras/homing.py”, line 85, in homing_move
wait = mcu_endstop.home_start(print_time, ENDSTOP_SAMPLE_TIME,
File “/home/biqu/klipper/klippy/extras/dockable_probe.py”, line 587, in home_start
if self.multi == MULTI_OFF:
File “/home/biqu/klipper/klippy/mcu.py”, line 285, in home_start
trsync.start(print_time, self._trigger_completion, expire_timeout)
File “/home/biqu/klipper/klippy/mcu.py”, line 195, in start
ffi_lib.trdispatch_mcu_setup(self._trdispatch_mcu, clock, expire_clock,
TypeError: initializer for ctype ‘struct trdispatch_mcu *’ must be a cdata pointer, not NoneType

@BelgarionNL :
Could you please put the code snippets between code braces

Format

It’s more readable then.

@BelgarionNL I think that issue is related to what’s happening with the auto Z calibration. Can you upload your full log (as an attachment, not copy-pasted)? I think I know how to address the problem but don’t have access to a printer until Thursday. I’ll keep you posted.

@YaaJ Thanks for the report! Those look like they should be trivial bugs to fix but again, I can’t test the code until Thursday.

@cloakedcode I Think I added the entire log already its in the middle?
I simply only copy pasted the part that is giving the problem.

both now for quad gantry level and z tilt.

also why dont you just make this into an automated plugin like z calibration. you can update that script from klipper and it would solve the issue if the guy doesnt want to merge this into klipper itself (which I think is lame)

Lines 111-112, replacing dock_sense_pin with self.dock_sense_pin :

  • clears the error
  • QUERY_DOCKABLE_PROBE now returns Probe Status: DOCKED if the probe is docked
  • returns Probe Status: UNKNOWN if the probe is not docked and not attached
  • returns Probe Status: ATTACHED if the probe is attached. Sounds good.

Could it be the same @ lines 91-92 ?

At least it also clears the error. But for now I can’t test because I’m out of microswitches so I can’t build a 3 wire probe. Half a dozen nice Japanese Omron D2F-01F-T are on the road… Hope I can test soon because next week and for a few weeks, I’ll be abroad.

[EDIT] forgot to tell what the dock sensor is… Its a KY-033 module I had lying around (TCR5000 reflective IR opto + LM393 comparator with adjustable reference)

That’s great news! Yes, I suspect you’ve found the bug with the probe sense pin as well. I was going to suggest those line changes but didn’t want to give you untested code. Turns out you’ve nailed it without any help from me so carry on!

If you felt like shooting an explanatory video about your dock sensor I’d for sure watch it. I’m curious to see what you’ve built. I watched your videos on the dock and those were fun for me. I’m probably partial as it’s always fun to see your code in the real world. :wink:

Oh! If you’re up to it, I’m abroad as you will be and can’t test code changes, but would really appreciate it if you’re up to testing some code changes. There’s an odd bug (as seen in this thread and the GitHub PR) but the fix might be totally unusable so I don’t want to push the change to GitHub. I’m planning on testing it later this week but if you’re up for testing it sooner… let me know.

Ok.
Ill try to make a video showing my experimental rig and controller, working with your plugin, in sync with the terminal and macros, and will upload the macros to Github. The printer was built for experimentation, but it’s a bed slinger, so it’s impossible to reproduce what @BelgarionNL is describing.

Will take some time : my video editing skills are on the low end !

For now, here’s the important gcodes, with comments (hopefully it’ll be understandable…) :

Dockable_Probe.cfg :

#####################################################################
# Dockable_Probe.cfg
#####################################################################

#___________________________________________________________________
[delayed_gcode autorun]

initial_duration: 1
gcode:
  {% set x_probe = 317 | float %} ; = dockable_probe position for reference Z offset (countersunk screw)
  SAVE_VARIABLE VARIABLE=x_probe VALUE={x_probe}

#___________________________________________________________________
; https://github.com/TypQxQ/KnobProbe_Klipper
[knobprobe]

pin: !P1.27 ; SKR 1.4 Z- STOP
deactivate_on_each_sample: False
z_offset: 0.0
speed: 5 ; /!\ 10 too fast for good repeatability, unlike bltouch
sample_retract_dist: 2.0
lift_speed: 100 ;5
; zero retries when setting 'samples_tolerance' to 0.01 -> 1 is enough
;samples_tolerance: 0.01 ; 100% success
samples: 1
;samples_tolerance_retries: 2

#___________________________________________________________________
[gcode_macro KNOBPROBE_ACCURACY]
description : KnobProbe Z-height accuracy at dock position, and return to previous position
gcode:
  {% set pos = printer.gcode_move.gcode_position %}
  {% set lift_speed = printer.configfile.settings.dockable_probe.lift_speed | float %} ; mm/s  
  {% set travel_speed = printer.configfile.settings.dockable_probe.travel_speed | float %} ; mm/s
  MOVE_TO_EXTRACT_PROBE
  PROBE_KNOBACCURACY {rawparams}
  DETACH_PROBE
  SAVE_GCODE_STATE NAME=state_KNOBPROBE_ACCURACY
  G90
  G0 Z{pos.z} F{60 * lift_speed}
  G0 X{pos.x} Y{pos.y} F{60 * travel_speed}
  RESTORE_GCODE_STATE NAME=state_KNOBPROBE_ACCURACY

#___________________________________________________________________
[dockable_probe]

pin: ^P0.10 ; SKR 1.4 BLTouch probe pin ; NC switch
dock_sense_pin: !P1.0 ; SKR 1.4 Z+ STOP / PwrDet
;probe_sense_pin: !P2.0 ; SKR 1.4 BLTouch servo out

x_offset: -1.84
y_offset: 20.84
z_offset: 2.55 ; increasing value lowers the nozzle
approach_position: 327.5, 150.0, 15.0 ; z also for lifting before probing the coountersunk screw
dock_position:     327.5, 150.0 
detach_position:   290.0, 150.0,  1.0 ; wipe left @ Z = 1 mm
z_hop:             15.0 ; also used before probing countersunk screw reference
speed: 5            ; probing speed  ; /!\ 10 too fast for good repeatability, unlike bltouch
lift_speed:   100.0 ; 50.0 ;15.0 ; default = speed
attach_speed: 150.0 ; speed for MOVE_TO_DOCK_PROBE
detach_speed: 150.0 ; speed for MOVE_TO_DETACH_PROBE
travel_speed: 150.0 ; speed for MOVE_TO_APPROACH_PROBE

; zero retries when setting 'samples_tolerance' to 0.01 -> 1 sample is enough
;samples_tolerance: 0.01 ; 100% success
samples: 1               ; default = 1
sample_retract_dist: 2.0 ; default = 2.0
;samples_result: average
;samples_tolerance_retries: 2
check_open_attach: True
dock_retries: 0 ; not relevant

#___________________________________________________________________
[gcode_macro MOVE_TO_APPROACH_PROBE]
description: Move close to the probe dock before attaching
rename_existing: MOVE_TO_APPROACH_PROBE_BASE
gcode:
  ;RESPOND MSG="MOVE_TO_APPROACH_PROBE"
  {% set x_approach, y_dummy, z_dummy = printer.configfile.settings.dockable_probe.approach_position.replace(' ', '').split(',') %}
  {% set travel_speed = printer.configfile.settings.dockable_probe.travel_speed | float %} ; mm/s
  {% set z_speed = printer.configfile.settings.dockable_probe.attach_speed | float %} ; mm/s  
  {% set x_dummy, y_dummy, z_detach = printer.configfile.settings.dockable_probe.detach_position.replace(' ', '').split(',') %}
  {% set z_hop = printer.configfile.settings.dockable_probe.z_hop | float %} ; mm/s
  SAVE_GCODE_STATE NAME=state_MOVE_TO_APPROACH_PROBE
  G90
  G0 Z{z_hop} F{60 * z_speed}
  G0 X{x_approach} F{60 * travel_speed}
  G0 Z{z_detach} F{60 * z_speed}
  RESTORE_GCODE_STATE NAME=state_MOVE_TO_APPROACH_PROBE

#___________________________________________________________________
[gcode_macro MOVE_TO_INSERT_PROBE]
description: Move near the dock with the probe attached before detaching. Default : alias for MOVE_TO_APPROACH_PROBE
rename_existing: MOVE_TO_INSERT_PROBE_BASE
gcode:
  ;RESPOND MSG="MOVE_TO_INSERT_PROBE"
  {% set z_speed = printer.configfile.settings.dockable_probe.attach_speed | float %} ; mm/s  
  {% set z_hop = printer.configfile.settings.dockable_probe.z_hop | float %} ; mm/s    
  SAVE_GCODE_STATE NAME=state_MOVE_TO_INSERT_PROBE
  G90
  G0 Z{z_hop} F{60 * z_speed}
  RESTORE_GCODE_STATE NAME=state_MOVE_TO_INSERT_PROBE
  MOVE_TO_INSERT_PROBE_BASE  
  
#___________________________________________________________________
[gcode_macro ATTACH_PROBE]
description: Check probe status and attach probe using the movement gcodes
rename_existing: ATTACH_PROBE_BASE
gcode:
  ;RESPOND MSG="ATTACH_PROBE"
  {% set pos = printer.gcode_move.gcode_position %}
  {% set travel_speed = printer.configfile.settings.dockable_probe.travel_speed | float %} ; mm/s
  {% set lift_speed = printer.configfile.settings.dockable_probe.lift_speed | float %} ; mm/s  
  ATTACH_PROBE_BASE
  SAVE_GCODE_STATE NAME=state_ATTACH_PROBE
  G90
  G0 Z{pos.z} F{60 * lift_speed}
  G0 X{pos.x} Y{pos.y} F{60 * travel_speed}
  RESTORE_GCODE_STATE NAME=state_ATTACH_PROBE

#___________________________________________________________________
[gcode_macro DETACH_PROBE]
description: Check probe status and detach probe using the movement gcodes
rename_existing: DETACH_PROBE_BASE
gcode:
  ;RESPOND MSG="DETACH_PROBE"
  DETACH_PROBE_BASE

#___________________________________________________________________
[gcode_macro MOVE_TO_DOCK_PROBE]
description: Move to connect the toolhead/dock to the probe"
rename_existing: MOVE_TO_DOCK_PROBE_BASE
gcode:
  ;RESPOND MSG="MOVE_TO_DOCK_PROBE"
  MOVE_TO_DOCK_PROBE_BASE

#___________________________________________________________________
[gcode_macro MOVE_TO_EXTRACT_PROBE]
description: Move away from the dock with the probe attached. Default : alias for MOVE_TO_APPROACH_PROBE.
rename_existing: MOVE_TO_EXTRACT_PROBE_BASE
gcode:
  ;RESPOND MSG="MOVE_TO_EXTRACT_PROBE"
  MOVE_TO_EXTRACT_PROBE_BASE

#___________________________________________________________________
[gcode_macro MOVE_TO_DETACH_PROBE]
description: Move away from the dock to detach the probe
rename_existing: MOVE_TO_DETACH_PROBE_BASE
gcode:
  ;RESPOND MSG="MOVE_TO_DETACH_PROBE"
  MOVE_TO_DETACH_PROBE_BASE

#___________________________________________________________________
[homing_override]

; parameters :
; X, Y, Z (separated with a blank space)
; M : performs a bed leveling after homing : saves detach + reattach moves

axes: xyz

gcode:

  ; parameters ; TODO : XY if instead of X Y
  {% set _X_ = params.X|default("false") %}
  {% set _Y_ = params.Y|default("false") %}
  {% set _Z_ = params.Z|default("false") %}
  {% set _O_ = params.O|default("false") %}
  {% set _M_ = params.M|default("false") %}
  {% set _XY_ = "false" %} ; workaround : G28 X Y sets Z to true ; we need to fix this !

;  RESPOND MSG="_X_ : "{_X_}
;  RESPOND MSG="_Y_ : "{_Y_}
;  RESPOND MSG="_Z_ : "{_Z_}
;  RESPOND MSG="_O_ : "{_O_}
;  RESPOND MSG="_XY_ : "{_XY_}

  {% if _X_ != "false" %} {% set _X_ = "true" %} {% endif %}
  {% if _Y_ != "false" %} {% set _Y_ = "true" %} {% endif %}
  {% if _Z_ != "false" %} {% set _Z_ = "true" %} {% endif %}
  {% if _O_ != "false" %} {% set _O_ = "true" %} {% endif %}
  {% if _M_ != "false" %} {% set _M_ = "true" %} {% endif %}

  ; G28 XY
  {% if _X_ == "true" and _Y_ == "true" and _Z_ == "true" %}
    {% set _XY_ = "true" %}
  {% endif %}

  ; G28 = G28 XYZ
  {% if _X_ == "false" and _Y_ == "false" and _Z_ == "false" %}
    {% set _X_ = "true" %}
    {% set _Y_ = "true" %}
    {% set _Z_ = "true" %}
    {% set _XY_= "true" %}
  {% endif %}

;  RESPOND MSG="_X_ : "{_X_}
;  RESPOND MSG="_Y_ : "{_Y_}
;  RESPOND MSG="_Z_ : "{_Z_}
;  RESPOND MSG="_O_ : "{_O_}

  {% if'x' in printer.toolhead.homed_axes %}
    {% set _X_is_homed = "true" %}
  {% else %}
    {% set _X_is_homed = "false" %}
  {% endif %}
  {% if'y' in printer.toolhead.homed_axes %}
    {% set _Y_is_homed = "true" %}
  {% else %}
    {% set _Y_is_homed = "false" %}
  {% endif %}
  {% if'z' in printer.toolhead.homed_axes %}    
    {% set _Z_is_homed = "true" %}
  {% else %}
    {% set _Z_is_homed = "false" %}
  {% endif %}

  ; defined in startup delayed macro (see printer.cfg) ; stored in variables.cfg
  ; = dockable_probe position for reference Z offset (countersunk screw)
  {% set x_probe = printer.save_variables.variables.x_probe | float %}
  ; printer settings
  {% set x_bed_center, y_bed_center = printer.configfile.config.bed_mesh.zero_reference_position.replace(' ', '').split(',') %}
  {% set z_max = printer.configfile.config.stepper_z.position_max | float %}
  {% set lift_accel = printer.configfile.config.printer.max_z_accel | float %} ; mm/s²
  ; dockable_probe settings
  {% set travel_speed = printer.configfile.config.dockable_probe.travel_speed | float %} ; mm/s
  {% set lift_speed = printer.configfile.config.dockable_probe.lift_speed | float %} ; mm/s
  {% set z_hop = printer.configfile.settings.dockable_probe.z_hop | float %}
  {% set x_knobprobe, y_dummy = printer.configfile.config.dockable_probe.dock_position.replace(' ', '').split(',') %}

  SB_STATUS_HOMING ; Neopixel Stealthburner

  {% if _X_ == "true" or _Y_ == "true" or _Z_ == true %}
    FORCE_MOVE STEPPER=stepper_z DISTANCE={z_hop} VELOCITY={lift_speed} ACCEL={lift_accel} ; lift z, mm/s, mm/s²
    {% if _X_ == "false" and _Y_ == "false" and _Z_ == "false" %}
      {% set _Z_ = "true" %}
    {% endif %}
  {% endif %}

  ; individual homings ; TODO : 'O' flag
  {% if _X_ == "true" %}
    RESPOND MSG="Homing X..."
    G28 X
  {% endif %}
  {% if _Y_ == "true" %}
    RESPOND MSG="Homing Y..."
    G28 Y
  {% endif %}

  ; Z calibration, auto Z offset, home Z, optional bed mesh
  {% if _Z_ == "true" %}

    SB_STATUS_CALIBRATING_Z ; Neopixel Stealthburner
    RESPOND MSG="Attaching probe and calibrating Z offset..."
    SAVE_GCODE_STATE NAME=state_G28
    SET_DOCKABLE_PROBE AUTO_ATTACH_DETACH=0

    ; probe is attached while knobprobing (probing nozzle)
    G90 ; absolute
    G0 X{x_knobprobe} F{60 * travel_speed} ; KnobProbe position
    {% set z_kinpos = z_max - z_hop  - 10 %}
    SET_KINEMATIC_POSITION Z={z_kinpos} ; gives "headroom" when moving the head down
    KNOBPROBE SAMPLES=3 SAMPLE_RETRACT_DIST=2 ; attaches the Klicky probe + gets nozzle position : 1st reference

    ; probe reference spot
    G91 ; relative
    G0 Z{z_hop} F{60 * lift_speed} ; lift
    G90 ; absolute
    G0 X{x_probe} F{60 * travel_speed} ; probe is attached, align with countersunk screw
    PROBE SAMPLES=3 SAMPLE_RETRACT_DIST=2 ; get Klicky position against countersunk screw : 2nd reference
    _SAVE_GCODE_OFFSET ; auto Z offset
    SB_STATUS_HOMING ; Neopixel Stealthburner
    RESPOND MSG="Homing Z..."

    ; reset Z, move to center, home Z
    SET_KINEMATIC_POSITION Z=0 
    G91 ; relative
    G0 Z{z_hop} F{60 * lift_speed} ; lift
    G90 ; absolute
    G0 X{x_bed_center} Y{y_bed_center} F{60 * travel_speed} ; bed center
    RESTORE_GCODE_STATE NAME=state_G28
    G28 Z

    ; bed mesh
    {% if _M_ == "true" and _X_ == "true" and _Y_ == "true" and _Z_ == "true" %}
      SB_STATUS_MESHING ; Neopixel Stealthburner
      RESPOND MSG="Meshing..."      
      BED_MESH_CALIBRATE
    {% endif %}

    ; end
    DETACH_PROBE
    SET_DOCKABLE_PROBE AUTO_ATTACH_DETACH=1
    SB_STATUS_READY ; Neopixel Stealthburner
    RESPOND MSG="Ready..."

  {% endif %}
  
#___________________________________________________________________
[gcode_macro BED_MESH_CALIBRATE]
description: Perform Mesh Bed Leveling
rename_existing: BASE_BED_MESH_CALIBRATE
gcode:
  ATTACH_PROBE
  BASE_BED_MESH_CALIBRATE

#___________________________________________________________________
[gcode_macro AUTO_GCODE_OFFSET]
description: To be inserted in the start gcode, after G28, before first move
gcode:
  {% set gcode_offset = printer.save_variables.variables.gcode_offset | float %}
  RESPOND TYPE=command MSG="AUTO GCODE OFFSET : SET_GCODE_OFFSET Z="{gcode_offset}  
  SET_GCODE_OFFSET Z={gcode_offset}

#___________________________________________________________________
[gcode_macro SET_REF_Z_OFFSET]
description: Save reference delta Z after calibration
; calls G28 and saves delta_z as ref_delta_z ; /!\ DO *NOT* REFACTORIZE /!\
gcode:
  SAVE_VARIABLE VARIABLE=ref_delta_z VALUE={3.14159265358979323846} ; dummy ref_delta_z = flag
  G28 ; G28 will automatically reset ref_delta_z

#___________________________________________________________________
[gcode_macro _SAVE_GCODE_OFFSET]
description: Calculates and save the gcode offset given reference delta Z and current delta Z
; called by G28 ; /!\ DO *NOT* REFACTORIZE /!\
gcode:
  {% set ref_delta_z = printer.save_variables.variables.ref_delta_z | default(3.14159265358979323846) | float %}
  {% if ref_delta_z == 3.14159265358979323846 %} ; flag : ref_delta_z == pi : not initialized
    _SAVE_REF_DELTA_Z ; save the the difference between KNOBPROBE and PROBE
  {% endif %}
  _SAVE_CUR_DELTA_Z ; save the the difference between KNOBPROBE and PROBE
  _CALC_GCODE_OFFSET ; calculate and save the gcode offset ( = reference delta_z - current delta_z)

#___________________________________________________________________
[gcode_macro _SAVE_REF_DELTA_Z]
description: Save nozzle delta Z as reference offset
; called by _SAVE_GCODE_OFFSET ; /!\ DO *NOT* REFACTORIZE /!\
gcode:
  {% set probe_z = printer.probe.last_z_result %} ; ABL probe Z probing
  {% set knobprobe_z = printer.knobprobe.last_z_result %} ; KnobProbe Z probing
  {% set delta_z = printer.probe.last_z_result - printer.knobprobe.last_z_result %} ; delta z for ref. tool
  SAVE_VARIABLE VARIABLE=ref_delta_z VALUE={delta_z} ; save ref delta Z

#___________________________________________________________________
[gcode_macro _SAVE_CUR_DELTA_Z]
description: Save current nozzle delta Z
; called by _SAVE_GCODE_OFFSET ; /!\ DO *NOT* REFACTORIZE /!\
gcode:
  {% set probe_z = printer.probe.last_z_result %} ; ABL Z probing
  {% set knobprobe_z = printer.knobprobe.last_z_result %} ; knobprobe Z probing
  {% set cur_delta_z = probe_z - knobprobe_z %} ; delta z for current tool
  SAVE_VARIABLE VARIABLE=cur_delta_z VALUE={cur_delta_z} ; save current probing offsets

#___________________________________________________________________
[gcode_macro _CALC_GCODE_OFFSET]
description: Calculate and save gcode offset
; called by _SAVE_GCODE_OFFSET ; /!\ DO *NOT* REFACTORIZE /!\
gcode:
  {% set ref_delta_z = printer.save_variables.variables.ref_delta_z | default(3.14159265358979323846) | float %}
  {% set cur_delta_z = printer.save_variables.variables.cur_delta_z %}
  {% set gcode_offset = ref_delta_z - cur_delta_z %} ; gcode offset = difference
  SAVE_VARIABLE VARIABLE=gcode_offset VALUE={gcode_offset} ; save gcode offset

Start_End_gcode.cfg :



; Cura         : http://files.fieldofview.com/cura/Replacement_Patterns.html
; Prusa Slicer : https://help.prusa3d.com/article/list-of-placeholders_205643
; Super Slicer : https://github.com/supermerill/SuperSlicer/wiki/Macro-&-Variable-list
;
; Cura Start G-code :
;
; START_PRINT NOZZLE_DIAM={machine_nozzle_size} BED_TEMP={material_bed_temperature_layer_0} TOOL_TEMP={material_print_temperature_layer_0}
;
; For saving a custom PID Profile  : PID_CALIBRATE HEATER=<config_name> TARGET=<temperature> PROFILE=<profile_name>
; for loading a custom PID Profile : PID_PROFILE_LOAD HEATER=<heater> PROFILE=<profile_name> [DEFAULT=<default_profile>]
;                                                          DEFAULT is optional and behaves like a getordefault method

#___________________________________________________________________
[gcode_macro PRINT_START]
description: Start G-Code
gcode:

  ;RESPOND MSG="PRINT_START received, calling START_PRINT"
  START_PRINT {rawparams}

#___________________________________________________________________
[gcode_macro START_PRINT]
description: Start G-Code
; ex. : START_PRINT NOZZLE_DIAM=0.4 BED_TEMP=60 TOOL_TEMP=200 CHAMBER_TEMP=0

gcode:

  {% set nozzle_diam = params.NOZZLE_DIAM|float %}   ; Cura : {machine_nozzle_size}
  {% set tool_temp = params.TOOL_TEMP|float %}       ; Cura : {material_print_temperature_layer_0}
  {% set bed_temp = params.BED_TEMP|float %}         ; Cura : {material_bed_temperature_layer_0}
  ;{% set chamber_temp = params.CHAMBER_TEMP|float %} ; Cura : {build_volume_temperature}  

  RESPOND TYPE=command MSG="START_PRINT"
  ;RESPOND TYPE=command MSG="Selected : Nozzle : "{nozzle_diam} #"mm ; Tool : "{tool_temp}"°C, "Bed : "{bed_temp}"°C, "Chamber :"{chamber_temp}"°C"
  RESPOND TYPE=command MSG="Selected : Nozzle : "{nozzle_diam} #"mm ; Tool : "{tool_temp}"°C, "Bed : "{bed_temp}"°C"

  ; https://github.com/Zeanon/klipper/tree/pid_profiles
  ; heaters.py led.py pid_calibrate.py

  {% if nozzle_diam == 0.2 %}
    PID_PROFILE HEATER=extruder LOAD=V6_02 ;DEFAULT=V6_04
    SAVE_VARIABLE VARIABLE=cur_tool VALUE='"V6_02"'
  {% elif nozzle_diam == 0.3 %}
    PID_PROFILE HEATER=extruder LOAD=V6_03 ;DEFAULT=V6_04
    SAVE_VARIABLE VARIABLE=cur_tool VALUE='"V6_03"'
  {% elif nozzle_diam  == 0.4 %}
    PID_PROFILE HEATER=extruder LOAD=V6_04 ;DEFAULT=V6_04
    SAVE_VARIABLE VARIABLE=cur_tool VALUE='"V6_04"'
  {% elif nozzle_diam  == 0.5 %}
    PID_PROFILE HEATER=extruder LOAD=V6_05 ;DEFAULT=V6_04
    SAVE_VARIABLE VARIABLE=cur_tool VALUE='"V6_05"'
  {% elif nozzle_diam  == 0.6 %}
    PID_PROFILE HEATER=extruder LOAD=V6_06 ;DEFAULT=V6_04
    SAVE_VARIABLE VARIABLE=cur_tool VALUE='"V6_06"'
  {% elif nozzle_diam  == 0.8 %}
    PID_PROFILE HEATER=extruder LOAD=V6_08 ;DEFAULT=V6_04
    SAVE_VARIABLE VARIABLE=cur_tool VALUE='"V6_08"'
  {% elif nozzle_diam  == 1.0 %}
    PID_PROFILE HEATER=extruder LOAD=V6_10 ;DEFAULT=V6_04
    SAVE_VARIABLE VARIABLE=cur_tool VALUE='"V6_10"'
  {% else %}
    RESPOND TYPE=error MSG={nozzle_diam}"mm nozzle ? Are you kidding me ???"
    ;_PRINT_ABORT
    CANCEL_PRINT
  {% endif %}

  ; M117 Heating...              ; LCD = Heating
  SB_STATUS_HEATING				 ; Neopixel	Stealthburner
  
  M140 S{bed_temp}               ; heat bed, don't wait
  M104 S{tool_temp}              ; heat tool, don't wait
  ;M141 S{chamber_temp}           ; heat chamber, don't wait  TODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODO
  
  M190 S{bed_temp}               ; wait for bed temp
  M109 S{tool_temp}              ; wait for tool temp
  ;M191 S{chamber_temp}           ; wait for chamber temp  TODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODOTODO

  G21                            ; metric
  G90                            ; absolute coordinates
  M82                            ; absolute extrusion mode
  G92 E0                         ; set extruder to zero

  G28 M ; home + bed mesh calibrate ; see CFG/Dockable_Probe.cfg

  ; stops heating after loading profiles ???
  M190 S{bed_temp}               ; wait for bed temp
  M109 S{tool_temp}              ; wait for tool temp

  AUTO_GCODE_OFFSET              ; from CFG/Dockable_Probe.cfg

  ; M117 Cleaning...             ; LCD = Cleaning
  SB_STATUS_CLEANING             ; Neopixel Stealthburner
  G0 X5 Y10 F20000               ; move in 5x10mm from edge
  G0 Z0.2 F1000                  ; move up 0.2mm
  G1 Y100 E12 F500               ; priming : extrude 12mm filament 100mm long
  G0 Y180 F4000                  ; quick wipe 80mm long
  G0 Z2 F1000                    ; move up 2mm to prevent scratching

  ; M117 Printing...              ; LCD = Printing...
  SB_STATUS_PRINTING             ; Neopixel Stealthburner
  G90                            ; absolute  coordinates

#___________________________________________________________________
[gcode_macro PRINT_END]
description: End G-Code
gcode:

  END_PRINT {rawparams}

#___________________________________________________________________
[gcode_macro END_PRINT]
description: End G-Code
gcode:

  ;G27                  ; park
  G91                   ; relative coordinates
  G0 Z+5 F1000          ; move up 5mm
  G90                   ; absolute  coordinates
  G0 X5 Y295 F10000     ; move in 5x10mm from edge
  M84                   ; disable motors
  M104 S0               ; hotend off, continue
  M140 S0               ; bed off, continue
  SB_STATUS_READY       ; Neopixel Stealthburner

The dock that can compute auto Z offset, with it’s opto dock sensor (ABS) :

A “few” earlier prototypes (PLA) :

Designed to be easily printable, @45° :

This could be a severe limitation : the printer was designed so the nozzle can go 20mm off the bed… (uses the full X extrusion, not sure it could be done with a stock Ender)

More information about the design : Building a CoreXY : machined or printed parts ?
Will update on Reprap forums later, when everything is 100% completed.
This printer was made to learn Kllipper, and learn about designing printers (wanted to make a Voron 2.4, will be a downsized Voron 24" when it will be made public ; meanwhile, maybe a Voron 0.2)

The core is Dockable_Probe for full automation, and PID profiles by Zeanon. Works perfectly, with brims within ±0.02 mm at worst.

It’s also a proof of concept for Auto Z offset integrated in Dockable_Probe :smiling_face:

1 Like