diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 74e23845b..b626b575e 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -4524,10 +4524,20 @@ impl OpenCADStudio { } HatchEditOperation::Update { origin, + store_origin, disassociate, style, annotative, } => { + if store_origin { + if let Some((x, y)) = origin { + if !self.tabs[i].scene.document.set_hatch_origin([x, y]) { + self.discard_last_undo_entry(i); + self.command_line.push_error("HATCHEDIT: cannot store default origin."); + return Task::none(); + } + } + } if let Some(acadrust::EntityType::Hatch(hatch)) = self.tabs[i].scene.document.get_entity_mut(handle) { diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index 2ed934146..55638e914 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -775,7 +775,7 @@ impl OpenCADStudio { selected, inherited, plane, - ); + ).with_origin(self.tabs[i].scene.document.hatch_origin()); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); self.refresh_area_preview(i); @@ -808,7 +808,7 @@ impl OpenCADStudio { scale, angle, annotative, - ).with_appearance(entity,self.tabs[i].scene.document.header.current_entity_color,self.tabs[i].scene.document.current_entity_transparency()); + ).with_appearance(entity,self.tabs[i].scene.document.header.current_entity_color,self.tabs[i].scene.document.current_entity_transparency()).with_origin(self.tabs[i].scene.document.hatch_origin()); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { @@ -816,7 +816,7 @@ impl OpenCADStudio { .push_error(crate::t!("HATCHEDIT: selected entity is not a hatch.").as_ref()); } } else { - let cmd = HatcheditCommand::new().with_appearance(None,self.tabs[i].scene.document.header.current_entity_color,self.tabs[i].scene.document.current_entity_transparency()); + let cmd = HatcheditCommand::new().with_appearance(None,self.tabs[i].scene.document.header.current_entity_color,self.tabs[i].scene.document.current_entity_transparency()).with_origin(self.tabs[i].scene.document.hatch_origin()); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } diff --git a/src/command.rs b/src/command.rs index 94ad82e6b..88a7be8d3 100644 --- a/src/command.rs +++ b/src/command.rs @@ -20,6 +20,7 @@ pub enum HatchEditOperation { }, Update { origin: Option<(f64, f64)>, + store_origin: bool, disassociate: bool, style: Option, annotative: Option, diff --git a/src/modules/draw/draw/hatch.rs b/src/modules/draw/draw/hatch.rs index 7a51f986e..8fa7c244a 100644 --- a/src/modules/draw/draw/hatch.rs +++ b/src/modules/draw/draw/hatch.rs @@ -196,6 +196,7 @@ pub struct HatchCommand { pattern_override: Option<(String, HatchPattern)>, angle_override: Option, scale_override: Option, + default_origin: [f64; 2], associative: bool, separate_hatches: bool, island_style: acadrust::entities::HatchStyleType, @@ -244,6 +245,7 @@ impl HatchCommand { pattern_override: None, angle_override: None, scale_override: None, + default_origin: [0.0, 0.0], associative: true, separate_hatches: false, island_style: inherited @@ -257,6 +259,8 @@ impl HatchCommand { command } + pub fn with_origin(mut self, origin: [f64; 2]) -> Self { self.default_origin = origin; self } + fn set_object_selection(&mut self, handles: Vec) { let mut segments = Vec::new(); for handle in &handles { @@ -425,10 +429,21 @@ impl HatchCommand { dashes: vec![], }]) }); - let (name, pattern) = self + let (name, mut pattern) = self .pattern_override .clone() .unwrap_or_else(|| (pat_name.to_string(), default_pattern)); + let angle = self.angle_override.unwrap_or(0.0); + let scale = self.scale_override.unwrap_or(1.0).max(1.0e-6); + if let (HatchPattern::Pattern(families), Some(anchor)) = (&mut pattern, local_boundary.first()) { + let (sin, cos) = (angle as f64).sin_cos(); + let dx = self.default_origin[0] - anchor[0] as f64; + let dy = self.default_origin[1] - anchor[1] as f64; + for family in families { + family.x0 += ((dx * cos + dy * sin) / scale as f64) as f32; + family.y0 += ((-dx * sin + dy * cos) / scale as f64) as f32; + } + } HatchModel { render_instance: None, boundary: std::sync::Arc::new(rel), diff --git a/src/modules/draw/draw/hatchedit.rs b/src/modules/draw/draw/hatchedit.rs index a884b659d..1a104f1d4 100644 --- a/src/modules/draw/draw/hatchedit.rs +++ b/src/modules/draw/draw/hatchedit.rs @@ -28,6 +28,9 @@ pub struct HatcheditCommand { step: HatcheditStep, origin: Option<(f64, f64)>, disassociate: bool, + store_origin: bool, + current_origin: [f64; 2], + origin_plane: cadkernel::space::Plane, style: Option, annotative: Option, annotative_current: bool, @@ -49,6 +52,9 @@ impl HatcheditCommand { step: HatcheditStep::PickHatch, origin: None, disassociate: false, + store_origin: false, + current_origin: [0.0, 0.0], + origin_plane: cadkernel::space::Plane::from_axes([0.0;3],[1.0,0.0,0.0],[0.0,1.0,0.0]), style: None, annotative: None, annotative_current: false, @@ -81,6 +87,9 @@ impl HatcheditCommand { }, origin: None, disassociate: false, + store_origin: false, + current_origin: [0.0, 0.0], + origin_plane: cadkernel::space::Plane::from_axes([0.0;3],[1.0,0.0,0.0],[0.0,1.0,0.0]), style: None, annotative: None, annotative_current: annotative, @@ -129,14 +138,20 @@ impl HatcheditCommand { } } pub fn with_appearance(mut self,entity:Option<&acadrust::EntityType>,current_color:acadrust::types::Color,current_transparency:acadrust::types::Transparency)->Self { - if let Some(acadrust::EntityType::Hatch(hatch)) = entity { self.style_current = hatch.style; } + if let Some(acadrust::EntityType::Hatch(hatch)) = entity { + self.style_current = hatch.style; + self.origin_plane = crate::entities::curve::ocs_plane(hatch.normal, hatch.elevation); + } self.source_appearance=entity.map(|e|{let c=e.common();(c.color,c.layer.clone(),c.transparency)}); self.current_color=current_color;self.current_transparency=current_transparency;self } + pub fn with_origin(mut self, origin: [f64; 2]) -> Self { self.current_origin = origin; self } + fn update_operation(&self) -> HatchEditOperation { HatchEditOperation::Update { origin: self.origin, + store_origin: self.store_origin, disassociate: self.disassociate, style: self.style, annotative: self.annotative, @@ -181,6 +196,9 @@ impl CadCommand for HatcheditCommand { } return match input { "annotative"=>if self.annotative_current {"Make hatch annotative [Yes/No] :"} else {"Make hatch annotative [Yes/No] :"}, + "origin"=>"[Use current origin/Set new origin] :", + "origin-point"=>"Select point:", + "origin-store"=>"Store as default origin? [Yes/No] :", "pattern"=>"Enter a pattern name or [Solid]:", "scale"=>"Specify a scale for the pattern:", "angle"=>"Specify an angle for the pattern:", @@ -205,7 +223,7 @@ impl CadCommand for HatcheditCommand { let scale = format!("{scale:.4}"); let angle = format!("{angle:.1}"); t!( - "HATCHEDIT Pattern:%{name} Scale:%{scale} Angle:%{angle} [Style/Properties/COlor/LAyer/Transparency/DRaw order/ASsociate/DIsassociate/ANnotative/recreate Boundary/separate Hatches] :", + "HATCHEDIT Pattern:%{name} Scale:%{scale} Angle:%{angle} [Style/Origin/Properties/COlor/LAyer/Transparency/DRaw order/ASsociate/DIsassociate/ANnotative/recreate Boundary/separate Hatches] :", name = name, scale = scale, angle = angle @@ -253,6 +271,8 @@ impl CadCommand for HatcheditCommand { if let Some(input) = self.input { use crate::command::CmdOption; return match input { + "origin" => vec![CmdOption::new("Use current origin", "U"), CmdOption::new("Set new origin", "S")], + "origin-store" => vec![CmdOption::new("Yes", "Y"), CmdOption::new("No", "N")], "style" => vec![CmdOption::new("Ignore", "I"), CmdOption::new("Outer", "O"), CmdOption::new("Normal", "N")], "draworder" => vec![CmdOption::new("Do not change", "N"), CmdOption::new("Send to back", "B"), CmdOption::new("Bring to front", "F"), CmdOption::new("Behind boundary", "H"), CmdOption::new("In front of boundary", "D")], "annotative" | "boundary-associate" => vec![CmdOption::new("Yes", "Y"), CmdOption::new("No", "N")], @@ -267,6 +287,7 @@ impl CadCommand for HatcheditCommand { } vec![ crate::command::CmdOption::new("Style", "S"), + crate::command::CmdOption::new("Origin", "O"), crate::command::CmdOption::new("Properties", "P"), crate::command::CmdOption::new("Color", "CO"), crate::command::CmdOption::new("Layer", "LA"), @@ -287,6 +308,21 @@ impl CadCommand for HatcheditCommand { use acadrust::types::{Color,Transparency}; let appearance=|color,layer,transparency|HatchEditOperation::Appearance{color,layer,transparency}; match input { + "origin" => match keyword.as_str() { + "U" | "USE" => { self.origin = Some((self.current_origin[0], self.current_origin[1])); return self.apply_result(self.update_operation()); } + "S" | "SET" => { self.input = Some("origin-point"); } + _ => {}, + }, + "origin-point" => { + let values: Option> = text.split(',').map(|s| s.trim().parse().ok()).collect(); + if let Some(values) = values.filter(|v| (2..=3).contains(&v.len()) && v.iter().all(|n| n.is_finite())) { + return Some(self.on_point(DVec3::new(values[0], values[1], values.get(2).copied().unwrap_or(0.0)))); + } + } + "origin-store" => { + self.store_origin = match keyword.as_str() { "Y" | "YES" => true, "N" | "NO" => false, _ => return Some(CmdResult::NeedPoint) }; + return self.apply_result(self.update_operation()); + } "style" => { self.style = match keyword.as_str() { "I" | "IGNORE" => Some(acadrust::entities::HatchStyleType::Ignore), @@ -352,7 +388,7 @@ impl CadCommand for HatcheditCommand { } return Some(CmdResult::NeedPoint); } - let next=match keyword.as_str(){"S"|"STYLE"=>Some("style"),"P"|"PROPERTIES"=>Some("pattern"),"CO"|"COLOR"=>Some("color"),"LA"|"LAYER"=>Some("layer"), + let next=match keyword.as_str(){"O"|"ORIGIN"=>Some("origin"),"S"|"STYLE"=>Some("style"),"P"|"PROPERTIES"=>Some("pattern"),"CO"|"COLOR"=>Some("color"),"LA"|"LAYER"=>Some("layer"), "AN"|"ANNOTATIVE"=>Some("annotative"),"T"|"TRANSPARENCY"=>Some("transparency"),"DR"|"DRAW"|"DRAW ORDER"=>Some("draworder"), "B"|"BOUNDARY"|"R"|"RECREATE"=>Some("boundary-type"),_=>None}; if let Some(input)=next {self.input=Some(input);return Some(CmdResult::NeedPoint);} @@ -472,6 +508,15 @@ impl CadCommand for HatcheditCommand { } fn on_point(&mut self, pt: DVec3) -> CmdResult { + if self.input == Some("origin-point") { + if let Some(point) = self.origin_plane.project(pt.to_array()) { + if point.iter().all(|value| value.is_finite()) { + self.origin = Some((point[0], point[1])); + self.input = Some("origin-store"); + } + } + return CmdResult::NeedPoint; + } if self.input==Some("associate-point") { if let Some((plane,sources))=&self.association_sources { let point=plane.to_local(pt); @@ -488,6 +533,9 @@ impl CadCommand for HatcheditCommand { None if matches!(self.step,HatcheditStep::EditOptions{..})=>{ self.input=Some("pattern");CmdResult::NeedPoint } + Some("origin")=>{self.origin=Some((self.current_origin[0],self.current_origin[1]));self.apply_result(self.update_operation()).unwrap_or(CmdResult::Cancel)}, + Some("origin-point")=>CmdResult::NeedPoint, + Some("origin-store")=>self.apply_result(self.update_operation()).unwrap_or(CmdResult::Cancel), Some("style")=>{self.style=Some(self.style_current);self.apply_result(self.update_operation()).unwrap_or(CmdResult::Cancel)}, Some("annotative")=>{self.annotative=Some(self.annotative_current);self.apply_result(self.update_operation()).unwrap_or(CmdResult::Cancel)}, Some("pattern")=>{