My Marlin configs for Fabrikator Mini and CTC i3 Pro B
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tool_change.cpp 44KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325
  1. /**
  2. * Marlin 3D Printer Firmware
  3. * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
  4. *
  5. * Based on Sprinter and grbl.
  6. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. *
  21. */
  22. #include "../inc/MarlinConfigPre.h"
  23. #include "tool_change.h"
  24. #include "probe.h"
  25. #include "motion.h"
  26. #include "planner.h"
  27. #include "temperature.h"
  28. #include "../MarlinCore.h"
  29. //#define DEBUG_TOOL_CHANGE
  30. #define DEBUG_OUT ENABLED(DEBUG_TOOL_CHANGE)
  31. #include "../core/debug_out.h"
  32. #if HAS_MULTI_EXTRUDER
  33. toolchange_settings_t toolchange_settings; // Initialized by settings.load()
  34. #endif
  35. #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE)
  36. migration_settings_t migration = migration_defaults;
  37. bool enable_first_prime;
  38. #endif
  39. #if ENABLED(TOOLCHANGE_FS_INIT_BEFORE_SWAP)
  40. bool toolchange_extruder_ready[EXTRUDERS];
  41. #endif
  42. #if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
  43. uint16_t singlenozzle_temp[EXTRUDERS];
  44. #endif
  45. #if BOTH(HAS_FAN, SINGLENOZZLE_STANDBY_FAN)
  46. uint8_t singlenozzle_fan_speed[EXTRUDERS];
  47. #endif
  48. #if ENABLED(MAGNETIC_PARKING_EXTRUDER) || defined(EVENT_GCODE_AFTER_TOOLCHANGE) || (ENABLED(PARKING_EXTRUDER) && PARKING_EXTRUDER_SOLENOIDS_DELAY > 0)
  49. #include "../gcode/gcode.h"
  50. #endif
  51. #if ENABLED(DUAL_X_CARRIAGE)
  52. #include "stepper.h"
  53. #endif
  54. #if ANY(SWITCHING_EXTRUDER, SWITCHING_NOZZLE, SWITCHING_TOOLHEAD)
  55. #include "servo.h"
  56. #endif
  57. #if ENABLED(EXT_SOLENOID) && DISABLED(PARKING_EXTRUDER)
  58. #include "../feature/solenoid.h"
  59. #endif
  60. #if ENABLED(MIXING_EXTRUDER)
  61. #include "../feature/mixing.h"
  62. #endif
  63. #if HAS_LEVELING
  64. #include "../feature/bedlevel/bedlevel.h"
  65. #endif
  66. #if HAS_FANMUX
  67. #include "../feature/fanmux.h"
  68. #endif
  69. #if HAS_PRUSA_MMU1
  70. #include "../feature/mmu/mmu.h"
  71. #elif HAS_PRUSA_MMU2
  72. #include "../feature/mmu/mmu2.h"
  73. #endif
  74. #if HAS_LCD_MENU
  75. #include "../lcd/marlinui.h"
  76. #endif
  77. #if ENABLED(ADVANCED_PAUSE_FEATURE)
  78. #include "../feature/pause.h"
  79. #endif
  80. #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
  81. #include "../gcode/gcode.h"
  82. #if TOOLCHANGE_FS_WIPE_RETRACT <= 0
  83. #undef TOOLCHANGE_FS_WIPE_RETRACT
  84. #define TOOLCHANGE_FS_WIPE_RETRACT 0
  85. #endif
  86. #endif
  87. #if DO_SWITCH_EXTRUDER
  88. #if EXTRUDERS > 3
  89. #define _SERVO_NR(E) ((E) < 2 ? SWITCHING_EXTRUDER_SERVO_NR : SWITCHING_EXTRUDER_E23_SERVO_NR)
  90. #else
  91. #define _SERVO_NR(E) SWITCHING_EXTRUDER_SERVO_NR
  92. #endif
  93. void move_extruder_servo(const uint8_t e) {
  94. planner.synchronize();
  95. #if EXTRUDERS & 1
  96. if (e < EXTRUDERS - 1)
  97. #endif
  98. {
  99. MOVE_SERVO(_SERVO_NR(e), servo_angles[_SERVO_NR(e)][e & 1]);
  100. safe_delay(500);
  101. }
  102. }
  103. #endif // DO_SWITCH_EXTRUDER
  104. #if ENABLED(SWITCHING_NOZZLE)
  105. #if SWITCHING_NOZZLE_TWO_SERVOS
  106. inline void _move_nozzle_servo(const uint8_t e, const uint8_t angle_index) {
  107. constexpr int8_t sns_index[2] = { SWITCHING_NOZZLE_SERVO_NR, SWITCHING_NOZZLE_E1_SERVO_NR };
  108. constexpr int16_t sns_angles[2] = SWITCHING_NOZZLE_SERVO_ANGLES;
  109. planner.synchronize();
  110. MOVE_SERVO(sns_index[e], sns_angles[angle_index]);
  111. safe_delay(500);
  112. }
  113. void lower_nozzle(const uint8_t e) { _move_nozzle_servo(e, 0); }
  114. void raise_nozzle(const uint8_t e) { _move_nozzle_servo(e, 1); }
  115. #else
  116. void move_nozzle_servo(const uint8_t angle_index) {
  117. planner.synchronize();
  118. MOVE_SERVO(SWITCHING_NOZZLE_SERVO_NR, servo_angles[SWITCHING_NOZZLE_SERVO_NR][angle_index]);
  119. safe_delay(500);
  120. }
  121. #endif
  122. #endif // SWITCHING_NOZZLE
  123. inline void _line_to_current(const AxisEnum fr_axis, const float fscale=1) {
  124. line_to_current_position(planner.settings.max_feedrate_mm_s[fr_axis] * fscale);
  125. }
  126. inline void slow_line_to_current(const AxisEnum fr_axis) { _line_to_current(fr_axis, 0.5f); }
  127. inline void fast_line_to_current(const AxisEnum fr_axis) { _line_to_current(fr_axis); }
  128. #if ENABLED(MAGNETIC_PARKING_EXTRUDER)
  129. float parkingposx[2], // M951 R L
  130. parkinggrabdistance, // M951 I
  131. parkingslowspeed, // M951 J
  132. parkinghighspeed, // M951 H
  133. parkingtraveldistance, // M951 D
  134. compensationmultiplier;
  135. inline void magnetic_parking_extruder_tool_change(const uint8_t new_tool) {
  136. const float oldx = current_position.x,
  137. grabpos = mpe_settings.parking_xpos[new_tool] + (new_tool ? mpe_settings.grab_distance : -mpe_settings.grab_distance),
  138. offsetcompensation = TERN0(HAS_HOTEND_OFFSET, hotend_offset[active_extruder].x * mpe_settings.compensation_factor);
  139. if (homing_needed_error(_BV(X_AXIS))) return;
  140. /**
  141. * Z Lift and Nozzle Offset shift ar defined in caller method to work equal with any Multi Hotend realization
  142. *
  143. * Steps:
  144. * 1. Move high speed to park position of new extruder
  145. * 2. Move to couple position of new extruder (this also discouple the old extruder)
  146. * 3. Move to park position of new extruder
  147. * 4. Move high speed to approach park position of old extruder
  148. * 5. Move to park position of old extruder
  149. * 6. Move to starting position
  150. */
  151. // STEP 1
  152. current_position.x = mpe_settings.parking_xpos[new_tool] + offsetcompensation;
  153. DEBUG_ECHOPAIR("(1) Move extruder ", int(new_tool));
  154. DEBUG_POS(" to new extruder ParkPos", current_position);
  155. planner.buffer_line(current_position, mpe_settings.fast_feedrate, new_tool);
  156. planner.synchronize();
  157. // STEP 2
  158. current_position.x = grabpos + offsetcompensation;
  159. DEBUG_ECHOPAIR("(2) Couple extruder ", int(new_tool));
  160. DEBUG_POS(" to new extruder GrabPos", current_position);
  161. planner.buffer_line(current_position, mpe_settings.slow_feedrate, new_tool);
  162. planner.synchronize();
  163. // Delay before moving tool, to allow magnetic coupling
  164. gcode.dwell(150);
  165. // STEP 3
  166. current_position.x = mpe_settings.parking_xpos[new_tool] + offsetcompensation;
  167. DEBUG_ECHOPAIR("(3) Move extruder ", int(new_tool));
  168. DEBUG_POS(" back to new extruder ParkPos", current_position);
  169. planner.buffer_line(current_position, mpe_settings.slow_feedrate, new_tool);
  170. planner.synchronize();
  171. // STEP 4
  172. current_position.x = mpe_settings.parking_xpos[active_extruder] + (active_extruder == 0 ? MPE_TRAVEL_DISTANCE : -MPE_TRAVEL_DISTANCE) + offsetcompensation;
  173. DEBUG_ECHOPAIR("(4) Move extruder ", int(new_tool));
  174. DEBUG_POS(" close to old extruder ParkPos", current_position);
  175. planner.buffer_line(current_position, mpe_settings.fast_feedrate, new_tool);
  176. planner.synchronize();
  177. // STEP 5
  178. current_position.x = mpe_settings.parking_xpos[active_extruder] + offsetcompensation;
  179. DEBUG_ECHOPAIR("(5) Park extruder ", int(new_tool));
  180. DEBUG_POS(" at old extruder ParkPos", current_position);
  181. planner.buffer_line(current_position, mpe_settings.slow_feedrate, new_tool);
  182. planner.synchronize();
  183. // STEP 6
  184. current_position.x = oldx;
  185. DEBUG_ECHOPAIR("(6) Move extruder ", int(new_tool));
  186. DEBUG_POS(" to starting position", current_position);
  187. planner.buffer_line(current_position, mpe_settings.fast_feedrate, new_tool);
  188. planner.synchronize();
  189. DEBUG_ECHOLNPGM("Autopark done.");
  190. }
  191. #elif ENABLED(PARKING_EXTRUDER)
  192. void pe_solenoid_init() {
  193. LOOP_LE_N(n, 1)
  194. #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT)
  195. pe_activate_solenoid(n);
  196. #else
  197. pe_deactivate_solenoid(n);
  198. #endif
  199. }
  200. void pe_set_solenoid(const uint8_t extruder_num, const uint8_t state) {
  201. switch (extruder_num) {
  202. case 1: OUT_WRITE(SOL1_PIN, state); break;
  203. default: OUT_WRITE(SOL0_PIN, state); break;
  204. }
  205. #if PARKING_EXTRUDER_SOLENOIDS_DELAY > 0
  206. gcode.dwell(PARKING_EXTRUDER_SOLENOIDS_DELAY);
  207. #endif
  208. }
  209. bool extruder_parked = true, do_solenoid_activation = true;
  210. // Modifies tool_change() behavior based on homing side
  211. bool parking_extruder_unpark_after_homing(const uint8_t final_tool, bool homed_towards_final_tool) {
  212. do_solenoid_activation = false; // Tell parking_extruder_tool_change to skip solenoid activation
  213. if (!extruder_parked) return false; // nothing to do
  214. if (homed_towards_final_tool) {
  215. pe_deactivate_solenoid(1 - final_tool);
  216. DEBUG_ECHOLNPAIR("Disengage magnet", (int)(1 - final_tool));
  217. pe_activate_solenoid(final_tool);
  218. DEBUG_ECHOLNPAIR("Engage magnet", (int)final_tool);
  219. extruder_parked = false;
  220. return false;
  221. }
  222. return true;
  223. }
  224. void parking_extruder_set_parked() { extruder_parked = true; }
  225. inline void parking_extruder_tool_change(const uint8_t new_tool, bool no_move) {
  226. if (!no_move) {
  227. constexpr float parkingposx[] = PARKING_EXTRUDER_PARKING_X;
  228. #if HAS_HOTEND_OFFSET
  229. const float x_offset = hotend_offset[active_extruder].x;
  230. #else
  231. constexpr float x_offset = 0;
  232. #endif
  233. const float midpos = (parkingposx[0] + parkingposx[1]) * 0.5f + x_offset,
  234. grabpos = parkingposx[new_tool] + (new_tool ? PARKING_EXTRUDER_GRAB_DISTANCE : -(PARKING_EXTRUDER_GRAB_DISTANCE)) + x_offset;
  235. /**
  236. * 1. Move to park position of old extruder
  237. * 2. Disengage magnetic field, wait for delay
  238. * 3. Move near new extruder
  239. * 4. Engage magnetic field for new extruder
  240. * 5. Move to parking incl. offset of new extruder
  241. * 6. Lower Z-Axis
  242. */
  243. // STEP 1
  244. DEBUG_POS("Start PE Tool-Change", current_position);
  245. // Don't park the active_extruder unless unparked
  246. if (!extruder_parked) {
  247. current_position.x = parkingposx[active_extruder] + x_offset;
  248. DEBUG_ECHOLNPAIR("(1) Park extruder ", int(active_extruder));
  249. DEBUG_POS("Moving ParkPos", current_position);
  250. fast_line_to_current(X_AXIS);
  251. // STEP 2
  252. planner.synchronize();
  253. DEBUG_ECHOLNPGM("(2) Disengage magnet");
  254. pe_deactivate_solenoid(active_extruder);
  255. // STEP 3
  256. current_position.x += active_extruder ? -10 : 10; // move 10mm away from parked extruder
  257. DEBUG_ECHOLNPGM("(3) Move near new extruder");
  258. DEBUG_POS("Move away from parked extruder", current_position);
  259. fast_line_to_current(X_AXIS);
  260. }
  261. // STEP 4
  262. planner.synchronize();
  263. DEBUG_ECHOLNPGM("(4) Engage magnetic field");
  264. // Just save power for inverted magnets
  265. TERN_(PARKING_EXTRUDER_SOLENOIDS_INVERT, pe_activate_solenoid(active_extruder));
  266. pe_activate_solenoid(new_tool);
  267. // STEP 5
  268. current_position.x = grabpos + (new_tool ? -10 : 10);
  269. fast_line_to_current(X_AXIS);
  270. current_position.x = grabpos;
  271. DEBUG_SYNCHRONIZE();
  272. DEBUG_POS("(5) Unpark extruder", current_position);
  273. slow_line_to_current(X_AXIS);
  274. // STEP 6
  275. current_position.x = midpos - TERN0(HAS_HOTEND_OFFSET, hotend_offset[new_tool].x);
  276. DEBUG_SYNCHRONIZE();
  277. DEBUG_POS("(6) Move midway between hotends", current_position);
  278. fast_line_to_current(X_AXIS);
  279. planner.synchronize(); // Always sync the final move
  280. DEBUG_POS("PE Tool-Change done.", current_position);
  281. extruder_parked = false;
  282. }
  283. else if (do_solenoid_activation) { // && nomove == true
  284. // Deactivate old extruder solenoid
  285. TERN(PARKING_EXTRUDER_SOLENOIDS_INVERT, pe_activate_solenoid, pe_deactivate_solenoid)(active_extruder);
  286. // Only engage magnetic field for new extruder
  287. TERN(PARKING_EXTRUDER_SOLENOIDS_INVERT, pe_deactivate_solenoid, pe_activate_solenoid)(new_tool);
  288. }
  289. do_solenoid_activation = true; // Activate solenoid for subsequent tool_change()
  290. }
  291. #endif // PARKING_EXTRUDER
  292. #if ENABLED(SWITCHING_TOOLHEAD)
  293. inline void swt_lock(const bool locked=true) {
  294. const uint16_t swt_angles[2] = SWITCHING_TOOLHEAD_SERVO_ANGLES;
  295. MOVE_SERVO(SWITCHING_TOOLHEAD_SERVO_NR, swt_angles[locked ? 0 : 1]);
  296. }
  297. void swt_init() { swt_lock(); }
  298. inline void switching_toolhead_tool_change(const uint8_t new_tool, bool no_move/*=false*/) {
  299. if (no_move) return;
  300. constexpr float toolheadposx[] = SWITCHING_TOOLHEAD_X_POS;
  301. const float placexpos = toolheadposx[active_extruder],
  302. grabxpos = toolheadposx[new_tool];
  303. /**
  304. * 1. Move to switch position of current toolhead
  305. * 2. Unlock tool and drop it in the dock
  306. * 3. Move to the new toolhead
  307. * 4. Grab and lock the new toolhead
  308. */
  309. // 1. Move to switch position of current toolhead
  310. DEBUG_POS("Start ST Tool-Change", current_position);
  311. current_position.x = placexpos;
  312. DEBUG_ECHOLNPAIR("(1) Place old tool ", int(active_extruder));
  313. DEBUG_POS("Move X SwitchPos", current_position);
  314. fast_line_to_current(X_AXIS);
  315. current_position.y = SWITCHING_TOOLHEAD_Y_POS - (SWITCHING_TOOLHEAD_Y_SECURITY);
  316. DEBUG_SYNCHRONIZE();
  317. DEBUG_POS("Move Y SwitchPos + Security", current_position);
  318. fast_line_to_current(Y_AXIS);
  319. // 2. Unlock tool and drop it in the dock
  320. planner.synchronize();
  321. DEBUG_ECHOLNPGM("(2) Unlock and Place Toolhead");
  322. swt_lock(false);
  323. safe_delay(500);
  324. current_position.y = SWITCHING_TOOLHEAD_Y_POS;
  325. DEBUG_POS("Move Y SwitchPos", current_position);
  326. slow_line_to_current(Y_AXIS);
  327. // Wait for move to complete, then another 0.2s
  328. planner.synchronize();
  329. safe_delay(200);
  330. current_position.y -= SWITCHING_TOOLHEAD_Y_CLEAR;
  331. DEBUG_POS("Move back Y clear", current_position);
  332. fast_line_to_current(Y_AXIS); // move away from docked toolhead
  333. // 3. Move to the new toolhead
  334. current_position.x = grabxpos;
  335. DEBUG_SYNCHRONIZE();
  336. DEBUG_ECHOLNPGM("(3) Move to new toolhead position");
  337. DEBUG_POS("Move to new toolhead X", current_position);
  338. fast_line_to_current(X_AXIS);
  339. current_position.y = SWITCHING_TOOLHEAD_Y_POS - (SWITCHING_TOOLHEAD_Y_SECURITY);
  340. DEBUG_SYNCHRONIZE();
  341. DEBUG_POS("Move Y SwitchPos + Security", current_position);
  342. fast_line_to_current(Y_AXIS);
  343. // 4. Grab and lock the new toolhead
  344. current_position.y = SWITCHING_TOOLHEAD_Y_POS;
  345. DEBUG_SYNCHRONIZE();
  346. DEBUG_ECHOLNPGM("(4) Grab and lock new toolhead");
  347. DEBUG_POS("Move Y SwitchPos", current_position);
  348. slow_line_to_current(Y_AXIS);
  349. // Wait for move to finish, pause 0.2s, move servo, pause 0.5s
  350. planner.synchronize();
  351. safe_delay(200);
  352. swt_lock();
  353. safe_delay(500);
  354. current_position.y -= SWITCHING_TOOLHEAD_Y_CLEAR;
  355. DEBUG_POS("Move back Y clear", current_position);
  356. fast_line_to_current(Y_AXIS); // Move away from docked toolhead
  357. planner.synchronize(); // Always sync the final move
  358. DEBUG_POS("ST Tool-Change done.", current_position);
  359. }
  360. #elif ENABLED(MAGNETIC_SWITCHING_TOOLHEAD)
  361. inline void magnetic_switching_toolhead_tool_change(const uint8_t new_tool, bool no_move/*=false*/) {
  362. if (no_move) return;
  363. constexpr float toolheadposx[] = SWITCHING_TOOLHEAD_X_POS,
  364. toolheadclearx[] = SWITCHING_TOOLHEAD_X_SECURITY;
  365. const float placexpos = toolheadposx[active_extruder],
  366. placexclear = toolheadclearx[active_extruder],
  367. grabxpos = toolheadposx[new_tool],
  368. grabxclear = toolheadclearx[new_tool];
  369. /**
  370. * 1. Move to switch position of current toolhead
  371. * 2. Release and place toolhead in the dock
  372. * 3. Move to the new toolhead
  373. * 4. Grab the new toolhead and move to security position
  374. */
  375. DEBUG_POS("Start MST Tool-Change", current_position);
  376. // 1. Move to switch position current toolhead
  377. current_position.y = SWITCHING_TOOLHEAD_Y_POS + SWITCHING_TOOLHEAD_Y_CLEAR;
  378. SERIAL_ECHOLNPAIR("(1) Place old tool ", int(active_extruder));
  379. DEBUG_POS("Move Y SwitchPos + Security", current_position);
  380. fast_line_to_current(Y_AXIS);
  381. current_position.x = placexclear;
  382. DEBUG_SYNCHRONIZE();
  383. DEBUG_POS("Move X SwitchPos + Security", current_position);
  384. fast_line_to_current(X_AXIS);
  385. current_position.y = SWITCHING_TOOLHEAD_Y_POS;
  386. DEBUG_SYNCHRONIZE();
  387. DEBUG_POS("Move Y SwitchPos", current_position);
  388. fast_line_to_current(Y_AXIS);
  389. current_position.x = placexpos;
  390. DEBUG_SYNCHRONIZE();
  391. DEBUG_POS("Move X SwitchPos", current_position);
  392. line_to_current_position(planner.settings.max_feedrate_mm_s[X_AXIS] * 0.25f);
  393. // 2. Release and place toolhead in the dock
  394. DEBUG_SYNCHRONIZE();
  395. DEBUG_ECHOLNPGM("(2) Release and Place Toolhead");
  396. current_position.y = SWITCHING_TOOLHEAD_Y_POS + SWITCHING_TOOLHEAD_Y_RELEASE;
  397. DEBUG_POS("Move Y SwitchPos + Release", current_position);
  398. line_to_current_position(planner.settings.max_feedrate_mm_s[Y_AXIS] * 0.1f);
  399. current_position.y = SWITCHING_TOOLHEAD_Y_POS + SWITCHING_TOOLHEAD_Y_SECURITY;
  400. DEBUG_SYNCHRONIZE();
  401. DEBUG_POS("Move Y SwitchPos + Security", current_position);
  402. line_to_current_position(planner.settings.max_feedrate_mm_s[Y_AXIS]);
  403. // 3. Move to new toolhead position
  404. DEBUG_SYNCHRONIZE();
  405. DEBUG_ECHOLNPGM("(3) Move to new toolhead position");
  406. current_position.x = grabxpos;
  407. DEBUG_POS("Move to new toolhead X", current_position);
  408. fast_line_to_current(X_AXIS);
  409. // 4. Grab the new toolhead and move to security position
  410. DEBUG_SYNCHRONIZE();
  411. DEBUG_ECHOLNPGM("(4) Grab new toolhead, move to security position");
  412. current_position.y = SWITCHING_TOOLHEAD_Y_POS + SWITCHING_TOOLHEAD_Y_RELEASE;
  413. DEBUG_POS("Move Y SwitchPos + Release", current_position);
  414. line_to_current_position(planner.settings.max_feedrate_mm_s[Y_AXIS]);
  415. current_position.y = SWITCHING_TOOLHEAD_Y_POS;
  416. DEBUG_SYNCHRONIZE();
  417. DEBUG_POS("Move Y SwitchPos", current_position);
  418. _line_to_current(Y_AXIS, 0.2f);
  419. #if ENABLED(PRIME_BEFORE_REMOVE) && (SWITCHING_TOOLHEAD_PRIME_MM || SWITCHING_TOOLHEAD_RETRACT_MM)
  420. #if SWITCHING_TOOLHEAD_PRIME_MM
  421. current_position.e += SWITCHING_TOOLHEAD_PRIME_MM;
  422. planner.buffer_line(current_position, MMM_TO_MMS(SWITCHING_TOOLHEAD_PRIME_FEEDRATE), new_tool);
  423. #endif
  424. #if SWITCHING_TOOLHEAD_RETRACT_MM
  425. current_position.e -= SWITCHING_TOOLHEAD_RETRACT_MM;
  426. planner.buffer_line(current_position, MMM_TO_MMS(SWITCHING_TOOLHEAD_RETRACT_FEEDRATE), new_tool);
  427. #endif
  428. #else
  429. planner.synchronize();
  430. safe_delay(100); // Give switch time to settle
  431. #endif
  432. current_position.x = grabxclear;
  433. DEBUG_POS("Move to new toolhead X + Security", current_position);
  434. _line_to_current(X_AXIS, 0.1f);
  435. planner.synchronize();
  436. safe_delay(100); // Give switch time to settle
  437. current_position.y += SWITCHING_TOOLHEAD_Y_CLEAR;
  438. DEBUG_POS("Move back Y clear", current_position);
  439. fast_line_to_current(Y_AXIS); // move away from docked toolhead
  440. planner.synchronize(); // Always sync last tool-change move
  441. DEBUG_POS("MST Tool-Change done.", current_position);
  442. }
  443. #elif ENABLED(ELECTROMAGNETIC_SWITCHING_TOOLHEAD)
  444. inline void est_activate_solenoid() { OUT_WRITE(SOL0_PIN, HIGH); }
  445. inline void est_deactivate_solenoid() { OUT_WRITE(SOL0_PIN, LOW); }
  446. void est_init() { est_activate_solenoid(); }
  447. inline void em_switching_toolhead_tool_change(const uint8_t new_tool, bool no_move) {
  448. if (no_move) return;
  449. constexpr float toolheadposx[] = SWITCHING_TOOLHEAD_X_POS;
  450. const float placexpos = toolheadposx[active_extruder],
  451. grabxpos = toolheadposx[new_tool];
  452. const xyz_pos_t &hoffs = hotend_offset[active_extruder];
  453. /**
  454. * 1. Raise Z-Axis to give enough clearance
  455. * 2. Move to position near active extruder parking
  456. * 3. Move gently to park position of active extruder
  457. * 4. Disengage magnetic field, wait for delay
  458. * 5. Leave extruder and move to position near new extruder parking
  459. * 6. Move gently to park position of new extruder
  460. * 7. Engage magnetic field for new extruder parking
  461. * 8. Unpark extruder
  462. * 9. Apply Z hotend offset to current position
  463. */
  464. DEBUG_POS("Start EMST Tool-Change", current_position);
  465. // 1. Raise Z-Axis to give enough clearance
  466. current_position.z += SWITCHING_TOOLHEAD_Z_HOP;
  467. DEBUG_POS("(1) Raise Z-Axis ", current_position);
  468. fast_line_to_current(Z_AXIS);
  469. // 2. Move to position near active extruder parking
  470. DEBUG_SYNCHRONIZE();
  471. DEBUG_ECHOLNPAIR("(2) Move near active extruder parking", active_extruder);
  472. DEBUG_POS("Moving ParkPos", current_position);
  473. current_position.set(hoffs.x + placexpos,
  474. hoffs.y + SWITCHING_TOOLHEAD_Y_POS + SWITCHING_TOOLHEAD_Y_CLEAR);
  475. fast_line_to_current(X_AXIS);
  476. // 3. Move gently to park position of active extruder
  477. DEBUG_SYNCHRONIZE();
  478. SERIAL_ECHOLNPAIR("(3) Move gently to park position of active extruder", active_extruder);
  479. DEBUG_POS("Moving ParkPos", current_position);
  480. current_position.y -= SWITCHING_TOOLHEAD_Y_CLEAR;
  481. slow_line_to_current(Y_AXIS);
  482. // 4. Disengage magnetic field, wait for delay
  483. planner.synchronize();
  484. DEBUG_ECHOLNPGM("(4) Disengage magnet");
  485. est_deactivate_solenoid();
  486. // 5. Leave extruder and move to position near new extruder parking
  487. DEBUG_ECHOLNPGM("(5) Move near new extruder parking");
  488. DEBUG_POS("Moving ParkPos", current_position);
  489. current_position.y += SWITCHING_TOOLHEAD_Y_CLEAR;
  490. slow_line_to_current(Y_AXIS);
  491. current_position.set(hoffs.x + grabxpos,
  492. hoffs.y + SWITCHING_TOOLHEAD_Y_POS + SWITCHING_TOOLHEAD_Y_CLEAR);
  493. fast_line_to_current(X_AXIS);
  494. // 6. Move gently to park position of new extruder
  495. current_position.y -= SWITCHING_TOOLHEAD_Y_CLEAR;
  496. if (DEBUGGING(LEVELING)) {
  497. planner.synchronize();
  498. DEBUG_ECHOLNPGM("(6) Move near new extruder");
  499. }
  500. slow_line_to_current(Y_AXIS);
  501. // 7. Engage magnetic field for new extruder parking
  502. DEBUG_SYNCHRONIZE();
  503. DEBUG_ECHOLNPGM("(7) Engage magnetic field");
  504. est_activate_solenoid();
  505. // 8. Unpark extruder
  506. current_position.y += SWITCHING_TOOLHEAD_Y_CLEAR;
  507. DEBUG_ECHOLNPGM("(8) Unpark extruder");
  508. slow_line_to_current(X_AXIS);
  509. planner.synchronize(); // Always sync the final move
  510. // 9. Apply Z hotend offset to current position
  511. DEBUG_POS("(9) Applying Z-offset", current_position);
  512. current_position.z += hoffs.z - hotend_offset[new_tool].z;
  513. DEBUG_POS("EMST Tool-Change done.", current_position);
  514. }
  515. #endif // ELECTROMAGNETIC_SWITCHING_TOOLHEAD
  516. #if EXTRUDERS
  517. inline void invalid_extruder_error(const uint8_t e) {
  518. SERIAL_ECHO_START();
  519. SERIAL_CHAR('T'); SERIAL_ECHO(int(e));
  520. SERIAL_CHAR(' '); SERIAL_ECHOLNPGM(STR_INVALID_EXTRUDER);
  521. }
  522. #endif
  523. #if ENABLED(DUAL_X_CARRIAGE)
  524. /**
  525. * @brief Dual X Tool Change
  526. * @details Change tools, with extra behavior based on current mode
  527. *
  528. * @param new_tool Tool index to activate
  529. * @param no_move Flag indicating no moves should take place
  530. */
  531. inline void dualx_tool_change(const uint8_t new_tool, bool &no_move) {
  532. DEBUG_ECHOPGM("Dual X Carriage Mode ");
  533. switch (dual_x_carriage_mode) {
  534. case DXC_FULL_CONTROL_MODE: DEBUG_ECHOLNPGM("FULL_CONTROL"); break;
  535. case DXC_AUTO_PARK_MODE: DEBUG_ECHOLNPGM("AUTO_PARK"); break;
  536. case DXC_DUPLICATION_MODE: DEBUG_ECHOLNPGM("DUPLICATION"); break;
  537. case DXC_MIRRORED_MODE: DEBUG_ECHOLNPGM("MIRRORED"); break;
  538. }
  539. // Get the home position of the currently-active tool
  540. const float xhome = x_home_pos(active_extruder);
  541. if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE // If Auto-Park mode is enabled
  542. && IsRunning() && !no_move // ...and movement is permitted
  543. && (delayed_move_time || current_position.x != xhome) // ...and delayed_move_time is set OR not "already parked"...
  544. ) {
  545. DEBUG_ECHOLNPAIR("MoveX to ", xhome);
  546. current_position.x = xhome;
  547. line_to_current_position(planner.settings.max_feedrate_mm_s[X_AXIS]); // Park the current head
  548. planner.synchronize();
  549. }
  550. // Activate the new extruder ahead of calling set_axis_is_at_home!
  551. active_extruder = new_tool;
  552. // This function resets the max/min values - the current position may be overwritten below.
  553. set_axis_is_at_home(X_AXIS);
  554. DEBUG_POS("New Extruder", current_position);
  555. switch (dual_x_carriage_mode) {
  556. case DXC_FULL_CONTROL_MODE:
  557. // New current position is the position of the activated extruder
  558. current_position.x = inactive_extruder_x;
  559. // Save the inactive extruder's position (from the old current_position)
  560. inactive_extruder_x = destination.x;
  561. DEBUG_ECHOLNPAIR("DXC Full Control curr.x=", current_position.x, " dest.x=", destination.x);
  562. break;
  563. case DXC_AUTO_PARK_MODE:
  564. idex_set_parked();
  565. break;
  566. default:
  567. break;
  568. }
  569. // Ensure X axis DIR pertains to the correct carriage
  570. stepper.set_directions();
  571. DEBUG_ECHOLNPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no");
  572. DEBUG_POS("New extruder (parked)", current_position);
  573. }
  574. #endif // DUAL_X_CARRIAGE
  575. /**
  576. * Prime active tool using TOOLCHANGE_FILAMENT_SWAP settings
  577. */
  578. #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
  579. void tool_change_prime() {
  580. if (toolchange_settings.extra_prime > 0
  581. && TERN(PREVENT_COLD_EXTRUSION, !thermalManager.targetTooColdToExtrude(active_extruder), 1)
  582. ) {
  583. destination = current_position; // Remember the old position
  584. const bool ok = TERN1(TOOLCHANGE_PARK, all_axes_homed() && toolchange_settings.enable_park);
  585. #if HAS_FAN && TOOLCHANGE_FS_FAN >= 0
  586. // Store and stop fan. Restored on any exit.
  587. REMEMBER(fan, thermalManager.fan_speed[TOOLCHANGE_FS_FAN], 0);
  588. #endif
  589. // Z raise
  590. if (ok) {
  591. // Do a small lift to avoid the workpiece in the move back (below)
  592. current_position.z += toolchange_settings.z_raise;
  593. #if HAS_SOFTWARE_ENDSTOPS
  594. NOMORE(current_position.z, soft_endstop.max.z);
  595. #endif
  596. fast_line_to_current(Z_AXIS);
  597. planner.synchronize();
  598. }
  599. // Park
  600. #if ENABLED(TOOLCHANGE_PARK)
  601. if (ok) {
  602. IF_DISABLED(TOOLCHANGE_PARK_Y_ONLY, current_position.x = toolchange_settings.change_point.x);
  603. IF_DISABLED(TOOLCHANGE_PARK_X_ONLY, current_position.y = toolchange_settings.change_point.y);
  604. planner.buffer_line(current_position, MMM_TO_MMS(TOOLCHANGE_PARK_XY_FEEDRATE), active_extruder);
  605. planner.synchronize();
  606. }
  607. #endif
  608. // Prime (All distances are added and slowed down to ensure secure priming in all circumstances)
  609. unscaled_e_move(toolchange_settings.swap_length + toolchange_settings.extra_prime, MMM_TO_MMS(toolchange_settings.prime_speed));
  610. // Cutting retraction
  611. #if TOOLCHANGE_FS_WIPE_RETRACT
  612. unscaled_e_move(-(TOOLCHANGE_FS_WIPE_RETRACT), MMM_TO_MMS(toolchange_settings.retract_speed));
  613. #endif
  614. // Cool down with fan
  615. #if HAS_FAN && TOOLCHANGE_FS_FAN >= 0
  616. thermalManager.fan_speed[TOOLCHANGE_FS_FAN] = toolchange_settings.fan_speed;
  617. gcode.dwell(toolchange_settings.fan_time * 1000);
  618. thermalManager.fan_speed[TOOLCHANGE_FS_FAN] = 0;
  619. #endif
  620. // Move back
  621. #if ENABLED(TOOLCHANGE_PARK)
  622. if (ok) {
  623. #if ENABLED(TOOLCHANGE_NO_RETURN)
  624. do_blocking_move_to_z(destination.z, planner.settings.max_feedrate_mm_s[Z_AXIS]);
  625. #else
  626. do_blocking_move_to(destination, MMM_TO_MMS(TOOLCHANGE_PARK_XY_FEEDRATE));
  627. #endif
  628. }
  629. #endif
  630. // Cutting recover
  631. unscaled_e_move(toolchange_settings.extra_resume + TOOLCHANGE_FS_WIPE_RETRACT, MMM_TO_MMS(toolchange_settings.unretract_speed));
  632. planner.synchronize();
  633. current_position.e = destination.e;
  634. sync_plan_position_e(); // Resume at the old E position
  635. }
  636. }
  637. #endif
  638. /**
  639. * Perform a tool-change, which may result in moving the
  640. * previous tool out of the way and the new tool into place.
  641. */
  642. void tool_change(const uint8_t new_tool, bool no_move/*=false*/) {
  643. if (TERN0(MAGNETIC_SWITCHING_TOOLHEAD, new_tool == active_extruder))
  644. return;
  645. #if ENABLED(MIXING_EXTRUDER)
  646. UNUSED(no_move);
  647. if (new_tool >= MIXING_VIRTUAL_TOOLS)
  648. return invalid_extruder_error(new_tool);
  649. #if MIXING_VIRTUAL_TOOLS > 1
  650. // T0-Tnnn: Switch virtual tool by changing the index to the mix
  651. mixer.T(new_tool);
  652. #endif
  653. #elif HAS_PRUSA_MMU2
  654. UNUSED(no_move);
  655. mmu2.tool_change(new_tool);
  656. #elif EXTRUDERS == 0
  657. // Nothing to do
  658. UNUSED(new_tool); UNUSED(no_move);
  659. #elif EXTRUDERS < 2
  660. UNUSED(no_move);
  661. if (new_tool) invalid_extruder_error(new_tool);
  662. return;
  663. #elif HAS_MULTI_EXTRUDER
  664. planner.synchronize();
  665. #if ENABLED(DUAL_X_CARRIAGE) // Only T0 allowed if the Printer is in DXC_DUPLICATION_MODE or DXC_MIRRORED_MODE
  666. if (new_tool != 0 && idex_is_duplicating())
  667. return invalid_extruder_error(new_tool);
  668. #endif
  669. if (new_tool >= EXTRUDERS)
  670. return invalid_extruder_error(new_tool);
  671. if (!no_move && homing_needed()) {
  672. no_move = true;
  673. DEBUG_ECHOLNPGM("No move (not homed)");
  674. }
  675. TERN_(HAS_LCD_MENU, if (!no_move) ui.return_to_status());
  676. #if ENABLED(DUAL_X_CARRIAGE)
  677. const bool idex_full_control = dual_x_carriage_mode == DXC_FULL_CONTROL_MODE;
  678. #else
  679. constexpr bool idex_full_control = false;
  680. #endif
  681. const uint8_t old_tool = active_extruder;
  682. const bool can_move_away = !no_move && !idex_full_control;
  683. #if HAS_LEVELING
  684. // Set current position to the physical position
  685. TEMPORARY_BED_LEVELING_STATE(false);
  686. #endif
  687. // First tool priming. To prime again, reboot the machine.
  688. #if BOTH(TOOLCHANGE_FILAMENT_SWAP, TOOLCHANGE_FS_PRIME_FIRST_USED)
  689. static bool first_tool_is_primed = false;
  690. if (new_tool == old_tool && !first_tool_is_primed && enable_first_prime) {
  691. tool_change_prime();
  692. first_tool_is_primed = true;
  693. toolchange_extruder_ready[old_tool] = true; // Primed and initialized
  694. }
  695. #endif
  696. if (new_tool != old_tool || TERN0(PARKING_EXTRUDER, extruder_parked)) { // PARKING_EXTRUDER may need to attach old_tool when homing
  697. destination = current_position;
  698. #if BOTH(TOOLCHANGE_FILAMENT_SWAP, HAS_FAN) && TOOLCHANGE_FS_FAN >= 0
  699. // Store and stop fan. Restored on any exit.
  700. REMEMBER(fan, thermalManager.fan_speed[TOOLCHANGE_FS_FAN], 0);
  701. #endif
  702. // Z raise before retraction
  703. #if ENABLED(TOOLCHANGE_ZRAISE_BEFORE_RETRACT) && DISABLED(SWITCHING_NOZZLE)
  704. if (can_move_away && TERN1(TOOLCHANGE_PARK, toolchange_settings.enable_park)) {
  705. // Do a small lift to avoid the workpiece in the move back (below)
  706. current_position.z += toolchange_settings.z_raise;
  707. #if HAS_SOFTWARE_ENDSTOPS
  708. NOMORE(current_position.z, soft_endstop.max.z);
  709. #endif
  710. fast_line_to_current(Z_AXIS);
  711. planner.synchronize();
  712. }
  713. #endif
  714. // Unload / Retract
  715. #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
  716. const bool should_swap = can_move_away && toolchange_settings.swap_length,
  717. too_cold = TERN0(PREVENT_COLD_EXTRUSION,
  718. !DEBUGGING(DRYRUN) && (thermalManager.targetTooColdToExtrude(old_tool) || thermalManager.targetTooColdToExtrude(new_tool))
  719. );
  720. if (should_swap) {
  721. if (too_cold) {
  722. SERIAL_ECHO_MSG(STR_ERR_HOTEND_TOO_COLD);
  723. if (ENABLED(SINGLENOZZLE)) { active_extruder = new_tool; return; }
  724. }
  725. else {
  726. #if ENABLED(TOOLCHANGE_FS_PRIME_FIRST_USED)
  727. // For first new tool, change without unloading the old. 'Just prime/init the new'
  728. if (first_tool_is_primed)
  729. unscaled_e_move(-toolchange_settings.swap_length, MMM_TO_MMS(toolchange_settings.retract_speed));
  730. first_tool_is_primed = true; // The first new tool will be primed by toolchanging
  731. #endif
  732. }
  733. }
  734. #endif
  735. TERN_(SWITCHING_NOZZLE_TWO_SERVOS, raise_nozzle(old_tool));
  736. REMEMBER(fr, feedrate_mm_s, XY_PROBE_FEEDRATE_MM_S);
  737. #if HAS_SOFTWARE_ENDSTOPS
  738. #if HAS_HOTEND_OFFSET
  739. #define _EXT_ARGS , old_tool, new_tool
  740. #else
  741. #define _EXT_ARGS
  742. #endif
  743. update_software_endstops(X_AXIS _EXT_ARGS);
  744. #if DISABLED(DUAL_X_CARRIAGE)
  745. update_software_endstops(Y_AXIS _EXT_ARGS);
  746. update_software_endstops(Z_AXIS _EXT_ARGS);
  747. #endif
  748. #endif
  749. #if DISABLED(TOOLCHANGE_ZRAISE_BEFORE_RETRACT) && DISABLED(SWITCHING_NOZZLE)
  750. if (can_move_away && TERN1(TOOLCHANGE_PARK, toolchange_settings.enable_park)) {
  751. // Do a small lift to avoid the workpiece in the move back (below)
  752. current_position.z += toolchange_settings.z_raise;
  753. #if HAS_SOFTWARE_ENDSTOPS
  754. NOMORE(current_position.z, soft_endstop.max.z);
  755. #endif
  756. fast_line_to_current(Z_AXIS);
  757. }
  758. #endif
  759. // Toolchange park
  760. #if ENABLED(TOOLCHANGE_PARK) && DISABLED(SWITCHING_NOZZLE)
  761. if (can_move_away && toolchange_settings.enable_park) {
  762. IF_DISABLED(TOOLCHANGE_PARK_Y_ONLY, current_position.x = toolchange_settings.change_point.x);
  763. IF_DISABLED(TOOLCHANGE_PARK_X_ONLY, current_position.y = toolchange_settings.change_point.y);
  764. planner.buffer_line(current_position, MMM_TO_MMS(TOOLCHANGE_PARK_XY_FEEDRATE), old_tool);
  765. planner.synchronize();
  766. }
  767. #endif
  768. #if HAS_HOTEND_OFFSET
  769. xyz_pos_t diff = hotend_offset[new_tool] - hotend_offset[old_tool];
  770. TERN_(DUAL_X_CARRIAGE, diff.x = 0);
  771. #else
  772. constexpr xyz_pos_t diff{0};
  773. #endif
  774. #if ENABLED(DUAL_X_CARRIAGE)
  775. dualx_tool_change(new_tool, no_move);
  776. #elif ENABLED(PARKING_EXTRUDER) // Dual Parking extruder
  777. parking_extruder_tool_change(new_tool, no_move);
  778. #elif ENABLED(MAGNETIC_PARKING_EXTRUDER) // Magnetic Parking extruder
  779. magnetic_parking_extruder_tool_change(new_tool);
  780. #elif ENABLED(SWITCHING_TOOLHEAD) // Switching Toolhead
  781. switching_toolhead_tool_change(new_tool, no_move);
  782. #elif ENABLED(MAGNETIC_SWITCHING_TOOLHEAD) // Magnetic Switching Toolhead
  783. magnetic_switching_toolhead_tool_change(new_tool, no_move);
  784. #elif ENABLED(ELECTROMAGNETIC_SWITCHING_TOOLHEAD) // Magnetic Switching ToolChanger
  785. em_switching_toolhead_tool_change(new_tool, no_move);
  786. #elif ENABLED(SWITCHING_NOZZLE) && !SWITCHING_NOZZLE_TWO_SERVOS // Switching Nozzle (single servo)
  787. // Raise by a configured distance to avoid workpiece, except with
  788. // SWITCHING_NOZZLE_TWO_SERVOS, as both nozzles will lift instead.
  789. if (!no_move) {
  790. const float newz = current_position.z + _MAX(-diff.z, 0.0);
  791. // Check if Z has space to compensate at least z_offset, and if not, just abort now
  792. const float maxz = _MIN(TERN(HAS_SOFTWARE_ENDSTOPS, soft_endstop.max.z, Z_MAX_POS), Z_MAX_POS);
  793. if (newz > maxz) return;
  794. current_position.z = _MIN(newz + toolchange_settings.z_raise, maxz);
  795. fast_line_to_current(Z_AXIS);
  796. }
  797. move_nozzle_servo(new_tool);
  798. #endif
  799. // Set the new active extruder
  800. if (DISABLED(DUAL_X_CARRIAGE)) active_extruder = new_tool;
  801. // The newly-selected extruder XYZ is actually at...
  802. DEBUG_ECHOLNPAIR("Offset Tool XYZ by { ", diff.x, ", ", diff.y, ", ", diff.z, " }");
  803. current_position += diff;
  804. // Tell the planner the new "current position"
  805. sync_plan_position();
  806. #if ENABLED(DELTA)
  807. //LOOP_XYZ(i) update_software_endstops(i); // or modify the constrain function
  808. const bool safe_to_move = current_position.z < delta_clip_start_height - 1;
  809. #else
  810. constexpr bool safe_to_move = true;
  811. #endif
  812. // Return to position and lower again
  813. const bool should_move = safe_to_move && !no_move && IsRunning();
  814. if (should_move) {
  815. #if BOTH(HAS_FAN, SINGLENOZZLE_STANDBY_FAN)
  816. singlenozzle_fan_speed[old_tool] = thermalManager.fan_speed[0];
  817. thermalManager.fan_speed[0] = singlenozzle_fan_speed[new_tool];
  818. #endif
  819. #if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
  820. singlenozzle_temp[old_tool] = thermalManager.temp_hotend[0].target;
  821. if (singlenozzle_temp[new_tool] && singlenozzle_temp[new_tool] != singlenozzle_temp[old_tool]) {
  822. thermalManager.setTargetHotend(singlenozzle_temp[new_tool], 0);
  823. TERN_(AUTOTEMP, planner.autotemp_update());
  824. TERN_(HAS_DISPLAY, thermalManager.set_heating_message(0));
  825. (void)thermalManager.wait_for_hotend(0, false); // Wait for heating or cooling
  826. }
  827. #endif
  828. #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
  829. if (should_swap && !too_cold) {
  830. float fr = toolchange_settings.unretract_speed;
  831. #if ENABLED(TOOLCHANGE_FS_INIT_BEFORE_SWAP)
  832. if (!toolchange_extruder_ready[new_tool]) {
  833. toolchange_extruder_ready[new_tool] = true;
  834. fr = toolchange_settings.prime_speed; // Next move is a prime
  835. unscaled_e_move(0, MMM_TO_MMS(fr)); // Init planner with 0 length move
  836. }
  837. #endif
  838. // Unretract (or Prime)
  839. unscaled_e_move(toolchange_settings.swap_length, MMM_TO_MMS(fr));
  840. // Extra Prime
  841. unscaled_e_move(toolchange_settings.extra_prime, MMM_TO_MMS(toolchange_settings.prime_speed));
  842. // Cutting retraction
  843. #if TOOLCHANGE_FS_WIPE_RETRACT
  844. unscaled_e_move(-(TOOLCHANGE_FS_WIPE_RETRACT), MMM_TO_MMS(toolchange_settings.retract_speed));
  845. #endif
  846. // Cool down with fan
  847. #if HAS_FAN && TOOLCHANGE_FS_FAN >= 0
  848. thermalManager.fan_speed[TOOLCHANGE_FS_FAN] = toolchange_settings.fan_speed;
  849. gcode.dwell(toolchange_settings.fan_time * 1000);
  850. thermalManager.fan_speed[TOOLCHANGE_FS_FAN] = 0;
  851. #endif
  852. }
  853. #endif
  854. // Prevent a move outside physical bounds
  855. #if ENABLED(MAGNETIC_SWITCHING_TOOLHEAD)
  856. // If the original position is within tool store area, go to X origin at once
  857. if (destination.y < SWITCHING_TOOLHEAD_Y_POS + SWITCHING_TOOLHEAD_Y_CLEAR) {
  858. current_position.x = 0;
  859. planner.buffer_line(current_position, planner.settings.max_feedrate_mm_s[X_AXIS], new_tool);
  860. planner.synchronize();
  861. }
  862. #else
  863. apply_motion_limits(destination);
  864. #endif
  865. // Should the nozzle move back to the old position?
  866. if (can_move_away) {
  867. #if ENABLED(TOOLCHANGE_NO_RETURN)
  868. // Just move back down
  869. DEBUG_ECHOLNPGM("Move back Z only");
  870. #if ENABLED(TOOLCHANGE_PARK)
  871. if (toolchange_settings.enable_park)
  872. #endif
  873. do_blocking_move_to_z(destination.z, planner.settings.max_feedrate_mm_s[Z_AXIS]);
  874. #else
  875. // Move back to the original (or adjusted) position
  876. DEBUG_POS("Move back", destination);
  877. #if ENABLED(TOOLCHANGE_PARK)
  878. if (toolchange_settings.enable_park) do_blocking_move_to_xy_z(destination, destination.z, MMM_TO_MMS(TOOLCHANGE_PARK_XY_FEEDRATE));
  879. #else
  880. do_blocking_move_to_xy(destination, planner.settings.max_feedrate_mm_s[X_AXIS]);
  881. do_blocking_move_to_z(destination.z, planner.settings.max_feedrate_mm_s[Z_AXIS]);
  882. #endif
  883. #endif
  884. }
  885. else DEBUG_ECHOLNPGM("Move back skipped");
  886. #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
  887. if (should_swap && !too_cold) {
  888. // Cutting recover
  889. unscaled_e_move(toolchange_settings.extra_resume + TOOLCHANGE_FS_WIPE_RETRACT, MMM_TO_MMS(toolchange_settings.unretract_speed));
  890. current_position.e = 0;
  891. sync_plan_position_e(); // New extruder primed and set to 0
  892. // Restart Fan
  893. #if HAS_FAN && TOOLCHANGE_FS_FAN >= 0
  894. RESTORE(fan);
  895. #endif
  896. }
  897. #endif
  898. TERN_(DUAL_X_CARRIAGE, idex_set_parked(false));
  899. }
  900. #if ENABLED(SWITCHING_NOZZLE)
  901. // Move back down. (Including when the new tool is higher.)
  902. if (!should_move)
  903. do_blocking_move_to_z(destination.z, planner.settings.max_feedrate_mm_s[Z_AXIS]);
  904. #endif
  905. TERN_(SWITCHING_NOZZLE_TWO_SERVOS, lower_nozzle(new_tool));
  906. } // (new_tool != old_tool)
  907. planner.synchronize();
  908. #if ENABLED(EXT_SOLENOID) && DISABLED(PARKING_EXTRUDER)
  909. disable_all_solenoids();
  910. enable_solenoid_on_active_extruder();
  911. #endif
  912. #if HAS_PRUSA_MMU1
  913. if (new_tool >= E_STEPPERS) return invalid_extruder_error(new_tool);
  914. select_multiplexed_stepper(new_tool);
  915. #endif
  916. #if DO_SWITCH_EXTRUDER
  917. planner.synchronize();
  918. move_extruder_servo(active_extruder);
  919. #endif
  920. TERN_(HAS_FANMUX, fanmux_switch(active_extruder));
  921. #ifdef EVENT_GCODE_AFTER_TOOLCHANGE
  922. if (!no_move && TERN1(DUAL_X_CARRIAGE, dual_x_carriage_mode == DXC_AUTO_PARK_MODE))
  923. gcode.process_subcommands_now_P(PSTR(EVENT_GCODE_AFTER_TOOLCHANGE));
  924. #endif
  925. SERIAL_ECHO_START();
  926. SERIAL_ECHOLNPAIR(STR_ACTIVE_EXTRUDER, int(active_extruder));
  927. #endif // HAS_MULTI_EXTRUDER
  928. }
  929. #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE)
  930. #define DEBUG_OUT ENABLED(DEBUG_TOOLCHANGE_MIGRATION_FEATURE)
  931. #include "../core/debug_out.h"
  932. bool extruder_migration() {
  933. #if ENABLED(PREVENT_COLD_EXTRUSION)
  934. if (thermalManager.targetTooColdToExtrude(active_extruder)) {
  935. DEBUG_ECHOLNPGM("Migration Source Too Cold");
  936. return false;
  937. }
  938. #endif
  939. // No auto-migration or specified target?
  940. if (!migration.target && active_extruder >= migration.last) {
  941. DEBUG_ECHO_MSG("No Migration Target");
  942. DEBUG_ECHO_MSG("Target: ", migration.target, " Last: ", migration.last, " Active: ", active_extruder);
  943. migration.automode = false;
  944. return false;
  945. }
  946. // Migrate to a target or the next extruder
  947. uint8_t migration_extruder = active_extruder;
  948. if (migration.target) {
  949. DEBUG_ECHOLNPGM("Migration using fixed target");
  950. // Specified target ok?
  951. const int16_t t = migration.target - 1;
  952. if (t != active_extruder) migration_extruder = t;
  953. }
  954. else if (migration.automode && migration_extruder < migration.last && migration_extruder < EXTRUDERS - 1)
  955. migration_extruder++;
  956. if (migration_extruder == active_extruder) {
  957. DEBUG_ECHOLNPGM("Migration source matches active");
  958. return false;
  959. }
  960. // Migration begins
  961. DEBUG_ECHOLNPGM("Beginning migration");
  962. migration.in_progress = true; // Prevent runout script
  963. planner.synchronize();
  964. // Remember position before migration
  965. const float resume_current_e = current_position.e;
  966. // Migrate the flow
  967. planner.set_flow(migration_extruder, planner.flow_percentage[active_extruder]);
  968. // Migrate the retracted state
  969. #if ENABLED(FWRETRACT)
  970. fwretract.retracted[migration_extruder] = fwretract.retracted[active_extruder];
  971. #endif
  972. // Migrate the temperature to the new hotend
  973. #if HAS_MULTI_HOTEND
  974. thermalManager.setTargetHotend(thermalManager.temp_hotend[active_extruder].target, migration_extruder);
  975. TERN_(AUTOTEMP, planner.autotemp_update());
  976. TERN_(HAS_DISPLAY, thermalManager.set_heating_message(0));
  977. thermalManager.wait_for_hotend(active_extruder);
  978. #endif
  979. // Migrate Linear Advance K factor to the new extruder
  980. TERN_(LIN_ADVANCE, planner.extruder_advance_K[active_extruder] = planner.extruder_advance_K[migration_extruder]);
  981. // Perform the tool change
  982. tool_change(migration_extruder);
  983. // Retract if previously retracted
  984. #if ENABLED(FWRETRACT)
  985. if (fwretract.retracted[active_extruder])
  986. unscaled_e_move(-fwretract.settings.retract_length, fwretract.settings.retract_feedrate_mm_s);
  987. #endif
  988. // If no available extruder
  989. if (EXTRUDERS < 2 || active_extruder >= EXTRUDERS - 2 || active_extruder == migration.last)
  990. migration.automode = false;
  991. migration.in_progress = false;
  992. current_position.e = resume_current_e;
  993. planner.synchronize();
  994. planner.set_e_position_mm(current_position.e); // New extruder primed and ready
  995. DEBUG_ECHOLNPGM("Migration Complete");
  996. return true;
  997. }
  998. #endif // TOOLCHANGE_MIGRATION_FEATURE