diff --git a/doc/changelog.d/2096.test.md b/doc/changelog.d/2096.test.md new file mode 100644 index 0000000000..705588f5b0 --- /dev/null +++ b/doc/changelog.d/2096.test.md @@ -0,0 +1 @@ +Add more code coverage \ No newline at end of file diff --git a/tests/integration/test_design.py b/tests/integration/test_design.py index e1f8ddf52a..4e9e4229fa 100644 --- a/tests/integration/test_design.py +++ b/tests/integration/test_design.py @@ -3416,3 +3416,48 @@ def test_get_body_bounding_box(modeler: Modeler): assert center.x.m == 0 assert center.y.m == 0 assert center.z.m == 0.5 + + +def test_extrude_faces_failure_log_to_file(modeler: Modeler): + """Test that the failure to extrude faces logs the correct message to a file.""" + # Create a design and body for testing + design = modeler.create_design("test_design") + body = design.extrude_sketch("test_body", Sketch().box(Point2D([0, 0]), 1, 1), 1) + + # Call the method with invalid parameters to trigger failure + result = modeler.geometry_commands.extrude_faces( + faces=[body.faces[0]], + distance=-10.0, # Invalid distance to trigger failure + direction=UnitVector3D([0, 0, 1]), + ) + # Assert the result is an empty list + assert result == [] + + result = modeler.geometry_commands.extrude_faces_up_to( + faces=[body.faces[0]], + up_to_selection=body.faces[0], # Using the same face as target to trigger failure + direction=UnitVector3D([0, 0, 1]), + seed_point=Point3D([0, 0, 0]), + ) + # Assert the result is an empty list + assert result == [] + + +def test_extrude_edges_missing_parameters(modeler: Modeler): + """Test that extrude_edges raises a ValueError when required parameters are missing.""" + # Create a design and body for testing + design = modeler.create_design("test_design") + body = design.extrude_sketch("test_body", Sketch().box(Point2D([0, 0]), 1, 1), 1) + + # Test case: Missing both `from_face` and `from_point`/`direction` + with pytest.raises( + ValueError, + match="To extrude edges, either a face or a direction and point must be provided.", + ): + modeler.geometry_commands.extrude_edges( + edges=[body.edges[0]], # Using the first edge of the body + distance=10.0, + from_face=None, + from_point=None, + direction=None, + ) diff --git a/tests/test_conversions.py b/tests/test_conversions.py index 86f8cf210b..4c8da9232f 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -23,11 +23,28 @@ from pint import Quantity import pytest +from ansys.api.dbu.v0.admin_pb2 import BackendType as GRPCBackendType +from ansys.api.dbu.v0.dbumodels_pb2 import PartExportFormat as GRPCPartExportFormat from ansys.api.geometry.v0.models_pb2 import ( CurveGeometry as GRPCCurve, + CurveGeometry as GRPCCurveGeometry, Material as GRPCMaterial, MaterialProperty as GRPCMaterialProperty, + Surface as GRPCSurface, + SurfaceType as GRPCSurfaceType, ) +from ansys.geometry.core._grpc._services.v0.conversions import ( + from_curve_to_grpc_curve, + from_design_file_format_to_grpc_part_export_format, + from_grpc_backend_type_to_backend_type, + from_grpc_curve_to_curve, + from_grpc_material_property_to_material_property, + from_grpc_surface_to_surface, + from_sketch_shapes_to_grpc_geometries, + from_surface_to_grpc_surface, + from_tess_options_to_grpc_tess_options, +) +from ansys.geometry.core.connection.backend import BackendType from ansys.geometry.core.connection.conversions import ( curve_to_grpc_curve, grpc_curve_to_curve, @@ -36,11 +53,14 @@ grpc_surface_to_surface, sketch_shapes_to_grpc_geometries, ) +from ansys.geometry.core.designer.design import DesignFileFormat from ansys.geometry.core.designer.face import SurfaceType from ansys.geometry.core.materials.material import MaterialPropertyType from ansys.geometry.core.math import Plane, Point2D, Point3D, UnitVector3D from ansys.geometry.core.misc.measurements import DEFAULT_UNITS, UNITS -from ansys.geometry.core.shapes.curves import Circle, Ellipse, Line +from ansys.geometry.core.misc.options import TessellationOptions +from ansys.geometry.core.shapes.curves import Circle, Curve, Ellipse, Line +from ansys.geometry.core.shapes.curves.nurbs import NURBSCurve from ansys.geometry.core.shapes.surfaces import Cone, Cylinder, PlaneSurface, Sphere, Torus from ansys.geometry.core.sketch import Arc, Polygon, SketchCircle, SketchEdge, SketchEllipse @@ -412,3 +432,356 @@ def test_grpc_material_property_to_material_property_invalid_units(): # Assert fallback logic assert material_property.type == "Density" assert material_property.quantity == 7850 # Fallback to value + + +def test_material_property_type_from_id_valid(): + """Test that MaterialPropertyType.from_id correctly handles valid IDs.""" + # Create a GRPCMaterialProperty with a valid ID + valid_material_property = GRPCMaterialProperty( + id=MaterialPropertyType.DENSITY.value, + display_name="Density", + value=7850, + units="kg/m^3", + ) + + # Call the function + material_property = from_grpc_material_property_to_material_property(valid_material_property) + + # Assert that the type is correctly set + assert material_property.type == "Density" + + +def test_material_property_type_from_id_invalid(): + """Test that MaterialPropertyType.from_id correctly handles invalid IDs.""" + # Create a GRPCMaterialProperty with an invalid ID + invalid_material_property = GRPCMaterialProperty( + id="INVALID_ID", + display_name="Invalid Property", + value=100, + units="unitless", + ) + + # Call the function + material_property = from_grpc_material_property_to_material_property(invalid_material_property) + + # Assert that the type falls back to the raw ID + assert material_property.type == "INVALID_ID" + + +def test_from_tess_options_to_grpc_tess_options(): + """Test conversion of TessellationOptions to GRPCTessellationOptions.""" + # Create a TessellationOptions object + tess_options = TessellationOptions( + surface_deviation=0.01, + angle_deviation=5.0, + max_aspect_ratio=10.0, + max_edge_length=100.0, + watertight=True, + ) + + # Call the function + grpc_tess_options = from_tess_options_to_grpc_tess_options(tess_options) + + # Assert that the GRPCTessellationOptions object is correctly created + assert grpc_tess_options.surface_deviation == tess_options.surface_deviation + assert grpc_tess_options.angle_deviation == tess_options.angle_deviation + assert grpc_tess_options.maximum_aspect_ratio == tess_options.max_aspect_ratio + assert grpc_tess_options.maximum_edge_length == tess_options.max_edge_length + assert grpc_tess_options.watertight == tess_options.watertight + + +@pytest.mark.parametrize( + "curve_data, expected_type, expected_attributes", + [ + # Test for Circle + ( + SketchCircle(Point2D([0, 0]), radius=Quantity(5, UNITS.m)), + Circle, + {"radius": 5}, + ), + # Test for Ellipse + ( + SketchEllipse( + Point2D([0, 0]), + major_radius=Quantity(10, UNITS.m), + minor_radius=Quantity(5, UNITS.m), + angle=0, + ), + Ellipse, + {"majorradius": 10, "minorradius": 5}, # Updated attribute names + ), + ], +) +def test_from_sketch_shapes_to_grpc_geometries(curve_data, expected_type, expected_attributes): + """Test from_sketch_shapes_to_grpc_geometries for various curve types.""" + # Create a plane for positioning + plane = Plane() + + # Call the function + if isinstance(curve_data, SketchCircle) or isinstance(curve_data, SketchEllipse): + faces = [curve_data] + edges = [] + else: + faces = [] + edges = [curve_data] + + grpc_geometries = from_sketch_shapes_to_grpc_geometries( + plane, edges=edges, faces=faces, only_one_curve=True + ) + + # Assert the result type + if expected_type == Circle: + assert len(grpc_geometries.circles) == 1 + result = grpc_geometries.circles[0] + elif expected_type == Ellipse: + assert len(grpc_geometries.ellipses) == 1 + result = grpc_geometries.ellipses[0] + + # Assert the attributes of the result + for attr, value in expected_attributes.items(): + if isinstance(value, dict): # For nested attributes like direction + for sub_attr, sub_value in value.items(): + assert getattr(result.direction, sub_attr) == sub_value + else: + assert getattr(result, attr) == value + + +def test_from_curve_to_grpc_curve_nurbs(): + """Test conversion of a NURBSCurve to GRPCCurveGeometry.""" + # Create a NURBSCurve object + control_points = [ + Point3D([0, 0, 0]), + Point3D([1, 1, 0]), + Point3D([2, 0, 0]), + ] + nurbs_curve = NURBSCurve.fit_curve_from_points(control_points, 2) + + # Call the function + grpc_curve = from_curve_to_grpc_curve(nurbs_curve) + + # Assert the result is a GRPCCurveGeometry with a NURBSCurve + assert isinstance(grpc_curve, GRPCCurveGeometry) + assert len(grpc_curve.nurbs_curve.control_points) == len(control_points) + + +def test_from_curve_to_grpc_curve_unsupported_type(): + """Test that an unsupported curve type raises a ValueError.""" + + # Create an unsupported curve type by implementing abstract methods + class UnsupportedCurve(Curve): + def __eq__(self, other): + return False + + def contains_param(self, param): + return False + + def contains_point(self, point): + return False + + def evaluate(self, param): + return Point3D([0, 0, 0]) + + def parameterization(self): + return (0, 1) + + def project_point(self, point): + return Point3D([0, 0, 0]) + + def transformed_copy(self, transformation): + return self + + unsupported_curve = UnsupportedCurve() + + # Assert that calling the function raises a ValueError + with pytest.raises(ValueError, match=f"Unsupported curve type: {type(unsupported_curve)}"): + from_curve_to_grpc_curve(unsupported_curve) + + +def test_from_grpc_curve_to_curve_ellipse(): + """Test conversion of GRPCCurveGeometry to Ellipse.""" + # Create GRPCCurveGeometry with Ellipse attributes + grpc_curve = GRPCCurveGeometry( + origin={"x": 0, "y": 0, "z": 0}, + reference={"x": 1, "y": 0, "z": 0}, + axis={"x": 0, "y": 0, "z": 1}, + major_radius=10.0, + minor_radius=5.0, + ) + + # Call the function + result = from_grpc_curve_to_curve(grpc_curve) + + # Assert the result is an Ellipse + assert isinstance(result, Ellipse) + assert result.origin.x == grpc_curve.origin.x + assert result.origin.y == grpc_curve.origin.y + assert result.origin.z == grpc_curve.origin.z + assert result.major_radius == Quantity(grpc_curve.major_radius, UNITS.m) + assert result.minor_radius == Quantity(grpc_curve.minor_radius, UNITS.m) + assert result.dir_x.x == grpc_curve.reference.x + assert result.dir_x.y == grpc_curve.reference.y + assert result.dir_x.z == grpc_curve.reference.z + assert result.dir_z.x == grpc_curve.axis.x + assert result.dir_z.y == grpc_curve.axis.y + assert result.dir_z.z == grpc_curve.axis.z + + +def test_from_surface_to_grpc_surface_plane(): + """Test conversion of a PlaneSurface to GRPCSurface and GRPCSurfaceType.""" + # Create a PlaneSurface object + origin = Point3D([0, 0, 0]) + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + plane_surface = PlaneSurface(origin, reference, axis) + + # Call the function + grpc_surface, surface_type = from_surface_to_grpc_surface(plane_surface) + + # Assert the GRPCSurface attributes + assert isinstance(grpc_surface, GRPCSurface) + assert grpc_surface.origin.x == origin.x + assert grpc_surface.origin.y == origin.y + assert grpc_surface.origin.z == origin.z + assert grpc_surface.reference.x == reference.x + assert grpc_surface.reference.y == reference.y + assert grpc_surface.reference.z == reference.z + assert grpc_surface.axis.x == axis.x + assert grpc_surface.axis.y == axis.y + assert grpc_surface.axis.z == axis.z + + # Assert the surface type + assert surface_type == GRPCSurfaceType.SURFACETYPE_PLANE + + +def test_from_grpc_surface_to_surface_cone(): + """Test conversion of GRPCSurface to Cone.""" + # Create GRPCSurface for Cone + grpc_surface = GRPCSurface( + origin={"x": 0, "y": 0, "z": 0}, + axis={"x": 0, "y": 0, "z": 1}, + reference={"x": 1, "y": 0, "z": 0}, + radius=5.0, + half_angle=30.0, + ) + + # Call the function + result = from_grpc_surface_to_surface(grpc_surface, SurfaceType.SURFACETYPE_CONE) + + # Assert the result is a Cone + assert isinstance(result, Cone) + assert result.origin.x == grpc_surface.origin.x + assert result.origin.y == grpc_surface.origin.y + assert result.origin.z == grpc_surface.origin.z + assert result.radius == Quantity(grpc_surface.radius, UNITS.m) + assert result.half_angle == grpc_surface.half_angle + + +def test_from_grpc_surface_to_surface_sphere(): + """Test conversion of GRPCSurface to Sphere.""" + # Create GRPCSurface for Sphere + grpc_surface = GRPCSurface( + origin={"x": 0, "y": 0, "z": 0}, + axis={"x": 0, "y": 0, "z": 1}, + reference={"x": 1, "y": 0, "z": 0}, + radius=10.0, + ) + + # Call the function + result = from_grpc_surface_to_surface(grpc_surface, SurfaceType.SURFACETYPE_SPHERE) + + # Assert the result is a Sphere + assert isinstance(result, Sphere) + assert result.origin.x == grpc_surface.origin.x + assert result.origin.y == grpc_surface.origin.y + assert result.origin.z == grpc_surface.origin.z + assert result.radius == Quantity(grpc_surface.radius, UNITS.m) + + +def test_from_grpc_surface_to_surface_torus(): + """Test conversion of GRPCSurface to Torus.""" + # Create GRPCSurface for Torus + grpc_surface = GRPCSurface( + origin={"x": 0, "y": 0, "z": 0}, + axis={"x": 0, "y": 0, "z": 1}, + reference={"x": 1, "y": 0, "z": 0}, + major_radius=15.0, + minor_radius=5.0, + ) + + # Call the function + result = from_grpc_surface_to_surface(grpc_surface, SurfaceType.SURFACETYPE_TORUS) + + # Assert the result is a Torus + assert isinstance(result, Torus) + assert result.origin.x == grpc_surface.origin.x + assert result.origin.y == grpc_surface.origin.y + assert result.origin.z == grpc_surface.origin.z + assert result.major_radius == Quantity(grpc_surface.major_radius, UNITS.m) + assert result.minor_radius == Quantity(grpc_surface.minor_radius, UNITS.m) + + +def test_from_grpc_surface_to_surface_none(): + """Test that an unsupported surface type returns None.""" + # Create GRPCSurface with valid attributes + grpc_surface = GRPCSurface( + origin={"x": 0, "y": 0, "z": 0}, + axis={"x": 0, "y": 0, "z": 1}, + reference={"x": 1, "y": 0, "z": 0}, + ) + + # Call the function with an unsupported surface type + result = from_grpc_surface_to_surface(grpc_surface, "UnsupportedType") + + # Assert the result is None + assert result is None + + +@pytest.mark.parametrize( + "grpc_backend_type, expected_backend_type", + [ + (GRPCBackendType.DISCOVERY, BackendType.DISCOVERY), + (GRPCBackendType.SPACECLAIM, BackendType.SPACECLAIM), + (GRPCBackendType.WINDOWS_DMS, BackendType.WINDOWS_SERVICE), + (GRPCBackendType.LINUX_DMS, BackendType.LINUX_SERVICE), + (GRPCBackendType.CORE_SERVICE_LINUX, BackendType.CORE_LINUX), + (GRPCBackendType.CORE_SERVICE_WINDOWS, BackendType.CORE_WINDOWS), + (GRPCBackendType.DISCOVERY_HEADLESS, BackendType.DISCOVERY_HEADLESS), + ], +) +def test_from_grpc_backend_type_to_backend_type_valid(grpc_backend_type, expected_backend_type): + """Test valid mappings from GRPCBackendType to BackendType.""" + backend_type = from_grpc_backend_type_to_backend_type(grpc_backend_type) + assert backend_type == expected_backend_type + + +def test_from_grpc_backend_type_to_backend_type_invalid(): + """Test that an invalid GRPCBackendType raises a ValueError.""" + invalid_backend_type = 999 # An invalid backend type not defined in GRPCBackendType + + with pytest.raises(ValueError, match=f"Invalid backend type: {invalid_backend_type}"): + from_grpc_backend_type_to_backend_type(invalid_backend_type) + + +@pytest.mark.parametrize( + "design_file_format, expected_export_format", + [ + (DesignFileFormat.STEP, GRPCPartExportFormat.PARTEXPORTFORMAT_STEP), + (DesignFileFormat.IGES, GRPCPartExportFormat.PARTEXPORTFORMAT_IGES), + ], +) +def test_from_design_file_format_to_grpc_part_export_format_valid( + design_file_format, expected_export_format +): + """Test valid mappings from DesignFileFormat to GRPCPartExportFormat.""" + export_format = from_design_file_format_to_grpc_part_export_format(design_file_format) + assert export_format == expected_export_format + + +def test_from_design_file_format_to_grpc_part_export_format_none(): + """Test that an unsupported DesignFileFormat returns None.""" + unsupported_format = "UnsupportedFormat" # An invalid format not defined in DesignFileFormat + + export_format = from_design_file_format_to_grpc_part_export_format(unsupported_format) + + # Assert that the result is None + assert export_format is None diff --git a/tests/test_primitives.py b/tests/test_primitives.py index 46beb1fe4c..99da224f69 100644 --- a/tests/test_primitives.py +++ b/tests/test_primitives.py @@ -46,6 +46,12 @@ Sphere, Torus, ) +from ansys.geometry.core.shapes.parameterization import ( + Parameterization, + ParamForm, + ParamType, +) +from ansys.geometry.core.shapes.surfaces.sphere import SphereEvaluation def test_cylinder(): @@ -182,6 +188,126 @@ def test_cylinder_evaluation(): assert np.allclose(eval2.v_derivative, Vector3D([0, 0, 1])) +def test_cylinder_radius_not_positive(): + """Test that a ValueError is raised when the radius is not positive.""" + origin = Point3D([0, 0, 0]) + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Test with zero radius + with pytest.raises(ValueError, match="Radius must be a real positive value."): + Cylinder(origin, 0, reference, axis) + + # Test with negative radius + with pytest.raises(ValueError, match="Radius must be a real positive value."): + Cylinder(origin, -5, reference, axis) + + +def test_cylinder_surface_area_height_not_positive(): + """Test that a ValueError is raised when the height is not positive in surface_area.""" + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cylinder instance + cylinder = Cylinder(origin, radius, reference, axis) + + # Test with zero height + with pytest.raises(ValueError, match="Height must be a real positive value."): + cylinder.surface_area(0) + + # Test with negative height + with pytest.raises(ValueError, match="Height must be a real positive value."): + cylinder.surface_area(-5) + + +def test_cylinder_volume_height_not_positive(): + """Test that a ValueError is raised when the height is not positive in volume.""" + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cylinder instance + cylinder = Cylinder(origin, radius, reference, axis) + + # Test with zero height + with pytest.raises(ValueError, match="Height must be a real positive value."): + cylinder.volume(0) + + # Test with negative height + with pytest.raises(ValueError, match="Height must be a real positive value."): + cylinder.volume(-5) + + +def test_cylinder_parameterization(): + """Test the parameterization method of the Cylinder class.""" + # Define valid inputs for the cylinder + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cylinder instance + cylinder = Cylinder(origin, radius, reference, axis) + + # Call the parameterization method + u, v = cylinder.parameterization() + + # Validate the u parameterization + assert isinstance(u, Parameterization) + assert u.form == ParamForm.PERIODIC + assert u.type == ParamType.CIRCULAR + assert u.interval.start == 0 + assert u.interval.end == 2 * np.pi + + # Validate the v parameterization + assert isinstance(v, Parameterization) + assert v.form == ParamForm.OPEN + assert v.type == ParamType.LINEAR + assert v.interval.start == -np.inf + assert v.interval.end == np.inf + + +def test_cylinder_contains_param_not_implemented(): + """Test that contains_param raises NotImplementedError.""" + # Define valid inputs for the cylinder + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cylinder instance + cylinder = Cylinder(origin, radius, reference, axis) + + # Define a parameter + param_uv = ParamUV(0.5, 0.5) + + # Assert that contains_param raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_param\\(\\) is not implemented."): + cylinder.contains_param(param_uv) + + +def test_cylinder_contains_point_not_implemented(): + """Test that contains_point raises NotImplementedError.""" + # Define valid inputs for the cylinder + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cylinder instance + cylinder = Cylinder(origin, radius, reference, axis) + + # Define a point + point = Point3D([5, 5, 5]) + + # Assert that contains_point raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_point\\(\\) is not implemented."): + cylinder.contains_point(point) + + def test_sphere(): """``Sphere`` construction and equivalency.""" # Create two Sphere objects @@ -287,6 +413,124 @@ def test_sphere_evaluation(): assert np.allclose(eval2.v_derivative.normalize(), UnitVector3D([-1, -1, 2])) +def test_sphere_reference_and_axis_not_perpendicular(): + """Test that a ValueError is raised when the reference and axis are not perpendicular.""" + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([1, 0, 0]) # Same direction as reference + + with pytest.raises( + ValueError, match="Circle reference \\(dir_x\\) and axis \\(dir_z\\) must be perpendicular." + ): + Sphere(origin, radius, reference, axis) + + +def test_sphere_radius_not_positive(): + """Test that a ValueError is raised when the radius is not positive.""" + origin = Point3D([0, 0, 0]) + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Test with zero radius + with pytest.raises(ValueError, match="Radius must be a real positive value."): + Sphere(origin, 0, reference, axis) + + # Test with negative radius + with pytest.raises(ValueError, match="Radius must be a real positive value."): + Sphere(origin, -5, reference, axis) + + +def test_sphere_project_point_origin(): + """Test the project_point method when the point is at the sphere's origin.""" + # Define valid inputs for the sphere + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a sphere instance + sphere = Sphere(origin, radius, reference, axis) + + # Define a point at the sphere's origin + point = Point3D([0, 0, 0]) + + # Call the project_point method + evaluation = sphere.project_point(point) + + # Validate the result + assert isinstance(evaluation, SphereEvaluation) + assert evaluation.parameter.u == 0 + assert evaluation.parameter.v == np.pi / 2 + + +def test_sphere_parameterization(): + """Test the parameterization method of the Sphere class.""" + # Define valid inputs for the sphere + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a sphere instance + sphere = Sphere(origin, radius, reference, axis) + + # Call the parameterization method + u, v = sphere.parameterization() + + # Validate the u parameterization + assert isinstance(u, Parameterization) + assert u.form == ParamForm.PERIODIC + assert u.type == ParamType.CIRCULAR + assert u.interval.start == 0 + assert u.interval.end == 2 * np.pi + + # Validate the v parameterization + assert isinstance(v, Parameterization) + assert v.form == ParamForm.CLOSED + assert v.type == ParamType.OTHER + assert v.interval.start == -np.pi / 2 + assert v.interval.end == np.pi / 2 + + +def test_sphere_contains_param_not_implemented(): + """Test that contains_param raises NotImplementedError.""" + # Define valid inputs for the sphere + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a sphere instance + sphere = Sphere(origin, radius, reference, axis) + + # Define a parameter + param_uv = ParamUV(0.5, 0.5) + + # Assert that contains_param raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_param\\(\\) is not implemented."): + sphere.contains_param(param_uv) + + +def test_sphere_contains_point_not_implemented(): + """Test that contains_point raises NotImplementedError.""" + # Define valid inputs for the sphere + origin = Point3D([0, 0, 0]) + radius = 10.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a sphere instance + sphere = Sphere(origin, radius, reference, axis) + + # Define a point + point = Point3D([5, 5, 5]) + + # Assert that contains_point raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_point\\(\\) is not implemented."): + sphere.contains_point(point) + + def test_cone(): """``Cone`` construction and equivalency.""" origin = Point3D([0, 0, 0]) @@ -455,6 +699,119 @@ def test_cone_evaluation(): assert np.allclose(eval2.v_derivative, Vector3D([0.70710678, 0.70710678, 1])) +def test_cone_radius_not_positive(): + """Test that a ValueError is raised when the radius is not positive.""" + origin = Point3D([0, 0, 0]) + half_angle = 45.0 # Valid half angle + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Test with zero radius + with pytest.raises(ValueError, match="Radius must be a real positive value."): + Cone(origin, 0, half_angle, reference, axis) + + # Test with negative radius + with pytest.raises(ValueError, match="Radius must be a real positive value."): + Cone(origin, -5, half_angle, reference, axis) + + +def test_cone_project_point_u_normalization(): + """Test the normalization of the u parameter in the project_point method.""" + # Define valid inputs for the cone + origin = Point3D([0, 0, 0]) + radius = 10.0 + half_angle = 45.0 # Valid half angle + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cone instance + cone = Cone(origin, radius, half_angle, reference, axis) + + # Define a point to project onto the cone that results in u < 0 + point_negative_u = Point3D([-5, -5, 5]) + evaluation_negative_u = cone.project_point(point_negative_u) + assert 0 <= evaluation_negative_u.parameter.u <= 2 * np.pi, ( + "u parameter is not normalized within [0, 2*pi]" + ) + + # Define a point to project onto the cone that results in u > 2 * np.pi + point_large_u = Point3D([100, 100, 5]) + evaluation_large_u = cone.project_point(point_large_u) + assert 0 <= evaluation_large_u.parameter.u <= 2 * np.pi, ( + "u parameter is not normalized within [0, 2*pi]" + ) + + +def test_cone_parameterization(): + """Test the parameterization method of the Cone class.""" + # Define valid inputs for the cone + origin = Point3D([0, 0, 0]) + radius = 10.0 + half_angle = 45.0 # Valid half angle + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cone instance + cone = Cone(origin, radius, half_angle, reference, axis) + + # Call the parameterization method + u, v = cone.parameterization() + + # Validate the u parameterization + assert isinstance(u, Parameterization) + assert u.form == ParamForm.PERIODIC + assert u.type == ParamType.CIRCULAR + assert u.interval.start == 0 + assert u.interval.end == 2 * np.pi + + # Validate the v parameterization + assert isinstance(v, Parameterization) + assert v.form == ParamForm.OPEN + assert v.type == ParamType.LINEAR + assert v.interval.start == cone.apex_param + assert v.interval.end == np.inf if cone.apex_param < 0 else cone.apex_param + + +def test_cone_contains_param_not_implemented(): + """Test that contains_param raises NotImplementedError.""" + # Define valid inputs for the cone + origin = Point3D([0, 0, 0]) + radius = 10.0 + half_angle = 45.0 # Valid half angle + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cone instance + cone = Cone(origin, radius, half_angle, reference, axis) + + # Define a parameter + param_uv = ParamUV(0.5, 0.5) + + # Assert that contains_param raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_param\\(\\) is not implemented."): + cone.contains_param(param_uv) + + +def test_cone_contains_point_not_implemented(): + """Test that contains_point raises NotImplementedError.""" + # Define valid inputs for the cone + origin = Point3D([0, 0, 0]) + radius = 10.0 + half_angle = 45.0 # Valid half angle + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a cone instance + cone = Cone(origin, radius, half_angle, reference, axis) + + # Define a point + point = Point3D([5, 5, 5]) + + # Assert that contains_point raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_point\\(\\) is not implemented."): + cone.contains_point(point) + + def test_torus(): """``Torus`` construction and equivalency.""" # Create two Torus objects @@ -549,6 +906,11 @@ def test_torus(): torus_mirror._reference, UnitVector3D([-0.11490753, -0.29684446, -0.94798714]) ) assert np.allclose(torus_mirror._axis, UnitVector3D([0, -0.9543083, 0.29882381])) + # Attempt to create a Torus and expect a ValueError + with pytest.raises( + ValueError, match=r"Torus reference \(dir_x\) and axis \(dir_z\) must be perpendicular." + ): + Torus(origin, major_radius, minor_radius, UnitVector3D([1, 0, 0]), UnitVector3D([1, 0, 0])) def test_torus_units(): @@ -682,6 +1044,76 @@ def test_torus_evaluation(): assert isinstance(eval3.max_curvature_direction, UnitVector3D) +def test_torus_parameterization(): + """Test the parameterization method of the Torus class.""" + # Define valid inputs for the torus + origin = Point3D([0, 0, 0]) + major_radius = 10.0 + minor_radius = 5.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a torus instance + torus = Torus(origin, major_radius, minor_radius, reference, axis) + + # Call the parameterization method + u, v = torus.parameterization() + + # Validate the u parameterization + assert isinstance(u, Parameterization) + assert u.form == ParamForm.PERIODIC + assert u.type == ParamType.CIRCULAR + assert u.interval.start == 0 + assert u.interval.end == 2 * np.pi + + # Validate the v parameterization + assert isinstance(v, Parameterization) + assert v.form == ParamForm.PERIODIC + assert v.type == ParamType.CIRCULAR + assert v.interval.start == -np.pi / 2 + assert v.interval.end == np.pi / 2 + + +def test_torus_contains_param_not_implemented(): + """Test that contains_param raises NotImplementedError.""" + # Define valid inputs for the torus + origin = Point3D([0, 0, 0]) + major_radius = 10.0 + minor_radius = 5.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a torus instance + torus = Torus(origin, major_radius, minor_radius, reference, axis) + + # Define a parameter + param_uv = ParamUV(0.5, 0.5) + + # Assert that contains_param raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_param\\(\\) is not implemented."): + torus.contains_param(param_uv) + + +def test_torus_contains_point_not_implemented(): + """Test that contains_point raises NotImplementedError.""" + # Define valid inputs for the torus + origin = Point3D([0, 0, 0]) + major_radius = 10.0 + minor_radius = 5.0 + reference = UnitVector3D([1, 0, 0]) + axis = UnitVector3D([0, 0, 1]) + + # Create a torus instance + torus = Torus(origin, major_radius, minor_radius, reference, axis) + + # Define a point + point = Point3D([5, 5, 5]) + + # Assert that contains_point raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_point\\(\\) is not implemented."): + torus.contains_point(point) + + def test_circle(): """``Circle`` construction and equivalency.""" origin = Point3D([0, 0, 0]) @@ -1054,3 +1486,100 @@ def test_nurbs_curve_point_projection(): assert projection.is_set() is True assert np.allclose(projection.position, Point3D([1, 0.5, 0])) assert np.isclose(projection.parameter, 0.5) + + +def test_nurbs_curve_equality_with_non_nurbs_object(): + """Test that __eq__ returns False when comparing with a non-NURBSCurve object.""" + # Create a valid NURBSCurve instance + control_points = [Point3D([0, 0, 0]), Point3D([1, 1, 1]), Point3D([2, 2, 2])] + degree = 2 + knots = [0, 0, 0, 1, 1, 1] + nurbs_curve = NURBSCurve.from_control_points(control_points, degree, knots) + + # Compare with a non-NURBSCurve object + non_nurbs_object = "Not a NURBSCurve" + assert nurbs_curve != non_nurbs_object, ( + "__eq__ should return False when comparing with a non-NURBSCurve object." + ) + + +def test_nurbs_curve_parameterization(): + """Test the parameterization method of the NURBSCurve class.""" + # Define valid inputs for the NURBS curve + control_points = [Point3D([0, 0, 0]), Point3D([1, 1, 1]), Point3D([2, 2, 2])] + degree = 2 + knots = [0, 0, 0, 1, 1, 1] + + # Create a NURBS curve instance + nurbs_curve = NURBSCurve.from_control_points(control_points, degree, knots) + + # Call the parameterization method + parameterization = nurbs_curve.parameterization() + + # Validate the parameterization + assert isinstance(parameterization, Parameterization) + assert parameterization.form == ParamForm.OTHER + assert parameterization.type == ParamType.OTHER + assert parameterization.interval.start == nurbs_curve.geomdl_nurbs_curve.domain[0] + assert parameterization.interval.end == nurbs_curve.geomdl_nurbs_curve.domain[1] + + +def test_nurbs_curve_transformed_copy(): + """Test the transformed_copy method of the NURBSCurve class.""" + # Define valid inputs for the NURBS curve + control_points = [Point3D([0, 0, 0]), Point3D([1, 1, 1]), Point3D([2, 2, 2])] + degree = 2 + knots = [0, 0, 0, 1, 1, 1] + + # Create a NURBS curve instance + nurbs_curve = NURBSCurve.from_control_points(control_points, degree, knots) + + # Define a transformation matrix (e.g., rotation + translation) + transformation_matrix = Matrix44( + [ + [1, 0, 0, 10], # Translate x by 10 + [0, 1, 0, 20], # Translate y by 20 + [0, 0, 1, 30], # Translate z by 30 + [0, 0, 0, 1], + ] + ) + + # Call the transformed_copy method + transformed_curve = nurbs_curve.transformed_copy(transformation_matrix) + assert isinstance(transformed_curve, NURBSCurve) + + +def test_nurbs_curve_contains_param_not_implemented(): + """Test that contains_param raises NotImplementedError.""" + # Define valid inputs for the NURBS curve + control_points = [Point3D([0, 0, 0]), Point3D([1, 1, 1]), Point3D([2, 2, 2])] + degree = 2 + knots = [0, 0, 0, 1, 1, 1] + + # Create a NURBS curve instance + nurbs_curve = NURBSCurve.from_control_points(control_points, degree, knots) + + # Define a parameter + param = 0.5 + + # Assert that contains_param raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_param\\(\\) is not implemented."): + nurbs_curve.contains_param(param) + + +def test_nurbs_curve_contains_point_not_implemented(): + """Test that contains_point raises NotImplementedError.""" + # Define valid inputs for the NURBS curve + control_points = [Point3D([0, 0, 0]), Point3D([1, 1, 1]), Point3D([2, 2, 2])] + degree = 2 + knots = [0, 0, 0, 1, 1, 1] + + # Create a NURBS curve instance + nurbs_curve = NURBSCurve.from_control_points(control_points, degree, knots) + + # Define a point + point = Point3D([1, 1, 1]) + + # Assert that contains_point raises NotImplementedError + with pytest.raises(NotImplementedError, match="contains_point\\(\\) is not implemented."): + nurbs_curve.contains_point(point)