diff --git a/src/app/commands/dim.rs b/src/app/commands/dim.rs index 5576e470b..ebf477099 100644 --- a/src/app/commands/dim.rs +++ b/src/app/commands/dim.rs @@ -49,7 +49,11 @@ impl OpenCADStudio { // quick leader-plus-annotation operation. "LEADER" | "QLEADER" => { use crate::modules::annotate::leader_cmd::LeaderCommand; - let new_cmd = LeaderCommand::new(); + let defaults = crate::scene::creation_style::current_dimension_defaults( + &self.tabs[i].scene.document, + ); + let multiplier = self.tabs[i].scene.creation_annotation_multiplier(); + let new_cmd = LeaderCommand::with_defaults(defaults, multiplier); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } @@ -486,7 +490,31 @@ impl OpenCADStudio { "MLEADER" => { use crate::modules::annotate::mleader_cmd::MLeaderCommand; - let new_cmd = MLeaderCommand::new(); + let name = self.tabs[i] + .scene + .document + .header + .current_mleader_style_name + .clone(); + let style = self.tabs[i] + .scene + .document + .objects + .iter() + .find_map(|(handle, object)| match object { + acadrust::objects::ObjectType::MultiLeaderStyle(style) + if style.name.eq_ignore_ascii_case(&name) => + { + let mut style = style.clone(); + style.handle = *handle; + Some(style) + } + _ => None, + }); + let multiplier = self.tabs[i].scene.creation_annotation_multiplier(); + let new_cmd = style.map_or_else(MLeaderCommand::new, |style| { + MLeaderCommand::with_style(style, multiplier) + }); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } @@ -500,7 +528,29 @@ impl OpenCADStudio { "TABLE" => { use crate::modules::annotate::table_cmd::TableCommand; - let cmd = TableCommand::new(); + let name = self.tabs[i] + .scene + .document + .header + .current_table_style_name + .clone(); + let style = self.tabs[i] + .scene + .document + .objects + .iter() + .find_map(|(handle, object)| match object { + acadrust::objects::ObjectType::TableStyle(style) + if style.name.eq_ignore_ascii_case(&name) => + { + Some((*handle, style.clone())) + } + _ => None, + }); + let multiplier = self.tabs[i].scene.creation_annotation_multiplier(); + let cmd = style.as_ref().map_or_else(TableCommand::new, |(handle, style)| { + TableCommand::with_style(*handle, style, multiplier) + }); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index 0dcda32dd..190b2b2fc 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -13,8 +13,28 @@ impl OpenCADStudio { "MLINE" => { use crate::modules::draw::draw::mline::MlineCommand; - let style = self.tabs[i].scene.document.header.multiline_style.clone(); - let cmd_obj = MlineCommand::with_style(style); + let style_name = self.tabs[i].scene.document.header.multiline_style.clone(); + let style = self.tabs[i] + .scene + .document + .objects + .iter() + .find_map(|(handle, object)| match object { + acadrust::objects::ObjectType::MLineStyle(style) + if style.name.eq_ignore_ascii_case(&style_name) => + { + Some((*handle, style.elements.len())) + } + _ => None, + }); + let (style_handle, element_count) = style + .map(|(handle, count)| (Some(handle), count)) + .unwrap_or((None, 2)); + let cmd_obj = MlineCommand::with_style( + style_name, + style_handle, + element_count, + ); self.command_line.push_info(&cmd_obj.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd_obj)); } @@ -47,7 +67,15 @@ impl OpenCADStudio { "ATTDEF" => { use crate::modules::draw::draw::attdef::AttdefCommand; - let cmd = AttdefCommand::new(); + let defaults = crate::scene::creation_style::current_text_defaults( + &self.tabs[i].scene.document, + ); + let cmd = AttdefCommand::with_text_defaults( + defaults.height, + defaults.style_name, + defaults.width_factor, + defaults.oblique_angle, + ); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } @@ -1049,7 +1077,11 @@ impl OpenCADStudio { // ── Annotate commands ────────────────────────────────────────── "TEXT" => { use crate::modules::annotate::text::TextCommand; - let new_cmd = TextCommand::new(); + let height = crate::scene::creation_style::current_text_defaults( + &self.tabs[i].scene.document, + ) + .height; + let new_cmd = TextCommand::with_height(height); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } @@ -1080,7 +1112,11 @@ impl OpenCADStudio { "MTEXT" => { use crate::modules::annotate::mtext::MTextCommand; - let new_cmd = MTextCommand::new(); + let height = crate::scene::creation_style::current_text_defaults( + &self.tabs[i].scene.document, + ) + .height; + let new_cmd = MTextCommand::with_height(height); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } diff --git a/src/app/properties.rs b/src/app/properties.rs index 1e85894ea..f2122ee88 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -1848,74 +1848,10 @@ impl OpenCADStudio { entity.common_mut().linetype_scale = celtscale; } - // A new dimension inherits the document's current dimension style - // (DIMSTYLE) instead of staying at the entity "Standard" default. Only - // fill in when still at the default so an explicitly-styled dimension - // is preserved. See #92. - if let acadrust::EntityType::Dimension(ref mut d) = entity { - let cur = self.tabs[i] - .scene - .document - .header - .current_dimstyle_name - .clone(); - let s = d.base().style_name.clone(); - if (s.is_empty() || s.eq_ignore_ascii_case("Standard")) && !cur.is_empty() { - d.base_mut().style_name = cur; - } - } - - // MultiLeader / Table inherit the document's current style (#92). These - // styles live in the objects dictionary, so resolve the current style - // name to its object handle. Left untouched when the command already - // assigned a style or no matching style object exists. - match &mut entity { - acadrust::EntityType::MultiLeader(ml) if ml.style_handle.is_none() => { - let name = self.tabs[i] - .scene - .document - .header - .current_mleader_style_name - .clone(); - if !name.is_empty() { - let found = - self.tabs[i].scene.document.objects.iter().find_map(|(h, o)| match o { - acadrust::objects::ObjectType::MultiLeaderStyle(s) - if s.name.eq_ignore_ascii_case(&name) => - { - Some((*h, s.clone())) - } - _ => None, - }); - if let Some((h, s)) = found { - debug_assert_eq!(h, s.handle); - crate::scene::annotative::apply_mleader_style(ml, &s); - } - } - } - acadrust::EntityType::Table(t) if t.table_style_handle.is_none() => { - let name = self.tabs[i] - .scene - .document - .header - .current_table_style_name - .clone(); - if !name.is_empty() { - t.table_style_handle = - self.tabs[i].scene.document.objects.iter().find_map(|(h, o)| { - match o { - acadrust::objects::ObjectType::TableStyle(s) - if s.name.eq_ignore_ascii_case(&name) => - { - Some(*h) - } - _ => None, - } - }); - } - } - _ => {} - } + crate::scene::creation_style::apply_current_creation_styles( + &self.tabs[i].scene.document, + &mut entity, + ); let text_style_annotative = match &entity { acadrust::EntityType::Text(text) => { diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index e8600dea4..93ccdab6d 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -1581,9 +1581,8 @@ fn tessellate_dimension_inner( } let style = effective_style.as_ref(); - // DIMSCALE rule: - // dimstyle.dimscale > 0 → final multiplier; ignore anno_scale. - // dimstyle.dimscale == 0 → annotative: use anno_scale (= 1/vp_scale). + // A positive style scale is fixed. A zero style scale uses the multiplier + // resolved by the scene for the current annotation or viewport context. let dim_scale = style .map(|s| { if s.dimscale > 1e-6 { diff --git a/src/modules/annotate/leader_cmd.rs b/src/modules/annotate/leader_cmd.rs index 4d7927c46..78a5c5c4a 100644 --- a/src/modules/annotate/leader_cmd.rs +++ b/src/modules/annotate/leader_cmd.rs @@ -34,11 +34,36 @@ pub fn tool() -> ToolDef { pub struct LeaderCommand { verts: Vec, ucs: Mat4, + dimension_style: String, + text_style: String, + text_height: f64, + display_scale: f64, + gap: f64, + arrow_size: f64, + annotative: bool, } impl LeaderCommand { - pub fn new() -> Self { - Self { verts: Vec::new(), ucs: Mat4::IDENTITY } + pub fn with_defaults( + defaults: crate::scene::creation_style::DimensionCreationDefaults, + annotation_multiplier: f64, + ) -> Self { + let display_scale = if defaults.annotative { + annotation_multiplier + } else { + defaults.scale + }; + Self { + verts: Vec::new(), + ucs: Mat4::IDENTITY, + dimension_style: defaults.style_name, + text_style: defaults.text_style_name, + text_height: defaults.text_height, + display_scale, + gap: defaults.gap, + arrow_size: defaults.arrow_size, + annotative: defaults.annotative, + } } } @@ -74,9 +99,30 @@ impl CadCommand for LeaderCommand { } // Place the leader plus an empty MText annotation, link them, then open // the in-place MText editor so the user types the annotation text. - let leader = build_leader(&self.verts, self.ucs); - let (anchor, attach) = annotation_anchor(&self.verts, leader.text_height, self.ucs); - let mtext = build_mtext("", anchor, leader.text_height, attach, self.ucs); + let leader = build_leader( + &self.verts, + self.ucs, + &self.dimension_style, + self.text_height, + self.gap, + self.arrow_size, + ); + let displayed_height = self.text_height * self.display_scale; + let (anchor, attach) = annotation_anchor(&self.verts, displayed_height, self.ucs); + let mtext_height = if self.annotative { + self.text_height + } else { + displayed_height + }; + let mtext = build_mtext( + "", + anchor, + mtext_height, + attach, + self.ucs, + &self.text_style, + self.annotative, + ); CmdResult::CommitManyAndEditText { entities: vec![EntityType::Leader(leader), EntityType::MText(mtext)], edit_index: 1, @@ -93,7 +139,10 @@ impl CadCommand for LeaderCommand { } let mut pts: Vec = self.verts.iter().map(|p| p.as_vec3()).collect(); pts.push(pt.as_vec3()); - Some(preview_wire(&pts)) + Some(preview_wire( + &pts, + (self.arrow_size * self.display_scale) as f32, + )) } } @@ -103,10 +152,21 @@ fn dv3(p: DVec3) -> Vector3 { Vector3::new(p.x, p.y, p.z) } -fn build_leader(verts: &[DVec3], ucs: Mat4) -> Leader { +fn build_leader( + verts: &[DVec3], + ucs: Mat4, + dimension_style: &str, + text_height: f64, + gap: f64, + arrow_size: f64, +) -> Leader { let mut l = Leader::from_vertices(verts.iter().map(|p| dv3(*p)).collect()); l.creation_type = LeaderCreationType::WithText; l.hookline_enabled = true; + l.dimension_style = dimension_style.to_string(); + l.text_height = text_height; + l.dimension_gap = gap; + l.arrow_size = arrow_size; // The hookline / text read along the UCS X axis (DXF "horizontal direction // for text"); the renderer uses it instead of world horizontal. let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X); @@ -143,11 +203,15 @@ fn build_mtext( height: f64, attach: AttachmentPoint, ucs: Mat4, + text_style: &str, + annotative: bool, ) -> MText { let mut m = MText::new(); m.value = text.to_string(); m.insertion_point = dv3(pos); m.height = height; + m.style = text_style.to_string(); + m.is_annotative = annotative; m.attachment_point = attach; // Text reads along the UCS X axis. let ux = ucs.transform_vector3(Vec3::X); @@ -155,10 +219,10 @@ fn build_mtext( m } -fn preview_wire(pts: &[Vec3]) -> WireModel { +fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel { let mut points: Vec<[f32; 3]> = pts.iter().map(|p| [p.x, p.y, p.z]).collect(); if pts.len() >= 2 { - let [w1, w2] = arrowhead_wings(pts[0], pts[1], 2.5); + let [w1, w2] = arrowhead_wings(pts[0], pts[1], arrow_size); points.push([f32::NAN; 3]); points.push([w1.x, w1.y, w1.z]); points.push([pts[0].x, pts[0].y, pts[0].z]); diff --git a/src/modules/annotate/mleader_cmd.rs b/src/modules/annotate/mleader_cmd.rs index 2952e7f3c..18f906566 100644 --- a/src/modules/annotate/mleader_cmd.rs +++ b/src/modules/annotate/mleader_cmd.rs @@ -29,11 +29,35 @@ pub fn tool() -> ToolDef { pub struct MLeaderCommand { verts: Vec, ucs: Mat4, + style: Option, + display_scale: f64, } impl MLeaderCommand { pub fn new() -> Self { - Self { verts: Vec::new(), ucs: Mat4::IDENTITY } + Self { + verts: Vec::new(), + ucs: Mat4::IDENTITY, + style: None, + display_scale: 1.0, + } + } + + pub fn with_style( + style: acadrust::objects::MultiLeaderStyle, + annotation_multiplier: f64, + ) -> Self { + let display_scale = if style.is_annotative { + annotation_multiplier + } else { + style.scale_factor + }; + Self { + verts: Vec::new(), + ucs: Mat4::IDENTITY, + style: Some(style), + display_scale, + } } } @@ -69,7 +93,13 @@ impl CadCommand for MLeaderCommand { } // Place the leader with empty text, then open the in-place MText editor // so the user types the annotation into the rich editor. - let ml = build_mleader("", &self.verts, self.ucs); + let ml = build_mleader( + "", + &self.verts, + self.ucs, + self.style.as_ref(), + self.display_scale, + ); CmdResult::CommitAndEditText(EntityType::MultiLeader(ml)) } @@ -84,7 +114,12 @@ impl CadCommand for MLeaderCommand { // Preview / rubber-band is GPU screen-space: downcast to f32 here. let mut pts: Vec = self.verts.iter().map(|p| p.as_vec3()).collect(); pts.push(pt.as_vec3()); - Some(preview_wire(&pts)) + let arrow_size = self + .style + .as_ref() + .map_or(2.5, |style| style.arrowhead_size) + * self.display_scale; + Some(preview_wire(&pts, arrow_size as f32)) } } @@ -94,7 +129,13 @@ fn v3(p: DVec3) -> Vector3 { Vector3::new(p.x, p.y, p.z) } -fn build_mleader(text: &str, verts: &[DVec3], ucs: Mat4) -> MultiLeader { +fn build_mleader( + text: &str, + verts: &[DVec3], + ucs: Mat4, + style: Option<&acadrust::objects::MultiLeaderStyle>, + display_scale: f64, +) -> MultiLeader { // Last vertex = content/text location; remaining = leader line points let (leader_pts, content_pt) = verts.split_at(verts.len() - 1); let content_pt = content_pt[0]; @@ -103,14 +144,22 @@ fn build_mleader(text: &str, verts: &[DVec3], ucs: Mat4) -> MultiLeader { let content_v3 = v3(content_pt); let mut ml = MultiLeader::with_text(text, content_v3, leader_v3); + if let Some(style) = style { + crate::scene::annotative::apply_mleader_style(&mut ml, style); + } else { + ml.text_height = 2.5; + ml.context.text_height = 2.5; + ml.arrowhead_size = 2.5; + ml.context.arrowhead_size = 2.5; + ml.dogleg_length = 2.5; + } - // Match Leader entity defaults - ml.text_height = 2.5; - ml.context.text_height = 2.5; - ml.arrowhead_size = 2.5; - ml.dogleg_length = 2.5; - - const DOGLEG: f64 = 2.5; + let landing_distance = ml.dogleg_length * display_scale; + let landing_gap = ml.context.landing_gap * display_scale; + ml.context.scale_factor = display_scale; + ml.context.text_height = ml.text_height * display_scale; + ml.context.arrowhead_size = ml.arrowhead_size * display_scale; + ml.context.landing_gap = landing_gap; // "Horizontal" landing + text run along the active UCS X axis (identity = // world), so the annotation reads square to the user's coordinate system. let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X); @@ -130,22 +179,22 @@ fn build_mleader(text: &str, verts: &[DVec3], ucs: Mat4) -> MultiLeader { // the text along the UCS X axis. root.direction = Vector3::new(landing.x as f64, landing.y as f64, 0.0); root.connection_point = content_v3; - root.landing_distance = DOGLEG; + root.landing_distance = landing_distance; } // Seed the text one landing-length past the leader end, on the side the // user dragged toward, offset along the UCS X axis. - let off = landing * DOGLEG as f32; + let off = landing * (landing_distance + landing_gap) as f32; ml.context.text_location = Vector3::new(content_v3.x + off.x as f64, content_v3.y + off.y as f64, content_v3.z); ml } -fn preview_wire(pts: &[Vec3]) -> WireModel { +fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel { let mut points: Vec<[f32; 3]> = pts.iter().map(|p| [p.x, p.y, p.z]).collect(); if pts.len() >= 2 { - let [w1, w2] = arrowhead_wings(pts[0], pts[1], 2.5); + let [w1, w2] = arrowhead_wings(pts[0], pts[1], arrow_size); points.push([f32::NAN; 3]); points.push([w1.x, w1.y, w1.z]); points.push([pts[0].x, pts[0].y, pts[0].z]); diff --git a/src/modules/annotate/mtext.rs b/src/modules/annotate/mtext.rs index c0773ff09..a6a6d6d6f 100644 --- a/src/modules/annotate/mtext.rs +++ b/src/modules/annotate/mtext.rs @@ -21,12 +21,14 @@ enum Step { pub struct MTextCommand { step: Step, + height: f64, } impl MTextCommand { - pub fn new() -> Self { + pub fn with_height(height: f64) -> Self { Self { step: Step::InsertPoint, + height, } } } @@ -48,7 +50,7 @@ impl CadCommand for MTextCommand { pos: pt, handle: None, initial: String::new(), - height: 0.25, + height: self.height, } } diff --git a/src/modules/annotate/table_cmd.rs b/src/modules/annotate/table_cmd.rs index 2b8cb3e26..4652e9979 100644 --- a/src/modules/annotate/table_cmd.rs +++ b/src/modules/annotate/table_cmd.rs @@ -41,12 +41,46 @@ enum Step { pub struct TableCommand { step: Step, + style_handle: Option, + column_width: f64, + row_height: f64, + preview_scale: f64, } impl TableCommand { pub fn new() -> Self { Self { step: Step::Columns, + style_handle: None, + column_width: COL_WIDTH, + row_height: ROW_HEIGHT, + preview_scale: 1.0, + } + } + + pub fn with_style( + style_handle: acadrust::Handle, + style: &acadrust::objects::TableStyle, + annotation_multiplier: f64, + ) -> Self { + let text_height = style + .data_row_style + .text_height + .max(style.header_row_style.text_height) + .max(style.title_row_style.text_height) + .max(1.0e-6); + let row_height = text_height * 1.5 + style.vertical_margin * 2.0; + let column_width = text_height * 8.0 + style.horizontal_margin * 2.0; + Self { + step: Step::Columns, + style_handle: Some(style_handle), + column_width, + row_height, + preview_scale: if style.annotative { + annotation_multiplier + } else { + 1.0 + }, } } } @@ -130,11 +164,12 @@ impl CadCommand for TableCommand { fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Step::Insertion { cols, rows } = self.step { let ins = Vector3::new(pt.x, pt.y, pt.z); - let table = TableBuilder::new(rows, cols) + let mut table = TableBuilder::new(rows, cols) .at(ins) - .row_height(ROW_HEIGHT) - .column_width(COL_WIDTH) + .row_height(self.row_height) + .column_width(self.column_width) .build(); + table.table_style_handle = self.style_handle; CmdResult::CommitAndExit(EntityType::Table(table)) } else { CmdResult::NeedPoint @@ -144,8 +179,8 @@ impl CadCommand for TableCommand { fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); if let Step::Insertion { cols, rows } = self.step { // Preview: outline of the table bounding box. - let w = (cols as f32) * COL_WIDTH as f32; - let h = (rows as f32) * ROW_HEIGHT as f32; + let w = (cols as f32) * (self.column_width * self.preview_scale) as f32; + let h = (rows as f32) * (self.row_height * self.preview_scale) as f32; let x = pt.x; let y = pt.y; let z = pt.z; diff --git a/src/modules/annotate/text.rs b/src/modules/annotate/text.rs index 98275e92a..c00ad9817 100644 --- a/src/modules/annotate/text.rs +++ b/src/modules/annotate/text.rs @@ -21,12 +21,14 @@ enum Step { pub struct TextCommand { step: Step, + height: f64, } impl TextCommand { - pub fn new() -> Self { + pub fn with_height(height: f64) -> Self { Self { step: Step::InsertPoint, + height, } } } @@ -48,7 +50,7 @@ impl CadCommand for TextCommand { pos: pt, handle: None, initial: String::new(), - height: 0.25, + height: self.height, } } diff --git a/src/modules/draw/draw/attdef.rs b/src/modules/draw/draw/attdef.rs index 07e8b3261..591b6ac46 100644 --- a/src/modules/draw/draw/attdef.rs +++ b/src/modules/draw/draw/attdef.rs @@ -33,15 +33,25 @@ enum Step { pub struct AttdefCommand { step: Step, - /// Text height in world units. height: f64, + text_style: String, + width_factor: f64, + oblique_angle: f64, } impl AttdefCommand { - pub fn new() -> Self { + pub fn with_text_defaults( + height: f64, + text_style: String, + width_factor: f64, + oblique_angle: f64, + ) -> Self { Self { step: Step::Tag, - height: 0.2, + height, + text_style, + width_factor, + oblique_angle, } } } @@ -130,6 +140,9 @@ impl CadCommand for AttdefCommand { default_value: default.clone(), insertion_point: Vector3::new(pt.x, pt.y, pt.z), height: self.height, + text_style: self.text_style.clone(), + width_factor: self.width_factor, + oblique_angle: self.oblique_angle, ..Default::default() }; attdef.common.layer = "0".into(); diff --git a/src/modules/draw/draw/mline.rs b/src/modules/draw/draw/mline.rs index bf48d18b1..674c93b68 100644 --- a/src/modules/draw/draw/mline.rs +++ b/src/modules/draw/draw/mline.rs @@ -19,6 +19,8 @@ pub struct MlineCommand { scale: f64, waiting_scale: bool, style_name: String, + style_handle: Option, + style_element_count: usize, } impl MlineCommand { @@ -29,15 +31,23 @@ impl MlineCommand { scale: 1.0, waiting_scale: false, style_name: "Standard".into(), + style_handle: None, + style_element_count: 2, } } - pub fn with_style(style_name: impl Into) -> Self { + pub fn with_style( + style_name: impl Into, + style_handle: Option, + style_element_count: usize, + ) -> Self { Self { points: vec![], scale: 1.0, waiting_scale: false, style_name: style_name.into(), + style_handle, + style_element_count: style_element_count.max(1), } } } @@ -94,7 +104,14 @@ impl CadCommand for MlineCommand { // Close command if (up == "C" || up == "CLOSE") && self.points.len() >= 3 { - let entity = build_mline(&self.points, self.scale, true, &self.style_name); + let entity = build_mline( + &self.points, + self.scale, + true, + &self.style_name, + self.style_handle, + self.style_element_count, + ); return Some(CmdResult::CommitAndExit(entity)); } @@ -126,7 +143,14 @@ impl CadCommand for MlineCommand { if self.points.len() < 2 { return CmdResult::Cancel; } - let entity = build_mline(&self.points, self.scale, false, &self.style_name); + let entity = build_mline( + &self.points, + self.scale, + false, + &self.style_name, + self.style_handle, + self.style_element_count, + ); CmdResult::CommitAndExit(entity) } @@ -171,18 +195,25 @@ impl CadCommand for MlineCommand { } } -fn build_mline(pts: &[DVec3], scale: f64, closed: bool, style_name: &str) -> EntityType { - let verts: Vec = pts - .iter() - .map(|p| Vector3::new(p.x, p.y, p.z)) - .collect(); - let mut mline = if closed { - MLine::closed_from_points(&verts) - } else { - MLine::from_points(&verts) - }; +fn build_mline( + pts: &[DVec3], + scale: f64, + closed: bool, + style_name: &str, + style_handle: Option, + style_element_count: usize, +) -> EntityType { + let mut mline = MLine::new(); mline.scale_factor = scale; mline.style_name = style_name.to_string(); + mline.style_handle = style_handle; + mline.style_element_count = style_element_count; + for point in pts { + mline.add_vertex(Vector3::new(point.x, point.y, point.z)); + } + if closed { + mline.close(); + } EntityType::MLine(mline) } diff --git a/src/scene/annotative.rs b/src/scene/annotative.rs index 3b023c481..2f27fa195 100644 --- a/src/scene/annotative.rs +++ b/src/scene/annotative.rs @@ -639,10 +639,33 @@ pub fn effective_annotation_scale_for( fallback: f32, scale_handle: Option, ) -> f32 { - if !is_annotative(doc, entity) { + let context_annotative = is_annotative(doc, entity); + let style_annotative = annotation_style_is_annotative(doc, entity); + if !context_annotative && !style_annotative { return 1.0; } + if matches!( + entity, + EntityType::Dimension(_) + | EntityType::Leader(_) + | EntityType::Tolerance(_) + | EntityType::Table(_) + ) { + return fallback; + } + if style_annotative + && matches!( + entity, + EntityType::Text(_) + | EntityType::MText(_) + | EntityType::AttributeEntity(_) + | EntityType::AttributeDefinition(_) + ) + { + return fallback; + } + // Unlike MTEXT / DIMENSION contexts, an MLEADER context carries its // already-scaled text height and overall scale factor. Keep the text // height as stored; make `ml.scale_factor * anno_scale` resolve to the @@ -1392,6 +1415,15 @@ fn mleader_style_annotative(doc: &CadDocument, handle: Option) -> bool { }) } +fn table_style_annotative(doc: &CadDocument, handle: Option) -> bool { + let Some(handle) = handle else { + return false; + }; + doc.objects.iter().any(|(object_handle, object)| { + matches!(object, ObjectType::TableStyle(style) if *object_handle == handle && style.annotative) + }) +} + /// Whether an object carries a per-object annotation context with at least one /// per-scale representation — its extension dictionary holds an /// `AcDbContextDataManager` whose `ACDB_ANNOTATIONSCALES` collection is @@ -1454,6 +1486,7 @@ pub fn annotation_style_is_annotative(doc: &CadDocument, entity: &EntityType) -> EntityType::MultiLeader(leader) => { mleader_style_annotative(doc, leader.style_handle) } + EntityType::Table(table) => table_style_annotative(doc, table.table_style_handle), _ => false, } } @@ -1473,6 +1506,8 @@ pub fn apply_mleader_style( entity.dogleg_length = style.landing_distance; entity.arrowhead_handle = style.arrowhead_handle; entity.arrowhead_size = style.arrowhead_size; + entity.context.arrowhead_size = style.arrowhead_size; + entity.context.landing_gap = style.landing_gap; entity.text_style_handle = style.text_style_handle; entity.text_color = style.text_color; entity.text_frame = style.text_frame; @@ -1509,6 +1544,20 @@ pub fn apply_mleader_style( entity.context.block_content_scale = entity.block_scale; entity.context.scale_factor = style.scale_factor; entity.enable_annotation_scale = style.is_annotative; + for root in &mut entity.context.leader_roots { + root.landing_distance = style.landing_distance; + root.text_attachment_direction = entity.text_attachment_direction; + for line in &mut root.lines { + if line.override_flags.is_empty() { + line.path_type = entity.path_type; + line.line_color = style.line_color; + line.line_type_handle = style.line_type_handle; + line.line_weight = style.line_weight; + line.arrowhead_handle = style.arrowhead_handle; + line.arrowhead_size = style.arrowhead_size; + } + } + } } pub fn apply_mleader_style_to_object( diff --git a/src/scene/creation_style.rs b/src/scene/creation_style.rs new file mode 100644 index 000000000..ed4a1fc53 --- /dev/null +++ b/src/scene/creation_style.rs @@ -0,0 +1,258 @@ +use acadrust::objects::ObjectType; +use acadrust::tables::{DimStyle, TextStyle}; +use acadrust::{CadDocument, EntityType, Handle}; + +#[derive(Debug, Clone)] +pub struct TextCreationDefaults { + pub style_name: String, + pub height: f64, + pub width_factor: f64, + pub oblique_angle: f64, +} + +#[derive(Debug, Clone)] +pub struct DimensionCreationDefaults { + pub style_name: String, + pub style_handle: Handle, + pub text_style_name: String, + pub text_height: f64, + pub gap: f64, + pub arrow_size: f64, + pub scale: f64, + pub annotative: bool, +} + +fn named_text_style(doc: &CadDocument, name: &str) -> Option { + doc.text_styles + .iter() + .find(|style| style.name.eq_ignore_ascii_case(name)) + .or_else(|| { + doc.text_styles + .iter() + .find(|style| style.name.eq_ignore_ascii_case("Standard")) + }) + .cloned() +} + +fn named_dim_style(doc: &CadDocument, name: &str) -> Option { + doc.dim_styles + .iter() + .find(|style| style.name.eq_ignore_ascii_case(name)) + .or_else(|| { + doc.dim_styles + .iter() + .find(|style| style.name.eq_ignore_ascii_case("Standard")) + }) + .cloned() +} + +pub fn current_text_defaults(doc: &CadDocument) -> TextCreationDefaults { + let requested = doc.header.current_text_style_name.trim(); + let style = named_text_style(doc, requested); + let variable_height = if doc.header.text_height > 1.0e-9 { + doc.header.text_height + } else { + style + .as_ref() + .map(|style| style.last_height) + .filter(|height| *height > 1.0e-9) + .unwrap_or(2.5) + }; + TextCreationDefaults { + style_name: style + .as_ref() + .map(|style| style.name.clone()) + .filter(|name| !name.trim().is_empty()) + .unwrap_or_else(|| "Standard".to_string()), + height: style + .as_ref() + .map(|style| style.height) + .filter(|height| *height > 1.0e-9) + .unwrap_or(variable_height), + width_factor: style.as_ref().map_or(1.0, |style| style.width_factor), + oblique_angle: style.as_ref().map_or(0.0, |style| style.oblique_angle), + } +} + +pub fn current_dimension_defaults(doc: &CadDocument) -> DimensionCreationDefaults { + let requested = doc.header.current_dimstyle_name.trim(); + let style = named_dim_style(doc, requested); + DimensionCreationDefaults { + style_name: style + .as_ref() + .map(|style| style.name.clone()) + .filter(|name| !name.trim().is_empty()) + .unwrap_or_else(|| "Standard".to_string()), + style_handle: style.as_ref().map_or(Handle::NULL, |style| style.handle), + text_style_name: style + .as_ref() + .map(|style| style.dimtxsty.clone()) + .filter(|name| !name.trim().is_empty()) + .unwrap_or_else(|| current_text_defaults(doc).style_name), + text_height: style.as_ref().map_or(2.5, |style| style.dimtxt), + gap: style.as_ref().map_or(0.625, |style| style.dimgap), + arrow_size: style.as_ref().map_or(2.5, |style| style.dimasz), + scale: style.as_ref().map_or(1.0, |style| { + if style.annotative { + 1.0 + } else if style.dimscale > 1.0e-9 { + style.dimscale + } else { + 1.0 + } + }), + annotative: style.as_ref().is_some_and(|style| style.annotative), + } +} + +fn entity_uses_default_style(name: &str) -> bool { + name.trim().is_empty() || name.eq_ignore_ascii_case("Standard") +} + +fn apply_text_defaults(doc: &CadDocument, entity: &mut EntityType) { + let active = current_text_defaults(doc); + let style_name = match entity { + EntityType::Text(text) => &mut text.style, + EntityType::MText(text) => &mut text.style, + EntityType::AttributeEntity(attribute) => &mut attribute.text_style, + EntityType::AttributeDefinition(attribute) => &mut attribute.text_style, + _ => return, + }; + if style_name.trim().is_empty() { + style_name.clone_from(&active.style_name); + } + let resolved = named_text_style(doc, style_name) + .unwrap_or_else(|| TextStyle::new(style_name.clone())); + match entity { + EntityType::Text(text) => { + if resolved.height > 1.0e-9 { + text.height = resolved.height; + } + if (text.width_factor - 1.0).abs() <= 1.0e-9 { + text.width_factor = resolved.width_factor; + } + if text.oblique_angle.abs() <= 1.0e-9 { + text.oblique_angle = resolved.oblique_angle; + } + if text.generation_flags == 0 { + text.generation_flags = (if resolved.flags.backward { 2 } else { 0 }) + | (if resolved.flags.upside_down { 4 } else { 0 }); + } + } + EntityType::MText(text) => { + if resolved.height > 1.0e-9 { + text.height = resolved.height; + } + } + EntityType::AttributeEntity(attribute) => { + if resolved.height > 1.0e-9 { + attribute.height = resolved.height; + } + if (attribute.width_factor - 1.0).abs() <= 1.0e-9 { + attribute.width_factor = resolved.width_factor; + } + if attribute.oblique_angle.abs() <= 1.0e-9 { + attribute.oblique_angle = resolved.oblique_angle; + } + if attribute.text_generation_flags == 0 { + attribute.text_generation_flags = + (if resolved.flags.backward { 2 } else { 0 }) + | (if resolved.flags.upside_down { 4 } else { 0 }); + } + } + EntityType::AttributeDefinition(attribute) => { + if resolved.height > 1.0e-9 { + attribute.height = resolved.height; + } + if (attribute.width_factor - 1.0).abs() <= 1.0e-9 { + attribute.width_factor = resolved.width_factor; + } + if attribute.oblique_angle.abs() <= 1.0e-9 { + attribute.oblique_angle = resolved.oblique_angle; + } + if attribute.text_generation_flags == 0 { + attribute.text_generation_flags = + (if resolved.flags.backward { 2 } else { 0 }) + | (if resolved.flags.upside_down { 4 } else { 0 }); + } + } + _ => {} + } +} + +fn apply_dimension_defaults(doc: &CadDocument, entity: &mut EntityType) { + let active = current_dimension_defaults(doc); + match entity { + EntityType::Dimension(dimension) => { + if entity_uses_default_style(&dimension.base().style_name) { + dimension.base_mut().style_name.clone_from(&active.style_name); + } + } + EntityType::Leader(leader) => { + if entity_uses_default_style(&leader.dimension_style) { + leader.dimension_style.clone_from(&active.style_name); + leader.text_height = active.text_height; + leader.dimension_gap = active.gap; + leader.arrow_size = active.arrow_size; + } + } + EntityType::Tolerance(tolerance) => { + if entity_uses_default_style(&tolerance.dimension_style_name) { + tolerance.dimension_style_name.clone_from(&active.style_name); + tolerance.dimension_style_handle = (!active.style_handle.is_null()) + .then_some(active.style_handle); + tolerance.text_height = active.text_height; + tolerance.dimension_gap = active.gap; + } + } + _ => {} + } +} + +fn apply_object_defaults(doc: &CadDocument, entity: &mut EntityType) { + match entity { + EntityType::MultiLeader(leader) if leader.style_handle.is_none() => { + let name = doc.header.current_mleader_style_name.trim(); + if let Some(style) = doc.objects.iter().find_map(|(handle, object)| match object { + ObjectType::MultiLeaderStyle(style) if style.name.eq_ignore_ascii_case(name) => { + let mut style = style.clone(); + style.handle = *handle; + Some(style) + } + _ => None, + }) { + crate::scene::annotative::apply_mleader_style(leader, &style); + } + } + EntityType::Table(table) if table.table_style_handle.is_none() => { + let name = doc.header.current_table_style_name.trim(); + table.table_style_handle = doc.objects.iter().find_map(|(handle, object)| match object { + ObjectType::TableStyle(style) if style.name.eq_ignore_ascii_case(name) => { + Some(*handle) + } + _ => None, + }); + } + EntityType::MLine(mline) if mline.style_handle.is_none() => { + let name = doc.header.multiline_style.trim(); + if let Some((handle, style_name)) = + doc.objects.iter().find_map(|(handle, object)| match object { + ObjectType::MLineStyle(style) if style.name.eq_ignore_ascii_case(name) => { + Some((*handle, style.name.clone())) + } + _ => None, + }) + { + mline.style_handle = Some(handle); + mline.style_name = style_name; + } + } + _ => {} + } +} + +pub fn apply_current_creation_styles(doc: &CadDocument, entity: &mut EntityType) { + apply_text_defaults(doc, entity); + apply_dimension_defaults(doc, entity); + apply_object_defaults(doc, entity); +} diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 415df352c..540f96569 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -8,6 +8,7 @@ pub mod annotative; pub mod cache; pub mod convert; +pub mod creation_style; pub mod model; pub mod pick; pub mod pipeline; @@ -3800,6 +3801,28 @@ impl Scene { self.scale_handle_ensuring("1:1") } + pub(crate) fn creation_annotation_multiplier(&self) -> f64 { + if self.current_layout == "Model" { + return self.annotation_scale.max(1.0e-9) as f64; + } + let Some(handle) = self.active_viewport else { + return 1.0; + }; + let Some(EntityType::Viewport(viewport)) = self.document.get_entity(handle) else { + return 1.0; + }; + let paper_to_model = vp_effective_scale( + viewport.custom_scale, + viewport.view_height, + viewport.height, + ); + if paper_to_model.is_finite() && paper_to_model > 1.0e-9 { + 1.0 / paper_to_model + } else { + 1.0 + } + } + pub fn set_annotation_scale_named(&mut self, name: &str) -> Option { let handle = self.scale_handle_ensuring(name)?; let ObjectType::Scale(scale) = self.document.objects.get(&handle)? else {