diff --git a/cadquery/hull.py b/cadquery/hull.py index a10131b10..79568bae9 100644 --- a/cadquery/hull.py +++ b/cadquery/hull.py @@ -87,7 +87,7 @@ def atan2p(x, y): def convert_and_validate(edges: Iterable[Edge]) -> Tuple[List[Arc], List[Point]]: - arcs: Set[Arc] = set() + arcs_by_center: dict = {} points: Set[Point] = set() for e in edges: @@ -102,14 +102,16 @@ def convert_and_validate(edges: Iterable[Edge]) -> Tuple[List[Arc], List[Point]] elif gt == "CIRCLE": c = e.arcCenter() r = e.radius() - a1, a2 = e._bounds() + key = (c.x, c.y) - arcs.add(Arc(Point(c.x, c.y), r, a1, a2)) + # keep one arc per center (largest radius for the hull) + if key not in arcs_by_center or r > arcs_by_center[key].r: + arcs_by_center[key] = Arc(Point(c.x, c.y), r, 0, 2 * pi) else: raise ValueError("Unsupported geometry {gt}") - return list(arcs), list(points) + return list(arcs_by_center.values()), list(points) def select_lowest_point(points: Points) -> Tuple[Point, int]: diff --git a/tests/test_hull.py b/tests/test_hull.py index 976783ea8..3b8210883 100644 --- a/tests/test_hull.py +++ b/tests/test_hull.py @@ -23,6 +23,36 @@ def test_hull(): assert h.isValid() +def test_hull_overlapping_circles(): + """Hull of overlapping circles should not raise ZeroDivisionError. + + When two circles overlap, boolean face fusion splits them into + multiple arc segments sharing the same center. arc_arc() must + handle these concentric arcs without dividing by zero. + """ + from cadquery import Sketch + + s = Sketch().push([(-19, 0), (19, 0)]).circle(35).reset().hull() + + assert s._faces.Area() > 0 + assert len(s._faces.Faces()) >= 1 + + +def test_hull_overlapping_circles_equal_radii_via_face(): + """Hull via .face() with overlapping equal-radii circles.""" + from cadquery import Sketch, Location, Vector + + s = ( + Sketch() + .face(Sketch().circle(35).moved(Location(Vector(-19, 0, 0)))) + .face(Sketch().circle(35).moved(Location(Vector(19, 0, 0)))) + .hull() + ) + + assert s._faces.Area() > 0 + assert len(s._faces.Faces()) >= 1 + + def test_validation(): with pytest.raises(ValueError):