feat(environment): multi-document on both engines — ezdxf document registry, document_list/activate/close, drawing_close folded in
EnvironmentContract carries every member of the track E interface; the two
drawing_properties_* members are @capability('dwgprops') in this worktree only
and become @abstractmethod when group C's implementations merge.
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
49fc6ac6a5
commit
174fb8010c
@@ -0,0 +1,120 @@
|
||||
"""Environment: documents, layer states, named views, UCS, application,
|
||||
preferences, drawing properties and operator prompts (v1.6 track E).
|
||||
|
||||
``AutoCADBackend`` composes every contract in this package, so importing
|
||||
``backends.base.AutoCADBackend`` is unchanged for callers.
|
||||
|
||||
Ownership: group V owns this module. The ``# ── properties ──`` block is
|
||||
declared here and *implemented* by group C on both backends; the six
|
||||
``@capability`` members are implemented on the COM backend only and refuse on
|
||||
ezdxf with a key both capability maps declare.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from backends.capability import capability
|
||||
|
||||
|
||||
class EnvironmentContract(ABC):
|
||||
# ── documents ────────────────────────────────────────────────────────────
|
||||
|
||||
@abstractmethod
|
||||
async def document_list(self) -> list[dict]:
|
||||
"""``[{"name", "path", "active": bool, "saved": bool, "entity_count"}]``."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def document_activate(self, name_or_path: str) -> dict:
|
||||
"""Make one open document the target of every later call.
|
||||
→ ``{"ok", "active", "previous"}``."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def document_close(
|
||||
self, name_or_path: str | None = None, save: bool = False, discard: bool = False
|
||||
) -> dict:
|
||||
"""Close one document (default: the active one).
|
||||
|
||||
Unsaved and neither ``save`` nor ``discard`` → ``ValueError`` naming the
|
||||
document; ``save`` on a document that has never been saved →
|
||||
``ValueError`` (no path to write, and the live engine would open a
|
||||
modal Save dialog). → ``{"ok", "closed", "saved": bool, "active": name|None}``.
|
||||
"""
|
||||
...
|
||||
|
||||
# ── layer states, named views, UCS: appended by Task 22 ──────────────────
|
||||
|
||||
# ── application, preferences, operator prompts (COM only) ───────────────
|
||||
|
||||
@capability( # noqa: B027 — @capability supplies the body
|
||||
"live_application",
|
||||
reason="only a live CAD application can be launched or attached",
|
||||
)
|
||||
async def system_launch(self, visible: bool = True, open_path: str | None = None) -> dict:
|
||||
"""→ ``{"launched": bool, "attached": bool, "version", "document"}``."""
|
||||
...
|
||||
|
||||
@capability( # noqa: B027 — @capability supplies the body
|
||||
"preferences",
|
||||
reason="AutoCAD preferences live in the running application, not in a file",
|
||||
)
|
||||
async def preferences_get(self, keys: list[str] | None = None) -> dict:
|
||||
"""→ ``{"values": {key: value}, "read_only": [keys]}``."""
|
||||
...
|
||||
|
||||
@capability( # noqa: B027 — @capability supplies the body
|
||||
"preferences",
|
||||
reason="AutoCAD preferences live in the running application, not in a file",
|
||||
)
|
||||
async def preferences_set(self, key: str, value) -> dict:
|
||||
"""→ ``{"ok", "key", "old", "new"}``."""
|
||||
...
|
||||
|
||||
@capability( # noqa: B027 — @capability supplies the body
|
||||
"interactive_prompt",
|
||||
reason="prompting the operator needs a live AutoCAD session",
|
||||
)
|
||||
async def user_pick_point(self, prompt: str) -> dict:
|
||||
"""→ ``{"cancelled": bool, "x", "y"}`` | ``{"timed_out": True}``."""
|
||||
...
|
||||
|
||||
@capability( # noqa: B027 — @capability supplies the body
|
||||
"interactive_prompt",
|
||||
reason="prompting the operator needs a live AutoCAD session",
|
||||
)
|
||||
async def user_select(self, prompt: str, mode: str = "single") -> dict:
|
||||
"""→ ``{"cancelled", "handles": [...]}``."""
|
||||
...
|
||||
|
||||
@capability( # noqa: B027 — @capability supplies the body
|
||||
"interactive_prompt",
|
||||
reason="prompting the operator needs a live AutoCAD session",
|
||||
)
|
||||
async def system_prompt_message(self, text: str) -> dict:
|
||||
"""→ ``{"ok"}``."""
|
||||
...
|
||||
|
||||
# ── properties (declared here; implemented by group C on both engines) ───
|
||||
|
||||
# TEMPORARY (track-e/environment worktree only — the merge agent deletes this
|
||||
# block and the two decorators below become @abstractmethod): until group C's
|
||||
# implementations land, declaring these abstract would stop both backends from
|
||||
# instantiating here. They refuse with a declared key rather than pretend.
|
||||
|
||||
@capability("dwgprops", reason="drawing properties arrive with group C's merge") # noqa: B027
|
||||
async def drawing_properties_get(self) -> dict:
|
||||
"""→ ``{"summary": {"title","subject","author","keywords","comments"}
|
||||
(None each headless), "summary_available": bool, "custom": {k: v}}``."""
|
||||
...
|
||||
|
||||
@capability("dwgprops", reason="drawing properties arrive with group C's merge") # noqa: B027
|
||||
async def drawing_properties_set(
|
||||
self, summary: dict | None = None, custom: dict | None = None
|
||||
) -> dict:
|
||||
"""ezdxf: ``summary`` with any non-None field → raise
|
||||
``UnsupportedCapabilityError("dwgprops")``; ``custom`` → header
|
||||
custom_vars (replace keys given; value None deletes).
|
||||
→ ``{"ok", "summary_written": [...], "custom_written": [...], "custom_deleted": [...]}``."""
|
||||
...
|
||||
Reference in New Issue
Block a user