My Marlin configs for Fabrikator Mini and CTC i3 Pro B
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

tool_change.cpp 43KB

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