diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index 934650290..7b9f22f2b 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -668,6 +668,7 @@ inventory::submit!(crate::command::CommandRegistration { "DGNFRAME", "PDFOSNAP", "UOSNAP", + "FIELDDISPLAY", "PDFIMPORTMODE", "PDFIMPORTFILTER", "PDFIMPORTLAYERS", diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index bde923ef3..2d888e318 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -1025,6 +1025,7 @@ impl OpenCADStudio { | "DWFOSNAP" | "DGNOSNAP" | "UOSNAP" + | "FIELDDISPLAY" | "PDFIMPORTMODE" | "PDFIMPORTFILTER" | "PDFIMPORTLAYERS" @@ -1232,6 +1233,40 @@ impl OpenCADStudio { } return Some(self.finish_dispatch(cmd)); } + if name == "FIELDDISPLAY" { + let current = i16::from(crate::entities::field::display()); + match value.as_deref().map(|v| v.trim().parse::().ok().filter(|v| (0..=1).contains(v))) { + Some(Some(mode)) => { + if current != mode { + crate::entities::field::set_display(mode == 1); + // A profile setting: redraw the fields of every open drawing. + for tab in &mut self.tabs { + let changes: Vec<_> = tab + .scene + .document + .entities() + .filter(|e| crate::entities::field::hosts_field(&tab.scene.document, e)) + .map(|e| (e.common().handle, crate::scene::ChangeKind::Modified)) + .collect(); + if !changes.is_empty() { + tab.scene.bump_entities(&changes); + } + } + self.save_config(); + } + } + Some(None) => { + self.command_line.push_error(crate::t!("Requires 0 or 1 only.").as_ref()); + self.command_line.push_output(&format!("Enter new value for FIELDDISPLAY <{current}>:")); + self.pending_setvar = Some(name.clone()); + } + None => { + self.command_line.push_output(&format!("Enter new value for FIELDDISPLAY <{current}>:")); + self.pending_setvar = Some(name.clone()); + } + } + return Some(self.finish_dispatch(cmd)); + } if matches!(name.as_str(), "PDFOSNAP" | "DWFOSNAP" | "DGNOSNAP" | "UOSNAP") { use crate::scene::model::pdf_vector as pv; use codec::entities::UnderlayType; diff --git a/src/app/settings.rs b/src/app/settings.rs index 530d660a5..13bb75d84 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -371,6 +371,8 @@ pub struct UserSettings { pub image_frame: i16, /// The Attribute Definition dialog's "Specify on-screen" choice. pub attdef_on_screen: bool, + /// FIELDDISPLAY: fields show on a gray background. + pub field_display: bool, /// Nested-copy symbol handling: false inserts, true binds. pub ncopy_bind: bool, /// Last Options page; unknown saved names fall back without rejecting the config. @@ -750,6 +752,7 @@ impl Default for UserSettings { grip_object_limit: DEFAULT_GRIP_OBJECT_LIMIT, image_frame: 1, attdef_on_screen: true, + field_display: true, ncopy_bind: false, cursor_type: CursorType::Crosshair, crosshair_color: None, diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 00ad0d722..5280d5946 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -677,6 +677,7 @@ impl OpenCADStudio { grip_object_limit: self.grip_object_limit, image_frame: crate::scene::frame::profile_image_mode(), attdef_on_screen: crate::modules::draw::draw::attdef::session().on_screen, + field_display: crate::entities::field::display(), ncopy_bind: self.ncopy_bind, cursor_type: self.cursor_type, crosshair_color: self.crosshair_color, @@ -771,6 +772,7 @@ impl OpenCADStudio { self.grip_object_limit = s.grip_object_limit.clamp(0, 32767); crate::scene::frame::set_profile_image_mode(s.image_frame); crate::modules::draw::draw::attdef::session().on_screen = s.attdef_on_screen; + crate::entities::field::set_display(s.field_display); self.ncopy_bind = s.ncopy_bind; self.cursor_type = s.cursor_type; self.crosshair_color = s.crosshair_color; diff --git a/src/entities/field.rs b/src/entities/field.rs index 19a12cca9..d4e06af1a 100644 --- a/src/entities/field.rs +++ b/src/entities/field.rs @@ -249,3 +249,29 @@ fn os_date_locale() -> codec::fields::DateLocale { #[cfg(not(target_os = "windows"))] codec::fields::DateLocale::default() } + +/// FIELDDISPLAY: whether fields show on a gray background (not plotted). +/// A profile setting, so one value for every drawing. +static FIELD_DISPLAY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true); + +pub fn display() -> bool { + FIELD_DISPLAY.load(std::sync::atomic::Ordering::Relaxed) +} + +pub fn set_display(on: bool) { + FIELD_DISPLAY.store(on, std::sync::atomic::Ordering::Relaxed); +} + +/// The gray the reference draws behind a field. +pub const BACKGROUND: [f32; 4] = [203.0 / 255.0, 203.0 / 255.0, 203.0 / 255.0, 1.0]; + +/// Whether `entity` hosts a field: its extension dictionary has ACAD_FIELD. +pub fn hosts_field(document: &CadDocument, entity: &codec::entities::EntityType) -> bool { + let Some(xdict) = entity.common().xdictionary_handle else { + return false; + }; + matches!( + document.objects.get(&xdict), + Some(codec::objects::ObjectType::Dictionary(d)) if d.get("ACAD_FIELD").is_some() + ) +} diff --git a/src/scene/convert/tessellate.rs b/src/scene/convert/tessellate.rs index 433932665..79459ef6c 100644 --- a/src/scene/convert/tessellate.rs +++ b/src/scene/convert/tessellate.rs @@ -1408,6 +1408,59 @@ pub fn tessellate( // also carries the glyph quads built above. if bins.is_empty() { let mut wires: Vec = Vec::new(); + // FIELDDISPLAY: a field shows on a gray box behind its + // glyphs, on screen only. + if text_aabb != WireModel::UNBOUNDED_AABB + && crate::entities::field::display() + && crate::entities::field::hosts_field(document, entity) + { + let corner_groups = match entity { + EntityType::MText(m) => { + let rotation = stroke_groups + .iter() + .find_map(|group| group.run.as_ref().map(|run| run.rotation as f64)) + .unwrap_or(m.rotation); + oriented_mtext_corner_groups(&sdf_verts, m, rotation, 0.0, anno) + } + EntityType::Text(t) => vec![oriented_text_corners( + &sdf_verts, + [t.insertion_point.x, t.insertion_point.y], + t.rotation, + 0.0, + )], + EntityType::AttributeEntity(a) => vec![oriented_text_corners( + &sdf_verts, + [a.insertion_point.x, a.insertion_point.y], + a.rotation, + 0.0, + )], + _ => Vec::new(), + }; + let mut ft = Vec::with_capacity(6 * corner_groups.len()); + let mut ftl = Vec::with_capacity(6 * corner_groups.len()); + for corners in &corner_groups { + for &k in &[0usize, 1, 2, 0, 2, 3] { + let (h, lo) = split_ds_xyz(corners[k][0], corners[k][1], elev_v); + ft.push(h); + ftl.push(lo); + } + } + if !ft.is_empty() { + wires.push(WireModel { + display_visible: true, + plot_visible: false, + name: name.clone(), + color: crate::entities::field::BACKGROUND, + selected, + line_weight_px, + aabb: WireModel::UNBOUNDED_AABB, + plinegen: true, + fill_tris: ft, + fill_tris_low: ftl, + ..Default::default() + }); + } + } // MTEXT background and frame follow the glyph bounds. if text_aabb != WireModel::UNBOUNDED_AABB { if let EntityType::MText(m) = entity {