The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <[email protected]>
Reading a drawing by path is gated off on wasm, and two features added
since 0.9.3 call it unconditionally: importing a drawing as a block, and
taking layer standards from one. Both compile into the web build, so the
web bundle stopped building and the v0.9.4 Pages deploy failed.
Neither feature can work in the browser — a file arrives through the page,
not from a filesystem the app can open — so the fix is not to make them
work but to let them say so. `load_file` now exists on wasm and returns
that as its error, which both callers already surface. Keeping the
signature means neither has to know that the web has no files, and the
next feature that reads a second drawing will not break the bundle
either.
Co-Authored-By: Claude Opus 5 <[email protected]>
Two places still showed the old mark after the logo was redrawn.
The window icon was built by drawing the letterforms stroke by stroke in
code, background colour and all, which made it the one icon in the
application not coming from assets/logo.svg. It now rasterises that file,
fitted to the square without distorting whatever aspect the logo has. It
returns nothing when the logo cannot be rendered, so the window falls back
to the platform default instead of to something invented here -- which is
how it ended up stale in the first place.
The icon installed into the icon theme was only ever written from inside
the desktop-entry write, behind that write's own "nothing changed" early
return. The entry's text does not change when the logo does, so once it
had been written the icon was never refreshed again. The two change for
different reasons; the icon is now checked first and on its own terms. It
already compares bytes, so an unchanged logo still costs only a read.
Worth knowing on Wayland: the compositor ignores the window icon entirely
and takes the dock icon from the desktop entry, so there the installed
theme file is the only one that matters -- and it refreshes when the file
associations are next set up, not at launch.
Co-Authored-By: Claude Opus 5 <[email protected]>
Drawings arrive from elsewhere carrying someone else's layer names, and
folding them into a set of house layers meant renaming and merging by
hand, one layer at a time.
LAYTRANS reads a layer set out of another drawing and moves this one onto
it. Given a file it maps every name the two already share and translates
at once, which is all most incoming drawings need. Bare, it opens a
translator: the drawing's layers on one side, the loaded set on the
other, mappings built up between them and applied only on Translate, so a
mapping can be reconsidered and the whole translation is a single undo
step. Mappings can be saved and read back, objects can be forced to
ByLayer so the target layer's properties are what actually shows, and the
result can be written out as a log.
The move itself is the one LAYMRG already made for a single pair, so
LAYMRG now goes through it too rather than keeping its own copy.
Three things fell out for free or nearly so. A standards file is a
drawing, so .dws and .dwt need nothing but a place in the file filter.
The entity list is flat and holds block definitions, so objects inside
blocks travel with everything else instead of being stranded on a layer
that no longer exists. And a target layer that this drawing already has
is left alone: the drawing has an opinion about it, and restyling it
would reach past what was asked for -- only newly created targets take
the standard's properties.
Mappings are saved beside the drawing rather than inside a DWS. That is
where AutoCAD keeps them, but putting them there would mean inventing a
place in the drawing that nothing else reads back.
Closes#624
Co-Authored-By: Claude Opus 5 <[email protected]>
Keep Layout output on the selected physical sheet and preserve hatch lineweights and plot styles through paper, viewport, and nested block paths.\n\nRefs #607
- unify Model and Paper Space plot area behavior
- add CTB discovery, read/write, and bundled defaults
- apply scale, render mode, and output options consistently
Use the same area pipeline as model space while keeping Layout bound to the physical sheet and printable margins. Share scale, rotation, clipping, and viewport fills across preview, PDF, and printer output.
Use the operating system trust store and Windows proxy settings while keeping TLS verification enabled. Add retryable registry errors with copyable diagnostics.\n\nRefs #563
Hold edit leases and compare disk fingerprints before replacement so another editor cannot be silently overwritten.
Offer retry, reload, Save As, or explicit overwrite recovery.
Closes#498
Anchor the opposite viewport corner during grip and STRETCH resizing.
Match plotted stroke-font pen widths to the SDF atlas unless CTB overrides.
Closes#435
The embedded DWG preview was the whole drawing fit to a canvas, encoded
as a 24-bit uncompressed DIB (~180 KB at 256²) — the bulk of the gap
between an OCS-saved file and AutoCAD's. Three changes:
- Frame it like the screen. `from_scene` now projects every wire vertex
through the live camera into a viewport-aspect canvas, so the thumbnail
is the current pan / zoom / rotation and only the visible region, not
the full extent. Off-screen / clipped segments don't draw (a `None`
projection or NaN separator breaks the polyline run). Works for any
view. `stamp_thumbnail` passes the model pane's pixel size and the
target version.
- PNG for R2013+ (AC1027+). A line drawing on a flat background is almost
one colour, so PNG collapses to a few KB. PNG previews are only valid
from R2013, so older targets keep a BMP.
- BMP is now 8-bit palettised, BI_RLE8-compressed (was 24-bit raw). The
long single-colour runs of a preview compress to a handful of KB; a
view with >256 colours (rare) falls back to the 24-bit DIB. A full
256-entry palette is written so the reader's `(1<<bpp)*4` pixel-data
offset lands correctly.
Net: the preview drops from ~120 KB to ~2-5 KB on every version.
Co-Authored-By: Claude Fable 5 <[email protected]>
entity_wires() returns the memoized Arc instead of a per-call deep copy;
MESH entities over 50k vertices ship no per-vertex snap/key tables
(matching PolyfaceMesh/PolygonMesh); the Face3D/other render split
caches a None marker and uses the base set directly when there is no
Face3D wire; test fix for the depth-map BlockCache::build signature.
Reviewed: correctness sound — wire AABB comes from entity_aabb (not
key_vertices) so large meshes stay selectable; snap consumers are
empty-guarded; the Arc/strong_count reasoning for try_resident_patch
holds. One consistent pre-existing quirk noted (auto_fit_viewport
centroid excludes tableless meshes, as Poly*Mesh already do).
Document linetypes referencing ltypeshp.shx glyphs (ZIGZAG, BATTING,
TRACKS) skipped their shape elements whenever the shape file wasn't on
disk — the usual case for traded drawings. The standard ltypeshp
numbers now bridge to the bundled LFF substitute shapes (130 TRACK1,
131 ZIG, 132 BOX, 133 CIRC1, 134 BAT); a resolvable file still wins
with the real SHX glyph.
The element rotation also converted twice: DWG stores radians, the
segment field carries degrees (the .lin unit), and the walker calls
to_radians() — a 180°-flipped second copy rendered at ~3°, folding the
below-baseline half of ZIGZAG/BATTING up onto the line. Convert at the
document boundary.
Co-Authored-By: Claude Fable 5 <[email protected]>
The SHX reader now serves all three consumers instead of just SHAPE
entities:
- Complex-linetype shape elements stopped being skipped: a document
linetype's Shape{number} resolves its style's shape file (shared path
fallbacks) and draws the real glyph; `.lin` catalog references keep
the file token and look the shape up by name. The converted-LFF
substitute set remains the fallback for anything unresolved.
- Face::Shx renders resolvable on-disk .SHX text FONTS with their real
stroke glyphs: character code → shape lookup, cap height normalised
to the 9-unit space every stroke-font consumer expects, advance from
the interpreter's final pen position, line spacing from the shape #0
above/below header, TTF fallback per missing glyph.
resolve_text_style resolves a style's .shx next to the drawing and
passes the absolute path through; unresolvable names keep their LFF
substitutes. Shapes-only files (no #0 header) are refused as fonts.
Co-Authored-By: Claude Fable 5 <[email protected]>
SHAPE entities only drew a diamond placeholder — .SHX files were never
parsed. A new reader (scene/text/shx.rs) decodes the classic
`AutoCAD-86 shapes 1.0/1.1` container and interprets the full shape
bytecode — pen up/down, scale divide/multiply, position push/pop,
subshapes, signed XY runs, octant/fractional/bulge arcs, the 16
direction vectors and the vertical-only skip — into unit-space
polylines, cached per file and per shape number.
The entity resolves its STYLE by handle (name fallback), the style's
shape file with the shared path fallbacks (as stored → relative to the
drawing → basename next to it; traded files carry foreign absolute
paths), then places the glyph with size × relative X scale, oblique
shear and rotation through the OCS mapping. load_file now records
doc.source_path so render-time resolution knows the drawing's folder.
A missing or unreadable shape file keeps the diamond placeholder.
Co-Authored-By: Claude Fable 5 <[email protected]>
PDF underlays only drew their clip outline / insertion cross; the page
itself never appeared. The chain now runs: the definition's file path
resolves with the raster-image fallbacks (as stored, relative to the
drawing, basename next to the drawing — attach-time relative paths go
stale), the page rasterises through the system pdftoppm (poppler) at
150 dpi — no new crate; missing tool degrades to the outline — cached
per (path, page) including negative results, and the pixels feed the
existing image pipeline as an ImageModel: 1 underlay unit = 1 PDF inch
(px/dpi × entity scale), rotated about the insertion, fade as opacity,
the clip polygon (non-inverted) as the textured region. Draw-order,
layer visibility and per-viewport freezes ride the generic image path;
in-place edits rebuild through the shared image seed.
When the page renders, the wire path draws its frame with an interior
pick surface instead of the lone cross, so selection covers the visible
page. wasm keeps the outline placeholder (no external process).
Co-Authored-By: Claude Fable 5 <[email protected]>
entity_wires() cloned the entire wire set — every point, fill, and snap
buffer of every entity — on each call, although the set is already
memoized behind an Arc and none of the 18 call sites mutates it. On
large mesh imports (#358: 42M faces, ~210 MB of wire content per 1M
triangles) that is a multi-GB allocation + memcpy on every pick, snap,
selection, or plot pass.
Return the Arc instead. Call sites iterate as before; the two
window-plot paths that build a culled owned subset now clone only the
wires inside the plot window, and the print pipeline moves the Arc into
its async task.
Measured (tests/mem_probe_meshent.rs, 1M-triangle MESH, release):
resident after entity_wires() drops 758 -> 547 MB (866 -> 646 B/face).
Co-Authored-By: Claude Fable 5 <[email protected]>
Xref merge now inserts entities in the source block's chain order and
carries SortEntitiesTable objects across (remapped to merged handles).
draw_depth_map values become [depth, half] so exploded block/xref fills
and wide-polyline band wires both compose child depth as
insert_depth + in-block rank x half, recursing into nested inserts with
the same divisor - bands and fills from one block interleave by the
block's internal order instead of stacking at the insert's single depth.
Band batches split per rank via WireModel.depth_override; thin wires
stay merged. The wire arena falls back to a full rebuild when a slab
carries mixed depths so refreshes don't flatten band overrides.
Co-Authored-By: Claude Fable 5 <[email protected]>
Every rfd dialog result funnels through sys::handle_path, which now
records the picked file's folder (persisted to the config dir), and all
pickers are built via sys::file_dialog(), seeded with that folder.
Save-As keeps the current tab's own directory as the first choice and
falls back to the remembered one.
Co-Authored-By: Claude Fable 5 <[email protected]>
Section marks and drawing-view borders are now real acadrust entity
types instead of Unknown-with-extras, so the renderer dispatches on the
variant directly, the arrow-direction resolver reads the active viewport
off the ViewBorder entity, and selection / properties show "Section
Symbol" and "View Border" instead of "Unknown". View borders are
non-plotting and tessellate to nothing; both count toward the lossy
Save-As warning since they round-trip via their raw records.
Bumps acadrust for the entity promotion.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The shader-band change stopped emitting a hatch fill for wide polylines, so
PDF export — which drew the band from that HatchModel — lost it and plotted
only the thin centre-line.
Stroke the centre-line wire at the band width instead. In `build_pdf`, a wire
carrying `world_width` sets the pen to `world_width * MM_TO_PT` with no
`/ scale`: a lineweight is absolute on paper (hence the divide), but a band is
drawing-unit geometry and must scale with the plot. The CTM's scale then
renders the band at `world_width * scale` on the sheet, matching the geometry,
and the linetype dash pattern strokes it dashed. Overrides any CTB pen weight
(the width is geometry, not a lineweight).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A linetype element can carry a `complex` record with no renderable content
(shape #0, null style handle, zero scale) — some DWGs store plain dash
elements that way (e.g. ISO03W100 "ISO dash space"). document_complex_lt
treated any `complex.is_some()` as complex and sent the entity down the CPU
apply_along path, which skips the GPU dash shader and its A-type endpoint
alignment. Gate on *renderable* content (non-empty text, or a shape with a
real shape-file handle) so these fall through to resolve_pattern and dash on
the GPU with proper A-type alignment.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Multilines were drawn with a fixed ±scale/2 two-line guess in the entity
colour, ignoring the MLINESTYLE entirely, so a drawing's custom style (per-
element offsets, colours and linetypes) was lost — a red/yellow multiline
rendered as a white box.
- mline.rs: resolve the MLINESTYLE (by style_handle, then name) and build one
parallel line per element from its offset x scale + justification shift.
- tessellate.rs: special-case MLINE (like Leader) to emit one WireModel per
element in the element's own colour, CPU-dashing every element through
apply_along so the parallel lines stay in phase.
- linetypes.rs: render a drawing's OWN complex linetypes (embedded text/shape),
document definition first and the bundled .lin catalog only as a fallback, so
a file-defined text linetype like PLYN shows its text.
- Run the dash/glyph layout in a local frame (f64 origin subtracted, re-split to
double-single after) so spacing stays precise at UTM coordinates, and pass
tracking = 1.0 so embedded text keeps its letter spacing.
Cargo.lock pins acadrust to the MLINE->MLINESTYLE handle-link reader fix.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
sdf_text_emits_vector_ops_at_the_run_position laid glyphs into the process-
global text atlas, released the lock, then re-read the atlas in emit_text.
Another test growing or resetting the atlas in between shifted the uv keys,
so the lookup missed and the assert saw zero polylines — ~1 in 5 parallel
runs. Its own comment already noted it couldn't catch the one thing worth
catching (a mirrored corner mapping); the size-based tests and text_gpu's
vertex-order tests cover what remains. Not worth a global test mutex.
The AppImage ships both the .desktop file and the hicolor icon, yet the dock
button came up blank on Wayland. A Wayland compositor has no StartupWMClass to
key on — it matches a window's app_id against the basename of an installed
.desktop file to find its icon — and iced defaults window app_id to an empty
string, which OCS never overrode, so nothing matched.
Set application_id to the reverse-DNS id the .desktop is installed under,
reusing the existing file_association::APP_ID const rather than a second copy.
iced enables both the x11 and wayland winit backends by default and feeds the
same application_id to each: on Wayland it is the app_id, on X11 it becomes
WM_CLASS. So this also changes the X11 WM_CLASS, and the old
StartupWMClass=OpenCADStudio would no longer match — realign it to the same id
so X11 keeps working too.
Confirmed on COSMIC/Wayland: the dock icon now appears. This is the Linux half
of the same class of bug as the Windows taskbar report (#392, still open): the
window not carrying the identity the OS needs to find its packaged icon.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
A UTM drawing plotted deformed — both geometry and text — while the same
drawing near the origin came out clean. Every pass of the exporter narrowed
an absolute world coordinate to f32 before the offset had a chance to cancel
it, and f32 resolution at UTM is ~0.03 m on easting and ~0.5 m on northing,
so the whole plot snapped onto that grid.
Four places did it, all now folding in f64 and narrowing only once the
result is a single-digit sheet-mm value:
- The wire pass never read `points_low` at all — the word did not appear in
the file — so it plotted the double-single "high" half alone and dropped
every residual.
- emit_hatch cast the f64 `world_origin` to f32, re-introducing at the last
step exactly the quantisation the boundary-relative encoding exists to
avoid.
- pattern_segments rasterised entirely in absolute f32. The pattern is
anchored at world (0,0), so a hatch at UTM runs a line index k of ~1e7:
`origin + k · step` and the `edge − line` intersections both fall apart
there. k also becomes i64 — `as i32` saturates silently, which would
collapse the range to a bogus run from i32::MIN rather than drop the fill.
- emit_text folded `pos + pos_low` in f32, which rounds straight back to
`pos`: the residual the pair exists to carry was discarded at precisely
the coordinates it was carried for.
The offsets themselves were also narrowed by the callers, so the value meant
to cancel a UTM coordinate was quantised before it could. Widen ox/oy to f64
through export_pdf, build_pdf, print_wires and the two plot-param builders;
printing shared the same path and the same bug.
Pre-existing, not from #390 — that change only routed text through the same
faulty pipe the wires were already in.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Review follow-ups to #390, which fixed PDF/print text for model space but
left the layout-plot path — the one issue #385 was filed from — broken.
viewport_content_wires rewrote points/snap_pts/key_vertices/aabb and then
cloned text_verts through untouched, so paper-space viewport text stayed at
model (UTM) coordinates: a dimension at model (25000, 12000) plotted its
lines onto the sheet and its glyphs kilometres off the page. A text-only
wire fared worse — TEXT/MTEXT carry no stroke points since text went
SDF-only, so it was dropped outright by the empty-clip and AABB rejections.
Project the glyphs through the same proj_abs the snap points use (via the
existing map_text_verts helper), cull per glyph on the quad centroid, count
them towards the paper AABB, and dim them like the wire colour.
Also in emit_text, which diverged from the wire pass it mirrors:
- divide the pen by `scale`, or a Fit/windowed plot prints text as
near-invisible hairlines while its own lines stay correct;
- apply the CTB plot style, or a monochrome.ctb plot renders the lines
black and leaves the text on its screen colour;
- reset the dash pattern, which is persistent PDF state the wire pass
leaves set — a HIDDEN-linetype last wire printed glyph outlines dashed.
export_table's doc claimed it honoured TEXTFILL and widened the bold pen
itself; it did neither. Gate fill_tris on textfill() so hollow-on-screen
text exports hollow, carry the bold flag and widen the pen by the 1.7x the
bake uses, and resolve Face once per family instead of once per glyph (the
atlas mutex is held for that whole walk).
uv_key identifies a glyph by its tile's uv_min, but grow_height rescales
every entry's V and reset rewinds the packer, leaving already-built quads
addressing the wrong tile — garbage on screen and a silent miss in the
export table, i.e. #385 again under #347's grow-the-atlas conditions. Bump
an atlas generation on both and fold it into the tessellation memo guard so
the text re-lays-out instead. Quads built earlier in the same pass that
grows are still stale for that pass; the guard heals them on the next one.
Tests: the integration test's "some wire has text_verts" assert passed on
pre-fix code too (the exporter ignored them; the scene always carried
them), and its dimension half was satisfied by the TEXT alone — assert per
entity handle instead, and stop colliding on fixed names in the shared temp
root. The unit test's bbox came from the same quads the mapping reads, so
it held by construction; pin the run's own world box instead. That still
cannot catch a mirrored corner assignment — verified by mirroring it — so
say so rather than claim coverage the assert doesn't have.
Co-Authored-By: Claude Opus 4.8 <[email protected]>