Commit Graph
301 Commits
Author SHA1 Message Date
Hakan SevenandClaude Sonnet 4.6 93614ab554 fix: apply global LTSCALE to linetype pattern rendering
Entity linetype_scale is a per-entity multiplier applied on top of the
document-level $LTSCALE header variable. The previous code used only the
entity scale (defaulting to 1.0), ignoring $LTSCALE entirely. Drawings
with a non-default LTSCALE showed dash patterns at the wrong size.

Effective scale is now: document.header.linetype_scale * entity linetype_scale.
Applied in both render::render_style_for and the complex-linetype path in
tessellate_entity.

Fixes part of #10

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-01 21:13:56 +03:00
Hakan SevenandClaude Sonnet 4.6 ebef467720 fix: hide sheet viewport border and improve crossing selection
Two layout-related fixes:

1. Sheet viewport (id=1) was rendered as a grey rectangle border in
   paper-space layouts. It is the paper boundary, not a user viewport,
   and should not appear. Now filtered via is_content_viewport() before
   tessellation. The redundant id==1 color branch is removed.

2. Box crossing-selection (right-to-left drag) only detected wires whose
   vertex points fell inside the selection box. Large entities like viewport
   frames could be visually crossed without any corner inside the box and
   would not be selected. Added segment-box edge intersection tests so any
   wire segment that crosses the selection rectangle boundary is included.

Fixes part of #10

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-01 21:12:14 +03:00
Hakan SevenandClaude Sonnet 4.6 01943c5b8d fix: correct paper-space grip projection and MTEXT attachment point rendering
- Grip points in paper space now use the 2-D linear to_px transform
  (tx/ty/ortho_size) instead of the 3-D view_proj matrix, matching the
  coordinate system used by PaperCanvas
- world_offset is no longer subtracted from paper-space entity grip
  positions — paper-space coordinates are already unoffset (same rule
  as wire tessellation in wires_for_block)
- MTEXT v_offset corrected for all attachment points: CXF glyphs sit
  on the baseline and extend upward, so Top attachment needs v_offset=-h,
  Bottom needs (n_lines-1)*line_h, Middle is the midpoint of the two

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-01 12:16:46 +03:00
Hakan SevenandClaude Sonnet 4.6 3298ebff91 fix: use PlotSettings paper dimensions for correct paper-space rendering
paper_limits() now reads paper_width/paper_height/plot_rotation from the
Layout object (populated by a patched acadrust from the embedded DWG
PlotSettings). This fixes layouts where min/max_limits do not match the
physical paper size (e.g. A4 portrait content inside A3 landscape limits).

DXF files use codes 44/45/73 from raw_plot_settings_codes; DWG files use
the PlotSettings block embedded in the LAYOUT object. Both fall back to
min/max_limits when paper_width is zero.

Depends on HakanSeven12/acadrust feat/layout-paper-dimensions (PR #21 on
hakanaktt/acadrust); using that fork until the PR is merged.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-01 08:04:35 +03:00
Hakan SevenandClaude Sonnet 4.6 cbb37a0909 fix: persist camera in View table and fix pitch clamp for exact top/bottom views
- Save camera to named View table entries (H7CAD_Camera_Model / H7CAD_Camera_<layout>)
  on every navigation change; restore from these entries on file open and tab switch.
  acadrust's DWG writer overrides *Active VPort view_height/view_center with
  zoom-extents values, but leaves the View table untouched, so this survives save.
- Fix pitch recovery: clamp vd.z to [-1, 1] instead of [-0.999, 0.999] so that
  asin(1.0) = π/2 exactly for plan/top views, preventing the subtle tilt on reload.
- Fix IEEE 754 edge case: atan2(+0, -0) = π for plan view direction (0,0,1);
  special-case yaw = 0 when both vd.x and vd.y are near zero.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-01 07:43:45 +03:00
Hakan SevenandClaude Sonnet 4.6 35e487934c fix: correct yaw recovery from view_direction vector
view_dir = rotation * Z = (sin(yaw)*cos(pitch), -cos(yaw)*cos(pitch), sin(pitch))
so yaw = atan2(x, -y), not atan2(x, y).
The sign error caused a 180-degree Z rotation on every save/reload cycle.
Applies to apply_active_vport_camera, apply_sheet_viewport_camera, and
camera_for_viewport.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-01 07:20:55 +03:00
Hakan SevenandClaude Sonnet 4.6 8f90a68930 feat: restore and persist saved camera position per layout
- On file open and layout tab switch, restore the camera from the
  document's saved view (*Active VPort for model space, sheet viewport
  for paper space) instead of fitting to wire bounds.
- After any zoom/pan/rotate, write the new camera position back to the
  document and mark the file dirty so it is saved with the file.
- Uses last_synced_camera_gen to avoid marking the file dirty on the
  initial camera restore.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-01 07:07:19 +03:00
Hakan SevenandClaude Sonnet 4.6 ed94a5b81f fix: correct viewport projection by converting view_target to wire-space
Model entity wires are tessellated with world_offset subtracted for GPU
precision, but view_target/view_center were raw model coordinates.
Subtracting world_offset from view_target aligns both coordinate systems
so model content projects correctly into viewport rectangles in paper space.

Also fixes scale priority (view_height before custom_scale) and adds
view_center DCS offset to the projection, completing the paper-space
viewport rendering.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-05-01 06:42:06 +03:00
Hakan SevenandClaude Sonnet 4.6 6a0b9169d6 fix: identify user viewports by geometry when id is 0 or negative
The acadrust DWG reader never assigns a viewport id (always 0).
Some DXF exporters use id=-1 for all viewports.  In both cases the
previous id > 1 check excluded every viewport from content rendering.

Replace is_user_viewport_id(id) with is_content_viewport(vp) that
uses a geometry heuristic for unknown ids (0 / negative):
  - id=1           → always sheet viewport (excluded)
  - id≥2           → always user viewport  (included)
  - id=0 or id<0   → sheet if center≈(0,0) AND scale≈1.0, else user

This allows DWG files and non-standard DXF files to display model
content inside their paper-space viewports and be entered via
double-click.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-30 22:06:43 +03:00
Hakan SevenandClaude Sonnet 4.6 66a73b00e1 fix: make viewports from external DXF/DWG files visible and enterable
Two root causes prevented paper-space viewports from loading correctly:

1. acadrust's ViewportStatusFlags::from_bits() maps bit 0 → is_on, but
   the real DXF/DWG spec uses bit 15 (0x8000) for "viewport on". Post-load
   fixup in io/mod.rs now corrects this for both DXF and DWG files: when
   bit 0 is clear but bit 15 is set, is_on is forced true and locked is
   derived from bit 14 (0x4000).

2. Some DXF exporters write id=-1 for all viewport entities instead of the
   standard id=1 (sheet) / id≥2 (user) convention. The is_user_viewport_id()
   helper now treats any id ≠ 0 and ≠ 1 as a user viewport, covering both
   the standard id≥2 and the non-standard id=-1 cases.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-30 21:57:02 +03:00
Hakan SevenandClaude Sonnet 4.6 00e61a1301 fix: correct entity color adaptation for viewport content in paper space
Model-space entities shown through a viewport were tessellated using
paper_bg_color (light), but the interactive viewport pane renders them
on bg_color (dark), causing white/black entities to appear incorrectly.

- model_wires_for_viewport: tessellate with bg_color so the viewport
  pane (dark background) gets correctly adapted entity colors
- viewport_content_wires: re-adapt projected wire colors using
  paper_bg_color before rendering onto the paper canvas

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-30 21:39:38 +03:00
HakanandClaude Sonnet 4.6 d06db45950 feat: adapt white/black entity colors to background luminance
White entities on light backgrounds (e.g. paper space) render as black;
black entities on dark backgrounds (e.g. model space) render as white.
All other colors pass through unchanged.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-30 10:00:46 +03:00
Hakan SevenandClaude Sonnet 4.6 7065bfa935 fix: do not apply world_offset to paper-space entities
Paper-space entities (wires, hatches, wipeouts) live in sheet
coordinates (~mm scale) and do not need the model-space centring
shift.  world_offset was incorrectly subtracted from their coordinates,
displacing all paper-space geometry away from the paper sheet.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-29 21:38:48 +03:00
Hakan SevenandClaude Sonnet 4.6 1a19a841e6 fix: correct polyline arc direction, text precision, Face3D edge color, buffer chunking
- fix: polyline arc center sign was inverted (- → +) causing arcs to render on the
  wrong side in LwPolyline and Polyline2D
- fix: store glyph strokes in local space with f64 origin so world_offset subtraction
  uses f64 precision; prevents blocky text at large UTM coordinates
- fix: Face3D fill darkened to 45% so edge wires are visually distinct from fill
- fix: wire batch vertex buffer chunked to 256 MB GPU limit (was panic on large files)
- fix: gpu_face3d_edges changed from Option<WireGpu> to Vec<WireGpu> to support chunks

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-29 20:58:44 +03:00
Hakan Seven 02203dba93 Revert "fix: skip degenerate triangle for 3-corner Face3D"
This reverts commit 517af91245.
2026-04-29 20:34:18 +03:00
Hakan SevenandClaude Sonnet 4.6 517af91245 fix: skip degenerate triangle for 3-corner Face3D
acadrust Face3D encodes triangles by setting fourth_corner == third_corner.
The fill builder was emitting a zero-area second triangle for these cases.
Now checks the p2==p3 distance and skips the second triangle.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-28 21:57:38 +03:00
Hakan SevenandClaude Sonnet 4.6 298098e764 perf: dedicated Face3D shader — N draw calls → 2
3DFACE entities previously created one WireGpu buffer per entity,
resulting in N separate draw calls per frame. For files with large
triangle meshes this was the dominant GPU CPU overhead.

New pipeline:
- face3d.wgsl: simple flat-color vertex + fragment shader (MVP only,
  no quad expansion, no pattern distance computation)
- Face3DGpu: batches ALL Face3D entities into a single TriangleList
  buffer (6 vertices per face, per-vertex color) — 1 draw call
- WireGpu::from_batch: merges N Face3D edge wire models into one
  quad-expanded buffer for edge rendering — 1 draw call
- split_face3d_wires: separates face3d from other wires at Primitive
  build time via document handle lookup (O(N), once per epoch)

Hit-testing via WireModel (CPU) is unaffected — face3d wires remain
in entity_wires_arc() and hit_test_wires() as before.

Before: N 3DFACE → N draw calls, N × 96 B/vertex WireVertex
After:  N 3DFACE → 2 draw calls (fill + edges), 28 B/vertex fill

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-28 21:53:31 +03:00
Hakan SevenandClaude Sonnet 4.6 5f69d8c3f0 fix: apply world_offset to hatch, solid, and wipeout boundary coordinates
hatch_model_from_dxf(), solid_hatch_model(), and wipeout_boundary_2d()
were converting DXF world-space coordinates directly to f32 without
subtracting world_offset. After the world_offset change moved all wire
geometry to local space, hatches/solids/wipeouts rendered at the wrong
position (displaced by the centroid offset, up to ~4M m for Turkish UTM).

All three functions now accept world_offset and apply it to every
boundary vertex before the f64→f32 cast.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 18:21:40 +03:00
Hakan SevenandClaude Sonnet 4.6 e191315713 fix: restore visibility for large-coordinate DXF files after world_offset regression
Three root causes caused all geometry and UCS arrows to disappear:

1. TruckObject::Lines and TruckObject::Text variants returned world-space
   f32 coordinates without applying world_offset, so fit_all() computed
   bounds spanning from local space to UTM world space (~4 M m), placing
   the camera impossibly far from all visible geometry.

2. Ray/XLine entities with unnormalized direction vectors (common in some
   DXF exporters) produced astronomically large far-point coordinates
   (up to 1e28) that overwhelmed the camera fit even after the offset fix.
   Fixed by normalizing the direction vector before multiplying by
   DISPLAY_EXTENT.

3. Origin-stuck corrupted entities (coordinates ≈ 0 in world space become
   ≈ -world_offset in local space) pulled the fit_all() bounds far from
   the actual drawing. Fixed by computing local_extent_max from EXTMIN/EXTMAX
   (10× safety margin) and skipping points outside that range in fit_all().

Additional hardening: wire_gpu.rs now skips ±inf segments (not just NaN),
and snap_pts/key_vertices in all TruckObject variants are offset-corrected.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 18:04:35 +03:00
Hakan SevenandClaude Sonnet 4.6 a2fb334966 fix: world_offset precision fix for large-coordinate DXF files (e.g. Turkish UTM)
Subtracts the DXF header extents centroid (world_offset) from all coordinates
in f64 BEFORE casting to f32. This eliminates the ~0.5 m ULP error that occurs
at 4,000,000 m UTM coordinates and caused geometry to disappear on zoom-out.

- Scene: adds world_offset field + compute_and_set_world_offset() method
- tessellate.rs: threads world_offset through all f64→f32 conversions
  (legacy_geometry, dimension_geometry, solid_wire_fallback, multileader)
- truck_tess.rs: to_local() helper already in place from prior session
- mod.rs: tessellate_entity() and entity_aabb() apply world_offset
- update.rs: compute_and_set_world_offset() called after doc load
- cmd_result.rs / solid3d_cmds.rs: pass correct offset to truck tessellators

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 07:41:38 +03:00
Hakan SevenandClaude Sonnet 4.6 e38638502e fix: selected wires always visible on top of occluding geometry
Changed selection overlay pipeline from depth_compare=Greater to
depth_compare=Always so selected wires are redrawn at full brightness
after all other passes, making them visible regardless of what is in
front of them.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 03:04:31 +03:00
Hakan SevenandClaude Sonnet 4.6 c27927c2b3 fix: X-ray ghost pass for selected wires occluded by other geometry
Selected wires are now visible through occluding objects. A second
wire_xray pipeline (depth_compare=Greater, depth_write_enabled=false)
renders 25%-alpha ghost copies of selected wires only where they are
behind the depth buffer, leaving fully-visible portions at full glow
brightness unchanged.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 03:01:42 +03:00
Hakan SevenandClaude Sonnet 4.6 392f4503d8 feat: complex linetype text elements and ellipse quadrant snaps
Complex linetypes now render embedded text elements (e.g. GAS_LINE's
"GAS" labels): the LtSegment::Text variant is parsed from LIN files,
scaled along with shape/dash segments, and tessellated via
cxf::tessellate_text_ex aligned to the linetype direction.

Full ellipses gain four Quadrant snap hints at ±major/±minor axis
endpoints in addition to the existing Center snap, matching AutoCAD
snap behaviour.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 02:34:51 +03:00
Hakan SevenandClaude Sonnet 4.6 7bae75f5d6 feat: dimension extension lines respect DIMEXO offset and DIMEXE overshoot
Extension lines for linear and aligned dimensions now start at
DIMEXO × DIMSCALE distance from the definition point (instead of at the
point itself) and extend DIMEXE × DIMSCALE units past the dimension
line, matching AutoCAD rendering. Both values fall back to 0 when the
dimstyle is not found.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 02:30:56 +03:00
Hakan SevenandClaude Sonnet 4.6 debc4b87a8 feat: dimension arrow size from DIMASZ×DIMSCALE in dimstyle
Arrow size for all dimension types (linear, aligned, radius, diameter,
angular) is now read from the associated DimStyle's dimasz field scaled
by dimscale, instead of being hardcoded at 0.12. Falls back to 0.12
when the style isn't found in the document's dim_styles table.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 02:28:48 +03:00
Hakan SevenandClaude Sonnet 4.6 0bb4209e2c fix: closed spline gap, hatch spline boundary, and spline commit grouping
- Spline: if flags.closed/periodic and B-spline endpoints don't meet,
  add an explicit closing Wire segment to eliminate the visual gap.
- Hatch BoundaryEdge::Spline: evaluate the B-spline at 16 uniform
  parameter samples (de Boor via truck BSplineCurve) instead of
  plotting raw control points as a polyline; falls back to control-
  point polyline for degenerate knot/degree inputs.
- Ellipse arc key_vertices and LWPolyline midpoint grips committed
  separately; this commit wraps the spline-related fixes.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 02:26:26 +03:00
Hakan SevenandClaude Sonnet 4.6 67022589df feat: render LWPolyline and Polyline2D wide strokes as solid fills
Wide polyline segments (constant_width, per-vertex start/end widths)
now appear as solid-filled quads or arc bands via HatchModel, generated
inline in synced_hatch_models(). Straight segments → trapezoid, arc
segments → arc-band polygon (inner/outer arc). MINSERT confirmed working
via acadrust explode_from_document → array_transforms().

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-27 02:20:28 +03:00
Hakan SevenandClaude Sonnet 4.6 5216c247c2 fix: Spline snap points and TextStyle backward/upside-down rendering
Spline entities now expose fit_points (or control_points when no fit
points are defined) as key_vertices, enabling Endpoint and Midpoint
snap to reach spline construction points.

TextStyle is_backward flag is applied by negating the width_factor,
producing horizontally mirrored text. is_upside_down rotates the text
180°. Both tessellate_text_ex and text_local_bounds now handle negative
width_factor without clamping it to zero.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 22:10:32 +03:00
Hakan SevenandClaude Sonnet 4.6 045ab96eab fix: resolve ByBlock color, linetype, and lineweight through INSERT chain
Block sub-entities with ByBlock color now inherit their INSERT's resolved
color instead of rendering as the default white. ByBlock linetype inherits
the INSERT's linetype pattern, and ByBlock lineweight inherits the INSERT's
effective line weight.

Implemented via render_style_for_block_sub(), called in tessellate_entity()
when expanding INSERT exploded entities.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 22:07:24 +03:00
Hakan SevenandClaude Sonnet 4.6 464bb8905f fix: INSERT Insertion snap point and Hatch boundary snap points
INSERT entities now expose their insertion point as a SnapHint::Insertion
pre-baked snap candidate, enabling OSNAP Insertion to locate block
reference origins. The cross marker is also positioned at the actual
insertion point instead of the origin.

Hatch entities now populate key_vertices from Polyline and Line boundary
edges (with correct elevation Z), enabling Endpoint and Midpoint snapping
to hatch boundary geometry.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 22:04:53 +03:00
Hakan SevenandClaude Sonnet 4.6 4504916f97 fix: resolve DXF %%x codes and render MText decoration strokes
Text/MText values now correctly handle DXF special-character sequences:
- %%d → °, %%p → ±, %%c → ⌀, %%nnn → Unicode scalar (resolved in tessellation)
- %%u / %%o toggle underline and overline strokes for TEXT entities

MText decoration codes (\L/\l, \O/\o, \K/\k) were previously consumed
as argument-bearing codes, silently eating the decorated text. Fixed
strip_mtext_codes to keep them as zero-width markers; tessellate_text_ex
now emits actual line strokes (underline, overline, strikethrough).
measure_mtext_chars and word_wrap skip markers so line widths are correct.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 21:57:51 +03:00
Hakan SevenandClaude Sonnet 4.6 db653c5a07 fix: derive dimension text rotation from axis geometry
acadrust's DXF reader does not parse group code 53 (text rotation),
so DimensionBase.text_rotation is always zero for DXF-loaded files.
Fall back to the natural axis-aligned rotation computed from geometry:
Linear dimensions use d.rotation (radians), Aligned dimensions use
atan2 of the second-first vector.  Normalize to (-π/2, π/2] so text
never appears upside-down.  Explicit non-zero overrides stored by the
DWG reader are preserved.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 17:08:12 +03:00
Hakan SevenandClaude Sonnet 4.6 19bb846c50 fix: dimension line geometry and DXF rotation unit mismatch
append_linear_dimension() applied the same perp offset to both d1 and d2,
but only d2 is guaranteed to be at the definition_point level; d1 must be
projected independently onto the dimension line, otherwise non-collinear
first/second points produce a parallelogram instead of a rectangle.

Fix: compute dim_line_pos = def.dot(perp) and project each endpoint
separately so the dimension line is always parallel to axis.

Additionally, the acadrust DXF reader stores LinearDimension.rotation from
group code 50 (degrees) without converting to radians, while DWG and our
own annotation commands store it in radians.  Add a post-load fixup in
load_file() that converts the rotation to radians for DXF files so
tessellation can call cos/sin uniformly.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 16:57:23 +03:00
Hakan SevenandClaude Sonnet 4.6 2255dc5176 fix: hatch visibility in synced_hatch_models()
Standalone hatches and hatches from block inserts were rendered
regardless of entity invisible flag, layer off/frozen state, or
whether the owning INSERT belongs to the current layout block.

- Filter self.hatches by invisible, layer, and belongs_to_visible_block
- Skip INSERT entities that are invisible, on a hidden layer, or outside
  the current layout block before exploding their contents
- Skip individual hatches produced by explode that are invisible or on
  a hidden layer

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 16:43:37 +03:00
Hakan SevenandClaude Sonnet 4.6 52c830e477 perf: Arc-cache paper_canvas_wires() to eliminate O(N) clone on navigation
paper_canvas_wires() now returns Arc<Vec<WireModel>> backed by
paper_canvas_cache (epoch-keyed). Pan/zoom frames hit the cache and
return a pointer copy; only geometry changes rebuild the wire list.
Completes the paper-canvas caching chain (L+M): sheet cache →
projected-per-viewport cache → full canvas Arc cache.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 16:30:21 +03:00
Hakan SevenandClaude Sonnet 4.6 9a1211842d perf: cache paper-canvas tessellation and viewport projections (Option L)
Add two epoch-keyed caches to eliminate per-frame work in paper-space layouts:

paper_sheet_cache — caches wires_for_block(layout_block) as Arc<Vec<WireModel>>.
Previously paper_sheet_wires() re-tessellated all paper-space entities (title
block, annotations, viewport borders) on every mouse-move frame.  Now it is an
O(1) Arc clone.  entity_wires_arc() reuses the same cache so a geometry-change
frame triggers only one tessellation pass even when both the GPU renderer and
the Iced canvas are active.

paper_projected_cache — caches projected+clipped wires per viewport handle.
viewport_content_wires() now uses model_wires_for_viewport_arc() for the
tessellation step (already O(1) via viewport_wire_cache) and stores the
projection+clip result keyed by (vp_handle, epoch).  Navigation frames
(pan/zoom on the paper sheet) are O(N_viewports) Arc/slice copies instead of
O(N_entities × N_points × N_viewports) tessellate+project work.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 16:21:45 +03:00
Hakan SevenandClaude Sonnet 4.6 bfd8d0411a perf: AABB snap pre-rejection via acadrust bounding_box() (Option K upgrade)
Add `aabb: [f32; 4]` field to WireModel (world-space 2-D bounding box).
In tessellate_entity() set each wire's AABB from e.as_entity().bounding_box()
via new entity_aabb() helper; Insert sub-entities each get their own AABB.
Preview/interim wires and any entity returning a zero-extent default box get
UNBOUNDED_AABB so they are never pre-rejected.

Replace the chord-sphere heuristic in Snapper::wire_in_range() with a proper
AABB vs snap-circle overlap test.  The new check is four scalar comparisons and
correctly handles closed curves (circles, arcs) which the chord-sphere approach
had to special-case.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 12:21:51 +03:00
Hakan SevenandClaude Sonnet 4.6 2da9301a2b perf: viewport wire cache, Arc hit_test_wires, snap world-space pre-rejection (I+J+K)
Option I: add per-viewport Arc wire cache so paper-space viewport rendering
no longer re-tessellates model content every frame.

Option J: hit_test_wires() returns Arc<Vec<WireModel>> — O(1) pointer copy
in model space instead of a full Vec clone on every mouse move during commands.
paper_canvas_hatches/wipeouts() now route through the epoch caches added in F.

Option K: add world_snap_r + wire_in_range() pre-check to Snapper::snap().
All Endpoint/Midpoint/Nearest/Perpendicular/Intersection loops skip wires
whose chord sphere is outside the snap circle using O(1) scalar comparisons,
eliminating O(entities × vertices) matrix multiply cost when zoomed in.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 11:55:08 +03:00
Hakan SevenandClaude Sonnet 4.6 81db3c37d0 perf: cache hatch/wipeout/image/mesh models per geometry epoch (F+G+H)
Add Arc<Vec<...>>-backed epoch caches for hatches, wipeouts, images, and
meshes — the same pattern used for wires. build_primitive() now returns
an O(1) Arc refcount bump on navigation frames instead of rebuilding
every collection from scratch.

Also wrap ImageModel.pixels in Arc<Vec<u8>> so cloning an ImageModel is
O(1) regardless of image resolution.

Navigation frames no longer copy pixel data, tessellated hatch boundaries,
or mesh geometry — all per-frame CPU work on unchanged geometry is now
eliminated.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 11:34:52 +03:00
Hakan SevenandClaude Sonnet 4.6 432e35ded3 perf: parallel wire tessellation with Rayon (Option C)
Extract tessellate_one body into a Send-compatible free function
tessellate_entity, then drive it with rayon::into_par_iter() in
wires_for_block(). Spreads file-open and post-edit tessellation across
all CPU cores. Navigation frames are unaffected (Options A/B cache
eliminates per-frame tessellation entirely).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 02:36:34 +03:00
Hakan SevenandClaude Sonnet 4.6 9c8c9a4060 perf: cache SortEntitiesTable scan (option E)
Replace the O(objects) linear scan inside wires_for_block() with a
sort_cache keyed by geometry_epoch. On a cache miss the index is built
once by scanning all document objects; subsequent calls within the same
epoch do an O(1) HashMap lookup (block_handle → entity→sort map).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 02:17:40 +03:00
Hakan SevenandClaude Sonnet 4.6 d1ecaf4695 perf: eliminate per-frame re-tessellation and GPU re-upload in large files
Add two complementary render caches that make mouse navigation essentially
free regardless of file size:

Option A – Wire tessellation cache: entity_wires_arc() caches the
tessellated Vec<WireModel> keyed by geometry_epoch and returns an Arc,
so navigation frames (no preview wires) incur zero Vec clones and zero
tessellation work. Primitive::wires changed to Arc<Vec<WireModel>>.

Option B – GPU buffer cache: Pipeline stores cached_epoch; prepare()
skips all upload_wires/hatches/images/meshes calls when the epoch is
unchanged, writing only the 192-byte camera uniform instead.

geometry_epoch is a process-wide AtomicU64 incremented by every Scene
mutation (add/erase entity, selection, layer visibility, layout switch,
populate_*, grip, transform, copy, clear, preview wires). Using a global
counter rather than a per-scene counter prevents two tabs that happen to
have the same local bump count from sharing a cached_epoch value, which
was causing the first file's geometry to remain on screen after opening a
second file.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-26 02:13:31 +03:00
Hakan SevenandClaude Sonnet 4.6 6b4bc1b832 fix: paper background no longer resizes when a viewport is added
White paper fill was calculated from entity bounding boxes (paper_entity_extents),
so adding a viewport caused the paper to visually shrink to the viewport's bounds.
Now paper_limits() (layout min/max limits) is used directly, and the unused
paper_entity_extents() function is removed.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-24 12:02:33 +03:00
Hakan SevenandClaude Sonnet 4.6 3ec7bf7065 refactor: deduplicate entity type name mapping
Make scene::properties::entity_type_name the single source of truth.
entity_type_label in helpers.rs now delegates to it instead of
maintaining a separate match list.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-24 01:44:33 +03:00
Hakan SevenandClaude Sonnet 4.6 a5057e26b1 fix: show specific entity type name in Properties panel for all entity types
Expanded entity_type_name() to cover all EntityType variants from acadrust
instead of falling back to the generic "Entity" label.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-24 01:40:33 +03:00
Hakan SevenandClaude Sonnet 4.6 5f3182fa9a fix: NAVVCUBE and UCSICON commands now properly toggle visibility
NAVVCUBE: propagate show_viewcube flag through ViewportPane and the wgpu
Primitive so the ViewCube pipeline skips upload/render when hidden.
UCSICON: bare command (no subcommand) now toggles current state instead
of printing usage info.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-24 01:31:52 +03:00
Hakan SevenandClaude Sonnet 4.6 00da80f6e5 fix: align ViewCube hit-test with widget bounds instead of full window
prepare() was passing viewport.logical_size() (full window) to
viewcube.upload(), while hit_test/hover_id used the shader widget's
bounds. With the properties panel present the widget is narrower than
the full window, so the visual cube was scaled smaller but the click
area stayed full-size — causing the mismatch that grew toward the
bottom-left corner.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-23 21:39:43 +03:00
Hakan SevenandClaude Sonnet 4.6 a6592cc9bc fix: ViewCube hover, glow, centroids, corner geometry and snap angles
- Hide OS crosshair and drawn crosshair when cursor is over ViewCube area
- Enhanced hover glow in WGSL shader (blue tint + brightness boost)
- Fixed region_centroids() Y-axis sign for all FRONT/BACK-related regions
- Swapped corner geometry assignments (TPF↔TBK, BTF↔BBK) to match
  intended FRONT/BACK semantics (sy=-1 = FRONT, sy=+1 = BACK)
- Corrected all snap_angles: yaw_pitch_to_quat(0,0) places eye at -Y
  (FRONT view), so FRONT=0 and BACK=PI — cascaded through edges/corners

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-23 19:11:19 +03:00
Hakan SevenandClaude Sonnet 4.6 f63858876d fix: align white paper fill with entity borders
- paper_entity_extents() computes the bounding box of actual DXF entities
  so the white fill always matches the drawn title-block / frame borders,
  regardless of whether Layout min/max_limits match entity positions
- Falls back to paper_limits() (→ A4 297×210) when layout has no entities
- Removed paper_boundary_wire: the white fill now acts as the visual paper
  edge so the near-white wire (invisible on white) was redundant
- LayoutCreate sets limits to A4 landscape immediately after add_layout()
  to override acadrust's imperial default (12×9)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-23 14:21:03 +03:00
Hakan SevenandClaude Sonnet 4.6 0a291c877b feat: A4 landscape default paper, white paper area on dark desk
- paper_limits() falls back to A4 landscape (297×210mm) even when no
  Layout object exists in the document
- paper_bg_color default changed to white [1,1,1,1] (paper surface color)
- PaperCanvas::draw() fills canvas with dark desk color first, then draws
  the paper area rectangle in paper_bg_color so the sheet is clearly
  visible against the desk background
- view.rs container background uses the fixed desk color instead of
  paper_bg_color so it no longer conflicts with the paper-surface color
- BACKGROUND RESET for paper space now resets to white instead of dark gray
- Removed unused PaperSheet variant, paper_sheet() constructor,
  build_paper_sheet_primitive() and associated match arms (dead code
  after 2D canvas replacement)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-23 14:05:07 +03:00