diff --git a/src/app/mod.rs b/src/app/mod.rs index 8262c8bd0..4292bfc79 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -492,6 +492,8 @@ pub(super) struct OpenCADStudio { crosshair_color: Option<[u8; 3]>, /// Editable Options buffer for the crosshair colour. crosshair_color_input: String, + /// Model-space lineweight preview scale, in percent (25..=200). + lineweight_display_scale: i32, /// Isometric drafting state and active axis pair. isometric_drafting: bool, iso_plane: settings::IsoPlane, @@ -1910,6 +1912,8 @@ pub enum Message { PickBoxChanged(i32), /// Set CURSORTYPE from Options. CursorTypeChanged(settings::CursorType), + /// Set the model-space lineweight preview scale from Options. + LineweightDisplayScaleChanged(i32), /// Edit the optional crosshair RGB value; blank restores automatic contrast. CrosshairColorChanged(String), /// Set the default type/version used when first saving a new drawing. @@ -3280,6 +3284,7 @@ impl OpenCADStudio { cursor_type: settings::CursorType::Crosshair, crosshair_color: None, crosshair_color_input: String::new(), + lineweight_display_scale: 100, isometric_drafting: false, iso_plane: settings::IsoPlane::Left, snap_angle_deg: 0.0, diff --git a/src/app/settings.rs b/src/app/settings.rs index 791f4070a..097fbad9d 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -164,6 +164,8 @@ pub struct UserSettings { pub cursor_type: CursorType, /// Explicit crosshair RGB. `None` keeps automatic background contrast. pub crosshair_color: Option<[u8; 3]>, + /// Model-space lineweight preview scale as a percentage. + pub lineweight_display_scale: i32, /// Isometric drafting changes the grid and crosshair to the active axis pair. pub isometric_drafting: bool, pub iso_plane: IsoPlane, @@ -282,6 +284,7 @@ impl Default for UserSettings { pick_box: 3, cursor_type: CursorType::Crosshair, crosshair_color: None, + lineweight_display_scale: 100, isometric_drafting: false, iso_plane: IsoPlane::Left, snap_angle_deg: 0.0, diff --git a/src/app/update/command.rs b/src/app/update/command.rs index a1eb51675..59cfba4a5 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -124,7 +124,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { self.tab_counter += 1; self.tabs[0] = crate::app::document::DocumentTab::new_drawing(self.tab_counter); self.active_tab = 0; - self.apply_bg_default(0); + self.apply_display_defaults(0); } else { self.tabs.remove(idx); if self.active_tab >= self.tabs.len() { diff --git a/src/app/update/dialog.rs b/src/app/update/dialog.rs index 10795046f..47ba96aba 100644 --- a/src/app/update/dialog.rs +++ b/src/app/update/dialog.rs @@ -210,7 +210,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven self.tabs[0] = crate::app::document::DocumentTab::new_drawing(self.tab_counter); self.active_tab = 0; - self.apply_bg_default(0); + self.apply_display_defaults(0); } else { self.tabs.remove(idx); if self.active_tab >= self.tabs.len() { diff --git a/src/app/update/file.rs b/src/app/update/file.rs index fe960b6ca..269e57a13 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -364,6 +364,7 @@ impl OpenCADStudio { pick_box: self.pick_box, cursor_type: self.cursor_type, crosshair_color: self.crosshair_color, + lineweight_display_scale: self.lineweight_display_scale, isometric_drafting: self.isometric_drafting, iso_plane: self.iso_plane, snap_angle_deg: self.snap_angle_deg, @@ -416,6 +417,7 @@ impl OpenCADStudio { .crosshair_color .map(crate::app::config::rgb_to_hex) .unwrap_or_default(); + self.lineweight_display_scale = s.lineweight_display_scale.clamp(25, 200); self.isometric_drafting = s.isometric_drafting; self.iso_plane = s.iso_plane; self.snap_angle_deg = if s.snap_angle_deg.is_finite() { @@ -469,11 +471,10 @@ impl OpenCADStudio { .take(200) .map(|(k, v)| (k.clone(), *v)) .collect(); - // Push the restored background onto every drawing tab that exists now - // (the start tab and any initial drawing). Tabs created later pick it - // up via `apply_bg_default` at their construction site. + // Push restored display defaults onto every drawing tab that exists now. + // Tabs created later pick them up at their construction site. for idx in 0..self.tabs.len() { - self.apply_bg_default(idx); + self.apply_display_defaults(idx); } self.rebuild_ribbon_modules(); } @@ -596,19 +597,18 @@ impl OpenCADStudio { h.object_snap_mode = osmode; } - /// Apply the persisted default background(s) to tab `idx`. No-op for the - /// start tab or when no default is set. Refreshes the tab's cached wires - /// and meshes so background-adaptive colours pick up the change. - pub(in crate::app) fn apply_bg_default(&mut self, idx: usize) { + /// Apply persisted viewport display defaults to tab `idx`. + pub(in crate::app) fn apply_display_defaults(&mut self, idx: usize) { let bg = self.default_bg_color; let paper_bg = self.default_paper_bg_color; - if bg.is_none() && paper_bg.is_none() { - return; - } let tab = &mut self.tabs[idx]; if tab.is_start { return; } + tab.scene.model_lineweight_scale = self.lineweight_display_scale as f32 / 100.0; + if bg.is_none() && paper_bg.is_none() { + return; + } if let Some(c) = bg { tab.bg_color = Some(c); tab.scene.bg_color = c; @@ -1286,7 +1286,7 @@ pub(super) fn on_open_file(&mut self) -> Task { self.tabs.push(new_tab); let idx = self.tabs.len() - 1; self.active_tab = idx; - self.apply_bg_default(idx); + self.apply_display_defaults(idx); idx }; @@ -2381,7 +2381,7 @@ pub(super) fn on_open_file(&mut self) -> Task { self.tab_counter += 1; self.tabs[i] = crate::app::document::DocumentTab::new_drawing(self.tab_counter); self.active_tab = i; - self.apply_bg_default(i); + self.apply_display_defaults(i); self.update(Message::OpenPathPicked(Some(( conflict.path, metadata.len(), diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index e2d7e0c3c..1f05bb5c4 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -1271,7 +1271,7 @@ impl OpenCADStudio { // creating one doesn't silently reset them (its header default is // 0 = no snaps); saving then persists them into the file. self.stamp_header_sysvars(idx); - self.apply_bg_default(idx); + self.apply_display_defaults(idx); self.sync_ribbon_layers(); self.sync_ribbon_styles(); // #21: reset ribbon Color / Linetype / Lineweight to the @@ -5787,6 +5787,16 @@ impl OpenCADStudio { Task::none() } + Message::LineweightDisplayScaleChanged(value) => { + self.lineweight_display_scale = value.clamp(25, 200); + let scale = self.lineweight_display_scale as f32 / 100.0; + for tab in &mut self.tabs { + tab.scene.model_lineweight_scale = scale; + } + self.persist_settings_if_changed(); + Task::none() + } + Message::CrosshairColorChanged(value) => { self.crosshair_color_input = value.clone(); if value.trim().is_empty() { diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 444b5fe4a..6a4e896c4 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -189,6 +189,7 @@ impl OpenCADStudio { self.cursor_type, self.crosshair_color, &self.crosshair_color_input, + self.lineweight_display_scale, flow, ) }, diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 21c7be850..1e9da7133 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1469,6 +1469,8 @@ pub struct Scene { /// Whether entity transparency is honoured on screen. When false the /// wire shader forces every line opaque (a uniform toggle, no retessellate). pub transparency_display: bool, + /// User-adjustable model-space lineweight preview multiplier. + pub model_lineweight_scale: f32, /// Selection filter: entity-type names excluded from interactive picking. /// Empty = every type is selectable. pub selection_filter: HashSet, @@ -1878,6 +1880,7 @@ impl Scene { refedit_keep: None, hover_highlight: None, transparency_display: true, + model_lineweight_scale: 1.0, selection_filter: HashSet::default(), preview_wires: vec![], preview_hatches: Arc::new(Vec::new()), diff --git a/src/scene/pipeline/uniforms.rs b/src/scene/pipeline/uniforms.rs index 2d2b59420..d3dd549d0 100644 --- a/src/scene/pipeline/uniforms.rs +++ b/src/scene/pipeline/uniforms.rs @@ -25,8 +25,8 @@ pub struct Uniforms { /// zooming inside MSPACE changes one scalar instead of re-tessellating and /// re-uploading every dashed wire. pub linetype_scale: f32, - /// Multiplier for screen-space lineweights. Model space keeps `1.0`; - /// paper layouts derive it from the current paper zoom. + /// Multiplier for screen-space lineweights. Model space uses the user's + /// preview scale; paper layouts derive it from the current paper zoom. pub lineweight_scale: f32, // ── Relative-to-eye (double-single) additions ─────────────────────────── diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 180c9943d..34ea633f0 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -3847,7 +3847,9 @@ impl Scene { full_bounds, self.document.header.lineweight_display || display_plot_lineweights, ); - if self.current_layout != "Model" { + if self.current_layout == "Model" { + uniforms.lineweight_scale = self.model_lineweight_scale; + } else { let paper_per_pixel = 2.0 * self.camera.borrow().ortho_size() / canvas.1.max(1.0); let paper_units_per_mm = self.paper_space_unit_factor() as f32; uniforms.lineweight_scale = paper_units_per_mm / paper_per_pixel / MM_TO_PX; diff --git a/src/ui/window/options.rs b/src/ui/window/options.rs index dcb83a4df..4ef3981d8 100644 --- a/src/ui/window/options.rs +++ b/src/ui/window/options.rs @@ -39,6 +39,7 @@ pub fn view_window<'a>( cursor_type: CursorType, crosshair_color: Option<[u8; 3]>, crosshair_color_input: &'a str, + lineweight_display_scale: i32, sizing: crate::ui::modal::ModalSizing, ) -> Element<'a, Message> { let selected_format = crate::io::SAVE_FORMAT_OPTIONS @@ -265,6 +266,28 @@ pub fn view_window<'a>( text(crate::t!("Leave the color blank to keep automatic viewport contrast.")) .size(11) .width(sizing.width), + Space::new().height(24), + text(crate::t!("Lineweight")).size(15), + Space::new().height(10), + row![ + text(crate::t!("Model display scale")).size(12).width(140), + slider( + 25..=200, + lineweight_display_scale.clamp(25, 200), + Message::LineweightDisplayScaleChanged, + ) + .step(1) + .width(Fill), + text(format!("{}%", lineweight_display_scale.clamp(25, 200))) + .size(11) + .width(44), + ] + .spacing(10) + .align_y(iced::Center), + Space::new().height(6), + text(crate::t!("Changes the on-screen width in Model without affecting plotted output.")) + .size(11) + .width(sizing.width), ] .spacing(0) .width(sizing.width);