From dc97bc3440ac83f88f311e49e36264aa7c4a9f85 Mon Sep 17 00:00:00 2001 From: David Straub Date: Fri, 2 Oct 2026 14:50:48 +0200 Subject: [PATCH 1/3] Accept Shape in Solid.fillet/chamfer Mixin3D.fillet/chamfer now take edgeList: Shape | Iterable[Edge]. A Shape is normalized with _get_edges(edges.edges()), as the free functions do. This fixes mypy/Pyright errors when passing Shape.edges(...) and a runtime crash when the selection is a single Edge. Fixes #2104 Co-Authored-By: Claude Opus 5.5 --- cadquery/occ_impl/shapes.py | 26 ++++++++++++++++++++------ tests/test_free_functions.py | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 7eb28caca..711171798 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -4027,15 +4027,15 @@ def makeShell(cls, listOfFaces: Iterable[Face]) -> Shell: class Mixin3D(object): - def fillet(self: Any, radius: float, edgeList: Iterable[Edge]) -> Any: + def fillet(self: Any, radius: float, edgeList: Shape | Iterable[Edge]) -> Any: """ Fillets the specified edges of this solid. :param radius: float > 0, the radius of the fillet - :param edgeList: a list of Edge objects, which must belong to this solid + :param edgeList: a list of Edge objects or a Shape containing edges, which must belong to this solid :return: Filleted solid """ - nativeEdges = [e.wrapped for e in edgeList] + nativeEdges = [e.wrapped for e in _edge_list(edgeList)] fillet_builder = BRepFilletAPI_MakeFillet(self.wrapped) @@ -4045,17 +4045,20 @@ def fillet(self: Any, radius: float, edgeList: Iterable[Edge]) -> Any: return self.__class__(fillet_builder.Shape()) def chamfer( - self: Any, length: float, length2: float | None, edgeList: Iterable[Edge] + self: Any, + length: float, + length2: float | None, + edgeList: Shape | Iterable[Edge], ) -> Any: """ Chamfers the specified edges of this solid. :param length: length > 0, the length (length) of the chamfer :param length2: length2 > 0, optional parameter for asymmetrical chamfer. Should be `None` if not required. - :param edgeList: a list of Edge objects, which must belong to this solid + :param edgeList: a list of Edge objects or a Shape containing edges, which must belong to this solid :return: Chamfered solid """ - nativeEdges = [e.wrapped for e in edgeList] + nativeEdges = [e.wrapped for e in _edge_list(edgeList)] # make a edge --> faces mapping edge_face_map = TopTools_IndexedDataMapOfShapeListOfShape() @@ -5304,6 +5307,17 @@ def _get_edges(*shapes: Shape) -> Iterable[Edge]: raise ValueError(f"Required type(s): Edge, Wire; encountered {t}") +def _edge_list(edges: Shape | Iterable[Edge]) -> Iterable[Edge]: + """ + Normalize a Shape or an iterable of edges to an iterable of edges. + """ + + if isinstance(edges, Shape): + return _get_edges(edges.edges()) + + return edges + + def _get_faces(*shapes: Shape) -> Iterable[Face]: """ Get faces or faces from wires or edges. diff --git a/tests/test_free_functions.py b/tests/test_free_functions.py index 24f7d8722..3802aefd8 100644 --- a/tests/test_free_functions.py +++ b/tests/test_free_functions.py @@ -1023,6 +1023,20 @@ def test_chamfer(): assert r.faces(">Z").Area() < 1 +@pytest.mark.parametrize("sel, n", [("|Z", 4), (">X and >Y", 1)]) +def test_fillet_chamfer_methods_shape_arg(sel, n): + + b = box(1, 1, 1) + edges = b.edges(sel) + + r1 = b.fillet(0.1, edges) + r2 = b.chamfer(0.1, None, edges) + + assert r1.isValid() + assert r2.isValid() + assert len(r1.Edges()) == len(r2.Edges()) == 12 + 3 * n + + def test_extrude(): v = vertex(0, 0, 0) From 8ebae4e541d46c06f8257411e79b34285ca3561f Mon Sep 17 00:00:00 2001 From: AU Date: Sun, 4 Oct 2026 13:23:28 +0200 Subject: [PATCH 2/3] Apply batched suggestions from code review Co-authored-by: AU --- cadquery/occ_impl/shapes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index 711171798..c8d79195a 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -4058,7 +4058,7 @@ def chamfer( :param edgeList: a list of Edge objects or a Shape containing edges, which must belong to this solid :return: Chamfered solid """ - nativeEdges = [e.wrapped for e in _edge_list(edgeList)] + nativeEdges = [e.wrapped for e in _get_edge_list(edgeList)] # make a edge --> faces mapping edge_face_map = TopTools_IndexedDataMapOfShapeListOfShape() @@ -5307,7 +5307,7 @@ def _get_edges(*shapes: Shape) -> Iterable[Edge]: raise ValueError(f"Required type(s): Edge, Wire; encountered {t}") -def _edge_list(edges: Shape | Iterable[Edge]) -> Iterable[Edge]: +def _get_edge_list(edges: Shape | Iterable[Edge]) -> Iterable[Edge]: """ Normalize a Shape or an iterable of edges to an iterable of edges. """ From cb440436400b86254f10f3d0489b9e50f2f71306 Mon Sep 17 00:00:00 2001 From: AU Date: Sun, 4 Oct 2026 13:34:32 +0200 Subject: [PATCH 3/3] Apply suggestion from @adam-urbanczyk --- cadquery/occ_impl/shapes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadquery/occ_impl/shapes.py b/cadquery/occ_impl/shapes.py index c8d79195a..97a6bd65d 100644 --- a/cadquery/occ_impl/shapes.py +++ b/cadquery/occ_impl/shapes.py @@ -4035,7 +4035,7 @@ def fillet(self: Any, radius: float, edgeList: Shape | Iterable[Edge]) -> Any: :param edgeList: a list of Edge objects or a Shape containing edges, which must belong to this solid :return: Filleted solid """ - nativeEdges = [e.wrapped for e in _edge_list(edgeList)] + nativeEdges = [e.wrapped for e in _get_edge_list(edgeList)] fillet_builder = BRepFilletAPI_MakeFillet(self.wrapped)