diff --git a/src/app/commands/inquiry.rs b/src/app/commands/inquiry.rs index d1577fc3e..9458f465b 100644 --- a/src/app/commands/inquiry.rs +++ b/src/app/commands/inquiry.rs @@ -253,6 +253,7 @@ impl OpenCADStudio { is_poly: true, convertible: false, mesh_size: None, + mesh_closed: None, }, )), acadrust::EntityType::Line(_) | acadrust::EntityType::Arc(_) => { @@ -262,6 +263,7 @@ impl OpenCADStudio { is_poly: false, convertible: true, mesh_size: None, + mesh_closed: None, }, )) } @@ -274,6 +276,7 @@ impl OpenCADStudio { mesh.m_vertex_count.max(0) as usize, mesh.n_vertex_count.max(0) as usize, )), + mesh_closed: Some((mesh.is_closed_m(), mesh.is_closed_n())), }, )), _ => None, @@ -284,7 +287,14 @@ impl OpenCADStudio { // the convert prompt) skips the select step. let preselected: Vec = self.tabs[i].scene.selected.iter().copied().collect(); - let cmd_obj = PeditCommand::new(info).with_preselection(&preselected); + let header = &self.tabs[i].scene.document.header; + let cmd_obj = PeditCommand::new( + info, + header.surface_type, + header.surface_u_density, + header.surface_v_density, + ) + .with_preselection(&preselected); self.command_line.push_info(&cmd_obj.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd_obj)); } diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index 381d1c186..471c67f08 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -1667,33 +1667,36 @@ impl OpenCADStudio { None => Ok((format!("SPLINESEGS = {}", h.spline_segments), false)), }, "SURFU" => match &value { - Some(v) => v - .parse::() - .map(|x| { + Some(v) => match v.parse::() { + Ok(x @ 0..=200) => { h.surface_u_density = x; - (format!("SURFU = {x}"), true) - }) - .map_err(|_| "SETVAR: integer value required.".into()), + Ok((format!("SURFU = {x}"), true)) + } + Ok(_) => Err("SETVAR: value must be between 0 and 200.".into()), + Err(_) => Err("SETVAR: integer value required.".into()), + }, None => Ok((format!("SURFU = {}", h.surface_u_density), false)), }, "SURFV" => match &value { - Some(v) => v - .parse::() - .map(|x| { + Some(v) => match v.parse::() { + Ok(x @ 0..=200) => { h.surface_v_density = x; - (format!("SURFV = {x}"), true) - }) - .map_err(|_| "SETVAR: integer value required.".into()), + Ok((format!("SURFV = {x}"), true)) + } + Ok(_) => Err("SETVAR: value must be between 0 and 200.".into()), + Err(_) => Err("SETVAR: integer value required.".into()), + }, None => Ok((format!("SURFV = {}", h.surface_v_density), false)), }, "SURFTYPE" => match &value { - Some(v) => v - .parse::() - .map(|x| { + Some(v) => match v.parse::() { + Ok(x @ (5 | 6 | 8)) => { h.surface_type = x; - (format!("SURFTYPE = {x}"), true) - }) - .map_err(|_| "SETVAR: integer value required.".into()), + Ok((format!("SURFTYPE = {x}"), true)) + } + Ok(_) => Err("SETVAR: value must be 5, 6, or 8.".into()), + Err(_) => Err("SETVAR: integer value required.".into()), + }, None => Ok((format!("SURFTYPE = {}", h.surface_type), false)), }, "SHADEDGE" => match &value { diff --git a/src/modules/draw/modify/pedit.rs b/src/modules/draw/modify/pedit.rs index 709c7f5cb..63051e5d0 100644 --- a/src/modules/draw/modify/pedit.rs +++ b/src/modules/draw/modify/pedit.rs @@ -22,6 +22,9 @@ pub struct PeditTarget { pub convertible: bool, /// M/N size for a legacy polygon mesh; absent for ordinary polylines. pub mesh_size: Option<(usize, usize)>, + /// Current M/N closure state for a polygon mesh. The option list exposes + /// only the operation applicable to each direction. + pub mesh_closed: Option<(bool, bool)>, } enum Mode { @@ -43,15 +46,40 @@ pub struct PeditCommand { info: HashMap, mode: Mode, undo_count: usize, + mesh_smooth_type: acadrust::entities::polygon_mesh::SurfaceSmoothType, + mesh_smooth_density: (i16, i16), + mesh_vertex_default: isize, + pending_mesh_closed: Option<(bool, bool)>, + mesh_closed_history: Vec>, } impl PeditCommand { - pub fn new(info: HashMap) -> Self { + pub fn new( + info: HashMap, + surface_type: i16, + surface_u_density: i16, + surface_v_density: i16, + ) -> Self { + use acadrust::entities::polygon_mesh::SurfaceSmoothType; + + let mesh_smooth_type = match surface_type { + 5 => SurfaceSmoothType::Quadratic, + 8 => SurfaceSmoothType::Bezier, + _ => SurfaceSmoothType::Cubic, + }; Self { target: None, info, mode: Mode::PickTarget, undo_count: 0, + mesh_smooth_type, + mesh_smooth_density: ( + surface_u_density.clamp(2, 200), + surface_v_density.clamp(2, 200), + ), + mesh_vertex_default: 1, + pending_mesh_closed: None, + mesh_closed_history: Vec::new(), } } @@ -79,6 +107,37 @@ impl PeditCommand { let handle = self.target?; self.info.get(&handle.value())?.mesh_size } + + fn mesh_closed(&self) -> Option<(bool, bool)> { + let handle = self.target?; + self.info.get(&handle.value())?.mesh_closed + } + + fn set_mesh_closed(&mut self, m_direction: bool, closed: bool) { + let Some(handle) = self.target else { + return; + }; + let Some(target) = self.info.get_mut(&handle.value()) else { + return; + }; + let Some((closed_m, closed_n)) = target.mesh_closed.as_mut() else { + return; + }; + if m_direction { + *closed_m = closed; + } else { + *closed_n = closed; + } + } + + fn replace_mesh_closed(&mut self, closed: (bool, bool)) { + let Some(handle) = self.target else { + return; + }; + if let Some(target) = self.info.get_mut(&handle.value()) { + target.mesh_closed = Some(closed); + } + } } impl CadCommand for PeditCommand { @@ -118,17 +177,26 @@ impl CadCommand for PeditCommand { fn options(&self) -> Vec { use crate::command::CmdOption; match &self.mode { - Mode::Options if self.mesh_size().is_some() => vec![ - CmdOption::new(t!("Edit vertex").as_ref(), "E"), - CmdOption::new(t!("Smooth surface").as_ref(), "S"), - CmdOption::new(t!("Desmooth").as_ref(), "D"), - CmdOption::new(t!("M close").as_ref(), "MC"), - CmdOption::new(t!("M open").as_ref(), "MO"), - CmdOption::new(t!("N close").as_ref(), "NC"), - CmdOption::new(t!("N open").as_ref(), "NO"), - CmdOption::new(t!("Undo").as_ref(), "U"), - CmdOption::new(t!("eXit").as_ref(), "X"), - ], + Mode::Options if self.mesh_size().is_some() => { + let (closed_m, closed_n) = self.mesh_closed().unwrap_or((false, false)); + vec![ + CmdOption::new(t!("Edit vertex").as_ref(), "E"), + CmdOption::new(t!("Smooth surface").as_ref(), "S"), + CmdOption::new(t!("Desmooth").as_ref(), "D"), + if closed_m { + CmdOption::new(t!("M open").as_ref(), "MO") + } else { + CmdOption::new(t!("M close").as_ref(), "MC") + }, + if closed_n { + CmdOption::new(t!("N open").as_ref(), "NO") + } else { + CmdOption::new(t!("N close").as_ref(), "NC") + }, + CmdOption::new(t!("Undo").as_ref(), "U"), + CmdOption::new(t!("eXit").as_ref(), "X"), + ] + } Mode::Options => vec![ CmdOption::new(t!("Close").as_ref(), "C"), CmdOption::new(t!("Open").as_ref(), "O"), @@ -214,6 +282,7 @@ impl CadCommand for PeditCommand { is_poly: true, convertible: false, mesh_size: None, + mesh_closed: None, }, ); self.target = Some(nh); @@ -223,6 +292,13 @@ impl CadCommand for PeditCommand { fn on_pedit_applied(&mut self) { self.undo_count = self.undo_count.saturating_add(1); + if let Some((m_direction, closed)) = self.pending_mesh_closed.take() { + let previous = self.mesh_closed(); + self.mesh_closed_history.push(previous); + self.set_mesh_closed(m_direction, closed); + } else { + self.mesh_closed_history.push(None); + } } fn wants_text_input(&self) -> bool { @@ -230,6 +306,7 @@ impl CadCommand for PeditCommand { } fn on_text_input(&mut self, text: &str) -> Option { + self.pending_mesh_closed = None; let up = text.trim().to_uppercase(); let mesh_size = self.mesh_size(); match &mut self.mode { @@ -273,12 +350,21 @@ impl CadCommand for PeditCommand { let row = *index / n; let column = *index % n; match up.as_str() { - "N" | "NEXT" => *index = (*index + 1) % count, - "P" | "PREVIOUS" => *index = (*index + count - 1) % count, - "L" | "LEFT" => *index = row * n + (column + n - 1) % n, - "R" | "RIGHT" => *index = row * n + (column + 1) % n, - "U" | "UP" => *index = ((row + m - 1) % m) * n + column, - "D" | "DOWN" => *index = ((row + 1) % m) * n + column, + "N" | "NEXT" => { + self.mesh_vertex_default = 1; + if *index + 1 < count { + *index += 1; + } + } + "P" | "PREVIOUS" => { + self.mesh_vertex_default = -1; + *index = index.saturating_sub(1); + } + "L" | "LEFT" if column > 0 => *index -= 1, + "R" | "RIGHT" if column + 1 < n => *index += 1, + "U" | "UP" if row + 1 < m => *index += n, + "D" | "DOWN" if row > 0 => *index -= n, + "L" | "LEFT" | "R" | "RIGHT" | "U" | "UP" | "D" | "DOWN" => {} "M" | "MOVE" => self.mode = Mode::MeshVertexMove(*index), "G" | "REGEN" => { // Mesh display is regenerated after every edit; keep the @@ -300,34 +386,54 @@ impl CadCommand for PeditCommand { } "S" | "SMOOTH" | "SMOOTH SURFACE" => Some(CmdResult::PeditOp { handle, - op: PeditOp::SetMeshSmooth( - acadrust::entities::polygon_mesh::SurfaceSmoothType::Quadratic, - ), + op: PeditOp::SetMeshSmooth { + smooth: self.mesh_smooth_type, + m_density: self.mesh_smooth_density.0, + n_density: self.mesh_smooth_density.1, + }, }), "D" | "DESMOOTH" => Some(CmdResult::PeditOp { handle, - op: PeditOp::SetMeshSmooth( - acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth, - ), - }), - "MC" | "MCLOSE" | "M CLOSE" => Some(CmdResult::PeditOp { - handle, - op: PeditOp::SetMeshClosedM(true), - }), - "MO" | "MOPEN" | "M OPEN" => Some(CmdResult::PeditOp { - handle, - op: PeditOp::SetMeshClosedM(false), - }), - "NC" | "NCLOSE" | "N CLOSE" => Some(CmdResult::PeditOp { - handle, - op: PeditOp::SetMeshClosedN(true), - }), - "NO" | "NOPEN" | "N OPEN" => Some(CmdResult::PeditOp { - handle, - op: PeditOp::SetMeshClosedN(false), + op: PeditOp::SetMeshSmooth { + smooth: + acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth, + m_density: self.mesh_smooth_density.0, + n_density: self.mesh_smooth_density.1, + }, }), + "MC" | "MCLOSE" | "M CLOSE" => { + self.pending_mesh_closed = Some((true, true)); + Some(CmdResult::PeditOp { + handle, + op: PeditOp::SetMeshClosedM(true), + }) + } + "MO" | "MOPEN" | "M OPEN" => { + self.pending_mesh_closed = Some((true, false)); + Some(CmdResult::PeditOp { + handle, + op: PeditOp::SetMeshClosedM(false), + }) + } + "NC" | "NCLOSE" | "N CLOSE" => { + self.pending_mesh_closed = Some((false, true)); + Some(CmdResult::PeditOp { + handle, + op: PeditOp::SetMeshClosedN(true), + }) + } + "NO" | "NOPEN" | "N OPEN" => { + self.pending_mesh_closed = Some((false, false)); + Some(CmdResult::PeditOp { + handle, + op: PeditOp::SetMeshClosedN(false), + }) + } "U" | "UNDO" if self.undo_count > 0 => { self.undo_count -= 1; + if let Some(Some(closed)) = self.mesh_closed_history.pop() { + self.replace_mesh_closed(closed); + } Some(CmdResult::UndoDocument) } "U" | "UNDO" => Some(CmdResult::NeedPoint), @@ -398,7 +504,9 @@ impl CadCommand for PeditCommand { } fn on_enter(&mut self) -> CmdResult { - match &self.mode { + let mesh_size = self.mesh_size(); + let vertex_default = self.mesh_vertex_default; + match &mut self.mode { Mode::JoinGather(list) if list.len() >= 2 => CmdResult::JoinEntities(list.clone()), Mode::JoinGather(_) => { self.mode = Mode::Options; @@ -408,6 +516,20 @@ impl CadCommand for PeditCommand { handle: *h, op: PeditOp::ConvertToPolyline, }, + Mode::MeshVertex(index) => { + let Some((m, n)) = mesh_size else { + return CmdResult::NeedPoint; + }; + let count = m.saturating_mul(n); + if vertex_default >= 0 { + if *index + 1 < count { + *index += 1; + } + } else { + *index = index.saturating_sub(1); + } + CmdResult::NeedPoint + } _ => CmdResult::Cancel, } } @@ -426,7 +548,11 @@ pub enum PeditOp { Decurve, SetMeshClosedM(bool), SetMeshClosedN(bool), - SetMeshSmooth(acadrust::entities::polygon_mesh::SurfaceSmoothType), + SetMeshSmooth { + smooth: acadrust::entities::polygon_mesh::SurfaceSmoothType, + m_density: i16, + n_density: i16, + }, MoveMeshVertex { index: usize, point: DVec3 }, } @@ -503,19 +629,25 @@ pub fn apply_pedit(entity: &mut EntityType, op: &PeditOp) -> bool { } _ => false, }, - PeditOp::SetMeshSmooth(smooth) => match entity { + PeditOp::SetMeshSmooth { + smooth, + m_density, + n_density, + } => match entity { EntityType::PolygonMesh(mesh) => { let mut changed = mesh.smooth_type != *smooth; mesh.smooth_type = *smooth; if mesh.smooth_type != acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth { - if !(2..=256).contains(&mesh.m_smooth_density) { - mesh.m_smooth_density = 6; + let m_density = (*m_density).clamp(2, 200); + let n_density = (*n_density).clamp(2, 200); + if mesh.m_smooth_density != m_density { + mesh.m_smooth_density = m_density; changed = true; } - if !(2..=256).contains(&mesh.n_smooth_density) { - mesh.n_smooth_density = 6; + if mesh.n_smooth_density != n_density { + mesh.n_smooth_density = n_density; changed = true; } }