fix(task-14): polyline bulges leave the backends in WCS, so a mirrored arc reads on its true side
The per-vertex `bulges` list was handed out in the entity's own frame next
to `points` that had been translated to WCS. A mirrored LWPOLYLINE
(extrusion / Normal -Z, what `entity_mirror` produces) is a reflection,
which reverses every arc's sense, so the reader tested endpoints against
the arc's mirror image: a line ending on the true apex dangled, a line
ending on empty paper made a junction, and `length` (frame-invariant)
said nothing. Both engines had it — ezdxf emitted `get_points("xyb")`
raw, COM returned `GetBulge` raw after reflecting `Coordinates`.
`ocs.wcs_bulge(extrusion, bulge)` now negates the sign for a left-handed
frame (the same rule `wcs_arc_angles` applies to an ARC's endpoints) and
both `_entity_info` paths route bulges through it whenever they translate
the points. Fenced by an end-to-end mirrored-pipe graph test, the COM
`_entity_info` fake with Normal -Z, and a boundary-level OCS test.
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
8fcd587b5b
commit
8b6f355c3d
+11
-2
@@ -403,13 +403,17 @@ def _entity_info(entity) -> EntityInfo:
|
||||
# changing its stride on a doc reading alone, with no way to verify
|
||||
# against live AutoCAD from here, risks breaking a path that works
|
||||
# today.
|
||||
normal = None
|
||||
if obj_name in _COM_LWPOLYLINE_NAMES:
|
||||
try:
|
||||
normal = tuple(entity.Normal)
|
||||
if not ocs.is_wcs_frame(normal):
|
||||
elevation = float(getattr(entity, "Elevation", 0.0))
|
||||
pts = [ocs.to_wcs_2d(normal, p[0], p[1], elevation) for p in pts]
|
||||
else:
|
||||
normal = None
|
||||
except Exception as exc:
|
||||
normal = None
|
||||
log.debug("OCS normalisation of polyline coordinates failed: %s", exc)
|
||||
props["points"] = pts
|
||||
props["closed"] = bool(entity.Closed)
|
||||
@@ -418,8 +422,13 @@ def _entity_info(entity) -> EntityInfo:
|
||||
# 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"])
|
||||
# is zero and no call is needed. GetBulge answers in the same OCS
|
||||
# as Coordinates, so a translated `points` list needs the bulges
|
||||
# translated with it — a mirrored frame reverses the arc's sense.
|
||||
bulges = _com_bulges(entity, coords, bool(entity.Closed), props["length"])
|
||||
if normal is not None:
|
||||
bulges = [ocs.wcs_bulge(normal, b) for b in bulges]
|
||||
props["bulges"] = bulges
|
||||
elif obj_name == "AcDbText":
|
||||
props["text"] = entity.TextString
|
||||
ins = entity.InsertionPoint
|
||||
|
||||
Reference in New Issue
Block a user