CAM: Fixed F parameter handling for only rotary axes

with tests
      and the commit for converting the refactored*
      postprocessors to the new API
This commit is contained in:
Lawrence Woestman
2025-02-10 10:35:25 -08:00
committed by Chris Hennes
parent 68e934ba18
commit b9c4bee4e3
2 changed files with 489 additions and 4 deletions

View File

@@ -1771,3 +1771,423 @@ G90
f"B{b_inches_output_expected} C{c_inches_output_expected}",
"--inches",
)
#############################################################################
def test19120(self):
"""Test F parameter handling."""
#
# Note that FreeCAD internal units are (mm or degrees) per second
# and postprocessor outputs are usually in (mm or degrees) per minute
#
axes_test_values = (
# test using all possible axes, all same, then all changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X20 Y30 Z10 A70 B80 C90 U40 V50 W60 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X20.000 Y30.000 Z10.000 A70.000 B80.000 C90.000 U40.000 V50.000 W60.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F74.0736
G1 X0.7874 Y1.1811 Z0.3937 A70.0000 B80.0000 C90.0000 U1.5748 V1.9685 W2.3622 F2.9163
""",
),
# test using all possible axes, just X changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X20 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X20.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.7874 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
""",
),
# test using all possible axes, just Y changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y30 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y30.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y1.1811 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
""",
),
# test using all possible axes, just Z changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y20 Z10 A40 B50 C60 U70 V80 W90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y20.000 Z10.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y0.7874 Z0.3937 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
""",
),
# test using all possible axes, just A changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y20 Z30 A70 B50 C60 U70 V80 W90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y20.000 Z30.000 A70.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 A70.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F74.0736
""",
),
# test using all possible axes, just B changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y20 Z30 A40 B80 C60 U70 V80 W90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y20.000 Z30.000 A40.000 B80.000 C60.000 U70.000 V80.000 W90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B80.0000 C60.0000 U2.7559 V3.1496 W3.5433 F74.0736
""",
),
# test using all possible axes, just C changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y20 Z30 A40 B50 C90 U70 V80 W90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C90.000 U70.000 V80.000 W90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C90.0000 U2.7559 V3.1496 W3.5433 F74.0736
""",
),
# test using all possible axes, just U changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U40 V80 W90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U40.000 V80.000 W90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U1.5748 V3.1496 W3.5433 F2.9163
""",
),
# test using all possible axes, just V changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V50 W90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V50.000 W90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V1.9685 W3.5433 F2.9163
""",
),
# test using all possible axes, just W changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 U70 V80 W60 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 U70.000 V80.000 W60.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W2.3622 F2.9163
""",
),
# test using just XYZ, all same, then all changing
(
[
Path.Command("G1 X10 Y20 Z30 F1.23456"),
Path.Command("G1 X10 Y20 Z30 F1.23456"),
Path.Command("G1 X20 Y30 Z10 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 F74.074
G1 X10.000 Y20.000 Z30.000 F74.074
G1 X20.000 Y30.000 Z10.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 F2.9163
G1 X0.7874 Y1.1811 Z0.3937 F2.9163
""",
),
# test using just UVW, all same, then all changing
(
[
Path.Command("G1 U70 V80 W90 F1.23456"),
Path.Command("G1 U70 V80 W90 F1.23456"),
Path.Command("G1 U40 V50 W60 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 U70.000 V80.000 W90.000 F74.074
G1 U70.000 V80.000 W90.000 F74.074
G1 U40.000 V50.000 W60.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 U2.7559 V3.1496 W3.5433 F2.9163
G1 U2.7559 V3.1496 W3.5433 F2.9163
G1 U1.5748 V1.9685 W2.3622 F2.9163
""",
),
# test using just ABC, which should not convert the feed rate for --inches
(
[
Path.Command("G1 A40 B50 C60 F1.23456"),
Path.Command("G1 A40 B50 C60 F1.23456"),
Path.Command("G1 A70 B80 C90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 A40.000 B50.000 C60.000 F74.074
G1 A40.000 B50.000 C60.000 F74.074
G1 A70.000 B80.000 C90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 A40.0000 B50.0000 C60.0000 F74.0736
G1 A40.0000 B50.0000 C60.0000 F74.0736
G1 A70.0000 B80.0000 C90.0000 F74.0736
""",
),
# test using XYZ and ABC, all same, then all changing
(
[
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 F1.23456"),
Path.Command("G1 X10 Y20 Z30 A40 B50 C60 F1.23456"),
Path.Command("G1 X20 Y30 Z10 A70 B80 C90 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 F74.074
G1 X10.000 Y20.000 Z30.000 A40.000 B50.000 C60.000 F74.074
G1 X20.000 Y30.000 Z10.000 A70.000 B80.000 C90.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 F2.9163
G1 X0.3937 Y0.7874 Z1.1811 A40.0000 B50.0000 C60.0000 F74.0736
G1 X0.7874 Y1.1811 Z0.3937 A70.0000 B80.0000 C90.0000 F2.9163
""",
),
# test using ABC and UVW, all same, then all changing
(
[
Path.Command("G1 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 A40 B50 C60 U70 V80 W90 F1.23456"),
Path.Command("G1 A70 B80 C90 U40 V50 W60 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 A40.000 B50.000 C60.000 U70.000 V80.000 W90.000 F74.074
G1 A70.000 B80.000 C90.000 U40.000 V50.000 W60.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F2.9163
G1 A40.0000 B50.0000 C60.0000 U2.7559 V3.1496 W3.5433 F74.0736
G1 A70.0000 B80.0000 C90.0000 U1.5748 V1.9685 W2.3622 F2.9163
""",
),
# test using X and A only, both same, then both changing
(
[
Path.Command("G1 X10 A40 F1.23456"),
Path.Command("G1 X10 A40 F1.23456"),
Path.Command("G1 X20 A70 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 X10.000 A40.000 F74.074
G1 X10.000 A40.000 F74.074
G1 X20.000 A70.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 X0.3937 A40.0000 F2.9163
G1 X0.3937 A40.0000 F74.0736
G1 X0.7874 A70.0000 F2.9163
""",
),
# test using A and U only, both same, then both changing
(
[
Path.Command("G1 A40 U70 F1.23456"),
Path.Command("G1 A40 U70 F1.23456"),
Path.Command("G1 A70 U40 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 A40.000 U70.000 F74.074
G1 A40.000 U70.000 F74.074
G1 A70.000 U40.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 A40.0000 U2.7559 F2.9163
G1 A40.0000 U2.7559 F74.0736
G1 A70.0000 U1.5748 F2.9163
""",
),
# test using A only, which should not convert the feed rate for --inches
(
[
Path.Command("G1 A40 F1.23456"),
Path.Command("G1 A40 F1.23456"),
Path.Command("G1 A70 F1.23456"),
],
"""G90
G21
G54
M6 T1
G1 A40.000 F74.074
G1 A40.000 F74.074
G1 A70.000 F74.074
""",
"""G90
G20
G54
M6 T1
G1 A40.0000 F74.0736
G1 A40.0000 F74.0736
G1 A70.0000 F74.0736
""",
),
)
for input_value, metric_output_expected, inches_output_expected in axes_test_values:
with self.subTest("F feed speed test", input_value=input_value):
self.multi_compare(input_value, metric_output_expected, "")
self.multi_compare(input_value, inches_output_expected, "--inches")

View File

@@ -28,6 +28,7 @@
# * *
# ***************************************************************************
import math
import re
from typing import Any, Callable, Dict, List, Tuple, Union
@@ -208,13 +209,19 @@ def default_axis_parameter(
command: str, # pylint: disable=unused-argument
param: str,
param_value: PathParameter,
parameters: PathParameters, # pylint: disable=unused-argument
current_location: PathParameters,
) -> str:
"""Process an axis parameter."""
#
# used to compare two floating point numbers for "close-enough equality"
#
epsilon: float = 0.00001
if (
not values["OUTPUT_DOUBLES"]
and param in current_location
and current_location[param] == param_value
and math.fabs(current_location[param] - param_value) < epsilon
):
return ""
return format_for_axis(values, Units.Quantity(param_value, Units.Length))
@@ -225,6 +232,7 @@ def default_D_parameter(
command: str,
param: str, # pylint: disable=unused-argument
param_value: PathParameter,
parameters: PathParameters, # pylint: disable=unused-argument
current_location: PathParameters, # pylint: disable=unused-argument
) -> str:
"""Process the D parameter."""
@@ -243,13 +251,20 @@ def default_F_parameter(
command: str,
param: str,
param_value: PathParameter,
parameters: PathParameters,
current_location: PathParameters,
) -> str:
"""Process the F parameter."""
#
# used to compare two floating point numbers for "close-enough equality"
#
epsilon: float = 0.00001
found: bool
if (
not values["OUTPUT_DOUBLES"]
and param in current_location
and current_location[param] == param_value
and math.fabs(current_location[param] - param_value) < epsilon
):
return ""
# Many posts don't use rapid speeds, but eventually
@@ -261,6 +276,26 @@ def default_F_parameter(
feed = Units.Quantity(param_value, Units.Velocity)
if feed.getValueAs(values["UNIT_SPEED_FORMAT"]) <= 0.0:
return ""
# if any of X, Y, Z, U, V, or W are in the parameters
# and any of their values is different than where the device currently should be
# then feed is in linear units
found = False
for key in ("X", "Y", "Z", "U", "V", "W"):
if key in parameters and math.fabs(current_location[key] - parameters[key]) > epsilon:
found = True
if found:
return format_for_feed(values, feed)
# else if any of A, B, or C are in the paramters, the feed is in degrees,
# which should not be converted when in --inches mode
found = False
for key in ("A", "B", "C"):
if key in parameters:
found = True
if found:
# converting from degrees per second to degrees per minute as well
return format(float(feed * 60.0), f'.{str(values["FEED_PRECISION"])}f')
# which leaves none of X, Y, Z, U, V, W, A, B, C,
# which should not be valid but return a converted value just in case
return format_for_feed(values, feed)
@@ -269,6 +304,7 @@ def default_int_parameter(
command: str, # pylint: disable=unused-argument
param: str, # pylint: disable=unused-argument
param_value: PathParameter,
parameters: PathParameters, # pylint: disable=unused-argument
current_location: PathParameters, # pylint: disable=unused-argument
) -> str:
"""Process a parameter that is treated like an integer."""
@@ -280,6 +316,7 @@ def default_length_parameter(
command: str, # pylint: disable=unused-argument
param: str, # pylint: disable=unused-argument
param_value: PathParameter,
parameters: PathParameters, # pylint: disable=unused-argument
current_location: PathParameters, # pylint: disable=unused-argument
) -> str:
"""Process a parameter that is treated like a length."""
@@ -291,6 +328,7 @@ def default_P_parameter(
command: str,
param: str, # pylint: disable=unused-argument
param_value: PathParameter,
parameters: PathParameters, # pylint: disable=unused-argument
current_location: PathParameters, # pylint: disable=unused-argument
) -> str:
"""Process the P parameter."""
@@ -309,6 +347,7 @@ def default_Q_parameter(
command: str,
param: str, # pylint: disable=unused-argument
param_value: PathParameter,
parameters: PathParameters, # pylint: disable=unused-argument
current_location: PathParameters, # pylint: disable=unused-argument
) -> str:
"""Process the Q parameter."""
@@ -324,13 +363,19 @@ def default_rotary_parameter(
command: str, # pylint: disable=unused-argument
param: str,
param_value: PathParameter,
parameters: PathParameters, # pylint: disable=unused-argument
current_location: PathParameters,
) -> str:
"""Process a rotarty parameter (such as A, B, and C)."""
#
# used to compare two floating point numbers for "close-enough equality"
#
epsilon: float = 0.00001
if (
not values["OUTPUT_DOUBLES"]
and param in current_location
and current_location[param] == param_value
and math.fabs(current_location[param] - param_value) < epsilon
):
return ""
# unlike other axis, rotary axis such as A, B, and C are always in degrees
@@ -343,6 +388,7 @@ def default_S_parameter(
command: str, # pylint: disable=unused-argument
param: str, # pylint: disable=unused-argument
param_value: PathParameter,
parameters: PathParameters, # pylint: disable=unused-argument
current_location: PathParameters, # pylint: disable=unused-argument
) -> str:
"""Process the S parameter."""
@@ -662,7 +708,25 @@ def parse_a_path(values: Values, gcode: Gcode, pathobj) -> None:
swap_tool_change_order = False
if "TOOL_BEFORE_CHANGE" in values and values["TOOL_BEFORE_CHANGE"]:
swap_tool_change_order = True
current_location.update(Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0}).Parameters)
current_location.update(
# the goal is to have initial values that aren't likely to match
# any "real" first parameter values
Path.Command(
"G0",
{
"X": 123456789.0,
"Y": 123456789.0,
"Z": 123456789.0,
"U": 123456789.0,
"V": 123456789.0,
"W": 123456789.0,
"A": 123456789.0,
"B": 123456789.0,
"C": 123456789.0,
"F": 123456789.0,
},
).Parameters
)
adaptive_op_variables = determine_adaptive_op(values, pathobj)
for c in pathobj.Path.Commands:
@@ -692,6 +756,7 @@ def parse_a_path(values: Values, gcode: Gcode, pathobj) -> None:
command,
parameter,
c.Parameters[parameter],
c.Parameters,
current_location,
)
if parameter_value: