From 792a87a1fa9ad9da45ddb9678ec7931c0e47eb80 Mon Sep 17 00:00:00 2001 From: Bernhard Date: Fri, 16 Jan 2026 12:51:30 +0100 Subject: [PATCH] add suuport for include_touched keyword, split some tests into intersect/touch, add new tests and fix xpass and xfail --- tests/test_direct_api/test_intersection.py | 106 +++++++++++++-------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/tests/test_direct_api/test_intersection.py b/tests/test_direct_api/test_intersection.py index 758fd6f..581b45e 100644 --- a/tests/test_direct_api/test_intersection.py +++ b/tests/test_direct_api/test_intersection.py @@ -16,14 +16,19 @@ class Case: expected: list | Vector | Location | Axis | Plane name: str xfail: None | str = None + include_touched: bool = False @pytest.mark.skip -def run_test(obj, target, expected): +def run_test(obj, target, expected, include_touched=False): + # Only Shape objects support include_touched parameter + kwargs = {} + if include_touched and isinstance(obj, Shape): + kwargs["include_touched"] = include_touched if isinstance(target, list): - result = obj.intersect(*target) + result = obj.intersect(*target, **kwargs) else: - result = obj.intersect(target) + result = obj.intersect(target, **kwargs) if INTERSECT_DEBUG: show([obj, target, result]) if expected is None: @@ -50,11 +55,15 @@ def make_params(matrix): marks = [pytest.mark.xfail(reason=case.xfail)] else: marks = [] - uid = f"{i} {obj_type}, {tar_type}, {case.name}" - params.append(pytest.param(case.object, case.target, case.expected, marks=marks, id=uid)) - if tar_type != obj_type and not isinstance(case.target, list): - uid = f"{i + 1} {tar_type}, {obj_type}, {case.name}" - params.append(pytest.param(case.target, case.object, case.expected, marks=marks, id=uid)) + # Add include_touched info to test id if specified + touched_suffix = ", touched" if case.include_touched else "" + uid = f"{i} {obj_type}, {tar_type}, {case.name}{touched_suffix}" + params.append(pytest.param(case.object, case.target, case.expected, case.include_touched, marks=marks, id=uid)) + # Swap obj and target to test symmetry, but NOT for include_touched tests + # (swapping may change behavior with boundary contacts) + if tar_type != obj_type and not isinstance(case.target, list) and not case.include_touched: + uid = f"{i + 1} {tar_type}, {obj_type}, {case.name}{touched_suffix}" + params.append(pytest.param(case.target, case.object, case.expected, case.include_touched, marks=marks, id=uid)) return params @@ -118,9 +127,9 @@ geometry_matrix = [ Case(lc1, lc1, Location, "coincident, co-z", None), ] -@pytest.mark.parametrize("obj, target, expected", make_params(geometry_matrix)) -def test_geometry(obj, target, expected): - run_test(obj, target, expected) +@pytest.mark.parametrize("obj, target, expected, include_touched", make_params(geometry_matrix)) +def test_geometry(obj, target, expected, include_touched): + run_test(obj, target, expected, include_touched) # Shape test matrices @@ -147,9 +156,9 @@ shape_0d_matrix = [ Case(vt1, [vt1, lc1], [Vertex], "multi to_intersect, coincident", None), ] -@pytest.mark.parametrize("obj, target, expected", make_params(shape_0d_matrix)) -def test_shape_0d(obj, target, expected): - run_test(obj, target, expected) +@pytest.mark.parametrize("obj, target, expected, include_touched", make_params(shape_0d_matrix)) +def test_shape_0d(obj, target, expected, include_touched): + run_test(obj, target, expected, include_touched) # 1d Shapes @@ -216,9 +225,9 @@ shape_1d_matrix = [ Case(wi5, [ed1, Vector(1, 0)], [Vertex], "multi to_intersect, multi intersect", None), ] -@pytest.mark.parametrize("obj, target, expected", make_params(shape_1d_matrix)) -def test_shape_1d(obj, target, expected): - run_test(obj, target, expected) +@pytest.mark.parametrize("obj, target, expected, include_touched", make_params(shape_1d_matrix)) +def test_shape_1d(obj, target, expected, include_touched): + run_test(obj, target, expected, include_touched) # 2d Shapes @@ -272,7 +281,9 @@ shape_2d_matrix = [ Case(fc1, fc3, [Edge], "intersecting", None), Case(fc1, fc4, [Face], "coplanar", None), Case(fc1, fc5, [Edge], "intersecting edge", None), - Case(fc1, fc6, [Vertex], "intersecting vertex", None), + # Face + Face crossing vertex: now requires include_touched + Case(fc1, fc6, None, "crossing vertex", None), + Case(fc1, fc6, [Vertex], "crossing vertex", None, True), Case(fc1, fc7, [Edge, Edge], "multi-intersecting", None), Case(fc7, Pos(Y=2) * fc7, [Face], "cyl intersecting", None), @@ -287,9 +298,9 @@ shape_2d_matrix = [ Case(fc7, [wi5, fc1], [Vertex], "multi to_intersect, intersecting", None), ] -@pytest.mark.parametrize("obj, target, expected", make_params(shape_2d_matrix)) -def test_shape_2d(obj, target, expected): - run_test(obj, target, expected) +@pytest.mark.parametrize("obj, target, expected, include_touched", make_params(shape_2d_matrix)) +def test_shape_2d(obj, target, expected, include_touched): + run_test(obj, target, expected, include_touched) # 3d Shapes sl1 = Box(2, 2, 2).solid() @@ -323,8 +334,10 @@ shape_3d_matrix = [ Case(sl1, ed3, None, "non-coincident", None), Case(sl1, ed1, [Edge], "intersecting", None), - Case(sl1, Pos(0, 1, 1) * ed1, [Edge], "edge collinear", "duplicate edges, BRepAlgoAPI_Common and _Section both return edge"), - Case(sl1, Pos(1, 1, 1) * ed1, [Vertex], "corner coincident", None), + Case(sl1, Pos(0, 1, 1) * ed1, [Edge], "edge collinear", None), # xfail removed + # Solid + Edge corner coincident: now requires include_touched + Case(sl1, Pos(1, 1, 1) * ed1, None, "corner coincident", None), + Case(sl1, Pos(1, 1, 1) * ed1, [Vertex], "corner coincident", None, True), Case(Pos(2.1, 1) * sl1, ed4, [Edge, Edge], "multi-intersect", None), Case(Pos(2, .5, -1) * sl1, wi6, None, "non-coincident", None), @@ -333,8 +346,12 @@ shape_3d_matrix = [ Case(sl2, fc1, None, "non-coincident", None), Case(sl1, fc1, [Face], "intersecting", None), - Case(Pos(3.5, 0, 1) * sl1, fc1, [Edge], "edge collinear", None), - Case(Pos(3.5, 3.5) * sl1, fc1, [Vertex], "corner coincident", None), + # Solid + Face edge collinear: now requires include_touched + Case(Pos(3.5, 0, 1) * sl1, fc1, None, "edge collinear", None), + Case(Pos(3.5, 0, 1) * sl1, fc1, [Edge], "edge collinear", None, True), + # Solid + Face corner coincident: now requires include_touched + Case(Pos(3.5, 3.5) * sl1, fc1, None, "corner coincident", None), + Case(Pos(3.5, 3.5) * sl1, fc1, [Vertex], "corner coincident", None, True), Case(Pos(.9) * sl1, fc7, [Face, Face], "multi-intersecting", None), Case(sl2, sh1, None, "non-coincident", None), @@ -342,17 +359,24 @@ shape_3d_matrix = [ Case(sl1, sl2, None, "non-coincident", None), Case(sl1, Pos(1, 1, 1) * sl1, [Solid], "intersecting", None), - Case(sl1, Pos(2, 2, 1) * sl1, [Edge], "edge collinear", None), - Case(sl1, Pos(2, 2, 2) * sl1, [Vertex], "corner coincident", None), + # Solid + Solid edge collinear: now requires include_touched + Case(sl1, Pos(2, 2, 1) * sl1, None, "edge collinear", None), + Case(sl1, Pos(2, 2, 1) * sl1, [Edge], "edge collinear", None, True), + # Solid + Solid corner coincident: now requires include_touched + Case(sl1, Pos(2, 2, 2) * sl1, None, "corner coincident", None), + Case(sl1, Pos(2, 2, 2) * sl1, [Vertex], "corner coincident", None, True), Case(sl1, Pos(.45) * sl3, [Solid, Solid], "multi-intersect", None), + # New test: Solid + Solid face coincident (touch) + Case(sl1, Pos(2, 0, 0) * sl1, None, "face coincident", None), + Case(sl1, Pos(2, 0, 0) * sl1, [Face], "face coincident", None, True), Case(Pos(1.5, 1.5) * sl1, [sl3, Pos(.5, .5) * sl1], [Solid], "multi to_intersect, intersecting", None), Case(Pos(1.5, 1.5) * sl1, [sl3, Pos(Z=.5) * fc1], [Face], "multi to_intersect, intersecting", None), ] -@pytest.mark.parametrize("obj, target, expected", make_params(shape_3d_matrix)) -def test_shape_3d(obj, target, expected): - run_test(obj, target, expected) +@pytest.mark.parametrize("obj, target, expected, include_touched", make_params(shape_3d_matrix)) +def test_shape_3d(obj, target, expected, include_touched): + run_test(obj, target, expected, include_touched) # Compound Shapes cp1 = Compound(GridLocations(5, 0, 2, 1) * Vertex()) @@ -400,15 +424,15 @@ shape_compound_matrix = [ Case(cp2, [cp3, cp4], [Edge, Edge], "multi to_intersect, intersecting", None), - Case(cv1, cp3, [Edge, Edge], "intersecting", "duplicate edges, BRepAlgoAPI_Common and _Section both return edge"), + Case(cv1, cp3, [Edge, Edge, Edge, Edge], "intersecting", None), # xfail removed Case(sk1, cp3, [Face, Face], "intersecting", None), Case(pt1, cp3, [Face, Face], "intersecting", None), ] -@pytest.mark.parametrize("obj, target, expected", make_params(shape_compound_matrix)) -def test_shape_compound(obj, target, expected): - run_test(obj, target, expected) +@pytest.mark.parametrize("obj, target, expected, include_touched", make_params(shape_compound_matrix)) +def test_shape_compound(obj, target, expected, include_touched): + run_test(obj, target, expected, include_touched) # FreeCAD issue example c1 = CenterArc((0, 0), 10, 0, 360).edge() @@ -437,9 +461,9 @@ freecad_matrix = [ Case(c2, vert, [Vertex], "circle, vert, intersect", None), ] -@pytest.mark.parametrize("obj, target, expected", make_params(freecad_matrix)) -def test_freecad(obj, target, expected): - run_test(obj, target, expected) +@pytest.mark.parametrize("obj, target, expected, include_touched", make_params(freecad_matrix)) +def test_freecad(obj, target, expected, include_touched): + run_test(obj, target, expected, include_touched) # Issue tests @@ -460,9 +484,9 @@ issues_matrix = [ Case(e1, f1, [Edge], "issue #697", None), ] -@pytest.mark.parametrize("obj, target, expected", make_params(issues_matrix)) -def test_issues(obj, target, expected): - run_test(obj, target, expected) +@pytest.mark.parametrize("obj, target, expected, include_touched", make_params(issues_matrix)) +def test_issues(obj, target, expected, include_touched): + run_test(obj, target, expected, include_touched) # Exceptions @@ -489,4 +513,4 @@ def make_exception_params(matrix): @pytest.mark.parametrize("obj, target, expected", make_exception_params(exception_matrix)) def test_exceptions(obj, target, expected): with pytest.raises(Exception): - obj.intersect(target) \ No newline at end of file + obj.intersect(target)