3D solid meshes inside block definitions were drawn once at block-local
coordinates without the INSERT transform, so a block authored in mm and
placed into a metre drawing at a 0.001 INSERT scale rendered ~1000x too
large. Split solid meshes into top-level (layout-owned, offset and drawn
flat) and block-definition (kept block-local) sets, and emit a transformed
instance of each block-def mesh per model-space INSERT, accumulating nested
transforms. Layout blocks are identified from the Layout objects'
block_record handles, since BlockRecord::is_layout() also flags ordinary
blocks in some files. (#123)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move grid and grid-snap toggles off the status bar (and its customization
menu) into the top-left viewport chip, now a single container holding the
horizontal/vertical split buttons, render-mode picker, and grid/snap
toggles, sized to the render-mode picker. Add grid + magnet (snap) icons.
Render the grid per model tile from each tile's own grid_on, in its own
bounds and camera, instead of a single grid in the active tile. This stops
the grid flickering on/off as the cursor crosses panes or leaves the
viewport, and lets each pane show its own grid independently.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make grid-snap a per-drawing view setting like grid visibility: drop it
from the persisted OSNAP set (settings.txt) and round-trip it through each
viewport's VPort SNAPMODE instead.
Grid display and grid-snap are now stored per model tile (like render
mode), written to and read from each tile's *Active VPort entry. The live
toggles mirror the active tile, and grid/snap are adopted from the active
viewport whenever it changes — on file load, tab switch, model-tile
activation, and new drawing — so they follow the active viewport rather
than a global preference.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grid on/off now round-trips through the drawing's *Active VPort, so it is a
per-file view setting like every other CAD app treats it. Drop show_grid
from UserSettings (and its settings.txt key) and instead adopt the opened
file's saved grid_on on load. A legacy "grid=" line in an old settings file
is harmlessly ignored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the in-app grid and grid-snap toggles (plus the adaptive grid
spacing) onto the *Active VPort entries so a saved DWG/DXF reflects what
the user actually has on, instead of always writing VPort defaults. The
flags are stamped when the toggles change and again before a save, and
carried across the camera-sync VPort rebuild so they survive an orbit/pan.
Only grid_on, snap_on and grid_spacing have an OCS counterpart; the other
VPort view fields (view twist, clip planes, lens length, etc.) have no
in-app source and stay at their faithful defaults.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neutralise vendor references in comments to plain behavioural descriptions
(object acquisition / object snap). Remove the PICK_HIGHLIGHT and
PICK_HIGHLIGHT_DIM wire colours: their only consumer was the stripped
storm-sewer add-on, and the retained plugin acquisition pathway draws the
ObjectPick snap glyph and per-command hover previews instead, so the
constants are dead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Turn the ribbon Clipboard "Paste" button into a dropdown:
- Paste — existing pick-a-point paste.
- Paste to Original Coordinates — drops the clipboard at its source
coordinates with no prompt.
- Paste as Block — wraps the clipboard contents in a new block definition
and starts an interactive insert (prompts for the drop point, with the
geometry rubber-banding under the cursor).
Also fixes two bugs the new commands exposed:
- entities_centroid summed the NaN separator points wire models carry,
so the paste base point came out NaN; non-finite points are now
skipped. A NaN base previously froze block tessellation outright.
- PASTEORIG added clipboard entities with their original handles
(duplicates on same-document paste); it now goes through
add_entity_clone and merges referenced records like the other pastes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`document.add_entity` only assigns the top-level handle, leaving inline
sub-entities that carry their own — INSERT attributes and 3D-polyline
vertices — with the source's handles. Copying such an entity therefore
produced duplicate handles in the document, which corrupts the saved DWG
so other CAD apps (QCAD, DWG TrueView) refuse to open it.
Add `Scene::add_entity_clone` / `reset_clone_subhandles`, which allocate
fresh handles for the entity and every inline sub-entity, and route all
duplication paths through it: COPY/ARRAY/MIRROR-copy (via copy_entities)
and clipboard paste. (#129)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Points could only render as a 1px dot — no way to change PDMODE/PDSIZE.
- PDMODE / PDSIZE commands set the header value and rebuild the glyphs.
- DDPTYPE opens a Point Style dialog: a 5x4 grid of rendered glyphs
(shape x enclosure), a size field, and relative/absolute radios.
- Relative (<=0) PDSIZE now sizes from the current world-per-pixel so the
marker stays a roughly constant on-screen size across zoom, rebuilding
on zoom via the existing camera-generation cache key. Absolute (>0)
scales with the world as before. Switching to absolute with a zero size
seeds a positive value from the on-screen size so it stays representable.
Part of the #100 extensibility epic (point style surfaced in #106).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the wasm bundle building, get it rendering in a browser:
- Enable iced `webgl` so wgpu uses WebGL2 where WebGPU is absent
(e.g. Firefox by default), and `fira-sans` so UI text has an
embedded font (the web has no system fonts) — without it every
label was blank.
- Replace `std::time::Instant` with `iced::time::Instant` (web-safe);
`Instant::now()` panics ("time not implemented") on wasm and crashed
at boot via the command line.
- Skip the batched-hatch pipeline on wasm: its bind group needs
vertex-stage storage buffers, which WebGL2 lacks (validation error
on "New drawing"). Fields become Option; upload/draw guard on them.
Hatches don't draw on the web for now; everything else does.
- Fix the trunk feature flag (`data-cargo-no-default-features`) and
ignore the `dist/` build output.
Native build + 75 tests unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wasm32 has no threads, so rayon's global pool would panic at runtime.
Add `crate::par::prelude`: native re-exports `rayon::prelude`, wasm
provides sequential `std`-iterator equivalents (par_iter / into_par_iter
/ flat_map_iter over slices, Vec, HashMap, HashSet). All call sites now
`use crate::par::prelude::*;` instead of rayon directly.
- wire_gpu's rayon `reduce` (2-arg, no std equivalent) is cfg-split to
a sequential flat_map concat on wasm.
- rayon moves to a non-wasm target dependency.
Native still runs in parallel; all 75 tests pass. wasm bin compiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Home tab was already titled "Draw"; rename the module to match:
folder modules/home → modules/draw, HomeModule → DrawModule, id "home"
→ "draw". The inner draw/ command folder is kept (modules::draw::draw).
Update build.rs PRIORITY ("home" → "draw") so the tab returns to the
front of the ribbon; registry.rs regenerates accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the `*_model` naming of the sibling render models (wire_model,
hatch_model, mesh_model, image_model). Also realign the related Scene
API for consistency: model_solids → solid_models, add_model_solid →
add_solid_model, register_model_solid → register_solid_model.
No behavior change; build and tests are green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the 30 flat modules under src/scene/ into six folders by role:
convert (entity→geometry), text (fonts/shaping), model (render models),
pick (hit-test/selection/grips/xclip), view (camera/transform/render),
cache (block + property caches). pipeline/ is unchanged.
Full path migration — all `scene::X` references become
`scene::<group>::X`, no compatibility re-exports. render.rs items that
were `pub(super)` (parent was scene) become `pub(in crate::scene)` to
keep their original scene-wide visibility.
No behavior change; build and tests are green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a TrueType/OpenType render path alongside the LFF stroke engine so
users can pick fonts installed on their machine for text and MText.
- Discover system families via fontdb (sysfont) and list them in the
text-style picker.
- Extract glyph outlines with ttf-parser, flattened into wire strokes
normalized to the same 9-unit cap-height space as LFF glyphs.
- Shape runs with cosmic-text (ligatures, Arabic joining, kerning, bidi)
plus automatic font fallback: a glyph missing from the chosen family
is taken from another installed font.
- Unify both sources behind a single Face type; a shared run tokenizer
drives the stroke (per-glyph) and shaped (TTF) renderers identically.
- Stroke fonts now fall back to a system TTF (cosmic-text picks it) for
characters no LFF font covers, replacing the unicode/iso3098 chain.
Also trim the embedded LFF set from 47 to 26, keeping the AutoCAD-SHX
equivalents and dropping LibreCAD-only fonts now covered by TrueType.
LFF builtins keep priority over same-named system fonts so DXF text
renders identically across machines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hatch fills render through the separate HatchModel pipeline, so the
block wire clip never touched them: hatches outside an XCLIP boundary
were still drawn. Clip each hatch boundary loop to the clip polygon
(Sutherland-Hodgman, even-odd islands preserved) when the insert carries
a spatial filter.
Also fix hatches inside the boundary that were never drawn: the hatch
collector exploded each insert only one level, so hatches nested in
sub-blocks were dropped entirely. Walk the full block tree (depth
guarded) so nested hatches render and clip like the rest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Block references carrying an XCLIP spatial filter were drawn in full,
ignoring the clip boundary. Resolve the filter through the INSERT's
extension dictionary (ACAD_FILTER -> SPATIAL) and clip the expanded
block geometry to the boundary polygon: polylines are split into the
inside runs, fill triangles are clipped, and snap/key vertices outside
the boundary are dropped.
The boundary vertices live in the clip-definition space, so they are
placed with world = T_insert * (inverse_block_transform * vert); this
keeps the clip correct even when the insert was rescaled after the clip
was created. Bumps acadrust to the build that decodes SPATIAL_FILTER.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebuild every ACIS face as a truck topological Face and mesh it with truck,
routing loaded 3DSOLID/BODY/REGION/SURFACE through the same kernel the Model
tab uses. plane→try_attach_plane, cone/cylinder/sphere/torus→revolution faces
(truck's Surface has no cone/sphere/torus), spline→BSplineSurface grid sample.
Per-vertex normals are computed analytically outward per surface type and
triangle winding is oriented to match, since truck's own orientation isn't
consistent across independently built faces. `tessellate_solid3d|body|region|
surface` try this first and fall back to the bespoke sampler.
Light the mesh shader with three spread directional lights plus ambient
(abs(dot) per light, two-sided) so faces — including the inner walls of an
open lofted surface — never read as a flat dark mass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ACAD_SURFACE entities now load and render. Their NURBS faces are
tessellated by building a truck BSplineSurface from the ACIS `spline-surface`
control net and knot vectors, then sampling its parametric grid — reusing the
same NURBS kernel the Model tab already builds on. Analytic faces
(plane/cone/sphere/torus) continue through the existing tessellator.
- New spline_tess.rs: parse the ACIS `nubs` block (degrees, knots with the
clamped end-multiplicity fix, control grid) into a truck BSplineSurface and
mesh it.
- tessellate_surface + Surface wired through the mesh-build, fallback-wire,
and entity-classification paths.
- Bump acadrust to the commit that adds the Surface entity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the image shader's uniform layout to the shared 112-byte Uniforms
struct (was 144) and project quad corners via view_proj so texture-quad
draws no longer fail uniform binding validation.
Offset image quad corners by world_offset, matching all other Model-space
geometry, so rasters land in the same local space instead of being shifted
away. Downscale oversized images to the GPU's max texture dimension (8192)
to keep texture creation from failing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ZOOM EXTENTS routes through fit_all, which built its bounds from the
wire set and returned early when there were none. A drawing of only 3D
solids has no wires, so ZOOM EXTENTS did nothing. Collect the
layout's mesh AABBs alongside the wires, keep going when only meshes
exist, and fold them into the fitted bounds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ZOOM EXTENTS / auto-fit derived the drawing bounds from wire AABBs and
tessellated key-vertices only. 3D solids (Solid3D/Region/Body) render as
meshes with an empty wire path, so they contributed nothing and a
drawing containing only solids zoomed to nothing. Fold each mesh's
offset-relative XY AABB into the extents on both the wire-cache and
tessellate-fallback paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The GPU in_polygon ray-cast walks sequential boundary pairs and never
wraps last→first, so it needs an explicit closing vertex. The polygon
fill branch already appends one, but the rectangular branch returned a
bare 4-corner quad — the v3→v0 (left) edge went untested and the solid
mask bled past the boundary. Newly created wipeouts take this branch,
so the fill rendered wider than the clicked box while the border/grips
(drawn as wires) stayed correct. Repeat corner 0 to close the loop.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hit_test_wires got a cache keyed on geometry_epoch alone (462d9b4), but
the underlying wire set IS view-culled (wires_for_block culls to
view_world_aabb). After zoom-in + edit, the cache pinned the set culled
to that close-up view; zooming back out, blocks that had been off-screen
were missing from the pick set, so hover/selection stopped working on
them until the next geometry change.
Revert to the camera-keyed entity_wires_arc for picking — it re-culls
when the view changes. The actual 0.5.1 hover regression fix (hatch
rebuild/re-upload on rollover) stays.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Selected hatches carry a tint (#71), so 0.5.1 keyed both the hatch model
cache and the static GPU buffer re-upload on `selection_generation`. But
that counter also bumps on hover, so moving the cursor over a new entity
rebuilt EVERY hatch model and re-uploaded the hatch + face3d buffers each
time — the large-file hover stutter introduced between 0.5.0 and 0.5.1.
The hatch tint depends only on the *selected set*, never on hover. Key
both on an order-independent signature of `selected` instead, so a
rollover keeps the caches warm; an actual select/deselect still rebuilds.
Also cache the picking wire set on `geometry_epoch` alone (separate from
the camera-keyed render cache) so a pan/zoom no longer re-tessellates the
whole un-culled model on the next mouse move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three hatch fixes:
- Selected hatches are tinted, but the hatch model cache and GPU upload
were keyed only on geometry_epoch, so Esc cleared the selection set
without repainting — the hatch stayed blue (#71). Key both on
selection_generation too.
- add_hatch stored the boundary entity in local render space, so the
boundary wire re-projected `world_offset` away from the fill on
offset (large-coordinate) drawings. Build it in WCS.
- Clicking a hatch only selected it on its boundary wire, not its fill.
Two causes: visible_hatches_for_click used a narrower block-visibility
test than the render path (fill drawn but not clickable), and
point_in_polygon closed every NaN sub-path — degrading the explicit
2-point edge encoding of a boundary into zero-area loops. Mirror the
render's visibility test and only auto-close sub-paths of >=3 verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the Phase 2.2 per-entity memo to the common modify commands, which
still did a full bump_geometry (whole-model re-tess + block rebuild):
- transform_entities (MOVE / ROTATE / SCALE / MIRROR) marks each
transformed handle dirty, then bump_geometry_no_blocks;
- erase_entities drops the deleted handles from the memo and
bump_geometry_no_blocks;
- copy_entities' new handles are natural memo misses, so just
bump_geometry_no_blocks.
All three are top-level operations (including on inserts) that can't change
a block definition, so keeping the block cache is sound. Each now
re-tessellates only the touched entities and reuses the memoized wires for
the rest, matching the grip / line-commit path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2.2. wires_for_block_culled re-tessellated every visible entity on
any geometry bump, so a single-entity edit cost a full model re-tess
(~30ms baseline after the block_epoch fix).
Add a per-entity memo (tess_memo: Handle -> Arc<Vec<WireModel>>) on the
culled Model render path. It's guarded by a hash of the tessellation
parameters (tol / view / anno / world_offset / bg / entered viewport); a
mismatch (zoom, layout switch) clears it. The render path now classifies
the visible set against the memo, materializes hits and tessellates only
the misses in parallel, then stores the misses back.
- bump_geometry clears the memo (structural change — safe default);
- incremental edits drop just the changed handle via mark_entity_dirty
and bump with bump_geometry_no_blocks: line commit (new handle is a
natural miss) and grip commit / Esc-cancel (mark the dragged handle).
The hit-test (view_aabb == None), paper and per-viewport paths bypass the
memo so their differing cull parameters don't thrash it. A single-entity
edit now skips the per-entity geometry math for the entire unchanged set;
the Vec assembly + batched-buffer upload stay O(N).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The block-definition tessellation cache (BlockCache::build — every block
defn) was keyed on geometry_epoch, so every edit rebuilt all block defns.
On a block-heavy drawing that turned the ~30ms visible-wire re-tess into a
~400ms spike at line-commit, grip first-move and grip release.
Key the block cache on a separate block_epoch. bump_geometry bumps both
geometry_epoch and block_epoch (safe default — xref, block create,
explode, REFCLOSE, undo, open all keep rebuilding blocks). Operations that
provably can't change a block definition now call bump_geometry_no_blocks,
which bumps only geometry_epoch so the visible wires re-tessellate against
the reused block cache:
- add_entity for a non-Insert/Block entity (line, arc, text, hatch, …) —
scene.add_entity only ever routes to model/paper space, never block
content;
- grip drag hide / commit / Esc-cancel — moving a top-level entity or an
insert never edits a block defn.
Line-commit and grip start/end drop from ~400ms to the ~30ms baseline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2.2 (partial). Dragging an entity's grip called scene.apply_grip on
every mouse move, which bumped geometry and re-tessellated the whole model
per move (plus an O(N) clone of every wire to exclude the edited one from
snapping). On a large drawing that's the 30ms->400ms spike, per frame, for
the duration of the drag.
apply_grip no longer bumps. The drag flow now:
- first move: hide the edited entity from the base tessellation (one
re-tess) and record it in grip_preview_handle;
- each move: apply the grip to the doc (no bump) and show the entity as a
one-entity overlay preview (set_preview_wires) — the base is a cache hit;
- commit on release / Esc: un-hide, drop the preview, one final re-tess.
Because the edited entity is hidden, it's already absent from
hit_test_wires, so snapping runs against the cached set directly — no
per-move clone and no self-snap. Two re-tessellations per drag instead of
one per move.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 2.2 (partial). set_preview_wires / clear_preview_wire /
set_interim_wire called bump_geometry, so every rubber-band frame of a
drawing command invalidated the wire cache and re-tessellated the whole
model — the same 30ms->400ms spike as selection, but while drawing.
Preview / interim wires are an overlay appended to the cached base wire
set in build_primitive; they are not part of the tessellation cache, so
they no longer bump geometry. The base model-tile tessellation stays a
cache hit (static camera, no geometry bump), and the overlay still forces
a GPU wire re-upload through the has_overlay content-id path. Real commits
(commit_entity / transform / copy) bump geometry on their own, so finished
edits and preview-clear still render correctly. Snap/hit-test via
entity_wires_arc also stops re-tessellating per preview frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Picking an entity called bump_geometry, which invalidated the wire cache
and re-tessellated the entire model just to repaint one entity — 30ms to
400ms on a large drawing (visible as a tess-ms spike on the PERF HUD).
Selection highlight is no longer baked into tessellation. wires_for_block_
culled now feeds tessellate_entity an empty selection set, so wires are
always base-coloured and the wire cache is selection-independent. The
highlight is applied in the GPU xray overlay: prepare filters the cached
wires by the live `selected ∪ hover` handle set, recolours them to
WireModel::SELECTED, and batches them — rebuilt only when a new
selection_generation (or the underlying wire content) changes.
Pure select / deselect / hover paths now call bump_selection instead of
bump_geometry, so a pick refreshes just the overlay — no re-tessellation,
no main wire re-upload (geometry_epoch and wire_content_id are unchanged).
Geometry edits, undo and file-open still bump_geometry and rebuild
everything. Grips/properties refresh through their existing app paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build_primitive ran split_face3d_wires every frame — for each wire a
string-parse + document handle lookup + clone, to separate Face3D wires
from the rest — even when a pan had just reused the tessellation. The
split is content-only, so memoize it by the Model-tile wire content id:
a pan-reuse (same id) reuses the split, and the common no-overlay frame
returns the cached `other` Arc with zero clone. Preview/interim frames
clone once to append. Non-tile paths split inline as before.
Completes the cheap-pan path: a pure pan now reuses the CPU
tessellation, the Face3D split, and the GPU wire upload — only the
view_proj uniform and per-frame scissors recompute.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3.2 (wire pan path). Wire vertex buffers are world-space, so a
camera move changes only the view_proj uniform (uploaded every frame),
never the wire data — yet upload_wires re-ran on every camera_generation
tick, re-packing vertices and re-writing the GPU buffer on each pan.
Stamp every Model-tile re-tessellation with a monotonic content id
(WIRE_CONTENT_GEN), reused when a pan reuses the tessellation. The
pipeline records the resident buffer's id and skips upload_wires when the
incoming id matches. The gate is pulled out of the (epoch, camera_gen)
block so a preview/interim wire change still uploads even with a static
camera; non-tile paths and overlay frames force a fresh id, preserving
current behaviour. Monotonic id sidesteps the ABA hazard a freed/realloc'd
Arc pointer would carry.
Pairs with the pan tessellation reuse: a pan now skips both the CPU
re-tessellation and the GPU wire upload.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3.1 (partial — pan). The Model-tile wire cache keyed on the exact
camera hash, so every pan re-tessellated the whole tile even though pan
changes neither the curve tol nor the culled set (beyond a shift). Re-key
on (geometry_epoch, pan_invariant_hash, tessellated_region):
- pan_invariant_hash covers rotation + wpp (tol) but excludes the pan
target, so a pure pan keeps it stable;
- the wires were culled to a 1.25x-margin region, recorded with the entry;
- a hit now also requires the tile's current visible rect to still fit
inside that region, guaranteeing every on-screen wire is present.
Result: panning within the existing margin reuses the tessellation (tess
ms -> ~0 on the PERF HUD) with no missing geometry. Zoom, orbit and edits
change the signature/epoch and rebuild as before; the cull margin is
unchanged so miss cost — and zoom behaviour — is identical. GPU still
re-uploads per camera_generation (diff upload is Phase 3.2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5.3 (CPU tessellation slice). Add a `PERF` command that toggles an
overlay on the active viewport showing the cost of the most recent wire
re-tessellation: ms, wire count, geometry epoch. Anchored top-left via
position_canvas_overlay.
Timing is captured at the wire-cache miss paths (model_tile_wires_arc,
paper_sheet_wires_arc) and stored in Cells on Scene, read directly by the
UI view (Scene is single-threaded / Rc-based, so build_primitive and view
share a thread). Reads ~0 ms on a warm cache, so it isolates the exact
work a miss costs — the slice 2.2 / 3.1 / 3.3 each move, making them
PR-to-PR comparable.
GPU upload/draw/GPU-wait spans (need wgpu timestamp queries) remain open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3.1 (partial). set_hover_highlight bumped the geometry epoch on
every hover-target change, forcing a full model re-tessellation. The
hover handle is folded into the tessellation highlight set as
selected ∪ {hover}, so hovering an already-selected entity contributes
nothing — the effective set and every resulting WireModel are identical.
Guard the bump on the effective highlight contribution actually changing;
hovering over or between selected entities is now free. The field is
still updated so hit-test / UI state stays current.
The full camera/selection-from-tessellation cache split (3.1 / 2.2) is
left open: highlight colour is baked into WireModel.color across several
tessellation sites, so decoupling it needs running-app verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5.2. Time each open phase — parse, purge, derived-cache build on
the loader thread, plus xref resolve on the UI thread — and emit a
breakdown line on open completion:
parse 1240ms · purge 80ms · caches 340ms · xref 60ms · total 1820ms
Makes open-time regressions (and the wins from 1.1 / 2.4) visible at a
glance. Timings ride back on DerivedCaches; total is wall time from the
Open click to post-xref.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 4.1. Handle / u64 keys dominate the hot maps (block_defn,
hatch / image / mesh caches, draw-depth map, viewport wire caches);
the default SipHash is wasted work on integer keys. Alias the std
types to rustc_hash::FxHashMap / FxHashSet crate-wide so every map and
set — including the cross-module draw-depth map shared between scene
and the GPU pipelines — uses the faster hasher consistently.
Mechanical: no behavioural change. Constructors moved from ::new() to
::default() (Fx variants carry no RandomState). Build + 13 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 1.1: resolve_xrefs now purges corrupt entities inline as it
merges each xref and returns the dropped count, removing the second
full-document purge_corrupt_entities walk in the FileOpened handler.
Every xref-bearing open saves one O(N) entities() pass.
Phase 2.4: split compute_world_offset into offset_prep / offset_centroid
/ world_offset_from_centers; centroid accumulation now rides the existing
cache-handle walk in build_derived_caches. Two O(N) entity walks collapse
into one, with identical MSPACE-membership filtering preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a scene hover_highlight handle that renders an entity with the
selection colour without joining the real selection. The cycling list
box sets it on row enter / clears on exit, so hovering a row glows the
matching object in the drawing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a per-type selection filter to the scene: the FILTER pill opens a
checklist of the entity types in the layout, and unchecking a type
excludes it from interactive picking (click, box and lasso selection all
honour it). The pill lights up while any type is filtered out.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a transparency_enable uniform read by the wire shader: when the new
TPY pill is off, every line is forced opaque; when on, baked entity
transparency shows through. Toggling is a uniform write, no retessellate
(same mechanism as the lineweight-display toggle). Pill is toggleable
from the status-bar customization menu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a hidden-entity set to the scene that the tessellation visibility
test skips, so hidden objects neither render nor hit-test. New commands
ISOLATEOBJECTS (show only the selection), HIDEOBJECTS (hide the
selection) and UNISOLATEOBJECTS (restore). An ISO status pill lights up
while anything is hidden and restores everything on click.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read the model-space annotation scale from the standard CANNOSCALE /
CANNOSCALEVALUE header variables instead of a user-variable hack, and
populate both the status-bar scale picker and the viewport Scale
property purely from the drawing's ACAD_SCALELIST — no built-in
fallback set. Xref-derived scales (`_XREF` suffix, `name|` prefix)
are filtered out so only the host drawing's own scales appear.
Bumps the patched acadrust to the commit that adds CANNOSCALE/
CANNOSCALEVALUE read/write.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tables without a baked block (e.g. created in-app) were drawn as a single
monochrome wire, ignoring cell colours/fills/border styling. Add a
coloured synthesis path that emits per-colour WireModels for:
- cell background fill (fill_color + fill_enabled),
- per-cell text in its content/text colour,
- grid borders with per-edge type/weight/colour/visibility (cell-style
override → row-style → default),
- horizontal/vertical margins and flow direction (down/up).
Imported tables keep using AutoCAD's baked block. Resolution layers the
per-cell CellStyle over the TableStyle row style over defaults.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each model-space tile now carries its own visual style instead of one
global mode shared by every pane:
- ModelTile gains a `render_mode`; the active tile renders with the live
picker mode, inactive tiles keep their stored style, so changing one
tile no longer changes the others. Split inherits the source style.
- SetRenderMode writes only the active tile; switching the active tile
mirrors its style back into the tab (picker + live render).
- Persisted through the *Active VPORT entries (acadrust now round-trips
the render mode via DXF code 281 / DWG RC 281), restored on open for
both tiled and single-tile model layouts.
- Bump acadrust to pick up the VPORT render-mode field.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two paper-space rendering fixes:
- The full-canvas sheet instance no longer draws model-block hatches/
wipeouts/images. Those belong inside the floating content viewports;
rendering them on the sheet let them bleed past the viewport borders
when model coords overlapped the paper area. The sheet now draws only
the paper block's own fills plus the synthetic white printable area.
- Pan/zoom/orbit inside a double-clicked floating viewport (MSPACE) now
re-culls and re-uploads the viewport's wire set. The per-viewport wire
cache key now hashes the viewport's own view (pan + zoom + orbit), and
the active-viewport pan/zoom/orbit handlers bump camera_generation so
the GPU re-uploads. Previously the stale frustum-culled subset stayed
on the GPU, so newly-revealed lines were invisible (hatches, uploaded
as the full set, still showed) until MSPACE was exited.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The overall "sheet" viewport now IS the paper view, so its own border
rectangle must not be drawn as an entity — filter it out of
paper_sheet_wires_arc (content viewport borders are kept).
Also update the doc comments that still referenced the removed
PaperCanvas widget / paper_canvas_wires to describe the GPU sheet path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Paper space now renders entirely through the unified GPU shader, exactly
like model space — there is no separate 2-D canvas. The full-canvas
top-locked "sheet" viewport draws the layout's entities, hatches,
wipeouts and images; the desk is the container background and the white
printable area is a synthetic solid hatch.
- The white sheet fill uses draw_depth -2.0 (entity fills/wires are in
the signed (-1, 1) range) so it is always strictly behind every object,
with a negligible z offset (BIAS 0.001) — no far-plane clipping.
- Delete src/scene/paper_canvas.rs (the PaperCanvas widget) and its dead
helpers (paper_canvas_wires / paper_sheet_wires / paper_canvas_cache).
paper_canvas_hatches / _wipeouts remain — still used by paper hit-test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>