From 9cab1f8f527a2cc61507a24eca9705f2b2755f8f Mon Sep 17 00:00:00 2001 From: Billy Huddleston Date: Wed, 19 Nov 2025 21:26:05 -0500 Subject: [PATCH] CAM: Make CAM tests and serialization robust to locale-dependent decimal separators Updated CAM unit tests and LinuxCNC serializer to handle decimal separators consistently, ensuring tests pass regardless of system locale. Assertions now compare FreeCAD.Units.Quantity objects directly or normalize decimal separators in strings. LinuxCNC serializer output is forced to use periods for decimals. src/Mod/CAM/CAMTests/TestPathToolBitListWidget.py: - Normalize decimal separators in tool description assertions for locale robustness. src/Mod/CAM/CAMTests/TestPathToolBitPropertyEditorWidget.py: - Use FreeCAD.Units.Quantity for direct quantity comparisons in property editor tests. src/Mod/CAM/CAMTests/TestPathToolBitSerializer.py: - Compare deserialized and serialized quantities using FreeCAD.Units.Quantity for consistency. src/Mod/CAM/CAMTests/TestPathToolShapeClasses.py: - Compare parameter values and units directly instead of relying on string formatting. src/Mod/CAM/Path/Tool/library/serializers/linuxcnc.py: - Force period as decimal separator in LinuxCNC serializer output to avoid locale issues. --- .../CAM/CAMTests/TestPathToolBitListWidget.py | 3 +- .../TestPathToolBitPropertyEditorWidget.py | 6 ++- .../CAM/CAMTests/TestPathToolBitSerializer.py | 44 ++++++++++++++----- .../CAM/CAMTests/TestPathToolShapeClasses.py | 6 ++- .../Path/Tool/library/serializers/linuxcnc.py | 6 +-- 5 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/Mod/CAM/CAMTests/TestPathToolBitListWidget.py b/src/Mod/CAM/CAMTests/TestPathToolBitListWidget.py index 13e0e4e9c1..e22f9055c1 100644 --- a/src/Mod/CAM/CAMTests/TestPathToolBitListWidget.py +++ b/src/Mod/CAM/CAMTests/TestPathToolBitListWidget.py @@ -56,7 +56,8 @@ class TestToolBitListWidget(PathTestWithAssets): self.assertEqual(cell_widget.tool_no, str(tool_no)) self.assertEqual(cell_widget.upper_text, toolbit.label) # Assuming the 5mm_Endmill asset has a shape named 'Endmill' - self.assertEqual(cell_widget.lower_text, "5.00 mm 4-flute endmill, 30.00 mm cutting edge") + normalized_lower_text = cell_widget.lower_text.replace(",00 ", ".00 ") + self.assertEqual(normalized_lower_text, "5.00 mm 4-flute endmill, 30.00 mm cutting edge") # Verify URI is stored in item data stored_uri = item.data(ToolBitUriRole) diff --git a/src/Mod/CAM/CAMTests/TestPathToolBitPropertyEditorWidget.py b/src/Mod/CAM/CAMTests/TestPathToolBitPropertyEditorWidget.py index 1b02dd1f9c..a241c44c15 100644 --- a/src/Mod/CAM/CAMTests/TestPathToolBitPropertyEditorWidget.py +++ b/src/Mod/CAM/CAMTests/TestPathToolBitPropertyEditorWidget.py @@ -100,13 +100,15 @@ class TestQuantityPropertyEditorWidget(unittest.TestCase): # Check if the real widget's value is updated self.assertEqual(self.editor.property("rawValue"), 10.0) # Check if the real widget's value and unit are updated - self.assertEqual(self.editor.property("value").UserString, "10.00 mm") + self.assertEqual( + self.editor.property("value"), FreeCAD.Units.Quantity(10.0, FreeCAD.Units.Length) + ) # Simulate changing the raw value and check if the object's value updates self.editor.lineEdit().setText("12.0") self.widget.updateProperty() self.assertEqual(self.obj.Length.Value, 12.0) - self.assertEqual(self.obj.Length.UserString, "12.00 mm") + self.assertEqual(self.obj.Length, FreeCAD.Units.Quantity(12.0, FreeCAD.Units.Length)) # Try assignment with unit. self.editor.lineEdit().setText("15.5 in") diff --git a/src/Mod/CAM/CAMTests/TestPathToolBitSerializer.py b/src/Mod/CAM/CAMTests/TestPathToolBitSerializer.py index 2f7fce5060..0f9f95755b 100644 --- a/src/Mod/CAM/CAMTests/TestPathToolBitSerializer.py +++ b/src/Mod/CAM/CAMTests/TestPathToolBitSerializer.py @@ -84,8 +84,12 @@ class TestCamoticsToolBitSerializer(_BaseToolBitSerializerTestCase): self.assertIsInstance(deserialized_bit, ToolBit) self.assertEqual(deserialized_bit.label, "Test Tool") - self.assertEqual(str(deserialized_bit.get_diameter()), "4.12 mm") - self.assertEqual(str(deserialized_bit.get_length()), "15.0 mm") + self.assertEqual( + deserialized_bit.get_diameter(), FreeCAD.Units.Quantity(4.12, FreeCAD.Units.Length) + ) + self.assertEqual( + deserialized_bit.get_length(), FreeCAD.Units.Quantity(15.0, FreeCAD.Units.Length) + ) self.assertEqual(deserialized_bit.get_shape_name(), "Endmill") @@ -99,8 +103,14 @@ class TestFCTBSerializer(_BaseToolBitSerializerTestCase): data = json.loads(serialized_data.decode("utf-8")) self.assertEqual(data.get("name"), "Test Tool") self.assertEqual(data.get("shape"), "endmill.fcstd") - self.assertEqual(data.get("parameter", {}).get("Diameter"), "4.12 mm") - self.assertEqual(data.get("parameter", {}).get("Length"), "15.00 mm", data) + self.assertEqual( + FreeCAD.Units.Quantity(data.get("parameter", {}).get("Diameter")), + FreeCAD.Units.Quantity(4.12, FreeCAD.Units.Length), + ) + self.assertEqual( + FreeCAD.Units.Quantity(data.get("parameter", {}).get("Length")), + FreeCAD.Units.Quantity(15.0, FreeCAD.Units.Length), + ) def test_extract_dependencies(self): """Test dependency extraction for FCTB.""" @@ -134,8 +144,12 @@ class TestFCTBSerializer(_BaseToolBitSerializerTestCase): self.assertIsInstance(deserialized_bit, ToolBit) self.assertEqual(deserialized_bit.label, "Test Tool") self.assertEqual(deserialized_bit.get_shape_name(), "Endmill") - self.assertEqual(str(deserialized_bit.get_diameter()), "4.12 mm") - self.assertEqual(str(deserialized_bit.get_length()), "15.0 mm") + self.assertEqual( + deserialized_bit.get_diameter(), FreeCAD.Units.Quantity(4.12, FreeCAD.Units.Length) + ) + self.assertEqual( + deserialized_bit.get_length(), FreeCAD.Units.Quantity(15.0, FreeCAD.Units.Length) + ) class TestYamlToolBitSerializer(_BaseToolBitSerializerTestCase): @@ -150,8 +164,14 @@ class TestYamlToolBitSerializer(_BaseToolBitSerializerTestCase): self.assertEqual(data.get("name"), "Test Tool") self.assertEqual(data.get("shape"), "endmill.fcstd") self.assertEqual(data.get("shape-type"), "Endmill") - self.assertEqual(data.get("parameter", {}).get("Diameter"), "4.12 mm") - self.assertEqual(data.get("parameter", {}).get("Length"), "15.00 mm") + self.assertEqual( + FreeCAD.Units.Quantity(data.get("parameter", {}).get("Diameter")), + FreeCAD.Units.Quantity(4.12, FreeCAD.Units.Length), + ) + self.assertEqual( + FreeCAD.Units.Quantity(data.get("parameter", {}).get("Length")), + FreeCAD.Units.Quantity(15.0, FreeCAD.Units.Length), + ) def test_extract_dependencies(self): """Test dependency extraction for YAML.""" @@ -196,8 +216,12 @@ class TestYamlToolBitSerializer(_BaseToolBitSerializerTestCase): self.assertEqual(deserialized_bit.id, "TestID") self.assertEqual(deserialized_bit.label, "Test Tool") self.assertEqual(deserialized_bit.get_shape_name(), "Endmill") - self.assertEqual(str(deserialized_bit.get_diameter()), "4.12 mm") - self.assertEqual(str(deserialized_bit.get_length()), "15.0 mm") + self.assertEqual( + deserialized_bit.get_diameter(), FreeCAD.Units.Quantity(4.12, FreeCAD.Units.Length) + ) + self.assertEqual( + deserialized_bit.get_length(), FreeCAD.Units.Quantity(15.0, FreeCAD.Units.Length) + ) # Test with ID argument. deserialized_bit = cast( diff --git a/src/Mod/CAM/CAMTests/TestPathToolShapeClasses.py b/src/Mod/CAM/CAMTests/TestPathToolShapeClasses.py index 0d2c97eb2e..54977d19e6 100644 --- a/src/Mod/CAM/CAMTests/TestPathToolShapeClasses.py +++ b/src/Mod/CAM/CAMTests/TestPathToolShapeClasses.py @@ -64,8 +64,10 @@ class TestPathToolShapeClasses(PathTestWithAssets): """Test base class initialization uses default parameters.""" # Provide a dummy filepath and id for instantiation shape = DummyShape(id="dummy_shape_1", filepath=Path("/fake/dummy.fcstd")) - self.assertEqual(str(shape.get_parameter("Param1")), "10.0 mm") - self.assertEqual(str(shape.get_parameter("Param2")), "5.0 deg") + self.assertEqual(shape.get_parameter("Param1").Value, 10.0) + self.assertEqual(shape.get_parameter("Param1").Unit, FreeCAD.Units.Unit("mm")) + self.assertEqual(shape.get_parameter("Param2").Value, 5.0) + self.assertEqual(shape.get_parameter("Param2").Unit, FreeCAD.Units.Unit("deg")) def test_base_init_with_kwargs(self): """Test base class initialization overrides defaults with kwargs.""" diff --git a/src/Mod/CAM/Path/Tool/library/serializers/linuxcnc.py b/src/Mod/CAM/Path/Tool/library/serializers/linuxcnc.py index 8f9e2aa10a..d6d22776b7 100644 --- a/src/Mod/CAM/Path/Tool/library/serializers/linuxcnc.py +++ b/src/Mod/CAM/Path/Tool/library/serializers/linuxcnc.py @@ -65,11 +65,11 @@ class LinuxCNCSerializer(AssetSerializer): # for a metric or imperial machine # Using user preferred for now if hasattr(diameter, "Value"): - diameter_value = diameter.Value + diameter_value = str(diameter.Value).replace(",", ".") elif isinstance(diameter, str): - diameter_value = diameter.split(" ")[0] + diameter_value = diameter.split(" ")[0].replace(",", ".") else: - diameter_value = diameter + diameter_value = str(diameter).replace(",", ".") line = ( f"T{bit_no} {pocket} X0 Y0 Z0 A0 B0 C0 U0 V0 W0 " f"D{diameter_value} I0 J0 Q0 ;{bit.label}\n"