✨ Polar Kinematics (#25214)
This commit is contained in:
committed by
Scott Lahteine
parent
33e5aad364
commit
7717beb793
@@ -915,7 +915,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Print surface diameter/2 minus unreachable space (avoid collisions with vertical towers).
|
// Print surface diameter/2 minus unreachable space (avoid collisions with vertical towers).
|
||||||
#define DELTA_PRINTABLE_RADIUS 140.0 // (mm)
|
#define PRINTABLE_RADIUS 140.0 // (mm)
|
||||||
|
|
||||||
// Maximum reachable area
|
// Maximum reachable area
|
||||||
#define DELTA_MAX_RADIUS 140.0 // (mm)
|
#define DELTA_MAX_RADIUS 140.0 // (mm)
|
||||||
@@ -969,7 +969,7 @@
|
|||||||
#if ENABLED(MORGAN_SCARA)
|
#if ENABLED(MORGAN_SCARA)
|
||||||
|
|
||||||
//#define DEBUG_SCARA_KINEMATICS
|
//#define DEBUG_SCARA_KINEMATICS
|
||||||
#define SCARA_FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly
|
#define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly
|
||||||
|
|
||||||
// Radius around the center where the arm cannot reach
|
// Radius around the center where the arm cannot reach
|
||||||
#define MIDDLE_DEAD_ZONE_R 0 // (mm)
|
#define MIDDLE_DEAD_ZONE_R 0 // (mm)
|
||||||
@@ -1004,7 +1004,7 @@
|
|||||||
#define TPARA_OFFSET_Y 0 // (mm)
|
#define TPARA_OFFSET_Y 0 // (mm)
|
||||||
#define TPARA_OFFSET_Z 0 // (mm)
|
#define TPARA_OFFSET_Z 0 // (mm)
|
||||||
|
|
||||||
#define SCARA_FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly
|
#define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly
|
||||||
|
|
||||||
// Radius around the center where the arm cannot reach
|
// Radius around the center where the arm cannot reach
|
||||||
#define MIDDLE_DEAD_ZONE_R 0 // (mm)
|
#define MIDDLE_DEAD_ZONE_R 0 // (mm)
|
||||||
@@ -1014,6 +1014,59 @@
|
|||||||
#define PSI_HOMING_OFFSET 0
|
#define PSI_HOMING_OFFSET 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// @section polar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POLAR Kinematics
|
||||||
|
* developed by Kadir ilkimen for PolarBear CNC and babyBear
|
||||||
|
* https://github.com/kadirilkimen/Polar-Bear-Cnc-Machine
|
||||||
|
* https://github.com/kadirilkimen/babyBear-3D-printer
|
||||||
|
*
|
||||||
|
* A polar machine can have different configurations.
|
||||||
|
* This kinematics is only compatible with the following configuration:
|
||||||
|
* X : Independent linear
|
||||||
|
* Y or B : Polar
|
||||||
|
* Z : Independent linear
|
||||||
|
*
|
||||||
|
* For example, PolarBear has CoreXZ plus Polar Y or B.
|
||||||
|
*
|
||||||
|
* Motion problem for Polar axis near center / origin:
|
||||||
|
*
|
||||||
|
* 3D printing:
|
||||||
|
* Movements very close to the center of the polar axis take more time than others.
|
||||||
|
* This brief delay results in more material deposition due to the pressure in the nozzle.
|
||||||
|
*
|
||||||
|
* Current Kinematics and feedrate scaling deals with this by making the movement as fast
|
||||||
|
* as possible. It works for slow movements but doesn't work well with fast ones. A more
|
||||||
|
* complicated extrusion compensation must be implemented.
|
||||||
|
*
|
||||||
|
* Ideally, it should estimate that a long rotation near the center is ahead and will cause
|
||||||
|
* unwanted deposition. Therefore it can compensate the extrusion beforehand.
|
||||||
|
*
|
||||||
|
* Laser cutting:
|
||||||
|
* Same thing would be a problem for laser engraving too. As it spends time rotating at the
|
||||||
|
* center point, more likely it will burn more material than it should. Therefore similar
|
||||||
|
* compensation would be implemented for laser-cutting operations.
|
||||||
|
*
|
||||||
|
* Milling:
|
||||||
|
* This shouldn't be a problem for cutting/milling operations.
|
||||||
|
*/
|
||||||
|
//#define POLAR
|
||||||
|
#if ENABLED(POLAR)
|
||||||
|
#define DEFAULT_SEGMENTS_PER_SECOND 180 // If movement is choppy try lowering this value
|
||||||
|
#define PRINTABLE_RADIUS 82.0f // (mm) Maximum travel of X axis
|
||||||
|
|
||||||
|
// Movements fall inside POLAR_FAST_RADIUS are assigned the highest possible feedrate
|
||||||
|
// to compensate unwanted deposition related to the near-origin motion problem.
|
||||||
|
#define POLAR_FAST_RADIUS 3.0f // (mm)
|
||||||
|
|
||||||
|
// Radius which is unreachable by the tool.
|
||||||
|
// Needed if the tool is not perfectly aligned to the center of the polar axis.
|
||||||
|
#define POLAR_CENTER_OFFSET 0.0f // (mm)
|
||||||
|
|
||||||
|
#define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly
|
||||||
|
#endif
|
||||||
|
|
||||||
// @section machine
|
// @section machine
|
||||||
|
|
||||||
// Articulated robot (arm). Joints are directly mapped to axes with no kinematics.
|
// Articulated robot (arm). Joints are directly mapped to axes with no kinematics.
|
||||||
@@ -1420,13 +1473,13 @@
|
|||||||
// 2 or 3 sets of coordinates for deploying and retracting the spring loaded touch probe on G29,
|
// 2 or 3 sets of coordinates for deploying and retracting the spring loaded touch probe on G29,
|
||||||
// if servo actuated touch probe is not defined. Uncomment as appropriate for your printer/probe.
|
// if servo actuated touch probe is not defined. Uncomment as appropriate for your printer/probe.
|
||||||
|
|
||||||
#define Z_PROBE_ALLEN_KEY_DEPLOY_1 { 30.0, DELTA_PRINTABLE_RADIUS, 100.0 }
|
#define Z_PROBE_ALLEN_KEY_DEPLOY_1 { 30.0, PRINTABLE_RADIUS, 100.0 }
|
||||||
#define Z_PROBE_ALLEN_KEY_DEPLOY_1_FEEDRATE XY_PROBE_FEEDRATE
|
#define Z_PROBE_ALLEN_KEY_DEPLOY_1_FEEDRATE XY_PROBE_FEEDRATE
|
||||||
|
|
||||||
#define Z_PROBE_ALLEN_KEY_DEPLOY_2 { 0.0, DELTA_PRINTABLE_RADIUS, 100.0 }
|
#define Z_PROBE_ALLEN_KEY_DEPLOY_2 { 0.0, PRINTABLE_RADIUS, 100.0 }
|
||||||
#define Z_PROBE_ALLEN_KEY_DEPLOY_2_FEEDRATE (XY_PROBE_FEEDRATE)/10
|
#define Z_PROBE_ALLEN_KEY_DEPLOY_2_FEEDRATE (XY_PROBE_FEEDRATE)/10
|
||||||
|
|
||||||
#define Z_PROBE_ALLEN_KEY_DEPLOY_3 { 0.0, (DELTA_PRINTABLE_RADIUS) * 0.75, 100.0 }
|
#define Z_PROBE_ALLEN_KEY_DEPLOY_3 { 0.0, (PRINTABLE_RADIUS) * 0.75, 100.0 }
|
||||||
#define Z_PROBE_ALLEN_KEY_DEPLOY_3_FEEDRATE XY_PROBE_FEEDRATE
|
#define Z_PROBE_ALLEN_KEY_DEPLOY_3_FEEDRATE XY_PROBE_FEEDRATE
|
||||||
|
|
||||||
#define Z_PROBE_ALLEN_KEY_STOW_1 { -64.0, 56.0, 23.0 } // Move the probe into position
|
#define Z_PROBE_ALLEN_KEY_STOW_1 { -64.0, 56.0, 23.0 } // Move the probe into position
|
||||||
|
@@ -168,6 +168,8 @@
|
|||||||
#include "module/polargraph.h"
|
#include "module/polargraph.h"
|
||||||
#elif IS_SCARA
|
#elif IS_SCARA
|
||||||
#include "module/scara.h"
|
#include "module/scara.h"
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
#include "module/polar.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if HAS_LEVELING
|
#if HAS_LEVELING
|
||||||
|
@@ -279,6 +279,7 @@
|
|||||||
#define STR_S_SEG_PER_SEC "S<seg-per-sec>"
|
#define STR_S_SEG_PER_SEC "S<seg-per-sec>"
|
||||||
#define STR_DELTA_SETTINGS "Delta (L<diagonal-rod> R<radius> H<height> S<seg-per-sec> XYZ<tower-angle-trim> ABC<rod-trim>)"
|
#define STR_DELTA_SETTINGS "Delta (L<diagonal-rod> R<radius> H<height> S<seg-per-sec> XYZ<tower-angle-trim> ABC<rod-trim>)"
|
||||||
#define STR_SCARA_SETTINGS "SCARA"
|
#define STR_SCARA_SETTINGS "SCARA"
|
||||||
|
#define STR_POLAR_SETTINGS "Polar"
|
||||||
#define STR_POLARGRAPH_SETTINGS "Polargraph"
|
#define STR_POLARGRAPH_SETTINGS "Polargraph"
|
||||||
#define STR_SCARA_P_T_Z "P<theta-psi-offset> T<theta-offset> Z<home-offset>"
|
#define STR_SCARA_P_T_Z "P<theta-psi-offset> T<theta-offset> Z<home-offset>"
|
||||||
#define STR_ENDSTOP_ADJUSTMENT "Endstop adjustment"
|
#define STR_ENDSTOP_ADJUSTMENT "Endstop adjustment"
|
||||||
|
@@ -334,16 +334,14 @@
|
|||||||
#else // UBL_SEGMENTED
|
#else // UBL_SEGMENTED
|
||||||
|
|
||||||
#if IS_SCARA
|
#if IS_SCARA
|
||||||
#define DELTA_SEGMENT_MIN_LENGTH 0.25 // SCARA minimum segment size is 0.25mm
|
#define SEGMENT_MIN_LENGTH 0.25 // SCARA minimum segment size is 0.25mm
|
||||||
#elif ENABLED(DELTA)
|
#elif IS_KINEMATIC
|
||||||
#define DELTA_SEGMENT_MIN_LENGTH 0.10 // mm (still subject to DEFAULT_SEGMENTS_PER_SECOND)
|
#define SEGMENT_MIN_LENGTH 0.10 // (mm) Still subject to DEFAULT_SEGMENTS_PER_SECOND
|
||||||
#elif ENABLED(POLARGRAPH)
|
|
||||||
#define DELTA_SEGMENT_MIN_LENGTH 0.10 // mm (still subject to DEFAULT_SEGMENTS_PER_SECOND)
|
|
||||||
#else // CARTESIAN
|
#else // CARTESIAN
|
||||||
#ifdef LEVELED_SEGMENT_LENGTH
|
#ifdef LEVELED_SEGMENT_LENGTH
|
||||||
#define DELTA_SEGMENT_MIN_LENGTH LEVELED_SEGMENT_LENGTH
|
#define SEGMENT_MIN_LENGTH LEVELED_SEGMENT_LENGTH
|
||||||
#else
|
#else
|
||||||
#define DELTA_SEGMENT_MIN_LENGTH 1.00 // mm (similar to G2/G3 arc segmentation)
|
#define SEGMENT_MIN_LENGTH 1.00 // (mm) Similar to G2/G3 arc segmentation
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -361,23 +359,23 @@
|
|||||||
const xyze_pos_t total = destination - current_position;
|
const xyze_pos_t total = destination - current_position;
|
||||||
|
|
||||||
const float cart_xy_mm_2 = HYPOT2(total.x, total.y),
|
const float cart_xy_mm_2 = HYPOT2(total.x, total.y),
|
||||||
cart_xy_mm = SQRT(cart_xy_mm_2); // Total XY distance
|
cart_xy_mm = SQRT(cart_xy_mm_2); // Total XY distance
|
||||||
|
|
||||||
#if IS_KINEMATIC
|
#if IS_KINEMATIC
|
||||||
const float seconds = cart_xy_mm / scaled_fr_mm_s; // Duration of XY move at requested rate
|
const float seconds = cart_xy_mm / scaled_fr_mm_s; // Duration of XY move at requested rate
|
||||||
uint16_t segments = LROUND(segments_per_second * seconds), // Preferred number of segments for distance @ feedrate
|
uint16_t segments = LROUND(segments_per_second * seconds), // Preferred number of segments for distance @ feedrate
|
||||||
seglimit = LROUND(cart_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length
|
seglimit = LROUND(cart_xy_mm * RECIPROCAL(SEGMENT_MIN_LENGTH)); // Number of segments at minimum segment length
|
||||||
NOMORE(segments, seglimit); // Limit to minimum segment length (fewer segments)
|
NOMORE(segments, seglimit); // Limit to minimum segment length (fewer segments)
|
||||||
#else
|
#else
|
||||||
uint16_t segments = LROUND(cart_xy_mm * RECIPROCAL(DELTA_SEGMENT_MIN_LENGTH)); // Cartesian fixed segment length
|
uint16_t segments = LROUND(cart_xy_mm * RECIPROCAL(SEGMENT_MIN_LENGTH)); // Cartesian fixed segment length
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
NOLESS(segments, 1U); // Must have at least one segment
|
NOLESS(segments, 1U); // Must have at least one segment
|
||||||
const float inv_segments = 1.0f / segments; // Reciprocal to save calculation
|
const float inv_segments = 1.0f / segments; // Reciprocal to save calculation
|
||||||
|
|
||||||
// Add hints to help optimize the move
|
// Add hints to help optimize the move
|
||||||
PlannerHints hints(SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments); // Length of each segment
|
PlannerHints hints(SQRT(cart_xy_mm_2 + sq(total.z)) * inv_segments); // Length of each segment
|
||||||
#if ENABLED(SCARA_FEEDRATE_SCALING)
|
#if ENABLED(FEEDRATE_SCALING)
|
||||||
hints.inv_duration = scaled_fr_mm_s / hints.millimeters;
|
hints.inv_duration = scaled_fr_mm_s / hints.millimeters;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -407,12 +407,12 @@ void GcodeSuite::G33() {
|
|||||||
towers_set = !parser.seen_test('T');
|
towers_set = !parser.seen_test('T');
|
||||||
|
|
||||||
// The calibration radius is set to a calculated value
|
// The calibration radius is set to a calculated value
|
||||||
float dcr = probe_at_offset ? DELTA_PRINTABLE_RADIUS : DELTA_PRINTABLE_RADIUS - PROBING_MARGIN;
|
float dcr = probe_at_offset ? PRINTABLE_RADIUS : PRINTABLE_RADIUS - PROBING_MARGIN;
|
||||||
#if HAS_PROBE_XY_OFFSET
|
#if HAS_PROBE_XY_OFFSET
|
||||||
const float total_offset = HYPOT(probe.offset_xy.x, probe.offset_xy.y);
|
const float total_offset = HYPOT(probe.offset_xy.x, probe.offset_xy.y);
|
||||||
dcr -= probe_at_offset ? _MAX(total_offset, PROBING_MARGIN) : total_offset;
|
dcr -= probe_at_offset ? _MAX(total_offset, PROBING_MARGIN) : total_offset;
|
||||||
#endif
|
#endif
|
||||||
NOMORE(dcr, DELTA_PRINTABLE_RADIUS);
|
NOMORE(dcr, PRINTABLE_RADIUS);
|
||||||
if (parser.seenval('R')) dcr -= _MAX(parser.value_float(), 0.0f);
|
if (parser.seenval('R')) dcr -= _MAX(parser.value_float(), 0.0f);
|
||||||
TERN_(HAS_DELTA_SENSORLESS_PROBING, dcr *= sensorless_radius_factor);
|
TERN_(HAS_DELTA_SENSORLESS_PROBING, dcr *= sensorless_radius_factor);
|
||||||
|
|
||||||
|
@@ -162,8 +162,8 @@ void GcodeSuite::M48() {
|
|||||||
float angle = random(0, 360);
|
float angle = random(0, 360);
|
||||||
const float radius = random(
|
const float radius = random(
|
||||||
#if ENABLED(DELTA)
|
#if ENABLED(DELTA)
|
||||||
int(0.1250000000 * (DELTA_PRINTABLE_RADIUS)),
|
int(0.1250000000 * (PRINTABLE_RADIUS)),
|
||||||
int(0.3333333333 * (DELTA_PRINTABLE_RADIUS))
|
int(0.3333333333 * (PRINTABLE_RADIUS))
|
||||||
#else
|
#else
|
||||||
int(5), int(0.125 * _MIN(X_BED_SIZE, Y_BED_SIZE))
|
int(5), int(0.125 * _MIN(X_BED_SIZE, Y_BED_SIZE))
|
||||||
#endif
|
#endif
|
||||||
|
@@ -181,6 +181,25 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
|
||||||
|
#include "../../module/polar.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M665: Set POLAR settings
|
||||||
|
* Parameters:
|
||||||
|
* S[segments] - Segments-per-second
|
||||||
|
*/
|
||||||
|
void GcodeSuite::M665() {
|
||||||
|
if (!parser.seen_any()) return M665_report();
|
||||||
|
if (parser.seenval('S')) segments_per_second = parser.value_float();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GcodeSuite::M665_report(const bool forReplay/*=true*/) {
|
||||||
|
report_heading_etc(forReplay, F(STR_POLAR_SETTINGS));
|
||||||
|
SERIAL_ECHOLNPGM_P(PSTR(" M665 S"), segments_per_second);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // IS_KINEMATIC
|
#endif // IS_KINEMATIC
|
||||||
|
@@ -335,7 +335,7 @@
|
|||||||
#include "../feature/encoder_i2c.h"
|
#include "../feature/encoder_i2c.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if IS_SCARA || defined(G0_FEEDRATE)
|
#if EITHER(IS_SCARA, POLAR) || defined(G0_FEEDRATE)
|
||||||
#define HAS_FAST_MOVES 1
|
#define HAS_FAST_MOVES 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
#if IS_KINEMATIC
|
#if IS_KINEMATIC
|
||||||
// Kinematics applied to the leveled position
|
// Kinematics applied to the leveled position
|
||||||
SERIAL_ECHOPGM(TERN(IS_SCARA, "ScaraK: ", "DeltaK: "));
|
SERIAL_ECHOPGM(TERN(POLAR, "Polar", TERN(IS_SCARA, "Scara", "Delta")) "K: " );
|
||||||
inverse_kinematics(leveled); // writes delta[]
|
inverse_kinematics(leveled); // writes delta[]
|
||||||
report_linear_axis_pos(delta);
|
report_linear_axis_pos(delta);
|
||||||
#endif
|
#endif
|
||||||
|
@@ -161,6 +161,7 @@ void GcodeSuite::M360() {
|
|||||||
SERIAL_ECHOLNPGM(
|
SERIAL_ECHOLNPGM(
|
||||||
TERN_(DELTA, "Delta")
|
TERN_(DELTA, "Delta")
|
||||||
TERN_(IS_SCARA, "SCARA")
|
TERN_(IS_SCARA, "SCARA")
|
||||||
|
TERN_(POLAR, "Polar")
|
||||||
TERN_(IS_CORE, "Core")
|
TERN_(IS_CORE, "Core")
|
||||||
TERN_(MARKFORGED_XY, "MarkForgedXY")
|
TERN_(MARKFORGED_XY, "MarkForgedXY")
|
||||||
TERN_(MARKFORGED_YX, "MarkForgedYX")
|
TERN_(MARKFORGED_YX, "MarkForgedYX")
|
||||||
|
@@ -106,7 +106,7 @@ void GcodeSuite::G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move/*=false*/)) {
|
|||||||
|
|
||||||
#endif // FWRETRACT
|
#endif // FWRETRACT
|
||||||
|
|
||||||
#if IS_SCARA
|
#if EITHER(IS_SCARA, POLAR)
|
||||||
fast_move ? prepare_fast_move_to_destination() : prepare_line_to_destination();
|
fast_move ? prepare_fast_move_to_destination() : prepare_line_to_destination();
|
||||||
#else
|
#else
|
||||||
prepare_line_to_destination();
|
prepare_line_to_destination();
|
||||||
|
@@ -218,7 +218,7 @@ void plan_arc(
|
|||||||
|
|
||||||
// Add hints to help optimize the move
|
// Add hints to help optimize the move
|
||||||
PlannerHints hints;
|
PlannerHints hints;
|
||||||
#if ENABLED(SCARA_FEEDRATE_SCALING)
|
#if ENABLED(FEEDRATE_SCALING)
|
||||||
hints.inv_duration = (scaled_fr_mm_s / flat_mm) * segments;
|
hints.inv_duration = (scaled_fr_mm_s / flat_mm) * segments;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -1406,7 +1406,7 @@
|
|||||||
#if ANY(MORGAN_SCARA, MP_SCARA, AXEL_TPARA)
|
#if ANY(MORGAN_SCARA, MP_SCARA, AXEL_TPARA)
|
||||||
#define IS_SCARA 1
|
#define IS_SCARA 1
|
||||||
#define IS_KINEMATIC 1
|
#define IS_KINEMATIC 1
|
||||||
#elif EITHER(DELTA, POLARGRAPH)
|
#elif ANY(DELTA, POLARGRAPH, POLAR)
|
||||||
#define IS_KINEMATIC 1
|
#define IS_KINEMATIC 1
|
||||||
#else
|
#else
|
||||||
#define IS_CARTESIAN 1
|
#define IS_CARTESIAN 1
|
||||||
|
@@ -267,6 +267,7 @@
|
|||||||
*/
|
*/
|
||||||
#if IS_KINEMATIC
|
#if IS_KINEMATIC
|
||||||
#undef LCD_BED_TRAMMING
|
#undef LCD_BED_TRAMMING
|
||||||
|
#undef SLOWDOWN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -274,12 +275,11 @@
|
|||||||
* Printable radius assumes joints can fully extend
|
* Printable radius assumes joints can fully extend
|
||||||
*/
|
*/
|
||||||
#if IS_SCARA
|
#if IS_SCARA
|
||||||
#undef SLOWDOWN
|
|
||||||
#if ENABLED(AXEL_TPARA)
|
#if ENABLED(AXEL_TPARA)
|
||||||
#define SCARA_PRINTABLE_RADIUS (TPARA_LINKAGE_1 + TPARA_LINKAGE_2)
|
#define PRINTABLE_RADIUS (TPARA_LINKAGE_1 + TPARA_LINKAGE_2)
|
||||||
#else
|
#else
|
||||||
#define QUICK_HOME
|
#define QUICK_HOME
|
||||||
#define SCARA_PRINTABLE_RADIUS (SCARA_LINKAGE_1 + SCARA_LINKAGE_2)
|
#define PRINTABLE_RADIUS (SCARA_LINKAGE_1 + SCARA_LINKAGE_2)
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -378,7 +378,6 @@
|
|||||||
*/
|
*/
|
||||||
#if ENABLED(DELTA)
|
#if ENABLED(DELTA)
|
||||||
#undef Z_SAFE_HOMING
|
#undef Z_SAFE_HOMING
|
||||||
#undef SLOWDOWN
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MESH_INSET
|
#ifndef MESH_INSET
|
||||||
@@ -3083,7 +3082,10 @@
|
|||||||
/**
|
/**
|
||||||
* Only constrain Z on DELTA / SCARA machines
|
* Only constrain Z on DELTA / SCARA machines
|
||||||
*/
|
*/
|
||||||
#if IS_KINEMATIC
|
#if ENABLED(POLAR)
|
||||||
|
#undef MIN_SOFTWARE_ENDSTOP_Y
|
||||||
|
#undef MAX_SOFTWARE_ENDSTOP_Y
|
||||||
|
#elif IS_KINEMATIC
|
||||||
#undef MIN_SOFTWARE_ENDSTOP_X
|
#undef MIN_SOFTWARE_ENDSTOP_X
|
||||||
#undef MIN_SOFTWARE_ENDSTOP_Y
|
#undef MIN_SOFTWARE_ENDSTOP_Y
|
||||||
#undef MAX_SOFTWARE_ENDSTOP_X
|
#undef MAX_SOFTWARE_ENDSTOP_X
|
||||||
@@ -3154,7 +3156,7 @@
|
|||||||
#if EITHER(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL)
|
#if EITHER(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL)
|
||||||
#if IS_KINEMATIC
|
#if IS_KINEMATIC
|
||||||
// Probing points may be verified at compile time within the radius
|
// Probing points may be verified at compile time within the radius
|
||||||
// using static_assert(HYPOT2(X2-X1,Y2-Y1)<=sq(DELTA_PRINTABLE_RADIUS),"bad probe point!")
|
// using static_assert(HYPOT2(X2-X1,Y2-Y1)<=sq(PRINTABLE_RADIUS),"bad probe point!")
|
||||||
// so that may be added to SanityCheck.h in the future.
|
// so that may be added to SanityCheck.h in the future.
|
||||||
#define _MESH_MIN_X (X_MIN_BED + MESH_INSET)
|
#define _MESH_MIN_X (X_MIN_BED + MESH_INSET)
|
||||||
#define _MESH_MIN_Y (Y_MIN_BED + MESH_INSET)
|
#define _MESH_MIN_Y (Y_MIN_BED + MESH_INSET)
|
||||||
|
@@ -658,6 +658,12 @@
|
|||||||
#error "(POLAR|DELTA|SCARA|TPARA)_SEGMENTS_PER_SECOND is now DEFAULT_SEGMENTS_PER_SECOND."
|
#error "(POLAR|DELTA|SCARA|TPARA)_SEGMENTS_PER_SECOND is now DEFAULT_SEGMENTS_PER_SECOND."
|
||||||
#elif ANY(DGUS_LCD_UI_ORIGIN, DGUS_LCD_UI_FYSETC, DGUS_LCD_UI_HIPRECY, DGUS_LCD_UI_MKS, DGUS_LCD_UI_RELOADED) && !defined(DGUS_LCD_UI)
|
#elif ANY(DGUS_LCD_UI_ORIGIN, DGUS_LCD_UI_FYSETC, DGUS_LCD_UI_HIPRECY, DGUS_LCD_UI_MKS, DGUS_LCD_UI_RELOADED) && !defined(DGUS_LCD_UI)
|
||||||
#error "DGUS_LCD_UI_[TYPE] is now set using DGUS_LCD_UI TYPE."
|
#error "DGUS_LCD_UI_[TYPE] is now set using DGUS_LCD_UI TYPE."
|
||||||
|
#elif defined(DELTA_PRINTABLE_RADIUS)
|
||||||
|
#error "DELTA_PRINTABLE_RADIUS is now PRINTABLE_RADIUS."
|
||||||
|
#elif defined(SCARA_PRINTABLE_RADIUS)
|
||||||
|
#error "SCARA_PRINTABLE_RADIUS is now PRINTABLE_RADIUS."
|
||||||
|
#elif defined(SCARA_FEEDRATE_SCALING)
|
||||||
|
#error "SCARA_FEEDRATE_SCALING is now FEEDRATE_SCALING."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// L64xx stepper drivers have been removed
|
// L64xx stepper drivers have been removed
|
||||||
@@ -1371,6 +1377,13 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POLAR warnings
|
||||||
|
*/
|
||||||
|
#if BOTH(POLAR, S_CURVE_ACCELERATION)
|
||||||
|
#warning "POLAR Kinematics may not work well with S_CURVE_ACCELERATION."
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special tool-changing options
|
* Special tool-changing options
|
||||||
*/
|
*/
|
||||||
@@ -1666,8 +1679,8 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
|
|||||||
/**
|
/**
|
||||||
* Allow only one kinematic type to be defined
|
* Allow only one kinematic type to be defined
|
||||||
*/
|
*/
|
||||||
#if MANY(DELTA, MORGAN_SCARA, MP_SCARA, AXEL_TPARA, COREXY, COREXZ, COREYZ, COREYX, COREZX, COREZY, MARKFORGED_XY, MARKFORGED_YX, ARTICULATED_ROBOT_ARM, FOAMCUTTER_XYUV)
|
#if MANY(DELTA, MORGAN_SCARA, MP_SCARA, AXEL_TPARA, COREXY, COREXZ, COREYZ, COREYX, COREZX, COREZY, MARKFORGED_XY, MARKFORGED_YX, ARTICULATED_ROBOT_ARM, FOAMCUTTER_XYUV, POLAR)
|
||||||
#error "Please enable only one of DELTA, MORGAN_SCARA, MP_SCARA, AXEL_TPARA, COREXY, COREXZ, COREYZ, COREYX, COREZX, COREZY, MARKFORGED_XY, MARKFORGED_YX, ARTICULATED_ROBOT_ARM, or FOAMCUTTER_XYUV."
|
#error "Please enable only one of DELTA, MORGAN_SCARA, MP_SCARA, AXEL_TPARA, COREXY, COREXZ, COREYZ, COREYX, COREZX, COREZY, MARKFORGED_XY, MARKFORGED_YX, ARTICULATED_ROBOT_ARM, FOAMCUTTER_XYUV, or POLAR."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1695,7 +1708,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
|
|||||||
* Junction deviation is incompatible with kinematic systems.
|
* Junction deviation is incompatible with kinematic systems.
|
||||||
*/
|
*/
|
||||||
#if HAS_JUNCTION_DEVIATION && IS_KINEMATIC
|
#if HAS_JUNCTION_DEVIATION && IS_KINEMATIC
|
||||||
#error "CLASSIC_JERK is required for DELTA and SCARA."
|
#error "CLASSIC_JERK is required for DELTA, SCARA, and POLAR."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1913,7 +1926,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
|
|||||||
static_assert(PROBING_MARGIN_RIGHT >= 0, "PROBING_MARGIN_RIGHT must be >= 0.");
|
static_assert(PROBING_MARGIN_RIGHT >= 0, "PROBING_MARGIN_RIGHT must be >= 0.");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define _MARGIN(A) TERN(IS_SCARA, SCARA_PRINTABLE_RADIUS, TERN(DELTA, DELTA_PRINTABLE_RADIUS, ((A##_BED_SIZE) / 2)))
|
#define _MARGIN(A) TERN(IS_KINEMATIC, PRINTABLE_RADIUS, ((A##_BED_SIZE) / 2))
|
||||||
static_assert(PROBING_MARGIN < _MARGIN(X), "PROBING_MARGIN is too large.");
|
static_assert(PROBING_MARGIN < _MARGIN(X), "PROBING_MARGIN is too large.");
|
||||||
static_assert(PROBING_MARGIN_BACK < _MARGIN(Y), "PROBING_MARGIN_BACK is too large.");
|
static_assert(PROBING_MARGIN_BACK < _MARGIN(Y), "PROBING_MARGIN_BACK is too large.");
|
||||||
static_assert(PROBING_MARGIN_FRONT < _MARGIN(Y), "PROBING_MARGIN_FRONT is too large.");
|
static_assert(PROBING_MARGIN_FRONT < _MARGIN(Y), "PROBING_MARGIN_FRONT is too large.");
|
||||||
@@ -2004,6 +2017,8 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
|
|||||||
|
|
||||||
#if IS_SCARA
|
#if IS_SCARA
|
||||||
#error "AUTO_BED_LEVELING_UBL does not yet support SCARA printers."
|
#error "AUTO_BED_LEVELING_UBL does not yet support SCARA printers."
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
#error "AUTO_BED_LEVELING_UBL does not yet support POLAR printers."
|
||||||
#elif DISABLED(EEPROM_SETTINGS)
|
#elif DISABLED(EEPROM_SETTINGS)
|
||||||
#error "AUTO_BED_LEVELING_UBL requires EEPROM_SETTINGS."
|
#error "AUTO_BED_LEVELING_UBL requires EEPROM_SETTINGS."
|
||||||
#elif !WITHIN(GRID_MAX_POINTS_X, 3, 15) || !WITHIN(GRID_MAX_POINTS_Y, 3, 15)
|
#elif !WITHIN(GRID_MAX_POINTS_X, 3, 15) || !WITHIN(GRID_MAX_POINTS_Y, 3, 15)
|
||||||
|
@@ -281,7 +281,7 @@ void DWINUI::Draw_FillCircle(uint16_t bcolor, uint16_t x,uint16_t y,uint8_t r) {
|
|||||||
// color2 : End color
|
// color2 : End color
|
||||||
uint16_t DWINUI::ColorInt(int16_t val, int16_t minv, int16_t maxv, uint16_t color1, uint16_t color2) {
|
uint16_t DWINUI::ColorInt(int16_t val, int16_t minv, int16_t maxv, uint16_t color1, uint16_t color2) {
|
||||||
uint8_t B, G, R;
|
uint8_t B, G, R;
|
||||||
const float n = (float)(val - minv) / (maxv - minv);
|
const float n = float(val - minv) / (maxv - minv);
|
||||||
R = (1 - n) * GetRColor(color1) + n * GetRColor(color2);
|
R = (1 - n) * GetRColor(color1) + n * GetRColor(color2);
|
||||||
G = (1 - n) * GetGColor(color1) + n * GetGColor(color2);
|
G = (1 - n) * GetGColor(color1) + n * GetGColor(color2);
|
||||||
B = (1 - n) * GetBColor(color1) + n * GetBColor(color2);
|
B = (1 - n) * GetBColor(color1) + n * GetBColor(color2);
|
||||||
@@ -296,7 +296,7 @@ uint16_t DWINUI::RainbowInt(int16_t val, int16_t minv, int16_t maxv) {
|
|||||||
uint8_t B, G, R;
|
uint8_t B, G, R;
|
||||||
const uint8_t maxB = 28, maxR = 28, maxG = 38;
|
const uint8_t maxB = 28, maxR = 28, maxG = 38;
|
||||||
const int16_t limv = _MAX(abs(minv), abs(maxv));
|
const int16_t limv = _MAX(abs(minv), abs(maxv));
|
||||||
float n = minv >= 0 ? (float)(val - minv) / (maxv - minv) : (float)val / limv;
|
float n = minv >= 0 ? float(val - minv) / (maxv - minv) : (float)val / limv;
|
||||||
LIMIT(n, -1, 1);
|
LIMIT(n, -1, 1);
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
R = 0;
|
R = 0;
|
||||||
|
@@ -333,7 +333,7 @@ namespace ExtUI {
|
|||||||
// This assumes the center is 0,0
|
// This assumes the center is 0,0
|
||||||
#if ENABLED(DELTA)
|
#if ENABLED(DELTA)
|
||||||
if (axis != Z) {
|
if (axis != Z) {
|
||||||
max = SQRT(sq(float(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y - axis])); // (Y - axis) == the other axis
|
max = SQRT(sq(float(PRINTABLE_RADIUS)) - sq(current_position[Y - axis])); // (Y - axis) == the other axis
|
||||||
min = -max;
|
min = -max;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -92,7 +92,7 @@ void _man_probe_pt(const xy_pos_t &xy) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _goto_tower_a(const_float_t a) {
|
void _goto_tower_a(const_float_t a) {
|
||||||
float dcr = DELTA_PRINTABLE_RADIUS - PROBING_MARGIN;
|
float dcr = PRINTABLE_RADIUS - PROBING_MARGIN;
|
||||||
TERN_(HAS_PROBE_XY_OFFSET, dcr -= HYPOT(probe.offset_xy.x, probe.offset_xy.y));
|
TERN_(HAS_PROBE_XY_OFFSET, dcr -= HYPOT(probe.offset_xy.x, probe.offset_xy.y));
|
||||||
TERN_(HAS_DELTA_SENSORLESS_PROBING, dcr *= sensorless_radius_factor);
|
TERN_(HAS_DELTA_SENSORLESS_PROBING, dcr *= sensorless_radius_factor);
|
||||||
xy_pos_t tower_vec = { cos(RADIANS(a)), sin(RADIANS(a)) };
|
xy_pos_t tower_vec = { cos(RADIANS(a)), sin(RADIANS(a)) };
|
||||||
|
@@ -68,7 +68,7 @@ void lcd_move_axis(const AxisEnum axis) {
|
|||||||
// This assumes the center is 0,0
|
// This assumes the center is 0,0
|
||||||
#if ENABLED(DELTA)
|
#if ENABLED(DELTA)
|
||||||
if (axis != Z_AXIS) {
|
if (axis != Z_AXIS) {
|
||||||
max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
|
max = SQRT(sq(float(PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
|
||||||
min = -max;
|
min = -max;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -734,7 +734,7 @@ static void moveAxis(const AxisEnum axis, const int8_t direction) {
|
|||||||
// This assumes the center is 0,0
|
// This assumes the center is 0,0
|
||||||
#if ENABLED(DELTA)
|
#if ENABLED(DELTA)
|
||||||
if (axis != Z_AXIS && axis != E_AXIS) {
|
if (axis != Z_AXIS && axis != E_AXIS) {
|
||||||
max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
|
max = SQRT(sq(float(PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
|
||||||
min = -max;
|
min = -max;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -714,7 +714,7 @@ static void moveAxis(const AxisEnum axis, const int8_t direction) {
|
|||||||
// This assumes the center is 0,0
|
// This assumes the center is 0,0
|
||||||
#if ENABLED(DELTA)
|
#if ENABLED(DELTA)
|
||||||
if (axis != Z_AXIS && axis != E_AXIS) {
|
if (axis != Z_AXIS && axis != E_AXIS) {
|
||||||
max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
|
max = SQRT(sq(float(PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
|
||||||
min = -max;
|
min = -max;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -715,7 +715,7 @@ static void moveAxis(const AxisEnum axis, const int8_t direction) {
|
|||||||
// This assumes the center is 0,0
|
// This assumes the center is 0,0
|
||||||
#if ENABLED(DELTA)
|
#if ENABLED(DELTA)
|
||||||
if (axis != Z_AXIS && axis != E_AXIS) {
|
if (axis != Z_AXIS && axis != E_AXIS) {
|
||||||
max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
|
max = SQRT(sq(float(PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis
|
||||||
min = -max;
|
min = -max;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -132,7 +132,7 @@ float delta_safe_distance_from_top() {
|
|||||||
xyz_pos_t cartesian{0};
|
xyz_pos_t cartesian{0};
|
||||||
inverse_kinematics(cartesian);
|
inverse_kinematics(cartesian);
|
||||||
const float centered_extent = delta.a;
|
const float centered_extent = delta.a;
|
||||||
cartesian.y = DELTA_PRINTABLE_RADIUS;
|
cartesian.y = PRINTABLE_RADIUS;
|
||||||
inverse_kinematics(cartesian);
|
inverse_kinematics(cartesian);
|
||||||
return ABS(centered_extent - delta.a);
|
return ABS(centered_extent - delta.a);
|
||||||
}
|
}
|
||||||
|
@@ -38,6 +38,10 @@
|
|||||||
#include "../lcd/marlinui.h"
|
#include "../lcd/marlinui.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(POLAR)
|
||||||
|
#include "polar.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAS_BED_PROBE
|
#if HAS_BED_PROBE
|
||||||
#include "probe.h"
|
#include "probe.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -145,11 +149,14 @@ xyz_pos_t cartes;
|
|||||||
#if HAS_SOFTWARE_ENDSTOPS
|
#if HAS_SOFTWARE_ENDSTOPS
|
||||||
float delta_max_radius, delta_max_radius_2;
|
float delta_max_radius, delta_max_radius_2;
|
||||||
#elif IS_SCARA
|
#elif IS_SCARA
|
||||||
constexpr float delta_max_radius = SCARA_PRINTABLE_RADIUS,
|
constexpr float delta_max_radius = PRINTABLE_RADIUS,
|
||||||
delta_max_radius_2 = sq(SCARA_PRINTABLE_RADIUS);
|
delta_max_radius_2 = sq(PRINTABLE_RADIUS);
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
constexpr float delta_max_radius = PRINTABLE_RADIUS,
|
||||||
|
delta_max_radius_2 = sq(PRINTABLE_RADIUS);
|
||||||
#else // DELTA
|
#else // DELTA
|
||||||
constexpr float delta_max_radius = DELTA_PRINTABLE_RADIUS,
|
constexpr float delta_max_radius = PRINTABLE_RADIUS,
|
||||||
delta_max_radius_2 = sq(DELTA_PRINTABLE_RADIUS);
|
delta_max_radius_2 = sq(PRINTABLE_RADIUS);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -183,6 +190,7 @@ xyz_pos_t cartes;
|
|||||||
inline void report_more_positions() {
|
inline void report_more_positions() {
|
||||||
stepper.report_positions();
|
stepper.report_positions();
|
||||||
TERN_(IS_SCARA, scara_report_positions());
|
TERN_(IS_SCARA, scara_report_positions());
|
||||||
|
TERN_(POLAR, polar_report_positions());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report the logical position for a given machine position
|
// Report the logical position for a given machine position
|
||||||
@@ -277,8 +285,7 @@ void report_current_position_projected() {
|
|||||||
#endif
|
#endif
|
||||||
);
|
);
|
||||||
|
|
||||||
stepper.report_positions();
|
report_more_positions();
|
||||||
TERN_(IS_SCARA, scara_report_positions());
|
|
||||||
report_current_grblstate_moving();
|
report_current_grblstate_moving();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +315,7 @@ void report_current_position_projected() {
|
|||||||
|
|
||||||
#if ENABLED(DELTA)
|
#if ENABLED(DELTA)
|
||||||
|
|
||||||
can_reach = HYPOT2(rx, ry) <= sq(DELTA_PRINTABLE_RADIUS - inset + fslop);
|
can_reach = HYPOT2(rx, ry) <= sq(PRINTABLE_RADIUS - inset + fslop);
|
||||||
|
|
||||||
#elif ENABLED(AXEL_TPARA)
|
#elif ENABLED(AXEL_TPARA)
|
||||||
|
|
||||||
@@ -343,6 +350,8 @@ void report_current_position_projected() {
|
|||||||
&& b < polargraph_max_belt_len + 1
|
&& b < polargraph_max_belt_len + 1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
can_reach = HYPOT(rx, ry) <= PRINTABLE_RADIUS;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return can_reach;
|
return can_reach;
|
||||||
@@ -426,6 +435,9 @@ void get_cartesian_from_steppers() {
|
|||||||
OPTARG(AXEL_TPARA, planner.get_axis_position_degrees(C_AXIS))
|
OPTARG(AXEL_TPARA, planner.get_axis_position_degrees(C_AXIS))
|
||||||
);
|
);
|
||||||
cartes.z = planner.get_axis_position_mm(Z_AXIS);
|
cartes.z = planner.get_axis_position_mm(Z_AXIS);
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
forward_kinematics(planner.get_axis_position_mm(X_AXIS), planner.get_axis_position_degrees(B_AXIS));
|
||||||
|
cartes.z = planner.get_axis_position_mm(Z_AXIS);
|
||||||
#else
|
#else
|
||||||
NUM_AXIS_CODE(
|
NUM_AXIS_CODE(
|
||||||
cartes.x = planner.get_axis_position_mm(X_AXIS),
|
cartes.x = planner.get_axis_position_mm(X_AXIS),
|
||||||
@@ -914,6 +926,8 @@ void restore_feedrate_and_scaling() {
|
|||||||
#if BOTH(HAS_HOTEND_OFFSET, DELTA)
|
#if BOTH(HAS_HOTEND_OFFSET, DELTA)
|
||||||
// The effector center position will be the target minus the hotend offset.
|
// The effector center position will be the target minus the hotend offset.
|
||||||
const xy_pos_t offs = hotend_offset[active_extruder];
|
const xy_pos_t offs = hotend_offset[active_extruder];
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
// For now, we don't limit POLAR
|
||||||
#else
|
#else
|
||||||
// SCARA needs to consider the angle of the arm through the entire move, so for now use no tool offset.
|
// SCARA needs to consider the angle of the arm through the entire move, so for now use no tool offset.
|
||||||
constexpr xy_pos_t offs{0};
|
constexpr xy_pos_t offs{0};
|
||||||
@@ -922,6 +936,8 @@ void restore_feedrate_and_scaling() {
|
|||||||
#if ENABLED(POLARGRAPH)
|
#if ENABLED(POLARGRAPH)
|
||||||
LIMIT(target.x, draw_area_min.x, draw_area_max.x);
|
LIMIT(target.x, draw_area_min.x, draw_area_max.x);
|
||||||
LIMIT(target.y, draw_area_min.y, draw_area_max.y);
|
LIMIT(target.y, draw_area_min.y, draw_area_max.y);
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
// Motion limits are as same as cartesian limits.
|
||||||
#else
|
#else
|
||||||
if (TERN1(IS_SCARA, axis_was_homed(X_AXIS) && axis_was_homed(Y_AXIS))) {
|
if (TERN1(IS_SCARA, axis_was_homed(X_AXIS) && axis_was_homed(Y_AXIS))) {
|
||||||
const float dist_2 = HYPOT2(target.x - offs.x, target.y - offs.y);
|
const float dist_2 = HYPOT2(target.x - offs.x, target.y - offs.y);
|
||||||
@@ -1055,6 +1071,8 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
|
|||||||
* and compare the difference.
|
* and compare the difference.
|
||||||
*/
|
*/
|
||||||
#define SCARA_MIN_SEGMENT_LENGTH 0.5f
|
#define SCARA_MIN_SEGMENT_LENGTH 0.5f
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
#define POLAR_MIN_SEGMENT_LENGTH 0.5f
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1107,6 +1125,8 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
|
|||||||
// For SCARA enforce a minimum segment size
|
// For SCARA enforce a minimum segment size
|
||||||
#if IS_SCARA
|
#if IS_SCARA
|
||||||
NOMORE(segments, cartesian_mm * RECIPROCAL(SCARA_MIN_SEGMENT_LENGTH));
|
NOMORE(segments, cartesian_mm * RECIPROCAL(SCARA_MIN_SEGMENT_LENGTH));
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
NOMORE(segments, cartesian_mm * RECIPROCAL(POLAR_MIN_SEGMENT_LENGTH));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// At least one segment is required
|
// At least one segment is required
|
||||||
@@ -1118,7 +1138,7 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
|
|||||||
|
|
||||||
// Add hints to help optimize the move
|
// Add hints to help optimize the move
|
||||||
PlannerHints hints(cartesian_mm * inv_segments);
|
PlannerHints hints(cartesian_mm * inv_segments);
|
||||||
TERN_(SCARA_FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
|
TERN_(FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
SERIAL_ECHOPGM("mm=", cartesian_mm);
|
SERIAL_ECHOPGM("mm=", cartesian_mm);
|
||||||
@@ -1185,7 +1205,7 @@ FORCE_INLINE void segment_idle(millis_t &next_idle_ms) {
|
|||||||
|
|
||||||
// Add hints to help optimize the move
|
// Add hints to help optimize the move
|
||||||
PlannerHints hints(cartesian_mm * inv_segments);
|
PlannerHints hints(cartesian_mm * inv_segments);
|
||||||
TERN_(SCARA_FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
|
TERN_(FEEDRATE_SCALING, hints.inv_duration = scaled_fr_mm_s / hints.millimeters);
|
||||||
|
|
||||||
//SERIAL_ECHOPGM("mm=", cartesian_mm);
|
//SERIAL_ECHOPGM("mm=", cartesian_mm);
|
||||||
//SERIAL_ECHOLNPGM(" segments=", segments);
|
//SERIAL_ECHOLNPGM(" segments=", segments);
|
||||||
|
@@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
#if IS_SCARA
|
#if IS_SCARA
|
||||||
#include "scara.h"
|
#include "scara.h"
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
#include "polar.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Error margin to work around float imprecision
|
// Error margin to work around float imprecision
|
||||||
|
@@ -3161,24 +3161,75 @@ bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s
|
|||||||
? xyz_pos_t(cart_dist_mm).magnitude()
|
? xyz_pos_t(cart_dist_mm).magnitude()
|
||||||
: TERN0(HAS_Z_AXIS, ABS(cart_dist_mm.z));
|
: TERN0(HAS_Z_AXIS, ABS(cart_dist_mm.z));
|
||||||
|
|
||||||
#if ENABLED(SCARA_FEEDRATE_SCALING)
|
#if DISABLED(FEEDRATE_SCALING)
|
||||||
|
|
||||||
|
const feedRate_t feedrate = fr_mm_s;
|
||||||
|
|
||||||
|
#elif IS_SCARA
|
||||||
|
|
||||||
// For SCARA scale the feedrate from mm/s to degrees/s
|
// For SCARA scale the feedrate from mm/s to degrees/s
|
||||||
// i.e., Complete the angular vector in the given time.
|
// i.e., Complete the angular vector in the given time.
|
||||||
const float duration_recip = hints.inv_duration ?: fr_mm_s / ph.millimeters;
|
const float duration_recip = hints.inv_duration ?: fr_mm_s / ph.millimeters;
|
||||||
const xyz_pos_t diff = delta - position_float;
|
const xyz_pos_t diff = delta - position_float;
|
||||||
const feedRate_t feedrate = diff.magnitude() * duration_recip;
|
const feedRate_t feedrate = diff.magnitude() * duration_recip;
|
||||||
#else
|
|
||||||
const feedRate_t feedrate = fr_mm_s;
|
#elif ENABLED(POLAR)
|
||||||
#endif
|
|
||||||
|
/**
|
||||||
|
* Motion problem for Polar axis near center / origin:
|
||||||
|
*
|
||||||
|
* 3D printing:
|
||||||
|
* Movements very close to the center of the polar axis take more time than others.
|
||||||
|
* This brief delay results in more material deposition due to the pressure in the nozzle.
|
||||||
|
*
|
||||||
|
* Current Kinematics and feedrate scaling deals with this by making the movement as fast
|
||||||
|
* as possible. It works for slow movements but doesn't work well with fast ones. A more
|
||||||
|
* complicated extrusion compensation must be implemented.
|
||||||
|
*
|
||||||
|
* Ideally, it should estimate that a long rotation near the center is ahead and will cause
|
||||||
|
* unwanted deposition. Therefore it can compensate the extrusion beforehand.
|
||||||
|
*
|
||||||
|
* Laser cutting:
|
||||||
|
* Same thing would be a problem for laser engraving too. As it spends time rotating at the
|
||||||
|
* center point, more likely it will burn more material than it should. Therefore similar
|
||||||
|
* compensation would be implemented for laser-cutting operations.
|
||||||
|
*
|
||||||
|
* Milling:
|
||||||
|
* This shouldn't be a problem for cutting/milling operations.
|
||||||
|
*/
|
||||||
|
feedRate_t calculated_feedrate = fr_mm_s;
|
||||||
|
const xyz_pos_t diff = delta - position_float;
|
||||||
|
if (!NEAR_ZERO(diff.b)) {
|
||||||
|
if (delta.a <= POLAR_FAST_RADIUS )
|
||||||
|
calculated_feedrate = settings.max_feedrate_mm_s[Y_AXIS];
|
||||||
|
else {
|
||||||
|
// Normalized vector of movement
|
||||||
|
const float diffBLength = ABS((2.0f * PI * diff.a) * (diff.b / 360.0f)),
|
||||||
|
diffTheta = DEGREES(ATAN2(diff.a, diffBLength)),
|
||||||
|
normalizedTheta = 1.0f - (ABS(diffTheta > 90.0f ? 180.0f - diffTheta : diffTheta) / 90.0f);
|
||||||
|
|
||||||
|
// Normalized position along the radius
|
||||||
|
const float radiusRatio = PRINTABLE_RADIUS/delta.a;
|
||||||
|
calculated_feedrate += (fr_mm_s * radiusRatio * normalizedTheta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const feedRate_t feedrate = calculated_feedrate;
|
||||||
|
|
||||||
|
#endif // POLAR && FEEDRATE_SCALING
|
||||||
|
|
||||||
TERN_(HAS_EXTRUDERS, delta.e = machine.e);
|
TERN_(HAS_EXTRUDERS, delta.e = machine.e);
|
||||||
if (buffer_segment(delta OPTARG(HAS_DIST_MM_ARG, cart_dist_mm), feedrate, extruder, ph)) {
|
if (buffer_segment(delta OPTARG(HAS_DIST_MM_ARG, cart_dist_mm), feedrate, extruder, ph)) {
|
||||||
position_cart = cart;
|
position_cart = cart;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
#else
|
|
||||||
|
#else // !IS_KINEMATIC
|
||||||
|
|
||||||
return buffer_segment(machine, fr_mm_s, extruder, hints);
|
return buffer_segment(machine, fr_mm_s, extruder, hints);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // buffer_line()
|
} // buffer_line()
|
||||||
|
|
||||||
#if ENABLED(DIRECT_STEPPING)
|
#if ENABLED(DIRECT_STEPPING)
|
||||||
|
@@ -50,6 +50,8 @@
|
|||||||
#include "delta.h"
|
#include "delta.h"
|
||||||
#elif ENABLED(POLARGRAPH)
|
#elif ENABLED(POLARGRAPH)
|
||||||
#include "polargraph.h"
|
#include "polargraph.h"
|
||||||
|
#elif ENABLED(POLAR)
|
||||||
|
#include "polar.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ABL_PLANAR
|
#if ABL_PLANAR
|
||||||
@@ -291,7 +293,7 @@ typedef struct PlannerBlock {
|
|||||||
|
|
||||||
} block_t;
|
} block_t;
|
||||||
|
|
||||||
#if ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY)
|
#if ANY(LIN_ADVANCE, FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY)
|
||||||
#define HAS_POSITION_FLOAT 1
|
#define HAS_POSITION_FLOAT 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -361,7 +363,7 @@ typedef struct {
|
|||||||
|
|
||||||
struct PlannerHints {
|
struct PlannerHints {
|
||||||
float millimeters = 0.0; // Move Length, if known, else 0.
|
float millimeters = 0.0; // Move Length, if known, else 0.
|
||||||
#if ENABLED(SCARA_FEEDRATE_SCALING)
|
#if ENABLED(FEEDRATE_SCALING)
|
||||||
float inv_duration = 0.0; // Reciprocal of the move duration, if known
|
float inv_duration = 0.0; // Reciprocal of the move duration, if known
|
||||||
#endif
|
#endif
|
||||||
#if ENABLED(HINTS_CURVE_RADIUS)
|
#if ENABLED(HINTS_CURVE_RADIUS)
|
||||||
@@ -913,8 +915,8 @@ class Planner {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SCARA AB axes are in degrees, not mm
|
// SCARA AB and Polar YB axes are in degrees, not mm
|
||||||
#if IS_SCARA
|
#if EITHER(IS_SCARA, POLAR)
|
||||||
FORCE_INLINE static float get_axis_position_degrees(const AxisEnum axis) { return get_axis_position_mm(axis); }
|
FORCE_INLINE static float get_axis_position_degrees(const AxisEnum axis) { return get_axis_position_mm(axis); }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
102
Marlin/src/module/polar.cpp
Normal file
102
Marlin/src/module/polar.cpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Marlin 3D Printer Firmware
|
||||||
|
* Copyright (c) 2023 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||||
|
*
|
||||||
|
* Based on Sprinter and grbl.
|
||||||
|
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POLAR Kinematics
|
||||||
|
* developed by Kadir ilkimen for PolarBear CNC and babyBear
|
||||||
|
* https://github.com/kadirilkimen/Polar-Bear-Cnc-Machine
|
||||||
|
* https://github.com/kadirilkimen/babyBear-3D-printer
|
||||||
|
*
|
||||||
|
* A polar machine can have different configurations.
|
||||||
|
* This kinematics is only compatible with the following configuration:
|
||||||
|
* X : Independent linear
|
||||||
|
* Y or B : Polar
|
||||||
|
* Z : Independent linear
|
||||||
|
*
|
||||||
|
* For example, PolarBear has CoreXZ plus Polar Y or B.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../inc/MarlinConfigPre.h"
|
||||||
|
|
||||||
|
#if ENABLED(POLAR)
|
||||||
|
|
||||||
|
#include "polar.h"
|
||||||
|
#include "motion.h"
|
||||||
|
#include "planner.h"
|
||||||
|
|
||||||
|
#include "../inc/MarlinConfig.h"
|
||||||
|
|
||||||
|
float segments_per_second = DEFAULT_SEGMENTS_PER_SECOND,
|
||||||
|
polar_center_offset = POLAR_CENTER_OFFSET;
|
||||||
|
|
||||||
|
float absoluteAngle(float a) {
|
||||||
|
if (a < 0.0) while (a < 0.0) a += 360.0;
|
||||||
|
else if (a > 360.0) while (a > 360.0) a -= 360.0;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
void forward_kinematics(const_float_t r, const_float_t theta) {
|
||||||
|
const float absTheta = absoluteAngle(theta);
|
||||||
|
float radius = r;
|
||||||
|
if (polar_center_offset > 0.0) radius = SQRT( ABS( sq(r) - sq(-polar_center_offset) ) );
|
||||||
|
cartes.x = cos(RADIANS(absTheta))*radius;
|
||||||
|
cartes.y = sin(RADIANS(absTheta))*radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
void inverse_kinematics(const xyz_pos_t &raw) {
|
||||||
|
const float x = raw.x, y = raw.y,
|
||||||
|
rawRadius = HYPOT(x,y),
|
||||||
|
posTheta = DEGREES(ATAN2(y, x));
|
||||||
|
|
||||||
|
static float current_polar_theta = 0;
|
||||||
|
|
||||||
|
float r = rawRadius,
|
||||||
|
theta = absoluteAngle(posTheta),
|
||||||
|
currentAbsTheta = absoluteAngle(current_polar_theta);
|
||||||
|
|
||||||
|
if (polar_center_offset > 0.0) {
|
||||||
|
const float offsetRadius = SQRT(ABS(sq(r) - sq(polar_center_offset)));
|
||||||
|
float offsetTheta = absoluteAngle(DEGREES(ATAN2(polar_center_offset, offsetRadius)));
|
||||||
|
theta = absoluteAngle(offsetTheta + theta);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float deltaTheta = theta - currentAbsTheta;
|
||||||
|
if (ABS(deltaTheta) <= 180)
|
||||||
|
theta = current_polar_theta + deltaTheta;
|
||||||
|
else {
|
||||||
|
if (currentAbsTheta > 180) theta = current_polar_theta + 360 + deltaTheta;
|
||||||
|
else theta = current_polar_theta - (360 - deltaTheta);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_polar_theta = theta;
|
||||||
|
|
||||||
|
delta.set(r, theta, raw.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void polar_report_positions() {
|
||||||
|
SERIAL_ECHOLNPGM("X: ", planner.get_axis_position_mm(X_AXIS),
|
||||||
|
" POLAR Theta:", planner.get_axis_position_degrees(B_AXIS),
|
||||||
|
" Z: ", planner.get_axis_position_mm(Z_AXIS)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
36
Marlin/src/module/polar.h
Normal file
36
Marlin/src/module/polar.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Marlin 3D Printer Firmware
|
||||||
|
* Copyright (c) 2023 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||||
|
*
|
||||||
|
* Based on Sprinter and grbl.
|
||||||
|
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* polar.h - POLAR-specific functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../core/types.h"
|
||||||
|
|
||||||
|
extern float segments_per_second;
|
||||||
|
|
||||||
|
float absoluteAngle(float a);
|
||||||
|
void forward_kinematics(const_float_t r, const_float_t theta);
|
||||||
|
|
||||||
|
void inverse_kinematics(const xyz_pos_t &raw);
|
||||||
|
void polar_report_positions();
|
@@ -195,12 +195,8 @@ public:
|
|||||||
|
|
||||||
#if HAS_BED_PROBE || HAS_LEVELING
|
#if HAS_BED_PROBE || HAS_LEVELING
|
||||||
#if IS_KINEMATIC
|
#if IS_KINEMATIC
|
||||||
static constexpr float printable_radius = (
|
|
||||||
TERN_(DELTA, DELTA_PRINTABLE_RADIUS)
|
|
||||||
TERN_(IS_SCARA, SCARA_PRINTABLE_RADIUS)
|
|
||||||
);
|
|
||||||
static constexpr float probe_radius(const xy_pos_t &probe_offset_xy=offset_xy) {
|
static constexpr float probe_radius(const xy_pos_t &probe_offset_xy=offset_xy) {
|
||||||
return printable_radius - _MAX(PROBING_MARGIN, HYPOT(probe_offset_xy.x, probe_offset_xy.y));
|
return float(PRINTABLE_RADIUS) - _MAX(PROBING_MARGIN, HYPOT(probe_offset_xy.x, probe_offset_xy.y));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -3212,7 +3212,7 @@ int32_t Stepper::triggered_position(const AxisEnum axis) {
|
|||||||
#if ANY(CORE_IS_XY, CORE_IS_XZ, MARKFORGED_XY, MARKFORGED_YX, IS_SCARA, DELTA)
|
#if ANY(CORE_IS_XY, CORE_IS_XZ, MARKFORGED_XY, MARKFORGED_YX, IS_SCARA, DELTA)
|
||||||
#define SAYS_A 1
|
#define SAYS_A 1
|
||||||
#endif
|
#endif
|
||||||
#if ANY(CORE_IS_XY, CORE_IS_YZ, MARKFORGED_XY, MARKFORGED_YX, IS_SCARA, DELTA)
|
#if ANY(CORE_IS_XY, CORE_IS_YZ, MARKFORGED_XY, MARKFORGED_YX, IS_SCARA, DELTA, POLAR)
|
||||||
#define SAYS_B 1
|
#define SAYS_B 1
|
||||||
#endif
|
#endif
|
||||||
#if ANY(CORE_IS_XZ, CORE_IS_YZ, DELTA)
|
#if ANY(CORE_IS_XZ, CORE_IS_YZ, DELTA)
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
".vscode"
|
".vscode"
|
||||||
],
|
],
|
||||||
"binary_file_patterns":
|
"binary_file_patterns":
|
||||||
[ "*.psd", "*.png", "*.jpg", "*.jpeg", "*.bdf", "*.patch", "avrdude_5.*", "*.svg", "*.bin", "*.woff" ],
|
[ "*.psd", "*.png", "*.jpg", "*.jpeg", "*.bdf", "*.patch", "avrdude_5.*", "*.svg", "*.bin", "*.woff", "*.otf" ],
|
||||||
"file_exclude_patterns":
|
"file_exclude_patterns":
|
||||||
[
|
[
|
||||||
"Marlin/platformio.ini",
|
"Marlin/platformio.ini",
|
||||||
|
Reference in New Issue
Block a user