392 lines
19 KiB
Python
392 lines
19 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Unit tests for the Path.Tool.Shape module and its utilities.
|
|
|
|
from pathlib import Path
|
|
from typing import Mapping, Tuple
|
|
import FreeCAD
|
|
from CAMTests.PathTestUtils import PathTestWithAssets
|
|
from Path.Tool.assets import DummyAssetSerializer
|
|
from Path.Tool.shape import (
|
|
ToolBitShape,
|
|
ToolBitShapeBallend,
|
|
ToolBitShapeVBit,
|
|
ToolBitShapeBullnose,
|
|
ToolBitShapeSlittingSaw,
|
|
)
|
|
|
|
|
|
# Helper dummy class for testing abstract methods
|
|
class DummyShape(ToolBitShape):
|
|
name = "dummy"
|
|
|
|
def __init__(self, id, **kwargs):
|
|
super().__init__(id=id, **kwargs)
|
|
# Always define defaults in the subclass
|
|
self._defaults = {
|
|
"Param1": FreeCAD.Units.Quantity("10 mm"),
|
|
"Param2": FreeCAD.Units.Quantity("5 deg"),
|
|
}
|
|
# Merge defaults into _params, allowing kwargs to override
|
|
self._params = self._defaults | self._params
|
|
|
|
@classmethod
|
|
def schema(cls) -> Mapping[str, Tuple[str, str]]:
|
|
return {
|
|
"Param1": (
|
|
FreeCAD.Qt.translate("Param1", "Parameter 1"),
|
|
"App::PropertyLength",
|
|
),
|
|
"Param2": (
|
|
FreeCAD.Qt.translate("Param2", "Parameter 2"),
|
|
"App::PropertyAngle",
|
|
),
|
|
}
|
|
|
|
@property
|
|
def label(self):
|
|
return "Dummy Shape"
|
|
|
|
|
|
def unit(param):
|
|
return param.getUserPreferred()[2]
|
|
|
|
|
|
class TestPathToolShapeClasses(PathTestWithAssets):
|
|
"""Tests for the concrete ToolBitShape subclasses."""
|
|
|
|
def _test_shape_common(self, alias):
|
|
uri = ToolBitShape.resolve_name(alias)
|
|
shape = self.assets.get(uri)
|
|
return shape.get_parameters()
|
|
|
|
def test_base_init_with_defaults(self):
|
|
"""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")
|
|
|
|
def test_base_init_with_kwargs(self):
|
|
"""Test base class initialization overrides defaults with kwargs."""
|
|
# Provide a dummy filepath for instantiation
|
|
shape = DummyShape(
|
|
id="dummy_shape_2",
|
|
filepath=Path("/fake/dummy.fcstd"),
|
|
Param1=FreeCAD.Units.Quantity("20 mm"),
|
|
Param3="Ignored",
|
|
)
|
|
self.assertEqual(shape.get_parameter("Param1").Value, 20.0)
|
|
self.assertEqual(shape.get_parameter("Param1").Value, 20.0)
|
|
self.assertEqual(
|
|
str(shape.get_parameter("Param1").Unit),
|
|
"Unit: mm (1,0,0,0,0,0,0,0) [Length]",
|
|
)
|
|
self.assertEqual(shape.get_parameter("Param2").Value, 5.0)
|
|
self.assertEqual(
|
|
str(shape.get_parameter("Param2").Unit),
|
|
"Unit: deg (0,0,0,0,0,0,0,1) [Angle]",
|
|
) # Should remain default
|
|
|
|
def test_base_get_set_parameter(self):
|
|
"""Test getting and setting individual parameters."""
|
|
# Provide a dummy filepath for instantiation
|
|
shape = DummyShape(id="dummy_shape_3", filepath=Path("/fake/dummy.fcstd"))
|
|
self.assertEqual(shape.get_parameter("Param1").Value, 10.0)
|
|
self.assertEqual(shape.get_parameter("Param1").Unit, FreeCAD.Units.Unit("mm"))
|
|
shape.set_parameter("Param1", FreeCAD.Units.Quantity("15 mm"))
|
|
self.assertEqual(shape.get_parameter("Param1").Value, 15.0)
|
|
self.assertEqual(shape.get_parameter("Param1").Unit, FreeCAD.Units.Unit("mm"))
|
|
with self.assertRaisesRegex(KeyError, "Shape 'dummy' has no parameter 'InvalidParam'"):
|
|
shape.get_parameter("InvalidParam")
|
|
|
|
def test_base_get_parameters(self):
|
|
"""Test getting the full parameter dictionary."""
|
|
# Provide a dummy filepath for instantiation
|
|
shape = DummyShape(
|
|
id="dummy_shape_4",
|
|
filepath=Path("/fake/dummy.fcstd"),
|
|
Param1=FreeCAD.Units.Quantity("12 mm"),
|
|
)
|
|
# Create mock quantity instances using the configured mock class
|
|
expected_param1 = FreeCAD.Units.Quantity("12.0 mm")
|
|
expected_param2 = FreeCAD.Units.Quantity("5.0 deg")
|
|
|
|
expected = {"Param1": expected_param1, "Param2": expected_param2}
|
|
params = shape.get_parameters()
|
|
self.assertEqual(params["Param1"].Value, expected["Param1"].Value)
|
|
self.assertEqual(str(params["Param1"].Unit), str(expected["Param1"].Unit))
|
|
self.assertEqual(params["Param2"].Value, expected["Param2"].Value)
|
|
self.assertEqual(str(params["Param2"].Unit), str(expected["Param2"].Unit))
|
|
|
|
def test_base_name_property(self):
|
|
"""Test the name property returns the primary alias."""
|
|
# Provide a dummy filepath for instantiation
|
|
shape = DummyShape(id="dummy_shape_5", filepath=Path("/fake/dummy.fcstd"))
|
|
self.assertEqual(shape.name, "dummy")
|
|
|
|
def test_base_get_parameter_label(self):
|
|
"""Test retrieving parameter labels."""
|
|
# Provide a dummy filepath for instantiation
|
|
shape = DummyShape(id="dummy_shape_6", filepath=Path("/fake/dummy.fcstd"))
|
|
self.assertEqual(shape.get_parameter_label("Param1"), "Parameter 1")
|
|
self.assertEqual(shape.get_parameter_label("Param2"), "Parameter 2")
|
|
# Test fallback for unknown parameter
|
|
self.assertEqual(shape.get_parameter_label("UnknownParam"), "UnknownParam")
|
|
|
|
def test_base_get_expected_shape_parameters(self):
|
|
"""Test retrieving the list of expected parameter names."""
|
|
expected = ["Param1", "Param2"]
|
|
self.assertCountEqual(DummyShape.get_expected_shape_parameters(), expected)
|
|
|
|
def test_base_str_repr(self):
|
|
"""Test string representation."""
|
|
# Provide a dummy filepath for instantiation
|
|
shape = DummyShape(id="dummy_shape_7", filepath=Path("/fake/dummy.fcstd"))
|
|
# Dynamically construct the expected string using the actual parameter string representations
|
|
params_str = ", ".join(f"{name}={str(val)}" for name, val in shape.get_parameters().items())
|
|
expected_str = f"dummy({params_str})"
|
|
self.assertEqual(str(shape), expected_str)
|
|
self.assertEqual(repr(shape), expected_str)
|
|
|
|
def test_base_resolve_name(self):
|
|
"""Test resolving shape aliases to canonical names."""
|
|
self.assertEqual(ToolBitShape.resolve_name("ballend").asset_id, "ballend")
|
|
self.assertEqual(ToolBitShape.resolve_name("Ballend").asset_id, "ballend")
|
|
self.assertEqual(ToolBitShape.resolve_name("v-bit").asset_id, "vbit")
|
|
self.assertEqual(ToolBitShape.resolve_name("VBit").asset_id, "vbit")
|
|
self.assertEqual(ToolBitShape.resolve_name("torus").asset_id, "bullnose")
|
|
self.assertEqual(ToolBitShape.resolve_name("bullnose").asset_id, "bullnose")
|
|
self.assertEqual(ToolBitShape.resolve_name("slitting-saw").asset_id, "slittingsaw")
|
|
self.assertEqual(ToolBitShape.resolve_name("SlittingSaw").asset_id, "slittingsaw")
|
|
# Test unknown name - should return the input name
|
|
self.assertEqual(ToolBitShape.resolve_name("nonexistent").asset_id, "nonexistent")
|
|
self.assertEqual(ToolBitShape.resolve_name("UnknownShape").asset_id, "UnknownShape")
|
|
|
|
def test_concrete_classes_instantiation(self):
|
|
"""Test that all concrete classes can be instantiated."""
|
|
# No patching of FreeCAD document operations here.
|
|
# The test relies on the actual FreeCAD environment.
|
|
|
|
shape_uris = self.assets.list_assets(asset_type="toolbitshape")
|
|
for uri in shape_uris:
|
|
# Skip the DummyShape asset if it exists
|
|
if uri.asset_id == "dummy":
|
|
continue
|
|
|
|
with self.subTest(uri=uri):
|
|
instance = self.assets.get(uri)
|
|
self.assertIsInstance(instance, ToolBitShape)
|
|
# Check if default params were set by checking if the
|
|
# parameters dictionary is not empty.
|
|
self.assertTrue(instance.get_parameters())
|
|
|
|
def test_get_shape_class(self):
|
|
"""Test the get_shape_class function."""
|
|
uri = ToolBitShape.resolve_name("ballend")
|
|
self.assets.get(uri) # Ensure it's loadable
|
|
|
|
self.assertEqual(ToolBitShape.get_subclass_by_name("ballend"), ToolBitShapeBallend)
|
|
self.assertEqual(ToolBitShape.get_subclass_by_name("v-bit"), ToolBitShapeVBit)
|
|
self.assertEqual(ToolBitShape.get_subclass_by_name("VBit"), ToolBitShapeVBit)
|
|
self.assertEqual(ToolBitShape.get_subclass_by_name("torus"), ToolBitShapeBullnose)
|
|
self.assertEqual(ToolBitShape.get_subclass_by_name("slitting-saw"), ToolBitShapeSlittingSaw)
|
|
self.assertIsNone(ToolBitShape.get_subclass_by_name("nonexistent"))
|
|
|
|
# The following tests for default parameters and labels
|
|
# should also not use mocks for FreeCAD document operations or Units.
|
|
# They should rely on the actual FreeCAD environment and the
|
|
# load_file method of the base class.
|
|
|
|
def test_toolbitshapeballend_defaults(self):
|
|
"""Test ToolBitShapeBallend default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
# The actual file content is not loaded in this test,
|
|
# only the default parameters are checked.
|
|
shape = self._test_shape_common("ballend")
|
|
self.assertEqual(shape["Diameter"].Value, 5.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["Length"].Value, 50.0)
|
|
self.assertEqual(unit(shape["Length"]), "mm")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("ballend")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("Diameter"), "Diameter")
|
|
self.assertEqual(instance.get_parameter_label("Length"), "Overall tool length")
|
|
|
|
def test_toolbitshapedrill_defaults(self):
|
|
"""Test ToolBitShapeDrill default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("drill")
|
|
self.assertEqual(shape["Diameter"].Value, 3.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["TipAngle"].Value, 119.0)
|
|
self.assertEqual(unit(shape["TipAngle"]), "°")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("drill")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("Diameter"), "Diameter")
|
|
self.assertEqual(instance.get_parameter_label("TipAngle"), "Tip angle")
|
|
|
|
def test_toolbitshapechamfer_defaults(self):
|
|
"""Test ToolBitShapeChamfer default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("chamfer")
|
|
self.assertEqual(shape["Diameter"].Value, 12.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["CuttingEdgeAngle"].Value, 60.0)
|
|
self.assertEqual(unit(shape["CuttingEdgeAngle"]), "°")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("chamfer")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("Diameter"), "Diameter")
|
|
|
|
def test_toolbitshapedovetail_defaults(self):
|
|
"""Test ToolBitShapeDovetail default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("dovetail")
|
|
self.assertEqual(shape["Diameter"].Value, 20.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["CuttingEdgeAngle"].Value, 60.0)
|
|
self.assertEqual(unit(shape["CuttingEdgeAngle"]), "°")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("dovetail")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("CuttingEdgeAngle"), "Cutting angle")
|
|
|
|
def test_toolbitshapeendmill_defaults(self):
|
|
"""Test ToolBitShapeEndmill default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("endmill")
|
|
self.assertEqual(shape["Diameter"].Value, 5.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["CuttingEdgeHeight"].Value, 30.0)
|
|
self.assertEqual(unit(shape["CuttingEdgeHeight"]), "mm")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("endmill")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("CuttingEdgeHeight"), "Cutting edge height")
|
|
|
|
def test_toolbitshapeprobe_defaults(self):
|
|
"""Test ToolBitShapeProbe default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("probe")
|
|
self.assertEqual(shape["Diameter"].Value, 6.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["ShaftDiameter"].Value, 4.0)
|
|
self.assertEqual(unit(shape["ShaftDiameter"]), "mm")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("probe")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("Diameter"), "Ball diameter")
|
|
self.assertEqual(instance.get_parameter_label("ShaftDiameter"), "Shaft diameter")
|
|
|
|
def test_toolbitshapereamer_defaults(self):
|
|
"""Test ToolBitShapeReamer default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("reamer")
|
|
self.assertEqual(shape["Diameter"].Value, 5.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["Length"].Value, 50.0)
|
|
self.assertEqual(unit(shape["Length"]), "mm")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("reamer")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("Diameter"), "Diameter")
|
|
|
|
def test_toolbitshapeslittingsaw_defaults(self):
|
|
"""Test ToolBitShapeSlittingSaw default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("slittingsaw")
|
|
self.assertEqual(shape["Diameter"].Value, 100.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["BladeThickness"].Value, 3.0)
|
|
self.assertEqual(unit(shape["BladeThickness"]), "mm")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("slittingsaw")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("BladeThickness"), "Blade thickness")
|
|
|
|
def test_toolbitshapetap_defaults(self):
|
|
"""Test ToolBitShapeTap default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("tap")
|
|
self.assertAlmostEqual(shape["Diameter"].Value, 8, 4)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["TipAngle"].Value, 90.0)
|
|
self.assertEqual(unit(shape["TipAngle"]), "°")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("tap")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("TipAngle"), "Tip angle")
|
|
|
|
def test_toolbitshapethreadmill_defaults(self):
|
|
"""Test ToolBitShapeThreadMill default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("threadmill")
|
|
self.assertEqual(shape["Diameter"].Value, 5.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["cuttingAngle"].Value, 60.0)
|
|
self.assertEqual(unit(shape["cuttingAngle"]), "°")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("threadmill")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("cuttingAngle"), "Cutting angle")
|
|
|
|
def test_toolbitshapebullnose_defaults(self):
|
|
"""Test ToolBitShapeBullnose default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("bullnose")
|
|
self.assertEqual(shape["Diameter"].Value, 5.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["FlatRadius"].Value, 1.5)
|
|
self.assertEqual(unit(shape["FlatRadius"]), "mm")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("bullnose")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("FlatRadius"), "Torus radius")
|
|
|
|
def test_toolbitshapevbit_defaults(self):
|
|
"""Test ToolBitShapeVBit default parameters and labels."""
|
|
# Provide a dummy filepath for instantiation.
|
|
shape = self._test_shape_common("vbit")
|
|
self.assertEqual(shape["Diameter"].Value, 10.0)
|
|
self.assertEqual(unit(shape["Diameter"]), "mm")
|
|
self.assertEqual(shape["CuttingEdgeAngle"].Value, 90.0)
|
|
self.assertEqual(unit(shape["CuttingEdgeAngle"]), "°")
|
|
self.assertEqual(shape["TipDiameter"].Value, 1.0)
|
|
self.assertEqual(unit(shape["TipDiameter"]), "mm")
|
|
# Need an instance to get parameter labels, get it from the asset manager
|
|
uri = ToolBitShape.resolve_name("vbit")
|
|
instance = self.assets.get(uri)
|
|
self.assertEqual(instance.get_parameter_label("CuttingEdgeAngle"), "Cutting edge angle")
|
|
|
|
def test_serialize_deserialize(self):
|
|
"""
|
|
Tests serialization and deserialization of a ToolBitShape object
|
|
using the Asset interface methods.
|
|
"""
|
|
# Load a shape instance from a fixture file
|
|
fixture_path = (
|
|
Path(__file__).parent / "Tools" / "Shape" / "test-path-tool-bit-shape-00.fcstd"
|
|
)
|
|
original_shape = ToolBitShape.from_file(fixture_path)
|
|
|
|
# Serialize the shape using the to_bytes method
|
|
serialized_data = original_shape.to_bytes(DummyAssetSerializer)
|
|
|
|
# Assert that the serialized data is bytes and not empty
|
|
self.assertIsInstance(serialized_data, bytes)
|
|
self.assertTrue(len(serialized_data) > 0)
|
|
|
|
# Deserialize the data using the from_bytes classmethod
|
|
# Provide an empty dependencies mapping for this test
|
|
deserialized_shape = ToolBitShape.from_bytes(
|
|
serialized_data, original_shape.get_id(), {}, DummyAssetSerializer
|
|
)
|
|
|
|
# Assert that the deserialized object is a ToolBitShape instance
|
|
self.assertIsInstance(deserialized_shape, ToolBitShape)
|
|
# Assert that the deserialized shape has the same parameters as the original
|
|
self.assertEqual(original_shape.get_parameters(), deserialized_shape.get_parameters())
|
|
self.assertEqual(original_shape.name, deserialized_shape.name)
|