From 93526847b5cf5d76ebd55ee4ebbdee5dfa8b8f7f Mon Sep 17 00:00:00 2001 From: kimstik Date: Mon, 14 Sep 2026 12:13:28 +0200 Subject: [PATCH 1/2] 99.6% hull.py test coverage --- cadquery/hull.py | 9 +++++++++ tests/test_hull.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/cadquery/hull.py b/cadquery/hull.py index 88f40d6c8..d709444a1 100644 --- a/cadquery/hull.py +++ b/cadquery/hull.py @@ -386,6 +386,11 @@ def find_hull(edges: Iterable[Edge]) -> Wire: # split into arcs and points arcs, points = convert_and_validate(edges) + # a lone circle is its own hull + if len(arcs) == 1 and not points: + a = arcs[0] + return Wire.assembleEdges([Edge.makeCircle(a.r, Vector(a.c.x, a.c.y))]) + # select the starting element start = select_lowest(arcs, points) rv.append(start) @@ -411,6 +416,10 @@ def find_hull(edges: Iterable[Edge]) -> Wire: segments.append(segment) next_ix = int(argmin(angles)) + + if angles[next_ix] == inf: + raise ValueError("Hull could not be closed") + current_e, current_angle, finished = update_hull( current_e, next_ix, entities, angles, segments, rv ) diff --git a/tests/test_hull.py b/tests/test_hull.py index 6b83e2476..83f37a82d 100644 --- a/tests/test_hull.py +++ b/tests/test_hull.py @@ -7,6 +7,10 @@ from cadquery import hull +def area(edges): + return cq.Face.makeFromWires(hull.find_hull(edges)).Area() + + def test_hull(): c1 = cq.Edge.makeCircle(0.5, (-1.5, 0.5, 0)) @@ -71,3 +75,40 @@ def test_eq(): assert a != p assert p != a assert a != None + + +def test_lines_only(): + edges = [ + cq.Edge.makeLine(cq.Vector(0, 0), cq.Vector(4, 0)), + cq.Edge.makeLine(cq.Vector(4, 0), cq.Vector(0, 3)), + cq.Edge.makeLine(cq.Vector(0, 3), cq.Vector(0, 0)), + ] + + assert area(edges) == pytest.approx(6.0) + + +def test_empty(): + with pytest.raises(ValueError): + hull.find_hull([]) + + +def test_arc_inside_hull(): + outer = [cq.Edge.makeCircle(20.0, (0, 0, 0)), cq.Edge.makeCircle(20.0, (60, 0, 0))] + arc = cq.Edge.makeCircle(5.0, (30, 0, 0), angle1=0, angle2=180) + + assert area(outer + [arc]) == pytest.approx(area(outer)) + + +def test_single_circle(): + assert area([cq.Edge.makeCircle(5.0, (0, 0, 0))]) == pytest.approx(25 * pi) + + +def test_stalled_march(): + # valid input the march cannot close; it used to loop forever + edges = [ + cq.Edge.makeCircle(6.0, (0, 12, 0)), + cq.Edge.makeLine(cq.Vector(-2, 5), cq.Vector(9, 10)), + ] + + with pytest.raises(ValueError): + hull.find_hull(edges) From 268a99a3cf212a4e4a0ba1ce485540d411a9aea2 Mon Sep 17 00:00:00 2001 From: kimstik Date: Mon, 14 Sep 2026 12:57:14 +0200 Subject: [PATCH 2/2] Fix the centre offset in Arc.s and Arc.e They were computed as if every arc were centred at the origin, so select_lowest_arc compared meaningless points for partial arcs. --- cadquery/hull.py | 4 ++-- tests/test_hull.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cadquery/hull.py b/cadquery/hull.py index d709444a1..2ea396642 100644 --- a/cadquery/hull.py +++ b/cadquery/hull.py @@ -73,8 +73,8 @@ def __init__(self, c: Point, r: float, a1: float, a2: float): self.a1 = a1 self.a2 = a2 - self.s = Point(r * cos(a1), r * sin(a1)) - self.e = Point(r * cos(a2), r * sin(a2)) + self.s = Point(c.x + r * cos(a1), c.y + r * sin(a1)) + self.e = Point(c.x + r * cos(a2), c.y + r * sin(a2)) self.ac = 2 * pi - (a1 - a2) def __hash__(self): diff --git a/tests/test_hull.py b/tests/test_hull.py index 83f37a82d..aecdb5511 100644 --- a/tests/test_hull.py +++ b/tests/test_hull.py @@ -112,3 +112,10 @@ def test_stalled_march(): with pytest.raises(ValueError): hull.find_hull(edges) + + +def test_arc_endpoints(): + a = hull.Arc(hull.Point(10.0, 20.0), 1.0, 0.0, pi) + + assert (a.s.x, a.s.y) == pytest.approx((11.0, 20.0)) + assert (a.e.x, a.e.y) == pytest.approx((9.0, 20.0))