diff --git a/cadquery/hull.py b/cadquery/hull.py index a10131b10..88f40d6c8 100644 --- a/cadquery/hull.py +++ b/cadquery/hull.py @@ -18,6 +18,9 @@ Entity = Union["Arc", "Point"] Hull = List[Union["Arc", "Point", "Segment"]] +# minimum arc span; below this makeCircle would return a full circle +TOL = 1e-9 + class Point: @@ -39,7 +42,7 @@ def __hash__(self): def __eq__(self, other): - return (self.x, self.y) == (other.x, other.y) + return type(self) == type(other) and (self.x, self.y) == (other.x, other.y) class Segment: @@ -74,6 +77,16 @@ def __init__(self, c: Point, r: float, a1: float, a2: float): self.e = Point(r * cos(a2), r * sin(a2)) self.ac = 2 * pi - (a1 - a2) + def __hash__(self): + + return hash((self.c, self.r, self.a1, self.a2)) + + def __eq__(self, other): + + return type(self) == type(other) and ( + (self.c, self.r, self.a1, self.a2) == (other.c, other.r, other.a1, other.a2) + ) + def atan2p(x, y): @@ -348,9 +361,10 @@ def finalize_hull(hull: Hull) -> Wire: a1 = degrees(atan2p(el_p.b.x - el.c.x, el_p.b.y - el.c.y)) a2 = degrees(atan2p(el_n.a.x - el.c.x, el_n.a.y - el.c.y)) - rv.append( - Edge.makeCircle(el.r, Vector(el.c.x, el.c.y), angle1=a1, angle2=a2) - ) + if abs(a2 - a1) > TOL: + rv.append( + Edge.makeCircle(el.r, Vector(el.c.x, el.c.y), angle1=a1, angle2=a2) + ) el1 = hull[1] if isinstance(el, Segment) and isinstance(el_n, Arc) and isinstance(el1, Segment): diff --git a/tests/test_hull.py b/tests/test_hull.py index 976783ea8..6b83e2476 100644 --- a/tests/test_hull.py +++ b/tests/test_hull.py @@ -1,3 +1,6 @@ +from itertools import permutations +from math import pi + import pytest import cadquery as cq @@ -30,3 +33,41 @@ def test_validation(): e1 = cq.Edge.makeEllipse(2, 1) c1 = cq.Edge.makeCircle(0.5, (-1.5, 0.5, 0)) hull.find_hull([c1, e1]) + + +def test_collinear(): + + r = 2.5 + spacing = 8.0 + + # collinear centres let an inner circle enter the hull as a zero span arc; + # only some traversal orders reach it, so permute the input + for n in (3, 4, 5): + + expected = spacing * (n - 1) * 2 * r + pi * r ** 2 + + for order in permutations(range(n)): + + edges = [cq.Edge.makeCircle(r, (i * spacing, 0, 0)) for i in order] + + h = hull.find_hull(edges) + + assert h.IsClosed() + assert h.isValid() + assert cq.Face.makeFromWires(h).Area() == pytest.approx(expected) + + +def test_eq(): + + a = hull.Arc(hull.Point(0.0, 0.0), 1.0, 0.0, 2 * pi) + b = hull.Arc(hull.Point(0.0, 0.0), 1.0, 0.0, 2 * pi) + p = hull.Point(0.0, 0.0) + + assert a == b + assert hash(a) == hash(b) + assert p == hull.Point(0.0, 0.0) + + assert a != hull.Arc(hull.Point(0.0, 0.0), 2.0, 0.0, 2 * pi) + assert a != p + assert p != a + assert a != None