diff --git a/CHANGELOG.md b/CHANGELOG.md index 603ca7b9..d9f41ec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Enhancement: Advanced TLO Calibration option. Here you can set the offset to use from tool setter, and/or the number of repeat probings to use - Enhancement: Added a warning popup if the controller version is lower than the firmware. This is not a supported config - Enhancement: Added Auto Ext. Out toggle to spindle dropdown and Config and Run screen. This can be used to automatically run a vacuum or compressor when the spindle is running +- Fixed: Resume-at-line restores feed rates from standalone and tightly packed F words before recovery moves - Fixed: Restore Keyboard Jogging state after Probing Popup is closed - Fixed: Repeated firmware checks now happen just once - Fixed: UI widget updates from the SerialMonitor() now dispatched via the main thread. This should reduce the number of RecycleView related crashes diff --git a/carveracontroller/Controller.py b/carveracontroller/Controller.py index cabedd3a..867e1628 100644 --- a/carveracontroller/Controller.py +++ b/carveracontroller/Controller.py @@ -882,52 +882,16 @@ def _find_last_feed_rate(self, lines=None, start_line=None, feed_lookup=None): if start_line < 1 or start_line > len(lines): return None - # Search backwards for G1/G01/G2/G02/G3/G03 commands + # F is modal independently of a motion command, so the most recent + # feed word may be on a standalone or tightly packed G-code line. for i in range(start_line - 2, -1, -1): - original_line = lines[i] - line = original_line.strip() - - # Remove comments using string methods - if ";" in line: - line = line[: line.index(";")] - # Remove parentheses comments using string methods - while "(" in line and ")" in line: - start_paren = line.index("(") - end_paren = line.index(")", start_paren) - line = line[:start_paren] + line[end_paren + 1 :] - - line = line.strip() - if not line: - continue - - # Check if line contains G1/G01/G2/G02/G3/G03 - line_upper = line.upper() - # Check for G1/G01/G2/G02/G3/G03 commands - # These can appear at start of line or anywhere in the line - g_commands = ["G1", "G01", "G2", "G02", "G3", "G03"] - found_g_command = False - for cmd in g_commands: - # Check if command appears at start - if line_upper.startswith(cmd): - found_g_command = True - break - # Check if command appears in the middle of line (preceded by non-alphanumeric) - cmd_pos = line_upper.find(cmd) - if cmd_pos > 0 and not line_upper[cmd_pos - 1].isalnum(): - # Make sure it's not followed by a digit (to avoid matching G10, G20, etc.) - if cmd_pos + len(cmd) >= len(line_upper) or not line_upper[cmd_pos + len(cmd)].isdigit(): - found_g_command = True - break - - if found_g_command: - # Extract F parameter using regex - f_match = re.search(r"F(\d+\.?\d*)", line) - if f_match: - try: - feed_rate = float(f_match.group(1)) - return feed_rate - except (ValueError, TypeError): - continue + for token in reversed(self._gcode_line_to_cmd_tokens(lines[i])): + if token[:1].upper() != "F": + continue + try: + return float(token[1:]) + except (ValueError, TypeError): + continue return None diff --git a/tests/unit/test_resume_feed_rate.py b/tests/unit/test_resume_feed_rate.py new file mode 100644 index 00000000..6acc690a --- /dev/null +++ b/tests/unit/test_resume_feed_rate.py @@ -0,0 +1,38 @@ +"""Tests for resume-at-line feed-rate recovery.""" + +import pytest + +from carveracontroller.Controller import Controller + + +@pytest.mark.parametrize( + ("lines", "expected"), + ( + (["G1 X0 F100\n", "F275\n", "G0 X1\n", "G1 X2\n"], 275.0), + (["G1 X0 F100\n", "X10Y10G1F425\n", "G28\n", "G1 X2\n"], 425.0), + (["G1 X0 F100\n", "G1 X1 ; F900\n", "G0 X2\n", "G1 X3\n"], 100.0), + (["G1 X0 F100\n", "G1 X1 (F900)\n", "G0 X2\n", "G1 X3\n"], 100.0), + ), +) +def test_recovers_feed_from_standalone_and_tightly_packed_words(lines, expected): + controller = Controller.__new__(Controller) + + assert controller._find_last_feed_rate(lines, 4) == expected + + +def test_restores_feed_before_recovery_z_move(): + controller = Controller.__new__(Controller) + controller._get_line_position_from_gcode_viewer = lambda _line: (1.0, 2.0, -3.0, None) + lines = [ + "G55\n", + "G1 X0 F100\n", + "F275\n", + "G1 X1\n", + "G1 X2\n", + ] + + commands = controller.playStartLineCommand("job.nc", 5, preview=True, lines=lines) + + feed_index = commands.index("buffer G1 F275") + z_index = commands.index("buffer G1 Z-3.000") + assert feed_index < z_index