Mark axes not-homed with HOME_AFTER_DEACTIVATE (#18907)
This commit is contained in:
parent
d10f7eae31
commit
7d2e4481c7
|
@ -26,7 +26,8 @@
|
||||||
|
|
||||||
#include "babystep.h"
|
#include "babystep.h"
|
||||||
#include "../MarlinCore.h"
|
#include "../MarlinCore.h"
|
||||||
#include "../module/planner.h"
|
#include "../module/motion.h" // for axes_should_home()
|
||||||
|
#include "../module/planner.h" // for axis_steps_per_mm[]
|
||||||
#include "../module/stepper.h"
|
#include "../module/stepper.h"
|
||||||
|
|
||||||
#if ENABLED(BABYSTEP_ALWAYS_AVAILABLE)
|
#if ENABLED(BABYSTEP_ALWAYS_AVAILABLE)
|
||||||
|
@ -54,7 +55,7 @@ void Babystep::add_mm(const AxisEnum axis, const float &mm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Babystep::add_steps(const AxisEnum axis, const int16_t distance) {
|
void Babystep::add_steps(const AxisEnum axis, const int16_t distance) {
|
||||||
if (DISABLED(BABYSTEP_WITHOUT_HOMING) && !TEST(axis_known_position, axis)) return;
|
if (DISABLED(BABYSTEP_WITHOUT_HOMING) && axes_should_home(_BV(axis))) return;
|
||||||
|
|
||||||
accum += distance; // Count up babysteps for the UI
|
accum += distance; // Count up babysteps for the UI
|
||||||
steps[BS_AXIS_IND(axis)] += distance;
|
steps[BS_AXIS_IND(axis)] += distance;
|
||||||
|
|
|
@ -321,7 +321,7 @@
|
||||||
// Check for commands that require the printer to be homed
|
// Check for commands that require the printer to be homed
|
||||||
if (may_move) {
|
if (may_move) {
|
||||||
planner.synchronize();
|
planner.synchronize();
|
||||||
if (axes_need_homing()) gcode.home_all_axes();
|
if (axes_should_home()) gcode.home_all_axes();
|
||||||
TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0));
|
TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -413,7 +413,7 @@ bool pause_print(const float &retract, const xyz_pos_t &park_point, const float
|
||||||
unscaled_e_move(retract, PAUSE_PARK_RETRACT_FEEDRATE);
|
unscaled_e_move(retract, PAUSE_PARK_RETRACT_FEEDRATE);
|
||||||
|
|
||||||
// Park the nozzle by moving up by z_lift and then moving to (x_pos, y_pos)
|
// Park the nozzle by moving up by z_lift and then moving to (x_pos, y_pos)
|
||||||
if (!axes_need_homing())
|
if (!axes_should_home())
|
||||||
nozzle.park(0, park_point);
|
nozzle.park(0, park_point);
|
||||||
|
|
||||||
#if ENABLED(DUAL_X_CARRIAGE)
|
#if ENABLED(DUAL_X_CARRIAGE)
|
||||||
|
|
|
@ -490,7 +490,7 @@ void GcodeSuite::G26() {
|
||||||
|
|
||||||
// Don't allow Mesh Validation without homing first,
|
// Don't allow Mesh Validation without homing first,
|
||||||
// or if the parameter parsing did not go OK, abort
|
// or if the parameter parsing did not go OK, abort
|
||||||
if (axis_unhomed_error()) return;
|
if (homing_needed_error()) return;
|
||||||
|
|
||||||
// Change the tool first, if specified
|
// Change the tool first, if specified
|
||||||
if (parser.seenval('T')) tool_change(parser.value_int());
|
if (parser.seenval('T')) tool_change(parser.value_int());
|
||||||
|
|
|
@ -183,7 +183,7 @@ G29_TYPE GcodeSuite::G29() {
|
||||||
faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action;
|
faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action;
|
||||||
|
|
||||||
// Don't allow auto-leveling without homing first
|
// Don't allow auto-leveling without homing first
|
||||||
if (axis_unhomed_error()) G29_RETURN(false);
|
if (homing_needed_error()) G29_RETURN(false);
|
||||||
|
|
||||||
if (!no_action && planner.leveling_active && parser.boolval('O')) { // Auto-level only if needed
|
if (!no_action && planner.leveling_active && parser.boolval('O')) { // Auto-level only if needed
|
||||||
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip");
|
if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip");
|
||||||
|
|
|
@ -118,7 +118,7 @@
|
||||||
DEBUG_SECTION(log_G28, "home_z_safely", DEBUGGING(LEVELING));
|
DEBUG_SECTION(log_G28, "home_z_safely", DEBUGGING(LEVELING));
|
||||||
|
|
||||||
// Disallow Z homing if X or Y homing is needed
|
// Disallow Z homing if X or Y homing is needed
|
||||||
if (axis_unhomed_error(_BV(X_AXIS) | _BV(Y_AXIS))) return;
|
if (homing_needed_error(_BV(X_AXIS) | _BV(Y_AXIS))) return;
|
||||||
|
|
||||||
sync_plan_position();
|
sync_plan_position();
|
||||||
|
|
||||||
|
@ -299,8 +299,8 @@ void GcodeSuite::G28() {
|
||||||
#else // NOT DELTA
|
#else // NOT DELTA
|
||||||
|
|
||||||
const bool homeZ = parser.seen('Z'),
|
const bool homeZ = parser.seen('Z'),
|
||||||
needX = homeZ && TERN0(Z_SAFE_HOMING, axes_need_homing(_BV(X_AXIS))),
|
needX = homeZ && TERN0(Z_SAFE_HOMING, axes_should_home(_BV(X_AXIS))),
|
||||||
needY = homeZ && TERN0(Z_SAFE_HOMING, axes_need_homing(_BV(Y_AXIS))),
|
needY = homeZ && TERN0(Z_SAFE_HOMING, axes_should_home(_BV(Y_AXIS))),
|
||||||
homeX = needX || parser.seen('X'), homeY = needY || parser.seen('Y'),
|
homeX = needX || parser.seen('X'), homeY = needY || parser.seen('Y'),
|
||||||
home_all = homeX == homeY && homeX == homeZ, // All or None
|
home_all = homeX == homeY && homeX == homeZ, // All or None
|
||||||
doX = home_all || homeX, doY = home_all || homeY, doZ = home_all || homeZ;
|
doX = home_all || homeX, doY = home_all || homeY, doZ = home_all || homeZ;
|
||||||
|
|
|
@ -584,7 +584,7 @@ void GcodeSuite::G425() {
|
||||||
TEMPORARY_SOFT_ENDSTOP_STATE(false);
|
TEMPORARY_SOFT_ENDSTOP_STATE(false);
|
||||||
TEMPORARY_BED_LEVELING_STATE(false);
|
TEMPORARY_BED_LEVELING_STATE(false);
|
||||||
|
|
||||||
if (axis_unhomed_error()) return;
|
if (homing_needed_error()) return;
|
||||||
|
|
||||||
measurements_t m;
|
measurements_t m;
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ extern const char SP_Y_STR[];
|
||||||
|
|
||||||
void GcodeSuite::M48() {
|
void GcodeSuite::M48() {
|
||||||
|
|
||||||
if (axis_unhomed_error()) return;
|
if (homing_needed_error()) return;
|
||||||
|
|
||||||
const int8_t verbose_level = parser.byteval('V', 1);
|
const int8_t verbose_level = parser.byteval('V', 1);
|
||||||
if (!WITHIN(verbose_level, 0, 4)) {
|
if (!WITHIN(verbose_level, 0, 4)) {
|
||||||
|
|
|
@ -126,7 +126,7 @@ void GcodeSuite::M240() {
|
||||||
|
|
||||||
#ifdef PHOTO_POSITION
|
#ifdef PHOTO_POSITION
|
||||||
|
|
||||||
if (axis_unhomed_error()) return;
|
if (homing_needed_error()) return;
|
||||||
|
|
||||||
const xyz_pos_t old_pos = {
|
const xyz_pos_t old_pos = {
|
||||||
current_position.x + parser.linearval('A'),
|
current_position.x + parser.linearval('A'),
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
*/
|
*/
|
||||||
void GcodeSuite::G12() {
|
void GcodeSuite::G12() {
|
||||||
// Don't allow nozzle cleaning without homing first
|
// Don't allow nozzle cleaning without homing first
|
||||||
if (axis_unhomed_error()) return;
|
if (homing_needed_error()) return;
|
||||||
|
|
||||||
#ifdef WIPE_SEQUENCE_COMMANDS
|
#ifdef WIPE_SEQUENCE_COMMANDS
|
||||||
if (!parser.seen_any()) {
|
if (!parser.seen_any()) {
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
*/
|
*/
|
||||||
void GcodeSuite::G27() {
|
void GcodeSuite::G27() {
|
||||||
// Don't allow nozzle parking without homing first
|
// Don't allow nozzle parking without homing first
|
||||||
if (axis_unhomed_error()) return;
|
if (homing_needed_error()) return;
|
||||||
nozzle.park(parser.ushortval('P'));
|
nozzle.park(parser.ushortval('P'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ void GcodeSuite::M701() {
|
||||||
|
|
||||||
#if ENABLED(NO_MOTION_BEFORE_HOMING)
|
#if ENABLED(NO_MOTION_BEFORE_HOMING)
|
||||||
// Don't raise Z if the machine isn't homed
|
// Don't raise Z if the machine isn't homed
|
||||||
if (axes_need_homing()) park_point.z = 0;
|
if (axes_should_home()) park_point.z = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLED(MIXING_EXTRUDER)
|
#if ENABLED(MIXING_EXTRUDER)
|
||||||
|
@ -149,7 +149,7 @@ void GcodeSuite::M702() {
|
||||||
|
|
||||||
#if ENABLED(NO_MOTION_BEFORE_HOMING)
|
#if ENABLED(NO_MOTION_BEFORE_HOMING)
|
||||||
// Don't raise Z if the machine isn't homed
|
// Don't raise Z if the machine isn't homed
|
||||||
if (axes_need_homing()) park_point.z = 0;
|
if (axes_should_home()) park_point.z = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLED(MIXING_EXTRUDER)
|
#if ENABLED(MIXING_EXTRUDER)
|
||||||
|
|
|
@ -61,7 +61,7 @@ void GcodeSuite::M206() {
|
||||||
* Use M206 to set these values directly.
|
* Use M206 to set these values directly.
|
||||||
*/
|
*/
|
||||||
void GcodeSuite::M428() {
|
void GcodeSuite::M428() {
|
||||||
if (axis_unhomed_error()) return;
|
if (homing_needed_error()) return;
|
||||||
|
|
||||||
xyz_float_t diff;
|
xyz_float_t diff;
|
||||||
LOOP_XYZ(i) {
|
LOOP_XYZ(i) {
|
||||||
|
|
|
@ -52,7 +52,7 @@ void GcodeSuite::G0_G1(
|
||||||
|
|
||||||
if (IsRunning()
|
if (IsRunning()
|
||||||
#if ENABLED(NO_MOTION_BEFORE_HOMING)
|
#if ENABLED(NO_MOTION_BEFORE_HOMING)
|
||||||
&& !axis_unhomed_error(
|
&& !homing_needed_error(
|
||||||
(parser.seen('X') ? _BV(X_AXIS) : 0)
|
(parser.seen('X') ? _BV(X_AXIS) : 0)
|
||||||
| (parser.seen('Y') ? _BV(Y_AXIS) : 0)
|
| (parser.seen('Y') ? _BV(Y_AXIS) : 0)
|
||||||
| (parser.seen('Z') ? _BV(Z_AXIS) : 0) )
|
| (parser.seen('Z') ? _BV(Z_AXIS) : 0) )
|
||||||
|
|
|
@ -181,7 +181,7 @@ void menu_advanced_settings();
|
||||||
#if ENABLED(DUAL_X_CARRIAGE)
|
#if ENABLED(DUAL_X_CARRIAGE)
|
||||||
|
|
||||||
void menu_idex() {
|
void menu_idex() {
|
||||||
const bool need_g28 = !(TEST(axis_known_position, Y_AXIS) && TEST(axis_known_position, Z_AXIS));
|
const bool need_g28 = axes_should_home(_BV(Y_AXIS)|_BV(Z_AXIS));
|
||||||
|
|
||||||
START_MENU();
|
START_MENU();
|
||||||
BACK_ITEM(MSG_CONFIGURATION);
|
BACK_ITEM(MSG_CONFIGURATION);
|
||||||
|
|
|
@ -1097,17 +1097,16 @@ void prepare_line_to_destination() {
|
||||||
current_position = destination;
|
current_position = destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t axes_need_homing(uint8_t axis_bits/*=0x07*/) {
|
uint8_t axes_should_home(uint8_t axis_bits/*=0x07*/) {
|
||||||
#define HOMED_FLAGS TERN(HOME_AFTER_DEACTIVATE, axis_known_position, axis_homed)
|
// Clear test bits that are trusted
|
||||||
// Clear test bits that are homed
|
if (TEST(axis_bits, X_AXIS) && TEST(axis_homed, X_AXIS)) CBI(axis_bits, X_AXIS);
|
||||||
if (TEST(axis_bits, X_AXIS) && TEST(HOMED_FLAGS, X_AXIS)) CBI(axis_bits, X_AXIS);
|
if (TEST(axis_bits, Y_AXIS) && TEST(axis_homed, Y_AXIS)) CBI(axis_bits, Y_AXIS);
|
||||||
if (TEST(axis_bits, Y_AXIS) && TEST(HOMED_FLAGS, Y_AXIS)) CBI(axis_bits, Y_AXIS);
|
if (TEST(axis_bits, Z_AXIS) && TEST(axis_homed, Z_AXIS)) CBI(axis_bits, Z_AXIS);
|
||||||
if (TEST(axis_bits, Z_AXIS) && TEST(HOMED_FLAGS, Z_AXIS)) CBI(axis_bits, Z_AXIS);
|
|
||||||
return axis_bits;
|
return axis_bits;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool axis_unhomed_error(uint8_t axis_bits/*=0x07*/) {
|
bool homing_needed_error(uint8_t axis_bits/*=0x07*/) {
|
||||||
if ((axis_bits = axes_need_homing(axis_bits))) {
|
if ((axis_bits = axes_should_home(axis_bits))) {
|
||||||
PGM_P home_first = GET_TEXT(MSG_HOME_FIRST);
|
PGM_P home_first = GET_TEXT(MSG_HOME_FIRST);
|
||||||
char msg[strlen_P(home_first)+1];
|
char msg[strlen_P(home_first)+1];
|
||||||
sprintf_P(msg, home_first,
|
sprintf_P(msg, home_first,
|
||||||
|
|
|
@ -40,8 +40,7 @@ constexpr uint8_t xyz_bits = _BV(X_AXIS) | _BV(Y_AXIS) | _BV(Z_AXIS);
|
||||||
FORCE_INLINE bool no_axes_homed() { return !axis_homed; }
|
FORCE_INLINE bool no_axes_homed() { return !axis_homed; }
|
||||||
FORCE_INLINE bool all_axes_homed() { return (axis_homed & xyz_bits) == xyz_bits; }
|
FORCE_INLINE bool all_axes_homed() { return (axis_homed & xyz_bits) == xyz_bits; }
|
||||||
FORCE_INLINE bool all_axes_known() { return (axis_known_position & xyz_bits) == xyz_bits; }
|
FORCE_INLINE bool all_axes_known() { return (axis_known_position & xyz_bits) == xyz_bits; }
|
||||||
FORCE_INLINE void set_all_unhomed() { axis_homed = 0; }
|
FORCE_INLINE void set_all_unhomed() { axis_homed = axis_known_position = 0; }
|
||||||
FORCE_INLINE void set_all_unknown() { axis_known_position = 0; }
|
|
||||||
|
|
||||||
FORCE_INLINE bool homing_needed() {
|
FORCE_INLINE bool homing_needed() {
|
||||||
return !TERN(HOME_AFTER_DEACTIVATE, all_axes_known, all_axes_homed)();
|
return !TERN(HOME_AFTER_DEACTIVATE, all_axes_known, all_axes_homed)();
|
||||||
|
@ -239,22 +238,18 @@ void do_z_clearance(const float &zclear, const bool z_known=true, const bool rai
|
||||||
//
|
//
|
||||||
// Homing
|
// Homing
|
||||||
//
|
//
|
||||||
|
void homeaxis(const AxisEnum axis);
|
||||||
uint8_t axes_need_homing(uint8_t axis_bits=0x07);
|
void set_axis_is_at_home(const AxisEnum axis);
|
||||||
bool axis_unhomed_error(uint8_t axis_bits=0x07);
|
void set_axis_never_homed(const AxisEnum axis);
|
||||||
|
uint8_t axes_should_home(uint8_t axis_bits=0x07);
|
||||||
|
bool homing_needed_error(uint8_t axis_bits=0x07);
|
||||||
|
|
||||||
#if ENABLED(NO_MOTION_BEFORE_HOMING)
|
#if ENABLED(NO_MOTION_BEFORE_HOMING)
|
||||||
#define MOTION_CONDITIONS (IsRunning() && !axis_unhomed_error())
|
#define MOTION_CONDITIONS (IsRunning() && !homing_needed_error())
|
||||||
#else
|
#else
|
||||||
#define MOTION_CONDITIONS IsRunning()
|
#define MOTION_CONDITIONS IsRunning()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void set_axis_is_at_home(const AxisEnum axis);
|
|
||||||
|
|
||||||
void set_axis_never_homed(const AxisEnum axis);
|
|
||||||
|
|
||||||
void homeaxis(const AxisEnum axis);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Workspace offsets
|
* Workspace offsets
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -359,7 +359,7 @@ bool Probe::set_deployed(const bool deploy) {
|
||||||
do_z_raise(_MAX(Z_CLEARANCE_BETWEEN_PROBES, Z_CLEARANCE_DEPLOY_PROBE));
|
do_z_raise(_MAX(Z_CLEARANCE_BETWEEN_PROBES, Z_CLEARANCE_DEPLOY_PROBE));
|
||||||
|
|
||||||
#if EITHER(Z_PROBE_SLED, Z_PROBE_ALLEN_KEY)
|
#if EITHER(Z_PROBE_SLED, Z_PROBE_ALLEN_KEY)
|
||||||
if (axis_unhomed_error(TERN_(Z_PROBE_SLED, _BV(X_AXIS)))) {
|
if (homing_needed_error(TERN_(Z_PROBE_SLED, _BV(X_AXIS)))) {
|
||||||
SERIAL_ERROR_MSG(STR_STOP_UNHOMED);
|
SERIAL_ERROR_MSG(STR_STOP_UNHOMED);
|
||||||
stop();
|
stop();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -840,12 +840,13 @@ void reset_stepper_drivers(); // Called by settings.load / settings.reset
|
||||||
//
|
//
|
||||||
// Axis steppers enable / disable macros
|
// Axis steppers enable / disable macros
|
||||||
//
|
//
|
||||||
|
#define FORGET_AXIS(A) TERN(HOME_AFTER_DEACTIVATE, set_axis_never_homed(A), CBI(axis_known_position, A))
|
||||||
|
|
||||||
#define ENABLE_AXIS_X() do{ ENABLE_STEPPER_X(); ENABLE_STEPPER_X2(); }while(0)
|
#define ENABLE_AXIS_X() do{ ENABLE_STEPPER_X(); ENABLE_STEPPER_X2(); }while(0)
|
||||||
#define DISABLE_AXIS_X() do{ DISABLE_STEPPER_X(); DISABLE_STEPPER_X2(); CBI(axis_known_position, X_AXIS); }while(0)
|
#define DISABLE_AXIS_X() do{ DISABLE_STEPPER_X(); DISABLE_STEPPER_X2(); FORGET_AXIS(X_AXIS); }while(0)
|
||||||
|
|
||||||
#define ENABLE_AXIS_Y() do{ ENABLE_STEPPER_Y(); ENABLE_STEPPER_Y2(); }while(0)
|
#define ENABLE_AXIS_Y() do{ ENABLE_STEPPER_Y(); ENABLE_STEPPER_Y2(); }while(0)
|
||||||
#define DISABLE_AXIS_Y() do{ DISABLE_STEPPER_Y(); DISABLE_STEPPER_Y2(); CBI(axis_known_position, Y_AXIS); }while(0)
|
#define DISABLE_AXIS_Y() do{ DISABLE_STEPPER_Y(); DISABLE_STEPPER_Y2(); FORGET_AXIS(Y_AXIS); }while(0)
|
||||||
|
|
||||||
#define ENABLE_AXIS_Z() do{ ENABLE_STEPPER_Z(); ENABLE_STEPPER_Z2(); ENABLE_STEPPER_Z3(); ENABLE_STEPPER_Z4(); }while(0)
|
#define ENABLE_AXIS_Z() do{ ENABLE_STEPPER_Z(); ENABLE_STEPPER_Z2(); ENABLE_STEPPER_Z3(); ENABLE_STEPPER_Z4(); }while(0)
|
||||||
|
|
||||||
|
@ -854,7 +855,7 @@ void reset_stepper_drivers(); // Called by settings.load / settings.reset
|
||||||
#else
|
#else
|
||||||
#define Z_RESET()
|
#define Z_RESET()
|
||||||
#endif
|
#endif
|
||||||
#define DISABLE_AXIS_Z() do{ DISABLE_STEPPER_Z(); DISABLE_STEPPER_Z2(); DISABLE_STEPPER_Z3(); DISABLE_STEPPER_Z4(); CBI(axis_known_position, Z_AXIS); Z_RESET(); }while(0)
|
#define DISABLE_AXIS_Z() do{ DISABLE_STEPPER_Z(); DISABLE_STEPPER_Z2(); DISABLE_STEPPER_Z3(); DISABLE_STEPPER_Z4(); FORGET_AXIS(Z_AXIS); Z_RESET(); }while(0)
|
||||||
|
|
||||||
//
|
//
|
||||||
// Extruder steppers enable / disable macros
|
// Extruder steppers enable / disable macros
|
||||||
|
|
|
@ -174,7 +174,7 @@ inline void fast_line_to_current(const AxisEnum fr_axis) { _line_to_current(fr_a
|
||||||
grabpos = mpe_settings.parking_xpos[new_tool] + (new_tool ? mpe_settings.grab_distance : -mpe_settings.grab_distance),
|
grabpos = mpe_settings.parking_xpos[new_tool] + (new_tool ? mpe_settings.grab_distance : -mpe_settings.grab_distance),
|
||||||
offsetcompensation = TERN0(HAS_HOTEND_OFFSET, hotend_offset[active_extruder].x * mpe_settings.compensation_factor);
|
offsetcompensation = TERN0(HAS_HOTEND_OFFSET, hotend_offset[active_extruder].x * mpe_settings.compensation_factor);
|
||||||
|
|
||||||
if (axis_unhomed_error(_BV(X_AXIS))) return;
|
if (homing_needed_error(_BV(X_AXIS))) return;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Z Lift and Nozzle Offset shift ar defined in caller method to work equal with any Multi Hotend realization
|
* Z Lift and Nozzle Offset shift ar defined in caller method to work equal with any Multi Hotend realization
|
||||||
|
|
Loading…
Reference in a new issue