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:
Umut CAN
2026-09-16 00:44:34 +03:00
co-authored by Claude Opus 5
parent 8fcd587b5b
commit 8b6f355c3d
5 changed files with 132 additions and 4 deletions
+17
View File
@@ -118,6 +118,23 @@ async def test_mirrored_polyline_points_are_wcs(backend):
_assert_points(info.properties["points"], [[-10, 0], [-20, 0], [-20, 10]])
async def test_mirrored_polyline_bulges_are_wcs(backend):
"""A reflection reverses an arc's sense, so the bulge sign flips with the
points — otherwise ``points`` and ``bulges`` describe two different arcs
and, ``length`` being frame-invariant, nothing in the payload says so."""
msp = backend._doc.modelspace()
poly = msp.add_lwpolyline([(10, 0, 1), (20, 0, 0), (20, 10, 0)], format="xyb")
mirrored = await backend.entity_mirror(poly.dxf.handle, *Y_AXIS)
info = await backend.entity_get(mirrored.handle)
_assert_points(info.properties["points"], [[-10, 0], [-20, 0], [-20, 10]])
# Bulge +1 bowed to -y from (10,0) to (20,0); mirrored across x=0 it runs
# (-10,0) to (-20,0) and still bows to -y, which in that direction is CW.
assert info.properties["bulges"] == pytest.approx([-1.0, 0.0, 0.0])
assert info.properties["bounding_box"]["min"][1] == pytest.approx(-5.0, abs=1e-9)
assert info.properties["length"] == pytest.approx(5 * 3.141592653589793 + 10, abs=1e-6)
async def test_mirrored_text_insertion_is_wcs(backend):
text = await backend.entity_create_text("PART A", 40, 15, 3.0)
mirrored = await backend.entity_mirror(text.handle, *Y_AXIS)