From 60c1e4261e6cf3df63c31430b2e7ca9c12fb1c96 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Mon, 17 Apr 2023 13:28:09 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Gather=20Z?= =?UTF-8?q?=20clearances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Marlin/Configuration.h | 8 +- Marlin/src/gcode/bedlevel/abl/G29.cpp | 15 +--- Marlin/src/gcode/calibrate/G28.cpp | 81 ++++++++++--------- Marlin/src/inc/Conditionals_post.h | 14 ++-- Marlin/src/inc/SanityCheck.h | 10 ++- .../lcd/extui/mks_ui/draw_z_offset_wizard.cpp | 10 +-- Marlin/src/lcd/menu/menu_probe_offset.cpp | 14 ++-- Marlin/src/module/motion.cpp | 19 +++-- Marlin/src/module/motion.h | 10 ++- Marlin/src/module/probe.h | 29 +------ 10 files changed, 100 insertions(+), 110 deletions(-) diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 9844b44e1048a..19407b61581b4 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -1402,7 +1402,7 @@ * on the right, enable and set TOUCH_MI_DEPLOY_XPOS to the deploy position. * * Also requires: BABYSTEPPING, BABYSTEP_ZPROBE_OFFSET, Z_SAFE_HOMING, - * and a minimum Z_HOMING_HEIGHT of 10. + * and a minimum Z_CLEARANCE_FOR_HOMING of 10. */ //#define TOUCH_MI_PROBE #if ENABLED(TOUCH_MI_PROBE) @@ -1730,10 +1730,10 @@ */ //#define Z_IDLE_HEIGHT Z_HOME_POS -//#define Z_HOMING_HEIGHT 4 // (mm) Minimal Z height before homing (G28) for Z clearance above the bed, clamps, ... - // Be sure to have this much clearance over your Z_MAX_POS to prevent grinding. +//#define Z_CLEARANCE_FOR_HOMING 4 // (mm) Minimal Z height before homing (G28) for Z clearance above the bed, clamps, ... + // Be sure to have this much clearance over your Z_MAX_POS to prevent grinding. -//#define Z_AFTER_HOMING 10 // (mm) Height to move to after homing Z +//#define Z_AFTER_HOMING 10 // (mm) Height to move to after homing Z // Direction of endstops when homing; 1=MAX, -1=MIN // :[-1,1] diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index e334412054ac3..dd325284af388 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -494,20 +494,13 @@ G29_TYPE GcodeSuite::G29() { #endif #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - if (!abl.dryrun - && (abl.gridSpacing != bedlevel.grid_spacing || abl.probe_position_lf != bedlevel.grid_start) - ) { - // Reset grid to 0.0 or "not probed". (Also disables ABL) - reset_bed_level(); - - // Can't re-enable (on error) until the new grid is written - abl.reenable = false; + if (!abl.dryrun && (abl.gridSpacing != bedlevel.grid_spacing || abl.probe_position_lf != bedlevel.grid_start)) { + reset_bed_level(); // Reset grid to 0.0 or "not probed". (Also disables ABL) + abl.reenable = false; // Can't re-enable (on error) until the new grid is written } - // Pre-populate local Z values from the stored mesh TERN_(IS_KINEMATIC, COPY(abl.z_values, bedlevel.z_values)); - - #endif // AUTO_BED_LEVELING_BILINEAR + #endif } // !g29_in_progress diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp index b5e7a1329549f..89e0c8d8d4c55 100644 --- a/Marlin/src/gcode/calibrate/G28.cpp +++ b/Marlin/src/gcode/calibrate/G28.cpp @@ -399,49 +399,50 @@ void GcodeSuite::G28() { doU = home_all || homeU, doV = home_all || homeV, doW = home_all || homeW ); + #if !HAS_Y_AXIS + constexpr bool doY = false; + #endif + #if HAS_Z_AXIS + UNUSED(needZ); UNUSED(homeZZ); - #else - constexpr bool doZ = false; - #if !HAS_Y_AXIS - constexpr bool doY = false; - #endif - #endif - // Z may home first, e.g., when homing away from the bed - TERN_(HOME_Z_FIRST, if (doZ) homeaxis(Z_AXIS)); + // Z may home first, e.g., when homing away from the bed + TERN_(HOME_Z_FIRST, if (doZ) homeaxis(Z_AXIS)); - // 'R' to specify a specific raise. 'R0' indicates no raise, e.g., for recovery.resume - // When 'R0' is used, there should already be adequate clearance, e.g., from homing Z to max. - const bool seenR = parser.seenval('R'); + // 'R' to specify a specific raise. 'R0' indicates no raise, e.g., for recovery.resume + // When 'R0' is used, there should already be adequate clearance, e.g., from homing Z to max. + const bool seenR = parser.seenval('R'); - // Use raise given by 'R' or Z_HOMING_HEIGHT (above the probe trigger point) - float z_homing_height = seenR ? parser.value_linear_units() : Z_HOMING_HEIGHT; + // Use raise given by 'R' or Z_CLEARANCE_FOR_HOMING (above the probe trigger point) + float z_homing_height = seenR ? parser.value_linear_units() : Z_CLEARANCE_FOR_HOMING; - // Check for any lateral motion that might require clearance - const bool may_skate = seenR || NUM_AXIS_GANG(doX, || doY, || TERN0(Z_SAFE_HOMING, doZ), || doI, || doJ, || doK, || doU, || doV, || doW); + // Check for any lateral motion that might require clearance + const bool may_skate = seenR || NUM_AXIS_GANG(doX, || doY, || TERN0(Z_SAFE_HOMING, doZ), || doI, || doJ, || doK, || doU, || doV, || doW); - if (seenR && z_homing_height == 0) { - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("R0 = No Z raise"); - } - else { - bool with_probe = ENABLED(HOMING_Z_WITH_PROBE); - // Raise above the current Z (which should be synced in the planner) - // The "height" for Z is a coordinate. But if Z is not trusted/homed make it relative. - if (seenR || !TERN(HOME_AFTER_DEACTIVATE, axis_is_trusted, axis_was_homed)(Z_AXIS)) { - z_homing_height += current_position.z; - with_probe = false; + if (seenR && z_homing_height == 0) { + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("R0 = No Z raise"); } + else { + bool with_probe = ENABLED(HOMING_Z_WITH_PROBE); + // Raise above the current Z (which should be synced in the planner) + // The "height" for Z is a coordinate. But if Z is not trusted/homed make it relative. + if (seenR || !TERN(HOME_AFTER_DEACTIVATE, axis_is_trusted, axis_was_homed)(Z_AXIS)) { + z_homing_height += current_position.z; + with_probe = false; + } - if (may_skate) { - // Apply Z clearance before doing any lateral motion - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Raise Z before homing:"); - do_z_clearance(z_homing_height, with_probe); + if (may_skate) { + // Apply Z clearance before doing any lateral motion + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Raise Z before homing:"); + do_z_clearance(z_homing_height, with_probe); + } } - } - // Init BLTouch ahead of any lateral motion, even if not homing with the probe - TERN_(BLTOUCH, if (may_skate) bltouch.init()); + // Init BLTouch ahead of any lateral motion, even if not homing with the probe + TERN_(BLTOUCH, if (may_skate) bltouch.init()); + + #endif // HAS_Z_AXIS // Diagonal move first if both are homing TERN_(QUICK_HOME, if (doX && doY) quick_home_xy()); @@ -497,11 +498,14 @@ void GcodeSuite::G28() { TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(saved_motion_state)); #if ENABLED(FOAMCUTTER_XYUV) - // skip homing of unused Z axis for foamcutters + + // Skip homing of unused Z axis for foamcutters if (doZ) set_axis_is_at_home(Z_AXIS); - #else + + #elif HAS_Z_AXIS + // Home Z last if homing towards the bed - #if HAS_Z_AXIS && DISABLED(HOME_Z_FIRST) + #if DISABLED(HOME_Z_FIRST) if (doZ) { #if EITHER(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN) stepper.set_all_z_lock(false); @@ -513,7 +517,7 @@ void GcodeSuite::G28() { #else homeaxis(Z_AXIS); #endif - probe.move_z_after_homing(); + do_move_after_z_homing(); } #endif @@ -525,7 +529,8 @@ void GcodeSuite::G28() { if (doV) homeaxis(V_AXIS), if (doW) homeaxis(W_AXIS) ); - #endif + + #endif // HAS_Z_AXIS sync_plan_position(); @@ -629,7 +634,7 @@ void GcodeSuite::G28() { report_current_position(); - if (ENABLED(NANODLP_Z_SYNC) && (doZ || ENABLED(NANODLP_ALL_AXIS))) + if (BOTH(NANODLP_Z_SYNC, NANODLP_ALL_AXIS) || TERN0(HAS_Z_AXIS, doZ)) SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP); TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(old_grblstate)); diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h index 4bbb43a2169d3..c09f6684504e4 100644 --- a/Marlin/src/inc/Conditionals_post.h +++ b/Marlin/src/inc/Conditionals_post.h @@ -3180,24 +3180,24 @@ #endif /** - * Z_HOMING_HEIGHT / Z_CLEARANCE_BETWEEN_PROBES + * Z_CLEARANCE_FOR_HOMING / Z_CLEARANCE_BETWEEN_PROBES */ -#ifndef Z_HOMING_HEIGHT +#ifndef Z_CLEARANCE_FOR_HOMING #ifdef Z_CLEARANCE_BETWEEN_PROBES - #define Z_HOMING_HEIGHT Z_CLEARANCE_BETWEEN_PROBES + #define Z_CLEARANCE_FOR_HOMING Z_CLEARANCE_BETWEEN_PROBES #else - #define Z_HOMING_HEIGHT 0 + #define Z_CLEARANCE_FOR_HOMING 0 #endif #endif #if PROBE_SELECTED #ifndef Z_CLEARANCE_BETWEEN_PROBES - #define Z_CLEARANCE_BETWEEN_PROBES Z_HOMING_HEIGHT + #define Z_CLEARANCE_BETWEEN_PROBES Z_CLEARANCE_FOR_HOMING #endif - #if Z_CLEARANCE_BETWEEN_PROBES > Z_HOMING_HEIGHT + #if Z_CLEARANCE_BETWEEN_PROBES > Z_CLEARANCE_FOR_HOMING #define Z_CLEARANCE_BETWEEN_MANUAL_PROBES Z_CLEARANCE_BETWEEN_PROBES #else - #define Z_CLEARANCE_BETWEEN_MANUAL_PROBES Z_HOMING_HEIGHT + #define Z_CLEARANCE_BETWEEN_MANUAL_PROBES Z_CLEARANCE_FOR_HOMING #endif #ifndef Z_CLEARANCE_MULTI_PROBE #define Z_CLEARANCE_MULTI_PROBE Z_CLEARANCE_BETWEEN_PROBES diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index fd58a9c54ea4e..8a3e0a6cda215 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -214,9 +214,11 @@ #elif defined(PID_ADD_EXTRUSION_RATE) #error "PID_ADD_EXTRUSION_RATE is now PID_EXTRUSION_SCALING and is DISABLED by default." #elif defined(Z_RAISE_BEFORE_HOMING) - #error "Z_RAISE_BEFORE_HOMING is now Z_HOMING_HEIGHT." + #error "Z_RAISE_BEFORE_HOMING is now Z_CLEARANCE_FOR_HOMING." #elif defined(MIN_Z_HEIGHT_FOR_HOMING) - #error "MIN_Z_HEIGHT_FOR_HOMING is now Z_HOMING_HEIGHT." + #error "MIN_Z_HEIGHT_FOR_HOMING is now Z_CLEARANCE_FOR_HOMING." +#elif defined(Z_HOMING_HEIGHT) + #error "Z_HOMING_HEIGHT is now Z_CLEARANCE_FOR_HOMING." #elif defined(Z_RAISE_BEFORE_PROBING) || defined(Z_RAISE_AFTER_PROBING) #error "Z_RAISE_(BEFORE|AFTER)_PROBING are deprecated. Use Z_CLEARANCE_DEPLOY_PROBE and Z_AFTER_PROBING instead." #elif defined(Z_RAISE_PROBE_DEPLOY_STOW) || defined(Z_RAISE_BETWEEN_PROBINGS) @@ -1945,8 +1947,8 @@ static_assert(X_MAX_LENGTH >= X_BED_SIZE, "Movement bounds (X_MIN_POS, X_MAX_POS #error "TOUCH_MI_PROBE requires TOUCH_MI_RETRACT_Z." #elif defined(Z_AFTER_PROBING) #error "TOUCH_MI_PROBE requires Z_AFTER_PROBING to be disabled." - #elif Z_HOMING_HEIGHT < 10 - #error "TOUCH_MI_PROBE requires Z_HOMING_HEIGHT >= 10." + #elif Z_CLEARANCE_FOR_HOMING < 10 + #error "TOUCH_MI_PROBE requires Z_CLEARANCE_FOR_HOMING >= 10." #elif DISABLED(BABYSTEP_ZPROBE_OFFSET) #error "TOUCH_MI_PROBE requires BABYSTEPPING with BABYSTEP_ZPROBE_OFFSET." #elif !HAS_RESUME_CONTINUE diff --git a/Marlin/src/lcd/extui/mks_ui/draw_z_offset_wizard.cpp b/Marlin/src/lcd/extui/mks_ui/draw_z_offset_wizard.cpp index dbe8577dc9952..69e547a68ccb6 100644 --- a/Marlin/src/lcd/extui/mks_ui/draw_z_offset_wizard.cpp +++ b/Marlin/src/lcd/extui/mks_ui/draw_z_offset_wizard.cpp @@ -102,8 +102,7 @@ static void event_handler(lv_obj_t *obj, lv_event_t event) { current_position.z = z_offset_ref; // Set Z to z_offset_ref, as we can expect it is at probe height probe.offset.z = calculated_z_offset; sync_plan_position(); - // Raise Z as if it was homed - do_z_clearance(Z_POST_CLEARANCE); + do_z_post_clearance(); hal.watchdog_refresh(); draw_return_ui(); return; @@ -111,11 +110,12 @@ static void event_handler(lv_obj_t *obj, lv_event_t event) { probe.offset.z = z_offset_backup; SET_SOFT_ENDSTOP_LOOSE(false); TERN_(HAS_LEVELING, set_bed_leveling_enabled(mks_leveling_was_active)); + // On cancel the Z position needs correction #if HOMING_Z_WITH_PROBE && defined(PROBE_OFFSET_WIZARD_START_Z) - set_axis_never_homed(Z_AXIS); // On cancel the Z position needs correction + set_axis_never_homed(Z_AXIS); queue.inject_P(PSTR("G28Z")); - #else // Otherwise do a Z clearance move like after Homing - do_z_clearance(Z_POST_CLEARANCE); + #else + do_z_post_clearance(); #endif hal.watchdog_refresh(); draw_return_ui(); diff --git a/Marlin/src/lcd/menu/menu_probe_offset.cpp b/Marlin/src/lcd/menu/menu_probe_offset.cpp index 34172de4581ab..62704cdedea67 100644 --- a/Marlin/src/lcd/menu/menu_probe_offset.cpp +++ b/Marlin/src/lcd/menu/menu_probe_offset.cpp @@ -44,10 +44,6 @@ void _goto_manual_move_z(const_float_t); // Global storage float z_offset_backup, calculated_z_offset, z_offset_ref; -inline void z_clearance_move() { - do_z_clearance(Z_POST_CLEARANCE); -} - void set_offset_and_go_back(const_float_t z) { probe.offset.z = z; SET_SOFT_ENDSTOP_LOOSE(false); @@ -75,17 +71,17 @@ void probe_offset_wizard_menu() { set_offset_and_go_back(calculated_z_offset); current_position.z = z_offset_ref; // Set Z to z_offset_ref, as we can expect it is at probe height sync_plan_position(); - z_clearance_move(); // Raise Z as if it was homed + do_z_post_clearance(); }); ACTION_ITEM(MSG_BUTTON_CANCEL, []{ set_offset_and_go_back(z_offset_backup); - // If wizard-homing was done by probe with PROBE_OFFSET_WIZARD_START_Z + // On cancel the Z position needs correction #if HOMING_Z_WITH_PROBE && defined(PROBE_OFFSET_WIZARD_START_Z) - set_axis_never_homed(Z_AXIS); // On cancel the Z position needs correction + set_axis_never_homed(Z_AXIS); queue.inject(F("G28Z")); - #else // Otherwise do a Z clearance move like after Homing - z_clearance_move(); + #else + do_z_post_clearance(); #endif }); diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp index 219f219e59768..6917bbc42a56f 100644 --- a/Marlin/src/module/motion.cpp +++ b/Marlin/src/module/motion.cpp @@ -38,6 +38,10 @@ #include "../lcd/marlinui.h" #endif +#if ALL(DWIN_LCD_PROUI, INDIVIDUAL_AXIS_HOMING_SUBMENU, MESH_BED_LEVELING) + #include "../lcd/e3v2/proui/dwin.h" +#endif + #if ENABLED(POLAR) #include "polar.h" #endif @@ -795,6 +799,14 @@ void do_blocking_move_to_x(const_float_t rx, const_feedRate_t fr_mm_s/*=0.0*/) { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("do_z_clearance_by(", zclear, ")"); do_z_clearance(current_position.z + zclear, false); } + void do_move_after_z_homing() { + DEBUG_SECTION(mzah, "do_move_after_z_homing", DEBUGGING(LEVELING)); + #if defined(Z_AFTER_HOMING) || ALL(DWIN_LCD_PROUI, INDIVIDUAL_AXIS_HOMING_SUBMENU, MESH_BED_LEVELING) + do_z_clearance(Z_POST_CLEARANCE, true, true); + #elif ENABLED(USE_PROBE_FOR_Z_HOMING) + probe.move_z_after_probing(); + #endif + } #endif // @@ -803,13 +815,10 @@ void do_blocking_move_to_x(const_float_t rx, const_feedRate_t fr_mm_s/*=0.0*/) { // static float saved_feedrate_mm_s; static int16_t saved_feedrate_percentage; -void remember_feedrate_and_scaling() { - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("remember_feedrate_and_scaling: fr=", feedrate_mm_s, " ", feedrate_percentage, "%"); +void remember_feedrate_scaling_off() { + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("remember_feedrate_scaling_off: fr=", feedrate_mm_s, " ", feedrate_percentage, "%"); saved_feedrate_mm_s = feedrate_mm_s; saved_feedrate_percentage = feedrate_percentage; -} -void remember_feedrate_scaling_off() { - remember_feedrate_and_scaling(); feedrate_percentage = 100; } void restore_feedrate_and_scaling() { diff --git a/Marlin/src/module/motion.h b/Marlin/src/module/motion.h index c93b1ef205e28..4cd4641e3dbc1 100644 --- a/Marlin/src/module/motion.h +++ b/Marlin/src/module/motion.h @@ -398,13 +398,21 @@ void do_blocking_move_to_x(const_float_t rx, const_feedRate_t fr_mm_s=0.0f); FORCE_INLINE void do_blocking_move_to_xy_z(const xyze_pos_t &raw, const_float_t z, const_feedRate_t fr_mm_s=0.0f) { do_blocking_move_to_xy_z(xy_pos_t(raw), z, fr_mm_s); } #endif -void remember_feedrate_and_scaling(); void remember_feedrate_scaling_off(); void restore_feedrate_and_scaling(); #if HAS_Z_AXIS + #if ALL(DWIN_LCD_PROUI, INDIVIDUAL_AXIS_HOMING_SUBMENU, MESH_BED_LEVELING) + #define Z_POST_CLEARANCE HMI_data.z_after_homing + #elif defined(Z_AFTER_HOMING) + #define Z_POST_CLEARANCE Z_AFTER_HOMING + #else + #define Z_POST_CLEARANCE Z_CLEARANCE_FOR_HOMING + #endif void do_z_clearance(const_float_t zclear, const bool with_probe=true, const bool lower_allowed=false); void do_z_clearance_by(const_float_t zclear); + void do_move_after_z_homing(); + inline void do_z_post_clearance() { do_z_clearance(Z_POST_CLEARANCE); } #else inline void do_z_clearance(float, bool=true, bool=false) {} inline void do_z_clearance_by(float) {} diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h index ebae929439a50..4c6f233905443 100644 --- a/Marlin/src/module/probe.h +++ b/Marlin/src/module/probe.h @@ -29,10 +29,6 @@ #include "motion.h" -#if ENABLED(DWIN_LCD_PROUI) - #include "../lcd/e3v2/proui/dwin.h" -#endif - #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../core/debug_out.h" @@ -51,16 +47,6 @@ #define PROBE_TRIGGERED() (READ(Z_MIN_PIN) == Z_MIN_ENDSTOP_HIT_STATE) #endif -#if ALL(DWIN_LCD_PROUI, INDIVIDUAL_AXIS_HOMING_SUBMENU, MESH_BED_LEVELING) - #define Z_POST_CLEARANCE HMI_data.z_after_homing -#elif defined(Z_AFTER_HOMING) - #define Z_POST_CLEARANCE Z_AFTER_HOMING -#elif defined(Z_HOMING_HEIGHT) - #define Z_POST_CLEARANCE Z_HOMING_HEIGHT -#else - #define Z_POST_CLEARANCE 10 -#endif - // In BLTOUCH HS mode, the probe travels in a deployed state. #define Z_PROBE_SAFE_CLEARANCE SUM_TERN(BLTOUCH, Z_CLEARANCE_BETWEEN_PROBES, bltouch.z_extra_clearance()) @@ -190,19 +176,10 @@ class Probe { static void use_probing_tool(const bool=true) IF_DISABLED(DO_TOOLCHANGE_FOR_PROBING, {}); - #ifndef Z_AFTER_PROBING - #define Z_AFTER_PROBING 0 - #endif - static void move_z_after_probing(const float z=Z_AFTER_PROBING) { + static void move_z_after_probing() { DEBUG_SECTION(mzah, "move_z_after_probing", DEBUGGING(LEVELING)); - if (z != 0) do_z_clearance(z, true, true); // Move down still permitted - } - static void move_z_after_homing() { - DEBUG_SECTION(mzah, "move_z_after_homing", DEBUGGING(LEVELING)); - #if defined(Z_AFTER_HOMING) || ALL(DWIN_LCD_PROUI, INDIVIDUAL_AXIS_HOMING_SUBMENU, MESH_BED_LEVELING) - move_z_after_probing(Z_POST_CLEARANCE); - #elif HAS_BED_PROBE - move_z_after_probing(); + #ifdef Z_AFTER_PROBING + do_z_clearance(Z_AFTER_PROBING, true, true); // Move down still permitted #endif }