Merge pull request #944 from ramox81/codex/complete-3dmesh-parity

Complete 3D polygon mesh properties and editing behavior
This commit is contained in:
Hakan Seven authored and GitHub committed 2026-08-31 19:15:22 +03:00
commit d80898f2ca
9 files changed
+262 -112

No files matched your search

+1 -1
View File
@@ -2829,7 +2829,7 @@ properties =
.point-y = Point Y .point-y = Point Y
.point-z = Point Z .point-z = Point Z
.polyfacemesh = PolyfaceMesh .polyfacemesh = PolyfaceMesh
.polygonmesh = PolygonMesh .polygonmesh = Polygon Mesh
.polyline2d = Polyline2D .polyline2d = Polyline2D
.polyline3d = 3D Polyline .polyline3d = 3D Polyline
.predefined = Predefined .predefined = Predefined
+11 -1
View File
@@ -253,6 +253,7 @@ impl OpenCADStudio {
is_poly: true, is_poly: true,
convertible: false, convertible: false,
mesh_size: None, mesh_size: None,
mesh_closed: None,
}, },
)), )),
acadrust::EntityType::Line(_) | acadrust::EntityType::Arc(_) => { acadrust::EntityType::Line(_) | acadrust::EntityType::Arc(_) => {
@@ -262,6 +263,7 @@ impl OpenCADStudio {
is_poly: false, is_poly: false,
convertible: true, convertible: true,
mesh_size: None, mesh_size: None,
mesh_closed: None,
}, },
)) ))
} }
@@ -274,6 +276,7 @@ impl OpenCADStudio {
mesh.m_vertex_count.max(0) as usize, mesh.m_vertex_count.max(0) as usize,
mesh.n_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, _ => None,
@@ -284,7 +287,14 @@ impl OpenCADStudio {
// the convert prompt) skips the select step. // the convert prompt) skips the select step.
let preselected: Vec<acadrust::Handle> = let preselected: Vec<acadrust::Handle> =
self.tabs[i].scene.selected.iter().copied().collect(); 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.command_line.push_info(&cmd_obj.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd_obj)); self.tabs[i].active_cmd = Some(Box::new(cmd_obj));
} }
+21 -18
View File
@@ -1667,33 +1667,36 @@ impl OpenCADStudio {
None => Ok((format!("SPLINESEGS = {}", h.spline_segments), false)), None => Ok((format!("SPLINESEGS = {}", h.spline_segments), false)),
}, },
"SURFU" => match &value { "SURFU" => match &value {
Some(v) => v Some(v) => match v.parse::<i16>() {
.parse::<i16>() Ok(x @ 0..=200) => {
.map(|x| {
h.surface_u_density = x; h.surface_u_density = x;
(format!("SURFU = {x}"), true) Ok((format!("SURFU = {x}"), true))
}) }
.map_err(|_| "SETVAR: integer value required.".into()), 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)), None => Ok((format!("SURFU = {}", h.surface_u_density), false)),
}, },
"SURFV" => match &value { "SURFV" => match &value {
Some(v) => v Some(v) => match v.parse::<i16>() {
.parse::<i16>() Ok(x @ 0..=200) => {
.map(|x| {
h.surface_v_density = x; h.surface_v_density = x;
(format!("SURFV = {x}"), true) Ok((format!("SURFV = {x}"), true))
}) }
.map_err(|_| "SETVAR: integer value required.".into()), 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)), None => Ok((format!("SURFV = {}", h.surface_v_density), false)),
}, },
"SURFTYPE" => match &value { "SURFTYPE" => match &value {
Some(v) => v Some(v) => match v.parse::<i16>() {
.parse::<i16>() Ok(x @ (5 | 6 | 8)) => {
.map(|x| {
h.surface_type = x; h.surface_type = x;
(format!("SURFTYPE = {x}"), true) Ok((format!("SURFTYPE = {x}"), true))
}) }
.map_err(|_| "SETVAR: integer value required.".into()), 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)), None => Ok((format!("SURFTYPE = {}", h.surface_type), false)),
}, },
"SHADEDGE" => match &value { "SHADEDGE" => match &value {
+8 -1
View File
@@ -2628,7 +2628,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
} else { } else {
raw_val raw_val
}; };
if matches!(field, "current_fit_point" | "current_control_point") { if matches!(
field,
"current_fit_point" | "current_control_point" | "pm_current_vertex"
) {
let count = handles let count = handles
.iter() .iter()
.filter_map(|handle| { .filter_map(|handle| {
@@ -2644,6 +2647,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
) => Some( ) => Some(
crate::entities::spline::control_vertex_count(spline), crate::entities::spline::control_vertex_count(spline),
), ),
(
"pm_current_vertex",
acadrust::EntityType::PolygonMesh(mesh),
) => Some(mesh.vertices.len()),
_ => None, _ => None,
} }
}) })
+39 -41
View File
@@ -306,13 +306,15 @@ fn polygon_mesh_display(mesh: &PolygonMesh) -> (Vec<[f64; 3]>, usize, usize) {
return (base, m, n); return (base, m, n);
} }
let degree = match mesh.smooth_type { let (u_degree, v_degree) = match mesh.smooth_type {
SurfaceSmoothType::NoSmooth => return (base, m, n), SurfaceSmoothType::NoSmooth => return (base, m, n),
SurfaceSmoothType::Quadratic => 2, SurfaceSmoothType::Quadratic => (2.min(m.saturating_sub(1)), 2.min(n.saturating_sub(1))),
SurfaceSmoothType::Cubic | SurfaceSmoothType::Bezier => 3, SurfaceSmoothType::Cubic => (3.min(m.saturating_sub(1)), 3.min(n.saturating_sub(1))),
// A Bezier surface uses every row/column in its polynomial patch.
// Giving it the cubic B-spline degree made both choices render the
// same surface even though the stored mesh type was different.
SurfaceSmoothType::Bezier => (m.saturating_sub(1), n.saturating_sub(1)),
}; };
let u_degree = degree.min(m.saturating_sub(1));
let v_degree = degree.min(n.saturating_sub(1));
if u_degree == 0 || v_degree == 0 { if u_degree == 0 || v_degree == 0 {
return (base, m, n); return (base, m, n);
} }
@@ -329,10 +331,10 @@ fn polygon_mesh_display(mesh: &PolygonMesh) -> (Vec<[f64; 3]>, usize, usize) {
let display_m = usize::try_from(mesh.m_smooth_density.max(2)) let display_m = usize::try_from(mesh.m_smooth_density.max(2))
.unwrap_or(2) .unwrap_or(2)
.min(256); .min(200);
let display_n = usize::try_from(mesh.n_smooth_density.max(2)) let display_n = usize::try_from(mesh.n_smooth_density.max(2))
.unwrap_or(2) .unwrap_or(2)
.min(256); .min(200);
let parameter = |index: usize, count: usize, closed: bool| { let parameter = |index: usize, count: usize, closed: bool| {
if closed { if closed {
index as f64 / count as f64 index as f64 / count as f64
@@ -449,24 +451,16 @@ impl PropertyEditable for PolygonMesh {
let current = crate::scene::view::dispatch::prop_current_vertex() let current = crate::scene::view::dispatch::prop_current_vertex()
.min(vertex_count.saturating_sub(1)); .min(vertex_count.saturating_sub(1));
let vertex = self.vertices.get(current); let vertex = self.vertices.get(current);
let display = if vertex_count == 0 { let current_vertex = if vertex_count == 0 { 0 } else { current + 1 };
"—".to_string()
} else {
format!("{} / {}", current + 1, vertex_count)
};
let smoothed = self.smooth_type != SurfaceSmoothType::NoSmooth;
let density_prop = |label: &str, field: &'static str, value: i16| {
if smoothed {
edit_scalar(label, field, value as f64)
} else {
ro(label, field, value.to_string())
}
};
vec![ vec![
PropSection { PropSection {
title: t!("Geometry").into_owned(), title: t!("Geometry").into_owned(),
props: vec![ props: vec![
stepper(t!("Current Vertex").as_ref(), "pm_current_vertex", display), edit_scalar(
t!("Current Vertex").as_ref(),
"pm_current_vertex",
current_vertex as f64,
),
edit(t!("Vertex X").as_ref(), edit(t!("Vertex X").as_ref(),
"pm_vx", "pm_vx",
vertex.map_or(0.0, |vertex| vertex.location.x), vertex.map_or(0.0, |vertex| vertex.location.x),
@@ -479,26 +473,34 @@ impl PropertyEditable for PolygonMesh {
"pm_vz", "pm_vz",
vertex.map_or(0.0, |vertex| vertex.location.z), vertex.map_or(0.0, |vertex| vertex.location.z),
), ),
ro(t!("M vertex count").as_ref(), "pm_m", self.m_vertex_count.to_string()),
ro(t!("N vertex count").as_ref(), "pm_n", self.n_vertex_count.to_string()),
Property { Property {
label: t!("M closed").into_owned(), label: t!("M closed").into_owned(),
field: "pm_closed_m", field: "pm_closed_m",
value: PropValue::BoolToggle { value: PropValue::Choice {
field: "pm_closed_m", selected: if self.is_closed_m() { "Yes" } else { "No" }.to_string(),
value: self.is_closed_m(), options: vec!["No".to_string(), "Yes".to_string()],
}, },
}, },
Property { Property {
label: t!("N closed").into_owned(), label: t!("N closed").into_owned(),
field: "pm_closed_n", field: "pm_closed_n",
value: PropValue::BoolToggle { value: PropValue::Choice {
field: "pm_closed_n", selected: if self.is_closed_n() { "Yes" } else { "No" }.to_string(),
value: self.is_closed_n(), options: vec!["No".to_string(), "Yes".to_string()],
}, },
}, },
density_prop(t!("M density").as_ref(), "pm_smooth_m", self.m_smooth_density), edit_scalar(
density_prop(t!("N density").as_ref(), "pm_smooth_n", self.n_smooth_density), t!("M density").as_ref(),
"pm_smooth_m",
self.m_smooth_density as f64,
),
edit_scalar(
t!("N density").as_ref(),
"pm_smooth_n",
self.n_smooth_density as f64,
),
ro(t!("M vertex count").as_ref(), "pm_m", self.m_vertex_count.to_string()),
ro(t!("N vertex count").as_ref(), "pm_n", self.n_vertex_count.to_string()),
], ],
}, },
PropSection { PropSection {
@@ -559,26 +561,22 @@ impl PropertyEditable for PolygonMesh {
}; };
self.smooth_type = smooth; self.smooth_type = smooth;
if self.smooth_type != SurfaceSmoothType::NoSmooth { if self.smooth_type != SurfaceSmoothType::NoSmooth {
if !(2..=256).contains(&self.m_smooth_density) { if !(2..=200).contains(&self.m_smooth_density) {
self.m_smooth_density = 6; self.m_smooth_density = 6;
} }
if !(2..=256).contains(&self.n_smooth_density) { if !(2..=200).contains(&self.n_smooth_density) {
self.n_smooth_density = 6; self.n_smooth_density = 6;
} }
} }
} }
"pm_smooth_m" => { "pm_smooth_m" => {
if self.smooth_type != SurfaceSmoothType::NoSmooth { if let Ok(density @ 0..=200) = value.trim().parse::<i16>() {
if let Ok(density @ 2..=256) = value.trim().parse::<i16>() { self.m_smooth_density = density;
self.m_smooth_density = density;
}
} }
} }
"pm_smooth_n" => { "pm_smooth_n" => {
if self.smooth_type != SurfaceSmoothType::NoSmooth { if let Ok(density @ 0..=200) = value.trim().parse::<i16>() {
if let Ok(density @ 2..=256) = value.trim().parse::<i16>() { self.n_smooth_density = density;
self.n_smooth_density = density;
}
} }
} }
"pm_vx" | "pm_vy" | "pm_vz" => { "pm_vx" | "pm_vy" | "pm_vz" => {
+1 -1
View File
@@ -33,7 +33,7 @@ pub fn ui_name(e: &EntityType) -> &'static str {
EntityType::Polyline2D(_) => "Polyline2D", EntityType::Polyline2D(_) => "Polyline2D",
EntityType::Polyline3D(_) => "Polyline3D", EntityType::Polyline3D(_) => "Polyline3D",
EntityType::PolyfaceMesh(_) => "PolyfaceMesh", EntityType::PolyfaceMesh(_) => "PolyfaceMesh",
EntityType::PolygonMesh(_) => "PolygonMesh", EntityType::PolygonMesh(_) => "Polygon Mesh",
EntityType::Text(_) => "Text", EntityType::Text(_) => "Text",
EntityType::MText(_) => "MText", EntityType::MText(_) => "MText",
EntityType::Dimension(_) => "Dimension", EntityType::Dimension(_) => "Dimension",
+1 -1
View File
@@ -2067,7 +2067,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Polygon - Circumscribed" => Some(("draw", "polygon-circumscribed")), "Polygon - Circumscribed" => Some(("draw", "polygon-circumscribed")),
"Polygon - Edge" => Some(("draw", "polygon-edge")), "Polygon - Edge" => Some(("draw", "polygon-edge")),
"Polygon - Inscribed" => Some(("draw", "polygon-inscribed")), "Polygon - Inscribed" => Some(("draw", "polygon-inscribed")),
"PolygonMesh" => Some(("properties", "polygonmesh")), "Polygon Mesh" => Some(("properties", "polygonmesh")),
"Polygonal" => Some(("layout", "polygonal")), "Polygonal" => Some(("layout", "polygonal")),
"Polyline" => Some(("common", "polyline")), "Polyline" => Some(("common", "polyline")),
"Polyline broken at vertex." => Some(("command", "polyline-broken-at-vertex")), "Polyline broken at vertex." => Some(("command", "polyline-broken-at-vertex")),
+179 -47
View File
@@ -22,6 +22,9 @@ pub struct PeditTarget {
pub convertible: bool, pub convertible: bool,
/// M/N size for a legacy polygon mesh; absent for ordinary polylines. /// M/N size for a legacy polygon mesh; absent for ordinary polylines.
pub mesh_size: Option<(usize, usize)>, 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 { enum Mode {
@@ -43,15 +46,40 @@ pub struct PeditCommand {
info: HashMap<u64, PeditTarget>, info: HashMap<u64, PeditTarget>,
mode: Mode, mode: Mode,
undo_count: usize, 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<Option<(bool, bool)>>,
} }
impl PeditCommand { impl PeditCommand {
pub fn new(info: HashMap<u64, PeditTarget>) -> Self { pub fn new(
info: HashMap<u64, PeditTarget>,
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 { Self {
target: None, target: None,
info, info,
mode: Mode::PickTarget, mode: Mode::PickTarget,
undo_count: 0, 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?; let handle = self.target?;
self.info.get(&handle.value())?.mesh_size 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 { impl CadCommand for PeditCommand {
@@ -118,17 +177,26 @@ impl CadCommand for PeditCommand {
fn options(&self) -> Vec<crate::command::CmdOption> { fn options(&self) -> Vec<crate::command::CmdOption> {
use crate::command::CmdOption; use crate::command::CmdOption;
match &self.mode { match &self.mode {
Mode::Options if self.mesh_size().is_some() => vec![ Mode::Options if self.mesh_size().is_some() => {
CmdOption::new(t!("Edit vertex").as_ref(), "E"), let (closed_m, closed_n) = self.mesh_closed().unwrap_or((false, false));
CmdOption::new(t!("Smooth surface").as_ref(), "S"), vec![
CmdOption::new(t!("Desmooth").as_ref(), "D"), CmdOption::new(t!("Edit vertex").as_ref(), "E"),
CmdOption::new(t!("M close").as_ref(), "MC"), CmdOption::new(t!("Smooth surface").as_ref(), "S"),
CmdOption::new(t!("M open").as_ref(), "MO"), CmdOption::new(t!("Desmooth").as_ref(), "D"),
CmdOption::new(t!("N close").as_ref(), "NC"), if closed_m {
CmdOption::new(t!("N open").as_ref(), "NO"), CmdOption::new(t!("M open").as_ref(), "MO")
CmdOption::new(t!("Undo").as_ref(), "U"), } else {
CmdOption::new(t!("eXit").as_ref(), "X"), 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![ Mode::Options => vec![
CmdOption::new(t!("Close").as_ref(), "C"), CmdOption::new(t!("Close").as_ref(), "C"),
CmdOption::new(t!("Open").as_ref(), "O"), CmdOption::new(t!("Open").as_ref(), "O"),
@@ -214,6 +282,7 @@ impl CadCommand for PeditCommand {
is_poly: true, is_poly: true,
convertible: false, convertible: false,
mesh_size: None, mesh_size: None,
mesh_closed: None,
}, },
); );
self.target = Some(nh); self.target = Some(nh);
@@ -223,6 +292,13 @@ impl CadCommand for PeditCommand {
fn on_pedit_applied(&mut self) { fn on_pedit_applied(&mut self) {
self.undo_count = self.undo_count.saturating_add(1); 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 { fn wants_text_input(&self) -> bool {
@@ -230,6 +306,7 @@ impl CadCommand for PeditCommand {
} }
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> { fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
self.pending_mesh_closed = None;
let up = text.trim().to_uppercase(); let up = text.trim().to_uppercase();
let mesh_size = self.mesh_size(); let mesh_size = self.mesh_size();
match &mut self.mode { match &mut self.mode {
@@ -273,12 +350,21 @@ impl CadCommand for PeditCommand {
let row = *index / n; let row = *index / n;
let column = *index % n; let column = *index % n;
match up.as_str() { match up.as_str() {
"N" | "NEXT" => *index = (*index + 1) % count, "N" | "NEXT" => {
"P" | "PREVIOUS" => *index = (*index + count - 1) % count, self.mesh_vertex_default = 1;
"L" | "LEFT" => *index = row * n + (column + n - 1) % n, if *index + 1 < count {
"R" | "RIGHT" => *index = row * n + (column + 1) % n, *index += 1;
"U" | "UP" => *index = ((row + m - 1) % m) * n + column, }
"D" | "DOWN" => *index = ((row + 1) % m) * n + column, }
"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), "M" | "MOVE" => self.mode = Mode::MeshVertexMove(*index),
"G" | "REGEN" => { "G" | "REGEN" => {
// Mesh display is regenerated after every edit; keep the // Mesh display is regenerated after every edit; keep the
@@ -300,34 +386,54 @@ impl CadCommand for PeditCommand {
} }
"S" | "SMOOTH" | "SMOOTH SURFACE" => Some(CmdResult::PeditOp { "S" | "SMOOTH" | "SMOOTH SURFACE" => Some(CmdResult::PeditOp {
handle, handle,
op: PeditOp::SetMeshSmooth( op: PeditOp::SetMeshSmooth {
acadrust::entities::polygon_mesh::SurfaceSmoothType::Quadratic, smooth: self.mesh_smooth_type,
), m_density: self.mesh_smooth_density.0,
n_density: self.mesh_smooth_density.1,
},
}), }),
"D" | "DESMOOTH" => Some(CmdResult::PeditOp { "D" | "DESMOOTH" => Some(CmdResult::PeditOp {
handle, handle,
op: PeditOp::SetMeshSmooth( op: PeditOp::SetMeshSmooth {
acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth, smooth:
), acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth,
}), m_density: self.mesh_smooth_density.0,
"MC" | "MCLOSE" | "M CLOSE" => Some(CmdResult::PeditOp { n_density: self.mesh_smooth_density.1,
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),
}), }),
"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 => { "U" | "UNDO" if self.undo_count > 0 => {
self.undo_count -= 1; self.undo_count -= 1;
if let Some(Some(closed)) = self.mesh_closed_history.pop() {
self.replace_mesh_closed(closed);
}
Some(CmdResult::UndoDocument) Some(CmdResult::UndoDocument)
} }
"U" | "UNDO" => Some(CmdResult::NeedPoint), "U" | "UNDO" => Some(CmdResult::NeedPoint),
@@ -398,7 +504,9 @@ impl CadCommand for PeditCommand {
} }
fn on_enter(&mut self) -> CmdResult { 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(list) if list.len() >= 2 => CmdResult::JoinEntities(list.clone()),
Mode::JoinGather(_) => { Mode::JoinGather(_) => {
self.mode = Mode::Options; self.mode = Mode::Options;
@@ -408,6 +516,20 @@ impl CadCommand for PeditCommand {
handle: *h, handle: *h,
op: PeditOp::ConvertToPolyline, 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, _ => CmdResult::Cancel,
} }
} }
@@ -426,7 +548,11 @@ pub enum PeditOp {
Decurve, Decurve,
SetMeshClosedM(bool), SetMeshClosedM(bool),
SetMeshClosedN(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 }, MoveMeshVertex { index: usize, point: DVec3 },
} }
@@ -503,19 +629,25 @@ pub fn apply_pedit(entity: &mut EntityType, op: &PeditOp) -> bool {
} }
_ => false, _ => false,
}, },
PeditOp::SetMeshSmooth(smooth) => match entity { PeditOp::SetMeshSmooth {
smooth,
m_density,
n_density,
} => match entity {
EntityType::PolygonMesh(mesh) => { EntityType::PolygonMesh(mesh) => {
let mut changed = mesh.smooth_type != *smooth; let mut changed = mesh.smooth_type != *smooth;
mesh.smooth_type = *smooth; mesh.smooth_type = *smooth;
if mesh.smooth_type if mesh.smooth_type
!= acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth != acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth
{ {
if !(2..=256).contains(&mesh.m_smooth_density) { let m_density = (*m_density).clamp(2, 200);
mesh.m_smooth_density = 6; let n_density = (*n_density).clamp(2, 200);
if mesh.m_smooth_density != m_density {
mesh.m_smooth_density = m_density;
changed = true; changed = true;
} }
if !(2..=256).contains(&mesh.n_smooth_density) { if mesh.n_smooth_density != n_density {
mesh.n_smooth_density = 6; mesh.n_smooth_density = n_density;
changed = true; changed = true;
} }
} }
+1 -1
View File
@@ -1788,7 +1788,7 @@ fn coord_suffix(label: &str) -> Option<(&str, usize)> {
/// Length of the coordinate group starting at `idx`: consecutive text rows /// Length of the coordinate group starting at `idx`: consecutive text rows
/// labelled "<Base> X", "<Base> Y" and optionally "<Base> Z". 0/1 = no group. /// labelled "<Base> X", "<Base> Y" and optionally "<Base> Z". 0/1 = no group.
fn coord_group_len(props: &[crate::scene::model::object::Property], idx: usize) -> usize { fn coord_group_len(props: &[crate::scene::model::object::Property], idx: usize) -> usize {
if props[idx].field == "pl3_vertex_x" { if matches!(props[idx].field, "pl3_vertex_x" | "pm_vx") {
return 0; return 0;
} }
let groupable = |p: &crate::scene::model::object::Property| { let groupable = |p: &crate::scene::model::object::Property| {