"""Tests for Edge.geom_equal and Wire.geom_equal methods.""" import pytest from build123d import ( Vertex, Edge, Wire, Spline, Rectangle, Circle, Ellipse, Bezier, GeomType, Location, Plane, ) class TestEdgeGeomEqualLine: """Tests for Edge.geom_equal with LINE type.""" def test_same_line(self): e1 = Edge.make_line((0, 0, 0), (1, 1, 1)) e2 = Edge.make_line((0, 0, 0), (1, 1, 1)) assert e1.geom_type == GeomType.LINE assert e1.geom_equal(e2) def test_different_line(self): e1 = Edge.make_line((0, 0, 0), (1, 1, 1)) e2 = Edge.make_line((0, 0, 0), (1, 1, 2)) assert not e1.geom_equal(e2) class TestEdgeGeomEqualCircle: """Tests for Edge.geom_equal with CIRCLE type.""" def test_same_circle(self): c1 = Circle(10) c2 = Circle(10) e1 = c1.edge() e2 = c2.edge() assert e1.geom_type == GeomType.CIRCLE assert e1.geom_equal(e2) def test_different_radius(self): c1 = Circle(10) c2 = Circle(11) e1 = c1.edge() e2 = c2.edge() assert not e1.geom_equal(e2) def test_same_arc(self): e1 = Edge.make_circle(10, start_angle=0, end_angle=90) e2 = Edge.make_circle(10, start_angle=0, end_angle=90) assert e1.geom_equal(e2) def test_different_arc_angle(self): e1 = Edge.make_circle(10, start_angle=0, end_angle=90) e2 = Edge.make_circle(10, start_angle=0, end_angle=180) assert not e1.geom_equal(e2) def test_different_circle_from_revolve(self): """Two circles with same radius/endpoints but different center/axis.""" from build123d import Axis, Line, RadiusArc, make_face, revolve f1 = make_face(RadiusArc((5, 0), (-5, 0), 15) + Line((5, 0), (-5, 0))) p1 = revolve(f1, Axis.X, 90) value1, value2 = p1.edges().filter_by(GeomType.CIRCLE) value2 = value2.reversed() # These circles have same endpoints after reversal but different center/axis assert not value1.geom_equal(value2) def test_different_location(self): """Circles with same radius but different center location.""" e1 = Edge.make_circle(10) e2 = Edge.make_circle(10).locate(Location((5, 0, 0))) assert not e1.geom_equal(e2) def test_same_location(self): """Circles with same radius and same non-origin location.""" e1 = Edge.make_circle(10).locate(Location((5, 5, 0))) e2 = Edge.make_circle(10).locate(Location((5, 5, 0))) assert e1.geom_equal(e2) def test_different_axis(self): """Circles with same radius but different axis direction.""" e1 = Edge.make_circle(10, plane=Plane.XY) e2 = Edge.make_circle(10, plane=Plane.XZ) assert not e1.geom_equal(e2) def test_same_axis(self): """Circles with same radius and same non-default axis.""" e1 = Edge.make_circle(10, plane=Plane.YZ) e2 = Edge.make_circle(10, plane=Plane.YZ) assert e1.geom_equal(e2) class TestEdgeGeomEqualEllipse: """Tests for Edge.geom_equal with ELLIPSE type.""" def test_same_ellipse(self): el1 = Ellipse(10, 5) el2 = Ellipse(10, 5) e1 = el1.edge() e2 = el2.edge() assert e1.geom_type == GeomType.ELLIPSE assert e1.geom_equal(e2) def test_different_major_radius(self): el1 = Ellipse(10, 5) el2 = Ellipse(11, 5) e1 = el1.edge() e2 = el2.edge() assert not e1.geom_equal(e2) def test_different_minor_radius(self): el1 = Ellipse(10, 5) el2 = Ellipse(10, 6) e1 = el1.edge() e2 = el2.edge() assert not e1.geom_equal(e2) def test_different_location(self): """Ellipses with same radii but different center location.""" e1 = Edge.make_ellipse(10, 5) e2 = Edge.make_ellipse(10, 5).locate(Location((5, 0, 0))) assert not e1.geom_equal(e2) def test_same_location(self): """Ellipses with same radii and same non-origin location.""" e1 = Edge.make_ellipse(10, 5).locate(Location((5, 5, 0))) e2 = Edge.make_ellipse(10, 5).locate(Location((5, 5, 0))) assert e1.geom_equal(e2) def test_different_axis(self): """Ellipses with same radii but different axis direction.""" e1 = Edge.make_ellipse(10, 5, plane=Plane.XY) e2 = Edge.make_ellipse(10, 5, plane=Plane.XZ) assert not e1.geom_equal(e2) def test_same_axis(self): """Ellipses with same radii and same non-default axis.""" e1 = Edge.make_ellipse(10, 5, plane=Plane.YZ) e2 = Edge.make_ellipse(10, 5, plane=Plane.YZ) assert e1.geom_equal(e2) class TestEdgeGeomEqualHyperbola: """Tests for Edge.geom_equal with HYPERBOLA type.""" def test_same_hyperbola(self): e1 = Edge.make_hyperbola(10, 5, start_angle=-45, end_angle=45) e2 = Edge.make_hyperbola(10, 5, start_angle=-45, end_angle=45) assert e1.geom_type == GeomType.HYPERBOLA assert e1.geom_equal(e2) def test_different_x_radius(self): e1 = Edge.make_hyperbola(10, 5, start_angle=-45, end_angle=45) e2 = Edge.make_hyperbola(11, 5, start_angle=-45, end_angle=45) assert not e1.geom_equal(e2) def test_different_y_radius(self): e1 = Edge.make_hyperbola(10, 5, start_angle=-45, end_angle=45) e2 = Edge.make_hyperbola(10, 6, start_angle=-45, end_angle=45) assert not e1.geom_equal(e2) def test_different_location(self): """Hyperbolas with same radii but different center location.""" e1 = Edge.make_hyperbola(10, 5, start_angle=-45, end_angle=45) e2 = Edge.make_hyperbola(10, 5, start_angle=-45, end_angle=45).locate( Location((5, 0, 0)) ) assert not e1.geom_equal(e2) def test_same_location(self): """Hyperbolas with same radii and same non-origin location.""" e1 = Edge.make_hyperbola(10, 5, start_angle=-45, end_angle=45).locate( Location((5, 5, 0)) ) e2 = Edge.make_hyperbola(10, 5, start_angle=-45, end_angle=45).locate( Location((5, 5, 0)) ) assert e1.geom_equal(e2) def test_different_axis(self): """Hyperbolas with same radii but different axis direction.""" e1 = Edge.make_hyperbola(10, 5, plane=Plane.XY, start_angle=-45, end_angle=45) e2 = Edge.make_hyperbola(10, 5, plane=Plane.XZ, start_angle=-45, end_angle=45) assert not e1.geom_equal(e2) def test_same_axis(self): """Hyperbolas with same radii and same non-default axis.""" e1 = Edge.make_hyperbola(10, 5, plane=Plane.YZ, start_angle=-45, end_angle=45) e2 = Edge.make_hyperbola(10, 5, plane=Plane.YZ, start_angle=-45, end_angle=45) assert e1.geom_equal(e2) class TestEdgeGeomEqualParabola: """Tests for Edge.geom_equal with PARABOLA type.""" def test_same_parabola(self): e1 = Edge.make_parabola(5, start_angle=0, end_angle=60) e2 = Edge.make_parabola(5, start_angle=0, end_angle=60) assert e1.geom_type == GeomType.PARABOLA assert e1.geom_equal(e2) def test_different_focal_length(self): e1 = Edge.make_parabola(5, start_angle=0, end_angle=60) e2 = Edge.make_parabola(6, start_angle=0, end_angle=60) assert not e1.geom_equal(e2) def test_different_location(self): """Parabolas with same focal length but different vertex location.""" e1 = Edge.make_parabola(5, start_angle=0, end_angle=60) e2 = Edge.make_parabola(5, start_angle=0, end_angle=60).locate( Location((5, 0, 0)) ) assert not e1.geom_equal(e2) def test_same_location(self): """Parabolas with same focal length and same non-origin location.""" e1 = Edge.make_parabola(5, start_angle=0, end_angle=60).locate( Location((5, 5, 0)) ) e2 = Edge.make_parabola(5, start_angle=0, end_angle=60).locate( Location((5, 5, 0)) ) assert e1.geom_equal(e2) def test_different_axis(self): """Parabolas with same focal length but different axis direction.""" e1 = Edge.make_parabola(5, plane=Plane.XY, start_angle=0, end_angle=60) e2 = Edge.make_parabola(5, plane=Plane.XZ, start_angle=0, end_angle=60) assert not e1.geom_equal(e2) def test_same_axis(self): """Parabolas with same focal length and same non-default axis.""" e1 = Edge.make_parabola(5, plane=Plane.YZ, start_angle=0, end_angle=60) e2 = Edge.make_parabola(5, plane=Plane.YZ, start_angle=0, end_angle=60) assert e1.geom_equal(e2) class TestEdgeGeomEqualBezier: """Tests for Edge.geom_equal with BEZIER type.""" def test_same_bezier(self): pts = [(0, 0), (1, 1), (2, 0)] b1 = Bezier(*pts) b2 = Bezier(*pts) e1 = b1.edge() e2 = b2.edge() assert e1.geom_type == GeomType.BEZIER assert e1.geom_equal(e2) def test_different_bezier(self): b1 = Bezier((0, 0), (1, 1), (2, 0)) b2 = Bezier((0, 0), (1, 2), (2, 0)) e1 = b1.edge() e2 = b2.edge() assert not e1.geom_equal(e2) def test_different_degree(self): """Bezier curves with different degrees (different number of control points).""" # Quadratic (degree 2, 3 points) b1 = Bezier((0, 0), (1, 1), (2, 0)) # Cubic (degree 3, 4 points) - adjusted to have same endpoints b2 = Bezier((0, 0), (0.5, 1), (1.5, 1), (2, 0)) e1 = b1.edge() e2 = b2.edge() assert e1.geom_type == GeomType.BEZIER assert e2.geom_type == GeomType.BEZIER assert not e1.geom_equal(e2) def test_rational_bezier_different_weights(self): """Rational Bezier curves with different weights.""" pts = [(0, 0, 0), (1, 1, 0), (2, 0, 0)] # Create rational Bezier with weights [1, 2, 1] e1 = Edge.make_bezier(*pts, weights=[1.0, 2.0, 1.0]) # Create rational Bezier with weights [1, 3, 1] e2 = Edge.make_bezier(*pts, weights=[1.0, 3.0, 1.0]) assert e1.geom_type == GeomType.BEZIER assert not e1.geom_equal(e2) class TestEdgeGeomEqualBSpline: """Tests for Edge.geom_equal with BSPLINE type.""" def test_same_spline(self): v = [Vertex(p) for p in ((-2, 0), (-1, 0), (0, 0), (1, 0), (2, 0))] s1 = Spline(*v) s2 = Spline(*v) e1 = s1.edge() e2 = s2.edge() assert e1.geom_type == GeomType.BSPLINE assert e1.geom_equal(e2) def test_different_spline(self): v1 = [Vertex(p) for p in ((-2, 0), (-1, 0), (0, 0), (1, 0), (2, 0))] v2 = [Vertex(p) for p in ((-2, 0), (-1, 1), (0, 0), (1, 0), (2, 0))] s1 = Spline(*v1) s2 = Spline(*v2) e1 = s1.edge() e2 = s2.edge() assert not e1.geom_equal(e2) def test_complex_spline(self): v = [ Vertex(p) for p in ( (-2, 0), (-1, 0), (0, 0), (1, 0), (2, 0), (3, 0.1), (4, 1), (5, 2.2), (6, 3), (7, 2), (8, -1), ) ] s1 = Spline(*v) s2 = Spline(*v) e1 = s1.edge() e2 = s2.edge() assert e1.geom_equal(e2) def test_different_periodicity(self): """BSplines with different periodicity (periodic vs non-periodic).""" # Same control points, different periodicity pts = [(0, 0), (1, 1), (2, 0), (1, -1)] e1 = Edge.make_spline(pts, periodic=False) e2 = Edge.make_spline(pts, periodic=True) assert e1.geom_type == GeomType.BSPLINE assert e2.geom_type == GeomType.BSPLINE # Different periodicity means not equal assert not e1.geom_equal(e2) def test_different_pole_count(self): """BSplines with different number of poles.""" # 5 points v1 = [Vertex(p) for p in ((0, 0), (1, 1), (2, 0), (3, 1), (4, 0))] # 6 points with same endpoints v2 = [ Vertex(p) for p in ((0, 0), (0.8, 0.8), (1.6, 0.2), (2.4, 0.8), (3.2, 0.2), (4, 0)) ] s1 = Spline(*v1) s2 = Spline(*v2) e1 = s1.edge() e2 = s2.edge() assert e1.geom_type == GeomType.BSPLINE assert e2.geom_type == GeomType.BSPLINE assert not e1.geom_equal(e2) def test_different_knot_values(self): """BSplines with different internal knot positions have different shapes.""" from OCP.Geom import Geom_BSplineCurve from OCP.TColgp import TColgp_Array1OfPnt from OCP.TColStd import TColStd_Array1OfReal, TColStd_Array1OfInteger from OCP.gp import gp_Pnt from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge # 5 poles for degree 3 with one internal knot poles = TColgp_Array1OfPnt(1, 5) poles.SetValue(1, gp_Pnt(0, 0, 0)) poles.SetValue(2, gp_Pnt(1, 2, 0)) poles.SetValue(3, gp_Pnt(2, 2, 0)) poles.SetValue(4, gp_Pnt(3, 2, 0)) poles.SetValue(5, gp_Pnt(4, 0, 0)) mults = TColStd_Array1OfInteger(1, 3) mults.SetValue(1, 4) mults.SetValue(2, 1) # Internal knot mults.SetValue(3, 4) # Internal knot at 0.5 knots1 = TColStd_Array1OfReal(1, 3) knots1.SetValue(1, 0.0) knots1.SetValue(2, 0.5) knots1.SetValue(3, 1.0) curve1 = Geom_BSplineCurve(poles, knots1, mults, 3, False) e1 = Edge(BRepBuilderAPI_MakeEdge(curve1).Edge()) # Internal knot at 0.3 - different position changes shape! knots2 = TColStd_Array1OfReal(1, 3) knots2.SetValue(1, 0.0) knots2.SetValue(2, 0.3) knots2.SetValue(3, 1.0) curve2 = Geom_BSplineCurve(poles, knots2, mults, 3, False) e2 = Edge(BRepBuilderAPI_MakeEdge(curve2).Edge()) assert e1.geom_type == GeomType.BSPLINE # Different internal knot position = different geometric shape assert (e1 @ 0.5) != (e2 @ 0.5) assert not e1.geom_equal(e2) def test_different_multiplicities(self): """BSplines with same poles/knots but different multiplicities have different shapes.""" from OCP.Geom import Geom_BSplineCurve from OCP.TColgp import TColgp_Array1OfPnt from OCP.TColStd import TColStd_Array1OfReal, TColStd_Array1OfInteger from OCP.gp import gp_Pnt from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge # Same 7 poles for both curves poles = TColgp_Array1OfPnt(1, 7) poles.SetValue(1, gp_Pnt(0, 0, 0)) poles.SetValue(2, gp_Pnt(1, 2, 0)) poles.SetValue(3, gp_Pnt(2, 1, 0)) poles.SetValue(4, gp_Pnt(3, 2, 0)) poles.SetValue(5, gp_Pnt(4, 1, 0)) poles.SetValue(6, gp_Pnt(5, 2, 0)) poles.SetValue(7, gp_Pnt(6, 0, 0)) # Same 4 knots for both curves knots = TColStd_Array1OfReal(1, 4) knots.SetValue(1, 0.0) knots.SetValue(2, 0.33) knots.SetValue(3, 0.67) knots.SetValue(4, 1.0) # Multiplicities [4, 1, 2, 4] - sum = 11 = 7 + 3 + 1 mults1 = TColStd_Array1OfInteger(1, 4) mults1.SetValue(1, 4) mults1.SetValue(2, 1) mults1.SetValue(3, 2) mults1.SetValue(4, 4) curve1 = Geom_BSplineCurve(poles, knots, mults1, 3, False) e1 = Edge(BRepBuilderAPI_MakeEdge(curve1).Edge()) # Multiplicities [4, 2, 1, 4] - same sum, swapped internal mults mults2 = TColStd_Array1OfInteger(1, 4) mults2.SetValue(1, 4) mults2.SetValue(2, 2) mults2.SetValue(3, 1) mults2.SetValue(4, 4) curve2 = Geom_BSplineCurve(poles, knots, mults2, 3, False) e2 = Edge(BRepBuilderAPI_MakeEdge(curve2).Edge()) assert e1.geom_type == GeomType.BSPLINE assert e2.geom_type == GeomType.BSPLINE # Same poles, same knots, different multiplicities = different shape assert (e1 @ 0.5) != (e2 @ 0.5) assert not e1.geom_equal(e2) def test_rational_bspline_different_weights(self): """Rational BSplines with different weights.""" from OCP.Geom import Geom_BSplineCurve from OCP.TColgp import TColgp_Array1OfPnt from OCP.TColStd import TColStd_Array1OfReal, TColStd_Array1OfInteger from OCP.gp import gp_Pnt from OCP.BRepBuilderAPI import BRepBuilderAPI_MakeEdge poles = TColgp_Array1OfPnt(1, 4) poles.SetValue(1, gp_Pnt(0, 0, 0)) poles.SetValue(2, gp_Pnt(1, 1, 0)) poles.SetValue(3, gp_Pnt(2, 1, 0)) poles.SetValue(4, gp_Pnt(3, 0, 0)) knots = TColStd_Array1OfReal(1, 2) knots.SetValue(1, 0.0) knots.SetValue(2, 1.0) mults = TColStd_Array1OfInteger(1, 2) mults.SetValue(1, 4) mults.SetValue(2, 4) # Weights [1, 2, 2, 1] weights1 = TColStd_Array1OfReal(1, 4) weights1.SetValue(1, 1.0) weights1.SetValue(2, 2.0) weights1.SetValue(3, 2.0) weights1.SetValue(4, 1.0) curve1 = Geom_BSplineCurve(poles, weights1, knots, mults, 3, False) e1 = Edge(BRepBuilderAPI_MakeEdge(curve1).Edge()) # Weights [1, 3, 3, 1] weights2 = TColStd_Array1OfReal(1, 4) weights2.SetValue(1, 1.0) weights2.SetValue(2, 3.0) weights2.SetValue(3, 3.0) weights2.SetValue(4, 1.0) curve2 = Geom_BSplineCurve(poles, weights2, knots, mults, 3, False) e2 = Edge(BRepBuilderAPI_MakeEdge(curve2).Edge()) assert e1.geom_type == GeomType.BSPLINE assert not e1.geom_equal(e2) class TestEdgeGeomEqualOffset: """Tests for Edge.geom_equal with OFFSET type.""" def test_same_offset(self): v = [Vertex(p) for p in ((0, 0), (1, 1), (2, 0), (3, 1))] s = Spline(*v) w = Wire([s.edge()]) offset_wire1 = w.offset_2d(0.1) offset_wire2 = w.offset_2d(0.1) offset_edges1 = [ e for e in offset_wire1.edges() if e.geom_type == GeomType.OFFSET ] offset_edges2 = [ e for e in offset_wire2.edges() if e.geom_type == GeomType.OFFSET ] assert len(offset_edges1) > 0 assert offset_edges1[0].geom_equal(offset_edges2[0]) def test_different_offset_value(self): v = [Vertex(p) for p in ((0, 0), (1, 1), (2, 0), (3, 1))] s = Spline(*v) w = Wire([s.edge()]) offset_wire1 = w.offset_2d(0.1) offset_wire2 = w.offset_2d(0.2) offset_edges1 = [ e for e in offset_wire1.edges() if e.geom_type == GeomType.OFFSET ] offset_edges2 = [ e for e in offset_wire2.edges() if e.geom_type == GeomType.OFFSET ] assert not offset_edges1[0].geom_equal(offset_edges2[0]) def test_different_offset_direction(self): """Offset curves with different offset directions (on different planes).""" from build123d import Axis v = [Vertex(p) for p in ((0, 0), (1, 1), (2, 0), (3, 1))] s = Spline(*v) w = Wire([s.edge()]) # Offset on XY plane (Z direction) offset_wire1 = w.offset_2d(0.1) offset_edges1 = [ e for e in offset_wire1.edges() if e.geom_type == GeomType.OFFSET ] # Rotate wire 90 degrees around X axis to put it on XZ plane w_rotated = w.rotate(Axis.X, 90) offset_wire2 = w_rotated.offset_2d(0.1) offset_edges2 = [ e for e in offset_wire2.edges() if e.geom_type == GeomType.OFFSET ] if len(offset_edges1) > 0 and len(offset_edges2) > 0: # Different directions means not equal assert not offset_edges1[0].geom_equal(offset_edges2[0]) class TestEdgeGeomEqualTolerance: """Tests for tolerance behavior in Edge.geom_equal.""" def test_circle_radius_within_tolerance(self): """Circle radii differing by less than tolerance are equal.""" e1 = Edge.make_circle(10.0) e2 = Edge.make_circle(10.0 + 1e-7) # Within default tol=1e-6 assert e1.geom_equal(e2) def test_circle_radius_outside_tolerance(self): """Circle radii differing by more than tolerance are not equal.""" e1 = Edge.make_circle(10.0) e2 = Edge.make_circle(10.0 + 1e-5) # Outside default tol=1e-6 assert not e1.geom_equal(e2) def test_tol_parameter_accepted(self): """The tol parameter is accepted by geom_equal. Note: The tol parameter affects property comparisons (radius, focal length, weights, knots, offset values) but endpoint comparison always uses Vector's built-in TOLERANCE (1e-6). Since most geometric differences also change endpoints, the custom tol has limited practical effect. """ e1 = Edge.make_line((0, 0), (1, 1)) e2 = Edge.make_line((0, 0), (1, 1)) # Parameter is accepted assert e1.geom_equal(e2, tol=1e-9) assert e1.geom_equal(e2, tol=0.1) def test_line_endpoint_within_tolerance(self): """Line endpoints differing by less than tolerance are equal.""" e1 = Edge.make_line((0, 0, 0), (1, 1, 1)) e2 = Edge.make_line((0, 0, 0), (1 + 1e-7, 1, 1)) assert e1.geom_equal(e2) def test_line_endpoint_outside_tolerance(self): """Line endpoints differing by more than tolerance are not equal.""" e1 = Edge.make_line((0, 0, 0), (1, 1, 1)) e2 = Edge.make_line((0, 0, 0), (1.001, 1, 1)) assert not e1.geom_equal(e2) class TestEdgeGeomEqualReversed: """Tests for reversed edge comparison.""" def test_line_reversed_not_equal(self): """Reversed line is not equal (different direction).""" e1 = Edge.make_line((0, 0), (1, 1)) e2 = Edge.make_line((1, 1), (0, 0)) assert not e1.geom_equal(e2) def test_arc_reversed_not_equal(self): """Reversed arc is not equal.""" e1 = Edge.make_circle(10, start_angle=0, end_angle=90) e2 = Edge.make_circle(10, start_angle=0, end_angle=90).reversed() # Reversed edge has swapped start/end points assert not e1.geom_equal(e2) def test_spline_reversed_not_equal(self): """Reversed spline is not equal.""" pts = [(0, 0), (1, 1), (2, 0), (3, 1)] s = Spline(*[Vertex(p) for p in pts]) e1 = s.edge() e2 = e1.reversed() assert not e1.geom_equal(e2) class TestEdgeGeomEqualArcVariations: """Tests for arc edge cases.""" def test_full_circle_equal(self): """Two full circles are equal.""" e1 = Edge.make_circle(10) e2 = Edge.make_circle(10) assert e1.geom_equal(e2) def test_arc_different_start_same_sweep(self): """Arcs with different start angles but same sweep are not equal.""" e1 = Edge.make_circle(10, start_angle=0, end_angle=90) e2 = Edge.make_circle(10, start_angle=90, end_angle=180) # Same radius and sweep angle, but different positions assert not e1.geom_equal(e2) def test_arc_same_endpoints_different_direction(self): """Arcs with same endpoints but opposite sweep direction.""" from build123d import AngularDirection e1 = Edge.make_circle( 10, start_angle=0, end_angle=90, angular_direction=AngularDirection.COUNTER_CLOCKWISE, ) e2 = Edge.make_circle( 10, start_angle=90, end_angle=0, angular_direction=AngularDirection.CLOCKWISE, ) # These trace different paths (short arc vs long arc) assert not e1.geom_equal(e2) class TestEdgeGeomEqualNumerical: """Tests for numerical edge cases.""" def test_very_small_edge(self): """Very small edges can be compared.""" e1 = Edge.make_line((0, 0), (1e-6, 1e-6)) e2 = Edge.make_line((0, 0), (1e-6, 1e-6)) assert e1.geom_equal(e2) def test_very_large_coordinates(self): """Edges with large coordinates can be compared.""" e1 = Edge.make_line((1e6, 1e6), (1e6 + 1, 1e6 + 1)) e2 = Edge.make_line((1e6, 1e6), (1e6 + 1, 1e6 + 1)) assert e1.geom_equal(e2) def test_large_coordinates_small_difference(self): """Small differences at large coordinates.""" e1 = Edge.make_line((1e6, 1e6), (1e6 + 1, 1e6 + 1)) e2 = Edge.make_line((1e6, 1e6), (1e6 + 1 + 1e-5, 1e6 + 1)) # Difference is above tolerance assert not e1.geom_equal(e2) class TestEdgeGeomEqual3DPositioning: """Tests for 3D positioning edge cases.""" def test_same_shape_different_z(self): """Same 2D shape at different Z levels are not equal.""" e1 = Edge.make_circle(10) e2 = Edge.make_circle(10).locate(Location((0, 0, 5))) assert not e1.geom_equal(e2) def test_line_in_3d(self): """3D lines with same geometry are equal.""" e1 = Edge.make_line((0, 0, 0), (1, 2, 3)) e2 = Edge.make_line((0, 0, 0), (1, 2, 3)) assert e1.geom_equal(e2) def test_line_in_3d_different(self): """3D lines with different Z are not equal.""" e1 = Edge.make_line((0, 0, 0), (1, 1, 0)) e2 = Edge.make_line((0, 0, 0), (1, 1, 1)) assert not e1.geom_equal(e2) class TestEdgeGeomEqualSplineVariations: """Tests for BSpline edge cases.""" def test_spline_control_point_within_tolerance(self): """Splines with control points within tolerance are equal.""" pts1 = [(0, 0), (1, 1), (2, 0)] pts2 = [(0, 0), (1 + 1e-7, 1), (2, 0)] e1 = Spline(*[Vertex(p) for p in pts1]).edge() e2 = Spline(*[Vertex(p) for p in pts2]).edge() assert e1.geom_equal(e2) def test_spline_control_point_outside_tolerance(self): """Splines with control points outside tolerance are not equal.""" pts1 = [(0, 0), (1, 1), (2, 0)] pts2 = [(0, 0), (1.001, 1), (2, 0)] e1 = Spline(*[Vertex(p) for p in pts1]).edge() e2 = Spline(*[Vertex(p) for p in pts2]).edge() assert not e1.geom_equal(e2) def test_spline_different_point_count(self): """Splines with different number of control points are not equal.""" pts1 = [(0, 0), (1, 1), (2, 0)] pts2 = [(0, 0), (0.5, 0.5), (1, 1), (2, 0)] e1 = Spline(*[Vertex(p) for p in pts1]).edge() e2 = Spline(*[Vertex(p) for p in pts2]).edge() # Different number of poles assert not e1.geom_equal(e2) class TestEdgeGeomEqualUnknownType: """Tests for the fallback case (OTHER/unknown geom types).""" def test_interpolation_points_used(self): """For unknown types, sample points are compared.""" # Create edges that would use the fallback path # Most common types are handled, but we can test the parameter e1 = Edge.make_line((0, 0), (1, 1)) e2 = Edge.make_line((0, 0), (1, 1)) # Even with different num_interpolation_points, these should be equal assert e1.geom_equal(e2, num_interpolation_points=3) assert e1.geom_equal(e2, num_interpolation_points=10) class TestWireGeomEqual: """Tests for Wire.geom_equal method.""" def test_same_rectangle_wire(self): r1 = Rectangle(10, 5) r2 = Rectangle(10, 5) assert r1.wire().geom_equal(r2.wire()) def test_different_rectangle_wire(self): r1 = Rectangle(10, 5) r2 = Rectangle(10, 6) assert not r1.wire().geom_equal(r2.wire()) def test_same_spline_wire(self): v = [Vertex(p) for p in ((0, 0), (1, 1), (2, 0), (3, 1))] s1 = Spline(*v) s2 = Spline(*v) w1 = Wire([s1.edge()]) w2 = Wire([s2.edge()]) assert w1.geom_equal(w2) def test_different_edge_count(self): r1 = Rectangle(10, 5) e = Edge.make_line((0, 0), (10, 0)) w1 = r1.wire() w2 = Wire([e]) assert not w1.geom_equal(w2) def test_identical_edge_objects(self): """Two wires sharing the same edge objects.""" e1 = Edge.make_line((0, 0), (1, 0)) e2 = Edge.make_line((1, 0), (1, 1)) e3 = Edge.make_line((1, 1), (0, 0)) w1 = Wire([e1, e2, e3]) w2 = Wire([e1, e2, e3]) # Same edge objects assert w1.geom_equal(w2) def test_geometrically_equal_edges(self): """Two wires with geometrically equal but distinct edge objects.""" # Wire 1 e1a = Edge.make_line((0, 0), (1, 0)) e2a = Edge.make_line((1, 0), (1, 1)) e3a = Edge.make_line((1, 1), (0, 0)) w1 = Wire([e1a, e2a, e3a]) # Wire 2 - same geometry, different objects e1b = Edge.make_line((0, 0), (1, 0)) e2b = Edge.make_line((1, 0), (1, 1)) e3b = Edge.make_line((1, 1), (0, 0)) w2 = Wire([e1b, e2b, e3b]) assert w1.geom_equal(w2) def test_edges_different_start_point(self): """Two closed wires with same geometry but different starting vertex are not equal.""" # Wire 1: starts at (0,0) e1a = Edge.make_line((0, 0), (1, 0)) e2a = Edge.make_line((1, 0), (1, 1)) e3a = Edge.make_line((1, 1), (0, 0)) w1 = Wire([e1a, e2a, e3a]) # Wire 2: starts at (1,1) due to different edge order in constructor e3b = Edge.make_line((1, 1), (0, 0)) e1b = Edge.make_line((0, 0), (1, 0)) e2b = Edge.make_line((1, 0), (1, 1)) w2 = Wire([e3b, e1b, e2b]) # Different starting point means not equal assert not w1.geom_equal(w2) def test_one_edge_reversed(self): """Two wires where one has an edge with reversed direction.""" # Wire 1: all edges in forward direction e1a = Edge.make_line((0, 0), (1, 0)) e2a = Edge.make_line((1, 0), (1, 1)) e3a = Edge.make_line((1, 1), (0, 0)) w1 = Wire([e1a, e2a, e3a]) # Wire 2: middle edge is reversed (direction (1,1) -> (1,0) instead of (1,0) -> (1,1)) e1b = Edge.make_line((0, 0), (1, 0)) e2b = Edge.make_line((1, 1), (1, 0)) # Reversed! e3b = Edge.make_line((1, 1), (0, 0)) w2 = Wire([e1b, e2b, e3b]) # order_edges should correct the orientation assert w1.geom_equal(w2) def test_closed_wire(self): """Two closed wires with same geometry.""" w1 = Wire( [ Edge.make_line((0, 0), (2, 0)), Edge.make_line((2, 0), (2, 2)), Edge.make_line((2, 2), (0, 2)), Edge.make_line((0, 2), (0, 0)), ] ) w2 = Wire( [ Edge.make_line((0, 0), (2, 0)), Edge.make_line((2, 0), (2, 2)), Edge.make_line((2, 2), (0, 2)), Edge.make_line((0, 2), (0, 0)), ] ) assert w1.is_closed assert w2.is_closed assert w1.geom_equal(w2) def test_mixed_edge_types(self): """Wires with mixed edge types (lines and arcs).""" # Wire with line + arc + line e1a = Edge.make_line((0, 0), (1, 0)) e2a = Edge.make_circle(0.5, start_angle=0, end_angle=180).locate( Location((1.5, 0, 0)) ) e3a = Edge.make_line((2, 0), (3, 0)) w1 = Wire([e1a, e2a, e3a]) e1b = Edge.make_line((0, 0), (1, 0)) e2b = Edge.make_circle(0.5, start_angle=0, end_angle=180).locate( Location((1.5, 0, 0)) ) e3b = Edge.make_line((2, 0), (3, 0)) w2 = Wire([e1b, e2b, e3b]) assert w1.geom_equal(w2) def test_mixed_edge_types_different(self): """Wires with mixed edge types that differ.""" # Wire 1: line + arc e1a = Edge.make_line((0, 0), (1, 0)) e2a = Edge.make_circle(0.5, start_angle=0, end_angle=180).locate( Location((1.5, 0, 0)) ) w1 = Wire([e1a, e2a]) # Wire 2: line + different arc (different radius) e1b = Edge.make_line((0, 0), (1, 0)) e2b = Edge.make_circle(0.6, start_angle=0, end_angle=180).locate( Location((1.6, 0, 0)) ) w2 = Wire([e1b, e2b]) assert not w1.geom_equal(w2) def test_all_edges_reversed_not_equal(self): """Wire traced in opposite direction is not equal.""" # Wire 1: (0,0) -> (3,0) e1a = Edge.make_line((0, 0), (1, 0)) e2a = Edge.make_line((1, 0), (2, 1)) e3a = Edge.make_line((2, 1), (3, 0)) w1 = Wire([e1a, e2a, e3a]) # Wire 2: (3,0) -> (0,0) - same path but opposite direction e1b = Edge.make_line((1, 0), (0, 0)) e2b = Edge.make_line((2, 1), (1, 0)) e3b = Edge.make_line((3, 0), (2, 1)) w2 = Wire([e3b, e2b, e1b]) assert not w1.geom_equal(w2) def test_open_wire_different_start(self): """Open wires with same edges but different starting edge - should not match.""" # For open wires, the start matters e1 = Edge.make_line((0, 0), (1, 0)) e2 = Edge.make_line((1, 0), (2, 1)) e3 = Edge.make_line((2, 1), (3, 0)) w1 = Wire([e1, e2, e3]) # Different edges entirely (shifted) e4 = Edge.make_line((1, 0), (2, 0)) e5 = Edge.make_line((2, 0), (3, 1)) e6 = Edge.make_line((3, 1), (4, 0)) w2 = Wire([e4, e5, e6]) assert not w1.geom_equal(w2) def test_wire_with_spline_edges(self): """Wires containing spline edges.""" pts1 = [(0, 0), (1, 1), (2, 0), (3, 1), (4, 0)] pts2 = [(4, 0), (5, 1), (6, 0)] s1a = Spline(*[Vertex(p) for p in pts1]) s2a = Spline(*[Vertex(p) for p in pts2]) w1 = Wire([s1a.edge(), s2a.edge()]) s1b = Spline(*[Vertex(p) for p in pts1]) s2b = Spline(*[Vertex(p) for p in pts2]) w2 = Wire([s1b.edge(), s2b.edge()]) assert w1.geom_equal(w2) def test_single_edge_wire(self): """Wires with single edge.""" w1 = Wire([Edge.make_line((0, 0), (5, 5))]) w2 = Wire([Edge.make_line((0, 0), (5, 5))]) assert w1.geom_equal(w2) def test_single_edge_wire_reversed_not_equal(self): """Single edge wire vs reversed single edge wire are not equal.""" w1 = Wire([Edge.make_line((0, 0), (5, 5))]) w2 = Wire([Edge.make_line((5, 5), (0, 0))]) # Opposite direction means not equal assert not w1.geom_equal(w2) class TestGeomEqualTypeMismatch: """Tests for type mismatch cases.""" def test_edge_vs_non_edge(self): e = Edge.make_line((0, 0), (1, 1)) w = Wire([e]) # Edge.geom_equal should return False for non-Edge assert not e.geom_equal(w) def test_wire_vs_non_wire(self): e = Edge.make_line((0, 0), (1, 1)) w = Wire([e]) # Wire.geom_equal should return False for non-Wire assert not w.geom_equal(e) def test_different_geom_types(self): line = Edge.make_line((0, 0, 0), (1, 1, 1)) circle = Circle(10).edge() assert not line.geom_equal(circle)