SECTION 24 completes at eleven tools with xref_attach, xref_manage,
image_attach, data_extract and drawing_export_dwg, on the four contract members
that genuinely differ per engine -- backends/contracts/refs.py. Everything else
in the sheet track composes the generic members and is written once.
The ActiveX members were read off the AutoCAD 2026 seat's own type library
(C:\Program Files\Common Files\Autodesk Shared\acax25enu.tlb, "AutoCAD 2025
Type Library" 1.0, the library the running 25.x seat registers) before a line of
the COM path was written, and the measurement is in the code beside the calls:
IAcadModelSpace.AttachExternalReference(PathName, Name, InsertionPoint,
Xscale, Yscale, Zscale, Rotation, bOverlay, Password)
IAcadModelSpace.AddRaster(imageFileName, InsertionPoint, ScaleFactor,
RotationAngle)
IAcadBlock: IsXRef (get), Path (get AND put -- so the repath is an assignment,
not a method), Reload(), Unload(), Bind(bPrefixName), Detach(),
XRefDatabase (get)
IAcadDocument.SaveAs(FullFileName, SaveAsType, vSecurityParams)
AcSaveAsType: ac2000_dwg=12 ac2004_dwg=24 ac2007_dwg=36 ac2010_dwg=48
ac2013_dwg=60 ac2018_dwg=64 (=acNative)
The running seat itself was not answering COM while this landed (its window was
Not Responding and GetActiveObject never returned), so the type library -- which
is the same seat's declared surface and reads without a call into the process --
is what the members are measured from; no document of the operator's was opened
or touched. This repository has been bitten four times by a fake that invented a
member, so the COM fakes in the tests model those names and nothing else.
ActiveX angles are radians, so the degree-to-radian conversion happens at that
boundary and nowhere else.
Headlessly, ezdxf.xref.attach carries the attachment (measured on ezdxf 1.4.4:
block flags 20 for an attachment and 24 for an overlay, so "is this an xref" is
flags & (BLK_XREF | BLK_XREF_OVERLAY) -- masking BLK_XREF alone misses every
overlay), add_image_def + add_image the raster, with Pillow reading the pixel
size so the placed image keeps the file's aspect.
Three capability keys, declared in both maps:
dwg_write -- measured absent here (odafc.is_installed() is False), so the
ezdxf entry is a runtime re-evaluation like viewport_render's
matplotlib check rather than a hardcoded False: installing the
ODA File Converter starts the call working with no edit to the
map. The refusal names the install route and drawing_export_dxf.
xref_live -- reload and bind need a live seat's xref manager and are refused
headlessly; list, detach and path are headless and are not
behind the key.
xlsx_write -- openpyxl is optional and absent here; CSV is always available,
so the refusal never blocks the deliverable and says so.
Two numbers the plan predicted wrongly are corrected to what the registry
actually does. `block` outranks `drawing` in _GROUP_TAG_PRIORITY, so the two
xref tools file under `blocks` (9 -> 11) beside block_insert -- which is where
an xref belongs, being a block definition that lives in another file -- and
`drawing` moves 11 -> 12, not 11 -> 14. And `data_extract` must not say "parts
list" in its own text: that is the golden discovery query for
entity_create_table, and a file writer outranking the tool that draws the thing
is a regression the tuning bar catches. It is described by its destination --
a file -- instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
"""Parts-list rows to CSV (always) and XLSX (with openpyxl)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import csv
|
|
from importlib.util import find_spec
|
|
|
|
import pytest
|
|
|
|
from backends.base import UnsupportedCapabilityError
|
|
from engineering.sheet.extract import EXTRACT_FORMATS, write_rows, xlsx_available
|
|
|
|
|
|
def _rows():
|
|
return (
|
|
{
|
|
"item": 1,
|
|
"qty": 2,
|
|
"designation": "ISO 4014 - M12x60",
|
|
"standard": "ISO 4014",
|
|
"material": "8.8",
|
|
"handles": ("A1", "A2"),
|
|
},
|
|
{
|
|
"item": 2,
|
|
"qty": 1,
|
|
"designation": "SHAFT 40h6",
|
|
"standard": "",
|
|
"material": "C45",
|
|
"handles": ("B1",),
|
|
},
|
|
)
|
|
|
|
|
|
def test_the_formats_are_closed_and_csv_is_always_one_of_them():
|
|
assert EXTRACT_FORMATS == ("csv", "xlsx")
|
|
|
|
|
|
def test_csv_writes_the_heading_row_and_one_row_per_part(tmp_path):
|
|
target = tmp_path / "bom.csv"
|
|
result = write_rows(_rows(), str(target), fmt="csv")
|
|
assert result["ok"] is True and result["rows"] == 2 and result["format"] == "csv"
|
|
with open(target, newline="", encoding="utf-8") as handle:
|
|
written = list(csv.reader(handle))
|
|
assert written[0] == ["POS", "QTY", "DESIGNATION", "STANDARD", "MATERIAL"]
|
|
assert written[1] == ["1", "2", "ISO 4014 - M12x60", "ISO 4014", "8.8"]
|
|
assert written[2][2] == "SHAFT 40h6"
|
|
assert len(written) == 3
|
|
|
|
|
|
def test_a_chosen_column_set_is_honoured(tmp_path):
|
|
target = tmp_path / "short.csv"
|
|
write_rows(_rows(), str(target), columns=("item", "designation"), fmt="csv")
|
|
with open(target, newline="", encoding="utf-8") as handle:
|
|
written = list(csv.reader(handle))
|
|
assert written[0] == ["POS", "DESIGNATION"]
|
|
assert all(len(row) == 2 for row in written)
|
|
|
|
|
|
def test_an_unknown_format_is_refused_with_the_list(tmp_path):
|
|
with pytest.raises(ValueError) as excinfo:
|
|
write_rows(_rows(), str(tmp_path / "x.ods"), fmt="ods")
|
|
message = str(excinfo.value)
|
|
assert "ods" in message and "csv" in message and "xlsx" in message
|
|
|
|
|
|
def test_no_rows_is_refused_rather_than_writing_an_empty_file(tmp_path):
|
|
target = tmp_path / "empty.csv"
|
|
with pytest.raises(ValueError):
|
|
write_rows((), str(target), fmt="csv")
|
|
assert not target.exists()
|
|
|
|
|
|
def test_xlsx_available_agrees_with_the_installed_package():
|
|
assert xlsx_available() is (find_spec("openpyxl") is not None)
|
|
|
|
|
|
def test_xlsx_refuses_with_the_capability_key_and_names_the_extra(tmp_path):
|
|
if xlsx_available():
|
|
result = write_rows(_rows(), str(tmp_path / "bom.xlsx"), fmt="xlsx")
|
|
assert result["ok"] is True and (tmp_path / "bom.xlsx").exists()
|
|
return
|
|
target = tmp_path / "bom.xlsx"
|
|
with pytest.raises(UnsupportedCapabilityError) as excinfo:
|
|
write_rows(_rows(), str(target), fmt="xlsx")
|
|
assert excinfo.value.capability == "xlsx_write"
|
|
assert "openpyxl" in str(excinfo.value)
|
|
assert "csv" in str(excinfo.value), "a refusal that does not name the way out is half a refusal"
|
|
assert not target.exists()
|