feat(display): add model lineweight scale

This commit is contained in:
Hakan Seven committed 2026-09-02 23:03:04 +03:00
1 parent edbc4d8ff8
commit 76bdab1e5b
11 files changed
+66 -19

No files matched your search

+5
View File
@@ -492,6 +492,8 @@ pub(super) struct OpenCADStudio {
crosshair_color: Option<[u8; 3]>, crosshair_color: Option<[u8; 3]>,
/// Editable Options buffer for the crosshair colour. /// Editable Options buffer for the crosshair colour.
crosshair_color_input: String, 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 state and active axis pair.
isometric_drafting: bool, isometric_drafting: bool,
iso_plane: settings::IsoPlane, iso_plane: settings::IsoPlane,
@@ -1910,6 +1912,8 @@ pub enum Message {
PickBoxChanged(i32), PickBoxChanged(i32),
/// Set CURSORTYPE from Options. /// Set CURSORTYPE from Options.
CursorTypeChanged(settings::CursorType), CursorTypeChanged(settings::CursorType),
/// Set the model-space lineweight preview scale from Options.
LineweightDisplayScaleChanged(i32),
/// Edit the optional crosshair RGB value; blank restores automatic contrast. /// Edit the optional crosshair RGB value; blank restores automatic contrast.
CrosshairColorChanged(String), CrosshairColorChanged(String),
/// Set the default type/version used when first saving a new drawing. /// Set the default type/version used when first saving a new drawing.
@@ -3280,6 +3284,7 @@ impl OpenCADStudio {
cursor_type: settings::CursorType::Crosshair, cursor_type: settings::CursorType::Crosshair,
crosshair_color: None, crosshair_color: None,
crosshair_color_input: String::new(), crosshair_color_input: String::new(),
lineweight_display_scale: 100,
isometric_drafting: false, isometric_drafting: false,
iso_plane: settings::IsoPlane::Left, iso_plane: settings::IsoPlane::Left,
snap_angle_deg: 0.0, snap_angle_deg: 0.0,
+3
View File
@@ -164,6 +164,8 @@ pub struct UserSettings {
pub cursor_type: CursorType, pub cursor_type: CursorType,
/// Explicit crosshair RGB. `None` keeps automatic background contrast. /// Explicit crosshair RGB. `None` keeps automatic background contrast.
pub crosshair_color: Option<[u8; 3]>, 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. /// Isometric drafting changes the grid and crosshair to the active axis pair.
pub isometric_drafting: bool, pub isometric_drafting: bool,
pub iso_plane: IsoPlane, pub iso_plane: IsoPlane,
@@ -282,6 +284,7 @@ impl Default for UserSettings {
pick_box: 3, pick_box: 3,
cursor_type: CursorType::Crosshair, cursor_type: CursorType::Crosshair,
crosshair_color: None, crosshair_color: None,
lineweight_display_scale: 100,
isometric_drafting: false, isometric_drafting: false,
iso_plane: IsoPlane::Left, iso_plane: IsoPlane::Left,
snap_angle_deg: 0.0, snap_angle_deg: 0.0,
+1 -1
View File
@@ -124,7 +124,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
self.tab_counter += 1; self.tab_counter += 1;
self.tabs[0] = crate::app::document::DocumentTab::new_drawing(self.tab_counter); self.tabs[0] = crate::app::document::DocumentTab::new_drawing(self.tab_counter);
self.active_tab = 0; self.active_tab = 0;
self.apply_bg_default(0); self.apply_display_defaults(0);
} else { } else {
self.tabs.remove(idx); self.tabs.remove(idx);
if self.active_tab >= self.tabs.len() { if self.active_tab >= self.tabs.len() {
+1 -1
View File
@@ -210,7 +210,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
self.tabs[0] = self.tabs[0] =
crate::app::document::DocumentTab::new_drawing(self.tab_counter); crate::app::document::DocumentTab::new_drawing(self.tab_counter);
self.active_tab = 0; self.active_tab = 0;
self.apply_bg_default(0); self.apply_display_defaults(0);
} else { } else {
self.tabs.remove(idx); self.tabs.remove(idx);
if self.active_tab >= self.tabs.len() { if self.active_tab >= self.tabs.len() {
+13 -13
View File
@@ -364,6 +364,7 @@ impl OpenCADStudio {
pick_box: self.pick_box, pick_box: self.pick_box,
cursor_type: self.cursor_type, cursor_type: self.cursor_type,
crosshair_color: self.crosshair_color, crosshair_color: self.crosshair_color,
lineweight_display_scale: self.lineweight_display_scale,
isometric_drafting: self.isometric_drafting, isometric_drafting: self.isometric_drafting,
iso_plane: self.iso_plane, iso_plane: self.iso_plane,
snap_angle_deg: self.snap_angle_deg, snap_angle_deg: self.snap_angle_deg,
@@ -416,6 +417,7 @@ impl OpenCADStudio {
.crosshair_color .crosshair_color
.map(crate::app::config::rgb_to_hex) .map(crate::app::config::rgb_to_hex)
.unwrap_or_default(); .unwrap_or_default();
self.lineweight_display_scale = s.lineweight_display_scale.clamp(25, 200);
self.isometric_drafting = s.isometric_drafting; self.isometric_drafting = s.isometric_drafting;
self.iso_plane = s.iso_plane; self.iso_plane = s.iso_plane;
self.snap_angle_deg = if s.snap_angle_deg.is_finite() { self.snap_angle_deg = if s.snap_angle_deg.is_finite() {
@@ -469,11 +471,10 @@ impl OpenCADStudio {
.take(200) .take(200)
.map(|(k, v)| (k.clone(), *v)) .map(|(k, v)| (k.clone(), *v))
.collect(); .collect();
// Push the restored background onto every drawing tab that exists now // Push restored display defaults onto every drawing tab that exists now.
// (the start tab and any initial drawing). Tabs created later pick it // Tabs created later pick them up at their construction site.
// up via `apply_bg_default` at their construction site.
for idx in 0..self.tabs.len() { for idx in 0..self.tabs.len() {
self.apply_bg_default(idx); self.apply_display_defaults(idx);
} }
self.rebuild_ribbon_modules(); self.rebuild_ribbon_modules();
} }
@@ -596,19 +597,18 @@ impl OpenCADStudio {
h.object_snap_mode = osmode; h.object_snap_mode = osmode;
} }
/// Apply the persisted default background(s) to tab `idx`. No-op for the /// Apply persisted viewport display defaults to tab `idx`.
/// start tab or when no default is set. Refreshes the tab's cached wires pub(in crate::app) fn apply_display_defaults(&mut self, idx: usize) {
/// and meshes so background-adaptive colours pick up the change.
pub(in crate::app) fn apply_bg_default(&mut self, idx: usize) {
let bg = self.default_bg_color; let bg = self.default_bg_color;
let paper_bg = self.default_paper_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]; let tab = &mut self.tabs[idx];
if tab.is_start { if tab.is_start {
return; 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 { if let Some(c) = bg {
tab.bg_color = Some(c); tab.bg_color = Some(c);
tab.scene.bg_color = c; tab.scene.bg_color = c;
@@ -1286,7 +1286,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
self.tabs.push(new_tab); self.tabs.push(new_tab);
let idx = self.tabs.len() - 1; let idx = self.tabs.len() - 1;
self.active_tab = idx; self.active_tab = idx;
self.apply_bg_default(idx); self.apply_display_defaults(idx);
idx idx
}; };
@@ -2381,7 +2381,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
self.tab_counter += 1; self.tab_counter += 1;
self.tabs[i] = crate::app::document::DocumentTab::new_drawing(self.tab_counter); self.tabs[i] = crate::app::document::DocumentTab::new_drawing(self.tab_counter);
self.active_tab = i; self.active_tab = i;
self.apply_bg_default(i); self.apply_display_defaults(i);
self.update(Message::OpenPathPicked(Some(( self.update(Message::OpenPathPicked(Some((
conflict.path, conflict.path,
metadata.len(), metadata.len(),
+11 -1
View File
@@ -1271,7 +1271,7 @@ impl OpenCADStudio {
// creating one doesn't silently reset them (its header default is // creating one doesn't silently reset them (its header default is
// 0 = no snaps); saving then persists them into the file. // 0 = no snaps); saving then persists them into the file.
self.stamp_header_sysvars(idx); self.stamp_header_sysvars(idx);
self.apply_bg_default(idx); self.apply_display_defaults(idx);
self.sync_ribbon_layers(); self.sync_ribbon_layers();
self.sync_ribbon_styles(); self.sync_ribbon_styles();
// #21: reset ribbon Color / Linetype / Lineweight to the // #21: reset ribbon Color / Linetype / Lineweight to the
@@ -5787,6 +5787,16 @@ impl OpenCADStudio {
Task::none() 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) => { Message::CrosshairColorChanged(value) => {
self.crosshair_color_input = value.clone(); self.crosshair_color_input = value.clone();
if value.trim().is_empty() { if value.trim().is_empty() {
+1
View File
@@ -189,6 +189,7 @@ impl OpenCADStudio {
self.cursor_type, self.cursor_type,
self.crosshair_color, self.crosshair_color,
&self.crosshair_color_input, &self.crosshair_color_input,
self.lineweight_display_scale,
flow, flow,
) )
}, },
+3
View File
@@ -1469,6 +1469,8 @@ pub struct Scene {
/// Whether entity transparency is honoured on screen. When false the /// Whether entity transparency is honoured on screen. When false the
/// wire shader forces every line opaque (a uniform toggle, no retessellate). /// wire shader forces every line opaque (a uniform toggle, no retessellate).
pub transparency_display: bool, 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. /// Selection filter: entity-type names excluded from interactive picking.
/// Empty = every type is selectable. /// Empty = every type is selectable.
pub selection_filter: HashSet<String>, pub selection_filter: HashSet<String>,
@@ -1878,6 +1880,7 @@ impl Scene {
refedit_keep: None, refedit_keep: None,
hover_highlight: None, hover_highlight: None,
transparency_display: true, transparency_display: true,
model_lineweight_scale: 1.0,
selection_filter: HashSet::default(), selection_filter: HashSet::default(),
preview_wires: vec![], preview_wires: vec![],
preview_hatches: Arc::new(Vec::new()), preview_hatches: Arc::new(Vec::new()),
+2 -2
View File
@@ -25,8 +25,8 @@ pub struct Uniforms {
/// zooming inside MSPACE changes one scalar instead of re-tessellating and /// zooming inside MSPACE changes one scalar instead of re-tessellating and
/// re-uploading every dashed wire. /// re-uploading every dashed wire.
pub linetype_scale: f32, pub linetype_scale: f32,
/// Multiplier for screen-space lineweights. Model space keeps `1.0`; /// Multiplier for screen-space lineweights. Model space uses the user's
/// paper layouts derive it from the current paper zoom. /// preview scale; paper layouts derive it from the current paper zoom.
pub lineweight_scale: f32, pub lineweight_scale: f32,
// ── Relative-to-eye (double-single) additions ─────────────────────────── // ── Relative-to-eye (double-single) additions ───────────────────────────
+3 -1
View File
@@ -3847,7 +3847,9 @@ impl Scene {
full_bounds, full_bounds,
self.document.header.lineweight_display || display_plot_lineweights, 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_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; 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; uniforms.lineweight_scale = paper_units_per_mm / paper_per_pixel / MM_TO_PX;
+23
View File
@@ -39,6 +39,7 @@ pub fn view_window<'a>(
cursor_type: CursorType, cursor_type: CursorType,
crosshair_color: Option<[u8; 3]>, crosshair_color: Option<[u8; 3]>,
crosshair_color_input: &'a str, crosshair_color_input: &'a str,
lineweight_display_scale: i32,
sizing: crate::ui::modal::ModalSizing, sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let selected_format = crate::io::SAVE_FORMAT_OPTIONS 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.")) text(crate::t!("Leave the color blank to keep automatic viewport contrast."))
.size(11) .size(11)
.width(sizing.width), .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) .spacing(0)
.width(sizing.width); .width(sizing.width);