Commit Graph
112 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 5 35a2714025 fix(camera): keep the eye's coordinates out of the view basis
A drawing placed at UTM shimmered as the camera moved.

The relative-to-eye path exists so the large eye translation never enters an
f32 matrix, and it zeroed the translation column to make sure. But the basis
was built first, by `look_at(eye, target)` with both positions cast to f32 —
and at (639792, 4517057) that grid is half a metre across in y. Rounding both
ends and then subtracting them left a direction that changed as the camera
moved, so the whole view basis jittered. Zeroing the translation afterwards
came too late to help: the damage was in the rotation.

It never needed the positions. The eye is `target + rotation·Z·distance`, so
the direction between them is the rotation's own Z — a unit vector carrying no
coordinate at all.

Two tests. Where the camera stands must not reach the matrix, which is the
property being restored; and at the origin, where the old construction had no
large coordinates to lose, the two must agree exactly — which is what says
this kept the same basis rather than turning the view round.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-10 02:36:28 +03:00
Hakan SevenandClaude Opus 5 62dfbfe2be fix(paper): give the sheet back its desk and its edge
A paper layout painted its whole canvas in the page colour, so the sheet
had no visible edge — zooming out never reached one, because the surround
was the same colour as the page. Floating viewports then painted
themselves black over it.

Both come from the same idea, that a viewport's canvas is a colour it can
be handed. What lies behind a viewport depends on what the viewport is:

- The full-canvas sheet shows the desk. `paper_bg_color` is the page, and
  the page is a bounded rectangle `paper_sheet_fill` already draws; using
  it for the canvas too erased the boundary between them. The desk is now
  its own colour, defined once in the scene and read by both the widget
  and the renderer, so the two cannot drift apart.
- A floating viewport sits on that sheet and must paint no canvas at all.
  Handing it a transparent one does not work: the background shader
  returns an opaque colour whatever alpha it is given, so alpha zero came
  out black. Skipping the pass is the only way to ask for see-through, so
  `skip_background` now suppresses both the clear and the pass. It is part
  of the frame signature too — without that, a cached frame would keep
  showing the canvas the flag was just turned off for.

Shading and fog still blend toward what is behind a floating viewport,
which is the page, so the uniform keeps that colour even though nothing
paints it.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-08 00:11:47 +03:00
Hakan Seven c45daeffb6 feat(render): add scene environment pipeline 2026-08-05 23:42:18 +03:00
Hakan Seven e07f4f959b fix(view): preserve projection framing
Use full 3D bounds to derive a safe perspective eye distance without changing the camera target, orientation, or field of view.

Refs #502
2026-08-05 12:47:46 +03:00
Hakan Seven 657d023fb7 feat(ucs): integrate coordinate systems
Route coordinate input, geometry creation, transforms, properties,\nviews, and viewport persistence through a shared working plane.\n\nRefs #484
2026-08-04 23:17:54 +03:00
Hakan Seven b1d55c6a7e feat(view): add visual style previews
- preview render modes on hover without committing
- distinguish 2D and 3D wireframe fill and depth behavior

Refs #483
2026-08-04 21:23:25 +03:00
Hakan Seven 282a5176af fix(groups): highlight full group on hover
Refs #454
2026-08-04 16:40:22 +03:00
Hakan Seven 539d83de47 feat(grips): improve direct editing
Refit arcs from fixed control points and highlight hovered grips.
Let Lengthen preview from the cursor or accept a typed distance.
2026-08-04 16:27:03 +03:00
Hakan Seven 5662e07303 feat(area): support multi-object selection 2026-08-04 00:01:12 +03:00
Hakan Seven 53cf31f33d feat(web): unify fonts and refine viewcube
Preload one language-aware font resource across the interface, drawing text, and navigation labels.\n\nRender the navigation cube, ring, outlines, and labels through a 4x MSAA target.
2026-08-03 15:28:51 +03:00
Hakan Seven baf9b240a4 feat(annotation): show all scales on highlight 2026-08-02 14:37:12 +03:00
Hakan Seven e03e091b3c feat(annotation): integrate object contexts 2026-08-02 12:00:15 +03:00
Hakan Seven f39481ac49 fix(scene): isolate layout render sources 2026-08-02 03:02:14 +03:00
Hakan Seven 2c64888048 fix(blocks): preserve UCS transformations
Bake BEDIT frames into block-local geometry while keeping model and viewport UCS isolated. Normalize legacy nonzero block origins during load.
2026-07-30 19:50:33 +03:00
Hakan Seven 8d5ccdbd25 fix(viewports): share command previews
Closes #548
2026-07-29 23:26:06 +03:00
Hakan Seven 572a662c71 fix(render): show hidden-line silhouettes
Closes #551
2026-07-29 23:14:30 +03:00
Hakan Seven 3897fb2661 fix(viewports): fit extents per pane
Closes #554
2026-07-29 23:03:06 +03:00
Hakan Seven 6ecd67ff6e feat(layer-state): add saved-state editor 2026-07-29 18:39:55 +03:00
Hakan Seven 93b9a840ff refactor(renderer): select paths by GPU limits
Keep storage fast paths on capable adapters and use packed or texture fallbacks only when device limits require them.\n\nCompact mesh-edge varyings to avoid WebGL shader rejection.
2026-07-29 12:49:41 +03:00
Hakan Seven 25166b9508 fix(hatch): preview pattern grip drag 2026-07-28 20:07:10 +03:00
Hakan Seven 00cc17ea55 fix(scene): isolate drawing-space state
Cancel stale interaction state across space changes and keep live previews and zoom extents in their active paper/model context.\n\nRefs #539\nRefs #540\nRefs #541
2026-07-28 18:51:20 +03:00
Hakan Seven 05b044f367 perf(scene): make geometry edits incremental
Avoid full document, cache, and GPU rebuilds for entity deltas so add, delete, history, selection, and live polyline edits stay responsive on large drawings.
2026-07-28 17:16:27 +03:00
Hakan Seven 2ac61bf99a fix(ui): theme remaining interface text 2026-07-28 12:29:31 +03:00
Hakan Seven 4887a53d66 perf(render): batch and cull mesh instances
Share repeated block geometry across GPU instances.\n\nPatch changed mesh chunks and cull large batches with indirect draws.
2026-07-27 01:11:21 +03:00
Hakan Seven 6fb68d1c7c feat(scene): integrate DWG object data 2026-07-26 23:38:38 +03:00
Hakan Seven eb23715605 feat(scene): integrate standard DWG data 2026-07-26 22:30:14 +03:00
Hakan Seven bca65600cd fix: stabilize web loading and paper rendering 2026-07-25 21:34:29 +03:00
Hakan Seven fe49cc8eb5 revert(web): drop vendored iced renderer
Keep iced_wgpu external and leave the WebGPU storage-buffer optimization out of scope.
2026-07-25 19:24:32 +03:00
Hakan Seven deec48ad99 perf: keep heavy work off UI thread 2026-07-25 18:49:18 +03:00
Hakan Seven 12bf126646 perf: remove large drawing stalls
Keep resident geometry and GPU resources stable across edits, MSPACE zoom, and Model/Paper switches. Add unified PERF output for tracing remaining costs.
2026-07-25 13:34:48 +03:00
Hakan Seven 39449ec684 perf: speed large drawing workflows 2026-07-25 09:05:52 +03:00
Hakan Seven 71e0b88839 perf: optimize large drawing workflows 2026-07-25 02:58:46 +03:00
Hakan Seven da34c6817a perf: eliminate dense edit stalls
Keep resident wires, draw depths, GPU slabs, and edit history incremental across entity changes. Defer rollover work to avoid cursor jumps on dense drawings.
2026-07-25 01:06:00 +03:00
Hakan Seven ea93e169ea perf: make edit history incremental
Replace full-document snapshots with Arc first-touch deltas and batch cache invalidation. Reuse interaction and GPU category caches across unrelated edits.
2026-07-24 23:25:33 +03:00
Hakan Seven 7a1c0ead0d fix(xref): preserve layer-zero colors
Treat namespaced XREF layer |0 as layer zero during block inheritance so ByLayer geometry keeps its source color.\n\nCloses #437
2026-07-24 18:43:04 +03:00
Hakan SevenandClaude Opus 4.8 2ff37b088b fix(camera): re-derive ortho near/far per frame so orbit never clips
The orthographic depth half-range was frozen at fit time and measured along
the fit-time eye direction. A 3-D drawing fitted straight-down (top view) got
a shallow depth span; orbiting only rotated the camera, never resized the
span, so the drawing's width loaded onto the eye axis and overran near/far —
clipping the model the moment it was tilted off top (#473).

Cache the model AABB on the camera and recompute the near/far depth from it
for the live eye direction every frame. This tracks orbit, viewcube snaps,
roll and restored views uniformly, and keeps the same tight, zoom-independent
depth precision (coincident solids / meshes / wires keep their draw order).
Falls back to the scalar half-range / distance-scaled span when no AABB is set.

Closes #473

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 14:57:16 +03:00
Hakan Seven 1803f10264 Merge PR #442: cut mesh-import wire-set memory ~35% (#358)
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).
2026-07-23 10:25:27 +03:00
Hakan SevenandClaude Fable 5 94609fc54a fix(text): bake draw-order depth into glyphs so fills can't cover text
Fill triangles (DIMTFILL backgrounds, MLEADER masks, hatches) carry the
entity's draw-order rank, but SDF glyph quads were pushed at neutral 0.0
— any entity in the front half of the draw order had its own background
fill painted over its text. gather_text_verts now adds the host wire's
wire_draw_depth to each glyph: a text background lands at the same
biased depth (glyphs draw after fills and win the LessEqual test), and
text respects draw order against other entities' fills too.

The #403 glyph-reuse fast path gains a rank-stability gate: adding or
removing an entity shifts every sibling's bias (the rank denominator
changes), so reuse is only safe across all-Modified edits.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 01:13:33 +03:00
Kevin GriffinandClaude Fable 5 e9198a47e5 perf(render): don't duplicate the wire set when no Face3D wire exists
The per-epoch Face3D/other split cloned every wire into two owned lists
even when the face3d list came out empty — for pure 2-D drawings and
mesh imports (#358) the 'other' list was a wire-for-wire copy of the
entire base set, held resident in split_cache alongside the original.

Cache None as an 'everything is other' marker and hand the render the
base set itself for that frame. The base Arc is deliberately not stored
in the cache: try_resident_patch can only apply an incremental patch
while the resident Arc is uniquely held, so pinning it there would
silently degrade every edit to a full rebuild.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 13:03:01 -07:00
Hakan SevenandClaude Fable 5 ea860cab72 fix(view): ZOOM Extents fills the window at the real aspect (#364)
fit_to_bounds fitted the bounding box's circumscribed DIAGONAL into the
viewport height regardless of shape, so a wide drawing filled only a
small central patch. Fit against the actual viewport aspect instead —
required half-height = max(|y|, |x|/aspect) over the view-space corners
— and trim the border margin from 20% to 10%. ZOOM EXTENTS/ALL and
ZOOM WINDOW both pass the live pane aspect.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:09:37 +03:00
Hakan SevenandClaude Fable 5 c0499a1d3c fix(render): SDF text reuse is per wire source (#403)
Paper layouts gather glyphs for the sheet and each content viewport in
one frame; the single-slot last_sdf_text handed the first source's
glyphs to the rest, hiding every model-space text entity inside
viewports (regression in aea1fd2). Key the reuse by render source, and
invalidate on Tolerance/ATTDEF edits and complex-linetype hosts too.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 19:26:30 +03:00
Hakan SevenandClaude Fable 5 87049967a2 fix(render): close remaining OCS/WCS gaps for mirrored entities
Follow-up to the LINE/POINT WCS fix - the same bug class elsewhere:

- 2D SOLID rendered its OCS corners raw in both the wire path and the
  shared fill builder (solid_hatch_model); SHAPE placed its marker at
  the raw OCS insertion. Both now map through the arbitrary axis.
- Grips: central wrap in view/dispatch.rs shows planar entities' grips
  at their world position (OCS→WCS) and converts drags back (WCS→OCS
  via the new wcs_point_to_ocs) - per-entity grip code stays in raw
  field space, INSERT keeps handling its own OCS.
- Pulls acadrust 81ebfee: OCS-aware bounding boxes, translate,
  transform and mirror for the ten planar entity types, so MOVE /
  ROTATE / MIRROR and culling work on mirrored entities too.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 17:58:09 +03:00
Hakan SevenandClaude Fable 5 e177f92ff5 fix(xref): blocks respect their internal draw order
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]>
2026-07-21 15:53:28 +03:00
Hakan SevenandClaude Opus 4.8 b884e33f38 fix(viewport): apply per-viewport layer freeze to fills, images and meshes
A content viewport that froze layers still drew those layers' hatches,
2-D solids, images, OLE frames and 3-D solids — only its wires honoured
`Viewport.frozen_layers`. A viewport meant to be empty (all content
layers frozen) leaked the whole model instead.

viewport_data_for fed content viewports the full model hatch/wipeout/
image/mesh sets with no per-viewport frozen filter. Thread the viewport's
frozen-layer set through the builders (synced_hatch_models,
exploded_insert_hatch_models, wipeout_models, image_models, mesh_models,
instanced_block_meshes) via Scene::layer_frozen_in — the same
layer-name→handle→membership test the wire path uses — and expose
*_for_viewport accessors that cache the filtered set per order-independent
frozen-set signature (viewports sharing a frozen set share the build; an
empty set returns the shared unfiltered arc, so model tiles and
non-freezing viewports are byte-identical).

Each viewport renders in its own pipeline slot with the hatch upload
gated on geometry_epoch, so a per-viewport filtered set uploads once per
epoch rather than every frame.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-20 19:01:30 +03:00
Hakan SevenandClaude Opus 4.8 626e8091f7 refactor(viewport): unify content-viewport clipping on the stencil path
Rectangular paper viewports now go through the same GPU stencil clip as
non-rectangular ones: viewport_clip_boundary_ndc returns the four rect
corners (a full-target mask) instead of nothing, so the render path has
no rect/non-rect special case — every content viewport carries a clip
boundary and draws with stencil reference 0xFF.

With a single clip path, the old per-wire / -image / -wipeout scissor
mechanism is fully dead and removed: compute_*_scissors, the
*_pixel_scissors buffers, the project_scissor helper, the per-pass
set_scissor_rect blocks, the vp_scissor field threaded through
WireModel / ImageModel / HatchModel and their GPU mirrors (WireGpu /
ImageGpu / WipeoutGpu / HatchWebGpu), from_run's scissor parameter, the
scissor grouping in build_wire_buffers, and the is_arena_eligible gate.
Net -245 lines across 54 files.

Rectangular viewports still clip exactly via their own render rectangle;
the stencil mask is a redundant full-target no-op there, kept only so
the clip code path stays uniform. XCLIP keeps its CPU wire clip.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-20 11:06:28 +03:00
Hakan SevenandClaude Opus 4.8 c97c2a8250 fix(viewport): clip non-rectangular paper viewports to their boundary shape
Paper content viewports with a circle / spline / ellipse clip boundary
drew their model content to the full bounding rectangle instead of the
shape. Each paper content viewport is its own render into its own screen
rectangle, so clipping is done per viewport: the boundary polygon is
projected into that viewport's render-target NDC (paper shape mapped
through the same visible-sub-rect crop as the content, so it stays fixed
to the viewport frame) and stamped into the stencil buffer with an
even-odd Invert, which marks the interior of any shape, convex or not,
without triangulation. Every content pass then draws only where the
stencil is set, so hatch, wire, mesh, 3DFACE, text, image, wipeout and
selection are all clipped uniformly.

Because Invert turns the cleared 0x00 into 0xFF, clipped content draws
with stencil reference 0xFF (unclipped content keeps reference 0). The
shared depth buffer moves to Depth24PlusStencil8 across every pipeline.

Rectangular viewports keep clipping via their own render rectangle and
XCLIP keeps its existing CPU wire clip; neither needs the stencil path.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-20 10:02:02 +03:00
Hakan SevenandClaude Opus 4.8 8c72a06790 feat(solid3d): ISOLINES and silhouettes on sphere and torus faces
Extends the curved-face wireframe treatment from cones to spheres and tori, so
a solid built from those faces reads the way a cylinder already did.

ISOLINES: sphere faces get meridian lines across the face's colatitude span at
evenly spaced longitudes; torus faces get minor cross-section circles at evenly
spaced revolution angles. Both clip to the face's own parametric window (from
the boundary polygon via a shared `angular_span` gap-finder), so a partial
sphere or torus arc doesn't draw lines out over the missing part.

Silhouettes (DISPSILH): `CurvedGen` becomes an enum. A sphere silhouettes as
the great circle perpendicular to the view, clipped to the face window; a torus
as the two curves where the tube turns edge-on, traced around the revolution.

The silhouette now uses the camera's view *direction* alone, not the eye
position — a single parallel direction for the whole scene — so the outline
follows the view angle, holds still under pan, and doesn't foreshorten, which
is the orthographic silhouette a CAD wireframe expects.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-17 23:30:05 +03:00
Hakan SevenandClaude Opus 4.8 ae5231427b feat(solid3d): view-dependent silhouettes (DISPSILH) on curved faces
ISOLINES show a curved solid from any angle, but its outline still cut across
whichever fixed longitudinal lines happened to face the eye. DISPSILH draws the
true silhouette — the two lines where a cylinder (or cone) turns edge-on to the
view — and it moves with the camera, so it cannot be a baked edge.

Tessellation stores a world-space generator per cone/cylinder face on the mesh
set (axis, radial frame, radius, cone taper, extent), sharing the face-extent
extraction with the ISOLINES collector. Each frame `upload_silhouettes` walks
those generators, solves `du·cosθ + dv·sinθ = -tanα·da` for the two edge-on
angles (`φ ± acos(...)`, `φ ± π/2` for a cylinder), and builds a line list in
the mesh vertex format — double-single, so it stays precise at UTM. It draws
through the existing wireframe / edge pipelines in the wireframe and
shaded-with-edges passes.

Gated on the document's DISPSILH header flag, which defaults off — so the
per-frame pass is a no-op unless the drawing asks for it. Scoped to top-level
solids: a block instance's generators aren't transformed by the INSTANCE pose
yet, so they are cleared on the block path (its isolines and feature edges
still draw).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-17 20:02:56 +03:00
Hakan SevenandClaude Opus 4.8 3e34da4ac6 chore(glam): use camera-module projection API for deprecated Mat4 rh ctors
glam 0.33.2 deprecated Mat4::{orthographic,perspective,look_at}_rh in
favour of the glam::camera::rh::* functions. Switch the 7 call sites in
camera.rs and viewcube.rs to the equivalent camera-module functions
(directx orthographic/perspective = same [0,1] depth range; look_at_mat4
= same view matrix), silencing the deprecation warnings. No behaviour
change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-17 01:49:26 +03:00
Hakan SevenandClaude Opus 4.8 1b60924bbe fix(view): show geometry far off the drawing plane
A benchmark drawing carrying a circle and an ellipse 800 km below its own
plane showed neither, and ZOOM Extents zoomed out to 800 km to find them.
Both come from sizing the view off the wrong axis.

Zoom and clipping answer to different extents and are now measured that
way: the zoom frames what the view actually shows, the extent across the
screen plane, while near/far span what lies along the eye direction.
Sizing both from the 3-D diagonal let one far-off Z drag the horizontal
zoom out with it. The two agree on a flat drawing, which is why it went
unnoticed.

A camera restored from a file's saved view keeps its pose, but nothing
had sized its near/far, so the projection fell back to a guess scaled
from the zoom distance — far too narrow when geometry reaches off the
plane, and the geometry was silently clipped away. It now takes the
drawing's own recorded extent.

$EXTMIN/$EXTMAX is untrustworthy for positioning — stale after an edit,
or the sentinel when the writer never computed it — which is why fit_all
scans entities instead. It is the right input here: too wide only costs a
little depth precision, too narrow deletes geometry, so the drawing's own
claim errs in the safe direction. It also costs nothing on open.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-15 22:21:38 +03:00