fix(task-18): the COM plot is foreground, by layout name, and confirmed on disk; plot_style_list reads PrinterStyleSheetPath

Three review findings against SECTION 19 on the live engine, each measured
on AutoCAD 2026 in a scratch document with the operator's BACKGROUNDPLOT=2:

- plot_style_list read Preferences.Files.PrintStyleSheetPath; the ActiveX
  member is PrinterStyleSheetPath, so the AttributeError was swallowed and
  every row came back installed=None. Renamed in the backend, the server
  docstring and the test fake (which faked the same wrong name). Live: 13
  rows, all 9 catalogue ctb files installed, four .stb files as installed.
- ComBackend.drawing_export_pdf returned ok before any file existed under
  background plotting, so batch_plot died on path.stat() with an unhandled
  FileNotFoundError and a later foreground plot raised E_FAIL. The export
  now sets BACKGROUNDPLOT=0 around PlotToFile (restored after), honours the
  method's bool and reports ok only with the file on disk (bytes included).
  batch_plot turns a backend RuntimeError or a phantom ok into a per-row
  ok:false, keeping the rows already plotted.
- With a sheet current, the Model row was that sheet plotted again: the COM
  export switched tabs only when a layout was named. layout=None now makes
  Model current (restoring the tab), and batch_plot names every layout,
  Model included, so both engines plot the tab the row is labelled with.
  Live: Model row /Title (Model) at 210x297 beside the A3 sheet, the active
  tab and BACKGROUNDPLOT restored.

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-21 21:36:07 +03:00
co-authored by Claude Opus 5
parent f25421709f
commit f6951e2597
4 changed files with 278 additions and 21 deletions
+66 -16
View File
@@ -1041,23 +1041,73 @@ class ComBackend(AutoCADBackend):
return await self.drawing_save_as(path, "dxf")
async def drawing_export_pdf(self, path: str, layout: str | None = None) -> dict:
"""Plot one layout to PDF through ``Plot.PlotToFile`` and confirm the file.
``layout=None`` means model space, as the tool advertises — the Model
tab is made current for the plot (and the previous tab restored), so
the row is never silently the sheet that happened to be active.
Measured on AutoCAD 2026 with the operator's default BACKGROUNDPLOT=2:
``PlotToFile`` returns True immediately, the job is queued to the
background plotter, and no file exists when the call comes back —
seconds later the folder was still empty, and a foreground plot
attempted after such a stranded job raised E_FAIL. So the plot is
forced to the foreground (``BACKGROUNDPLOT=0``, restored afterwards),
the method's boolean is honoured, and ``ok`` is reported only once the
file is on disk with a size.
"""
def _sync():
doc = _acad_doc()
previous = None
if layout:
previous = doc.ActiveLayout.Name
if previous != layout:
doc.ActiveLayout = doc.Layouts.Item(layout)
target = layout.strip() if layout and layout.strip() else "Model"
previous = doc.ActiveLayout.Name
switched = False
if previous.lower() != target.lower():
doc.ActiveLayout = doc.Layouts.Item(target)
switched = True
old_background = None
try:
plot = doc.Plot
plot.PlotToFile(path, "DWG To PDF.pc3")
result = {"ok": True, "path": path}
if layout:
result["layout"] = layout
return result
try:
old_background = int(doc.GetVariable("BACKGROUNDPLOT"))
except Exception as exc:
log.debug("BACKGROUNDPLOT unreadable (%s); plotting as configured", exc)
if old_background not in (None, 0):
doc.SetVariable("BACKGROUNDPLOT", 0)
plotted = doc.Plot.PlotToFile(path, "DWG To PDF.pc3")
written = Path(path)
if not plotted:
return {
"ok": False,
"path": path,
"layout": target,
"error": "PlotToFile returned False: AutoCAD refused the plot "
"(check the layout's plot area and the 'DWG To PDF.pc3' device)",
}
if not written.is_file():
return {
"ok": False,
"path": path,
"layout": target,
"error": "PlotToFile reported success but wrote no file at "
f"{path} (BACKGROUNDPLOT was {old_background})",
}
return {
"ok": True,
"path": path,
"layout": target,
"bytes": written.stat().st_size,
}
finally:
if previous is not None and previous != layout:
doc.ActiveLayout = doc.Layouts.Item(previous)
if old_background not in (None, 0):
try:
doc.SetVariable("BACKGROUNDPLOT", old_background)
except Exception as exc:
log.warning("could not restore BACKGROUNDPLOT=%s: %s", old_background, exc)
if switched:
try:
doc.ActiveLayout = doc.Layouts.Item(previous)
except Exception as exc:
log.warning("could not restore the active layout %r: %s", previous, exc)
return await self._run(_sync)
@@ -2267,7 +2317,7 @@ class ComBackend(AutoCADBackend):
async def plot_style_list(self) -> list[dict]:
"""The ctb catalogue, marked against the files in AutoCAD's plot style path.
``Preferences.Files.PrintStyleSheetPath`` is a ``;``-separated list of
``Preferences.Files.PrinterStyleSheetPath`` is a ``;``-separated list of
folders. Every read is guarded: a folder that cannot be listed is
skipped, and when the Preferences object is unreachable the catalogue
rows come back with ``installed: None`` rather than a fabricated False.
@@ -2277,9 +2327,9 @@ class ComBackend(AutoCADBackend):
def _sync():
rows = [{"name": name, "source": "catalog", "installed": None} for name in CTB_CATALOG]
try:
raw = str(_acad_app().Preferences.Files.PrintStyleSheetPath or "")
raw = str(_acad_app().Preferences.Files.PrinterStyleSheetPath or "")
except Exception as exc:
log.debug("Preferences.Files.PrintStyleSheetPath unreadable: %s", exc)
log.debug("Preferences.Files.PrinterStyleSheetPath unreadable: %s", exc)
return rows
installed: dict[str, str] = {}
for folder in (part.strip() for part in raw.split(";")):