fix(task-7): crossings test the exact arc, so a line through a jump's apex counts

flatten_bulges chops a semicircle into an even number of chords (36 for
r=50, 12 for r=5), so a flattened vertex lands exactly on the apex — the
axis of symmetry, the one place a P&ID line most plausibly meets a
crossing jump. segments_cross treats a vertex hit as a junction, so both
neighbouring chords dropped it and the arc contributed 0: the (45,0)->(55,0)
bulge -1 jump reported crossings 0 at x=50 and 1 at x=50.3 or x=49, the
plan's own (0,0)->(100,0) semicircle 0 at x=50, and the mirrored frame
(extrusion -Z) 0 at x=-50.

count_crossings now takes bulges= (per-chain DXF bulge lists, zero-padded)
and tests every bulged edge as its exact arc through the new
segment_arc_crossings — circle/segment intersection restricted to the
sweep — with the same junction rule as the straight case: a hit within
_EPS of the segment's ends, of the arc's own two ends, or a tangent, is
not a crossing. Nothing along the sweep is a vertex any more. The arc
parametrisation is factored into _arc_of_bulge and shared with
flatten_bulges, which keeps its promised signature and its ezdxf pin but
no longer feeds the crossing count. draw_line passes the vertices and
bulges through unflattened.

Pinned in tests: the apex case on the semicircle (x=50, y 20->60), the
r=5 jump at x=50 / 50.3 / 49 on +Z and -Z frames, the junction cases
(T-touch on the arc, through the arc's endpoint, chord side, tangent
along the apex, in-and-out at y=4.9 counts 2), and 4x400 seeded random
segment/arc pairs against ezdxf's ConstructionArc.intersect_line (a
20 000-case run in the worktree found 0 mismatches). The eight new
assertions fail on the flattened rule and pass on the exact one.

Headless finding, headless evidence; no new ActiveX call was written, so
the shared live AutoCAD was not touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-22 11:06:04 +03:00
co-authored by Claude Opus 5
parent 9cb4bee587
commit efcc8bbba1
4 changed files with 201 additions and 30 deletions
+34 -1
View File
@@ -467,7 +467,11 @@ def test_payload_helpers_accept_only_well_formed_values():
async def test_crossings_follow_a_bulged_line_not_its_chord(backend):
"""A foreign P&ID line with a semicircular jump (bulge -1 from (0,0) to
(100,0) bows to +Y, apex (50,50)): a vertical utility line at x=30 from
y=20 to y=60 crosses the arc once at y≈45.8 and its chord never."""
y=20 to y=60 crosses the arc once at y≈45.8 and its chord never — and so
does one at x=50, straight through the apex. The apex is the axis of
symmetry, where a flattened semicircle (an even chord count) puts a
vertex; a vertex hit is a junction to ``segments_cross``, so a chord
chain reported 0 there. The arc is tested exactly, not flattened."""
msp = backend._msp()
msp.add_lwpolyline(
[(0, 0, 0, 0, -1.0), (100, 0, 0, 0, 0)],
@@ -480,10 +484,39 @@ async def test_crossings_follow_a_bulged_line_not_its_chord(backend):
assert lines[0]["bulges"] == [-1.0, 0.0]
result = await draw_line(backend, {"x": 30, "y": 20}, {"x": 30, "y": 60}, line_class="utility")
assert result["crossings"] == 1
apex = await draw_line(backend, {"x": 50, "y": 20}, {"x": 50, "y": 60}, line_class="utility")
assert apex["crossings"] == 1, "through the apex — the flattened vertex — is still one crossing"
below = await draw_line(backend, {"x": 70, "y": -20}, {"x": 70, "y": -60}, line_class="utility")
assert below["crossings"] == 0, "the chord side of the arc is empty"
@pytest.mark.parametrize("extrusion", [(0, 0, 1), (0, 0, -1)])
async def test_crossings_count_a_line_through_a_jump_apex(backend, extrusion):
"""The jump a P&ID draws where two lines cross: (45,0)→(55,0) bulge -1, a
5 mm semicircle with its apex at (50,5). A line at x=50 meets the apex
exactly (36 chords of r=50, 12 of r=5: always even, always a vertex on
the axis) and is one crossing, the same as at x=50.3 or x=49 — on a
mirrored frame (extrusion -Z, WCS x negated) too."""
msp = backend._msp()
msp.add_lwpolyline(
[(0, 0, 0, 0, 0), (45, 0, 0, 0, -1.0), (55, 0, 0, 0, 0), (100, 0, 0, 0, 0)],
format="xyseb",
dxfattribs={"layer": "PROCESS-PIPING-MAIN", "extrusion": extrusion},
)
backend._mark_dirty()
sign = extrusion[2]
for x in (50.0, 50.3, 49.0):
drawn = await draw_line(
backend, {"x": sign * x, "y": -20}, {"x": sign * x, "y": 20}, line_class="utility"
)
assert drawn["crossings"] == 1, f"x={sign * x}"
await backend.entity_delete(drawn["handle"])
beside = await draw_line(
backend, {"x": sign * 60, "y": 2}, {"x": sign * 60, "y": 20}, line_class="utility"
)
assert beside["crossings"] == 0, "clear of the jump and above the straight run"
async def test_com_existing_lines_carry_bulges(monkeypatch):
payload = line_payload("process_major", "100-P-7", "100", "P", None, None, 7, None, None)
poly = _com_pid_polyline("3E", (0.0, 0.0, 100.0, 0.0), payload)