fix(task-22): ucs_list decides "world" from the frame, not the empty name — an unnamed UCS is its own current row
An unnamed UCS (UCS Origin / 3P without saving, the common kind) has an empty UCSNAME exactly like WCS does, so both engines answered `current: world` while the drawing was not in WCS — verified live on AutoCAD 2026 (`_.UCS _O 10,10,0` → UCSNAME="" WORLDUCS=0) and on the DXF it writes ($UCSNAME='' $UCSORG=(0,20,0)). A silent wrong value. - COM: a named match still wins; otherwise WORLDUCS (AutoCAD's own answer) decides, and WORLDUCS=0 appends a `name: None` row built from UCSORG/UCSXDIR/UCSYDIR. - ezdxf: the same rule against $UCSORG/$UCSXDIR/$UCSYDIR via the new `engineering.environment.ucs.is_world_axes`; a stale $UCSNAME falls back to the frame instead of leaving no row current. - Server `ucs_list` result: `current` is null for an unnamed UCS (never `world`), plus `current_unnamed: bool` so null is unambiguous. - Tests: helper, headless (unnamed row, stale name, and the exact AutoCAD DXF over the wire), and COM fake reading WORLDUCS + the frame variables. 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
4bf686e943
commit
64cec37cc7
+23
-5
@@ -1733,7 +1733,7 @@ class ComBackend(AutoCADBackend):
|
||||
}
|
||||
|
||||
async def ucs_list(self) -> list[dict]:
|
||||
from engineering.environment.ucs import WORLD
|
||||
from engineering.environment.ucs import WORLD, WORLD_ORIGIN, WORLD_X_AXIS, WORLD_Y_AXIS
|
||||
|
||||
def _sync():
|
||||
app = _acad_app()
|
||||
@@ -1742,15 +1742,33 @@ class ComBackend(AutoCADBackend):
|
||||
rows = [
|
||||
{
|
||||
"name": WORLD,
|
||||
"origin": [0.0, 0.0, 0.0],
|
||||
"x_axis": [1.0, 0.0, 0.0],
|
||||
"y_axis": [0.0, 1.0, 0.0],
|
||||
"current": current == "",
|
||||
"origin": list(WORLD_ORIGIN),
|
||||
"x_axis": list(WORLD_X_AXIS),
|
||||
"y_axis": list(WORLD_Y_AXIS),
|
||||
"current": False,
|
||||
}
|
||||
]
|
||||
collection = doc.UserCoordinateSystems
|
||||
for index in range(int(collection.Count)):
|
||||
rows.append(self._com_ucs_row(collection.Item(index), current))
|
||||
if any(row["current"] for row in rows[1:]):
|
||||
return rows
|
||||
# No saved entry is current. UCSNAME is empty for an unnamed UCS
|
||||
# (UCS Origin / 3P without saving) as well as for WCS — verified
|
||||
# live (AutoCAD 2026: after `_.UCS _O 10,10,0`, UCSNAME="" and
|
||||
# WORLDUCS=0). WORLDUCS is AutoCAD's own answer to "is this WCS".
|
||||
if int(app.GetVariable("WORLDUCS")) == 1:
|
||||
rows[0]["current"] = True
|
||||
else:
|
||||
rows.append(
|
||||
{
|
||||
"name": None,
|
||||
"origin": [float(c) for c in app.GetVariable("UCSORG")],
|
||||
"x_axis": [float(c) for c in app.GetVariable("UCSXDIR")],
|
||||
"y_axis": [float(c) for c in app.GetVariable("UCSYDIR")],
|
||||
"current": True,
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
return await self._run(_sync)
|
||||
|
||||
@@ -86,7 +86,14 @@ class EnvironmentContract(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def ucs_list(self) -> list[dict]:
|
||||
"""→ ``[{"name", "origin", "x_axis", "y_axis", "current": bool}]`` + the implicit "world"."""
|
||||
"""→ ``[{"name", "origin", "x_axis", "y_axis", "current": bool}]`` + the implicit "world".
|
||||
|
||||
Exactly one row is current. An *unnamed* current UCS (``UCS Origin`` /
|
||||
``3P`` without saving) is a trailing row with ``name: None`` carrying
|
||||
its frame — "world" is current only when the frame is the WCS
|
||||
(WORLDUCS live, the ``$UCS*`` header headless), never because the
|
||||
name is empty.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -2692,22 +2692,41 @@ class EzdxfBackend(AutoCADBackend):
|
||||
}
|
||||
|
||||
async def ucs_list(self) -> list[dict]:
|
||||
from engineering.environment.ucs import WORLD
|
||||
from engineering.environment.ucs import (
|
||||
WORLD,
|
||||
WORLD_ORIGIN,
|
||||
WORLD_X_AXIS,
|
||||
WORLD_Y_AXIS,
|
||||
is_world_axes,
|
||||
)
|
||||
|
||||
def _sync():
|
||||
doc = self._require_doc()
|
||||
current = str(doc.header.get("$UCSNAME", "") or "")
|
||||
rows = [self._ucs_row(WORLD, (0, 0, 0), (1, 0, 0), (0, 1, 0), current == "")]
|
||||
header = doc.header
|
||||
current = str(header.get("$UCSNAME", "") or "")
|
||||
origin = tuple(header.get("$UCSORG", WORLD_ORIGIN))
|
||||
x_axis = tuple(header.get("$UCSXDIR", WORLD_X_AXIS))
|
||||
y_axis = tuple(header.get("$UCSYDIR", WORLD_Y_AXIS))
|
||||
rows = [self._ucs_row(WORLD, WORLD_ORIGIN, WORLD_X_AXIS, WORLD_Y_AXIS, False)]
|
||||
named_current = False
|
||||
for ucs in doc.ucs:
|
||||
is_current = current != "" and ucs.dxf.name.lower() == current.lower()
|
||||
named_current = named_current or is_current
|
||||
rows.append(
|
||||
self._ucs_row(
|
||||
ucs.dxf.name,
|
||||
ucs.dxf.origin,
|
||||
ucs.dxf.xaxis,
|
||||
ucs.dxf.yaxis,
|
||||
current != "" and ucs.dxf.name.lower() == current.lower(),
|
||||
ucs.dxf.name, ucs.dxf.origin, ucs.dxf.xaxis, ucs.dxf.yaxis, is_current
|
||||
)
|
||||
)
|
||||
if named_current:
|
||||
return rows
|
||||
# No saved entry is current. An empty $UCSNAME is NOT "world": an
|
||||
# unnamed UCS (UCS Origin / 3P without saving) has an empty name
|
||||
# too, and AutoCAD writes exactly that. The frame decides, the way
|
||||
# WORLDUCS does live.
|
||||
if is_world_axes(origin, x_axis, y_axis):
|
||||
rows[0]["current"] = True
|
||||
else:
|
||||
rows.append(self._ucs_row(None, origin, x_axis, y_axis, True))
|
||||
return rows
|
||||
|
||||
return await self._async(_sync)
|
||||
|
||||
Reference in New Issue
Block a user