diff --git a/Cargo.lock b/Cargo.lock index 808da012..c751ee84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.1" -source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=5b2ae66#5b2ae66d0bd7b0da2d13392d3c3332f8a39caa1f" +source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=564859e#564859e77197fdb98193b2477115709e6821229b" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index dc49bfb1..af5833c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ glam = { version = "0.33", features = ["bytemuck"] } rfd = "0.17" clap = { version = "4", features = ["derive"] } env_logger = "0.11" -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b2ae66", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "564859e", features = ["serde"] } cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b2b1d4b", features = ["acis", "offset"] } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" @@ -61,7 +61,7 @@ iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aa iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } [patch."https://github.com/HakanSeven12/cadcodec.git"] -acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "5b2ae66" } +acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "564859e" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] } diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index d35a3225..4f32d776 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -1246,6 +1246,12 @@ impl OpenCADStudio { .document .header .dimension_associativity; + let single_source_dimension = matches!( + &entity, + acadrust::EntityType::Dimension( + acadrust::entities::Dimension::Ordinate(_) + ) + ); let pending = if association_mode == 0 { let layer = self.tabs[i].active_layer.clone(); if layer != "0" || entity.as_entity().layer().is_empty() { @@ -1295,7 +1301,13 @@ impl OpenCADStudio { if association_mode == 2 { let sources = source.map_or_else( || self.tabs[i].scene.infer_dimension_sources(handle), - |source| [Some(source), Some(source)], + |source| { + if single_source_dimension { + [Some(source), None] + } else { + [Some(source), Some(source)] + } + }, ); self.tabs[i] .scene @@ -3066,7 +3078,6 @@ impl OpenCADStudio { stretched |= mv(&mut d.definition_point); } Dimension::Ordinate(d) => { - stretched |= mv(&mut d.definition_point); stretched |= mv(&mut d.feature_location); stretched |= mv(&mut d.leader_endpoint); } diff --git a/src/app/update/command.rs b/src/app/update/command.rs index ab25667d..8b753705 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -1154,10 +1154,17 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { .zip(self.tabs[i].selected_grips.iter()) .find(|(owner, g)| **owner == popup.handle && g.id == popup.grip_id) { - // "Move with Leader" drags the whole multileader; the - // others move just the picked grip. + // The synthetic whole-object grip is specific to a + // multileader. Dimension text grips implement their + // own leader relationship and must keep their real ID. + let is_multileader = matches!( + self.tabs[i].scene.document.get_entity(popup.handle), + Some(acadrust::EntityType::MultiLeader(_)) + ); let (grip_id, is_translate) = - if matches!(item.action, GripMenuAction::MoveWithLeader) { + if matches!(item.action, GripMenuAction::MoveWithLeader) + && is_multileader + { (crate::entities::multileader::MOVE_ALL_GRIP, true) } else { (popup.grip_id, g.is_midpoint) diff --git a/src/command.rs b/src/command.rs index 797b27b4..a79db230 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1212,7 +1212,7 @@ pub enum CmdResult { CommitEntitiesAndExit(Vec), /// Commit an acadrust entity to the document and end the command. CommitAndExit(EntityType), - /// Commit a linear/aligned dimension using the drawing's association mode. + /// Commit a dimension using the drawing's association mode. /// Object-selection workflows may retain their source. CommitDimension { entity: EntityType, diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index ae303266..b49fde1a 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -124,6 +124,23 @@ fn properties(dim: &Dimension) -> Vec { ], }]; } + if let Dimension::Ordinate(d) = dim { + return vec![PropSection { + title: t!("Misc").into_owned(), + props: vec![ + Property { + label: t!("Dimension style").into_owned(), + field: "style_name", + value: PropValue::PlainText(d.base.style_name.clone()), + }, + edit_angle( + t!("Rotation").as_ref(), + "ordinate_rotation", + -d.base.horizontal_direction.to_degrees(), + ), + ], + }]; + } let mut props = base_props(dim.base()); match dim { Dimension::Aligned(d) => { @@ -351,6 +368,40 @@ fn assign_deg(value: &str, target: &mut f64) -> bool { } fn apply_geom_prop(dim: &mut Dimension, field: &str, value: &str) { + let ordinate_auto_text = match dim { + Dimension::Ordinate(ordinate) if !ordinate.base.text_user_positioned => { + Some(dimension_text_pos_f64(dim, None, 2.5, 1.0)) + } + _ => None, + }; + if let Dimension::Ordinate(ordinate) = dim { + match field { + "ordinate_rotation" => { + if let Some(angle) = parse_f64(value) { + ordinate.base.horizontal_direction = -angle.to_radians(); + ordinate.refresh_measurement(); + } + return; + } + "text_x" | "text_y" => { + let old_text = ordinate_auto_text.unwrap_or(ordinate.base.text_middle_point); + let mut new_text = old_text; + let changed = if field == "text_x" { + assign_f64(value, &mut new_text.x) + } else { + assign_f64(value, &mut new_text.y) + }; + if changed { + let delta = new_text - old_text; + ordinate.leader_endpoint = ordinate.leader_endpoint + delta; + ordinate.base.text_middle_point = new_text; + ordinate.base.text_user_positioned = true; + } + return; + } + _ => {} + } + } if apply_base_prop(dim.base_mut(), field, value) { return; } @@ -709,6 +760,42 @@ fn apply_large_radial_fields(d: &mut DimensionLargeRadial, field: &str, value: & } fn apply_transform(dim: &mut Dimension, t: &EntityTransform) { + if matches!(dim, Dimension::Ordinate(_)) { + match t { + EntityTransform::Translate(delta) => dim.translate(Vector3::new( + delta.x, delta.y, delta.z, + )), + EntityTransform::Rotate { + center, + axis, + angle_rad, + } => crate::scene::view::transform::apply_standard_transform( + dim, + *center, + *axis, + *angle_rad, + ), + EntityTransform::Scale { center, factor } => { + crate::scene::view::transform::apply_standard_scale(dim, *center, *factor) + } + EntityTransform::Mirror { + p1, + p2, + working_normal, + } => acadrust::Entity::apply_transform( + dim, + &crate::scene::view::transform::reflection_about_working_line( + *p1, + *p2, + *working_normal, + ), + ), + EntityTransform::Affine(transform) => { + acadrust::Entity::apply_transform(dim, transform) + } + } + return; + } match t { EntityTransform::Translate(d) => dim.translate(acadrust::types::Vector3::new( d.x as f64, d.y as f64, d.z as f64, @@ -1193,10 +1280,9 @@ impl Grippable for Dimension { center_grip(4, text), ], Dimension::Ordinate(d) => vec![ - square_grip(0, dv3(&d.definition_point)), - center_grip(1, dv3(&d.feature_location)), - center_grip(2, dv3(&d.leader_endpoint)), - center_grip(3, text), + square_grip(0, dv3(&d.feature_location)), + center_grip(1, dv3(&d.leader_endpoint)), + center_grip(2, text), ], Dimension::Arc(d) => { let mut grips = vec![ @@ -1227,12 +1313,25 @@ impl Grippable for Dimension { fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { + if matches!(self, Dimension::Ordinate(_)) && grip_id == 2 { + let old_text = dimension_text_pos_f64(self, None, 2.5, 1.0); + let mut new_text = old_text; + apply_to_v3(&mut new_text, &apply); + let delta = new_text - old_text; + if let Dimension::Ordinate(d) = self { + d.leader_endpoint = d.leader_endpoint + delta; + d.base.text_middle_point = new_text; + d.base.text_user_positioned = true; + d.refresh_measurement(); + } + return; + } // Last grip always moves the text. let text_grip = match self { Dimension::Linear(_) | Dimension::Aligned(_) => 3, Dimension::Radius(_) | Dimension::Diameter(_) => 2, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => 4, - Dimension::Ordinate(_) => 3, + Dimension::Ordinate(_) => 2, Dimension::Arc(d) => if d.has_leader { 6 } else { 4 }, Dimension::LargeRadial(_) => 4, }; @@ -1288,9 +1387,15 @@ impl Grippable for Dimension { _ => {} }, Dimension::Ordinate(d) => match grip_id { - 0 => apply_to_v3(&mut d.definition_point, &apply), - 1 => apply_to_v3(&mut d.feature_location, &apply), - 2 => apply_to_v3(&mut d.leader_endpoint, &apply), + 0 => apply_to_v3(&mut d.feature_location, &apply), + 1 => { + let old = d.leader_endpoint; + apply_to_v3(&mut d.leader_endpoint, &apply); + if d.base.text_user_positioned { + d.base.text_middle_point = + d.base.text_middle_point + (d.leader_endpoint - old); + } + } _ => {} }, Dimension::Arc(d) => match grip_id { @@ -1326,7 +1431,7 @@ impl Grippable for Dimension { Dimension::Linear(_) | Dimension::Aligned(_) => (2, 3), Dimension::Radius(_) | Dimension::Diameter(_) => (1, 2), Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => (3, 4), - Dimension::Ordinate(_) => (0, 3), + Dimension::Ordinate(_) => (1, 2), Dimension::Arc(d) => (3, if d.has_leader { 6 } else { 4 }), Dimension::LargeRadial(_) => (3, 4), }; @@ -1365,6 +1470,11 @@ impl Grippable for Dimension { action: GripMenuAction::Center, }, ] + } else if matches!(self, Dimension::Ordinate(_)) { + vec![GripMenuItem { + label: "Stretch", + action: GripMenuAction::Stretch, + }] } else if grip_id == dim_line_grip { vec![ GripMenuItem { @@ -1394,7 +1504,7 @@ impl Grippable for Dimension { Dimension::Linear(_) | Dimension::Aligned(_) => (2, 3), Dimension::Radius(_) | Dimension::Diameter(_) => (1, 2), Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => (3, 4), - Dimension::Ordinate(_) => (0, 3), + Dimension::Ordinate(_) => (1, 2), Dimension::Arc(d) => (3, if d.has_leader { 6 } else { 4 }), Dimension::LargeRadial(_) => (3, 4), }; @@ -1441,7 +1551,7 @@ impl Grippable for Dimension { Dimension::Linear(_) | Dimension::Aligned(_) => 3, Dimension::Radius(_) | Dimension::Diameter(_) => 2, Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) => 4, - Dimension::Ordinate(_) => 3, + Dimension::Ordinate(_) => 2, Dimension::Arc(d) => if d.has_leader { 6 } else { 4 }, Dimension::LargeRadial(_) => 4, }; @@ -1682,7 +1792,7 @@ pub fn style_sections( _ => choice_value("None", &["None", "Background", "Color"]), }; - vec![ + let mut sections = vec![ PropSection { title: t!("Lines & Arrows").into_owned(), props: vec![ @@ -2312,7 +2422,52 @@ pub fn style_sections( ), ], }, - ] + ]; + if matches!(dimension, Dimension::Ordinate(_)) { + for section in &mut sections { + section.props.retain(|property| match section.title.as_str() { + title if title == t!("Lines & Arrows").as_ref() => matches!( + property.field, + "dim_arrow_size" + | "dim_ext_line_lineweight" + | "dim_ext_line_fixed" + | "dim_ext_line_fixed_length" + | "dim_ext_line_color" + | "dim_ext_line_offset" + ), + title if title == t!("Text").as_ref() => !matches!( + property.field, + "dim_text_outside_align" | "dim_text_pos_hor" | "dim_text_inside_align" + ), + title if title == t!("Fit").as_ref() => matches!( + property.field, + "dim_text_movement" | "dim_scale_overall" + ), + _ => true, + }); + } + let dim_scale = if s.dimscale > 1e-6 { s.dimscale } else { 1.0 }; + let text_position = dimension_text_pos_f64( + dimension, + Some(s), + real(ov::DIMTXT, s.dimtxt) * dim_scale, + dim_scale, + ); + for section in &mut sections { + for property in &mut section.props { + match property.field { + "text_x" => { + property.value = PropValue::EditText(format!("{:.4}", text_position.x)); + } + "text_y" => { + property.value = PropValue::EditText(format!("{:.4}", text_position.y)); + } + _ => {} + } + } + } + } + sections } fn property(label: &str, field: &'static str, value: PropValue) -> Property { @@ -3640,16 +3795,23 @@ fn dimension_geometry( ); } Dimension::Ordinate(d) => { - add_segment( - &mut g.dim_lines, - lv(d.feature_location), - lv(d.definition_point), - ); - add_segment( - &mut g.dim_lines, - lv(d.definition_point), - lv(d.leader_endpoint), - ); + if !suppress.ext1 { + let fixed_length = params + .dimfxlon + .then_some(params.dimfxl.max(0.0) as f64); + let points = d.leader_polyline( + (params.arrow_len * 2.0) as f64, + params.dimexo as f64, + fixed_length, + ); + for pair in points.windows(2) { + let start = lv(pair[0]); + let end = lv(pair[1]); + if (end - start).length_squared() > 1e-12 { + add_segment(&mut g.ext_lines, start, end); + } + } + } } Dimension::Arc(d) => { append_angular_dimension( @@ -4061,7 +4223,6 @@ fn dimension_snap_pts(dim: &Dimension) -> Vec<(glam::DVec3, SnapHint)> { node(d.definition_point), ], Dimension::Ordinate(d) => vec![ - node(d.definition_point), node(d.feature_location), node(d.leader_endpoint), ], @@ -4271,6 +4432,8 @@ fn dimension_text_rotation(dim: &Dimension, style: Option<&DimStyle>) -> f64 { let outside = dimension_text_is_outside(dim, style); if base.text_rotation.abs() > 1e-9 { base.text_rotation + } else if matches!(dim, Dimension::Ordinate(_)) { + dimension_text_natural_rotation(dim) } else if base.horizontal_direction.abs() > 1e-9 { base.horizontal_direction } else if (outside && dimtoh) || (!outside && dimtih) { @@ -4338,6 +4501,14 @@ fn dimension_text_natural_rotation(dim: &Dimension) -> f64 { let dy = d.second_point.y - d.first_point.y; dy.atan2(dx) } + Dimension::Ordinate(d) => { + let axis_rotation = -d.base.horizontal_direction; + if d.is_ordinate_type_x { + axis_rotation + std::f64::consts::FRAC_PI_2 + } else { + axis_rotation + } + } _ => 0.0, }; // Clamp to (-π/2, π/2] so text never appears upside-down. @@ -5119,6 +5290,26 @@ fn dimension_text_pos_f64( dimtad, ) } + Dimension::Ordinate(d) => { + let (x_axis, y_axis) = d.local_axes(); + let text_axis = if d.is_ordinate_type_x { y_axis } else { x_axis }; + let perpendicular = if d.is_ordinate_type_x { x_axis } else { y_axis }; + let delta = d.leader_endpoint - d.feature_location; + let direction_sign = if delta.dot(&text_axis) < 0.0 { -1.0 } else { 1.0 }; + let perpendicular_sign = if delta.dot(&perpendicular) < 0.0 { + -1.0 + } else { + 1.0 + }; + let vertical = match dimtad { + 0 => dimtvp * text_height, + 4 => -perp_off, + _ => perp_off, + }; + d.leader_endpoint + + text_axis * (direction_sign * (text_w * 0.5 + dimgap)) + + perpendicular * (perpendicular_sign * vertical) + } _ => { // Non-linear (radius / diameter / angular / ordinate): lift the // natural mid point straight up by the style offset. A user-dragged diff --git a/src/modules/annotate/ordinate_dim.rs b/src/modules/annotate/ordinate_dim.rs index 203659bf..6370466c 100644 --- a/src/modules/annotate/ordinate_dim.rs +++ b/src/modules/annotate/ordinate_dim.rs @@ -32,9 +32,22 @@ enum Step { LeaderEndpoint { feature: DVec3 }, } +#[derive(Clone, Copy)] +enum DatumMode { + Automatic, + X, + Y, +} + pub struct OrdinateDimCommand { step: Step, plane: WorkingPlane, + datum_mode: DatumMode, + text_override: Option, + awaiting_text: bool, + mtext_override: bool, + text_angle: Option, + awaiting_angle: bool, } impl OrdinateDimCommand { @@ -42,6 +55,12 @@ impl OrdinateDimCommand { Self { step: Step::FeaturePoint, plane: WorkingPlane::default(), + datum_mode: DatumMode::Automatic, + text_override: None, + awaiting_text: false, + mtext_override: false, + text_angle: None, + awaiting_angle: false, } } } @@ -56,9 +75,23 @@ impl CadCommand for OrdinateDimCommand { } fn prompt(&self) -> String { + if self.awaiting_text { + return if self.mtext_override { + t!("DIMORDINATE Enter formatted dimension text (blank = measured value):") + .into_owned() + } else { + t!("DIMORDINATE Enter dimension text (blank = measured value):").into_owned() + }; + } + if self.awaiting_angle { + return t!("DIMORDINATE Specify text angle (degrees):").into_owned(); + } match self.step { Step::FeaturePoint => t!("DIMORDINATE Specify feature location:").into_owned(), - Step::LeaderEndpoint { .. } => t!("DIMORDINATE Specify leader endpoint:").into_owned(), + Step::LeaderEndpoint { .. } => t!( + "DIMORDINATE Specify leader endpoint [Xdatum/Ydatum/Mtext/Text/Angle]:" + ) + .into_owned(), } } @@ -71,26 +104,105 @@ impl CadCommand for OrdinateDimCommand { Step::LeaderEndpoint { feature } => { let feature = self.plane.to_local(feature); let pt = self.plane.to_local(pt); - let is_x = is_x_type(feature, pt); - let elbow = ordinate_elbow(feature, pt, is_x); + let is_x = match self.datum_mode { + DatumMode::Automatic => is_x_type(feature, pt), + DatumMode::X => true, + DatumMode::Y => false, + }; let mut dim = DimensionOrdinate::new(v3(feature), v3(pt), is_x); - // The leader is an orthogonal L from the feature to the - // endpoint; store its elbow as the definition point. Without it - // the renderer draws feature → (0,0,0) → endpoint, kinking the - // leader through the world origin. The old code also worked in - // the wrong (XZ) plane, dropping the Y coordinate. (#150) - dim.definition_point = v3(elbow); - dim.base.definition_point = v3(elbow); - CmdResult::CommitAndExit(self.plane.place_entity(EntityType::Dimension( - Dimension::Ordinate(dim), - ))) + crate::entities::dimension::set_dimension_text_override( + &mut dim.base, + self.text_override.clone(), + ); + if let Some(angle) = self.text_angle { + dim.base.text_rotation = angle; + } + dim.refresh_measurement(); + CmdResult::CommitDimension { + entity: self.plane.place_entity(EntityType::Dimension( + Dimension::Ordinate(dim), + )), + source: None, + } } } } fn on_enter(&mut self) -> CmdResult { + if self.awaiting_text { + self.awaiting_text = false; + return CmdResult::NeedPoint; + } + if self.awaiting_angle { + self.awaiting_angle = false; + return CmdResult::NeedPoint; + } CmdResult::Cancel } + + fn wants_text_input(&self) -> bool { + true + } + + fn point_step_accepts_keywords(&self) -> bool { + !self.awaiting_text && !self.awaiting_angle + } + + fn wants_text_with_spaces(&self) -> bool { + self.awaiting_text + } + + fn on_text_input(&mut self, text: &str) -> Option { + if self.awaiting_text { + let value = text.trim(); + self.text_override = if value.is_empty() || value == "<>" { + None + } else { + Some(value.to_string()) + }; + self.awaiting_text = false; + return Some(CmdResult::NeedPoint); + } + if self.awaiting_angle { + let value = text.trim(); + self.text_angle = if value.is_empty() { + None + } else { + crate::entities::common::parse_typed_angle(value) + }; + self.awaiting_angle = false; + return Some(CmdResult::NeedPoint); + } + if !matches!(self.step, Step::LeaderEndpoint { .. }) { + return None; + } + match text.trim().to_uppercase().as_str() { + "X" | "XDATUM" => { + self.datum_mode = DatumMode::X; + Some(CmdResult::NeedPoint) + } + "Y" | "YDATUM" => { + self.datum_mode = DatumMode::Y; + Some(CmdResult::NeedPoint) + } + "T" | "TEXT" => { + self.mtext_override = false; + self.awaiting_text = true; + Some(CmdResult::NeedPoint) + } + "M" | "MTEXT" => { + self.mtext_override = true; + self.awaiting_text = true; + Some(CmdResult::NeedPoint) + } + "A" | "ANGLE" => { + self.awaiting_angle = true; + Some(CmdResult::NeedPoint) + } + _ => None, + } + } + fn on_mouse_move(&mut self, pt: DVec3) -> Option { let feature = match self.step { Step::LeaderEndpoint { feature } => feature, @@ -98,17 +210,24 @@ impl CadCommand for OrdinateDimCommand { }; let feature = self.plane.to_local(feature); let pt = self.plane.to_local(pt); - let is_x = is_x_type(feature, pt); - let elbow = ordinate_elbow(feature, pt, is_x); - let feature = self.plane.to_world(feature); - let elbow = self.plane.to_world(elbow); - let pt = self.plane.to_world(pt); + let is_x = match self.datum_mode { + DatumMode::Automatic => is_x_type(feature, pt), + DatumMode::X => true, + DatumMode::Y => false, + }; + let dim = DimensionOrdinate::new(v3(feature), v3(pt), is_x); + let points = dim.leader_polyline(0.44, 0.0, None); // Screen-only rubber band: downcast to f32 at the preview boundary. - Some(preview_wire(vec![ - feature.as_vec3(), - elbow.as_vec3(), - pt.as_vec3(), - ])) + Some(preview_wire( + points + .into_iter() + .map(|point| { + self.plane + .to_world(DVec3::new(point.x, point.y, point.z)) + .as_vec3() + }) + .collect(), + )) } } @@ -122,17 +241,7 @@ fn v3(p: DVec3) -> Vector3 { fn is_x_type(feature: DVec3, leader: DVec3) -> bool { let dx = (leader.x - feature.x).abs(); let dy = (leader.y - feature.y).abs(); - dy >= dx -} - -/// Orthogonal elbow of the ordinate leader: an X-datum runs along Y from the -/// feature then jogs across in X; a Y-datum runs along X then jogs in Y. -fn ordinate_elbow(feature: DVec3, leader: DVec3, is_x: bool) -> DVec3 { - if is_x { - DVec3::new(feature.x, leader.y, feature.z) - } else { - DVec3::new(leader.x, feature.y, feature.z) - } + dy > dx } fn preview_wire(points: Vec) -> WireModel { diff --git a/src/modules/draw/modify/explode.rs b/src/modules/draw/modify/explode.rs index 302033f9..88a6c197 100644 --- a/src/modules/draw/modify/explode.rs +++ b/src/modules/draw/modify/explode.rs @@ -851,7 +851,20 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec { // Per-element commons so a baked block keeps the style's colours/lineweights: // extension lines (DIMCLRE/DIMLWE), dimension line + arrows + centre mark // (DIMCLRD/DIMLWD), text (DIMCLRT). - let ext_c = dim_common(&base.common, met.dimclre, met.dimlwe); + let mut ext_c = dim_common(&base.common, met.dimclre, met.dimlwe); + let override_data = &base.common.extended_data; + if let Some(color) = crate::entities::dim_override::color( + override_data, + crate::entities::dim_override::DIMCLRE, + ) { + ext_c.color = color; + } + if let Some(lineweight) = crate::entities::dim_override::int( + override_data, + crate::entities::dim_override::DIMLWE, + ) { + ext_c.line_weight = acadrust::types::LineWeight::from_value(lineweight); + } let dim_c = dim_common(&base.common, met.dimclrd, met.dimlwd); let mut result: Vec = Vec::new(); @@ -1041,8 +1054,33 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec { )); } Dimension::Ordinate(d) => { - result.push(make_seg(&d.feature_location, &d.definition_point, &dim_c)); - result.push(make_seg(&d.definition_point, &d.leader_endpoint, &dim_c)); + if !met.dimse1 { + use crate::entities::dim_override as ov; + let scale = ov::real(override_data, ov::DIMSCALE) + .filter(|value| *value > 1e-6) + .unwrap_or(1.0); + let dogleg = ov::real(override_data, ov::DIMASZ) + .unwrap_or(met.dimasz / scale) + * scale + * 2.0; + let offset = ov::real(override_data, ov::DIMEXO) + .unwrap_or(met.dimexo / scale) + * scale; + let fixed_on = ov::int(override_data, ov::DIMFXLON) + .map(|value| value != 0) + .unwrap_or(met.dimfxlon); + let fixed_length = fixed_on.then(|| { + ov::real(override_data, ov::DIMFXL) + .unwrap_or(met.dimfxl / scale) + * scale + }); + let points = d.leader_polyline(dogleg, offset, fixed_length); + for pair in points.windows(2) { + if (pair[1] - pair[0]).length() > 1e-12 { + result.push(make_seg(&pair[0], &pair[1], &ext_c)); + } + } + } } Dimension::Arc(d) => { result.extend(angular_block_segs( diff --git a/src/scene/dimension_assoc.rs b/src/scene/dimension_assoc.rs index 2814695e..147b20eb 100644 --- a/src/scene/dimension_assoc.rs +++ b/src/scene/dimension_assoc.rs @@ -115,10 +115,11 @@ fn resolve_reference(scene: &Scene, reference: &AssocDimensionReference) -> Opti .copied() } -fn dimension_points(dimension: &Dimension) -> Option<[Vector3; 2]> { +fn dimension_points(dimension: &Dimension) -> Option<[Option; 2]> { match dimension { - Dimension::Linear(linear) => Some([linear.first_point, linear.second_point]), - Dimension::Aligned(aligned) => Some([aligned.first_point, aligned.second_point]), + Dimension::Linear(linear) => Some([Some(linear.first_point), Some(linear.second_point)]), + Dimension::Aligned(aligned) => Some([Some(aligned.first_point), Some(aligned.second_point)]), + Dimension::Ordinate(ordinate) => Some([Some(ordinate.feature_location), None]), _ => None, } } @@ -186,13 +187,14 @@ impl Scene { return; }; let resolved: [Option<(Handle, i32, f64, u8)>; 2] = std::array::from_fn(|index| { + let point = source_data[index]?; let source = sources[index]?; let entity = self.document.get_entity(source)?; - source_marker(entity, source_data[index]).map(|marker| { + source_marker(entity, point).map(|marker| { ( source, marker, - source_parameter(entity, source_data[index]), + source_parameter(entity, point), if matches!(entity, EntityType::Circle(_)) { 10 } else { @@ -230,7 +232,7 @@ impl Scene { marker, parameter, osnap_type, - source_data[index], + source_data[index].expect("resolved dimension point"), )); } } @@ -274,7 +276,7 @@ impl Scene { return [None, None]; }; points.map(|point| { - self.document + point.and_then(|point| self.document .entities() .filter(|entity| entity.common().handle != dimension) .filter_map(|entity| { @@ -283,7 +285,7 @@ impl Scene { }) .filter(|(distance, _)| *distance <= 1e-16) .min_by(|first, second| first.0.total_cmp(&second.0)) - .map(|(_, handle)| handle) + .map(|(_, handle)| handle)) }) } @@ -353,6 +355,12 @@ impl Scene { aligned.base.actual_measurement = aligned.measurement(); aligned.base.definition_point = aligned.definition_point; } + Dimension::Ordinate(ordinate) => { + if let Some(feature) = first { + ordinate.feature_location = feature; + } + ordinate.refresh_measurement(); + } _ => continue, } refreshed.push((association.dimension, ChangeKind::Modified));