From 0a26709d139b629205520cd3958f74bf93db354a Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 11:36:24 +0300 Subject: [PATCH] Count: measured messages and listing, groups, Current area, boundary checks, fills and solids recoloured - COUNTCOLOR / COUNTERRORCOLOR / COUNTSERVICE reject any other value with the range message; COUNTTABLE ? lists in the reference order, padded, 22 lines a page with Press ENTER to continue:, then the totals. - Target picks report N found / N found, M total; several objects (or one that is not a block reference) count their copies, shown as Geometries in the palette. - Current area is the area in use (kept aside by Entire model space), otherwise all of model space; an invalid Object boundary ends with the reference message. - Count colours reach solids and fills; the palette list and the count are recomputed only when the drawing changes. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 1 + locales/bg-BG/opencadstudio.ftl | 1 + locales/cs-CZ/opencadstudio.ftl | 1 + locales/de-DE/opencadstudio.ftl | 1 + locales/el-GR/opencadstudio.ftl | 1 + locales/en-US/opencadstudio.ftl | 1 + locales/es-ES/opencadstudio.ftl | 1 + locales/fi-FI/opencadstudio.ftl | 1 + locales/fr-FR/opencadstudio.ftl | 1 + locales/hi-IN/opencadstudio.ftl | 1 + locales/hu-HU/opencadstudio.ftl | 1 + locales/it-IT/opencadstudio.ftl | 1 + locales/ja-JP/opencadstudio.ftl | 1 + locales/ko-KR/opencadstudio.ftl | 1 + locales/nl-NL/opencadstudio.ftl | 1 + locales/pl-PL/opencadstudio.ftl | 1 + locales/pt-BR/opencadstudio.ftl | 1 + locales/ru-RU/opencadstudio.ftl | 1 + locales/tr-TR/opencadstudio.ftl | 1 + locales/zh-CN/opencadstudio.ftl | 1 + locales/zh-TW/opencadstudio.ftl | 1 + src/app/commands/count.rs | 48 +++++++++++------ src/app/update/file.rs | 2 +- src/app/view/mod.rs | 1 + src/locale_catalog.rs | 1 + src/modules/annotate/count_cmd.rs | 87 ++++++++++++++++++++++++------- src/scene/entity.rs | 2 + src/scene/mod.rs | 33 +++++++++++- src/ui/window/count_palette.rs | 75 +++++++++++++++++++------- 31 files changed, 214 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d77d98502..e869f168d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4206,7 +4206,7 @@ dependencies = [ [[package]] name = "opencadcodec" version = "0.5.5" -source = "git+https://github.com/ramox81/cadcodec.git?rev=48b86d0#48b86d08c895f07b53753888363d1d9a6b282305" +source = "git+https://github.com/ramox81/cadcodec.git?rev=178b596#178b596ba1606ce6d4ed719cd48551750a0f32a5" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 51be8093f..e5ef28bce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "48b86d0" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "178b596" } [patch.crates-io] iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index d8f9892f5..8a6956780 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -5955,3 +5955,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 4453f5bf9..331b7cba7 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -5943,3 +5943,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index d0a43de2c..4fafb4c8d 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -5940,3 +5940,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 96d321278..e2a2f8ae4 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -5941,3 +5941,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index db3cab564..95f7054d0 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -5943,3 +5943,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 51b717a8d..764b2f5c7 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -5944,3 +5944,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 3e7750281..5c228e77d 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -5943,3 +5943,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index a1d1ab170..b6b1cc717 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -5940,3 +5940,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 65bfde6e0..faaae44cd 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -5942,3 +5942,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 21035e9e7..a7efb14dc 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -5931,3 +5931,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 6906cabd4..fafcb7755 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -5942,3 +5942,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index f652cc7d6..5bb46d938 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -5942,3 +5942,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index d819a86ee..212923b58 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -5931,3 +5931,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 254a0f857..4c0f4307f 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -5942,3 +5942,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 105fe9086..62a0f0efc 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -5941,3 +5941,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 89854dd6f..73582eaf9 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -5942,3 +5942,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 395f9eef5..3c5d40863 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -5943,3 +5943,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 0c6c0735f..1a532105c 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -5944,3 +5944,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index faeb502e3..10943532b 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -5866,3 +5866,4 @@ count-feature = .toolbar-count = Sayım: __ocs_fmt_0__ .overlap = Üst üste binen nesne .show-instances = Örnekleri göster + .geometries = Geometriler: __ocs_fmt_0__ diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index ca77965ed..3fbd610ca 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -5926,3 +5926,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index d7013d976..fb702b25f 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -5938,3 +5938,4 @@ count-feature = .toolbar-count = Count: __ocs_fmt_0__ .overlap = Overlapping object .show-instances = Show Count Instances + .geometries = Geometries: __ocs_fmt_0__ diff --git a/src/app/commands/count.rs b/src/app/commands/count.rs index 101c21c65..94fd8a73e 100644 --- a/src/app/commands/count.rs +++ b/src/app/commands/count.rs @@ -216,7 +216,7 @@ impl OpenCADStudio { self.persist_settings_if_changed(); self.apply_count_display(i); } - Ok(_) => { + _ => { self.command_line.push_error(&if max == 1 { "Requires 0 or 1 only.".to_string() } else { @@ -224,10 +224,6 @@ impl OpenCADStudio { }); ask(self); } - Err(_) => { - self.command_line.push_error("Requires an integer value."); - ask(self); - } } } @@ -249,6 +245,10 @@ impl OpenCADStudio { /// Recolour the drawing for count mode: counted references, duplicates, /// the rest faded (nothing changes without a target). pub(in crate::app) fn apply_count_display(&mut self, i: usize) { + let computed = self.tabs[i].count.as_ref().map(|m| m.compute(&self.tabs[i].scene.document)); + if let (Some(mode), Some(c)) = (self.tabs[i].count.as_mut(), computed) { + mode.cache = Some(c); + } let display = self.tabs[i].count.as_ref().filter(|m| m.target.is_some()).map(|m| { let r = m.result(&self.tabs[i].scene.document); crate::scene::CountDisplay { @@ -278,6 +278,7 @@ impl OpenCADStudio { pub(in crate::app) fn close_count(&mut self, i: usize) { if let Some(mode) = self.tabs[i].count.take() { self.drop_count_boundary(i, mode.boundary); + self.drop_count_boundary(i, mode.remembered.and_then(|(_, b)| b)); } self.tabs[i].scene.set_count_display(None); } @@ -310,10 +311,13 @@ impl OpenCADStudio { let mode = self.tabs[i].count.as_ref(); Some((mode.and_then(|m| m.area.clone()), mode.and_then(|m| m.boundary))) } - "C" => { - let (x0, y0, x1, y1) = self.display_plot_window()?; - Some((Some(vec![[x0, y0], [x1, y0], [x1, y1], [x0, y1]]), None)) - } + // Current area: the count area in use (also one Entire model space + // set aside), else all of model space. + "C" => Some(match self.tabs[i].count.as_ref() { + Some(m) if m.area.is_some() => (m.area.clone(), m.boundary), + Some(m) => m.remembered.clone().map_or((None, None), |(a, b)| (Some(a), b)), + None => (None, None), + }), "R" => { let p = points(); let (a, b) = (p.first()?, p.get(1)?); @@ -404,11 +408,18 @@ impl OpenCADStudio { }; // The picked targets do not stay selected. self.tabs[i].scene.deselect_all(); - let old = self.tabs[i].count.take(); - if let Some(old) = old.as_ref().filter(|o| o.boundary != boundary) { - self.drop_count_boundary(i, old.boundary); + // Entire model space counts without the area but keeps it for a later + // Current area; a new area replaces it. + let mut remembered = None; + if let Some(old) = self.tabs[i].count.take() { + let old_area = old.area.map(|a| (a, old.boundary)).or(old.remembered); + if area.is_none() { + remembered = old_area; + } else if let Some((_, b)) = old_area.filter(|(_, b)| *b != boundary) { + self.drop_count_boundary(i, b); + } } - let mode = CountMode { area, boundary, target, cursor: None, epoch: 0 }; + let mode = CountMode { area, boundary, target, remembered, ..CountMode::default() }; if let Some(name) = mode.target_name() { let n = mode.count(&self.tabs[i].scene.document); self.command_line.push_output(&format!("{name} ...... {n}")); @@ -566,13 +577,16 @@ impl OpenCADStudio { self.tabs[i].dirty = true; } - /// Zoom to a reference (← / →, an error of the report). + /// Zoom to a counted object (← / →, an error of the report). fn zoom_to_insert(&mut self, i: usize, h: Handle) { let doc = &self.tabs[i].scene.document; - let Some(EntityType::Insert(ins)) = doc.get_entity(h) else { return }; - let Some(corners) = codec::count::insert_corners(doc, ins, 0) else { return }; + let Some(entity) = doc.get_entity(h) else { return }; + let outline = codec::count::entity_outline(doc, entity); + if outline.iter().all(|part| part.is_empty()) { + return; + } let (mut lo, mut hi) = (glam::Vec3::splat(f32::MAX), glam::Vec3::splat(f32::MIN)); - for c in corners { + for c in outline.into_iter().flatten() { let p = glam::Vec3::new(c.x as f32, c.y as f32, c.z as f32); lo = lo.min(p); hi = hi.max(p); diff --git a/src/app/update/file.rs b/src/app/update/file.rs index ecc041ff1..4fd0f783d 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -5544,7 +5544,7 @@ impl OpenCADStudio { /// Current visible rectangle in the active space. The result is deliberately /// not clamped to the paper sheet: Display and Window may include the grey /// canvas outside the sheet, matching Model-space plotting. - pub(in crate::app) fn display_plot_window(&self) -> Option<(f64, f64, f64, f64)> { + fn display_plot_window(&self) -> Option<(f64, f64, f64, f64)> { let scene = &self.tabs[self.active_tab].scene; let (canvas_w, canvas_h) = scene.selection.borrow().vp_size; let viewport = if scene.current_layout == "Model" { diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 0c2db547f..8528c1c9e 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -3035,6 +3035,7 @@ impl OpenCADStudio { &self.count_palette, tab.count.as_ref(), &tab.scene.document, + tab.scene.geometry_epoch, width, auto_collapse, ), diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 78d01a199..e1526f410 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -5545,6 +5545,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Count: {}" => Some(("count-feature", "toolbar-count")), "Overlapping object" => Some(("count-feature", "overlap")), "Show Count Instances" => Some(("count-feature", "show-instances")), + "Geometries: {}" => Some(("count-feature", "geometries")), _ => None, } } diff --git a/src/modules/annotate/count_cmd.rs b/src/modules/annotate/count_cmd.rs index 8e75a99ab..9b0517d6b 100644 --- a/src/modules/annotate/count_cmd.rs +++ b/src/modules/annotate/count_cmd.rs @@ -144,8 +144,16 @@ impl CadCommand for CountCommand { } fn on_selection_complete(&mut self, handles: Vec) -> CmdResult { + // The selection arrives whole after every pick: `1 found`, then + // `1 found, 2 total`. + let found = handles.iter().filter(|h| !self.targets.contains(h)).count(); + let first = self.targets.is_empty(); self.targets = handles; - CmdResult::ReportMeasurement(format!("{} found", self.targets.len())) + CmdResult::ReportMeasurement(if first { + format!("{found} found") + } else { + format!("{found} found, {} total", self.targets.len()) + }) } fn needs_entity_pick(&self) -> bool { @@ -162,11 +170,13 @@ impl CadCommand for CountCommand { fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult { match self.picked.take() { - Some(EntityType::LwPolyline(pl)) if pl.is_closed && pl.vertices.len() >= 3 => { + Some(EntityType::LwPolyline(pl)) if codec::count::valid_boundary(&pl) => { self.area_done(format!("O {:X}", handle.value())) } - // Anything else: the prompt again. - _ => CmdResult::NeedPoint, + _ => CmdResult::CancelWithMessage( + "Invalid count area boundary object. Select a closed polyline consisting of line segments and does not intersect itself." + .into(), + ), } } @@ -244,6 +254,8 @@ pub struct CountTableCommand { blocks: Vec, others: [usize; 3], names: Vec, + /// Listing lines still to show after `Press ENTER to continue:`. + pending: Vec, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -251,38 +263,75 @@ enum TableStep { Names, List, Point, + Page, } impl CountTableCommand { pub fn new(blocks: Vec, others: [usize; 3]) -> Self { - Self { step: TableStep::Names, blocks, others, names: Vec::new() } + Self { step: TableStep::Names, blocks, others, names: Vec::new(), pending: Vec::new() } } /// The palette's Create Table: the blocks are chosen, only the point is asked. pub fn placing(names: Vec) -> Self { - Self { step: TableStep::Point, blocks: Vec::new(), others: [0; 3], names } + Self { step: TableStep::Point, blocks: Vec::new(), others: [0; 3], names, pending: Vec::new() } } - /// `?`: the defined blocks matching the pattern and the summary. - fn listing(&self, pattern: &str) -> String { + /// `?`: the defined blocks matching the pattern, paged by 22 lines + /// (`Press ENTER to continue:` in between), then the summary. + fn listing(&mut self, pattern: &str) -> CmdResult { let pattern = if pattern.trim().is_empty() { "*" } else { pattern.trim() }; - let mut out = String::from("Defined blocks."); - for name in &self.blocks { - if pattern.split(',').any(|p| crate::io::xref_model::wildcard_match(name, p.trim())) { - out.push_str(&format!("\n \"{name}\"")); - } + let mut names: Vec<&String> = self + .blocks + .iter() + .filter(|name| pattern.split(',').any(|p| crate::io::xref_model::wildcard_match(name, p.trim()))) + .collect(); + names.sort_by_key(|name| list_order(name)); + self.pending = std::iter::once(String::new()) + .chain(std::iter::once("Defined blocks.".to_string())) + .chain(names.iter().map(|name| format!(" {:<31}", format!("\"{name}\"")))) + .collect(); + self.next_page() + } + + fn next_page(&mut self) -> CmdResult { + let page: Vec = self.pending.drain(..self.pending.len().min(22)).collect(); + if !self.pending.is_empty() { + self.step = TableStep::Page; + return CmdResult::ReportMeasurement(page.join(" +")); } - out.push_str(&format!( - "\n\nUser External Dependent Unnamed\nBlocks References Blocks Blocks\n{:>5}{:>11}{:>13}{:>12}", + CmdResult::Measurement(format!( + "{} + +User External Dependent Unnamed +Blocks References Blocks Blocks +{:>5}{:>10}{:>12}{:>12} +", + page.join(" +"), self.blocks.len(), self.others[0], self.others[1], self.others[2] - )); - out + )) } } +/// Block listing order: `_` first, then `-`, space and other punctuation, +/// digits, and letters without case. +fn list_order(name: &str) -> Vec<(u32, u32)> { + name.chars() + .map(|c| match c { + '_' => (0, 0), + '-' => (1, 0), + ' ' => (2, 0), + '0'..='9' => (4, c as u32), + c if c.is_alphabetic() => (5, c.to_lowercase().next().unwrap_or(c) as u32), + c => (3, c as u32), + }) + .collect() +} + impl CadCommand for CountTableCommand { fn name(&self) -> &'static str { "COUNTTABLE" @@ -293,6 +342,7 @@ impl CadCommand for CountTableCommand { TableStep::Names => "Enter block name(s) to include or [?] :".into(), TableStep::List => "Enter block(s) to list <*>:".into(), TableStep::Point => "Specify insertion point:".into(), + TableStep::Page => "Press ENTER to continue:".into(), } } @@ -327,7 +377,8 @@ impl CadCommand for CountTableCommand { self.step = TableStep::Point; CmdResult::NeedPoint } - TableStep::List => CmdResult::Measurement(self.listing(text)), + TableStep::List => self.listing(text), + TableStep::Page => self.next_page(), TableStep::Point => return None, }) } diff --git a/src/scene/entity.rs b/src/scene/entity.rs index 9c22246bb..fcb98ef22 100644 --- a/src/scene/entity.rs +++ b/src/scene/entity.rs @@ -1451,6 +1451,7 @@ impl Scene { } } } + m.color = self.count_color(handle, m.color, self.current_bg()); if tint_selected && self.selected.contains(&handle) { m.color = [0.15, 0.55, 1.00, m.color[3]]; } @@ -1752,6 +1753,7 @@ impl Scene { ) }); } + model.color = self.count_color(context.root_handle, model.color, hatch_bg); if tint_selected && self.selected.contains(&context.root_handle) { model.color = [0.15, 0.55, 1.00, model.color[3]]; } diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 59e26575b..60d442f3f 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -4007,6 +4007,9 @@ impl Scene { ); } } + if self.meshes.contains_key(&h) { + material.diffuse = self.count_color(h, material.diffuse, bg); + } (h, material) }) }) @@ -4108,9 +4111,28 @@ impl Scene { return; } self.count_display = display; + self.hatch_cache.borrow_mut().clear(); + self.recolor_meshes(); self.bump_geometry_no_blocks(); } + /// The colour count mode gives an object drawn as part of `handle` (a + /// top-level entity): the count colours, else faded; unchanged outside + /// count mode. + pub(crate) fn count_color(&self, handle: Handle, color: [f32; 4], bg: [f32; 4]) -> [f32; 4] { + let Some(count) = &self.count_display else { + return color; + }; + let [r, g, b, _] = if count.errors.contains(&handle) { + count.error_color + } else if count.counted.contains(&handle) { + count.color + } else { + return crate::scene::cache::block_cache::fade_toward_bg(color, bg); + }; + [r, g, b, color[3]] + } + /// Fade the colours of wires that belong to entities outside the REFEDIT /// keep set (no-op when not editing). The geometry is untouched, so /// hit-testing still works on faded entities. In count mode the counted @@ -4118,8 +4140,7 @@ impl Scene { fn apply_refedit_fade(&self, wires: &mut [WireModel], bg: [f32; 4]) { if let Some(count) = &self.count_display { for w in wires.iter_mut() { - let h = Self::handle_from_wire_name(&w.name); - w.color = match h { + w.color = match Self::handle_from_wire_name(&w.name) { Some(h) if count.errors.contains(&h) => count.error_color, Some(h) if count.counted.contains(&h) => count.color, _ => crate::scene::cache::block_cache::fade_toward_bg(w.color, bg), @@ -8745,6 +8766,14 @@ impl Scene { self.material_base_dir.as_deref(), ); } + if self.count_display.is_some() { + let own = transformed.instance_color.or(set.display_color()).unwrap_or([1.0; 4]); + let color = self.count_color(context.root_handle, own, self.current_bg()); + transformed.instance_color = Some(color); + if let Some(material) = transformed.material.as_mut() { + material.diffuse = color; + } + } transformed.instance_handle = Some(context.root_handle); out.push(transformed); }, diff --git a/src/ui/window/count_palette.rs b/src/ui/window/count_palette.rs index 1c33dc886..8802d0ca5 100644 --- a/src/ui/window/count_palette.rs +++ b/src/ui/window/count_palette.rs @@ -33,7 +33,8 @@ pub enum CountTarget { matching: [bool; 3], picked: bool, }, - /// Several picked objects counted as one group. + /// Picked objects (several, or one that is not a reference): the copies + /// of the group are counted. Group(Vec), } @@ -50,6 +51,10 @@ pub struct CountMode { pub cursor: Option, /// The scene geometry epoch the colouring was computed at. pub epoch: u64, + /// The area Entire model space set aside, for a later Current area. + pub remembered: Option<(Vec<[f64; 2]>, Option<(Handle, bool)>)>, + /// The result and count at `epoch` (recomputed when the drawing changes). + pub cache: Option<(CountResult, usize)>, } /// A count mode's result: the counted references and the overlapping @@ -80,13 +85,35 @@ impl CountMode { } pub fn result(&self, doc: &CadDocument) -> CountResult { + match &self.cache { + Some((result, _)) => result.clone(), + None => self.compute(doc).0, + } + } + + /// What the count shows: the counted references, or the copies of a group. + pub fn count(&self, doc: &CadDocument) -> usize { + match &self.cache { + Some((_, n)) => *n, + None => self.compute(doc).1, + } + } + + /// The result and the count, worked out from the drawing. + pub fn compute(&self, doc: &CadDocument) -> (CountResult, usize) { + if let Some(CountTarget::Group(handles)) = &self.target { + let groups = codec::count::group_matches(doc, handles, self.area.as_deref()); + let result = CountResult { counted: groups.concat(), errors: Vec::new(), overlapped: Vec::new() }; + return (result, groups.len()); + } + let result = self.block_result(doc); + let n = result.counted.len(); + (result, n) + } + + fn block_result(&self, doc: &CadDocument) -> CountResult { match &self.target { - None => CountResult::default(), - Some(CountTarget::Group(handles)) => CountResult { - counted: handles.iter().copied().filter(|h| doc.get_entity(*h).is_some()).collect(), - errors: Vec::new(), - overlapped: Vec::new(), - }, + None | Some(CountTarget::Group(_)) => CountResult::default(), Some(CountTarget::Block { name, .. }) => { let instances = self.instances(doc); let key = self.key(&instances); @@ -100,15 +127,6 @@ impl CountMode { } } - /// What the count shows: the counted references, or 1 for a group whose - /// objects all exist. - pub fn count(&self, doc: &CadDocument) -> usize { - match &self.target { - Some(CountTarget::Group(handles)) => usize::from(handles.iter().all(|h| doc.get_entity(*h).is_some())), - _ => self.result(doc).counted.len(), - } - } - /// The name the toolbar and the command line show for the target. pub fn target_name(&self) -> Option { match &self.target { @@ -137,6 +155,8 @@ pub struct CountPalette { pub color: i16, pub error_color: i16, pub service: bool, + /// The list's references for (document, geometry epoch, area). + pub list_cache: std::cell::RefCell>, std::rc::Rc>)>>, } impl Default for CountPalette { @@ -153,6 +173,7 @@ impl Default for CountPalette { color: 3, error_color: 1, service: true, + list_cache: Default::default(), } } } @@ -434,7 +455,12 @@ fn mode_view<'a>(palette: &'a CountPalette, mode: &'a CountMode, doc: &CadDocume let mut details = column![button( row![ crate::ui::icons::themed_success(CHECK_ICON, 12.0), - text(format!("{name}: {}", mode.count(doc))).size(12).width(Fill), + text(match mode.target { + Some(CountTarget::Group(_)) => crate::tf!("Geometries: {}", mode.count(doc)).into_owned(), + _ => format!("{name}: {}", mode.count(doc)), + }) + .size(12) + .width(Fill), ] .spacing(6) .align_y(iced::Center), @@ -488,16 +514,25 @@ pub fn view<'a>( palette: &'a CountPalette, mode: Option<&'a CountMode>, doc: &'a CadDocument, + epoch: u64, width: f32, auto_collapse: bool, ) -> Element<'a, Message> { let title_bar = crate::ui::dock::title_bar(PanelId::Count, crate::t!("Count palette").into_owned(), auto_collapse); - // ponytail: the list is recounted on every view; cache by geometry epoch - // if very large drawings make the palette slow. let body = match mode { Some(mode) if mode.target.is_some() => mode_view(palette, mode, doc), _ => { - let instances = codec::count::block_instances(doc, mode.and_then(|m| m.area.as_deref())); + let area = mode.and_then(|m| m.area.clone()); + let key = (doc as *const CadDocument as usize, epoch); + let mut cache = palette.list_cache.borrow_mut(); + let instances = match cache.as_ref() { + Some((d, e, a, list)) if (*d, *e) == key && *a == area => list.clone(), + _ => { + let list = std::rc::Rc::new(codec::count::block_instances(doc, area.as_deref())); + *cache = Some((key.0, key.1, area, list.clone())); + list + } + }; list_view(palette, &instances) } };