merge: track-e group V
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
@@ -16,6 +16,7 @@ from backends.contracts.drawing import DrawingContract
|
||||
from backends.contracts.entity_creation import EntityCreationContract
|
||||
from backends.contracts.entity_modification import EntityModificationContract
|
||||
from backends.contracts.entity_query import EntityQueryContract
|
||||
from backends.contracts.environment import EnvironmentContract
|
||||
from backends.contracts.gdt import GdtContract
|
||||
from backends.contracts.identity import IdentityContract
|
||||
from backends.contracts.layers import LayerContract
|
||||
@@ -39,6 +40,7 @@ __all__ = [
|
||||
"EntityCreationContract",
|
||||
"EntityModificationContract",
|
||||
"EntityQueryContract",
|
||||
"EnvironmentContract",
|
||||
"GdtContract",
|
||||
"IdentityContract",
|
||||
"LayerContract",
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
"""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 (portable ACADMCP_LAYERSTATES XRECORDs, both engines) ──
|
||||
|
||||
@abstractmethod
|
||||
async def layer_state_save(self, name: str, description: str | None = None) -> dict:
|
||||
"""→ ``{"ok", "name", "layer_count", "replaced"}``."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def layer_state_restore(self, name: str, properties: list[str] | None = None) -> dict:
|
||||
"""``properties`` ⊆ {"on","frozen","locked","color","linetype","lineweight","plot","current"};
|
||||
→ ``{"ok", "name", "applied", "missing_layers", "new_layers"}``."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def layer_state_list(self) -> list[dict]:
|
||||
"""→ ``[{"name", "description", "layer_count"}]``."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def layer_state_delete(self, name: str) -> dict: ...
|
||||
|
||||
# ── named views ─────────────────────────────────────────────────────────
|
||||
|
||||
@abstractmethod
|
||||
async def view_named_save(
|
||||
self, name: str, center=None, height: float | None = None, width: float | None = None
|
||||
) -> dict:
|
||||
"""→ ``{"ok", "name", "center": [x, y], "height", "width", "replaced"}``."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def view_named_restore(self, name: str) -> dict:
|
||||
"""COM: ZOOM Window on the saved rectangle (``"applied": "zoom_window"``
|
||||
plus the read-back ``viewctr`` / ``viewsize``); ezdxf: fits the window
|
||||
into the ``*Active`` VPORT without touching its aspect ratio,
|
||||
``"applied": "header_only"`` plus ``vport_height`` / ``aspect_ratio``."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def view_named_list(self) -> list[dict]: ...
|
||||
|
||||
# ── UCS (stored and made current; tool coordinates stay WCS) ────────────
|
||||
|
||||
@abstractmethod
|
||||
async def ucs_list(self) -> list[dict]:
|
||||
"""→ ``[{"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
|
||||
async def ucs_set(self, name: str, origin, x_axis, y_axis) -> dict:
|
||||
"""non-orthogonal → ``ValueError`` with the measured angle."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def ucs_restore(self, name: str) -> dict:
|
||||
"""``"world"`` resets."""
|
||||
...
|
||||
|
||||
# ── 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": [...]}``; ``mode="single"`` adds ``"picked": [x, y]`` in WCS."""
|
||||
...
|
||||
|
||||
@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) ───
|
||||
|
||||
@abstractmethod
|
||||
async def drawing_properties_get(self) -> dict:
|
||||
"""→ ``{"summary": {"title","subject","author","keywords","comments"}
|
||||
(None each headless), "summary_available": bool, "custom": {k: v}}``."""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
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": [...]}``."""
|
||||
...
|
||||
@@ -36,11 +36,18 @@ class PremiumContract(ABC):
|
||||
|
||||
_plan_spec: PlanSpec | None = None
|
||||
_preflight_result: Any = None
|
||||
# The standard `drawing_apply_iso_layers` last bootstrapped on *this*
|
||||
# drawing (None until it runs) — `_active_layer_set_id` reads it ahead of
|
||||
# the PlanSpec. Drawing-scoped like the plan: a live backend clears it
|
||||
# when AutoCAD's active document changes, the headless registry keeps one
|
||||
# per open document.
|
||||
_active_layer_set: LayerSetId | None = None
|
||||
|
||||
def _reset_document_state(self) -> None:
|
||||
"""Clear metadata that is valid only for the current drawing."""
|
||||
self._plan_spec = None
|
||||
self._preflight_result = None
|
||||
self._active_layer_set = None
|
||||
self._gdt_datums_defined = set()
|
||||
self._gdt_datums_referenced = set()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user