From f0d00c1d53efc34c2dd7d1dfcf23e78a66fd59e7 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 24 Aug 2026 12:10:29 +0300 Subject: [PATCH] Align linear dimension properties and grips --- src/app/properties.rs | 25 +- src/app/update/command.rs | 33 ++- src/app/update/mod.rs | 16 +- src/entities/dim_override.rs | 272 +++++++++++++++++++++- src/entities/dimension.rs | 439 ++++++++++++++++++++++++++++++++--- 5 files changed, 749 insertions(+), 36 deletions(-) diff --git a/src/app/properties.rs b/src/app/properties.rs index a7a5088b3..7b5e481c0 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -1021,7 +1021,11 @@ impl OpenCADStudio { && s.name.eq_ignore_ascii_case("Standard")) }) { - sections.extend(crate::entities::dimension::style_sections(style)); + sections.extend(crate::entities::dimension::style_sections( + style, + d, + &self.tabs[i].scene.document, + )); // The style groups are a read-only mirror, but the // dim-line colour is an editable per-object override: @@ -1036,6 +1040,25 @@ impl OpenCADStudio { "dim_line_color", PropValue::ColorChoice(dim_c), ); + for (field, code, inherited) in [ + ("dim_ext_line_color", dov::DIMCLRE, style.dimclre), + ("dim_text_color", dov::DIMCLRT, style.dimclrt), + ( + "dim_text_fill_color", + dov::DIMTFILLCLR, + style.dimtfillclr, + ), + ] { + let color = dov::color(&d.base().common.extended_data, code) + .unwrap_or_else(|| { + acadrust::types::Color::from_index(inherited) + }); + set_row_value( + &mut sections, + field, + PropValue::ColorChoice(color), + ); + } } } diff --git a/src/app/update/command.rs b/src/app/update/command.rs index 83d007583..4ec4c44f6 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -1140,6 +1140,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { if matches!( item.action, GripMenuAction::Stretch + | GripMenuAction::MoveWithDimLine | GripMenuAction::MoveWithLeader | GripMenuAction::MoveIndependent ) { @@ -1839,7 +1840,24 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { } self.push_undo_snapshot(i, "CHPROP"); - if field == "vscale_std" { + if field.starts_with("dim_") { + for &handle in &handles { + if self.tabs[i].scene.is_layer_locked(handle) { + continue; + } + if matches!( + self.tabs[i].scene.document.get_entity(handle), + Some(acadrust::EntityType::Dimension(_)) + ) { + crate::entities::dim_override::set_property( + &mut self.tabs[i].scene.document, + handle, + field, + &value, + ); + } + } + } else if field == "vscale_std" { for &handle in &handles { if matches!( self.tabs[i].scene.document.get_entity(handle), @@ -2410,6 +2428,19 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { continue; } match field { + _ if field.starts_with("dim_") => { + if matches!( + self.tabs[i].scene.document.get_entity(handle), + Some(acadrust::EntityType::Dimension(_)) + ) { + crate::entities::dim_override::set_property( + &mut self.tabs[i].scene.document, + handle, + field, + &val, + ); + } + } "hyperlink" => { // Stored in the standard PE_URL XDATA // record; an empty value clears it. diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 679b3ad9a..2dd58b15b 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -4834,8 +4834,20 @@ impl OpenCADStudio { // line with the rest of the dim-colour stack (index-only through // the file layer). Guarded to leaders / dimensions so a mixed // selection can't stamp the override onto other entities. - if field == "dim_line_color" { + if matches!( + field.as_str(), + "dim_line_color" + | "dim_ext_line_color" + | "dim_text_color" + | "dim_text_fill_color" + ) { let aci = color.approximate_index(); + let code = match field.as_str() { + "dim_ext_line_color" => crate::entities::dim_override::DIMCLRE, + "dim_text_color" => crate::entities::dim_override::DIMCLRT, + "dim_text_fill_color" => crate::entities::dim_override::DIMTFILLCLR, + _ => crate::entities::dim_override::DIMCLRD, + }; let targets: Vec = handles .iter() .copied() @@ -4853,7 +4865,7 @@ impl OpenCADStudio { crate::entities::dim_override::set( &mut self.tabs[i].scene.document, handle, - crate::entities::dim_override::DIMCLRD, + code, Some(acadrust::xdata::XDataValue::Integer16(aci)), ); } diff --git a/src/entities/dim_override.rs b/src/entities/dim_override.rs index 7535f1438..688430736 100644 --- a/src/entities/dim_override.rs +++ b/src/entities/dim_override.rs @@ -9,7 +9,7 @@ use acadrust::types::Color; use acadrust::xdata::{ExtendedData, XDataValue}; -use acadrust::{CadDocument, Handle}; +use acadrust::{CadDocument, EntityType, Handle}; // DXF group codes of the dimension variables surfaced on the leader panel. pub const DIMSCALE: i16 = 40; // overall scale (real) @@ -229,3 +229,273 @@ pub fn set(doc: &mut CadDocument, handle: Handle, code: i16, value: Option Option { + Some(match field { + "dim_arrow_size" => DIMASZ, + "dim_line_ext" => DIMDLE, + "dim_ext_line_ext" => DIMEXE, + "dim_ext_line_offset" => DIMEXO, + "dim_ext_line_fixed_length" => DIMFXL, + "dim_text_height" => DIMTXT, + "dim_text_offset" => DIMGAP, + "dim_scale_overall" => DIMSCALE, + "dim_roundoff" => DIMRND, + "dim_scale_linear" => DIMLFAC, + "dim_alt_scale_factor" => DIMALTF, + "dim_alt_roundoff" => DIMALTRND, + "dim_tolerance_limit_lower" => DIMTM, + "dim_tolerance_limit_upper" => DIMTP, + "dim_tolerance_text_height" => DIMTFAC, + _ => return None, + }) +} + +pub fn property_int_code(field: &str) -> Option { + Some(match field { + "dim_line_lineweight" => DIMLWD, + "dim_ext_line_lineweight" => DIMLWE, + "dim_ext_line_fixed" => DIMFXLON, + "dim_text_pos_vert" => DIMTAD, + "dim_text_pos_hor" => DIMJUST, + "dim_text_outside_align" => DIMTOH, + "dim_text_inside_align" => DIMTIH, + "dim_fit" => DIMATFIT, + "dim_text_inside" => DIMTIX, + "dim_text_movement" => DIMTMOVE, + "dim_line_forced" => DIMTOFL, + "dim_line_inside" => DIMSOXD, + "dim_units" => DIMLUNIT, + "dim_precision" => DIMDEC, + "dim_decimal_separator" => DIMDSEP, + "dim_angle_format" => DIMAUNIT, + "dim_angle_precision" => DIMADEC, + "dim_alt_enabled" => DIMALT, + "dim_alt_format" => DIMALTU, + "dim_alt_precision" => DIMALTD, + "dim_tolerance_precision" => DIMTDEC, + "dim_tolerance_pos_vert" => DIMTOLJ, + _ => return None, + }) +} + +pub fn property_string_code(field: &str) -> Option { + Some(match field { + "dim_prefix_suffix" => DIMPOST, + "dim_alt_prefix_suffix" => DIMAPOST, + _ => return None, + }) +} + +fn yes(value: &str) -> Option { + match value.trim().to_ascii_lowercase().as_str() { + "yes" | "on" | "true" | "1" => Some(1), + "no" | "off" | "false" | "0" => Some(0), + _ => None, + } +} + +fn inherited_int(doc: &CadDocument, handle: Handle, code: i16) -> i16 { + let Some(EntityType::Dimension(dimension)) = doc.get_entity(handle) else { + return 0; + }; + if let Some(value) = int(&dimension.base().common.extended_data, code) { + return value; + } + let Some(style) = doc.dim_styles.iter().find(|style| { + style.name.eq_ignore_ascii_case(&dimension.base().style_name) + || (dimension.base().style_name.trim().is_empty() + && style.name.eq_ignore_ascii_case("Standard")) + }) else { + return 0; + }; + match code { + DIMSD1 => style.dimsd1 as i16, + DIMSD2 => style.dimsd2 as i16, + DIMSE1 => style.dimse1 as i16, + DIMSE2 => style.dimse2 as i16, + DIMZIN => style.dimzin, + DIMALTZ => style.dimaltz, + DIMTZIN => style.dimtzin, + DIMTOL => style.dimtol as i16, + DIMLIM => style.dimlim as i16, + _ => 0, + } +} + +pub fn set_property( + doc: &mut CadDocument, + handle: Handle, + field: &str, + value: &str, +) -> bool { + let trimmed = value.trim(); + let handle_field = match field { + "dim_arrowhead_1" => Some((DIMBLK1, true)), + "dim_arrowhead_2" => Some((DIMBLK2, true)), + "dim_linetype" => Some((DIMLTYPE, false)), + "dim_ext_linetype_1" => Some((DIMLTEX1, false)), + "dim_ext_linetype_2" => Some((DIMLTEX2, false)), + _ => None, + }; + if let Some((code, block)) = handle_field { + let resolved = if block { + (trimmed != "Closed filled") + .then(|| { + doc.block_records + .iter() + .find(|record| record.name == trimmed) + .map(|record| record.handle) + }) + .flatten() + } else { + doc.line_types + .iter() + .find(|line_type| line_type.name == trimmed) + .map(|line_type| line_type.handle) + }; + set(doc, handle, code, resolved.map(XDataValue::Handle)); + return true; + } + let inverse_boolean_code = match field { + "dim_line_1" => Some(DIMSD1), + "dim_line_2" => Some(DIMSD2), + "dim_ext_line_1" => Some(DIMSE1), + "dim_ext_line_2" => Some(DIMSE2), + _ => None, + }; + if let Some(code) = inverse_boolean_code { + let Some(visible) = yes(trimmed) else { + return false; + }; + set(doc, handle, code, Some(XDataValue::Integer16((visible == 0) as i16))); + return true; + } + let bit_field = match field { + "dim_suppress_leading_zeros" => Some((DIMZIN, 4)), + "dim_suppress_trailing_zeros" => Some((DIMZIN, 8)), + "dim_alt_suppress_leading_zeros" => Some((DIMALTZ, 4)), + "dim_alt_suppress_trailing_zeros" => Some((DIMALTZ, 8)), + "dim_tolerance_suppress_leading_zeros" => Some((DIMTZIN, 4)), + "dim_tolerance_suppress_trailing_zeros" => Some((DIMTZIN, 8)), + _ => None, + }; + if let Some((code, bit)) = bit_field { + let Some(enabled) = yes(trimmed) else { + return false; + }; + let current = inherited_int(doc, handle, code); + let value = if enabled != 0 { + current | bit + } else { + current & !bit + }; + set(doc, handle, code, Some(XDataValue::Integer16(value))); + return true; + } + if field == "dim_tolerance_display" { + let (tolerance, limits) = match trimmed.to_ascii_lowercase().as_str() { + "deviation" => (1, 0), + "limits" => (0, 1), + "none" => (0, 0), + _ => return false, + }; + set(doc, handle, DIMTOL, Some(XDataValue::Integer16(tolerance))); + set(doc, handle, DIMLIM, Some(XDataValue::Integer16(limits))); + return true; + } + if let Some(code) = property_real_code(field) { + if trimmed.is_empty() { + set(doc, handle, code, None); + } else if let Ok(number) = trimmed.parse::() { + set(doc, handle, code, Some(XDataValue::Real(number))); + } else { + return false; + } + return true; + } + if let Some(code) = property_string_code(field) { + set( + doc, + handle, + code, + (!trimmed.is_empty()).then(|| XDataValue::String(value.to_string())), + ); + return true; + } + if let Some(code) = property_int_code(field) { + let parsed = match field { + "dim_ext_line_fixed" + | "dim_text_outside_align" + | "dim_text_inside_align" + | "dim_text_inside" + | "dim_line_forced" + | "dim_line_inside" + | "dim_alt_enabled" => yes(trimmed), + "dim_decimal_separator" => trimmed + .chars() + .next() + .map(|character| character as i16), + "dim_units" => match trimmed.to_ascii_lowercase().as_str() { + "scientific" => Some(1), + "decimal" => Some(2), + "engineering" => Some(3), + "architectural" => Some(4), + "fractional" => Some(5), + "desktop" => Some(6), + _ => trimmed.parse().ok(), + }, + "dim_angle_format" => match trimmed.to_ascii_lowercase().as_str() { + "decimal degrees" => Some(0), + "degrees/minutes/seconds" => Some(1), + "gradians" => Some(2), + "radians" => Some(3), + "surveyors units" => Some(4), + _ => trimmed.parse().ok(), + }, + "dim_text_pos_vert" => match trimmed.to_ascii_lowercase().as_str() { + "centered" => Some(0), + "above" => Some(1), + "outside" => Some(2), + "jis" => Some(3), + "below" => Some(4), + _ => trimmed.parse().ok(), + }, + "dim_text_pos_hor" => match trimmed.to_ascii_lowercase().as_str() { + "centered" => Some(0), + "at extension line 1" => Some(1), + "at extension line 2" => Some(2), + "over extension line 1" => Some(3), + "over extension line 2" => Some(4), + _ => trimmed.parse().ok(), + }, + "dim_fit" => match trimmed.to_ascii_lowercase().as_str() { + "either text or arrows" => Some(0), + "arrows" => Some(1), + "text" => Some(2), + "both text and arrows" => Some(3), + "always keep text" => Some(4), + _ => trimmed.parse().ok(), + }, + "dim_text_movement" => match trimmed.to_ascii_lowercase().as_str() { + "move dimension line" => Some(0), + "add leader" => Some(1), + "move text freely" => Some(2), + _ => trimmed.parse().ok(), + }, + "dim_tolerance_pos_vert" => match trimmed.to_ascii_lowercase().as_str() { + "bottom" => Some(0), + "middle" => Some(1), + "top" => Some(2), + _ => trimmed.parse().ok(), + }, + _ => trimmed.parse().ok(), + }; + let Some(number) = parsed else { + return false; + }; + set(doc, handle, code, Some(XDataValue::Integer16(number))); + return true; + } + false +} diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index adda3e8f2..2b1708e62 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -60,6 +60,44 @@ fn base_props(base: &DimensionBase) -> Vec Vec { + if let Dimension::Linear(d) = dim { + let base = &d.base; + return vec![PropSection { + title: t!("Misc").into_owned(), + props: vec![ + crate::scene::model::object::Property { + label: t!("Dimension style").into_owned(), + field: "style_name", + value: crate::scene::model::object::PropValue::PlainText( + base.style_name.clone(), + ), + }, + ro( + t!("Measurement").as_ref(), + "measurement", + format!("{:.4}", d.measurement()), + ), + crate::scene::model::object::Property { + label: t!("Text override").into_owned(), + field: "text_override", + value: crate::scene::model::object::PropValue::PlainText( + base.text_override().unwrap_or("").to_string(), + ), + }, + edit_angle( + t!("Text rotation").as_ref(), + "text_rotation", + base.text_rotation.to_degrees(), + ), + edit_angle(t!("Dim line angle").as_ref(), "rotation", d.rotation.to_degrees()), + edit_angle( + t!("Extension line angle").as_ref(), + "ext_line_rotation", + d.ext_line_rotation.to_degrees(), + ), + ], + }]; + } let mut props = base_props(dim.base()); match dim { Dimension::Aligned(d) => { @@ -234,16 +272,12 @@ fn angular_props( fn apply_base_prop(base: &mut DimensionBase, field: &str, value: &str) -> bool { match field { - "text" => { - base.text = value.to_string(); - true - } - "user_text" => { - base.user_text = if value.trim().is_empty() { + "text" | "user_text" | "text_override" => { + base.set_text_override(if value.trim().is_empty() { None } else { Some(value.to_string()) - }; + }); true } "style_name" => { @@ -305,6 +339,8 @@ fn apply_geom_prop(dim: &mut Dimension, field: &str, value: &str) { Dimension::Arc(d) => apply_arc_fields(d, field, value), Dimension::LargeRadial(d) => apply_large_radial_fields(d, field, value), } + let definition_point = dim.definition_point(); + dim.base_mut().definition_point = definition_point; dim.base_mut().actual_measurement = dim.measurement(); } @@ -1031,6 +1067,24 @@ fn dimension_line_grip_position(dim: &Dimension) -> Option { Some((p1 + p2) * 0.5) } +fn above_dimension_text_position(dim: &Dimension) -> Option { + let center = dimension_line_grip_position(dim)?; + let (ax, ay) = match dim { + Dimension::Linear(d) => (d.rotation.cos(), d.rotation.sin()), + Dimension::Aligned(d) => { + let dx = d.second_point.x - d.first_point.x; + let dy = d.second_point.y - d.first_point.y; + let length = (dx * dx + dy * dy).sqrt(); + if length <= 1e-12 { + return None; + } + (dx / length, dy / length) + } + _ => return Some(center + DVec3::Y), + }; + Some(center + DVec3::new(-ay, ax, 0.0)) +} + impl Grippable for Dimension { fn grips(&self) -> Vec { // Auto-placed dimensions carry a zero text_middle_point sentinel; put @@ -1212,6 +1266,8 @@ impl Grippable for Dimension { restore_dim_text_relative_position(self, saved); } + let definition_point = self.definition_point(); + self.base_mut().definition_point = definition_point; self.base_mut().actual_measurement = self.measurement(); } @@ -1301,33 +1357,62 @@ impl Grippable for Dimension { b.text_middle_point.x = 0.0; b.text_middle_point.y = 0.0; b.text_middle_point.z = 0.0; + b.text_user_positioned = false; } A::Center if grip_id == text_grip => { - // Snap text to the centre of the dimension line. - // Approximate as midpoint of first/second extension - // origins for Linear / Aligned dimensions. - match self { - Dimension::Linear(d) => { - let mx = (d.first_point.x + d.second_point.x) * 0.5; - let my = (d.first_point.y + d.second_point.y) * 0.5; - d.base.text_middle_point.x = mx; - d.base.text_middle_point.y = my; - } - Dimension::Aligned(d) => { - let mx = (d.first_point.x + d.second_point.x) * 0.5; - let my = (d.first_point.y + d.second_point.y) * 0.5; - d.base.text_middle_point.x = mx; - d.base.text_middle_point.y = my; - } - _ => {} + if let Some(point) = dimension_line_grip_position(self) { + let base = self.base_mut(); + base.text_middle_point = Vector3::new(point.x, point.y, point.z); + base.text_user_positioned = true; + } + } + A::ReverseArrows => { + let base = self.base_mut(); + base.flip_arrow1 = !base.flip_arrow1; + base.flip_arrow2 = !base.flip_arrow2; + } + A::AboveDimLine if grip_id == text_grip => { + if let Some(point) = above_dimension_text_position(self) { + let base = self.base_mut(); + base.text_middle_point = Vector3::new(point.x, point.y + 1.0, point.z); + base.text_user_positioned = true; } } - // Stretch / Move-variants / Reverse Arrows / Rotate Text / - // Above Dim Line need either a follow-up drag or a numeric - // prompt — wired to default Stretch behaviour for now. _ => {} } } + + fn grip_menu_value_prompt( + &self, + grip_id: usize, + action: crate::scene::model::object::GripMenuAction, + ) -> Option<&'static str> { + use crate::scene::model::object::GripMenuAction as A; + let text_grip = match self { + Dimension::Linear(_) | Dimension::Aligned(_) => 3, + Dimension::Radius(_) | Dimension::Diameter(_) => 2, + Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => 4, + Dimension::Ordinate(_) => 3, + Dimension::Arc(d) => if d.has_leader { 6 } else { 4 }, + Dimension::LargeRadial(_) => 4, + }; + (grip_id == text_grip && matches!(action, A::RotateText)) + .then_some("Specify text rotation") + } + + fn apply_grip_menu_value( + &mut self, + grip_id: usize, + action: crate::scene::model::object::GripMenuAction, + value: f64, + ) { + use crate::scene::model::object::GripMenuAction as A; + if self.grip_menu_value_prompt(grip_id, action).is_some() + && matches!(action, A::RotateText) + { + self.base_mut().text_rotation = value.to_radians(); + } + } } // ── Tessellation ───────────────────────────────────────────────────────── @@ -1347,7 +1432,11 @@ use acadrust::types::{Color as AcadColor, Vector3}; /// Units, Alternate Units, Tolerances) built from the resolved DimStyle. These /// are read-only mirrors of the style's dimension variables, injected by the /// panel after the entity's own Misc/Geometry groups. -pub fn style_sections(style: &DimStyle) -> Vec { +pub fn style_sections( + style: &DimStyle, + dimension: &Dimension, + document: &CadDocument, +) -> Vec { let s = style; let yn = |b: bool| if b { "Yes" } else { "No" }; let onoff = |b: bool| if b { "On" } else { "Off" }; @@ -1368,11 +1457,36 @@ pub fn style_sections(style: &DimStyle) -> Vec Vec value.clone(), + _ => continue, + }; + if let Some(code) = crate::entities::dim_override::property_real_code(property.field) { + let value = crate::entities::dim_override::real(data, code) + .map(|number| number.to_string()) + .unwrap_or(inherited); + property.value = crate::scene::model::object::PropValue::EditText(value); + continue; + } + if let Some(code) = crate::entities::dim_override::property_string_code(property.field) { + let value = crate::entities::dim_override::string(data, code).unwrap_or(inherited); + property.value = crate::scene::model::object::PropValue::PlainText(value); + continue; + } + if let Some(code) = crate::entities::dim_override::property_int_code(property.field) { + let number = crate::entities::dim_override::int(data, code); + let value = number.map(|number| number.to_string()).unwrap_or(inherited); + property.value = match property.field { + "dim_ext_line_fixed" + | "dim_text_outside_align" + | "dim_text_inside_align" + | "dim_text_inside" + | "dim_line_forced" + | "dim_line_inside" + | "dim_alt_enabled" => crate::scene::model::object::PropValue::Choice { + selected: number.map(|number| yes_no(number != 0)).unwrap_or(value), + options: vec!["Yes".to_string(), "No".to_string()], + }, + "dim_units" => crate::scene::model::object::PropValue::Choice { + selected: linear_unit_label(number.unwrap_or(s.dimlunit)).to_string(), + options: vec![ + "Scientific", "Decimal", "Engineering", "Architectural", + "Fractional", "Desktop", + ] + .into_iter() + .map(str::to_string) + .collect(), + }, + "dim_angle_format" => crate::scene::model::object::PropValue::Choice { + selected: angle_unit_label(number.unwrap_or(s.dimaunit)).to_string(), + options: vec![ + "Decimal degrees", "Degrees/minutes/seconds", "Gradians", + "Radians", "Surveyors units", + ] + .into_iter() + .map(str::to_string) + .collect(), + }, + "dim_text_pos_vert" => choice_value( + match number.unwrap_or(s.dimtad) { + 1 => "Above", + 2 => "Outside", + 3 => "JIS", + 4 => "Below", + _ => "Centered", + }, + &["Centered", "Above", "Outside", "JIS", "Below"], + ), + "dim_text_pos_hor" => choice_value( + match number.unwrap_or(s.dimjust) { + 1 => "At extension line 1", + 2 => "At extension line 2", + 3 => "Over extension line 1", + 4 => "Over extension line 2", + _ => "Centered", + }, + &[ + "Centered", "At extension line 1", "At extension line 2", + "Over extension line 1", "Over extension line 2", + ], + ), + "dim_fit" => choice_value( + match number.unwrap_or(s.dimatfit) { + 1 => "Arrows", + 2 => "Text", + 3 => "Both text and arrows", + 4 => "Always keep text", + _ => "Either text or arrows", + }, + &[ + "Either text or arrows", "Arrows", "Text", + "Both text and arrows", "Always keep text", + ], + ), + "dim_text_movement" => choice_value( + match number.unwrap_or(s.dimtmove) { + 1 => "Add leader", + 2 => "Move text freely", + _ => "Move dimension line", + }, + &["Move dimension line", "Add leader", "Move text freely"], + ), + "dim_tolerance_pos_vert" => choice_value( + match number.unwrap_or(s.dimtolj) { + 1 => "Middle", + 2 => "Top", + _ => "Bottom", + }, + &["Bottom", "Middle", "Top"], + ), + _ => crate::scene::model::object::PropValue::EditText(value), + }; + } + } + + let integer_or = |code, inherited| { + crate::entities::dim_override::int(data, code).unwrap_or(inherited) + }; + for property in sections + .iter_mut() + .flat_map(|section| section.props.iter_mut()) + { + let selected = match property.field { + "dim_line_1" => Some(if integer_or(crate::entities::dim_override::DIMSD1, s.dimsd1 as i16) == 0 { "On" } else { "Off" }), + "dim_line_2" => Some(if integer_or(crate::entities::dim_override::DIMSD2, s.dimsd2 as i16) == 0 { "On" } else { "Off" }), + "dim_ext_line_1" => Some(if integer_or(crate::entities::dim_override::DIMSE1, s.dimse1 as i16) == 0 { "On" } else { "Off" }), + "dim_ext_line_2" => Some(if integer_or(crate::entities::dim_override::DIMSE2, s.dimse2 as i16) == 0 { "On" } else { "Off" }), + "dim_suppress_leading_zeros" => Some(if integer_or(crate::entities::dim_override::DIMZIN, s.dimzin) & 4 != 0 { "Yes" } else { "No" }), + "dim_suppress_trailing_zeros" => Some(if integer_or(crate::entities::dim_override::DIMZIN, s.dimzin) & 8 != 0 { "Yes" } else { "No" }), + "dim_alt_suppress_leading_zeros" => Some(if integer_or(crate::entities::dim_override::DIMALTZ, s.dimaltz) & 4 != 0 { "Yes" } else { "No" }), + "dim_alt_suppress_trailing_zeros" => Some(if integer_or(crate::entities::dim_override::DIMALTZ, s.dimaltz) & 8 != 0 { "Yes" } else { "No" }), + "dim_tolerance_suppress_leading_zeros" => Some(if integer_or(crate::entities::dim_override::DIMTZIN, s.dimtzin) & 4 != 0 { "Yes" } else { "No" }), + "dim_tolerance_suppress_trailing_zeros" => Some(if integer_or(crate::entities::dim_override::DIMTZIN, s.dimtzin) & 8 != 0 { "Yes" } else { "No" }), + _ => None, + }; + if let Some(selected) = selected { + let options = if matches!( + property.field, + "dim_line_1" | "dim_line_2" | "dim_ext_line_1" | "dim_ext_line_2" + ) { + &["On", "Off"][..] + } else { + &["Yes", "No"][..] + }; + property.value = choice_value(selected, options); + } + if property.field == "dim_tolerance_display" { + let limits = integer_or(crate::entities::dim_override::DIMLIM, s.dimlim as i16) != 0; + let tolerance = integer_or(crate::entities::dim_override::DIMTOL, s.dimtol as i16) != 0; + let selected = if limits { "Limits" } else if tolerance { "Deviation" } else { "None" }; + property.value = choice_value(selected, &["None", "Deviation", "Limits"]); + } + } + + let arrow_options: Vec = std::iter::once("Closed filled".to_string()) + .chain( + document + .block_records + .iter() + .map(|record| record.name.clone()) + .filter(|name| !name.is_empty()), + ) + .collect(); + let linetype_options: Vec = document + .line_types + .iter() + .map(|line_type| line_type.name.clone()) + .filter(|name| !name.is_empty()) + .collect(); + for property in sections + .iter_mut() + .flat_map(|section| section.props.iter_mut()) + { + let mut current = match &property.value { + crate::scene::model::object::PropValue::ReadOnly(value) => value.clone(), + _ => continue, + }; + if matches!(property.field, "dim_arrowhead_1" | "dim_arrowhead_2") { + let code = if property.field == "dim_arrowhead_1" { + crate::entities::dim_override::DIMBLK1 + } else { + crate::entities::dim_override::DIMBLK2 + }; + if let Some(handle) = crate::entities::dim_override::handle(data, code) { + current = block_name(document, handle, ""); + } + property.value = crate::scene::model::object::PropValue::Choice { + selected: current, + options: arrow_options.clone(), + }; + } else if matches!( + property.field, + "dim_linetype" | "dim_ext_linetype_1" | "dim_ext_linetype_2" + ) { + let code = match property.field { + "dim_linetype" => crate::entities::dim_override::DIMLTYPE, + "dim_ext_linetype_1" => crate::entities::dim_override::DIMLTEX1, + _ => crate::entities::dim_override::DIMLTEX2, + }; + if let Some(handle) = crate::entities::dim_override::handle(data, code) { + current = linetype_name(document, handle); + } + property.value = crate::scene::model::object::PropValue::Choice { + selected: current, + options: linetype_options.clone(), + }; + } + } + sections +} + +fn choice_value( + selected: &str, + options: &[&str], +) -> crate::scene::model::object::PropValue { + crate::scene::model::object::PropValue::Choice { + selected: selected.to_string(), + options: options.iter().map(|value| (*value).to_string()).collect(), + } +} + +fn block_name(document: &CadDocument, handle: acadrust::Handle, fallback: &str) -> String { + if handle.is_null() { + return if fallback.is_empty() { + "Closed filled".to_string() + } else { + fallback.to_string() + }; + } + document + .block_records + .iter() + .find(|record| record.handle == handle) + .map(|record| record.name.clone()) + .unwrap_or_else(|| fallback.to_string()) +} + +fn linetype_name(document: &CadDocument, handle: acadrust::Handle) -> String { + document + .line_types + .iter() + .find(|line_type| line_type.handle == handle) + .map(|line_type| line_type.name.clone()) + .unwrap_or_else(|| "ByBlock".to_string()) +} + +fn linear_unit_label(value: i16) -> &'static str { + match value { + 1 => "Scientific", + 3 => "Engineering", + 4 => "Architectural", + 5 => "Fractional", + 6 => "Desktop", + _ => "Decimal", + } +} + +fn angle_unit_label(value: i16) -> &'static str { + match value { + 1 => "Degrees/minutes/seconds", + 2 => "Gradians", + 3 => "Radians", + 4 => "Surveyors units", + _ => "Decimal degrees", + } } use acadrust::{CadDocument, EntityType, Handle}; @@ -4143,4 +4521,3 @@ mod arch_format_tests { assert_eq!(format_fractional(6.5, 0), "6 1/2"); } } -