fix(task-14): foreign-line membership before junctions, and edges measure the arc the drawing holds
Two reader defects, both about trusting a derived number over the drawing.
Stale junctions: endpoints were resolved before a foreign line's membership
was decided, so a P&ID line ending on a foreign line that was then discarded
(it touched no node) kept `{"junction": "J1"}` while `junctions` was empty
and `stats.dangling` was one short. Node attachment now runs first for every
edge, the foreign lines that attach are admitted, and only then are junctions
and dangling ends resolved over the edges actually in the graph.
Chord length: `length` was a chord walk over the vertices, 12.5% short on a
semicircular crossing jump with no field saying so, and junction detection
tested the chord too, so a line ending on the arc dangled. Both engines now
report per-vertex `bulges` next to `points` (COM asks GetBulge only when
`Length` exceeds the chord walk, since an arc is always longer than its
chord); the reader takes the engine's bulge-aware `length` and tests
endpoints against the arc. The chord sum survives only for an engine that
reports no length, and is then labelled `length_approximate`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5
parent
e246b5e86a
commit
8fcd587b5b
@@ -326,6 +326,28 @@ def _com_bulge(entity, index: int) -> float:
|
||||
return 0.0
|
||||
|
||||
|
||||
def _com_bulges(entity, coords, closed: bool, length) -> list[float]:
|
||||
"""Every vertex's bulge, read only when ``Length`` says an arc exists.
|
||||
|
||||
``coords`` is the flat ``Coordinates`` variant (x0, y0, x1, y1, ...). A
|
||||
circular arc is strictly longer than its chord, so when AutoCAD's own
|
||||
``Length`` equals the chord walk the polyline is straight everywhere and
|
||||
the per-vertex COM round trips are skipped.
|
||||
"""
|
||||
count = len(coords) // 2
|
||||
pts = [(float(coords[i]), float(coords[i + 1])) for i in range(0, 2 * count, 2)]
|
||||
chord = sum(math.dist(a, b) for a, b in zip(pts, pts[1:], strict=False))
|
||||
if closed and count > 1:
|
||||
chord += math.dist(pts[-1], pts[0])
|
||||
try:
|
||||
straight = abs(float(length) - chord) <= 1e-9 * max(1.0, chord)
|
||||
except (TypeError, ValueError):
|
||||
straight = False
|
||||
if straight:
|
||||
return [0.0] * count
|
||||
return [_com_bulge(entity, i) for i in range(count)]
|
||||
|
||||
|
||||
# What a lightweight polyline calls itself over ActiveX. The live name is
|
||||
# ``AcDbPolyline`` (measured on AutoCAD 2026, 2026-08-06 — the member profile in
|
||||
# tests/test_com_backend.py); ``AcDbLWPolyline`` is the DXF-flavoured spelling
|
||||
@@ -392,6 +414,12 @@ def _entity_info(entity) -> EntityInfo:
|
||||
props["points"] = pts
|
||||
props["closed"] = bool(entity.Closed)
|
||||
props["length"] = entity.Length
|
||||
# Per-vertex bulges, parallel to `points` (ezdxf parity). GetBulge
|
||||
# is one cross-process call per vertex, so it is only paid when
|
||||
# the polyline can carry an arc: an arc is always longer than its
|
||||
# chord, so a `Length` equal to the chord walk means every bulge
|
||||
# is zero and no call is needed.
|
||||
props["bulges"] = _com_bulges(entity, coords, bool(entity.Closed), props["length"])
|
||||
elif obj_name == "AcDbText":
|
||||
props["text"] = entity.TextString
|
||||
ins = entity.InsertionPoint
|
||||
|
||||
@@ -813,6 +813,10 @@ def _entity_info_dxf(ent) -> EntityInfo:
|
||||
_verts = [(float(x), float(y), float(b)) for x, y, b in ent.get_points(format="xyb")]
|
||||
_area, _length = measure.polygon_area_perimeter(_verts, bool(ent.closed))
|
||||
props["length"] = round(_length, 6)
|
||||
# The bulge of the segment leaving each vertex (DXF convention), so
|
||||
# a reader that walks `points` can test against the arc the drawing
|
||||
# holds instead of the chord it does not. Parallel to `points`.
|
||||
props["bulges"] = [b for _x, _y, b in _verts]
|
||||
if ent.closed:
|
||||
props["area"] = round(_area, 6)
|
||||
elif ent_type == "TEXT":
|
||||
|
||||
Reference in New Issue
Block a user