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:
Umut CAN
2026-09-17 15:10:00 +03:00
co-authored by Claude Opus 5
parent 4bf686e943
commit 64cec37cc7
6 changed files with 209 additions and 19 deletions
+23 -5
View File
@@ -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)
+8 -1
View File
@@ -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
+27 -8
View File
@@ -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)