Commit Graph
3363 Commits
Author SHA1 Message Date
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
Hakan Seven 912b217d26 fix: delegate dimension transforms to cadcodec 2026-09-14 14:22:10 +03:00
Hakan Seven 247e48bfe8 Merge pull request #1248 from kevin9327/fix/dimension-rotate-mirror-off-world-z 2026-09-14 14:15:11 +03:00
kevin9327andClaude Opus 5 23cd448c7f fix(dimension): rotate and mirror dimensions in working planes off world Z
With a working plane other than world XY, ROTATE and MIRROR handed a
non-ordinate dimension to acadrust's Entity::apply_transform, which only
translates those dimension types by where the transform sends the origin.
The definition points, plane and angles stayed unturned, so the dimension
jumped back once its block was baked again.

Send both through the existing Affine branch, which ALIGN already uses and
which transforms points, normal and stored angles together. The world-Z
paths are unchanged.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-14 19:52:20 +09:00
Hakan Seven cba64631b8 fix: render PDF attachments on every platform
Fixes #1243
2026-09-14 12:53:06 +03:00
Hakan Seven 010d23fa95 chore: sync reviewed dependency mains 2026-09-14 10:02:49 +03:00
Hakan Seven 2f741d689f fix: address open PR review findings 2026-09-14 09:45:37 +03:00
Hakan Seven 3fe91fa6e1 Merge pull request #1240 from kevin9327/fix/step-export-large-coordinates 2026-09-14 09:29:08 +03:00
Hakan Seven 16a0f210d5 Merge pull request #1239 from kevin9327/fix/obj-import-missing-normals 2026-09-14 09:28:56 +03:00
Hakan Seven 28b49bf453 Merge pull request #1238 from kevin9327/fix/units-negative-zero 2026-09-14 09:28:45 +03:00
Hakan Seven ed09d287a4 Merge pull request #1237 from kevin9327/fix/linear-dim-rotate-mirror-axis 2026-09-14 09:28:31 +03:00
kevin9327andClaude Opus 5 d6a6b26675 fix(step): export mesh vertices with their low residual at large coordinates
Scene meshes store each position as an f32 half plus a low residual so
geometry stays precise at survey coordinates, and the renderer adds the
two back together. STEP export read only the f32 half. Near x = 500000
that half steps in 1/32 units, so a solid at 500000.123 was written as
500000.125, and the edge directions and planes were built from the same
snapped points.

Build each exported vertex from both halves in f64 and derive directions
and the fallback face normal from those. Meshes without a residual export
as before.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-14 08:59:58 +09:00
Hakan Seven fc1788df65 fix: export interoperable DWG constraint graphs 2026-09-14 02:57:33 +03:00
kevin9327andClaude Opus 5 977cdd4888 fix(obj): give faces without usable normals their face normal on import
IMPORTOBJ only computed face normals when the file listed no normals at
all. When a file did list some, a face that omitted its normal indices,
or pointed at an index past the end of the list, kept a zero normal. That
zero reaches the mesh shader, which normalizes it for lighting.

Track which vertices got a normal from the file and give the rest their
face normal. Supplied normals are kept, and a file without normals is
handled as before.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-14 08:51:03 +09:00
kevin9327andClaude Opus 5 19a0ac79ab fix(units): drop the minus sign from lengths and angles that round to zero
Rotating geometry leaves float noise in coordinates: turning the point
(0, 10) by 180 degrees gives X = -1.2246e-15. Rust formats that as
"-0.0000", so Properties showed "-0.0000" (or -0", -0d0'0", -0g) for a
value that is zero at the drawing's precision.

format_length and format_angle now drop the sign when every printed digit
is zero. Values that still show a digit keep it, and scientific notation
is unaffected.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-14 08:40:20 +09:00
kevin9327andClaude Opus 5 019447d9b5 fix(dimension): keep the linear measurement axis through ROTATE and MIRROR
ROTATE and MIRROR in the drawing plane moved a linear dimension's points
but left its stored rotation (the axis it measures along) unchanged. A
horizontal dimension rotated 90 degrees then measured its now-vertical
points along X and its value dropped to 0; mirroring about a slanted line
did the same. Polar ARRAY goes through the same path.

Turn the axis by the rotation angle, and reflect it about the mirror line.
A zero-length mirror line still leaves the dimension untouched.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-14 08:29:31 +09:00
Hakan Seven 7b4cef41f7 Complete parametric ribbon and constraint tools 2026-09-14 01:05:39 +03:00
Hakan Seven f3716bb516 Redesign parametric ribbon layout 2026-09-13 23:36:30 +03:00
Hakan Seven f47df9824d Arrange coincident constraint glyphs 2026-09-13 23:16:31 +03:00
Hakan Seven 5c9ca5b897 Improve constraint glyph placement and readability 2026-09-13 23:08:54 +03:00
Hakan Seven 78ab1c8013 Keep grip edits on the drawing plane and preserve constraint undo 2026-09-13 22:33:17 +03:00
Hakan Seven 64e64c80a1 Merge pull request #1235 with kernel-based dimension leaders 2026-09-13 21:14:47 +03:00
Hakan Seven 84836b0f0c Merge pull request #1234 with safe hyperlink routing 2026-09-13 21:03:30 +03:00
Hakan Seven 95bad2a3b2 Fix web folder paths and allow main-only web hotfix deployment 2026-09-13 20:30:12 +03:00
github-actions[bot] a43fb253f6 Release v2026.37 v2026.37 2026-09-13 15:55:00 +00:00
ysdirector 171fc48987 feat(viewport): Ctrl+click opens an entity PE_URL hyperlink
Entities that carry the standard PE_URL XDATA record (what HYPERLINK writes)
can now be followed from the canvas: Ctrl+click opens the link in the system
browser and consumes the click, so no box/lasso selection starts.

- scene::selection::pe_url_of() reads the record with the same rule as the
  Properties panel hyperlink field; unit-tested.
- The ViewCube/compass hit region wins over a hyperlink drawn beneath it.
- Unchanged behaviour when Ctrl is not held or the entity has no link.
2026-09-13 23:53:50 +08:00
Hakan Seven 05d95e2e72 Use a centered triangular ruler for measurement tools 2026-09-13 14:34:42 +03:00
Hakan Seven ec53dfa34b Translate Measure ribbon label 2026-09-13 14:17:16 +03:00
Hakan Seven 86b059f2d4 Remove implicit sketch constraint inference 2026-09-13 14:02:32 +03:00
ysdirector e76751604f fix(dimension): extend the DIMTMOVE=1 leader under the text for linear dims
DIMTMOVE=1 ("moves the text and adds a leader") draws the leader from the midpoint
of the dimension line to the text, so a text dragged outside the dimension line gets
a diagonal that starts in the middle of the line and crosses the extension lines.

Mechanical-drawing practice (GB/T 4458.4, and what AutoCAD itself draws) extends the
dimension line along its own axis so that it runs underneath the text. Linear and
aligned dimensions now do that whenever the text is outside the dimension-line extent;
when the text is still inside it (only moved up/down) the previous connect-to-text
leader is kept. Every other dimension type is unchanged, and only dimensions with
DIMTMOVE=1 are affected at all (the shipped default is 0).

Adds four unit tests covering the placements (outside right, outside left, inside,
radius draws none) plus an integration render smoke test driven through the real
Scene::entity_wires() -> export_pdf() pipeline, gated by OCS_DIM_LEADER_PDF so CI
stays side-effect free.
2026-09-13 18:10:02 +08:00
Hakan Seven 052b6b239d fix: clean up mobile sponsor artwork 2026-09-13 12:53:24 +03:00
Hakan Seven e2b37edd23 feat: add mobile sponsor banner 2026-09-13 10:28:05 +03:00
gianlucafiore dd304a8424 Merge pull request #1225 from gianlucafiore/fix/issue-1135-workflow-improvements
Improve command workflow for undo, move, stretch and fillet
2026-09-12 21:44:13 -03:00
gianlucafiore 5d77c2dd00 feat(fillet): allow radius from two picked points 2026-09-12 21:41:34 -03:00
gianlucafiore 8a45a84c43 fix(stretch): highlight affected vertices during selection 2026-09-12 21:41:34 -03:00
gianlucafiore 7ab311d86c fix(move): keep source geometry visible during preview 2026-09-12 21:41:34 -03:00
gianlucafiore 17ecd9ff4d fix(commands): handle Ctrl+Z inside active commands 2026-09-12 21:41:34 -03:00
gianlucafiore d68f297d8d fix(print-all): preserve applied plot options 2026-09-12 21:41:34 -03:00
Hakan Seven 07acd1284c Render procedural checker materials correctly 2026-09-13 01:50:42 +03:00
Hakan Seven 12c1c330fc fix(viewport): keep hidden-border content visible 2026-09-12 23:22:44 +03:00
Hakan Seven 9f71927d6f fix(mtext): honor explicit dynamic column breaks 2026-09-12 21:53:32 +03:00
Hakan Seven 9f303c8b5a Use actual release download counts 2026-09-12 21:16:19 +03: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
PanosandClaude Opus 5 02b0f4c7d9 Tell the user when the scene is drawn without a GPU
A day of pan/zoom that had suddenly become unusable on a 1.2M-entity
drawing turned out to be no regression at all: an unattended NVIDIA
userspace update had been applied without a reboot, the Vulkan ICD could
no longer talk to the still-loaded kernel module, and wgpu silently fell
through to Mesa's llvmpipe. Nothing in the app said so. iced picks the
adapter and names it only under RUST_LOG, so the only evidence was 32
`llvmpipe-N` threads in /proc. The same silence covers WARP on Windows,
SwiftShader in browsers, and the case where wgpu finds no adapter at all
and iced quietly draws the chrome with tiny-skia while the shader widget
is dropped, leaving a blank viewport. This commit makes all of that
visible, on every platform, without new dependencies.

Detection (src/scene/pipeline/mod.rs)
-------------------------------------
`GpuStatus` is derived from two signals:

* `record_gpu_adapter`, run from `MultiPipeline::new` (once per device,
  next to the existing uncaptured-error handler), reads
  `Device::adapter_info()`, prints one `[gpu] adapter: <name> (<backend>,
  <device type>, driver …)` line — stderr natively, `console.warn` on the
  web, which has no stderr — and keeps a copy.
* `note_scene_draw`, run from `ViewportPane::draw`, counts how often the
  viewport asked for a primitive. Every renderer makes that call; only
  wgpu goes on to `prepare` and create a device. Three draws with no
  device ever recorded therefore mean the primitive is being dropped.

The verdict is one of `Unknown`, `Hardware`, `Software(adapter)` or
`NoRenderer`. `Software` is `DeviceType::Cpu` — what Vulkan reports for
llvmpipe, DX12 for WARP and GL for a renderer string that gives it away —
plus a name match on the fallbacks browsers expose (SwiftShader, llvmpipe,
lavapipe, "Microsoft Basic Render"), because the WebGPU backend reports
every adapter as `Other` (gfx-rs/wgpu#8819). Metal has no software device,
so on macOS only `NoRenderer` can occur (a VM without a GPU); it is caught
by the draw count like everywhere else.

`gpu_status_if_changed` costs one atomic load per app message: a
generation counter moves only when a device is recorded or the draw count
crosses its threshold, so the app re-derives the verdict a handful of
times per session.

Notification (src/app/startup.rs, view/modal.rs, ui/statusbar)
-------------------------------------------------------------
`refresh_gpu_status` runs after every message. On a degraded verdict it
speaks through three channels, because many users never read the command
line:

* A popup, `ModalKind::GpuWarning`, queued once per verdict through the
  startup-modal queue so it waits its turn behind the file open. It names
  the adapter, states the consequence (slow navigation, unstable or
  missing rendering) and gives the remedy that usually applies on the
  running platform: a driver update not yet followed by a reboot on
  Linux, a missing driver or a Remote Desktop / VM session on Windows, a
  VM without a Metal device on macOS, hardware acceleration switched off
  in the browser. "Don't show again for this device" stores
  `GpuStatus::identity()` in `settings.gpu_warning_silenced`; keying it by
  verdict means silencing "software rendering on llvmpipe" does not
  silence a later, different failure.
* A status-bar pill in the theme's warning colours, `⚠ Software rendering`
  or `⚠ No GPU renderer`. It is not a `StatusPill`, so it cannot be hidden,
  and it stays for the session; clicking it reopens the popup, silenced or
  not.
* A command-line entry through the new `CommandLine::push_warning`, which
  is styled like an error but deliberately does not touch `last_error` /
  `error_revision`: automation reads those to decide whether the command it
  just ran failed, and a graphics warning is not a failed command.

`subscription()` requests frames only while the verdict is `Unknown` on a
document tab (the Start page draws no viewport), so the probe costs one
frame on a working GPU, three on none, then stops.

Verification
------------
* Live on the machine that had the incident, with the Vulkan loader
  restricted to Mesa (`VK_DRIVER_FILES=…/lvp_icd.json WGPU_BACKEND=vulkan`):
  popup → OK → pill → click reopens; "Don't show again" → restart → pill
  only, no popup.
* `gpu_tests::the_adapter_is_reported_once_per_device` (ignored, needs an
  adapter) asserts the classification on a real device; it was run on both
  llvmpipe (`Cpu`) and an NVIDIA GT 1030 (`DiscreteGpu`).
* Unit tests cover the name heuristic, the verdict function, the identity
  rule, the warning text with an adapter name that itself contains
  parentheses and commas, the settings round trip and the command-line
  semantics. New strings are in all 21 locales as the `gpu` Fluent group.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-12 16:19:41 +03:00
PanosandClaude Opus 5 5f161bb037 Show the exact build in About, Copy Info, REPORT and --version
Builds from main reported only the release version (2026.36) even when
HEAD was hundreds of commits past the tag. build.rs now also emits
OCS_TAG_DISTANCE, OCS_COMMIT_DATE, OCS_BUILD_METADATA and
OCS_FULL_VERSION: a clean checkout of the release tag stays "2026.36",
anything else becomes "2026.36+194.gef189d77", with ".dirty" appended
for uncommitted changes and only the hash when no tag can be counted
from (shallow clone, tarball). OCS_APP_VERSION is unchanged so the
update check, donation gate, User-Agents and window title keep working.

The About dialog gains Build / Commit date / Profile cards, Copy Info,
the REPORT issue template and recovery reports carry the full version,
and `-V`/`--version` print it instead of Cargo's 2026.36.0. Release
workflows check out full history so release binaries see the tag.

Also fixes the build being marked dirty when there is no repository:
diff-index exits 128 there, not 1.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-12 16:19:40 +03: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
Hakan Seven 754cc89fab Merge PR #1216: expose MTP in snap menus 2026-09-12 15:12:04 +03:00