The dot-named temp file is stored Hidden by Samba and the attribute
followed it through the replace. Clear it unless the drawing was
already hidden.
Fixes#1414
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`with_printer_devmode` sized the driver's DEVMODE with a
`DocumentPropertiesW` call that passed `DM_OUT_BUFFER` and no output
buffer to write into. That combination is not a size query: drivers
answer it with -1, the `needed <= 0` guard returned early, and the
function handed back `None` for every printer on every machine.
Measured here on Windows 11 x86-64, both an installed virtual printer
and a physical one:
fMode = 0 -> 5428 / 2684 bytes (the real size)
fMode = DM_OUT_BUFFER, NULL out -> -1
Two things were broken by it, both silently:
- `oriented_printer_devmode` builds the DEVMODE a print job is spooled
with. Getting `None` back, the direct GDI path fell through to
"CreateDC then falls back to the driver default", so the plot's
orientation and sheet never reached the job. A landscape plot came
out however the driver happened to be set. This is the route users
without a PDF application take.
- `PRINTERS <name>` could never name the driver's default sheet,
because the id it matches on comes from that same DEVMODE.
With the size query asking for a size, both work: the report now says
"Default sheet: ISO A4 (210 x 297 mm)" for both printers here, and a
landscape plot leaves `dmOrientation = DMORIENT_LANDSCAPE` set in the
job's DEVMODE.
The two new tests both fail with the old flag and pass with it removed.
The existing GDI ink tests do not catch this: they accept the raster
rotation fallback as an equally good outcome, so the page looked right
while the DEVMODE was empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Properties… in the Plot dialog froze the application on Windows.
`on_printer_properties` called `DocumentPropertiesW` with `DM_IN_PROMPT`
from inside the closure handed to `iced::window::run`, and iced runs that
closure synchronously on the winit event-loop thread. The driver's
property sheet wants to run its own modal message loop on that same
thread, so the two loops collide: the sheet is never created at all, the
window stops painting, and the thread spins at 100% of one core with no
way back.
Measured on Windows 11 x86-64: the pinned thread is the window's own UI
thread, and compstui.dll plus the driver's PrintConfig.dll are loaded
into the process, so execution does reach the driver's property-sheet
code and never leaves it. It reproduces with a physical printer as
readily as with Microsoft Print to PDF, so the mechanism was broken for
every printer, not only the virtual one named in the report.
The system's own printing-preferences UI is now started as a separate
process, which pumps its own messages and leaves ours free, so the
freeze is impossible by construction rather than merely unlikely. The
command that does it already existed for the case where no printer is
selected; it now serves a named printer too, and the in-process modal
path is gone. Preferences still reach the job: the sheet writes the
per-user DEVMODE that the plot path reads back through
`DocumentPropertiesW`, which was confirmed by changing the orientation
in the sheet and watching the value the plot path reads change with it.
The new test pins down that the printer name must reach `printui` as a
single argument: split across several it matches nothing and the process
exits 0 without drawing anything, which would be a failure with nothing
to show the user.
Fixes#1474
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DWFATTACH / -DWFATTACH and DGNATTACH / -DGNATTACH attach a DWF / DWFx sheet
or a DGN (V7 or V8) model with the reference's prompts; ATTACH routes the
file types to them. The sheet or model is read into vector paths, drawn as a
transparent underlay raster (contrast, fade, monochrome and background
adjustment apply) and offers its geometry to object snaps. Definitions are
kept in ACAD_DWFDEFINITIONS / ACAD_DGNDEFINITIONS, the External References
palette lists and reloads them, and image textures get line-keeping mip
levels so thin underlay lines stay visible when zoomed out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An image's transparency flag decides whether transparent pixels show what
is behind the image or draw in their stored colour: the image pass and PDF
plots now follow it (it was ignored, so every PNG alpha showed through).
TRANSPARENCY selects images and asks for the mode, defaulting to the first
image's current one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
opencadcodec a35f43e reads R13/R14 LAYER records as plottable, the root
cause #1507 worked around on load; drop the host-side correction. Its
tests stay and now cover the codec fix end to end. They wrote one temp
file per version and ran in parallel, so each test now gets its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The LAYER record only grew a plot flag in R2000: R13/R14 carry frozen,
off, frozen-in-new-viewport and locked as four separate bits and nothing
else, so the reader has no bit to read and leaves `is_plottable` at the
`false` it starts from. Every layer of such a drawing then read as "don't
plot", the plot pass dropped every wire, and PLOT, the preview and
QUICKPRINT all rendered a blank sheet while the drawing still looked right
on screen — display and plot visibility are separate flags, so nothing
about the drawing itself looked wrong.
Restore what the format means by default — a layer plots unless a file
says otherwise — for the two versions that carry the four-bit record, on
both load entry points (bytes and path). R2000 and later keep the flag the
file stores, so a genuinely unplottable layer (DEFPOINTS) stays unplottable.
Tests: an R14 DWG written and read back keeps every layer plottable, the
path-based entry point does the same for R13, a modern file keeps the flag
it stored, and an R14 drawing tessellating to wires that are all
plot_visible locks the whole chain that used to blank the page.
The codec and kernel repositories and crates are renamed opencadcodec and
opencadkernel. Inside OCS they are depended on under short keys, so code
reads `codec::`, `kernel::` and the new `graph::` (opencadgraph, the node
graph engine). File-format and protocol spellings stay as they were: the
ACIS product id `acadrust`, the ACADRUST_* switches and the plugin
version keys `acadrust_version` / `acadrust_source`.
A button left of MCP on the command line opens a translucent node graph
over the viewport, with a palette of object, input, math, logic, list,
text and geometry nodes added by click or drag. An object node owns
drawing entities and its rows are the Properties panel's own rows for
them: the single-object page is extracted from refresh_properties as
entity_property_sections, values use the control protocol's
property_json, and writes go through set_property_value with a property
target override, so the panel, MCP and the graph share one read/write
path. A list reaching an object row makes one entity per element. Each
graph action is one undo step; the canvas pans and zooms.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `--export ... --target-version`, and explicit target_format /
target_version on automation and protocol saves; a conversion that
would drop unsupported records is refused unless allow_lossy is set.
- `audit` read op: dangling layers/blocks, duplicate handles, non-finite
bounds, serialization, lossy records and the source DXF handle graph.
- `save_verified`: writes the requested version from the same snapshot a
normal save writes, reopens it, checks the version and the entity
manifest, audits the ASCII DXF handle graph and returns a SHA-256.
- ZOOM EXTENTS includes SDF text glyphs.
- Live MCP acceptance script and an optional reference-console audit
runner under docs/automation.
Squashed from the PR with review fixes: merged with the DWT save path,
the save snapshot, the tool lists added since, and vendor-neutral
harness names; the dated verification record is left out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `XREF <args>` runs whole: probing the bare verb first now opens the
External References palette, which refreshed every entry and toggled
the palette before `XREF Reload A` / `XREF ?` ran.
- Update the two tests that encoded the old behaviour: `-XREF ?` prints
the reference table, and attaching a missing file is rejected.
- Drop the duplicate "Specify On-screen" catalog entry that shadowed the
translated common key, with its unused ftl keys.
- Move `remove_pipe_symbols`' doc comment back above its function.
- Note in `document_for_save` that resolved xref content stays out.
- Gate the desktop-only preview cap and tab index for wasm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operations docs/automation/API-SPEC.md (PR #1383) describes, brought
over from huaninstratech/OpenCADStudio so the specification no longer runs
ahead of the code:
- entity CRUD: entities_create, entities_delete, entities_transform,
entities_copy_to, and `where` filters on query
- blocks and export: block_define, block_delete, a normalised wblock
- publishing: layout_create, page_setup_set, plot (per_page included)
- metadata: sysvar, xdata_set / xdata_get, file_identity
- collections: group_create, selection_set_save / load
- control plane: batch, events, view_focus, close
- the REST transport: `--http <port>`, the /api/v1 routes and their
OpenAPI 3 description
- person-in-the-loop picks: user_select and getpoint, hosted by the GUI
over the same REST routes, with on-screen pending signals
Taken from the fork's commits b7a7ed80, 69396b42, 64f1cf6a, c3aafc8e,
bb5a7bfa, 52317a5c, 50d293ba, b90e194f, 517c6397, 53a5c4c9, a49ac045,
37de5a46, a78b1822, 6d9aed2a, 33f522da, 5b5cafe2, 02a7e78d, 94df40dc,
fd5bc14d, 485fac61 and 2c5fc994.
Left out on purpose: 37de5a46 also removed the donation surface (the
Patreon fetch, the donation prompt, the Start-page supporters rail, the
DONATE command and its locale strings) and replaced the About dialog's call
to action; that is the fork's own product decision and stays out. The
fork's version bump and its unrelated IFC/BIM work are not part of this.
Adapted while porting: the REST server and the GUI bridge are desktop-only
and a template start is refused in the web build, so the wasm target still
compiles; the two plot tests read the PDF through the shared stream helper,
because exported streams are Flate-compressed; the benches set the new
crosshair field; comments and docs describe each operation in its own
terms rather than by another product's API names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference follows the cursor while its insertion point, scale or
rotation is asked (preview-only PScale/PX/PY/PZ/PRotate included); a
relative path chosen before the drawing had a file becomes relative on the
first save, and relative paths start from "./" at or below the drawing's
folder; -XREF name options report "No matching xref names found.".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Attach External Reference dialog after XATTACH / ATTACH; ATTACH routes
drawings, PDFs and images; placement prompts and messages follow the
reference (presets, Corner/XYZ, nonzero check, existing definition reuse,
missing file, self-reference); name keeps the file's case; overlay and
path type at attach; one undo step for definition and INSERT; -XREF option
prompt and `?` table; XREF opens the palette; ER alias; xref Properties
rows; dependent symbols flagged and remapped and resolved content left out
of the saved file; dialog_ok automation action; localized labels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
printpdf 0.9's `PdfSaveOptions::optimize` is a no-op — its `doc.compress()`
call is commented out — so every page's content stream (megabytes of vector
operators for a CAD sheet) was written uncompressed: a 16-sheet drawing set
exported as 280 MB. Serialise through `printpdf::to_lopdf_doc` and run
lopdf's `compress()` before writing; the same export is now 49 MB and
readers see ordinary FlateDecode streams.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`shx.rs` only accepted the classic `AutoCAD-86 shapes 1.x` container, so
every modern Autodesk font (`txt.shx`, `simplex.shx`, `romans.shx`, … are
`unifont 1.0`) and every Asian big font (`chineset.shx`, `hztxt.shx`,
`bigfont.shx` — `bigfont 1.0`) was rejected and text fell back to the LFF
substitutes and TrueType fallback glyphs. Drawings authored with SHX big
fonts (the norm for Chinese / Japanese / Korean sheets) never showed their
real glyphs.
- Parse all three containers. Unifont glyphs are keyed by Unicode; big-font
glyphs by their double-byte code from a sparse hash table of
(code, length, offset) entries. Big fonts reference subshapes with a
two-byte `7, 0, hi, lo`.
- `bigfont_glyph` encodes a character with encoding_rs (Big5 / GBK /
Shift_JIS / EUC-KR, most likely first for the file name) and takes the
first code the font has.
- `Face::WithBig` pairs a style's big font with its primary face: full-width
characters come from the big font, everything else from the primary —
how a STYLE with a "Big Font" behaves in AutoCAD. `resolve_text_style`
resolves `big_font_file` like the primary (next to the drawing, then the
user fonts folder) and rides it along in the font name.
- The missing-fonts dialog lists unresolvable big fonts too.
Unit tests cover a synthetic big font (Big5 lookup, cap-height scaling, no
aliasing of unknown codes), a synthetic unifont, and the pairing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- The acadrust DXF reader has converted ATTRIB/ATTDEF rotation to radians
since f226110 (in every codec rev OCS pinned from 2026-08-08 on), while
fix_dxf_dimension_rotations still applied to_radians a second time on
load. Only Shape needs the conversion here. Surfaced by #1391's hunk.
- Shape-file text styles are found by their is_shape_file flag instead of
an empty name (also from #1391).
- #1393's add_layer/modify_layer wrote an unresolved linetype name into
the LAYER record; a name the drawing (plus the standard set) cannot
supply is now refused, through one resolve_linetype helper.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- tests/fixtures/plot/.page-setups-metric.dxf.ocs.lock was the app's own
single-instance lock file, committed by accident in #1343; remove it and
ignore *.ocs.lock.
- load_plotsettings_into_dialog: #1343's refresh_custom_scale_fields borrowed
self while #1360 still wrote through the plot_dialog borrow; write the
style error first.
- The wasm list_printers stub returns the Result #1358 gave the native one.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Opening a drawing that another program or machine holds without read
sharing used to fail with a raw OS error, and the memory-mapped fast
path could kill the process outright: a mapped file that shrinks
mid-parse (a writer saving over it) or a cloud placeholder that cannot
page in raises an in-page exception no Rust code can catch.
Read the DWG through a guarded fast path instead: mmap when possible,
and fall back to one in-memory snapshot read — opened with the most
permissive share mode Windows offers (FILE_SHARE_READ | WRITE |
DELETE) — whenever the map fails or an I/O error surfaces mid-parse.
A snapshot can never fault: a concurrent writer just ends the read
early. Sharing violations (os error 32/33) now answer with a message
that names the file and the remedy, and OneDrive-style placeholder
attributes skip mmap entirely.
Tables copied from other applications into the plot styles folder were
listed but refused on selection, showing as "missing" with the reason
lost on a console: the loader failed hard on a checksum or a text length
that did not add up and on any non-UTF-8 byte — and those files carry
their descriptions in Windows-1252.
- decompress_ctb reads the file as it is: a checksum or length mismatch is
reported and read past (a genuinely cut-off file is still an error), a
zlib stream with a bad trailer is retried as raw deflate, and text that
is not UTF-8 is read as Windows-1252 (pens and colours are numbers and
unaffected; other code pages only show wrong characters in descriptions).
- PlotStyleTable carries load_warnings; every load site reports them on the
command line, so a table that needed tolerance says so.
- load_named explains itself: a .stb name says named tables are not
supported yet, a .ctb missing from the folder says which folder to copy
it into (instead of "Unknown built-in plot style").
- The Plot dialog shows the reason under the Table row (style_error) —
from a selection, from a page setup that names a table, and from the
Load… picker, whose error used to be swallowed.
- A table picked with Load… from outside the plot styles folder is copied
into it (unless a file of that name is already there), so it is listed
from then on and page setups naming it resolve after a restart.
Tests: flipped checksum, text length off by one, a short payload field,
Windows-1252 descriptions, a truncated stream, .stb and missing names.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- #1352 pinned cadkernel/cadkernel-constraints to ramox81's fork. Its one
extra commit (cadkernel #67, SymmetricLineDirections) is now on
HakanSeven12/cadkernel main as the same SHA, so the manifests and lock
point back at the upstream repo at rev 0493b59.
- SYCONSTRAINT reports a repeated relation with the same message Fix and
Vertical use, as an error instead of plain output.
- The knot-less spline test consumes the add_entity results.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The plot dialog asks CUPS printers for their sheets and margins; on Windows
the query was a stub, so every printer showed the paper catalogue with
unverified margins. The driver is now asked the way the plot dialog expects:
- windows_media (compiled and tested everywhere): the three parallel
DeviceCapabilities tables (ids, sizes in tenths of a millimetre, 64-WCHAR
names) become one PrinterMedia per distinct size — a rotated or
borderless twin folds onto its sheet, catalogue sheets keep their name,
unknown sizes carry the driver's — with the device's margins on every
entry and the driver's default sheet found by id or by user size.
- print_to_printer (Windows): DeviceCapabilitiesW for the tables, the
default DEVMODE for the default sheet, one portrait printer DC for the
unprintable margins (physical offsets and extents, per device — a
documented approximation). The DEVMODE fetch is shared with the print
path (with_printer_devmode).
- A direct print now asks the driver for the plot's own sheet (by id, or
as a user-defined size), so an A3 plot prints on A3 rather than shrunk
into the driver's default; a driver that refuses the sheet is asked for
the orientation alone.
- The capability cache remembers printers that could not be asked, so a
slow failing query runs once per session, not on every dialog open.
- PRINTERS <name> reports a printer's sheets, margins and default sheet.
Tests: names (NUL trim, full slot, lossy), media folding and dropping,
default by id / user size / none, DC margins, paper id lookup.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On Windows a plot with no printer chosen went to the PDF application's
bare "print" verb — whatever that application considered its default —
and a viewer without a "printto" verb (Edge, store readers) silently
dropped the printer the dialog had chosen. The dialog also never named the
system default, and a failed printer enumeration left only "Default" and
"PDF" with the reason written to a console nobody sees.
- The default printer is looked up by name (GetDefaultPrinterW) both for
the dialog's "System default printer (…)" entry and before every job.
- A pure route decision (plan_print_route) resolves "Default" to that
name and uses the PDF application only when it registered a "printto"
verb for .pdf (AssocQueryStringW); otherwise the job goes straight to
the spooler through the existing GDI path. The report says which:
"Sent to printer: NAME (direct print | via the PDF application)".
- list_printers reports the system's own error; the plot dialog shows it
under the printer row and the command line warns once. A desktop without
CUPS is an empty list, not an error.
- PRINTERS command: default printer, listed printers (or the error) and,
on Windows, the print route — a report to paste into an issue.
- Properties… on the Default entry opens that printer's preferences sheet.
- GDI path: the driver's dmSize is kept (its private data lives behind
it), the print raster is no longer memoised for the session, and a
spooled temporary PDF is removed.
Tests: route planning (named / blank / no default / printto or not), the
PRINTERS command; the two Windows-only GDI tests pass the resolved name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On the web build, {"op":"open","path":…} resolves the name against the
browser's recent-files cache, so a file that is not already there cannot be
opened by the control channel. Accept {"op":"open","name":…,
"data_base64":…} instead: the bytes go through the same parse, caching
and recovery path as a file chosen in the browser picker
(io::open_bytes_web, now shared with pick_and_load_web).
Native builds keep path-based open and reject data_base64 with
invalid_request.
Refs #1349
crate::network and the open crate are desktop-only, but the community-font
download (font_repo.rs) and the XREF row Open action call them
unconditionally, so cargo check --target wasm32-unknown-unknown fails on
main. Gate both; on the web download_fonts returns an error, since the web
build has no network client or fonts folder.
When a display plot style maps an ACI to a fill pattern, wire fill
triangles are re-emitted as PLOTSTYLE hatches. Those took
depth_override raw — a per-block child label for block wires and 0.0
for top-level ones — while everything else in the hatch/wipeout pass
sorts at the composed depths[insert] + label * half. The fill could
land outside its host's band and get buried under its own block's
masks (same shape as the block-text bug).
Both the display and PDF paths now travel through wire_draw_depth /
the PlotWire's composed depth, and the display cache key rides the
depth generation so a DRAWORDER edit re-ranks the fills without
needing a wire rebuild.
load_file, used by headless export, xrefs and block import, finalizes a
drawing without purging, so the spline degree repair never ran there and
`--export drawing.dxf out.dwg` still hit the knot underflow. Run the repair
when any load is finalized.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Retaining a line's length added a P2PDistance constraint, whose gradient
divides by the current distance. For a zero-length line the solver got a NaN
Jacobian and its SVD fallback never returned, freezing the editor (e.g.
ECONSTRAINT on a line drawn by clicking one point twice). Hold coincident
endpoints together with per-axis differences instead.
Control-point splines read without knots are given a degree their control
points can carry (at most n - 1) and a clamped knot vector when a drawing
loads. Generating knots for fewer points than the degree underflowed during
DWG write: a panic in debug builds, an unbounded allocation in release, and
reachable from autosave. A lone control point with no knots or fit points is
purged as corrupt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>