fix(task-13): mirrored-symbol ports and COM-visible existing lines

Two review findings on pid_line_draw, both root-caused:

1. existing_pid_lines() filtered entity_list(type_filter="LWPOLYLINE") and
   tested info.type == "LWPOLYLINE". A live LWPOLYLINE's ActiveX ObjectName
   is AcDbPolyline (the repo's own 2026-08-06 measurement), which the COM
   engine reports as POLYLINE, so on live AutoCAD every auto-built number was
   seq 1, crossings always 0 and port_reuse always []. The reader now lists
   without a type filter and accepts {LWPOLYLINE, POLYLINE, 2DPOLYLINE} plus
   LINE — the same set the Task 14 graph reader keeps. The COM _entity_info
   polyline branch matched only the DXF-flavoured "AcDbLWPolyline", so a live
   LWPOLYLINE never got `points` either; it now accepts both spellings (with
   the OCS normalisation), which is what makes the crossing count real there.

2. resolve_endpoint() applied only x_scale, so a mirrored INSERT
   (entity_mirror writes y_scale = -1) resolved its port to a point off the
   symbol and the line was drawn through the pump body. transform_port takes
   an optional y_scale (default: uniform, signature unchanged) and applies the
   full reflect-then-rotate transform to point, direction and radius; a
   stretched symbol (|x_scale| != |y_scale|) has no circle for a radial port
   and is refused with a clear ValueError instead of a silent wrong number.

Tests: mirror about both axes resolves onto the real geometry, a stretched
copy is refused, and a fake-COM AcDbPolyline probe sees the existing line
with its seq, vertices and from-ref.

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-15 23:48:35 +03:00
co-authored by Claude Opus 5
parent e8f1a666f8
commit e924b5db99
5 changed files with 243 additions and 40 deletions
+16 -6
View File
@@ -326,6 +326,15 @@ def _com_bulge(entity, index: int) -> float:
return 0.0
# 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
# this code once matched on alone, which left every live LWPOLYLINE without
# ``points`` because the branch never fired. Both are accepted so the vertices
# come through whichever spelling a seat reports.
_COM_LWPOLYLINE_NAMES = ("AcDbPolyline", "AcDbLWPolyline")
def _entity_info(entity) -> EntityInfo:
"""Convert a COM entity object to EntityInfo dataclass."""
try:
@@ -362,16 +371,17 @@ def _entity_info(entity) -> EntityInfo:
# Arc length (N3) — parity with ezdxf so length_range selects ARCs.
_sweep = (entity.EndAngle - entity.StartAngle) % (2.0 * math.pi)
props["length"] = entity.Radius * _sweep
elif obj_name in ("AcDbLWPolyline", "AcDb2dPolyline"):
elif obj_name in _COM_LWPOLYLINE_NAMES or obj_name == "AcDb2dPolyline":
coords = list(entity.Coordinates)
pts = [[coords[i], coords[i + 1]] for i in range(0, len(coords), 2)]
# ActiveX hands back WCS for every point property on this backend
# *except* this one: "LightweightPolyline object: the variant is an
# array of 2D points in OCS." Scoped to AcDbLWPolyline deliberately —
# AcDb2dPolyline's Coordinates may be 3D triples, and 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.
if obj_name == "AcDbLWPolyline":
# array of 2D points in OCS." Scoped to the lightweight polyline
# deliberately — AcDb2dPolyline's Coordinates may be 3D triples, and
# 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.
if obj_name in _COM_LWPOLYLINE_NAMES:
try:
normal = tuple(entity.Normal)
if not ocs.is_wcs_frame(normal):