fix(pid): existing line payloads are read tolerantly and crossings follow arcs, not chords
A foreign or hand-edited ACADMCP_PID payload (from: 'garbage', seq: True) made the next pid_line_draw raise TypeError; payload_port_refs / payload_seq now accept only well-formed values and a reader result never raises. An existing polyline with a bulge was tested as its chord, so a line through the arc reported crossings 0; flatten_bulges (pure, pinned against ezdxf.math's flattening) feeds count_crossings on both engines. 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
cfb5d5082c
commit
9cb4bee587
@@ -13,6 +13,7 @@ from .lines import (
|
||||
PID_LINE_LAYERS,
|
||||
aim_points,
|
||||
count_crossings,
|
||||
flatten_bulges,
|
||||
format_line_number,
|
||||
label_placement,
|
||||
marker_positions,
|
||||
@@ -106,13 +107,57 @@ def _vertices(info) -> list[tuple[float, float]]:
|
||||
return [(float(p[0]), float(p[1])) for p in info.properties.get("points") or []]
|
||||
|
||||
|
||||
def _bulges(info, count: int) -> list[float]:
|
||||
"""Per-vertex bulges as the engine reports them (both engines put a
|
||||
``bulges`` list next to ``points``); straight when it reports none."""
|
||||
raw = info.properties.get("bulges") or []
|
||||
out = []
|
||||
for value in raw[:count]:
|
||||
try:
|
||||
out.append(float(value or 0.0))
|
||||
except (TypeError, ValueError):
|
||||
out.append(0.0)
|
||||
return out + [0.0] * (count - len(out))
|
||||
|
||||
|
||||
def payload_port_refs(payload) -> list[tuple[str, str]]:
|
||||
"""``(handle, port)`` for each well-formed ``from``/``to`` in a line payload.
|
||||
|
||||
A foreign or hand-edited payload can carry anything under those keys
|
||||
(``"garbage"``, ``None``, a number); reading it used to raise
|
||||
``TypeError`` out of ``draw_line``. Reader results never raise: a
|
||||
malformed end is simply not a reference.
|
||||
"""
|
||||
refs = []
|
||||
if not isinstance(payload, dict):
|
||||
return refs
|
||||
for key in ("from", "to"):
|
||||
ref = payload.get(key)
|
||||
if not isinstance(ref, dict):
|
||||
continue
|
||||
handle, port = ref.get("handle"), ref.get("port")
|
||||
if isinstance(handle, str) and isinstance(port, str):
|
||||
refs.append((handle, port))
|
||||
return refs
|
||||
|
||||
|
||||
def payload_seq(payload) -> int | None:
|
||||
"""The payload's sequence number when it is a genuine int (``True`` is not)."""
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
seq = payload.get("seq")
|
||||
return seq if type(seq) is int else None
|
||||
|
||||
|
||||
async def existing_pid_lines(backend: AutoCADBackend) -> list[dict]:
|
||||
"""Every LINE/polyline on a P&ID line layer, with its payload when it has one.
|
||||
|
||||
Listed without a type filter: the two engines name a lightweight polyline
|
||||
differently (``_POLY_TYPES``), and a filter spelled for one of them is blind
|
||||
on the other — every auto-built number would restart at 1 and no crossing
|
||||
or port reuse would ever be reported there.
|
||||
or port reuse would ever be reported there. ``vertices`` are the raw
|
||||
vertices; ``bulges`` (one per vertex, DXF convention) let the crossing test
|
||||
follow an arc instead of its chord.
|
||||
"""
|
||||
out = []
|
||||
offset = 0
|
||||
@@ -124,7 +169,15 @@ async def existing_pid_lines(backend: AutoCADBackend) -> list[dict]:
|
||||
if info.layer.upper() not in PID_LINE_LAYERS:
|
||||
continue
|
||||
payload = await read_payload(backend, info.handle) if info.type in _POLY_TYPES else None
|
||||
out.append({"handle": info.handle, "vertices": _vertices(info), "payload": payload})
|
||||
vertices = _vertices(info)
|
||||
out.append(
|
||||
{
|
||||
"handle": info.handle,
|
||||
"vertices": vertices,
|
||||
"bulges": _bulges(info, len(vertices)),
|
||||
"payload": payload,
|
||||
}
|
||||
)
|
||||
if len(page) < _PAGE:
|
||||
break
|
||||
offset += _PAGE
|
||||
@@ -184,17 +237,16 @@ async def draw_line(
|
||||
path = route(s_point, s_dir, e_point, e_dir, stub=stub, mode=route_mode)
|
||||
|
||||
existing = await existing_pid_lines(backend)
|
||||
crossings = count_crossings(path, [ln["vertices"] for ln in existing])
|
||||
crossings = count_crossings(
|
||||
path, [flatten_bulges(ln["vertices"], ln["bulges"]) for ln in existing]
|
||||
)
|
||||
used: set[tuple[str, str]] = set()
|
||||
max_seq = 0
|
||||
for ln in existing:
|
||||
payload = ln["payload"] or {}
|
||||
for key in ("from", "to"):
|
||||
ref = payload.get(key)
|
||||
if ref:
|
||||
used.add((ref["handle"], ref["port"]))
|
||||
if isinstance(payload.get("seq"), int):
|
||||
max_seq = max(max_seq, payload["seq"])
|
||||
used.update(payload_port_refs(ln["payload"]))
|
||||
seq = payload_seq(ln["payload"])
|
||||
if seq is not None:
|
||||
max_seq = max(max_seq, seq)
|
||||
port_reuse = [
|
||||
{"handle": ep["handle"], "port": ep["port"]}
|
||||
for ep in (start, end)
|
||||
|
||||
Reference in New Issue
Block a user