This commit is contained in:
Andrew 2025-12-20 22:46:46 -06:00 committed by GitHub
commit 940d2bf4a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 538 additions and 115 deletions

View file

@ -2339,22 +2339,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
/**
@ -3699,6 +3683,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
*
@ -3966,6 +3977,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
*
@ -3975,16 +4000,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
/**

View file

@ -711,7 +711,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

View file

@ -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;
}

View file

@ -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)

View file

@ -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
#if ENABLED(GCODE_MACROS)
char GcodeSuite::macros[GCODE_MACROS_SLOTS][GCODE_MACROS_SLOT_SIZE + 1] = {{ 0 }};
#endif
@ -160,6 +181,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
*
@ -169,6 +238,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;
@ -176,19 +246,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')) ) {
@ -217,8 +300,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
@ -440,6 +523,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
@ -455,6 +543,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

View file

@ -64,8 +64,12 @@
* G35 - Read bed corners to help adjust bed screws: T<screw_thread> (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
@ -438,6 +442,50 @@ public:
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);
@ -604,11 +652,6 @@ private:
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
@ -621,6 +664,11 @@ private:
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();
@ -631,15 +679,22 @@ private:
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
@ -790,14 +845,9 @@ private:
#endif
static void M108();
static void M112();
static void M410();
#if ENABLED(HOST_PROMPT_SUPPORT)
static void M876();
#endif
static void M110();
static void M111();
static void M112();
#if ENABLED(HOST_KEEPALIVE_FEATURE)
static void M113();
@ -1082,11 +1132,18 @@ private:
static void M407();
#endif
static void M410();
#if HAS_FILAMENT_SENSOR
static void M412();
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);
@ -1098,6 +1155,16 @@ private:
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);
@ -1230,6 +1297,11 @@ private:
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
@ -1267,6 +1339,10 @@ private:
static void M871();
#endif
#if HAS_GCODE_M876
static void M876();
#endif
#if HAS_LIN_ADVANCE_K
static void M900();
static void M900_report(const bool forReplay=true);
@ -1337,16 +1413,9 @@ private:
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
@ -1371,11 +1440,6 @@ private:
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); });
};

View file

@ -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();
}
}
/**

View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
#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<linear> X coordinate of the scaling center
* Y<linear> Y coordinate of the scaling center
* Z<linear> Z coordinate of the scaling center
* I<float> scaling factor for X axis
* J<float> scaling factor for Y axis
* K<float> scaling factor for Z axis
* P<float> scaling factor
* C<bool> 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

View file

@ -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))

View file

@ -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 <https://www.gnu.org/licenses/>.
*
*/
#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<linear> X coordinate of the rotation center for the current workspace
* Y<linear> Y coordinate of the rotation center for the current workspace
* R<float> 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

View file

@ -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

View file

@ -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;
}

View file

@ -3265,8 +3265,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

View file

@ -3765,6 +3765,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

View file

@ -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());

View file

@ -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); }

View file

@ -143,8 +143,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

View file

@ -75,12 +75,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));

View file

@ -774,16 +774,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
}

View file

@ -2591,9 +2591,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);
* }
@ -2601,7 +2601,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;

View file

@ -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) {

View file

@ -48,10 +48,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
@ -273,10 +273,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));
const xyz_pos_t calculated_fk = xyz_pos_t({ x, y, SQRT(rho2 - sq(x) - sq(y)) }) ;
cartes = calculated_fk + robot_shoulder_offset + tool_offset - robot_workspace_offset;
@ -380,8 +380,8 @@ float segments_per_second = DEFAULT_SEGMENTS_PER_SECOND;
GAMMA = ATAN2(SG, CG), // Method 2
// Angle of Shoulder Joint, elevation angle measured from horizontal plane XY (r+)
//PHI = asin(tpos.z/RHO) + asin(L2 * sin(GAMMA) / RHO), // Method 1
PHI = ATAN2(tpos.z, RXY) + ATAN2(K2, K1), // Method 2
//PHI = asinf(tpos.z/RHO) + asinf(L2 * sin(GAMMA) / RHO), // Method 1
PHI = ATAN2(tpos.z, RXY) + ATAN2(K2, K1), // Method 2
// Elbow motor angle measured from horizontal, same reference frame as shoulder angle (r+)
PSI = PHI + GAMMA;

View file

@ -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 PROBE_WAKEUP_TIME_MS \
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"

View file

@ -328,6 +328,8 @@ FILAMENT_LOAD_UNLOAD_GCODES = build_src_filter=+<src/gcode/feature/pa
HAS_STEALTHCHOP = build_src_filter=+<src/gcode/feature/trinamic/M569.cpp>
CNC_WORKSPACE_PLANES = build_src_filter=+<src/gcode/geometry/G17-G19.cpp>
CNC_COORDINATE_SYSTEMS = build_src_filter=+<src/gcode/geometry/G53-G59.cpp>
SCALE_WORKSPACE = build_src_filter=+<src/gcode/geometry/G50_G51.cpp>
ROTATE_WORKSPACE = build_src_filter=+<src/gcode/geometry/G68_G69.cpp>
HAS_HOME_OFFSET = build_src_filter=+<src/gcode/geometry/M206_M428.cpp>
EXPECTED_PRINTER_CHECK = build_src_filter=+<src/gcode/host/M16.cpp>
HOST_KEEPALIVE_FEATURE = build_src_filter=+<src/gcode/host/M113.cpp>