From 88839b783ae57336ae09cff7a995f16b5ea136f6 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:56:48 +0300 Subject: [PATCH] Match active view properties palette --- src/app/properties.rs | 154 ++++++++++++++---------- src/app/update/command.rs | 239 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+), 60 deletions(-) diff --git a/src/app/properties.rs b/src/app/properties.rs index 7879843ae..f97fbf6f1 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -247,28 +247,63 @@ impl OpenCADStudio { .then(|| header.stylesheet.clone()) }) .unwrap_or_else(|| "None".to_string()); - let ucs_per_viewport = scene + let active_model_vport = doc.vports.iter().find(|viewport| { + viewport + .name + .trim_start_matches('*') + .eq_ignore_ascii_case("active") + }); + let active_entity_viewport = scene .active_viewport .and_then(|handle| doc.get_entity(handle)) .and_then(|entity| match entity { - acadrust::EntityType::Viewport(viewport) => { - Some(viewport.ucs_per_viewport) - } + acadrust::EntityType::Viewport(viewport) => Some(viewport), _ => None, - }) - .unwrap_or(false); + }); + let ucs_icon_on = active_entity_viewport + .map(|viewport| viewport.ucs_icon_visible) + .or_else(|| active_model_vport.map(|viewport| viewport.ucsicon_lower)) + .unwrap_or(self.show_ucs_icon); + let ucs_icon_at_origin = active_entity_viewport + .map(|viewport| viewport.ucs_icon_at_origin) + .or_else(|| active_model_vport.map(|viewport| viewport.ucsicon_origin)) + .unwrap_or(self.ucs_icon_at_origin); + let ucs_per_viewport = active_entity_viewport + .map(|viewport| viewport.ucs_per_viewport) + .or_else(|| active_model_vport.map(|viewport| viewport.ucs_per_viewport)) + .unwrap_or(true); let ucs_name = tab .active_ucs .as_ref() .map(|ucs| ucs.name.trim()) .filter(|name| !name.is_empty()) - .unwrap_or("World") + .unwrap_or("") .to_string(); let annotation_scale = if header.current_annotation_scale.trim().is_empty() { "1:1".to_string() } else { header.current_annotation_scale.clone() }; + let annotation_scale_options = scene + .scale_list() + .into_iter() + .map(|(name, _, _)| name) + .collect(); + let mut plot_table_options = vec!["None".to_string()]; + plot_table_options.extend(crate::io::plot_style::available_ctb_names()); + let visual_style = match tab.visual_style.as_str() { + "Wireframe 2D" => t!("2D Wireframe").into_owned(), + "Wireframe 3D" => t!("3D Wireframe").into_owned(), + value => value.to_string(), + }; + let visual_style_options = crate::modules::view::visual_style::VISUAL_STYLES + .iter() + .map(|style| match style.label { + "Wireframe 2D" => t!("2D Wireframe").into_owned(), + "Wireframe 3D" => t!("3D Wireframe").into_owned(), + value => value.to_string(), + }) + .collect(); let sections = vec![ PropSection { title: t!("General").into_owned(), @@ -356,18 +391,23 @@ impl OpenCADStudio { } }, }, - read_only(t!("Plot style table").as_ref(), plot_table.clone()), + Property { + label: t!("Plot style table").into_owned(), + field: "view_plot_style_table", + value: PropValue::Choice { + selected: plot_table.clone(), + options: plot_table_options, + }, + }, read_only( t!("Plot table attached to").as_ref(), - if plot_table == "None" { - "None".to_string() - } else { - scene.current_layout.clone() - }, + scene.current_layout.clone(), ), read_only( t!("Plot table type").as_ref(), - if header.plotstyle_mode { + if plot_table == "None" { + t!("Not available").into_owned() + } else if header.plotstyle_mode { t!("Color-dependent plot styles").into_owned() } else { t!("Named plot styles").into_owned() @@ -388,55 +428,49 @@ impl OpenCADStudio { PropSection { title: t!("Misc").into_owned(), props: vec![ - read_only(t!("Annotation scale").as_ref(), annotation_scale), - read_only( - t!("UCS icon On").as_ref(), - if self.show_ucs_icon { "Yes" } else { "No" }.to_string(), - ), - read_only( - t!("UCS icon at origin").as_ref(), - if self.ucs_icon_at_origin { "Yes" } else { "No" } - .to_string(), - ), - read_only( - t!("UCS per viewport").as_ref(), - if ucs_per_viewport { "Yes" } else { "No" }.to_string(), - ), + Property { + label: t!("Annotation scale").into_owned(), + field: "view_annotation_scale", + value: PropValue::Choice { + selected: annotation_scale, + options: annotation_scale_options, + }, + }, + Property { + label: t!("UCS icon On").into_owned(), + field: "view_ucs_icon_on", + value: PropValue::Choice { + selected: if ucs_icon_on { "Yes" } else { "No" }.to_string(), + options: vec!["Yes".to_string(), "No".to_string()], + }, + }, + Property { + label: t!("UCS icon at origin").into_owned(), + field: "view_ucs_icon_at_origin", + value: PropValue::Choice { + selected: if ucs_icon_at_origin { "Yes" } else { "No" }.to_string(), + options: vec!["Yes".to_string(), "No".to_string()], + }, + }, + Property { + label: t!("UCS per viewport").into_owned(), + field: "view_ucs_per_viewport", + value: PropValue::Choice { + selected: if ucs_per_viewport { "Yes" } else { "No" }.to_string(), + options: vec!["Yes".to_string(), "No".to_string()], + }, + }, read_only(t!("UCS Name").as_ref(), ucs_name), - read_only(t!("Visual Style").as_ref(), tab.visual_style.clone()), + Property { + label: t!("Visual Style").into_owned(), + field: "view_visual_style", + value: PropValue::Choice { + selected: visual_style, + options: visual_style_options, + }, + }, ], }, - // Document-wide named-parameter table embedded here - // instead of a separate modal — see `PropValue:: - // ParamRow`'s doc comment. Belongs on the no- - // selection (drawing-level) page, not a per-entity - // one: a parameter isn't owned by any one entity. - PropSection { - title: t!("Parameters").into_owned(), - props: { - let mut props: Vec = scene - .named_parameters() - .iter() - .enumerate() - .map(|(index, p)| Property { - label: String::new(), - field: "named_parameter", - value: PropValue::ParamRow { - index, - name: p.name.clone(), - formula: p.source.clone(), - resolved: scene.named_parameters().resolve(&p.name).map_err(|e| e.to_string()), - }, - }) - .collect(); - props.push(Property { - label: String::new(), - field: "named_parameter_add", - value: PropValue::ParamAddRow, - }); - props - }, - }, ]; ui::PropertiesPanel { title: t!("No selection").into_owned(), diff --git a/src/app/update/command.rs b/src/app/update/command.rs index a23ddf81b..f64699b76 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -2993,6 +2993,245 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { self.tabs[i].dirty = true; self.refresh_properties(); } + "view_plot_style_table" => { + let next = if value.eq_ignore_ascii_case("None") { + String::new() + } else { + value.clone() + }; + let layout_name = self.tabs[i].scene.current_layout.clone(); + let current = self.tabs[i] + .scene + .document + .objects + .values() + .find_map(|object| { + let acadrust::objects::ObjectType::Layout(layout) = object + else { + return None; + }; + layout + .name + .eq_ignore_ascii_case(&layout_name) + .then(|| layout.plot_style_sheet.clone()) + }) + .unwrap_or_else(|| { + self.tabs[i].scene.document.header.stylesheet.clone() + }); + if current != next { + self.push_undo_snapshot(i, "PLOTSTYLE"); + for object in self.tabs[i].scene.document.objects.values_mut() { + if let acadrust::objects::ObjectType::Layout(layout) = object { + if layout.name.eq_ignore_ascii_case(&layout_name) { + layout.plot_style_sheet = next.clone(); + layout.plot_flags.plot_plot_styles = !next.is_empty(); + layout.plot_flags.show_plot_styles = !next.is_empty(); + } + } + } + self.tabs[i].scene.document.header.stylesheet = next; + self.tabs[i].dirty = true; + } + self.refresh_properties(); + } + "view_annotation_scale" => { + let current = self.tabs[i] + .scene + .document + .header + .current_annotation_scale + .clone(); + if !current.eq_ignore_ascii_case(&value) { + self.push_undo_snapshot(i, "CANNOSCALE"); + if self.tabs[i].scene.set_annotation_scale_named(&value).is_some() { + self.tabs[i].dirty = true; + } else { + self.discard_last_undo_entry(i); + } + } + self.refresh_properties(); + } + "view_ucs_icon_on" => { + let next = value.eq_ignore_ascii_case("Yes"); + let active_viewport = self.tabs[i].scene.active_viewport; + let current = active_viewport + .and_then(|handle| self.tabs[i].scene.document.get_entity(handle)) + .and_then(|entity| match entity { + acadrust::EntityType::Viewport(viewport) => { + Some(viewport.ucs_icon_visible) + } + _ => None, + }) + .or_else(|| { + self.tabs[i] + .scene + .document + .vports + .iter() + .find(|viewport| { + viewport + .name + .trim_start_matches('*') + .eq_ignore_ascii_case("active") + }) + .map(|viewport| viewport.ucsicon_lower) + }) + .unwrap_or(self.show_ucs_icon); + if current != next { + self.push_undo_snapshot(i, "UCSICON"); + if let Some(handle) = active_viewport { + if let Some(acadrust::EntityType::Viewport(viewport)) = + self.tabs[i].scene.document.get_entity_mut(handle) + { + viewport.ucs_icon_visible = next; + } + } else if let Some(viewport) = self.tabs[i] + .scene + .document + .vports + .iter_mut() + .find(|viewport| { + viewport + .name + .trim_start_matches('*') + .eq_ignore_ascii_case("active") + }) + { + viewport.ucsicon_lower = next; + } + self.show_ucs_icon = next; + self.persist_settings_if_changed(); + self.tabs[i].dirty = true; + } + self.refresh_properties(); + } + "view_ucs_icon_at_origin" => { + let next = value.eq_ignore_ascii_case("Yes"); + let active_viewport = self.tabs[i].scene.active_viewport; + let current = active_viewport + .and_then(|handle| self.tabs[i].scene.document.get_entity(handle)) + .and_then(|entity| match entity { + acadrust::EntityType::Viewport(viewport) => { + Some(viewport.ucs_icon_at_origin) + } + _ => None, + }) + .or_else(|| { + self.tabs[i] + .scene + .document + .vports + .iter() + .find(|viewport| { + viewport + .name + .trim_start_matches('*') + .eq_ignore_ascii_case("active") + }) + .map(|viewport| viewport.ucsicon_origin) + }) + .unwrap_or(self.ucs_icon_at_origin); + if current != next { + self.push_undo_snapshot(i, "UCSICON"); + if let Some(handle) = active_viewport { + if let Some(acadrust::EntityType::Viewport(viewport)) = + self.tabs[i].scene.document.get_entity_mut(handle) + { + viewport.ucs_icon_at_origin = next; + } + } else if let Some(viewport) = self.tabs[i] + .scene + .document + .vports + .iter_mut() + .find(|viewport| { + viewport + .name + .trim_start_matches('*') + .eq_ignore_ascii_case("active") + }) + { + viewport.ucsicon_origin = next; + } + self.ucs_icon_at_origin = next; + self.persist_settings_if_changed(); + self.tabs[i].dirty = true; + } + self.refresh_properties(); + } + "view_ucs_per_viewport" => { + let next = value.eq_ignore_ascii_case("Yes"); + let active_viewport = self.tabs[i].scene.active_viewport; + let current = active_viewport + .and_then(|handle| self.tabs[i].scene.document.get_entity(handle)) + .and_then(|entity| match entity { + acadrust::EntityType::Viewport(viewport) => { + Some(viewport.ucs_per_viewport) + } + _ => None, + }) + .or_else(|| { + self.tabs[i] + .scene + .document + .vports + .iter() + .find(|viewport| { + viewport + .name + .trim_start_matches('*') + .eq_ignore_ascii_case("active") + }) + .map(|viewport| viewport.ucs_per_viewport) + }) + .unwrap_or(true); + if current != next { + self.push_undo_snapshot(i, "UCSVP"); + if let Some(handle) = active_viewport { + if let Some(acadrust::EntityType::Viewport(viewport)) = + self.tabs[i].scene.document.get_entity_mut(handle) + { + viewport.ucs_per_viewport = next; + } + } else if let Some(viewport) = self.tabs[i] + .scene + .document + .vports + .iter_mut() + .find(|viewport| { + viewport + .name + .trim_start_matches('*') + .eq_ignore_ascii_case("active") + }) + { + viewport.ucs_per_viewport = next; + } + self.tabs[i].dirty = true; + } + self.refresh_properties(); + } + "view_visual_style" => { + use acadrust::entities::ViewportRenderMode as Mode; + let mode = match value.as_str() { + "2D Wireframe" | "Wireframe 2D" => Some(Mode::Wireframe2D), + "3D Wireframe" | "Wireframe 3D" => Some(Mode::Wireframe3D), + "Hidden Line" => Some(Mode::HiddenLine), + "Flat Shaded" => Some(Mode::FlatShaded), + "Gouraud Shaded" => Some(Mode::GouraudShaded), + "Flat Shaded + Edges" => Some(Mode::FlatShadedWithEdges), + "Gouraud Shaded + Edges" => { + Some(Mode::GouraudShadedWithEdges) + } + _ => None, + }; + if let Some(mode) = mode { + let task = self.on_set_render_mode(mode); + self.refresh_properties(); + return task; + } + self.refresh_properties(); + } _ => {} } }