Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 9 additions & 45 deletions carveracontroller/Controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
38 changes: 38 additions & 0 deletions tests/unit/test_resume_feed_rate.py
Original file line number Diff line number Diff line change
@@ -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
Loading