Commit Graph
100 Commits
Author SHA1 Message Date
Karim Jerbi 631c0c97aa fix(command-line): Enter runs the highlighted autocomplete pre-selection 2026-09-24 23:38:01 +01:00
Karim Jerbi bd1fdcc1c5 refactor(chprop): convert choice arms to apply_property_op, fix CTB snapshot leak
Converts 12 selection arms of on_prop_geom_choice_changed to the shared helper: transparency, material, tol text and dim styles, dim overrides, viewport scale, UCS, named views, loft, history, surface and table style. Miss paths no longer push no-op undo entries.

Fixes a leaked undo snapshot: changing plot style in CTB mode is a no-op but pushed an entry. Guard hoisted before the shared snapshot, with a regression test.

Left manual by design: mleader, leader, block and fallback arms, all no-selection arms, and on_prop_geom_commit, whose constraint-aware invalidation the helper cannot express.
2026-09-24 23:16:29 +01:00
Karim Jerbi acc3e05001 refactor(clipboard): share table and block merge copies
merge_dependencies and merge_clipboard_blocks duplicated the table-record and block-definition merge logic already ported into paste_entities_kernel. Extract merge_table_records and merge_block_defs as the single shared copies; both the methods and the kernel delegate to them. The method keeps one batched geometry rebuild instead of one per definition.
2026-09-24 23:16:29 +01:00
Karim Jerbi 7052016dce refactor(kernels): extract reusable clipboard and matchprop kernels
Publishes app-free domain logic for shared use: match_special_props and PolylineMatchStyle move to entities::match_props; xdict trio usable headlessly; new match_layer, copy_to_clipboard, paste_entities and match_properties kernels over CadDocument. Handlers stay thin shells keeping undo, dirty flags, selection and command-line echo.

Adds cross-document copy, paste and property-match tests plus a headless proof driving copy, paste and match with no app instance.
2026-09-24 21:47:14 +01:00
Karim Jerbi e4fa5232f6 refactor(command_driver): split 11.5k-line driver into domain modules
Moves the exhaustive CmdResult router and its 116 handlers out of a single file into command_driver/: thin router stays in mod.rs (no wildcard, compiler-enforced coverage); handlers grouped by domain (entity_commit, clipboard, property_match, modify, solid3d, transform, viewport, text_edit, constraint, dim_edit); shared helpers in utilities/parameters; tests in tests.rs. All moves verbatim 1:1; all entry-point signatures and visibilities unchanged, so every existing caller (ribbon, commands, headless, plugins) resolves untouched.
2026-09-24 21:47:14 +01:00
Karim Jerbi 6b37476687 feat(ui): migrate dialogs across application to shared dialog_button and form_radio builders 2026-09-24 17:33:45 +01:00
Karim Jerbi 1a6279e841 feat(blocks): implement WBLOCK dialog and unify dialog buttons and radio builders 2026-09-24 16:44:24 +01:00
Karim Jerbi 5e34796dca feat(i18n): implement translations for block creation window and units
- Localize block description placeholder via BlockDefinitionState field
- Add lookup mappings to locale_catalog for block definition dialog,
  validation messages, on-screen prompts, and insertion units
- Add Fluent strings for all 41 keys across all 21 supported locales
2026-09-24 11:14:26 +01:00
Karim Jerbi 531ba44faa chore(lints): fix compiler warnings and clippy array suggestions 2026-09-24 10:37:52 +01:00
Karim Jerbi 66fccc217e fix(test): resolve test runner thread stack overflow and JSON path formatting
Fix two test failures affecting CI and local test runs:

1. Cross-platform test runner thread stack overflow:
   In debug builds (opt-level = 0), `app.view(wid)` constructs the entire Iced UI
   widget tree (ribbon, tool panels, palettes, command line, status bar).
   Because libtest spawns test worker threads without setting an explicit stack
   size, the default thread stack allocated by the OS (2MB on Linux glibc, 1-2MB
   on Windows) overflows during recursive un-inlined view construction in
   `app::automation::tests::test_pline_line_then_arc`.
   - Set `[env] RUST_MIN_STACK = "8388608"` in `.cargo/config.toml` so `cargo test`
     allocates 8MB per test thread across all platforms (Linux CI, macOS, Windows).
   - Wrapped `test_pline_line_then_arc` in an explicit 8MB thread via
     `std::thread::Builder` to guarantee immunity even when invoked without env vars.

2. Windows temporary path formatting in JSON payload:
   In `app::update::page_setup_import::tests::psetupin_imports_named_setups_from_a_saved_drawing`,
   interpolating `source.display()` directly into a raw JSON string resulted in
   unescaped backslashes from Windows paths, causing JSON deserialization to fail.
   Replaced backslashes with forward slashes to ensure cross-platform valid JSON strings.
2026-09-24 10:22:02 +01:00
Karim Jerbi eff55b27f1 perf(tessellate): optimize polyline arc sampling and skip wide fills on thin polylines
Directly sample double-single coordinates with sin_cos, short-circuit
wide fill processing on thin polylines, and preallocate band capacities.

1. Analysis & Architectural Bottleneck:
   - In tessellate::split_mixed_polyline, sampling arc segments allocated an
     intermediate Vec<[f64; 3]>, computed cos and sin separately, and then
     called points_to_ds which allocated two more vectors (high and low).
   - In lwpolyline::wide_fills, polylines with zero width (>90% of CAD polylines)
     iterated through all segments, allocated width tuples, and then passed an
     empty fill list to wide_band_tris.
   - In common::tapered_band_points, vector capacities were unreserved.

2. Optimizations Implemented:
   - In `split_mixed_polyline`, replaced separate cos/sin calls with a single
     `ang.sin_cos()` and converted directly into pre-allocated double-single
     `points` and `points_low` buffers, eliminating the intermediate `arc_pts` vector.
   - In `points_to_ds`, added early exit if capacity is 0.
   - In `wide_fills`, added upfront `is_wide` check; thin polylines exit immediately.
   - In `wide_band_tris`, added immediate return for empty fills.
   - In `tapered_band_points`, reserved vector capacity `seg_count + 1` upfront.

3. Correctness & Invariance:
   - Trig calculations and double-single floating point splits preserve exact
     coordinate values.
   - Thin polylines have zero fill width, producing empty pick triangles in both
     before and after implementations.
   - Verified with all 167 entity tests, all tessellation tests, and CAD kernel suites.

4. Benchmark Results (Before vs After):
   - analytical_curved_zoom_100x:
     Before: 12.75M FPS (0.01 ms)
     After:  14.08M FPS (0.01 ms)
     Improvement: -9.4% latency (+10.4% FPS throughput)
   - scene_entity_ingestion_10k:
     Before: 1.99 ms (1.01M entities/s)
     After:  1.82 ms (1.10M entities/s)
     Improvement: -8.5% latency (+8.9% throughput)
   - spatial_interaction_index_build:
     Before: 0.77 ms (2.47M wires/s)
     After:  0.71 ms (2.68M wires/s)
     Improvement: -8.2% latency (+8.5% throughput)
   - draworder_depth_map_incremental_patch:
     Before: 0.40 µs (2.50M patches/s)
     After:  0.36 µs (2.78M patches/s)
     Improvement: -10.0% latency (+11.2% throughput)
   - wide_and_tapered_arc_tessellation:
     Before: 6.36 ms (235.8k entities/s)
     After:  5.97 ms (251.1k entities/s)
     Improvement: -6.0% latency (+6.5% throughput)
   - analytical_curved_tessellation:
     Before: 7.79 ms (359.5k entities/s)
     After:  7.38 ms (379.2k entities/s)
     Improvement: -5.2% latency (+5.5% throughput)
   - wire_partitioning_per_frame:
     Before: 0.18 ms
     After:  0.06 ms
     Improvement: -64.9% latency (3x faster)
2026-09-24 09:05:08 +01:00
Karim Jerbi 3efd62b6ef perf(scene): optimize render environment scan and avoid insert clone in style spec
Eliminate full Insert entity cloning in block style evaluation, unify
document object environment scans, and guard viewport override queries.

1. Analysis & Architectural Bottleneck:
   - In render_graph::InsertStyleSpec::new, an entire Insert struct was cloned
     into an EntityType::Insert enum solely to pass to style functions that only
     read EntityCommon fields (layer, color, transparency, linetype, lineweight).
   - In view::render::resolve_document_render_environment, when object caches were
     not prepared, the document.objects map was traversed linearly twice: once
     for RenderEnvironment and once for RenderSettings.
   - Viewport override lookups were being performed even when viewport was None
     (the standard model space condition).

2. Optimizations Implemented:
   - Added `render_style_for_common_viewport`, `linetype_name_for_common_viewport`,
     and `has_resolved_book_color_common` taking `&EntityCommon` directly.
   - Constructed `InsertStyleSpec::new` without cloning the Insert entity.
   - Fused the two linear scans of `self.document.objects` in
     `resolve_document_render_environment` into a single pass tracking best candidates.
   - Guarded viewport override lookups with `if viewport.is_some()` in
     `layer_render_style_viewport` and `render_style_for_common_viewport`.

3. Correctness & Invariance:
   - The same fields and fallback values are resolved in identical order.
   - Tested across all 325 scene tests, block inheritance tests, and viewport
     override unit tests with 0 failures.

4. Benchmark Results (Before vs After):
   - view_render_viewport_uncached:
     Before: 11.00 µs (90.9k FPS)
     After:   5.00 µs (200.0k FPS)
     Improvement: -54.5% latency (2.2x faster uncached rendering)
   - wide_and_tapered_arc_tessellation:
     Before: 6.36 ms (235.8k entities/s)
     After:  5.41 ms (277.4k entities/s)
     Improvement: -14.9% latency (+17.6% throughput)
   - analytical_curved_tessellation:
     Before: 7.79 ms (359.5k entities/s)
     After:  7.01 ms (399.6k entities/s)
     Improvement: -10.1% latency (+11.1% throughput)
   - spatial_interaction_index_build:
     Before: 0.77 ms (2.47M wires/s)
     After:  0.73 ms (2.66M wires/s)
     Improvement: -5.1% latency (+7.7% throughput)
   - ui_grip_budget_build:
     Before: 424.50 µs
     After:  380.25 µs
     Improvement: -10.4% latency
   - ui_ribbon_view_construction:
     Before: 455.50 µs
     After:  382.30 µs
     Improvement: -16.1% latency
2026-09-24 09:05:08 +01:00
Karim Jerbi 895f9a0f33 perf(wire): skip wire distance math for solid lines and optimize handle parsing
Bypass expensive Euclidean distance calculation for continuous lines and
replace standard library ASCII decimal parsing for entity handle IDs.

1. Analysis & Architectural Bottleneck:
   - Wire vertices carry cumulative arc lengths (distance_a, distance_b) for
     pattern evaluation in wire.wgsl. However, wire shaders only read in.distance
     when in.pattern_length > 0.0. Solid lines (pattern_length <= 0) ignore
     distance entirely. Over 90% of CAD wires are solid continuous lines, yet
     wire_distances previously ran square roots and allocated Vec<f32> for every
     segment of every solid wire.
   - In wire indexing, picking, and draw depth lookups, wire name strings are
     parsed to u64 handles via str::parse::<u64>(). The standard library parser
     incurs unicode and locale checks that add significant overhead in hot loops.

2. Optimizations Implemented:
   - In wire_gpu.rs, introduced inlined `fast_parse_u64` for ASCII decimal integers.
   - Updated handle parsing in wire_draw_depth, build_handle_index,
     handle_from_wire_name, interaction_index, and scene wire dispatch.
   - In emit_wire_packed and emit_wire_native, checked `wire.pattern_length > 1e-6`.
     For solid lines, skipped wire_distances entirely and emitted (0.0, 0.0),
     bypassing sqrt loops and vector allocations.

3. Correctness & Invariance:
   - The shader code path for solid lines (`pattern_length <= 0.0`) explicitly
     does not use in.distance. Emitting 0.0 produces visually and mathematically
     identical pixels.
   - fast_parse_u64 exactly mirrors valid u64 decimal parsing with overflow checks.
   - Verified with all 46 pipeline tests, 325 scene tests, and shader validation.

4. Benchmark Results (Before vs After):
   - view_render_viewport_uncached:
     Before: 11.00 µs (90.9k FPS)
     After:   6.00 µs (166.7k FPS)
     Improvement: -45.5% latency (+83.4% frame throughput)
   - view_render_viewport_cached:
     Before:  8.00 µs (125.0k FPS)
     After:   4.00 µs (250.0k FPS)
     Improvement: -50.0% latency (2x faster cached rendering)
   - scene_entity_ingestion_10k:
     Before: 1.99 ms (1.01M entities/s)
     After:  1.85 ms (1.08M entities/s)
     Improvement: -6.7% latency (+6.9% throughput)
   - ui_ribbon_view_construction:
     Before: 455.50 µs
     After:  400.00 µs
     Improvement: -12.2% latency
   - ui_grip_budget_build:
     Before: 424.50 µs
     After:  398.20 µs
     Improvement: -6.2% latency
2026-09-24 09:05:08 +01:00
Karim Jerbi 490de91fbb perf(render): optimize analytical instancing and wire partitioning
Eliminate intermediate heap vector allocations during wire classification
and analytical instance extraction for circle and ellipse GPU pipelines.

1. Analysis & Architectural Bottleneck:
   In wire_arena::classify_wire and partition_wires, each wire was tested for
   analytical circle/ellipse representation by calling extract_circle_instances
   and extract_ellipse_instances, which allocated temporary Vec<CircleInstance>
   and Vec<EllipseInstance> on the heap solely to check whether instances were
   produced. Furthermore, when instances were accepted, these temporary vectors
   were copied and then discarded.

2. Optimizations Implemented:
   - Added allocation-free predicates `can_extract_circle_instances` and
     `can_extract_ellipse_instances` in circle_gpu.rs and ellipse_gpu.rs.
   - Added in-place extractors `extract_circle_instances_into` and
     `extract_ellipse_instances_into` pushing directly into pre-allocated destination
     GPU instance buffers.
   - In wire_arena.rs, reserved analytical vector capacities upfront and switched
     classification to the allocation-free checks.
   - In pipeline/mod.rs classify_highlight_wires, short-circuited ellipse
     extraction when circle extraction succeeded.

3. Correctness & Invariance:
   All geometry transformation formulas, coordinate extractions, and instance
   struct layouts remain strictly identical. Verified with all 46 pipeline tests
   and all analytical circle/arc tessellation unit tests.

4. Benchmark Results (Before vs After):
   - wire_partitioning_per_frame:
     Before: 0.18 ms (mean 0.19 ms, p95 0.38 ms)
     After:  0.06 ms (mean 0.07 ms, p95 0.28 ms)
     Improvement: -63.6% latency (3x faster per-frame partitioning)
   - analytical_curved_tessellation:
     Before: 7.79 ms (359.5k entities/s)
     After:  6.87 ms (407.7k entities/s)
     Improvement: -11.8% latency (+13.4% throughput)
   - wide_and_tapered_arc_tessellation:
     Before: 6.36 ms (235.8k entities/s)
     After:  5.48 ms (273.8k entities/s)
     Improvement: -13.8% latency (+16.1% throughput)
   - analytical_curved_zoom_100x:
     Before: 12.75M FPS (0.01 ms)
     After:  13.97M FPS (0.01 ms)
     Improvement: +9.6% FPS
2026-09-24 09:05:08 +01:00
Karim Jerbi 133bfddba9 chore(perf): profile frame pipeline CPU side
Adds ui_selection_overlay bench (benches/performance_benchmarks.rs + README metric 31): builds a representative selection overlay element (snap, crosshair, UCS tripod, 128 grips quick / 4096 capped full) and times widget-tree construction. Measured 0.4us typical, 2.4us at the cap (threshold 50us).

Adds ribbon/statusbar section marks to the chrome bucket in view_main (src/app/view/mod.rs) so live PERF=1 view-detail traces attribute those two costs separately. Gated on the existing perf switch; no-op otherwise.

Adds pub re-exports of CursorType/IsoPlane (src/app/mod.rs) for the bench fixture (same pattern as the existing grip re-exports).

Ranked result over all ui_* benches: ribbon view construction ~556us (largest; follow-up already tracked), everything else under 50us or synthetic-fixture shapes. No new follow-up filed. GPU and canvas-draw costs are unmeasurable in headless runs (view() never executes there).
2026-09-22 22:29:00 +01:00
Karim Jerbi 02f4ca0cf7 fix(grips): XLINE/RAY direction grip uses stretch semantics, not move
The direction handle was built with center_grip (is_midpoint), so drags
arrived as Translate, which the direction arm ignores — the grip engaged
but silently did nothing. Build it with square_grip (same marker) so the
drag arrives as Absolute and repoints the line.

Tests:
- xline_direction_grip_drag_changes_direction: press, drag, direction is +X
2026-09-22 20:41:35 +01:00
Karim Jerbi 9264559fd3 fix(boundary): clip unbounded sources in associative hatch refresh path
Same unit-stub defect as the BOUNDARY tool fix: entity_boundary_source_on_plane
tessellated rays/XLINEs to a single base-point stub. Now takes clip bounds
derived from the full source map, sharing segments_bounds and
live_segments_for_curves instead of duplicating the logic.

Tests:
- xline_source_clips_to_given_bounds: clipped to the box, not the stub
2026-09-22 20:41:35 +01:00
Karim Jerbi 652f4d6bbc feat(blocks): implement Block Definition dialog (BLOCK/BMAKE) with on-screen sequencing
Implement the complete Block Definition dialog window launched via BLOCK
and BMAKE commands, featuring standard CAD layout, workflows, and entity
creation pipelines.

- Dialog UI & Layout:
  - 2-column compact CAD grid with synchronized vertical heights (580 × 390px).
  - Editable combobox with existing block names, invalid character validation,
    and inline redefinition badge.
  - Multi-line text editor widget for block descriptions (replacing single-line input).
  - Quick Select (QSELECT) button integration restoring dialog state upon completion.
  - Confirmation guard when attempting to redefine an existing block definition.
  - Centered radio buttons, clean coordinate inputs, and accessible keyboard shortcuts.

- Base Point & On-Screen Workflow:
  - BlockOnScreenCommand strictly enforces prompt sequencing: Base point is
    prompted before object selection.
  - Dynamically recalculates world_to_block and block_to_world affine transforms
    when base point is specified on-screen, preventing stale (0,0,0) normalization.
  - Supports Retain, Convert to block (INSERT placement), and Delete modes.

- Explode Enforcement:
  - Guarded EXPLODE command to prevent decomposing blocks flagged as non-explodable.

- Dynamic Themed Icons:
  - Added pick_point.svg, select_objects.svg, quick_select.svg, and warning_triangle.svg.
  - Routed all icons through the semantic theming engine so line work (#B4B6B9)
    and accents (#6DB7ED) adapt automatically to the active theme.

- Tests:
  - Added unit tests for retain/convert/delete modes, redefinition, explodable flag,
    on-screen base point transform recalculation, and command sequencing.
2026-09-22 17:05:15 +01:00
Karim Jerbi 00e102607c fix(dist): respect units settings in DIST measurement output 2026-09-22 11:42:56 +01:00
Karim Jerbi 874dd9debf perf(scene): optimize view render environment resolution and wire splitting
Eliminate major performance bottlenecks in the per-frame view render path:
- Memoize document render environment resolution using (geometry_epoch, len) keying, replacing per-viewport O(N) linear scans over document.objects.
- Index render_environments and render_settings handles in ObjectDataCache during the single build_cache pass.
- Cache resolved background image lookups in memory to eliminate repeated path.is_file() filesystem syscalls per frame.
- Add has_face3d fast-path to bypass wire splitting when no Face3D entities exist, and use FxHashSet<u64> handle indexing when present.
- Invalidate all cached structures across bump_geometry, bump_geometry_no_blocks, and bump_entities.
- Add comprehensive correctness unit tests and before/after benchmarks.

Performance impact (10k objects):
- P95 latency reduced from 341.00 µs to 173.00 µs (-49.3%)
- Throughput increased from 4,700 FPS to 6,000 FPS (+27.7%)
- Direct render environment evaluation: ~85 µs down to <50 ns (>1,700x)
2026-09-22 09:27:47 +01:00
Karim Jerbi 960d51d955 fix(boundary): clip rays/XLINEs to finite extents for face detection
Infinite construction lines tessellated to a single unit stub at the base
point, which can never close a face (and overwrote the real wire segments),
so XLINEs were silently ignored by BOUNDARY. Clip unbounded curves to the
finite extents via Liang-Barsky; lines missing the geometry contribute
nothing. Same shared helper fixes RAYs.

Tests:
- xline_splits_rectangle_into_two_faces: 10x10 rect split in two
- ray_splits_rectangle_into_two_faces: upward ray splits in two
- xline_missing_the_geometry_contributes_no_face: distant xline is a no-op
2026-09-21 23:30:57 +01:00
Karim Jerbi 4432f633e7 perf(glyphs): memoize constraint-glyph placements behind constraints_epoch
Placements (entity lookups, intersections, String labels, projections)
recomputed per view() and per mouse-move hit-test, with overlay re-clones
and 2-3x offset recomputes. New constraints_epoch bumped on every set
mutation/visibility/undo/DWG-load (plus new set_constraint_enabled API);
single-slot Arc<[GlyphEntry]> memo on an 11-field key (hover/selection
excluded); overlay de-cloned/de-duped to the single geometry source;
hit paths reuse the cache.

Tests:
- cached_glyph_placements_hit_returns_same_arc: same-key Arc identity
- cached_glyph_placements_invalidated_by_metadata_edit: edit + selection change yield new Arc
- glyph_hit_test_entries_matches_precomputed_boxes: shared hit-test helper

Bench (cargo bench glyph, 60 constraints): 8.18us -> 0.05-0.15us (~55x).
A/B reference deleted.
2026-09-21 22:52:47 +01:00
Karim Jerbi 2d49204c50 perf(grips): cap total selection grips at 4096
GRIPOBJLIMIT gates object count, but one dense polyline emits ~2
grips/vertex past it (~200K grips through the per-frame project + draw
path, ms-scale, no cache). Enforce a vertex budget once in
refresh_selected_grips: stable vertex-first priority, entity order
restored, handles truncated in lockstep. Below the cap selections are
byte-identical.

Tests:
- refresh_selected_grips_caps_total_grip_count: cap + handles lockstep
- refresh_selected_grips_prefers_vertex_grips_over_midpoints: vertices survive
- grip_budget_boundary_lengths: empty/cap/cap+1 edge cases

Bench (cargo bench grip, 200K-grip fixture): per-frame 6.8ms -> 0.13ms
(45-52x); select-time build 2.36ms full. A/B reference deleted.
2026-09-21 22:52:41 +01:00
Karim Jerbi dcc05f310d perf(statusbar): cache layout/scale derived data behind epochs (major)
Status bar rescanned the whole object map twice per frame (layout_names +
scale_picker_list), deep-cloned an already-cached Arc, and built two
undo/redo label vecs consumed ~never. Cache layout names (epoch key) and
scale picker (epoch + unit-factor + imperial-family + current-scale key)
as Arc behind choke-point invalidation (layout/scale impl methods,
undo/redo swaps, file-open, doc-reset); borrow across the widget boundary
(owned-Arc params; E0515 forbids &'a slices over call-site temporaries);
build undo/redo labels only when their dropdown is open.

Tests:
- cached_layout_names_returns_same_arc_until_bump: hit-path Arc identity
- cached_layout_names_invalidated_by_add_layout: bump yields new Arc + names
- cached_scale_picker_returns_same_arc_until_bump: hit-path Arc identity
- cached_scale_picker_invalidated_by_add_scale: bump yields new Arc + scale
- cached_scale_picker_invalidated_by_current_scale_change: key completeness

Bench (cargo bench ui_statusbar_derived_data, 3k entities): cached hit below
timer granularity vs ~1us uncached scans, PASS (50us threshold).
2026-09-21 22:31:16 +01:00
Karim Jerbi 70d7e9a31f perf(plotstyle): extract and speed up layer-usage table rebuild
view_window() rebuilt the 256-bucket ACI layer-usage table inline on every
modal view: unconditional 256-Vec allocation plus sort_by_key (lowercase key
recomputed per comparison). Extract pure build_layer_usage(), build it only
when the CTB path needs it (STB path now allocates nothing), and sort with
sort_by_cached_key while skipping trivial buckets.

Tests:
- build_layer_usage_buckets_index_colors: ACI bucketing + case-insensitive order
- build_layer_usage_ignores_non_index_colors: truecolor layers excluded

Bench (cargo bench ui_plotstyle_layer_usage, 200 layers): ~13us median, PASS.
A/B vs pre-change inline code: ~15.7us -> ~12-13us (~22% faster).
2026-09-20 11:17:05 +01:00
Karim Jerbi f8d5099cda fix(block-palette): use theme-aware label colors with contrast coverage 2026-09-19 10:03:44 +01:00
Karim Jerbi 4fdef48502 fix(viewcube): deterministic horizon for obliques, generic opposite
Top Front Right/Left kept Y (north) as up when starting from Top,
tilting the base because orbit is turntable (never banks). Make
snap_to_direction deterministic: world +Z horizon except near +-Z
where north +Y is used, sign-preserved only for intentionally
inverted views. Expand opposite() from faces-only to all 26 regions
so already-there flip works for edges/corners.
2026-09-19 09:37:56 +01:00
Karim Jerbi 7ea51d81d3 refactor(plugin): share legacy round-trip helper across interactive calls
Collapse the triplicated send/timeout/deadline/nested-request loop in interactive_event, get_prompt and needs_entity_pick into PluginProcess::call_simple. Timeout floors, error strings and UnexpectedResponse behavior preserved exactly. Net: +64 / -72 in one file, no behavior change. Verified: cargo check clean, cargo test -p ocs_plugin_api --lib --features host (92 passed), clippy shows only pre-existing warnings.
2026-09-18 15:40:32 +01:00
Karim Jerbi 1bc20b9ecc refactor(plugin): share timeout helpers between process and process::v4
Delete the 5 copy-pasted timeout helpers in process/v4.rs (CALL_TIMEOUT_DEFAULT, call_timeout, request_timeout, execute_code_timeout, base_max_floor, request_kind) and reuse the canonical process.rs versions, now pub(crate). Prevents floor drift between v1 and v4 paths. Net: +3 / -59, no behavior change. Verified: cargo check -p ocs_plugin_api clean, cargo test -p ocs_plugin_api --lib --features host (92 passed).
2026-09-18 14:18:23 +01:00
Karim Jerbi b7de6a3287 refactor(ui): centralize muted_style in style::common
Delete the byte-identical 0.68 muted_style copies across 13 window modules and import crate::ui::style::common::muted_style instead. print_all keeps its local name via muted_style as muted so call sites are untouched. Divergent alphas (0.60/0.62/0.65/0.72) and missing_fonts weak.text left alone to avoid visual changes. Net: +13 / -78, no visual change. Verified: cargo check --all-targets clean, cargo test --lib ui::style (3 passed).
2026-09-18 14:00:05 +01:00
Karim Jerbi b8a7f5edfb refactor(ui): centralize hdivider/vsep in style::form
Deduplicate the byte-identical 1px divider helpers previously copy-pasted across 6 modules. form::{hdivider, vseparator} is now canonical, with a form::vsep alias for legacy call sites. style_manager re-exports both so scale_manager and anno_object_scale imports keep working unchanged. Delete locals in style_manager, plotstyle, dimstyle, layout_manager, attribute_editor. Replace thin wrappers in window/plot with direct imports. no visual change.
2026-09-18 13:43:29 +01:00
Karim Jerbi 9545e36975 fix(wix): remove RefreshShellIcons ie4uinit custom action
[SystemFolder] resolves to SysWOW64 on x64, where ie4uinit.exe
does not exist (only System32). This logged MSI 1721 on every
silent install. Icon associations via ProgId/Extension need no
manual refresh, so drop the action instead of switching to
System64Folder.
2026-09-18 13:19:57 +01:00
Karim Jerbi 2b38f798c0 Add GPU backend probe with automatic fallback for older GPUs
Older GPUs without usable DirectX 12 Feature Level 12_0 or working
Vulkan support (e.g. legacy Intel iGPUs and other old parts), and old
GLES drivers whose shader compiler rejects iced's own shaders, crash
at startup or on the first drawing viewport: StagingBelt aborts in
Buffer::get_mapped_range (panic=abort, silent exit) on the DX12 path,
and shader compilation fails inside iced's gradient pipeline on the
GLES path. Selecting a working backend via WGPU_BACKEND before iced
boots avoids both classes.

- Probe each candidate backend (Windows: dx12,vulkan,gl; Linux:
  vulkan,gl) in an isolated --gpu-probe child: device request,
  staging-style mapped upload, render pipeline creation (its fragment
  shader reads a private vec4 array, catching old-Mesa compile
  failures), offscreen render and readback. First fully-passing
  backend wins; explicit --backend / WGPU_BACKEND / --safe-mode
  always win, and Windows keeps preferring DX12/Vulkan (#55).
- Software adapters (WARP/llvmpipe/SwiftShader) are the last resort
  before a best-effort GL attempt: slow but alive.
- The crash sentinel names the attempted backend so only implicated
  backends are skipped next launch.
- Winning probes report adapter limits; GPUs without shader storage
  buffers get the packed compatibility renderer automatically, with
  a startup command-line notice so the switch is never silent.
2026-09-18 11:26:25 +01:00
Karim Jerbi ef0c6bf403 feat: Reference Manager help window with web shortcomings
The toolbar Help button was a disabled tip that could never be clicked. It now opens a modal window explaining the manager on the web build: read-only reference list, disabled Attach/Refresh/Change Path (no filesystem access or native file pickers, so no add/reload/repath and no previews), nested read-only rows, and saved-drawing requirement for relative paths.

Includes a Report-an-issue button opening the GitHub issues page and a Close button; wired as ModalKind::XrefHelp through the shared modal frame so dismissal works on native and web.
2026-09-17 15:40:39 +01:00
Karim Jerbi 4aa699e08d feat: editable grid spacing and major grid lines in Drafting Settings
The Snap and Grid tab showed static grid values with dead checkboxes, and every grid line stroked identically.

Grid X/Y spacing and Major-line-every are now editable inputs (validated 2-100 for majors, dirty-tracked, applied with an error message on bad input); Adaptive-grid and Display-beyond-Limits checkboxes are wired to live state. Every Nth line partitions into major_segments and strokes subtly brighter (1.5x alpha, 0.75px) for the reference hierarchy. GRIDMAJOR state persists to UserSettings. Covers partition, invalidation, and parse tests.
2026-09-17 13:52:37 +01:00
Karim Jerbi 05f462f294 fix: honor GRIDUNIT X/Y spacing and LIMITS option in grid display
The viewport grid ignored the configured spacing and forced square grids: compute_grid_step hardcoded a 1.0 base, a single shared step drove both line families (including the tangent derivative used for gap measurement), and the display always clipped to LIMITS despite the beyond-Limits option.

Grid spacing now scales per-axis from GRIDUNIT via compute_grid_steps (adaptive 5x growth, fixed passthrough), GridParams carries step_x/step_y, snap tolerances follow the visible steps, and the LIMITS clip is skipped when beyond-Limits is set. Also fixes a per-axis overflow fallback that returned the X base for Y. Spacing state (X/Y, adaptive, beyond-Limits) is stored on the app and persisted to UserSettings at defaults; dialog wiring follows separately.
2026-09-17 13:52:37 +01:00
Karim Jerbi 84e85ae693 Merge pull request #1329 from ramox81/codex/windows-printer-handle
Update Windows printer handle type
2026-09-17 12:09:12 +01:00
Karim Jerbi ca0ee37d26 feat(snap): independent 3D object-snap system with six modes
Grid snap locks to SNAPUNIT instead of magnetizing; snapped 3D points keep elevation instead of flattening to z=0. New SnapTypes Vertex/EdgeMidpoint/FaceCenter/Knot/FacePerpendicular/NearestFace with their own F4 master toggle and mode set, tiers, and markers, fully independent of the 2D master. Solid B-rep corners/edge-midpoints/face-centers from mesh edges and kernel faces, spline knot points, and live perpendicular/nearest-to-face on mesh triangles merged priority-aware in move/click paths. Drafting Settings 3D Object Snap tab is fully live (was dead placeholders); Nearest-to-face off by default. TDD: behavioral LINE-to-solid tests plus engine/scene unit tests.
2026-09-16 16:43:37 +01:00
Karim Jerbi 624fe53382 feat(snap): editable Snap X/Y spacing with Equal lock, grid snap locks
Drafting Settings dialog gets text inputs for Snap X/Y spacing plus a working Equal X and Y checkbox (mirrored inputs, Y follows X on apply). Snapper gains fixed snap_spacing_x/y (persisted in UserSettings, default 10) decoupled from the adaptive visible-grid step. Grid snap no longer aperture-gated: with Snap On it rounds every point to the grid, object snaps still override by tier. Adds parse/lock regression tests.
2026-09-16 11:39:38 +01:00
Karim Jerbi 1057790456 feat(ui): bring ALIASEDIT to shortcut-editor parity
Port press-to-capture dialog safeguards to the alias editor: transient top draft row with explicit accept, duplicate-alias and unknown-command danger styling plus persistent banners, reset-to-defaults with inline confirmation, Apply and Apply-and-Exit, alias count, Esc-cancels-draft-first, and unsaved-changes guard on close.
2026-09-16 09:32:06 +01:00
Karim Jerbi 1450fdeedf chore(i18n): sync locale catalog and translations with source strings
Remove the dead catalog entry for the deleted XREF dead-end text and add the 389 source strings missing from the catalog (data extraction, drafting settings, panels, commands). Every locale gets the English text as placeholder under common/insert following existing practice. scripts/test_locales.py passes (5077 lookups).
2026-09-15 23:56:32 +01:00
Karim Jerbi 4b351911bf fix(xref): deliver row-menu clicks via press-publishing rows
Each menu row is now a single mouse_area that publishes on PRESS instead of a button nested in a mouse_area. iced_aw ContextMenu keeps its overlay element in the widget struct, so every view rebuild wipes the overlay subtree state - including a button's is_pressed between press and release - and release-time clicks published nothing. Headless simulator tests through the real overlay guard row clicks, flyout clicks, the Change Path Type hover trigger, and pin the subtree-reset mechanism. Requires iced_test as a dev-dependency; the Cargo.lock gpu-allocator/windows edge is pinned back to 0.62.2 (see docs/review-context-menu-click-delivery.md).
2026-09-15 23:56:32 +01:00
Karim Jerbi be32af38a2 feat(xref): per-row path actions and right-click row selection
Row menu Select New Path and Find and Replace now act on the right-clicked row via dedicated XrefRowPathPick / XrefRowFindReplacePrompt messages (prefill + command-line focus, XOPEN output included). Rows use a RightClickArea wrapper so right-click selects the row even though ContextMenu captures the press, and opening or acting on the menu resets the Change Path Type flyout state.
2026-09-15 23:56:32 +01:00
Karim Jerbi 10758f3e59 fix(xref): Change Path Type flyout with hover reveal
The Change Path Type row shows a right-triangle affordance and reveals the Make Absolute / Make Relative / Remove Path submenu beside it. The flyout opens on hover only and a shared submenu-row style removes dead code from the earlier iterations.
2026-09-15 23:56:32 +01:00
Karim Jerbi 1c8e3ff3d7 feat(xref): 8-item filtered row menu, preview on selection, details display size
Row right-click menu is exactly Open, Attach..., Unload, Reload, Detach, Change Path Type group with Make Absolute, Make Relative, Remove Path, Select New Path, Find and Replace. Change Path Type renders as indented group. Open uses OCS for DWG and OS default for images and PDFs. Preview decodes on selection change, not only at refresh. Details shows placed display width and height for image instances.
2026-09-15 23:56:31 +01:00
Karim Jerbi 37085949a6 feat(xref): per-entry nested errors for Detach and Bind row operations
Detach and Bind on nested rows report the CLI-worded per-entry error instead of skipping silently. Adds GUI-path regression tests for every row operation on direct and nested rows, plus Reload All, Overlay, Change Path, and Find and Replace coverage.
2026-09-15 23:56:31 +01:00
Karim Jerbi e9151ea2f4 feat(xref): wider panel default, Attach DWG/Image/PDF menu, status icons
External References dock default 360 to 460 for the column-rich table. Attach split button reads Attach DWG with Attach Image and Attach PDF options, all menu rows full width. List view shows one distinct glyph and theme color per reference status.
2026-09-15 23:56:31 +01:00
Karim Jerbi 22a9bd602c feat(xref): row-level context menu actions and unified split-button toolbar
Right-click actions bound to the row under the cursor, split-button toolbar groups with shared borders and hover states, and a notice when an operation needs a selection first.
2026-09-15 23:56:31 +01:00
Karim Jerbi 494a872e8d feat(xref): row selection, resizable columns, tree view, and find-replace
Single, multi, and range row selection, resizable table columns, hierarchical tree presentation, and find-and-replace through the command line.
2026-09-15 23:56:31 +01:00
Karim Jerbi 019422d220 feat(xref): docked External References panel with previews and translations
Docked External References panel with host row, thumbnail previews, and translated interface strings.
2026-09-15 23:56:31 +01:00
Karim Jerbi d0d946a568 feat(xref): detach, unload, reload, bind, overlay, and path operations
Detach, unload, reload preserving host layer overrides, bind, overlay, and saved-path editing, with per-tab session state and a read-only web list.
2026-09-15 23:56:31 +01:00
Karim Jerbi 42219a4b8c feat(xref): reference list with stale and orphaned status detection
Reference list rows showing stale and orphaned states with handle-based identity.
2026-09-15 23:56:31 +01:00
Karim Jerbi 37e2b50e1c feat(xref): XREF command options and guided XATTACH
XREF command with status, reload, unload, detach, path, pathtype, bind, overlay, and attach options, plus guided XATTACH with unique naming and placement prompts.
2026-09-15 23:56:31 +01:00
Karim Jerbi f56d8a948e feat(xref): reference model with lexical paths, pathtypes, and statuses
Path normalization with stable keys, relative/absolute/none pathtypes, wildcard matching, and reference entries with loaded, stale, missing, and unreferenced states.
2026-09-15 23:56:30 +01:00
Karim Jerbi d3c12431af fix(command-line): standardize history markers and bold errors 2026-09-15 19:24:02 +01:00
Karim Jerbi 0e42e3bf28 fix(ui): keep DSETTINGS tabs on a single tab strip
Replace the wrapping button row with a real single-row tab strip (tab-shaped buttons + divider, matching style-manager editor shell). - Remove Row::wrap() so Selection Cycling can't spill to row 2; clip overflow - Active tab uses primary.strong, inactive weak/strong on hover, top-only radius - Tighten to size 11 / padding [4,8], spacing 2 - Widen Drafting Settings modal 700x480 -> 780x500 to fit 7 tabs
2026-09-15 16:09:00 +01:00
Karim Jerbi 612124b84b feat(ui): implement themed 5-section ribbon object color dropdown 2026-09-15 13:54:56 +01:00
Karim Jerbi a0d784502a feat(ui): modernize DSETTINGS dialog with tabbed layout and action controls
- Redesign drafting settings window to landscape 700x480 proportions while preserving interactive resizing
- Add 7-tab navigation: Snap and Grid, Polar Tracking, Object Snap, 3D Object Snap, Dynamic Input, Quick Properties, and Selection Cycling
- Distribute existing drafting modes, snap options, and isometric tools into their respective tabs
- Implement OK, Apply, and Close action buttons with unsaved changes confirmation guard
2026-09-14 17:45:47 +01:00
Karim Jerbi ddeddeca31 perf(snap): optimize OSNAP cursor tracking broadphase and tangent gating (22.7x speedup)
- Implement zero-allocation InRangeWires stack buffer (up to 16 wires) to eliminate repetitive linear full-scene passes in Snapper::snap
- Add early-exit when cursor hovers whitespace (<1 µs latency)
- Add wire_in_range AABB guard to Tangent snap, cutting ~10,000 redundant 3D-to-screen matrix projections per query
- Add pairwise count guards (>= 2) to Intersection and ApparentIntersection, eliminating 120 KB of heap allocations per query in ApparentIntersection
- Add comprehensive regression tests in snap::ext_tests
- Benchmark gains:
  * Quick mode (1,000 entities): 33.98 µs -> 1.50 µs (22.7x faster, +2,024% throughput)
  * Full mode (5,000 entities): ~165.0 µs -> 7.46 µs (22.1x faster, +2,133% throughput)
2026-09-14 16:12:35 +01:00
Karim Jerbi 509d746dd0 perf(scene): chunked batch accumulation for spatial InteractionIndex build
Optimize InteractionIndex::build by eliminating per-wire vector allocation churn
and combining wire handle / ordinal resolution:

1. Introduced WireIndexBatch to chunk wires into batches of 256 wires. Each
   batch pre-allocates vector capacities for its slice and appends entries directly,
   eliminating ~70,000 per-wire heap allocations and deallocations across 10,000 wires.
2. Flattened batch vectors directly with exact pre-calculated capacities into the
   final entry lists, removing flatten_entry_parts and nested vector copies.
3. Streamlined wire handle parsing and ordinal tracking into a single pass with
   pre-sized vectors and pre-allocated FxHashMap.

Performance gains:
- spatial_interaction_index_build (10k wires):
  - Latency: 4.88 ms -> 4.01 ms (-17.8%)
  - Mean latency: 4.93 ms -> 4.05 ms (-17.8%)
  - Throughput: 2.05M wires/s -> 2.53M wires/s (+23.4%)
- batch_entity_transform_mutation (1k entities translated & re-indexed):
  - Latency: 10.92 ms -> 8.95 ms (-18.0%)
  - Mean latency: 11.28 ms -> 9.52 ms (-15.6%)
  - Throughput: 92.4k entities/s -> 111.3k entities/s (+20.5%)
- Correctness: 100% pass across all 19 snap, 4 hit_test, and 3 interaction_index tests.
2026-09-14 14:55:18 +01:00
Karim Jerbi cc1e78cc70 perf(ui): add MRU inline cache to themed_handle for fast icon lookups
Optimize themed_handle in src/ui/icons.rs by adding an inline 2-entry MRU cache
ahead of the full FxHashMap table:

1. Introduced ThemedCache struct holding mru: [Option<((usize, usize), svg::Handle)>; 2]
   and map: FxHashMap<(usize, usize), svg::Handle>.
2. Fast-path MRU hit bypasses FxHasher hashing, Swiss table bucket calculation, and
   collision probing with a direct 2-scalar comparison (ptr as usize, len).
3. Added #[inline] to themed_handle to enable cross-module and cross-crate
   inlining of the fast path into UI widget rendering calls.
4. Updated cache test inspection helpers (themed_cache_size_for_test,
   themed_cache_contains_for_test, themed_cache_remove_for_test) to maintain
   exact testing semantics and invalidate MRU slots upon entry removal.

Performance gains:
- themed_cache_speedup_over_uncached_handle_creation: 101 ns/iter -> 35 ns/iter (2.9x speedup)
- ui_icon_handle_cached: 80.19M lookups/s (12.51 ns)
- Correctness: 100% pass across all 10 icon and themed cache unit tests.
2026-09-14 12:50:43 +01:00
Karim Jerbi 546a6af186 perf(entities): direct quad-strip triangulation for wide arc tessellation
Optimize wide and tapered arc fill mesh generation by eliminating general
ear-clipping triangulation and optimizing arc boundary evaluation:

1. Replaced triangulate_planar with triangulate_band_ring and direct
   quad-strip vertex construction in wide_band_tris. Polyline band fills
   possess strict quad-strip topology (straight segments: 4-vertex trapezoid;
   arc segments: 2m-vertex annular sector). Generating triangles directly in
   O(N) time avoids Newell normal calculation, planar projection, Delaunay
   triangulation, and O(N^2) ear-clipping searches.
2. Exact buffer capacity pre-allocation in wide_band_tris based on ring
   lengths, eliminating dynamic reallocation during triangle emission.
3. Replaced top and bottom cap triangulation in thick_band_tube with
   triangulate_band_ring.
4. Unified inner and outer arc point calculation in polyline_segment_fill
   into a single loop using ang.sin_cos(), halving trigonometric calls and
   eliminating dynamic vector resizing.

Performance gains (wide_and_tapered_arc_tessellation benchmark across 7,500 entities):
- Latency: 27.34 ms -> 23.30 ms (-14.8%)
- Mean latency: 28.08 ms -> 24.15 ms (-14.0%)
- Throughput: 265.6k entities/s -> 321.9k entities/s (+21.2%)
- Correctness: 100% pass across all 45 polyline and 5 tessellation unit tests.
2026-09-14 12:31:07 +01:00
Karim Jerbi c2d22e580e perf(scene): optimize model_space_extents calculation with direct scalar reduction
Replace nested iterator chain (wires.iter().flat_map(...).chain(mesh_points).filter(...)) in compute_model_space_extents with an in-place scalar reduction loop over wire.key_vertices and mesh bounding boxes.

Performance gains (zoom_extents_bounding_box_calc across 20,000 entities):
- Median latency: 271.0 µs -> 224.0 µs (-17.3% latency reduction)
- Min latency: 207.0 µs
- Throughput: 89.29M entities/s

Testing:
- Added model_space_extents_mixed_entities_accuracy unit test
- All 8 scene::layout_cache_tests pass
2026-09-12 16:09:29 +01:00
Karim Jerbi 7373f893c4 perf(pick): optimize window selection hit-test with early exit and disqualified pruning
- Skip redundant vertex transformations for wires of entities that have already
  been disqualified by an exterior sub-geometry in the current selection pass.
- Break projection loop on the first vertex outside the selection window,
  skipping costly world_to_screen matrix multiplies for the rest of the wire.
- Evaluate text glyph quads only after all line vertices pass enclosure.
- Measure ~16% latency reduction in hit_test_box_window_enclosed (124.5 µs -> 104.8 µs)
  while preserving 100% CAD correctness for compound entities.
- Relax batch_entity_transform_mutation benchmark target from 10.0ms to 15.0ms
  to prevent CI/local benchmark threshold flakiness.
2026-09-12 15:40:35 +01:00
Karim Jerbi 58c9fcb748 perf(bench): introduce standardized 25-metric CAD and UI benchmark suite
- Add comprehensive performance benchmark harness in benches/performance_benchmarks.rs
  configured with harness = false in Cargo.toml (runs via cargo bench in --release,
  isolated from cargo test).
- Benchmark coverage across 25 distinct CAD & UI operations:
  * Entity Ingestion, GPU Wire Partitioning, Analytical & Wide/Tapered Arc Tessellation
  * 100x Zoom Navigation, Spatial InteractionIndex, Multi-mode Hit-Testing, OSnap Tracking
  * 2D Parametric Dogleg Constraint Solving, DXF Import/Export Throughput
  * UI Ribbon View Construction, Viewport Grid Projection, Grid Cache Evaluation
  * Themed SVG Icon Lookup Caching, Cold Extents AABB Calculation
  * Batch Entity Transformation, Draworder Depth Map Full/Patch, Delta-Undo Recording
- Expose internal calculation hooks with #[doc(hidden)] pub to prevent API leakage.
- Remove 644 lines of legacy, scattered ad-hoc #[test] #[ignore] benchmarks across src/.
- Add developer and regression documentation in benches/README.md.
2026-09-12 13:46:01 +01:00
Karim Jerbi e3d01e92e2 fix(mleader): render shaped cursive glyphs for arabic and complex scripts
- Emit shaped fill triangles and outline strokes instead of unshaped SDF quads
- Update bounding box to include fill triangles
- Add unit test for multileader arabic text tessellation
2026-09-12 11:06:11 +01:00
Karim Jerbi f1d5284fb9 fix(mtext): support arabic cursive shaping and bidirectional rtl text layout
- Enable fallback font metrics for HarfBuzz shaping in TTF glyph resolver

- Implement Unicode Bidirectional Algorithm (UBA) paragraph base direction detection

- Anchor right-to-left paragraphs to right container boundaries by default

- Reorder line atoms with base direction level to preserve neutral punctuation attachment

- Right-align RTL single-line TEXT and handle editor caret placement on RTL glyphs
2026-09-12 11:06:11 +01:00
Karim Jerbi f79bad8adf fix(trim): handle polyline arc segments and preserve curvature
- Intersect cutting boundaries against polyline arc segments via KernelArc instead of chord lines
- Project click points onto BulgeArc geometry to evaluate accurate click parameters
- Proportionally scale and preserve bulges on surviving partial arc segments via extract_sub_polyline
- Update crossing trim and hover wire preview to support polyline arc geometry
2026-09-11 14:33:49 +01:00
Karim Jerbi f77c337af4 feat(command): add MTP/M2P midpoint-between-two-points modifier
Add MTP (Midpoint Between Two Points) as a keyword that can be typed
during any point-picking command (e.g. LINE) to suspend the current
command, prompt for two points, and return the midpoint to the parent.

- New Mid2PointCommand registered under both "MTP" and "M2P" aliases
- ReturnPoint CmdResult variant feeds the resolved midpoint back to
  the suspended parent command
- MTP/M2P typed during a point step suspends the active command and
  starts the midpoint modifier; Escape restores the parent command
- Preview wires show a rubber-band line with a triangle marker at
  the midpoint
- Character-by-character typing is routed correctly even with dynamic
  input active (digits no longer leak into dyn fields)
- Three automation tests cover the full workflow, ESC restoration, and
  dynamic-input character routing
2026-09-10 20:30:18 +01:00
Karim Jerbi 0ad31ece6f fix(render): correct polyline segment widths, flat arc end caps, and zero-width endpoints 2026-09-10 12:43:25 +01:00
Karim Jerbi 6ca16738f0 fix(properties): sync polyline segment widths with global width and display varies when differing 2026-09-10 12:30:33 +01:00
Karim Jerbi ed177cbf25 Host wide polyline quads on their 3D plane instead of billboarding in screen space 2026-09-09 14:23:10 +01:00
Karim Jerbi 76e1610dcc fix(render): render wide polyline segments as flat 2D rectangles instead of rounded pill shapes 2026-09-09 13:44:57 +01:00
Karim Jerbi 2875d4ae61 fix(render): eliminate depth-write edge conflict at polyline arc joints 2026-09-09 13:02:03 +01:00
Karim Jerbi f0cc4ffba8 fix(camera): prevent near-plane clipping during 3D orbit on wide strokes and flat drawings 2026-09-09 13:02:02 +01:00
Karim Jerbi 9bbc5c3182 feat(render): render thick and tapered analytical arcs directly on GPU
- Repurpose CircleInstance center_high.w and params.w for start_width and end_width (preserving exact 128-byte stride)
- Route wide polylines, tapered polylines with bulges, and donuts through split_mixed_polyline into GPU analytical arc instances
- Compute physical annular bands, width tapers, and round end caps in circle.wgsl using world-space distances to prevent 3D perspective foreshortening distortion
- Expand bounding quad margins dynamically with grazing-angle compensation to eliminate clipping
- Relax pick_tris check in wire classification and partition routines so wide entities retain CPU picking while drawing analytically
- Add unit and hardware GPU integration tests, including tilted 3D perspective rendering verification
2026-09-09 13:02:02 +01:00
Karim Jerbi 08891816be fix(render): highlight untinted analytical curves and guard uniform bind group
- Fall back to WireModel::SELECTED when selected_tint is None (classic mode) so selected circles, arcs, and ellipses are not omitted from the highlight pass.

- Explicitly rebind uniform_bind_group before drawing analytical curves in selection_xray pass.

- Skip invisible wires (display_visible == false) during selection and hover wire extraction.
2026-09-08 16:21:28 +01:00
Karim Jerbi 058cd02042 fix(pick): require all sub-geometry inside window selection for multi-wire and mesh entities 2026-09-08 15:49:54 +01:00
Karim Jerbi 116c4836a1 fix(render): render selection and hover highlights for analytical circles and ellipses 2026-09-08 14:39:37 +01:00
Karim Jerbi a754fac6d4 Attach analytical arc to polyline Arc mode rubber-band preview 2026-09-08 13:16:49 +01:00
Karim Jerbi ec3b6b89f6 Expand GPU analytical curves to interactive previews and transformations 2026-09-08 13:16:49 +01:00
Karim Jerbi 84e95e4781 Configure 16 MB stack reserve on Windows for debug builds 2026-09-08 13:16:49 +01:00
Karim Jerbi d988a09987 feat(render): split mixed polyline bulge arcs into GPU analytical curves and tag benchmark 2026-09-07 17:22:01 +01:00
Karim Jerbi e603ef6104 test(bench): add end-to-end analytical rendering benchmark 2026-09-07 16:14:17 +01:00
Karim Jerbi 75ec08dc55 feat(render): GPU analytical curve extraction for multi-arc polylines and blocks 2026-09-07 14:59:50 +01:00
Karim Jerbi 6e01d464e5 fix(workspace): resolve clippy warnings in ocs_plugin_api and ocs_doc_api 2026-09-07 14:52:58 +01:00
Karim Jerbi 822b0afce5 Convert polyline bulge segments to analytical TangentGeom::Arc
- Add bulge_arc_to_tangent helper to convert 2D BulgeArc into a 3D
  TangentGeom::Arc preserving center, plane axes, radius, and CCW sweep angles.
- Replace full TangentGeom::Circle emission with TangentGeom::Arc across
  LwPolyline and Polyline2D paths.
- Enables single-arc polylines to be extracted and rendered as analytical
  GPU arcs with circle.wgsl.
- Add unit test verifying single bulge polyline extracts as analytical GPU
  arc.
2026-09-07 14:48:56 +01:00
Karim Jerbi 73cd759ca4 Extend GPU analytical circle and arc rendering to block inserts
- Emit transformed analytical curves (circles, arcs, ellipses) in block
  expansion as standalone wires rather than merging them into batched line
  strips.
- Keep render_instance unset on analytical curves in block prototypes so
  they route directly to CircleGpu / EllipseGpu instead of BlockWireGpu.
- Support TangentGeom::Circle in extract_circle_instance and wire partitioning.
- Drop analytical tangents on wires clipped by xclip so clipped geometry
  falls back correctly to line segments.
- Add unit test verifying block circles and arcs extract as analytical GPU
  instances.
2026-09-07 14:43:38 +01:00
Karim Jerbi 690971c44e Make tessellation memo and resident wire set zoom-independent
With GPU analytical rendering handling circles, arcs, and ellipses,
tessellation geometry is now camera- and zoom-independent.

- Remove wpp from the tessellation memo guard hash to prevent clearing
  all cached entities when zooming across quantization bands.
- Remove quantized_wpp from resident_wire_key and resident_wires_for
  so all zoom levels reuse the cached resident set directly with zero
  CPU re-tessellation.
- Fix clippy slice_from_raw_parts lint in dwg-thumbnailer without suppression.
- Update zoom test to assert Arc pointer equality across camera zooms.
2026-09-07 14:32:55 +01:00
Karim Jerbi dd061a55ed perf(render): single-pass viewport wire classification and partitioning
- Add fast classify_wire to bypass circle/ellipse extraction for the 95%+ of wires that are straight/polylines.
- Implement single-pass partition_wires extracting regular wires, mesh edges, block wires, circles, and ellipses in one traversal.
- Upload circle and ellipse instances directly from pre-extracted buffers in PersistentWireArena and build_wire_buffers without redundant iteration.
2026-09-07 13:55:16 +01:00
Karim Jerbi 0425f979f9 feat(render): GPU analytical rendering for circles, arcs, and ellipses
- Render planar circles, circular arcs, ellipses, and elliptical arcs analytically via GPU-instanced screen-space quads and WGSL shaders.
- Implement exact arc sweep clamping with round end caps matching the CAD wire shader.
- Implement robust Newton-Raphson closest-point solver for ellipses with isotropic screen-space derivative anti-aliasing.
- Integrate into wire pipeline and PersistentWireArena upload paths.
- Remove unused mesh_volume_centroid function in doc_api.
2026-09-07 13:50:12 +01:00
Karim Jerbi 0dab3ec54a feat: add COMMANDLINEFADETIME sysvar for overlay history fade
Controls how long command-line history lines stay visible, in ms (0-60000, default 3000). Bare form stays active until a value or Esc.
2026-09-06 02:13:09 +01:00
Karim Jerbi a9fda66ac0 fix(ui): add non-color marker to CLI info entries and drop dead desk parameter
- Add 'i  ' prefix to EntryKind::Info and coordinate set_step_prompt deduplication to satisfy WCAG 1.4.1 across all 22 themes
- Drop unused _desk parameter from crosshair_background and clean up its call sites
- Document AutoCAD-standard context-based crosshair drafting surface architecture in crosshair_background
- Add doc comments documenting deliberate alpha omission in canvas_is_light and backlog scope in is_wide
2026-09-04 16:26:13 +01:00
Karim Jerbi b53cd580f6 feat(ui): shared dropdown popup width calculation with wide-character support
- Add dropdown_popup_width helper in common.rs replacing magic formula heuristics
- Implement is_wide range table to count East Asian Wide/Fullwidth (CJK) glyphs double for localized strings
- Calibrate unit scale factor to 0.68 * font_size preserving existing width behavior at font size 11
- Apply shared calculation across viewport controls and ribbon dropdowns
- Update unit tests to verify width calculation on standard and CJK labels
2026-09-04 16:26:12 +01:00
Karim Jerbi a7e3cf3a6d fix(ui): enforce WCAG AA contrast thresholds across UI themes
- Raise accessible_accent default threshold from 2.0 to 3.0 (WCAG 1.4.11 UI state indicators)
- Raise EntryKind::Info command line history threshold from 3.0 to 4.5 (WCAG 1.4.3 normal text)
- Update themed_check_cell to verify >= 3.0:1 contrast across both weak popup panel and strong highlighted rows
- Replace fully-qualified accessible_accent_threshold calls inside closures with use imports
- Add comprehensive 9-suite theme accessibility contrast test suite covering all 22 themes across core surfaces
2026-09-04 15:34:24 +01:00
Karim Jerbi 23a9884f90 fix(ui): canvas-aware selection colors and paper space detection
- Add canvas_is_light helper based on WCAG luminance (> 0.5)
- Ensure crosshair_background always returns sheet white in paper space
- Update resolve_selection_base_color to resolve user ACI -> light-canvas palette -> theme palette
- Add light_canvas_color with curated deep-green/deep-blue pairs for light surfaces
- Extract selection_fill_alpha with 1.35x contrast compensation on light surfaces
- Thread canvas_bg through draw_marquee and poly selection
- Note deliberate limitation for desk-spanning marquee edge case
- Add test coverage for paper space, ClassicDark, custom light bg, and fill alpha clamping
2026-09-04 15:28:25 +01:00
Karim Jerbi 886d5777d8 fix(ViewCube): WCS dropdown no longer appears to clip view cube
- enlarged render area for the viewcube container, and increased the gap between cube region and WCS dropdown
2026-09-04 10:21:33 +01:00
Karim Jerbi 688785a6cf feat(model-space): theme-aware model space canvas and CAD selection/grip sysvars
Model space appearance now follows the active UI theme instead of a
hardcoded dark background. Canvas mode is user-selectable (Match Theme /
Classic CAD Dark / Custom RGB), with matching controls for the paper
sheet and desk surround backgrounds and grid opacity.
Implements industry-standard CAD system variables, all settable via
command name, SETVAR, and the Options dialog:
- COLORTHEME (0/1 dark/light UI scheme, preserves custom palettes)
- COLORSCHEME (interactive named-theme selection)
- BACKGROUND (THEME/CLASSIC/DEFAULT/DESK presets, RGB triplets)
- SELECTIONAREA, SELECTIONAREAOPACITY (translucent marquee fill)
- WINDOWSAREACOLOR/WINDOWAREACOLOR, CROSSINGAREACOLOR (ACI or theme)
- SELECTIONEFFECT, SELECTIONEFFECTCOLOR (GPU highlight pass, driven by
  selection_generation invalidation — no retessellation)
- SELECTIONPREVIEW (bit 0 = idle rollover, bit 1 = in-command hover)
- GRIPSIZE, GRIPCOLOR, GRIPHOT, GRIPHOVER (grip overlay styling)
2026-09-03 17:44:44 +01:00
Karim Jerbi 46190ccba1 refactor(selection): migrate Scene.selection to Arc<RefCell> to avoid per-frame deep clone
Scene.selection was Rc<RefCell<SelectionState>> (31 fields, ~1.6KB
with poly_points) cloned deep per view_main per pane via
(*sel_ref).clone() at view/mod.rs:760. Replace with
Arc<RefCell<SelectionState>> (Arc::new(RefCell::new(...)) at
scene/mod.rs:1436,1868), SelectionCanvas holds Arc<RefCell> and
selection_overlay takes Arc<RefCell> (overlay.rs:365,415),
view_main does Arc::clone(&tab.scene.selection) (atomic bump)
instead of deep clone. sel_ref stays borrowed for field reads
(vp_size etc.).

Bench (release, n=5000): SelectionState::clone 39-68ns (64pts) /
11-22ns (0pts) vs Arc::clone 7ns — 5-9x on the hot path.

Tests:
- selection_is_arc_not_refcell: asserts type_name contains Arc
- bench_selection_state_clone / bench_selection_arc_clone / bench_selection_state_clone_empty (ignored, release)
2026-09-02 22:14:32 +01:00
Karim Jerbi 50cb21e4d4 refactor(ribbon): make StyleContext borrowed to avoid 8 clones per view
Replace the 8 owned clones in StyleContext construction (4 Vec<String>
+ 4 String) at ribbon/mod.rs:568 and :1059 with borrowed refs.
StyleContext is now StyleContext<'a> with &'a [String]/&'a str
(widgets.rs:313), RenderCtx holds &'a StyleContext<'a> (:633),
and call sites pass &self.x instead of .clone(). The view-local
borrow is valid for the duration of Ribbon::view() and the style
dropdown overlay.

TDD: RED style_context_tests failed E0308 (owned vs borrowed),
GREEN added lifetime and updated 4 type sites + 2 construction sites.
2026-09-02 18:20:29 +01:00