diff --git a/Cargo.lock b/Cargo.lock index 2a087f73a..64e87c24a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.5.5" -source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=7ea4247#7ea4247134af60c8c1617426849d4fa8233660c8" +source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=5b682ed#5b682ed66ea2c89be8142c8dd83d83774fc3de08" dependencies = [ "ahash 0.8.12", "anyhow", @@ -922,7 +922,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=a0b962a#a0b962ae8045e7221ecb5d478adc5ad58e2706ba" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=6f046af#6f046afdda94d8cd9cdbd7fc0ab73ee726aac9ab" dependencies = [ "acadrust", "cavalier_contours", @@ -933,7 +933,7 @@ dependencies = [ [[package]] name = "cadkernel-constraints" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=a0b962a#a0b962ae8045e7221ecb5d478adc5ad58e2706ba" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=6f046af#6f046afdda94d8cd9cdbd7fc0ab73ee726aac9ab" dependencies = [ "nalgebra", ] diff --git a/Cargo.toml b/Cargo.toml index 05791c276..7b9768873 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,9 +33,9 @@ rfd = "0.17" clap = { version = "4", features = ["derive", "string"] } env_logger = "0.11" log = "0.4" -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7ea4247", features = ["serde"] } -cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "a0b962a", features = ["acis", "offset"] } -cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "a0b962a" } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b682ed", features = ["serde"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "6f046af", features = ["acis", "offset"] } +cadkernel-constraints = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "6f046af" } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] } diff --git a/benches/README.md b/benches/README.md index a9af5c970..a91fa0450 100644 --- a/benches/README.md +++ b/benches/README.md @@ -19,7 +19,7 @@ cargo bench --bench performance_benchmarks -- --quick --- -## Benchmark Suite Catalog (25 Metrics) +## Benchmark Suite Catalog (26 Metrics) ### 1. Scene & Entity Management - `scene_entity_ingestion_10k`: Ingestion throughput for 10,000 mixed 2D entities (Lines, Circles, Arcs, Polylines). @@ -51,6 +51,7 @@ cargo bench --bench performance_benchmarks -- --quick - `ui_grid_cache_hit_evaluation`: Grid overlay cache key construction and reuse decision. - `ui_icon_handle_cached`: Themed SVG icon cache hit (FxHashMap lookup + Handle clone). - `ui_icon_handle_uncached`: Uncached SVG icon memory parse and Handle allocation. +- `ui_plotstyle_layer_usage`: Plot Style 256-bucket ACI layer-usage table rebuild (200 layers). ### 6. State Management & Draworder - `selection_state_deep_clone`: Deep cloning of `SelectionState` (with 64 polygon points). diff --git a/benches/performance_benchmarks.rs b/benches/performance_benchmarks.rs index f51a08aa6..0374eaf68 100644 --- a/benches/performance_benchmarks.rs +++ b/benches/performance_benchmarks.rs @@ -13,6 +13,7 @@ //! 10. UI Ribbon View Widget Tree Construction //! 11. UI Viewport Grid Geometry Projection & Overlay Cache Key Evaluation //! 12. UI Themed SVG Icon Lookup Caching vs Uncached Parse +//! 12b. Plot Style Layer-Usage Table Rebuild (256-bucket ACI table) //! 13. Wide & Tapered Arc + Donut Tessellation (7.5k offset polyline curves) //! 14. Model Space Extents & Bounding Box Calculation (ZOOM EXTENTS) //! 15. Batch Entity Transformation & Incremental Dirty-Tracking @@ -64,6 +65,7 @@ use OpenCADStudio::ui::overlay::{ }; use OpenCADStudio::ui::properties::LinetypeItem; use OpenCADStudio::ui::ribbon::{LayerInfo, Ribbon}; +use OpenCADStudio::ui::style::plotstyle::build_layer_usage; // ── Metric Structures ─────────────────────────────────────────────────────── @@ -1359,6 +1361,53 @@ fn bench_ui_icon_caching(runner: &mut BenchmarkRunner) { } } +// ── 12b. Plot Style Layer-Usage Table Rebuild ─────────────────────────────── +// Covers Mission #19 `build_layer_usage`: layer names bucketed by ACI into a +// 256-bucket table, rebuilt per Plot Style modal view. + +fn bench_ui_plotstyle_layer_usage(runner: &mut BenchmarkRunner) { + if !runner.should_run("ui_plotstyle_layer_usage") { + return; + } + + let mut doc = CadDocument::new(); + for i in 0..200 { + let mut layer = acadrust::tables::Layer::new(&format!("LAYER_{i:03}")); + layer.handle = doc.allocate_handle(); + layer.color = acadrust::types::Color::Index((i % 8 + 1) as u8); + let _ = doc.layers.add(layer); + } + + // Warm-up for allocator settling + for _ in 0..10 { + let _ = black_box(build_layer_usage(&doc)); + } + + let n = if runner.quick_mode { 20 } else { 100 }; + let runs = 5; + let mut samples = Vec::with_capacity(runs); + + for _ in 0..runs { + let t0 = Instant::now(); + for _ in 0..n { + let usage = build_layer_usage(black_box(&doc)); + black_box(usage); + } + let per_us = (t0.elapsed().as_micros() as f64) / (n as f64); + samples.push(per_us); + } + + let median_us = samples[samples.len() / 2]; + runner.record( + "ui_plotstyle_layer_usage", + "Plot Style 256-bucket ACI layer-usage table rebuild (200 layers, 8 buckets x25)", + "µs", + samples, + Some((1_000_000.0 / median_us, "rebuilds/s")), + Some(50.0), // Target threshold < 50 µs + ); +} + // ── 13. Wide & Tapered Arc + Donut Tessellation ───────────────────────────── fn bench_wide_and_tapered_arc_tessellation(runner: &mut BenchmarkRunner) { @@ -1761,6 +1810,7 @@ fn main() { bench_ui_ribbon_view_construction(&mut runner); bench_ui_grid_geometry(&mut runner); bench_ui_icon_caching(&mut runner); + bench_ui_plotstyle_layer_usage(&mut runner); bench_wide_and_tapered_arc_tessellation(&mut runner); bench_zoom_extents_calculation(&mut runner); bench_batch_entity_mutation(&mut runner); diff --git a/crates/ocs_plugin_api/Cargo.toml b/crates/ocs_plugin_api/Cargo.toml index 23bd59fe6..76f48daa2 100644 --- a/crates/ocs_plugin_api/Cargo.toml +++ b/crates/ocs_plugin_api/Cargo.toml @@ -15,7 +15,7 @@ serde_json = { version = "1", optional = true } # Pulled in only by the `host` feature, which adds the `acadrust`-typed # `HostApi` runtime surface. The default crate stays dependency-free so engine # crates and external tooling can depend on the manifest/ribbon contract cheaply. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7ea4247", optional = true, features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b682ed", optional = true, features = ["serde"] } # Runtime IPC and serialization (host feature only). interprocess = { version = "2", optional = true } @@ -39,7 +39,7 @@ serde_json = "1" serde = { version = "1", features = ["derive"] } cargo-lock = "11" # acadrust is scanned at build time to generate the embedded type registry. -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7ea4247", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b682ed", features = ["serde"] } [dev-dependencies] serde_json = "1" diff --git a/crates/ocs_web_worker/Cargo.toml b/crates/ocs_web_worker/Cargo.toml index 8b28027d5..210e58d2d 100644 --- a/crates/ocs_web_worker/Cargo.toml +++ b/crates/ocs_web_worker/Cargo.toml @@ -8,7 +8,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7ea4247", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b682ed", features = ["serde"] } bincode = "1.3" serde = { version = "1", features = ["derive"] } console_error_panic_hook = "0.1" diff --git a/plugins/opencad-python/Cargo.lock b/plugins/opencad-python/Cargo.lock index 4fdfea5f5..449bbb423 100644 --- a/plugins/opencad-python/Cargo.lock +++ b/plugins/opencad-python/Cargo.lock @@ -5,7 +5,7 @@ version = 4 [[package]] name = "acadrust" version = "0.5.5" -source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=7ea4247#7ea4247134af60c8c1617426849d4fa8233660c8" +source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=5b682ed#5b682ed66ea2c89be8142c8dd83d83774fc3de08" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/src/modules/insert/xattach.rs b/src/modules/insert/xattach.rs index 9124421b5..b7be7c47a 100644 --- a/src/modules/insert/xattach.rs +++ b/src/modules/insert/xattach.rs @@ -335,6 +335,8 @@ pub fn prepare_xref_block( anonymous: false, has_attributes: false, is_external: false, + // A freshly attached reference is loaded; UNLOAD flips this later. + is_xref_unloaded: false, }; br.xref_path = store_path.clone(); let _ = scene.document.block_records.add(br); diff --git a/src/ui/style/plotstyle.rs b/src/ui/style/plotstyle.rs index cf2b93a0b..1c943fb3d 100644 --- a/src/ui/style/plotstyle.rs +++ b/src/ui/style/plotstyle.rs @@ -84,6 +84,35 @@ fn field_style(theme: &Theme, status: text_input::Status) -> text_input::Style { } } +/// Layer names bucketed by ACI (AutoCAD Color Index), index = ACI. +/// +/// Pure function of `document.layers`: one entry per layer whose color is +/// `Color::Index(aci)` with `1 <= aci < 256`, each bucket sorted +/// case-insensitively. Extracted from `view_window` so the table can be +/// unit-tested and benched without building widgets. `pub` (not +/// `pub(crate)`) so the `cargo bench` harness (`benches/`, an external +/// crate) can measure it as `ui_plotstyle_layer_usage`. +pub fn build_layer_usage(document: &acadrust::CadDocument) -> Vec> { + let mut layer_usage = vec![Vec::::new(); 256]; + + for layer in document.layers.iter() { + if let acadrust::types::Color::Index(aci) = &layer.color { + let index = *aci as usize; + + if index > 0 && index < layer_usage.len() { + layer_usage[index].push(layer.name.clone()); + } + } + } + + for layers in &mut layer_usage { + if layers.len() > 1 { + layers.sort_by_cached_key(|name| name.to_ascii_lowercase()); + } + } + layer_usage +} + pub fn view_window<'a>( document: &'a acadrust::CadDocument, table: Option<&'a crate::io::plot_style::PlotStyleTable>, @@ -95,23 +124,11 @@ pub fn view_window<'a>( ) -> Element<'a, Message> { let show_layer_usage = !table.is_some_and(|table| table.is_stb); -let mut layer_usage = vec![Vec::::new(); 256]; - - if show_layer_usage { - for layer in document.layers.iter() { - if let acadrust::types::Color::Index(aci) = &layer.color { - let index = *aci as usize; - - if index > 0 && index < layer_usage.len() { - layer_usage[index].push(layer.name.clone()); - } - } - } - - for layers in &mut layer_usage { - layers.sort_by_key(|name| name.to_ascii_lowercase()); - } - } + let layer_usage = if show_layer_usage { + build_layer_usage(document) + } else { + Vec::new() + }; let table_name = table .map(|t| t.name.clone()) .unwrap_or_else(|| t!("(no table loaded)").into_owned()); @@ -536,3 +553,58 @@ let mut layer_usage = vec![Vec::::new(); 256]; .height(sizing.height) .into() } + +#[cfg(test)] +mod layer_usage_tests { + use super::*; + + fn indexed_layer( + doc: &mut acadrust::CadDocument, + name: &str, + aci: u8, + ) -> acadrust::tables::Layer { + let mut layer = acadrust::tables::Layer::new(name); + layer.handle = doc.allocate_handle(); + layer.color = acadrust::types::Color::Index(aci); + layer + } + + #[test] + fn build_layer_usage_buckets_index_colors() { + let mut doc = acadrust::CadDocument::new(); + let a = indexed_layer(&mut doc, "WALLS", 1); + let b = indexed_layer(&mut doc, "doors", 1); + let c = indexed_layer(&mut doc, "ROOF", 3); + let _ = doc.layers.add(a); + let _ = doc.layers.add(b); + let _ = doc.layers.add(c); + + let usage = build_layer_usage(&doc); + + assert_eq!(usage.len(), 256); + assert_eq!(usage[1], vec!["doors".to_string(), "WALLS".to_string()]); + assert_eq!(usage[3], vec!["ROOF".to_string()]); + assert!(usage[2].is_empty()); + } + + #[test] + fn build_layer_usage_ignores_non_index_colors() { + let mut doc = acadrust::CadDocument::new(); + let mut truecolor = acadrust::tables::Layer::new("TRUECOLOR"); + truecolor.handle = doc.allocate_handle(); + truecolor.color = acadrust::types::Color::Rgb { + r: 10, + g: 20, + b: 30, + }; + let _ = doc.layers.add(truecolor); + + let usage = build_layer_usage(&doc); + + assert!( + usage.iter().all(|bucket| !bucket.iter().any(|n| n == "TRUECOLOR")), + "truecolor layer must not appear in any ACI bucket" + ); + } + +}