forked from 1b-agent/opencadstudio-upstream
Add hatch editing option stages and appearance association workflows
This commit is contained in:
1 parent
7d0c059b30
commit
c947e1ce50
4 files changed
+220
-11
No files matched your search
@@ -4456,6 +4456,31 @@ impl OpenCADStudio {
|
||||
.push_error(crate::t!("HATCHEDIT: hatch entity not found.").as_ref());
|
||||
} else {
|
||||
use crate::command::HatchEditOperation;
|
||||
if matches!(&operation,HatchEditOperation::BeginAssociate) {
|
||||
let associative=matches!(self.tabs[i].scene.document.get_entity(handle),Some(acadrust::EntityType::Hatch(h)) if h.is_associative);
|
||||
if associative {
|
||||
self.command_line.push_info("HATCHEDIT: hatch is already associative.");
|
||||
self.tabs[i].active_cmd=None;
|
||||
}else{
|
||||
let command=crate::modules::draw::draw::hatchedit::HatcheditCommand::for_association(handle,name,scale,angle);
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd=Some(Box::new(command));
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
if let HatchEditOperation::DrawOrderBoundary{above}=&operation {
|
||||
let references:Vec<_>=match self.tabs[i].scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Hatch(h))=>h.paths.iter().flat_map(|p|p.boundary_handles.iter())
|
||||
.filter(|h|self.tabs[i].scene.document.get_entity(**h).is_some()).map(|h|format!("{:X}",h.value())).collect(),
|
||||
_=>Vec::new(),
|
||||
};
|
||||
self.tabs[i].active_cmd=None;
|
||||
if references.is_empty(){self.command_line.push_info("HATCHEDIT: no associated boundary objects.");return Task::none();}
|
||||
self.tabs[i].scene.deselect_all();self.tabs[i].scene.select_entity(handle,false);
|
||||
let command=format!("DRAWORDER {} {}",if *above{"ABOVE"}else{"UNDER"},references.join(" "));
|
||||
return self.dispatch_view(&command,i).unwrap_or_else(Task::none);
|
||||
}
|
||||
if matches!(
|
||||
&operation,
|
||||
HatchEditOperation::DrawOrderFront | HatchEditOperation::DrawOrderBack
|
||||
@@ -4472,6 +4497,22 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.push_undo_snapshot(i, "HATCHEDIT");
|
||||
match operation {
|
||||
HatchEditOperation::Appearance { color, layer, transparency } => {
|
||||
let layer=layer.map(|name|if name=="."{self.tabs[i].active_layer.clone()}else{name});
|
||||
if layer.as_ref().is_some_and(|name|self.tabs[i].scene.document.layers.get(name).is_none()) {
|
||||
self.discard_last_undo_entry(i);
|
||||
self.command_line.push_error("HATCHEDIT: layer not found.");
|
||||
return Task::none();
|
||||
}
|
||||
if let Some(entity)=self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||
let common=entity.common_mut();
|
||||
if let Some(value)=color {common.color=value;common.color_name=None;common.color_book_handle=None;}
|
||||
if let Some(value)=layer {common.layer=value;}
|
||||
if let Some(value)=transparency {common.transparency=value;}
|
||||
}
|
||||
self.tabs[i].scene.bump_entities(&[(handle,crate::scene::ChangeKind::Modified)]);
|
||||
self.refresh_properties();
|
||||
}
|
||||
HatchEditOperation::Update {
|
||||
origin,
|
||||
disassociate,
|
||||
@@ -4591,7 +4632,25 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.edit_hatch_boundary_handles(handle, &handles, false);
|
||||
}
|
||||
HatchEditOperation::RecreateBoundary => {
|
||||
HatchEditOperation::AssociateBoundaries(handles) => {
|
||||
let plane=match self.tabs[i].scene.document.get_entity(handle) {
|
||||
Some(acadrust::EntityType::Hatch(h))=>{
|
||||
let storage=crate::entities::curve::ocs_plane(h.normal,h.elevation);
|
||||
crate::command::WorkingPlane::new(glam::DVec3::from_array(storage.origin),glam::DVec3::from_array(storage.x_axis),glam::DVec3::from_array(storage.y_axis))
|
||||
},
|
||||
_=>self.tabs[i].ucs_xform().working_plane(),
|
||||
};
|
||||
let mut sources=self.tabs[i].scene.boundary_sources_on_plane(plane,1e-6);
|
||||
sources.retain(|h,_|handles.contains(h)&&*h!=handle);
|
||||
if crate::scene::boundary_faces(&sources,1e-6).is_empty() {
|
||||
self.discard_last_undo_entry(i);
|
||||
self.command_line.push_error("HATCHEDIT: selected objects do not form a closed boundary.");
|
||||
return Task::none();
|
||||
}
|
||||
let valid_handles:Vec<_>=sources.keys().copied().collect();
|
||||
self.tabs[i].scene.edit_hatch_boundary_handles(handle,&valid_handles,true);
|
||||
}
|
||||
HatchEditOperation::RecreateBoundary { associate } => {
|
||||
let source = self.tabs[i].scene.document.get_entity(handle).cloned();
|
||||
if let Some(acadrust::EntityType::Hatch(source)) = source {
|
||||
let storage = crate::entities::curve::ocs_plane(
|
||||
@@ -4611,7 +4670,7 @@ impl OpenCADStudio {
|
||||
handles.push(boundary);
|
||||
}
|
||||
}
|
||||
if let Some(acadrust::EntityType::Hatch(hatch)) =
|
||||
if associate { if let Some(acadrust::EntityType::Hatch(hatch)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||
{
|
||||
for (path, boundary) in
|
||||
@@ -4621,7 +4680,7 @@ impl OpenCADStudio {
|
||||
path.flags.set_external(true);
|
||||
}
|
||||
hatch.is_associative = !handles.is_empty();
|
||||
}
|
||||
} }
|
||||
self.tabs[i].scene.bump_entities(&[(
|
||||
handle,
|
||||
crate::scene::ChangeKind::Modified,
|
||||
@@ -4650,7 +4709,9 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
HatchEditOperation::DrawOrderFront
|
||||
| HatchEditOperation::DrawOrderBack => unreachable!(),
|
||||
| HatchEditOperation::DrawOrderBack
|
||||
| HatchEditOperation::DrawOrderBoundary {..}
|
||||
| HatchEditOperation::BeginAssociate => unreachable!(),
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.command_line
|
||||
|
||||
@@ -808,7 +808,7 @@ impl OpenCADStudio {
|
||||
scale,
|
||||
angle,
|
||||
annotative,
|
||||
);
|
||||
).with_appearance(entity,self.tabs[i].scene.document.header.current_entity_color);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
} else {
|
||||
|
||||
+9
-1
@@ -13,13 +13,21 @@ use glam::DVec3;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HatchEditOperation {
|
||||
Appearance {
|
||||
color: Option<acadrust::types::Color>,
|
||||
layer: Option<String>,
|
||||
transparency: Option<acadrust::types::Transparency>,
|
||||
},
|
||||
Update {
|
||||
origin: Option<(f64, f64)>,
|
||||
disassociate: bool,
|
||||
style: Option<acadrust::entities::HatchStyleType>,
|
||||
annotative: Option<bool>,
|
||||
},
|
||||
RecreateBoundary,
|
||||
RecreateBoundary { associate: bool },
|
||||
BeginAssociate,
|
||||
AssociateBoundaries(Vec<Handle>),
|
||||
DrawOrderBoundary { above: bool },
|
||||
Separate,
|
||||
AddBoundaries(Vec<Handle>),
|
||||
RemoveBoundaries(Vec<Handle>),
|
||||
|
||||
@@ -31,6 +31,10 @@ pub struct HatcheditCommand {
|
||||
style: Option<acadrust::entities::HatchStyleType>,
|
||||
annotative: Option<bool>,
|
||||
annotative_current: bool,
|
||||
input: Option<&'static str>,
|
||||
boundary_selection: Vec<Handle>,
|
||||
source_appearance: Option<(acadrust::types::Color,String,acadrust::types::Transparency)>,
|
||||
current_color: acadrust::types::Color,
|
||||
}
|
||||
|
||||
impl HatcheditCommand {
|
||||
@@ -42,6 +46,10 @@ impl HatcheditCommand {
|
||||
style: None,
|
||||
annotative: None,
|
||||
annotative_current: false,
|
||||
input: None,
|
||||
boundary_selection: Vec::new(),
|
||||
source_appearance: None,
|
||||
current_color: acadrust::types::Color::ByLayer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +72,10 @@ impl HatcheditCommand {
|
||||
style: None,
|
||||
annotative: None,
|
||||
annotative_current: annotative,
|
||||
input: None,
|
||||
boundary_selection: Vec::new(),
|
||||
source_appearance: None,
|
||||
current_color: acadrust::types::Color::ByLayer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +98,15 @@ impl HatcheditCommand {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn for_association(handle:Handle,name:String,scale:f32,angle:f32)->Self {
|
||||
let mut command=Self::with_handle(handle,name,scale,angle,false);
|
||||
command.input=Some("associate-select");command
|
||||
}
|
||||
pub fn with_appearance(mut self,entity:Option<&acadrust::EntityType>,current_color:acadrust::types::Color)->Self {
|
||||
self.source_appearance=entity.map(|e|{let c=e.common();(c.color,c.layer.clone(),c.transparency)});
|
||||
self.current_color=current_color;self
|
||||
}
|
||||
|
||||
fn update_operation(&self) -> HatchEditOperation {
|
||||
HatchEditOperation::Update {
|
||||
origin: self.origin,
|
||||
@@ -102,6 +123,42 @@ impl CadCommand for HatcheditCommand {
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
if let Some(input)=self.input {
|
||||
if let Some((color,layer,transparency))=&self.source_appearance {
|
||||
match input {
|
||||
"color"=>return format!("New color [Truecolor/. (for use current)] <{color:?}>:"),
|
||||
"layer"=>return format!("Specify layer or [. (for use current)] <{layer}>:"),
|
||||
"transparency"=>return format!("Specify transparency (0-90) or ByLayer/ByBlock <{}>:",match transparency {
|
||||
acadrust::types::Transparency::ByLayer=>"ByLayer".into(),
|
||||
acadrust::types::Transparency::ByBlock=>"ByBlock".into(),
|
||||
value=>format!("{:.0}",value.as_percent()*100.0),
|
||||
}),
|
||||
_=>{},
|
||||
}
|
||||
}
|
||||
if let HatcheditStep::EditOptions{name,scale,angle,..}=&self.step {
|
||||
match input {
|
||||
"pattern"=>return format!("Enter a pattern name or [Solid] <{name}>:"),
|
||||
"scale"=>return format!("Specify a scale for the pattern <{scale:.4}>:"),
|
||||
"angle"=>return format!("Specify an angle for the pattern <{angle:.4}>:"),
|
||||
_=>{},
|
||||
}
|
||||
}
|
||||
return match input {
|
||||
"pattern"=>"Enter a pattern name or [Solid]:",
|
||||
"scale"=>"Specify a scale for the pattern:",
|
||||
"angle"=>"Specify an angle for the pattern:",
|
||||
"color"=>"New color [Truecolor] <ByLayer>:",
|
||||
"truecolor"=>"Specify RGB color (red,green,blue):",
|
||||
"layer"=>"Specify layer or [. (for use current)]:",
|
||||
"transparency"=>"Specify transparency value (0-90) or ByLayer/ByBlock:",
|
||||
"draworder"=>"Enter draw order [do Not change/send to Back/bring to Front/send beHind boundary/bring in front of bounDary] <do Not change>:",
|
||||
"boundary-type"=>"Enter type of boundary object [Polyline] <Polyline>:",
|
||||
"boundary-associate"=>"Reassociate hatch with new boundary? [Yes/No] <No>:",
|
||||
"associate-select"=>"Select boundary objects:",
|
||||
_=>"Specify value:",
|
||||
}.into();
|
||||
}
|
||||
match &self.step {
|
||||
HatcheditStep::PickHatch => t!("HATCHEDIT Select hatch:").into_owned(),
|
||||
HatcheditStep::EditOptions {
|
||||
@@ -110,7 +167,7 @@ impl CadCommand for HatcheditCommand {
|
||||
let scale = format!("{scale:.4}");
|
||||
let angle = format!("{angle:.1}");
|
||||
t!(
|
||||
"HATCHEDIT Pattern:%{name} Scale:%{scale} Angle:%{angle} [P pattern / S scale / A angle / O x,y / D disassociate / Y style / N annotative / R recreate / E separate / + handles / - handles | Enter]:",
|
||||
"HATCHEDIT Pattern:%{name} Scale:%{scale} Angle:%{angle} [Properties/COlor/LAyer/Transparency/DRaw order/Disassociate/Annotative/Recreate/Separate] <Properties>:",
|
||||
name = name,
|
||||
scale = scale,
|
||||
angle = angle
|
||||
@@ -123,6 +180,10 @@ impl CadCommand for HatcheditCommand {
|
||||
fn needs_entity_pick(&self) -> bool {
|
||||
matches!(self.step, HatcheditStep::PickHatch)
|
||||
}
|
||||
fn is_selection_gathering(&self)->bool {self.input==Some("associate-select")}
|
||||
fn on_selection_complete(&mut self,handles:Vec<Handle>)->CmdResult {
|
||||
self.boundary_selection=handles;CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_entity_pick(&mut self, handle: Handle, _pt: DVec3) -> CmdResult {
|
||||
if handle.is_null() {
|
||||
@@ -140,14 +201,21 @@ impl CadCommand for HatcheditCommand {
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
matches!(self.step, HatcheditStep::EditOptions { .. })
|
||||
matches!(self.step, HatcheditStep::EditOptions { .. })&&!self.is_selection_gathering()
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
if self.input.is_some() {return Vec::new();}
|
||||
if !matches!(self.step, HatcheditStep::EditOptions { .. }) {
|
||||
return Vec::new();
|
||||
}
|
||||
vec![
|
||||
crate::command::CmdOption::new("Properties", "P"),
|
||||
crate::command::CmdOption::new("Color", "CO"),
|
||||
crate::command::CmdOption::new("Layer", "LA"),
|
||||
crate::command::CmdOption::new("Transparency", "T"),
|
||||
crate::command::CmdOption::new("Draw order", "DR"),
|
||||
crate::command::CmdOption::new("Associate", "AS"),
|
||||
crate::command::CmdOption::new("Disassociate", "D"),
|
||||
crate::command::CmdOption::new("Annotative", "N"),
|
||||
crate::command::CmdOption::new("Recreate boundary", "R"),
|
||||
@@ -159,6 +227,62 @@ impl CadCommand for HatcheditCommand {
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
let keyword=text.trim().to_ascii_uppercase();
|
||||
if let Some(input)=self.input {
|
||||
use acadrust::types::{Color,Transparency};
|
||||
let appearance=|color,layer,transparency|HatchEditOperation::Appearance{color,layer,transparency};
|
||||
match input {
|
||||
"color"=>{
|
||||
if matches!(keyword.as_str(),"T"|"TRUECOLOR") {self.input=Some("truecolor");return Some(CmdResult::NeedPoint);}
|
||||
let color=match keyword.as_str(){"."=>Some(self.current_color),"BYLAYER"=>Some(Color::ByLayer),"BYBLOCK"=>Some(Color::ByBlock),
|
||||
"RED"=>Some(Color::Index(1)),"YELLOW"=>Some(Color::Index(2)),"GREEN"=>Some(Color::Index(3)),
|
||||
"CYAN"=>Some(Color::Index(4)),"BLUE"=>Some(Color::Index(5)),"MAGENTA"=>Some(Color::Index(6)),"WHITE"=>Some(Color::Index(7)),
|
||||
n=>n.parse::<i16>().ok().filter(|v|(0..=256).contains(v)).map(Color::from_index)};
|
||||
return Some(color.and_then(|v|self.apply_result(appearance(Some(v),None,None))).unwrap_or(CmdResult::NeedPoint));
|
||||
}
|
||||
"truecolor"=>{
|
||||
let rgb:Option<Vec<u8>>=keyword.split(',').map(|s|s.trim().parse().ok()).collect();
|
||||
if let Some(rgb)=rgb.filter(|rgb|rgb.len()==3) {return self.apply_result(appearance(Some(Color::from_rgb(rgb[0],rgb[1],rgb[2])),None,None));}
|
||||
}
|
||||
"layer"=>if !text.trim().is_empty(){return self.apply_result(appearance(None,Some(text.trim().to_owned()),None));},
|
||||
"transparency"=>{
|
||||
let value=match keyword.as_str(){"BYLAYER"=>Some(Transparency::BY_LAYER),"BYBLOCK"=>Some(Transparency::BY_BLOCK),
|
||||
n=>n.parse::<u8>().ok().filter(|v|*v<=90).map(|v|Transparency::from_percent(v as f64 / 100.0))};
|
||||
if let Some(value)=value{return self.apply_result(appearance(None,None,Some(value)));}
|
||||
}
|
||||
"draworder"=>return match keyword.as_str(){"F"|"FRONT"=>self.apply_result(HatchEditOperation::DrawOrderFront),
|
||||
"H"|"BEHIND"=>self.apply_result(HatchEditOperation::DrawOrderBoundary{above:false}),
|
||||
"D"=>self.apply_result(HatchEditOperation::DrawOrderBoundary{above:true}),
|
||||
"B"|"BACK"=>self.apply_result(HatchEditOperation::DrawOrderBack),"N"|"NOT"=>Some(CmdResult::Cancel),_=>Some(CmdResult::NeedPoint)},
|
||||
"boundary-type"=>if matches!(keyword.as_str(),"P"|"POLYLINE"){self.input=Some("boundary-associate");},
|
||||
"boundary-associate"=>return match keyword.as_str(){
|
||||
"Y"|"YES"=>self.apply_result(HatchEditOperation::RecreateBoundary{associate:true}),
|
||||
"N"|"NO"=>self.apply_result(HatchEditOperation::RecreateBoundary{associate:false}),
|
||||
_=>Some(CmdResult::NeedPoint),
|
||||
},
|
||||
"pattern"=>{
|
||||
if crate::scene::model::hatch_patterns::find(&keyword).is_some() {
|
||||
if let HatcheditStep::EditOptions{name,..}=&mut self.step{*name=keyword.clone();}
|
||||
if keyword=="SOLID" {return self.apply_result(self.update_operation());}
|
||||
self.input=Some("scale");
|
||||
}
|
||||
}
|
||||
"scale"|"angle"=>if let Ok(value)=keyword.parse::<f32>() {if value.is_finite()&&(input=="angle"||value>0.0){
|
||||
if let HatcheditStep::EditOptions{scale,angle,..}=&mut self.step{if input=="scale"{*scale=value;}else{*angle=value;}}
|
||||
if input=="scale"{self.input=Some("angle");}else{return self.apply_result(self.update_operation());}
|
||||
}},
|
||||
_=>{},
|
||||
}
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
let next=match keyword.as_str(){"P"|"PROPERTIES"=>Some("pattern"),"CO"|"COLOR"=>Some("color"),"LA"|"LAYER"=>Some("layer"),
|
||||
"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);}
|
||||
if matches!(keyword.as_str(),"AS"|"ASSOCIATE"){return self.apply_result(HatchEditOperation::BeginAssociate);}
|
||||
if matches!(keyword.as_str(),"DI"|"DISASSOCIATE"){
|
||||
self.disassociate=true;return self.apply_result(self.update_operation());
|
||||
}
|
||||
let (_handle, name, scale, angle) = match &mut self.step {
|
||||
HatcheditStep::EditOptions {
|
||||
handle,
|
||||
@@ -236,7 +360,7 @@ impl CadCommand for HatcheditCommand {
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
if text == "R" || text == "RECREATE" {
|
||||
return self.apply_result(HatchEditOperation::RecreateBoundary);
|
||||
self.input=Some("boundary-type");return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
if text == "E" || text == "SEPARATE" {
|
||||
return self.apply_result(HatchEditOperation::Separate);
|
||||
@@ -273,8 +397,24 @@ impl CadCommand for HatcheditCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
// Enter without text → apply current settings
|
||||
self.apply_result(self.update_operation()).unwrap_or(CmdResult::Cancel)
|
||||
match self.input {
|
||||
None if matches!(self.step,HatcheditStep::EditOptions{..})=>{
|
||||
self.input=Some("pattern");CmdResult::NeedPoint
|
||||
}
|
||||
Some("pattern")=>{
|
||||
if matches!(&self.step,HatcheditStep::EditOptions{name,..} if name.eq_ignore_ascii_case("SOLID")) {
|
||||
self.apply_result(self.update_operation()).unwrap_or(CmdResult::Cancel)
|
||||
}else{self.input=Some("scale");CmdResult::NeedPoint}
|
||||
}
|
||||
Some("scale")=>{self.input=Some("angle");CmdResult::NeedPoint}
|
||||
Some("angle")=>self.apply_result(self.update_operation()).unwrap_or(CmdResult::Cancel),
|
||||
Some("boundary-type")=>{self.input=Some("boundary-associate");CmdResult::NeedPoint},
|
||||
Some("boundary-associate")=>self.apply_result(HatchEditOperation::RecreateBoundary{associate:false}).unwrap_or(CmdResult::Cancel),
|
||||
Some("associate-select")=>if self.boundary_selection.is_empty(){CmdResult::Cancel}else{
|
||||
self.apply_result(HatchEditOperation::AssociateBoundaries(self.boundary_selection.clone())).unwrap_or(CmdResult::Cancel)
|
||||
},
|
||||
_=>CmdResult::Cancel,
|
||||
}
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
|
||||
Reference in new issue
Block a user