diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index f1feb946881f..4153b86a4a04 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -2315,22 +2315,6 @@ //#define FAST_BUTTON_POLLING // Poll buttons at ~1kHz on 8-bit AVR. Set to 'false' for slow polling on 32-bit. -// @section safety - -/** - * The watchdog hardware timer will do a reset and disable all outputs - * if the firmware gets too overloaded to read the temperature sensors. - * - * If you find that watchdog reboot causes your AVR board to hang forever, - * enable WATCHDOG_RESET_MANUAL to use a custom timer instead of WDTO. - * NOTE: This method is less reliable as it can only catch hangups while - * interrupts are enabled. - */ -#define USE_WATCHDOG -#if ENABLED(USE_WATCHDOG) - //#define WATCHDOG_RESET_MANUAL -#endif - // @section lcd /** @@ -3671,6 +3655,33 @@ // @section cnc +/** + * CNC Coordinate Systems + * + * Enables G53 and G54-G59.3 commands to select coordinate systems + * and G92.1 to reset the workspace to native machine space. + */ +//#define CNC_COORDINATE_SYSTEMS + +/** + * Coordinate System Scaling + * + * Enable G51 to scale and G50 to cancel scaling of the coordinate system. + * Mirroring can be achieved by using G51 with negative factors. + * + */ +//#define SCALE_WORKSPACE + +/** + * Rotate Workspace + * + * Enable G68 to rotate and G69 to cancel rotation of the workspace. + */ +//#define ROTATE_WORKSPACE +#if ENABLED(ROTATE_WORKSPACE) && DISABLED(DELTA) + //#define LIMIT_ROTATION_ANGLE // Limit rotation on square beds +#endif + /** * Spindle & Laser control * @@ -3938,6 +3949,20 @@ // @section safety +/** + * The watchdog hardware timer will do a reset and disable all outputs + * if the firmware gets too overloaded to read the temperature sensors. + * + * If you find that watchdog reboot causes your AVR board to hang forever, + * enable WATCHDOG_RESET_MANUAL to use a custom timer instead of WDTO. + * NOTE: This method is less reliable as it can only catch hangups while + * interrupts are enabled. + */ +#define USE_WATCHDOG +#if ENABLED(USE_WATCHDOG) + //#define WATCHDOG_RESET_MANUAL +#endif + /** * Stepper Driver Anti-SNAFU Protection * @@ -3947,16 +3972,6 @@ */ //#define DISABLE_DRIVER_SAFE_POWER_PROTECT -// @section cnc - -/** - * CNC Coordinate Systems - * - * Enables G53 and G54-G59.3 commands to select coordinate systems - * and G92.1 to reset the workspace to native machine space. - */ -//#define CNC_COORDINATE_SYSTEMS - // @section security /** diff --git a/Marlin/src/gcode/bedlevel/G26.cpp b/Marlin/src/gcode/bedlevel/G26.cpp index b655c3026fe0..8e7b0e2e9bd5 100644 --- a/Marlin/src/gcode/bedlevel/G26.cpp +++ b/Marlin/src/gcode/bedlevel/G26.cpp @@ -712,7 +712,7 @@ void GcodeSuite::G26() { #endif float trig_table[A_CNT]; for (uint8_t i = 0; i < A_CNT; ++i) - trig_table[i] = INTERSECTION_CIRCLE_RADIUS * cos(RADIANS(i * A_INT)); + trig_table[i] = INTERSECTION_CIRCLE_RADIUS * cosf(RADIANS(i * A_INT)); #endif // !ARC_SUPPORT diff --git a/Marlin/src/gcode/calibrate/G33.cpp b/Marlin/src/gcode/calibrate/G33.cpp index 1eabb30ffade..89259bf6bb36 100644 --- a/Marlin/src/gcode/calibrate/G33.cpp +++ b/Marlin/src/gcode/calibrate/G33.cpp @@ -209,7 +209,7 @@ static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_poi I_LOOP_CAL_PT(rad, start, steps) { const float a = RADIANS(210 + (360 / NPP) * (rad - 1)), r = dcr * 0.1; - const xy_pos_t vec = { cos(a), sin(a) }; + const xy_pos_t vec = { cosf(a), sinf(a) }; z_pt[CEN] += calibration_probe(vec * r, stow_after_each, probe_at_offset); if (isnan(z_pt[CEN])) return false; } @@ -234,12 +234,12 @@ static bool probe_calibration_points(float z_pt[NPP + 1], const int8_t probe_poi const float a = RADIANS(210 + (360 / NPP) * (rad - 1)), r = dcr * (1 - 0.1 * (zig_zag ? offset - circle : circle)), interpol = FMOD(rad, 1); - const xy_pos_t vec = { cos(a), sin(a) }; + const xy_pos_t vec = { cosf(a), sinf(a) }; const float z_temp = calibration_probe(vec * r, stow_after_each, probe_at_offset); if (isnan(z_temp)) return false; // split probe point to neighbouring calibration points - z_pt[uint8_t(LROUND(rad - interpol + NPP - 1)) % NPP + 1] += z_temp * sq(cos(RADIANS(interpol * 90))); - z_pt[uint8_t(LROUND(rad - interpol)) % NPP + 1] += z_temp * sq(sin(RADIANS(interpol * 90))); + z_pt[uint8_t(LROUND(rad - interpol + NPP - 1)) % NPP + 1] += z_temp * sq(cosf(RADIANS(interpol * 90))); + z_pt[uint8_t(LROUND(rad - interpol)) % NPP + 1] += z_temp * sq(sinf(RADIANS(interpol * 90))); } FLIP(zig_zag); } @@ -266,7 +266,7 @@ static void reverse_kinematics_probe_points(float z_pt[NPP + 1], abc_float_t mm_ LOOP_CAL_ALL(rad) { const float a = RADIANS(210 + (360 / NPP) * (rad - 1)), r = (rad == CEN ? 0.0f : dcr); - pos.set(cos(a) * r, sin(a) * r, z_pt[rad]); + pos.set(cosf(a) * r, sinf(a) * r, z_pt[rad]); inverse_kinematics(pos); mm_at_pt_axis[rad] = delta; } diff --git a/Marlin/src/gcode/calibrate/M48.cpp b/Marlin/src/gcode/calibrate/M48.cpp index 0c4355f5b107..036f978c504a 100644 --- a/Marlin/src/gcode/calibrate/M48.cpp +++ b/Marlin/src/gcode/calibrate/M48.cpp @@ -195,8 +195,8 @@ void GcodeSuite::M48() { // Choose the next position as an offset to chosen test position const xy_pos_t noz_pos = test_position - probe.offset_xy; xy_pos_t next_pos = { - noz_pos.x + float(cos(RADIANS(angle))) * radius, - noz_pos.y + float(sin(RADIANS(angle))) * radius + noz_pos.x + float(cosf(RADIANS(angle))) * radius, + noz_pos.y + float(sinf(RADIANS(angle))) * radius }; #if ENABLED(DELTA) diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index d4f74336de7d..d5cc7ac9ae53 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -104,6 +104,27 @@ relative_t GcodeSuite::axis_relative; // Init in constructor xyz_pos_t GcodeSuite::coordinate_system[MAX_COORDINATE_SYSTEMS]; #endif +#if ENABLED(SCALE_WORKSPACE) + GcodeSuite::scaling_center_t GcodeSuite::scaling_center; + GcodeSuite::scaling_factor_t GcodeSuite::scaling_factor; + bool GcodeSuite::scaling_flag = false; // true if scaling is active +#endif + +#if ENABLED(ROTATE_WORKSPACE) + float GcodeSuite::rotation_cos = 1.0f; + float GcodeSuite::rotation_sin = 0.0f; + float GcodeSuite::rotation_angle; // = 0.0f + xy_pos_t GcodeSuite::rotation_center; // = { 0.0f, 0.0f } + bool GcodeSuite::rotation_flag = false; // true if rotation is active + + void GcodeSuite::set_rotation_angle(const float angle) { + rotation_angle = angle; + const float angle_rad = RADIANS(rotation_angle); + rotation_cos = cosf(angle_rad); + rotation_sin = sinf(angle_rad); + } +#endif + void GcodeSuite::report_echo_start(const bool forReplay) { if (!forReplay) SERIAL_ECHO_START(); } void GcodeSuite::report_heading(const bool forReplay, FSTR_P const fstr, const bool eol/*=true*/) { if (forReplay) return; @@ -156,6 +177,54 @@ int8_t GcodeSuite::get_target_e_stepper_from_command(const int8_t dval/*=-1*/) { return -1; } +#if ANY(ROTATE_WORKSPACE, SCALE_WORKSPACE) + + // Apply inverse transformations + void GcodeSuite::inverse_workspace_transforms(xyz_pos_t &point) { + #if ENABLED(ROTATE_WORKSPACE) + // Inverse Rotation + if (rotation_flag) { + const float dx = point.x - rotation_center.x, dy = point.y - rotation_center.y; + point.x = rotation_center.x + dx * rotation_cos - dy * (-rotation_sin); + point.y = rotation_center.y + dx * (-rotation_sin) + dy * rotation_cos; + } + #endif + + #if ENABLED(SCALE_WORKSPACE) + // Inverse Scaling + if (scaling_flag) { + point.x = scaling_center.x + (point.x - scaling_center.x) / scaling_factor.x; + TERN_(HAS_Y_AXIS, point.y = scaling_center.y + (point.y - scaling_center.y) / scaling_factor.y); + #if HAS_Z_AXIS + point.z = TERN(SCALE_Z_FROM_NONZERO, scaling_center.z + (point.z - scaling_center.z), point.z) / scaling_factor.z; + #endif + } + #endif + } + + // Apply forward transformations + void GcodeSuite::apply_workspace_transforms(xyz_pos_t &point) { + #if ENABLED(SCALE_WORKSPACE) + // Apply Scaling transformation + if (scaling_flag) { + point.x = scaling_center.x + (point.x - scaling_center.x) * scaling_factor.x; + TERN_(HAS_Y_AXIS, point.y = scaling_center.y + (point.y - scaling_center.y) * scaling_factor.y); + TERN_(HAS_Z_AXIS, point.z = scaling_center.z + (point.z - scaling_center.z) * scaling_factor.z); + } + #endif + + #if ENABLED(ROTATE_WORKSPACE) + // Apply Rotation transformation + if (rotation_flag) { + const float dx = point.x - rotation_center.x, dy = point.y - rotation_center.y; + point.x = rotation_center.x + dx * rotation_cos - dy * rotation_sin; + point.y = rotation_center.y + dx * rotation_sin + dy * rotation_cos; + } + #endif + } + +#endif // ROTATE_WORKSPACE || SCALE_WORKSPACE + /** * Set XYZ...E destination and feedrate from the current G-Code command * @@ -165,6 +234,7 @@ int8_t GcodeSuite::get_target_e_stepper_from_command(const int8_t dval/*=-1*/) { */ void GcodeSuite::get_destination_from_command() { xyze_bool_t seen{false}; + xyze_pos_t raw_destination; #if ENABLED(CANCEL_OBJECTS) const bool &skip_move = cancelable.state.skipping; @@ -172,19 +242,32 @@ void GcodeSuite::get_destination_from_command() { constexpr bool skip_move = false; #endif + raw_destination = current_position; // Get the current position + + #if ANY(ROTATE_WORKSPACE, SCALE_WORKSPACE) + // Apply inverse transformations + if (TERN0(SCALE_WORKSPACE, scaling_flag) || TERN0(ROTATE_WORKSPACE, rotation_flag)) + inverse_workspace_transforms(raw_destination); + #endif + // Get new XYZ position, whether absolute or relative LOOP_NUM_AXES(i) { if ( (seen[i] = parser.seenval(AXIS_CHAR(i))) ) { - const float v = parser.value_axis_units((AxisEnum)i); - if (skip_move) - destination[i] = current_position[i]; - else - destination[i] = axis_is_relative(AxisEnum(i)) ? current_position[i] + v : LOGICAL_TO_NATIVE(v, i); + if (!skip_move) { + const float v = parser.value_axis_units((AxisEnum)i); + raw_destination[i] = axis_is_relative(AxisEnum(i)) ? raw_destination[i] + v : LOGICAL_TO_NATIVE(v, i); + } } - else - destination[i] = current_position[i]; } + destination = raw_destination; // Get the final machine destination + + #if ANY(SCALE_WORKSPACE, ROTATE_WORKSPACE) + // Apply forward transformations + if (TERN0(SCALE_WORKSPACE, scaling_flag) || TERN0(ROTATE_WORKSPACE, rotation_flag)) + apply_workspace_transforms(destination); + #endif + #if HAS_EXTRUDERS // Get new E position, whether absolute or relative if ( (seen.e = parser.seenval('E')) ) { @@ -213,8 +296,8 @@ void GcodeSuite::get_destination_from_command() { print_job_timer.incFilamentUsed(destination.e - current_position.e); #endif - // Get ABCDHI mixing factors #if ALL(MIXING_EXTRUDER, DIRECT_MIXING_IN_G1) + // Get ABCDHI mixing factors M165(); #endif @@ -436,6 +519,11 @@ void GcodeSuite::process_parsed_command(bool no_ok/*=false*/) { case 42: G42(); break; // G42: Coordinated move to a mesh point #endif + #if ENABLED(SCALE_WORKSPACE) + case 50: G50(); break; // G50: Cancel Workspace Scaling + case 51: G51(); break; // G51: Set Workspace Scaling + #endif + #if ENABLED(CNC_COORDINATE_SYSTEMS) case 53: G53(); break; // G53: (prefix) Apply native workspace case 54: G54(); break; // G54: Switch to Workspace 1 @@ -451,6 +539,11 @@ void GcodeSuite::process_parsed_command(bool no_ok/*=false*/) { case 61: G61(); break; // G61: Apply/restore saved coordinates. #endif + #if ENABLED(ROTATE_WORKSPACE) + case 68: G68(); break; // G68: Set Workspace Rotation + case 69: G69(); break; // G69: Cancel Workspace Rotation + #endif + #if ALL(PTC_PROBE, PTC_BED) case 76: G76(); break; // G76: Calibrate first layer compensation values #endif diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 87d626effce8..de2ef63050eb 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -64,8 +64,12 @@ * G35 - Read bed corners to help adjust bed screws: T (Requires ASSISTED_TRAMMING) * G38 - Probe in any direction using the Z_MIN_PROBE (Requires G38_PROBE_TARGET) * G42 - Coordinated move to a mesh point (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BLINEAR, or AUTO_BED_LEVELING_UBL) + * G50 - Cancel Workspace Scaling (Requires SCALE_WORKSPACE) + * G51 - Set Workspace Scaling (Requires SCALE_WORKSPACE) * G60 - Save current position. (Requires SAVED_POSITIONS) * G61 - Apply/Restore saved coordinates. (Requires SAVED_POSITIONS) + * G68 - Set Workspace Rotation (Requires ROTATE_WORKSPACE) + * G69 - Cancel Workspace Rotation (Requires ROTATE_WORKSPACE) * G76 - Calibrate first layer temperature offsets. (Requires PTC_PROBE and PTC_BED) * G80 - Cancel current motion mode (Requires GCODE_MOTION_MODES) * G90 - Use Absolute Coordinates @@ -432,6 +436,50 @@ class GcodeSuite { static bool select_coordinate_system(const int8_t _new); #endif + #if ANY(ROTATE_WORKSPACE, SCALE_WORKSPACE) + static void inverse_workspace_transforms(xyz_pos_t &point); + static void apply_workspace_transforms(xyz_pos_t &point); + + #if ENABLED(SCALE_WORKSPACE) + #if HAS_Z_AXIS && Z_MIN_POS != 0 + #define SCALE_Z_FROM_NONZERO 1 + #endif + typedef struct ScalingCenter { + float x = 0.0f; + #if HAS_Y_AXIS + float y = 0.0f; + #endif + #if SCALE_Z_FROM_NONZERO + float z = 0.0f; + #elif HAS_Z_AXIS + static constexpr float z = 0.0f; + #endif + void reset() { x = TERN_(HAS_Y_AXIS, y =) TERN_(SCALE_Z_FROM_NONZERO, z =) 0.0f; } + } scaling_center_t; + typedef struct ScalingFactor { + float x = 1.0f; + #if HAS_Y_AXIS + float y = 1.0f; + #endif + #if HAS_Z_AXIS + float z = 1.0f; + #endif + void reset() { x = TERN_(HAS_Y_AXIS, y =) TERN_(HAS_Z_AXIS, z =) 1.0f; } + } scaling_factor_t; + + static scaling_center_t scaling_center; + static scaling_factor_t scaling_factor; + static bool scaling_flag; + #endif + + #if ENABLED(ROTATE_WORKSPACE) + static float rotation_angle; + static xy_pos_t rotation_center; + static bool rotation_flag; + static void set_rotation_angle(const float angle); + #endif + #endif + static millis_t previous_move_ms, max_inactive_time; FORCE_INLINE static bool stepper_max_timed_out(const millis_t ms=millis()) { return max_inactive_time && ELAPSED(ms, previous_move_ms, max_inactive_time); @@ -593,11 +641,6 @@ class GcodeSuite { static void G34(); #endif - #if ENABLED(Z_STEPPER_AUTO_ALIGN) - static void M422(); - static void M422_report(const bool forReplay=true); - #endif - #if ENABLED(ASSISTED_TRAMMING) static void G35(); #endif @@ -610,6 +653,11 @@ class GcodeSuite { static void G42(); #endif + #if ENABLED(SCALE_WORKSPACE) + static void G50(); + static void G51(); + #endif + #if ENABLED(CNC_COORDINATE_SYSTEMS) static void G53(); static void G54(); @@ -620,15 +668,22 @@ class GcodeSuite { static void G59(); #endif - #if ALL(PTC_PROBE, PTC_BED) - static void G76(); - #endif - #if SAVED_POSITIONS static void G60(); static void G61(int8_t slot=-1); #endif + #if ENABLED(ROTATE_WORKSPACE) + static void G68(); + static void G69(); + static float rotation_cos; + static float rotation_sin; + #endif + + #if ALL(PTC_PROBE, PTC_BED) + static void G76(); + #endif + #if ENABLED(GCODE_MOTION_MODES) static void G80(); #endif @@ -779,9 +834,6 @@ class GcodeSuite { static void M108(); static void M112(); static void M410(); - #if ENABLED(HOST_PROMPT_SUPPORT) - static void M876(); - #endif #endif static void M110(); @@ -1075,6 +1127,11 @@ class GcodeSuite { static void M412_report(const bool forReplay=true); #endif + #if ENABLED(POWER_LOSS_RECOVERY) + static void M413(); + static void M413_report(const bool forReplay=true); + #endif + #if HAS_MULTI_LANGUAGE static void M414(); static void M414_report(const bool forReplay=true); @@ -1086,6 +1143,16 @@ class GcodeSuite { static void M421(); #endif + #if ENABLED(Z_STEPPER_AUTO_ALIGN) + static void M422(); + static void M422_report(const bool forReplay=true); + #endif + + #if ENABLED(X_AXIS_TWIST_COMPENSATION) + static void M423(); + static void M423_report(const bool forReplay=true); + #endif + #if ENABLED(BACKLASH_GCODE) static void M425(); static void M425_report(const bool forReplay=true); @@ -1213,6 +1280,11 @@ class GcodeSuite { static void MMU3_report(const bool forReplay=true); #endif + #if ENABLED(CONTROLLER_FAN_EDITABLE) + static void M710(); + static void M710_report(const bool forReplay=true); + #endif + #if ENABLED(GCODE_REPEAT_MARKERS) static void M808(); #endif @@ -1249,6 +1321,10 @@ class GcodeSuite { static void M871(); #endif + #if HAS_GCODE_M876 + static void M876(); + #endif + #if ENABLED(LIN_ADVANCE) static void M900(); static void M900_report(const bool forReplay=true); @@ -1319,16 +1395,9 @@ class GcodeSuite { static void M999(); #if ENABLED(POWER_LOSS_RECOVERY) - static void M413(); - static void M413_report(const bool forReplay=true); static void M1000(); #endif - #if ENABLED(X_AXIS_TWIST_COMPENSATION) - static void M423(); - static void M423_report(const bool forReplay=true); - #endif - #if HAS_MEDIA static void M1001(); #endif @@ -1353,11 +1422,6 @@ class GcodeSuite { static void M7219(); #endif - #if ENABLED(CONTROLLER_FAN_EDITABLE) - static void M710(); - static void M710_report(const bool forReplay=true); - #endif - static void T(const int8_t tool_index) IF_DISABLED(HAS_TOOLCHANGE, { UNUSED(tool_index); }); }; diff --git a/Marlin/src/gcode/geometry/G17-G19.cpp b/Marlin/src/gcode/geometry/G17-G19.cpp index 312b89286c27..0b5c4e8ff2d3 100644 --- a/Marlin/src/gcode/geometry/G17-G19.cpp +++ b/Marlin/src/gcode/geometry/G17-G19.cpp @@ -37,8 +37,13 @@ inline void report_workspace_plane() { } inline void set_workspace_plane(const GcodeSuite::WorkspacePlane plane) { - gcode.workspace_plane = plane; - if (DEBUGGING(INFO)) report_workspace_plane(); + if (TERN0(ROTATE_WORKSPACE, !NEAR_ZERO(gcode.rotation_angle))) { + SERIAL_ECHOLNPGM("Workspace plane cannnot change while using workspace rotation!"); + } + else { + gcode.workspace_plane = plane; + if (DEBUGGING(INFO)) report_workspace_plane(); + } } /** diff --git a/Marlin/src/gcode/geometry/G50_G51.cpp b/Marlin/src/gcode/geometry/G50_G51.cpp new file mode 100644 index 000000000000..a0161794ec17 --- /dev/null +++ b/Marlin/src/gcode/geometry/G50_G51.cpp @@ -0,0 +1,149 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(SCALE_WORKSPACE) + +#include "../gcode.h" +#include "../../module/motion.h" + +/** + * G50: Cancel Workspace Scaling + */ +void GcodeSuite::G50() { + scaling_factor.reset(); + scaling_center.reset(); + SERIAL_ECHO_MSG("G50: Workspace scaling canceled"); + scaling_flag = false; +} + +/** + * G51: Set Workspace Scaling + * + * Scale the current workspace coordinate system. + * + * Parameters: + * X X coordinate of the scaling center + * Y Y coordinate of the scaling center + * Z Z coordinate of the scaling center + * I scaling factor for X axis + * J scaling factor for Y axis + * K scaling factor for Z axis + * P scaling factor + * C Use current position for axes (X, Y, Z) + */ +void GcodeSuite::G51() { + bool use_current_pos = parser.seen('C'); // Check if 'C' parameter is present + + if (parser.seenval('P')) { + const float sf = parser.value_float(); + scaling_factor.x = sf; + TERN_(HAS_Y_AXIS, scaling_factor.y = sf); + TERN_(HAS_Z_AXIS, scaling_factor.z = sf); + SERIAL_ECHO_MSG("G51: Workspace scaling set to: ", sf); + } + else { + if (parser.seenval('I')) scaling_factor.x = parser.value_float(); + #if HAS_Y_AXIS + if (parser.seenval('J')) scaling_factor.y = parser.value_float(); + #endif + #if HAS_Z_AXIS + if (parser.seenval('K')) scaling_factor.z = parser.value_float(); + #endif + + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM_P( + PSTR("G51: Workspace scaling set to: X"), scaling_factor.x + #if HAS_Y_AXIS + , SP_Y_STR, scaling_factor.y + #endif + #if HAS_Z_AXIS + , SP_Z_STR, scaling_factor.z + #endif + ); + } + + rotation_center.x = X_CENTER; + TERN_(HAS_Y_AXIS, rotation_center.y = Y_CENTER); + + // X-axis scaling + const bool seenX = parser.seen('X'), hasX = seenX && parser.has_value(); + if (use_current_pos && hasX) { + SERIAL_ECHO_MSG("?(X) cannot have a value when used with 'C'."); + return; + } + scaling_center.x = ( + (use_current_pos && seenX) + ? current_position.x + : hasX + ? LOGICAL_TO_NATIVE(parser.value_axis_units(X_AXIS), X_AXIS) + : rotation_center.x + ); + + // Y-axis scaling + #if HAS_Y_AXIS + const bool seenY = parser.seen('Y'), hasY = seenY && parser.has_value(); + if (use_current_pos && hasY) { + SERIAL_ECHO_MSG("?(Y) cannot have a value when used with 'C'."); + return; + } + scaling_center.y = ( + (use_current_pos && seenY) + ? current_position.y + : hasY + ? LOGICAL_TO_NATIVE(parser.value_axis_units(Y_AXIS), Y_AXIS) + : rotation_center.y + ); + #endif + + // Z-axis scaling + #if SCALE_Z_FROM_NONZERO + const bool seenZ = parser.seen('Z'), hasZ = seenZ && parser.has_value(); + if (use_current_pos && hasZ) { + SERIAL_ECHO_MSG("?(Z) cannot have a value when used with 'C'."); + return; + } + scaling_center.z = ( + (use_current_pos && seenZ) + ? current_position.z + : hasZ + ? LOGICAL_TO_NATIVE(parser.value_axis_units(Z_AXIS), Z_AXIS) + : 0.0f + ); + #endif + + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM_P( + PSTR("Workspace scaling center X"), scaling_center.x + #if HAS_Y_AXIS + , SP_Y_STR, scaling_center.y + #endif + #if HAS_Z_AXIS + , SP_Z_STR, scaling_center.z + #endif + ); + + scaling_flag = true; // Set flag to true +} + +#endif // SCALE_WORKSPACE diff --git a/Marlin/src/gcode/geometry/G53-G59.cpp b/Marlin/src/gcode/geometry/G53-G59.cpp index 017b54a084a4..c4c8859b6bff 100644 --- a/Marlin/src/gcode/geometry/G53-G59.cpp +++ b/Marlin/src/gcode/geometry/G53-G59.cpp @@ -33,6 +33,10 @@ */ bool GcodeSuite::select_coordinate_system(const int8_t _new) { if (active_coordinate_system == _new) return false; + if (TERN0(ROTATE_WORKSPACE, gcode.rotation_angle)) { + SERIAL_ECHOLNPGM("Cannot change workspace while rotation is active!"); + return false; + } active_coordinate_system = _new; xyz_float_t new_offset{0}; if (WITHIN(_new, 0, MAX_COORDINATE_SYSTEMS - 1)) diff --git a/Marlin/src/gcode/geometry/G68_G69.cpp b/Marlin/src/gcode/geometry/G68_G69.cpp new file mode 100644 index 000000000000..8db09f1e7484 --- /dev/null +++ b/Marlin/src/gcode/geometry/G68_G69.cpp @@ -0,0 +1,88 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#include "../../inc/MarlinConfigPre.h" + +#if ENABLED(ROTATE_WORKSPACE) + +#include "../gcode.h" +#include "../../module/motion.h" + +//#define USE_45DEG_INCREMENTS // Allow 45-degree increments on square beds + +/** + * G68: Set Workspace Rotation + * + * Set the rotation (about Z axis) for the current workspace (begins at 0). + * + * Parameters: + * X X coordinate of the rotation center for the current workspace + * Y Y coordinate of the rotation center for the current workspace + * R Rotation angle in degrees (Required) + * + * Example: + * G68 R45 ; Rotate active workspace by 45° counter-clockwise (when viewed from positive Z) + * G68 R-30 ; Rotate active workspace by -30° + * G68 R180 ; Rotate active workspace by 180° + * + * NOTES: + * - Only rotation is set. No translation/offset is changed. + * - All subsequent moves are rotated by the specified angle. + * - It is an error to change workspace or working plane while workspace rotation is active + * (https://forums.autodesk.com/t5/fusion-manufacture-forum/probing-and-updating-wcs-for-angle/td-p/9487027 , + * https://www.machsupport.com/forum/index.php?topic=43012) + */ +void GcodeSuite::G68() { + if (!parser.seenval('R')) { + SERIAL_ECHO_MSG("?(R)otation angle is required."); + return; + } + const float input_deg = parser.value_float(); + + #if ENABLED(LIMIT_ROTATION_ANGLE) + // Check for a valid input angle + if ((ABS(input_deg) % TERN(USE_45DEG_INCREMENTS, 45, 90)) != 0) { + SERIAL_ECHO_MSG("?(R)otation must be a multiple of " TERN(USE_45DEG_INCREMENTS, "45", "90") "."); + return; + } + #endif + + gcode.set_rotation_angle(input_deg); + SERIAL_ECHO_MSG("G68: Rotation angle set to ", input_deg, " degrees."); + + // Assume the object rotates around the bed center + rotation_center.x = X_CENTER; + TERN_(HAS_Y_AXIS, rotation_center.y = Y_CENTER); + + rotation_flag = true; // Set flag to true +} + +/** + * G69: Cancel Workspace Rotation + */ +void GcodeSuite::G69() { + gcode.set_rotation_angle(0.0f); + SERIAL_ECHO_MSG("G68: Workspace rotation canceled"); + rotation_flag = false; +} + +#endif // ROTATE_WORKSPACE diff --git a/Marlin/src/gcode/motion/G0_G1.cpp b/Marlin/src/gcode/motion/G0_G1.cpp index b489b659aefc..498c3649740f 100644 --- a/Marlin/src/gcode/motion/G0_G1.cpp +++ b/Marlin/src/gcode/motion/G0_G1.cpp @@ -39,8 +39,6 @@ #include "../../lcd/sovol_rts/sovol_rts.h" #endif -extern xyze_pos_t destination; - #if ENABLED(VARIABLE_G0_FEEDRATE) feedRate_t fast_move_feedrate = MMM_TO_MMS(G0_FEEDRATE); #endif diff --git a/Marlin/src/gcode/motion/G2_G3.cpp b/Marlin/src/gcode/motion/G2_G3.cpp index e8051d2c2af6..c65056ad43bc 100644 --- a/Marlin/src/gcode/motion/G2_G3.cpp +++ b/Marlin/src/gcode/motion/G2_G3.cpp @@ -342,7 +342,7 @@ void plan_arc( // Compute exact location by applying transformation matrix from initial radius vector(=-offset). // To reduce stuttering, the sin and cos could be computed at different times. // For now, compute both at the same time. - const float Ti = i * theta_per_segment, cos_Ti = cos(Ti), sin_Ti = sin(Ti); + const float Ti = i * theta_per_segment, cos_Ti = cosf(Ti), sin_Ti = sinf(Ti); rvec.a = -offset[0] * cos_Ti + offset[1] * sin_Ti; rvec.b = -offset[0] * sin_Ti - offset[1] * cos_Ti; } diff --git a/Marlin/src/inc/Conditionals-5-post.h b/Marlin/src/inc/Conditionals-5-post.h index 6e5fa8de6c1d..b1875e429cdc 100644 --- a/Marlin/src/inc/Conditionals-5-post.h +++ b/Marlin/src/inc/Conditionals-5-post.h @@ -3249,8 +3249,8 @@ #define SKEW_FACTOR_MIN -1 #define SKEW_FACTOR_MAX 1 - #define _GET_SIDE(a,b,c) (SQRT(2*sq(a)+2*sq(b)-4*sq(c))*0.5) - #define _SKEW_SIDE(a,b,c) tan(M_PI*0.5-acos((sq(a)-sq(b)-sq(c))/(2*c*b))) + #define _GET_SIDE(a,b,c) (SQRT(2*sq(a)+2*sq(b)-4*sq(c))*0.5f) + #define _SKEW_SIDE(a,b,c) tanf(M_PI*0.5-acosf((sq(a)-sq(b)-sq(c))/(2*c*b))) #define _SKEW_FACTOR(a,b,c) _SKEW_SIDE(float(a),_GET_SIDE(float(a),float(b),float(c)),float(c)) #ifndef XY_SKEW_FACTOR diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index a8467ac60665..1b7aa9b5ae29 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -3781,6 +3781,10 @@ static_assert(_PLUS_TEST(3), "DEFAULT_MAX_ACCELERATION values must be positive." #error "CNC_COORDINATE_SYSTEMS is incompatible with NO_WORKSPACE_OFFSETS." #endif +#if ENABLED(ROTATE_WORKSPACE) && !HAS_Y_AXIS + #error "ROTATE_WORKSPACE requires a machine with XY axes." +#endif + #if !BLOCK_BUFFER_SIZE #error "BLOCK_BUFFER_SIZE must be non-zero." #elif BLOCK_BUFFER_SIZE > 64 diff --git a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/ftdi_eve_lib/extended/circular_progress.cpp b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/ftdi_eve_lib/extended/circular_progress.cpp index 7849b1e4616f..38c66b36da31 100644 --- a/Marlin/src/lcd/extui/ftdi_eve_touch_ui/ftdi_eve_lib/extended/circular_progress.cpp +++ b/Marlin/src/lcd/extui/ftdi_eve_touch_ui/ftdi_eve_lib/extended/circular_progress.cpp @@ -66,27 +66,27 @@ namespace FTDI { // Paint upper-right quadrant cmd.cmd(BEGIN(EDGE_STRIP_A)); cmd.cmd(VERTEX2F(cx, cy)); - cmd.cmd(VERTEX2F(cx + ro*sin(a1) + 16,cy - ro*cos(a1) + 8)); + cmd.cmd(VERTEX2F(cx + ro * sinf(a1) + 16, cy - ro * cosf(a1) + 8)); // Paint lower-right quadrant if (a > M_PI/2) { cmd.cmd(BEGIN(EDGE_STRIP_R)); cmd.cmd(VERTEX2F(cx, cy)); - cmd.cmd(VERTEX2F(cx + ro*cos(a2),cy + ro*sin(a2) + 16)); + cmd.cmd(VERTEX2F(cx + ro * cosf(a2), cy + ro * sinf(a2) + 16)); } // Paint lower-left quadrant if (a > M_PI) { cmd.cmd(BEGIN(EDGE_STRIP_B)); cmd.cmd(VERTEX2F(cx, cy)); - cmd.cmd(VERTEX2F(cx - ro*sin(a3) - 8,cy + ro*cos(a3))); + cmd.cmd(VERTEX2F(cx - ro * sinf(a3) - 8, cy + ro * cosf(a3))); } // Paint upper-left quadrant if (a > 1.5*M_PI) { cmd.cmd(BEGIN(EDGE_STRIP_L)); cmd.cmd(VERTEX2F(cx, cy)); - cmd.cmd(VERTEX2F(cx - ro*cos(a4),cy - ro*sin(a4))); + cmd.cmd(VERTEX2F(cx - ro * cosf(a4), cy - ro * sinf(a4))); } cmd.cmd(RESTORE_CONTEXT()); diff --git a/Marlin/src/lcd/menu/menu_delta_calibrate.cpp b/Marlin/src/lcd/menu/menu_delta_calibrate.cpp index 5fae531e4a5c..348c4f58693c 100644 --- a/Marlin/src/lcd/menu/menu_delta_calibrate.cpp +++ b/Marlin/src/lcd/menu/menu_delta_calibrate.cpp @@ -92,7 +92,7 @@ void _man_probe_pt(const xy_pos_t &xy) { float dcr = PRINTABLE_RADIUS - PROBING_MARGIN; TERN_(HAS_PROBE_XY_OFFSET, dcr -= HYPOT(probe.offset_xy.x, probe.offset_xy.y)); 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 = { cosf(RADIANS(a)), sinf(RADIANS(a)) }; _man_probe_pt(tower_vec * dcr); } void _goto_tower_x() { _goto_tower_a(210); } diff --git a/Marlin/src/libs/nozzle.cpp b/Marlin/src/libs/nozzle.cpp index 0f92bac81253..76fb9ba36173 100644 --- a/Marlin/src/libs/nozzle.cpp +++ b/Marlin/src/libs/nozzle.cpp @@ -144,8 +144,8 @@ Nozzle nozzle; for (uint8_t s = 0; s < strokes; ++s) for (uint8_t i = 0; i < NOZZLE_CLEAN_CIRCLE_FN; ++i) do_blocking_move_to_xy( - middle.x + sin((RADIANS(360) / NOZZLE_CLEAN_CIRCLE_FN) * i) * radius, - middle.y + cos((RADIANS(360) / NOZZLE_CLEAN_CIRCLE_FN) * i) * radius + middle.x + sinf((RADIANS(360) / NOZZLE_CLEAN_CIRCLE_FN) * i) * radius, + middle.y + cosf((RADIANS(360) / NOZZLE_CLEAN_CIRCLE_FN) * i) * radius ); // Let's be safe diff --git a/Marlin/src/module/delta.cpp b/Marlin/src/module/delta.cpp index 2f0415babc9a..17edd78abc70 100644 --- a/Marlin/src/module/delta.cpp +++ b/Marlin/src/module/delta.cpp @@ -76,12 +76,12 @@ void refresh_delta_clip_start_height() { */ void recalc_delta_settings() { constexpr abc_float_t trt = DELTA_RADIUS_TRIM_TOWER; - delta_tower[A_AXIS].set(cos(RADIANS(210 + delta_tower_angle_trim.a)) * (delta_radius + trt.a), // front left tower - sin(RADIANS(210 + delta_tower_angle_trim.a)) * (delta_radius + trt.a)); - delta_tower[B_AXIS].set(cos(RADIANS(330 + delta_tower_angle_trim.b)) * (delta_radius + trt.b), // front right tower - sin(RADIANS(330 + delta_tower_angle_trim.b)) * (delta_radius + trt.b)); - delta_tower[C_AXIS].set(cos(RADIANS( 90 + delta_tower_angle_trim.c)) * (delta_radius + trt.c), // back middle tower - sin(RADIANS( 90 + delta_tower_angle_trim.c)) * (delta_radius + trt.c)); + delta_tower[A_AXIS].set(cosf(RADIANS(210 + delta_tower_angle_trim.a)) * (delta_radius + trt.a), // front left tower + sinf(RADIANS(210 + delta_tower_angle_trim.a)) * (delta_radius + trt.a)); + delta_tower[B_AXIS].set(cosf(RADIANS(330 + delta_tower_angle_trim.b)) * (delta_radius + trt.b), // front right tower + sinf(RADIANS(330 + delta_tower_angle_trim.b)) * (delta_radius + trt.b)); + delta_tower[C_AXIS].set(cosf(RADIANS( 90 + delta_tower_angle_trim.c)) * (delta_radius + trt.c), // back middle tower + sinf(RADIANS( 90 + delta_tower_angle_trim.c)) * (delta_radius + trt.c)); delta_diagonal_rod_2_tower.set(sq(delta_diagonal_rod + delta_diagonal_rod_trim.a), sq(delta_diagonal_rod + delta_diagonal_rod_trim.b), sq(delta_diagonal_rod + delta_diagonal_rod_trim.c)); diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp index 1282e605eba7..f96f1afc6e00 100644 --- a/Marlin/src/module/motion.cpp +++ b/Marlin/src/module/motion.cpp @@ -760,16 +760,18 @@ void get_cartesian_from_steppers() { 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 - NUM_AXIS_CODE( - cartes.x = planner.get_axis_position_mm(X_AXIS), - cartes.y = planner.get_axis_position_mm(Y_AXIS), - cartes.z = planner.get_axis_position_mm(Z_AXIS), - cartes.i = planner.get_axis_position_mm(I_AXIS), - cartes.j = planner.get_axis_position_mm(J_AXIS), - cartes.k = planner.get_axis_position_mm(K_AXIS), - cartes.u = planner.get_axis_position_mm(U_AXIS), - cartes.v = planner.get_axis_position_mm(V_AXIS), - cartes.w = planner.get_axis_position_mm(W_AXIS) + cartes.set( + NUM_AXIS_LIST( + planner.get_axis_position_mm(X_AXIS), + planner.get_axis_position_mm(Y_AXIS), + planner.get_axis_position_mm(Z_AXIS), + planner.get_axis_position_mm(I_AXIS), + planner.get_axis_position_mm(J_AXIS), + planner.get_axis_position_mm(K_AXIS), + planner.get_axis_position_mm(U_AXIS), + planner.get_axis_position_mm(V_AXIS), + planner.get_axis_position_mm(W_AXIS) + ) ); #endif } diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp index 99df08205dc2..68ff2af506ef 100644 --- a/Marlin/src/module/planner.cpp +++ b/Marlin/src/module/planner.cpp @@ -2627,9 +2627,9 @@ bool Planner::_populate_block( * constexpr float c = 1.00751495f; // Correction factor to center error around 0 * for (int i = 0; i < jd_lut_count - 1; ++i) { * const float x0 = (sq(i) - 1) / sq(i), - * y0 = acos(x0) * (i == 0 ? 1 : c), + * y0 = acosf(x0) * (i == 0 ? 1 : c), * x1 = i < jd_lut_count - 1 ? 0.5 * x0 + 0.5 : 0.999999f, - * y1 = acos(x1) * (i < jd_lut_count - 1 ? c : 1); + * y1 = acosf(x1) * (i < jd_lut_count - 1 ? c : 1); * jd_lut_k[i] = (y0 - y1) / (x0 - x1); * jd_lut_b[i] = (y1 * x0 - y0 * x1) / (x0 - x1); * } @@ -2637,7 +2637,7 @@ bool Planner::_populate_block( * // Compute correction factor (Set c to 1.0f first!) * float min = INFINITY, max = -min; * for (float t = 0; t <= 1; t += 0.0003f) { - * const float e = acos(t) / approx(t); + * const float e = acosf(t) / approx(t); * if (isfinite(e)) { * if (e < min) min = e; * if (e > max) max = e; diff --git a/Marlin/src/module/polar.cpp b/Marlin/src/module/polar.cpp index fb35ae9f58ff..6e8066bed7f9 100644 --- a/Marlin/src/module/polar.cpp +++ b/Marlin/src/module/polar.cpp @@ -58,8 +58,7 @@ void forward_kinematics(const float r, const float 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; + cartes.set(cosf(RADIANS(absTheta)) * radius, sinf(RADIANS(absTheta)) * radius); } void inverse_kinematics(const xyz_pos_t &raw) { diff --git a/Marlin/src/module/scara.cpp b/Marlin/src/module/scara.cpp index d6016d77a1b9..8ca7aaf8c160 100644 --- a/Marlin/src/module/scara.cpp +++ b/Marlin/src/module/scara.cpp @@ -49,10 +49,10 @@ float segments_per_second = DEFAULT_SEGMENTS_PER_SECOND; * Integrated into Marlin and slightly restructured by Joachim Cerny. */ void forward_kinematics(const float a, const float b) { - const float a_sin = sin(RADIANS(a)) * L1, - a_cos = cos(RADIANS(a)) * L1, - b_sin = sin(RADIANS(SUM_TERN(MP_SCARA, b, a))) * L2, - b_cos = cos(RADIANS(SUM_TERN(MP_SCARA, b, a))) * L2; + const float a_sin = sinf(RADIANS(a)) * L1, + a_cos = cosf(RADIANS(a)) * L1, + b_sin = sinf(RADIANS(SUM_TERN(MP_SCARA, b, a))) * L2, + b_cos = cosf(RADIANS(SUM_TERN(MP_SCARA, b, a))) * L2; cartes.x = a_cos + b_cos + scara_offset.x; // theta cartes.y = a_sin + b_sin + scara_offset.y; // phi @@ -199,10 +199,10 @@ float segments_per_second = DEFAULT_SEGMENTS_PER_SECOND; // Convert ABC inputs in degrees to XYZ outputs in mm void forward_kinematics(const float a, const float b, const float c) { const float w = c - b, - r = L1 * cos(RADIANS(b)) + L2 * sin(RADIANS(w - (90 - b))), - x = r * cos(RADIANS(a)), - y = r * sin(RADIANS(a)), - rho2 = L1_2 + L2_2 - 2.0f * L1 * L2 * cos(RADIANS(w)); + r = L1 * cosf(RADIANS(b)) + L2 * sinf(RADIANS(w - (90 - b))), + x = r * cosf(RADIANS(a)), + y = r * sinf(RADIANS(a)), + rho2 = L1_2 + L2_2 - 2.0f * L1 * L2 * cosf(RADIANS(w)); cartes = robot_offset + xyz_pos_t({ x, y, SQRT(rho2 - sq(x) - sq(y)) }); } @@ -285,7 +285,7 @@ float segments_per_second = DEFAULT_SEGMENTS_PER_SECOND; GAMMA = ATAN2(SG, CG), // Method 2 // Angle of Shoulder Joint, elevation angle measured from horizontal (r+) - //PHI = asin(spos.z/RHO) + asin(L2 * sin(GAMMA) / RHO), + //PHI = asinf(spos.z/RHO) + asinf(L2 * sinf(GAMMA) / RHO), PHI = ATAN2(spos.z, RXY) + ATAN2(K2, K1), // Method 2 // Elbow motor angle measured from horizontal, same frame as shoulder (r+) diff --git a/buildroot/tests/mega2560 b/buildroot/tests/mega2560 index d7a149063c0b..21d2be5dbbf7 100755 --- a/buildroot/tests/mega2560 +++ b/buildroot/tests/mega2560 @@ -68,7 +68,7 @@ opt_enable VIKI2 BOOT_MARLIN_LOGO_ANIMATED SDSUPPORT AUTO_REPORT_SD_STATUS \ AUTO_BED_LEVELING_3POINT DEBUG_LEVELING_FEATURE PROBE_PT_1 PROBE_PT_2 PROBE_PT_3 \ EEPROM_SETTINGS EEPROM_CHITCHAT M114_DETAIL AUTO_REPORT_POSITION \ NO_VOLUMETRICS EXTENDED_CAPABILITIES_REPORT AUTO_REPORT_TEMPERATURES AUTOTEMP G38_PROBE_TARGET JOYSTICK \ - DIRECT_STEPPING DETECT_BROKEN_ENDSTOP \ + DIRECT_STEPPING DETECT_BROKEN_ENDSTOP SCALE_WORKSPACE ROTATE_WORKSPACE \ FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE Z_SAFE_HOMING FIL_RUNOUT3_PULLUP exec_test $1 $2 "Azteeg X3 Pro | EXTRUDERS 4 | VIKI2 | Servo Probe | Multiple runout sensors (x4)" "$3" diff --git a/ini/features.ini b/ini/features.ini index c3fc6df53780..c02979e287c2 100644 --- a/ini/features.ini +++ b/ini/features.ini @@ -324,6 +324,8 @@ FILAMENT_LOAD_UNLOAD_GCODES = build_src_filter=+ CNC_WORKSPACE_PLANES = build_src_filter=+ CNC_COORDINATE_SYSTEMS = build_src_filter=+ +SCALE_WORKSPACE = build_src_filter=+ +ROTATE_WORKSPACE = build_src_filter=+ HAS_HOME_OFFSET = build_src_filter=+ EXPECTED_PRINTER_CHECK = build_src_filter=+ HOST_KEEPALIVE_FEATURE = build_src_filter=+