Commit Graph
301 Commits
Author SHA1 Message Date
HakanandClaude Opus 4.7 26271b8c63 perf(scene): zoom-adaptive curve tolerance for top-level wires (Phase 3.2)
Edge tessellation used a fixed 0.005 world-unit chord-height tolerance
regardless of zoom — a 1000 m radius arc viewed at 50 mm/px still emitted
hundreds of segments that all collapsed onto the same screen pixel.

`truck_tess` now reads tolerance from a process-wide AtomicU64 override.
`Scene::wires_for_block` sets the override to ~0.5 px chord height
(world_per_pixel × 0.5) before the par_iter, then a Drop guard clears it
back to the default. Floor stays at the original CURVE_TOL so extreme
zoom-in keeps full quality. Block-cache rebuilds and off-render paths
(snap, hit-test) always see the default — the override is bracketed to
the render call only.

Roadmap entry for Phase 3.2 removed; Implementation Order / Key Files
tables updated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 14:07:28 +03:00
HakanandClaude Opus 4.7 0584e3c862 perf(scene): text LOD ladder 1/5 px with baseline-line tier
Replaces the 2/4 drop/greek/full ladder with 1/5 baseline/greek/full so
zoomed-out text still leaves a visual hint instead of disappearing:

  h_px < 1   → OBB baseline line in the text color
  1 ≤ h < 5  → greeked OBB rect
  h_px ≥ 5   → full per-glyph stroke

Extra guard: in the baseline tier, the line itself is dropped when its
projected length is under 2 px (single-char text seen edge-on would
otherwise emit a sub-pixel segment).

Text/MText is exempted from the generic 5 px AABB cull (top-level and
block-defn paths) so it can reach this ladder even when its bounding
box projects below the threshold.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 13:19:43 +03:00
HakanandClaude Opus 4.7 6cc2323905 fix(scene): skip pixel-size LOD in paper space
PaperCanvas (Iced 2-D canvas) never feeds set_render_pixel_scale, so
last_world_per_pixel held either zero or a stale model-world value.
Paper-space entity AABBs (mm) tested against model-scale wpp all
projected below the 5 px threshold and got culled — leaving only
Viewports and Inserts (which bypass the cull). world_per_pixel() now
returns None in paper space, mirroring view_world_aabb()'s existing
skip with the same reasoning: the paper canvas is small enough that
LOD culling buys nothing.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 09:45:48 +03:00
Hakan SevenandClaude Opus 4.7 f713354de1 fix(block_cache): drop zero-bbox placeholders from defn LO scan
`Entity::bounding_box()` for empty / vertex-less entities (Polyline,
LwPolyline, Hatch, etc.) returns `BoundingBox3D::from_point(Vector3::ZERO)` —
the (0, 0, 0)→(0, 0, 0) placeholder. Including those in the LO centroid
scan dragged it toward the origin even for UTM-authored blocks, defeating
the precision-preservation: stored f32 then sat at UTM-minus-near-zero,
losing precision before tessellate even returned. Reject the zero
placeholder so only real geometry contributes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 06:30:52 +03:00
Hakan SevenandClaude Opus 4.7 0d543e8c88 fix(block_cache): harden defn local_offset against NaN + bad-extent poison
Two latent traps in the first-pass centroid scan:

- A bbox where x/y were finite but z was NaN would skip the z update;
  if NO entity contributed a finite z, `min[2]` stayed at `INFINITY` and
  `max[2]` at `NEG_INFINITY`. `(INF + NEG_INF) * 0.5 = NaN`, so the defn's
  `local_offset.z` was NaN, propagating to every cast point in the defn.
  Track `have` per axis and default missing axes to 0.

- A single corrupt entity at parser-garbage coords (1e15+) silently
  dragged the centroid out of the real geometry's neighbourhood. Reject
  per-axis values above `SANE_EXTENT` so one bad bbox doesn't ruin the
  whole defn.

Also include `Insert::insert_point` as a contributing sample so a
composite block (only nested Inserts, no direct wires) still gets a
non-zero offset — useful for the nested-defn AABB transforms even
though no direct LocalWire stores against it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 21:56:27 +03:00
Hakan SevenandClaude Opus 4.7 2f0220b1a0 feat(xref+block): bind xref tables, fade rendered geometry, per-defn precision offset
Xref work:
- merge_xref_into_block now copies the xref's layer / linetype / nested
  block_record tables into the host under `{xref}|{name}` prefixes and
  remaps entity references, so ByLayer/ByBlock resolution lands on the
  right entry instead of falling back to WHITE + 1 px.
- expand_insert (and the explode fallback) detect xref BlockRecords by
  flag and apply `fade_toward_bg(color, bg, 0.5)` to every emitted final
  color (selection bypasses the fade). ExpandCtx propagates `is_xref` +
  `bg_color` through nested expansions.

Block precision groundwork:
- BlockDefn gains `local_offset: [f64; 3]` set in build_defn from the
  AABB centre of the defn's direct entities. tessellate_sub_local now
  passes that offset to tessellate(), so LocalWire.points / fill_tris /
  aabb_local / text_obb_local are stored in offset-frame f32 and don't
  lose precision for distant content.
- emit_wire / emit_greeked_text / transform_tangent take the defn offset
  and re-add it in f64 before composing with accum_xform. defn_aabb_
  recursive transforms nested children from their offset frame into the
  parent's. transform_offset_aabb_xy replaces transform_aabb_xy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 21:45:37 +03:00
Hakan SevenandClaude Opus 4.7 ea87b3db76 fix(scene): greek text OBB honors attachment point, propagates into blocks
Top-level OBB used insertion_point as anchor and hardcoded MText anchor to
bottom-left, so greeked rects landed on the opposite side of the actual
text whenever MText used a non-default attachment_point or TEXT used a
non-Left/Baseline alignment. Block-defn subs never set or read
`text_obb_local`, so greeked text inside Inserts fell back to the
axis-aligned AABB and ignored entity rotation entirely.

- Share OBB corner computation via `text_obb_corners_native`.
- Map all 9 MText AttachmentPoint variants to (h_anchor, v_anchor).
- For TEXT, anchor on `alignment_point` (DXF 11) when alignment != Left+Baseline.
- Populate `LocalWire.text_obb_local` in `tessellate_sub_local`.
- `emit_greeked_text` transforms OBB corners by `accum_xform`, falls back
  to AABB only when no OBB was precomputed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 20:49:33 +03:00
Hakan ab69203414 feat(scene): enhance text LOD handling and add OBB support for Text/MText 2026-05-14 15:21:35 +03:00
Hakan e043e54f7e perf(block_cache): apply text LOD ladder inside block defns
Text greeking previously only fired for top-level Text/MText — text
that was baked into a block defn at cache build time still ran the
full glyph stroke path on every expand, so cadastral parcel labels
(typically inside xrefed blocks) saw none of the LOD benefit.

  - `LocalWire` now records `text_height_local` for Text / MText subs.
  - `expand_defn`'s wire arm applies the same 5 / 10 px ladder: cull
    below 5 px (after scaling the local glyph height by the accumulated
    transform's Y-scale), greek between 5 and 10, full glyphs above.
  - `emit_greeked_text` reuses the batch infrastructure so multiple
    greeked rects in the same style still collapse into one buffer.
2026-05-14 15:01:51 +03:00
Hakan 84621a2fab tune(scene): text LOD ladder 5/10 px (cull / greek / full)
Per real-world feedback the previous greeking threshold (4 px → full)
left a lot of unreadable text still going through the glyph path
without any visible payoff. Sharper ladder now:

  - h < 5 px   → drop (illegible, not worth even a rect)
  - 5–10 px    → greeked rect in text color
  - h ≥ 10 px  → full per-glyph tessellation
2026-05-14 14:56:12 +03:00
HakanandClaude Opus 4.7 242e8870c3 perf(scene): greek top-level Text/MText below 4px height
When a Text/MText entity's anno-scaled glyph height projects to fewer
than 4 screen pixels, drop the per-glyph stroke tessellation and emit
a single filled rectangle in the text's color instead. The user still
sees "text lives here" at the original color and footprint, but the
expensive font-stroke generation is skipped for thousands of unreadable
labels at fit-all / zoom-out views.

Pre-boosts the fill color to compensate for the 0.45 ambient-occlusion
dim the face3d pipeline applies (it's meant for 3D-shaded mesh solids,
not 2D text greeking).

Note: applies only to top-level entities; Text/MText *inside* block
defs is already tessellated into the block cache at build time, so
greeking those would require either a cache rebuild on zoom or a
parallel "low-detail" cache. Left for later if real-world test
suggests it's needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 14:49:59 +03:00
HakanandClaude Opus 4.7 c5ed83c752 perf(scene): pixel-size LOD cull at zoom-out
Frustum culling only helps at zoom-in (offscreen geometry); zoom-out
still tessellated and uploaded every wire even when an entire entity
projected to a single pixel. Now we also drop wires whose world AABB
projects smaller than 0.5px in the active view — a fit-all view of a
city plan skips sub-pixel symbols, lines, and curves before they ever
hit the GPU.

  - `Scene` captures `world_per_pixel` each render from camera ortho_size
    / widget height.
  - `expand_insert` / `tessellate_entity` take an optional
    `world_per_pixel`; per-LocalWire / per-nested / whole-Insert /
    top-level entity AABB → pixel-size test → skip if < MIN_PIXEL_SIZE.
  - One-off paths (hit-test, viewport pane) pass `None` so they still
    see full geometry regardless of zoom.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 14:22:39 +03:00
HakanandClaude Opus 4.7 8d5ecdef51 fix(scene): correct view_world_aabb space + guard initial render
Two bugs in the new frustum culler that left the GUI rendering nothing
on file open:

1. `cam.target` is already in **local** f32 space — `fit_to_bounds`
   populates it from local wire coords — so subtracting `world_offset`
   again pushed the view rect to the negated centroid, with the actual
   geometry far outside.
2. The first call to `entity_wires()` after open comes from `fit_all()`
   itself, while `camera_generation` is still 0 and the camera is at
   its default origin. Culling against that rect discarded every wire
   → fit_all saw an empty list → camera never moved → the user-visible
   render stayed empty forever.

`view_world_aabb` now returns `None` until `camera_generation > 0`
(letting fit_all collect a real extent), then derives the view rect
directly from `cam.target` in local space.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 14:02:48 +03:00
HakanandClaude Opus 4.7 78cbb5570e perf(scene): frustum-cull wires using camera view AABB
Block defns and top-level entities now know their world-space XY
AABB, and `tessellate_entity` / `expand_insert` early-out for anything
that doesn't intersect the camera's view rect. Tested commit-only on
time_open (correct subset emitted with default camera); GUI run
pending user verification.

Cache plumbing:
  - `LocalWire` / `BlockDefn` carry `aabb_local` (post-build recursive
    union over nested defns).
  - `Scene::view_world_aabb()` derives a 25%-margined world rect from
    the active camera + last-seen render aspect.
  - `wire_cache` / `paper_sheet_cache` keys widen to
    `(geometry_epoch, camera_generation)` so pan/zoom invalidates the
    cached wire list — and pipeline `cached_epoch` widens to match
    so the GPU re-uploads on view change.
  - `XrefStatus::Unloaded` ack already shipped — orthogonal to this.

Also adds `examples/xref_states.rs` to dump per-block_record xref
metadata for diagnosing the is_loaded / unloaded distinction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 13:52:04 +03:00
HakanandClaude Opus 4.7 def72e435e perf(scene): cache block defns + batch wires by style
Tessellates each block_record once into local-coord LocalWires; Insert
references transform-copy + ByBlock-resolve the cache rather than
explode+re-tessellate per reference. Same-style wires inside one Insert
are NaN-batched into a single WireModel to keep wgpu buffer counts
sane — large blocks that previously bailed to a marker now render in
a handful of buffers each.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 11:12:08 +03:00
Hakan SevenandClaude Opus 4.7 c61d648dc7 fix(scene): unfreeze open on huge xref Inserts
A real DWG with an xref containing ~74k sub-entities froze on the first
render: Insert tessellation exploded the block, style-resolved every sub
serially, and one bad sub could hang the loop forever.

- Insert short-circuits to an insertion marker when the referenced block
  has more than INSERT_SUB_LIMIT (5_000) sub-entities. Documented as
  temporary in RENDERING_OPTIMIZATION.md.
- Re-run purge_corrupt_entities after resolve_xrefs so parser-garbage
  inside xref content is caught (initial purge runs before xref load).
- build_derived_caches: single pass over entities collecting handles
  per kind; tessellation reads via doc.get_entity instead of cloning
  every EntityType. Image decode now parallel too.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 23:02:48 +03:00
HakanandClaude Opus 4.7 9b6d5437fa revert: drop wire segment caps that silently truncated geometry
Remove MAX_SEGS_PER_WIRE = 100_000 and MAX_TOTAL_SEGS = 3_000_000.
These were stop-gap GPU-memory guards that mutated visible output:
single complex entities were cut mid-geometry, and entire wires past
the 3M total were dropped by arrival order with no spatial awareness.
Either is worse than honestly drawing whatever the file contains —
truncated arcs and missing road segments look like bugs to the user,
not "we ran out of GPU memory."

The matching "Temporary Workarounds" section in
RENDERING_OPTIMIZATION.md goes with them. Per-entity LOD and
viewport culling (Phase 1 + 3.2 in the same doc) remain the right
long-term answer; until they land, prefer correctness over a soft
crash on pathological files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:11:54 +03:00
HakanandClaude Opus 4.7 2f751c08f4 fix(scene): drop Z gate in fit_all so high-elevation entities show
Civil 3D files can legitimately carry profile geometry at huge Z
(10^6+ units) inside an otherwise small XY footprint — long-section
polylines on a road, for example. fit_all's outlier gate previously
filtered any point whose |x|, |y|, or |z| exceeded 10× the header
extent half-size, which dropped those polylines and made ZOOM
EXTENTS center on the main drawing only, leaving the profile
geometry permanently invisible.

The gate exists to reject origin-stuck entities and Ray/XLine far
points, both of which manifest as large XY excursions after
world_offset subtraction. Z-axis outliers are not a known garbage
pattern, so the gate now only checks XY.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:05:38 +03:00
HakanandClaude Opus 4.7 b2588e1790 feat(io): drop parser-garbage entities at load time
acadrust's DWG reader occasionally desynchronises on certain files
(observed on render.dwg: 1519 of 1763 LwPolylines arrived with non-unit
normals in the 1e200+ range and 100k phantom vertices). Tessellating
those entities allocates gigabytes and produces NaN-laden wires,
freezing the UI.

Add a pre-tessellation guard that scans freshly loaded documents and
drops any entity failing a cheap sanity check: non-unit normal,
non-finite or absurd coordinate, or vertex count above 100k. Count of
dropped entities is threaded through DerivedCaches and surfaced as a
warning in the command line so the user knows the file was patched up
on the way in.

Same file in DXF form parses cleanly (0 corrupt), so this is strictly
a DWG-path workaround until the upstream parser bug is fixed.

The new examples/inspect_polyline.rs is a one-off diagnostic that dumps
LwPolyline/Polyline2D/Polyline3D fields plus a corrupt-entity count;
useful for narrowing down the acadrust bug.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:00:16 +03:00
Hakan Seven 288ccff95a fix(render): preserve f64 precision until world_offset subtraction
Entity converters were casting WCS coordinates to f32 before the renderer
subtracted world_offset, which destroyed sub-unit precision on drawings
sitting at large coordinates (typical UTM-style CAD files). The multileader
case made it visible — glyphs warped under rotation — but every entity
that emits TruckObject::Lines/SegmentedLines/key_vertices/fill_tris was
affected.

Type changes (in src/scene/acad_to_truck.rs):
- TruckObject::Lines / SegmentedLines: Vec<[f32; 3]> → Vec<[f64; 3]>
- TruckEntity::key_vertices / fill_tris: f32 → f64

All ~17 producer files updated to keep WCS coordinates in f64 right up to
the TruckEntity boundary. The consumer in scene/tessellate.rs now performs
`(x - ox) as f32` (f64 subtract, then cast) for points, key_vertices, and
fill_tris — preserving precision near the camera origin even when the
drawing sits at million-unit coordinates.

catmull_rom_pts (multileader spline) is now f64; the scene path widens its
offset-relative f32 ctrl points to f64 around the call and narrows back at
the output.

Notes:
- Table layout still uses glam::Vec3 (f32) internally; widened to f64 only
  at the TruckEntity boundary for type compatibility. Separate cleanup to
  switch its internal math to f64 is tracked.
- snap_pts and tangent_geoms remain f32 — they are UI feedback (snap
  candidates, tangent hints) and the precision is sufficient there.
2026-05-12 23:47:40 +03:00
Hakan Seven 8bfa5088c4 fix(multileader): render text properly — strip MText codes, honor full layout
Multileader text was emitted as a single literal stroke pass with no MText
code handling, no rotation, no attachment, no line breaks, no per-piece
color and bad f32 precision under world_offset. Two separate render paths
both had the same bug, and one was masking the fix in the other.

This rewrite, applied to both paths:

- Strips MText format codes (`{\fArial Black|b0|i0|c162|p34;...}`) so the
  rendered text is the actual content, not the markup.
- Honors text_rotation (preferring text_direction so transforms survive),
  text_attachment_point (Left/Center/Right horizontal anchor), and
  text_left_attachment for vertical alignment via a shared
  v_offset_for_attachment helper.
- Splits on \P/\n/\N and word-wraps to text_width.
- Applies line_spacing_factor with the same 5/3 × height convention as
  MText.
- Resolves the text style via text_style_handle (fallback STANDARD).
- Applies ml.scale_factor everywhere (arrow size, dogleg length, text
  height, text width). anno_scale is now only applied when
  enable_annotation_scale is set.
- Splits the scene-level output into separate WireModels so leader,
  text, frame and background fill each carry their own colour
  (ctx.text_color, ml.text_frame, ctx.background_fill_color).
- Mirrors text_direction, base_direction and root.direction in
  apply_transform so a mirrored multileader's text follows the geometry
  instead of staying at its original rotation.
- Computes the text origin and frame corners in offset-relative f32
  space — drawings sit at large absolute coordinates and casting to f32
  before subtracting world_offset was wrecking the rotated sub-glyph
  precision.
2026-05-12 23:27:53 +03:00
Hakan Seven 200f10fd25 fix(explode): correct arc/ellipse direction in mirrored INSERT blocks
Mirrored INSERTs reverse OCS handedness, but acadrust's explode used to
keep the original normal — making CCW-around-normal arcs and ellipses
sweep the wrong way. The fix lives in acadrust 7c711e9, which flips the
new normal when det < 0 and reprojects center/major_axis/angles into the
new OCS. H7CAD's fix_mirrored_arc workaround is no longer needed.
2026-05-12 22:04:12 +03:00
HakanandClaude Sonnet 4.6 a3efad9caf fix(hatch): align EllipticArc angle logic with CircularArc
Use reflection (TAU - sa, TAU - ea) for CW arcs and span.abs() for
segment count, consistent with the CircularArc pattern in both
hatch boundary (mod.rs) and tessellate (tessellate.rs).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 17:47:54 +03:00
HakanandClaude Sonnet 4.6 4b5f4c7ee8 fix(hatch): sync color from entity in synced_hatch_models
Color was read from the stale HatchModel cache instead of the live
entity, so property-panel color changes appeared on boundaries (wire)
but not on the hatch fill pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 17:33:16 +03:00
Hakan ca07268c7c cargo fmt 2026-05-12 10:36:15 +03:00
Hakan 468cbde5e1 fix(hatch): improve angle handling for circular arcs in tessellation 2026-05-12 09:41:41 +03:00
Hakan Seven 773a5f74cb fix(hatch): more improvement on arc boundary and pattern 2026-05-10 20:29:39 +03:00
Hakan Seven 461d0c5d86 hatch boundary correction for cw ccw 2026-05-09 23:56:04 +03:00
HakanandClaude Sonnet 4.6 321433ee45 fix(render): cap wire segments to prevent GPU memory exhaustion
Adds two-layer protection against large entities crashing the renderer:
- MAX_SEGS_PER_WIRE (100K): per-entity hard cap, excess segments truncated
- MAX_TOTAL_SEGS (3M): scene-level budget in upload_wires, excess wires skipped

Both limits log a warning to stderr when triggered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 17:24:28 +03:00
HakanandClaude Sonnet 4.6 4b4c0d31d2 chore: switch acadrust to GitHub source, fix removed is_annotative field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:50:02 +03:00
Hakan Seven 048bb41aef feat(render): solid fill for PolyfaceMesh and PolygonMesh entities
PolyfaceMesh and PolygonMesh were wireframe-only. They now render with
the same flat-color solid fill as Face3D entities.

- Add fill_tris: Vec<[f32;3]> to TruckEntity and WireModel
- PolyfaceMesh: fan-triangulate each face into fill_tris
- PolygonMesh: triangulate each grid quad into fill_tris
- tessellate.rs Lines branch: apply world_offset to fill_tris
- Face3DGpu::from_wires: collect fill_tris from all wires in addition
  to Face3D key_vertices quads
- upload_face3d: pass all_wires so fill_tris are batched into one draw call
2026-05-06 23:24:26 +03:00
Hakan Seven d30f80f963 chore: suppress dead_code warning on compute_and_set_world_offset 2026-05-06 23:07:37 +03:00
Hakan Seven 2da3640b45 perf: build hatch/image/mesh caches off the UI thread
Parsing + DerivedCaches construction now runs entirely in a dedicated
OS thread inside open_path(). FileOpened handler only assigns the
pre-built caches, keeping the UI thread free during file load.

- Add build_derived_caches(): parallel hatch + mesh tessellation via rayon
- Extend Message::FileOpened / open_path return type with DerivedCaches
- Drop populate_*_from_document() calls from FileOpened handler
2026-05-06 23:04:53 +03:00
Hakan Seven 45f9f96c93 perf: implement L–S performance improvements
- L: pre-project wire endpoints to screen before ApparentIntersection pair loops
- O+S: tight per-segment AABB cull + move a0/a1 Vec3 outside inner snap loop
- P: epoch-cached entity→block reverse map replaces O(E×B) scan
- Q: lazy per-point projection in hit tests eliminates per-wire Vec<Point> alloc
- R: HatchModel.boundary Arc<Vec> so clone is pointer bump not heap alloc
2026-05-06 22:42:22 +03:00
Hakan SevenandClaude Sonnet 4.6 b83c982bc2 fix(scene): hatch pattern scale and per-entity annotative check
- Hatch pattern_scale multiplied by annotation_scale in model space so
  pattern density stays consistent when CANNOSCALE changes
- Annotative check now uses AcAnnoPO xdata (R2007+ marker) instead of
  relying solely on group-code-293 flag; falls back to is_annotative
  for files with no xdata (old DXF backward compat)
- acadrust patched to local path for is_annotative field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 22:19:28 +03:00
HakanandClaude Sonnet 4.6 0e5dec0948 feat: annotation scale system (CANNOSCALE, PSLTSCALE, MultiLeader, Leader)
- Scale popup dropdown replaces cycle behavior; paper-space viewport scale
  editable when a viewport is active/selected
- CANNOSCALE persisted via DXF $USERR1 (read on open, write on save)
- PSLTSCALE: linetype dash lengths scaled by viewport anno_scale so dashes
  appear uniform physical size in paper space
- MultiLeader: arrow size, dogleg length, and text height scale with
  anno_scale; leader line paths (world coords) stay fixed
- Leader: arrow size and hookline length scale with anno_scale; also
  corrects world_offset subtraction that was previously deferred to the
  TruckObject::Lines path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 17:50:46 +03:00
Hakan 534e4ff26b feat: implement Grippable and PropertyEditable traits for Solid3D, Region, and Body; add snap points and geometry properties 2026-05-06 10:00:37 +03:00
Hakan SevenandClaude Sonnet 4.6 899b3f4796 feat: GPU scissor rect for paper-space viewport wires
Viewport-projected wires now clip at pixel level to their viewport boundary.

- WireModel.vp_scissor: Option<[f32; 4]> — paper-space bbox set on projection
- WireGpu.vp_scissor carries it through to the draw loop
- Pipeline.compute_wire_scissors() converts paper→NDC→pixel each frame
- render() calls set_scissor_rect() per wire, reset after viewport group
- INTEGRATION_GAPS.md: Viewport ⚠️ → ✅, all 51 gaps resolved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 07:12:05 +03:00
Hakan SevenandClaude Sonnet 4.6 27aca41dd1 feat: plinegen + SAB parsing for Solid3D/Region/Body
LwPolyline PLINEGEN=0 (DXF default): emit SegmentedLines with NaN
separators between vertex-to-vertex segments; wire_gpu resets
arc-length to 0 after each NaN so linetype pattern restarts per vertex.
PLINEGEN=1: existing Contour path unchanged (continuous pattern).
WireModel gains plinegen field propagated through tessellate pipeline.

Solid3D/Region/Body: call SabReader::read() when parse_sat() returns
None due to binary SAB format (DWG files / newer AutoCAD exports).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 06:55:28 +03:00
Hakan SevenandClaude Sonnet 4.6 63b17547f9 fix: warn on missing non-ASCII CXF glyphs; close ellipse snap gap
cxf.rs: missing non-ASCII glyphs now emit eprintln! once per font+char
pair via a global Mutex<HashSet> dedup set. ASCII chars silent (expected).

INTEGRATION_GAPS.md: ellipse partial arc snap marked done — endpoints are
correctly emitted via key_vertices → SnapType::Endpoint in snap engine.
Unicode warning gap closed. plinegen behaviour documented.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 20:20:16 +03:00
Hakan Seven 603d07412e refactor: replace angle calculations with radians for consistency 2026-05-03 21:58:38 +03:00
Hakan SevenandClaude Sonnet 4.6 9b29db61d4 feat: apply OCS→WCS transform to Hatch wire outline boundary edges
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 20:24:38 +03:00
Hakan SevenandClaude Sonnet 4.6 4527819b2d chore: remove stale multi-viewport limitation comment from ViewportPaneMode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 20:18:08 +03:00
Hakan SevenandClaude Sonnet 4.6 5a7ebbbb38 feat: add Node snap points to dimension defpoints
Dimension measurement origin points (first_point, second_point,
definition_point, angle_vertex, etc.) are now emitted as Node snap
hints so OSNAP Node mode can locate them precisely without relying on
the tessellated geometry wire alone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 17:15:41 +03:00
Hakan SevenandClaude Sonnet 4.6 000f74f951 feat: render CircularArc and EllipticArc hatch boundary edges
Hatch boundaries with arc or ellipse edges were silently dropped from
the wire outline, leaving gaps in the hatch contour. CircularArc and
EllipticArc boundary edges are now tessellated as polyline segments and
their centers are emitted as Center snap hints.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 17:15:02 +03:00
Hakan SevenandClaude Sonnet 4.6 674a94f756 feat: implement OCS→WCS transform for Arc, Circle, and Line
Entities carrying a non-standard extrusion normal (normal ≠ (0,0,1)) were
rendered flat in the XY plane. The DXF arbitrary-axis algorithm is now
implemented in scene::transform::{ocs_axes, ocs_point_to_wcs} and applied
during tessellation of Arc, Circle, and Line so they render correctly on
tilted planes in 3D DXF files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 17:13:42 +03:00
Hakan SevenandClaude Sonnet 4.6 c7b050fb09 feat: emit Insertion snap point for block references
Block INSERTs are expanded into sub-entity wires for rendering but the
OSNAP Insertion hint at the block reference origin was never generated.
Now a zero-geometry WireModel carrying the Insertion snap hint is appended
so OSNAP Insertion mode can locate the block origin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 17:08:20 +03:00
Hakan SevenandClaude Sonnet 4.6 2eb19a21bb fix: identify DWG sheet viewport by center=(0,0) instead of scale heuristic
Debug output revealed the sheet viewport has id=0 (DWG reader never sets
the field), scale=0.8965 (not ≈1.0, so the 0.02 tolerance missed it),
and center=(0.0, 0.0) — exactly at the paper-space origin.

In DWG format the overall/sheet viewport always has its center at the
origin. Content viewports are placed at their actual paper positions and
always have a non-zero center. Switching from the scale heuristic to the
center-position check makes the detection reliable regardless of what
scale value the sheet viewport happens to carry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 15:15:13 +03:00
Hakan SevenandClaude Sonnet 4.6 b346eb4d6e fix: remove dead code warning and fix sheet viewport double-click in DWG files
Remove unused `screen_delta_to_world` camera method (superseded by the
corrected pan_active_viewport formula).

Fix `is_content_viewport`: the old geometry fallback required the viewport
center to be near the origin, but DWG files never write the viewport id
field (stays 0) and the sheet viewport center is the paper centre
(e.g. 105, 148.5 mm for A4) — not the origin. This caused the sheet
viewport to be classified as a content viewport in DWG files, making it
both tessellated (border visible) and enterable via double-click.

The new heuristic uses only the scale: a sheet viewport has scale ≈ 1:1
(view_height ≈ paper height); true content viewports have deliberate
drawing scales (1:50, 1:100, …) that are far from 1.0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 14:55:12 +03:00
Hakan SevenandClaude Sonnet 4.6 fbb6a20f10 fix: correct MSPACE viewport pan speed to match mouse movement
The old formula (distance × 0.001) was a fixed approximation that ignored
the paper camera's current zoom level and the viewport's paper-space size,
causing panning to feel sluggish.

New formula: model_per_pixel = (2 × paper_half_h / canvas_height) × (view_height / vp_height)
This converts screen pixels → paper-space mm → model units correctly,
making the content follow the cursor regardless of paper zoom or scale.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 21:39:05 +03:00