mirror of
https://github.com/gumyr/build123d.git
synced 2026-03-09 08:11:37 -07:00
968 lines
34 KiB
Python
968 lines
34 KiB
Python
"""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)
|