forked from 1b-agent/opencadstudio-upstream
Add polyline vertex and multiple-object editing
This commit is contained in:
1 parent
7d0c059b30
commit
376faec4db
4 files changed
+249
-14
No files matched your search
@@ -2312,8 +2312,12 @@ impl OpenCADStudio {
|
|||||||
self.tabs[i].scene.invalidate_dim_block_recorded(nh);
|
self.tabs[i].scene.invalidate_dim_block_recorded(nh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let pedit_entities = if self.tabs[i].active_cmd.as_ref().is_some_and(|command| command.name() == "PEDIT") {
|
||||||
|
new_handles.iter().filter_map(|new| self.tabs[i].scene.document.get_entity(*new).cloned()).collect::<Vec<_>>()
|
||||||
|
} else { Vec::new() };
|
||||||
if let Some(cmd) = &mut self.tabs[i].active_cmd {
|
if let Some(cmd) = &mut self.tabs[i].active_cmd {
|
||||||
cmd.on_entity_replaced(handle, &new_handles);
|
cmd.on_entity_replaced(handle, &new_handles);
|
||||||
|
for entity in pedit_entities { cmd.inject_picked_entity(entity); }
|
||||||
}
|
}
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||||
@@ -3205,13 +3209,44 @@ impl OpenCADStudio {
|
|||||||
self.restore_pre_cmd_tangent();
|
self.restore_pre_cmd_tangent();
|
||||||
}
|
}
|
||||||
CmdResult::PeditOp { handle, op } => {
|
CmdResult::PeditOp { handle, op } => {
|
||||||
if self.reject_locked_edit(i, handle) {
|
if !matches!(&op, crate::modules::draw::modify::pedit::PeditOp::Multiple(_, _)) && self.reject_locked_edit(i, handle) {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
use crate::modules::draw::modify::pedit::{
|
use crate::modules::draw::modify::pedit::{
|
||||||
apply_pedit, convert_to_polyline, PeditOp,
|
apply_pedit, convert_to_polyline, PeditOp,
|
||||||
};
|
};
|
||||||
match &op {
|
match &op {
|
||||||
|
PeditOp::Multiple(handles, operation) => {
|
||||||
|
let replacements: Vec<_> = handles.iter().filter(|handle| !self.tabs[i].scene.is_layer_locked(**handle)).filter_map(|handle| {
|
||||||
|
let original = self.tabs[i].scene.document.get_entity(*handle)?;
|
||||||
|
if matches!(operation.as_ref(), PeditOp::ConvertToPolyline) {
|
||||||
|
convert_to_polyline(original).map(|replacement| (*handle, replacement, true))
|
||||||
|
} else {
|
||||||
|
let mut replacement = original.clone();
|
||||||
|
apply_pedit(&mut replacement, operation).then_some((*handle, replacement, false))
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
if replacements.is_empty() { return Task::none(); }
|
||||||
|
self.push_undo_snapshot(i, "PEDIT");
|
||||||
|
for (handle, replacement, converted) in replacements {
|
||||||
|
let updated_handle = if converted {
|
||||||
|
self.tabs[i].scene.erase_entities(&[handle]);
|
||||||
|
let new_handle = self.tabs[i].scene.add_entity(replacement);
|
||||||
|
if let Some(command) = self.tabs[i].active_cmd.as_mut() { command.on_entity_replaced(handle, &[new_handle]); }
|
||||||
|
new_handle
|
||||||
|
} else {
|
||||||
|
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) { *entity = replacement; }
|
||||||
|
self.tabs[i].scene.refresh_fill_model(handle);
|
||||||
|
self.tabs[i].scene.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||||
|
handle
|
||||||
|
};
|
||||||
|
let updated = self.tabs[i].scene.document.get_entity(updated_handle).cloned();
|
||||||
|
if let (Some(command), Some(entity)) = (self.tabs[i].active_cmd.as_mut(), updated) { command.inject_picked_entity(entity); }
|
||||||
|
}
|
||||||
|
if let Some(command) = self.tabs[i].active_cmd.as_mut() { command.on_pedit_applied(); }
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
// The convert replaces the entity (new handle).
|
// The convert replaces the entity (new handle).
|
||||||
PeditOp::ConvertToPolyline => {
|
PeditOp::ConvertToPolyline => {
|
||||||
let converted = self.tabs[i]
|
let converted = self.tabs[i]
|
||||||
@@ -3240,7 +3275,9 @@ impl OpenCADStudio {
|
|||||||
.map(|e| apply_pedit(e, &op))
|
.map(|e| apply_pedit(e, &op))
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if changed {
|
if changed {
|
||||||
|
let updated = self.tabs[i].scene.document.get_entity(handle).cloned();
|
||||||
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||||
|
if let Some(entity) = updated { command.inject_picked_entity(entity); }
|
||||||
command.on_pedit_applied();
|
command.on_pedit_applied();
|
||||||
}
|
}
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
@@ -4749,6 +4786,12 @@ impl OpenCADStudio {
|
|||||||
let active = self.tabs[i].active_cmd.take();
|
let active = self.tabs[i].active_cmd.take();
|
||||||
self.undo_active_tab();
|
self.undo_active_tab();
|
||||||
self.tabs[i].active_cmd = active;
|
self.tabs[i].active_cmd = active;
|
||||||
|
if self.tabs[i].active_cmd.as_ref().is_some_and(|command| command.name() == "PEDIT") {
|
||||||
|
let entities: Vec<_> = self.tabs[i].scene.document.entities().cloned().collect();
|
||||||
|
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
||||||
|
for entity in entities { command.inject_picked_entity(entity); }
|
||||||
|
}
|
||||||
|
}
|
||||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||||
if let Some(p) = prompt {
|
if let Some(p) = prompt {
|
||||||
self.command_line.push_info(&p);
|
self.command_line.push_info(&p);
|
||||||
|
|||||||
@@ -294,6 +294,7 @@ impl OpenCADStudio {
|
|||||||
header.surface_u_density,
|
header.surface_u_density,
|
||||||
header.surface_v_density,
|
header.surface_v_density,
|
||||||
)
|
)
|
||||||
|
.with_entities(self.tabs[i].scene.document.entities().cloned())
|
||||||
.with_preselection(&preselected);
|
.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));
|
||||||
|
|||||||
@@ -29,10 +29,16 @@ pub struct PeditTarget {
|
|||||||
|
|
||||||
enum Mode {
|
enum Mode {
|
||||||
PickTarget,
|
PickTarget,
|
||||||
|
MultipleGather,
|
||||||
|
MultipleConvert,
|
||||||
Options,
|
Options,
|
||||||
/// Picked a Line/Arc: asking "Turn it into one? [Yes/No]".
|
/// Picked a Line/Arc: asking "Turn it into one? [Yes/No]".
|
||||||
ConvertPrompt(Handle),
|
ConvertPrompt(Handle),
|
||||||
AwaitWidth,
|
AwaitWidth,
|
||||||
|
AwaitLinetype,
|
||||||
|
PolyVertex(usize),
|
||||||
|
PolyMove(usize),
|
||||||
|
PolyInsert(usize),
|
||||||
/// Join: gathering additional segments; Enter merges.
|
/// Join: gathering additional segments; Enter merges.
|
||||||
JoinGather(Vec<Handle>),
|
JoinGather(Vec<Handle>),
|
||||||
/// Polygon-mesh vertex navigation uses the mesh's row-major control net.
|
/// Polygon-mesh vertex navigation uses the mesh's row-major control net.
|
||||||
@@ -43,6 +49,10 @@ enum Mode {
|
|||||||
|
|
||||||
pub struct PeditCommand {
|
pub struct PeditCommand {
|
||||||
target: Option<Handle>,
|
target: Option<Handle>,
|
||||||
|
entities: HashMap<u64, EntityType>,
|
||||||
|
multiple: Vec<Handle>,
|
||||||
|
multiple_history: Vec<Vec<Handle>>,
|
||||||
|
pending_multiple: Option<Vec<Handle>>,
|
||||||
info: HashMap<u64, PeditTarget>,
|
info: HashMap<u64, PeditTarget>,
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
undo_count: usize,
|
undo_count: usize,
|
||||||
@@ -69,6 +79,10 @@ impl PeditCommand {
|
|||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
target: None,
|
target: None,
|
||||||
|
entities: HashMap::default(),
|
||||||
|
multiple: Vec::new(),
|
||||||
|
multiple_history: Vec::new(),
|
||||||
|
pending_multiple: None,
|
||||||
info,
|
info,
|
||||||
mode: Mode::PickTarget,
|
mode: Mode::PickTarget,
|
||||||
undo_count: 0,
|
undo_count: 0,
|
||||||
@@ -86,6 +100,17 @@ impl PeditCommand {
|
|||||||
/// Adopt a pre-selected entity (pickfirst): a selected polyline skips the
|
/// Adopt a pre-selected entity (pickfirst): a selected polyline skips the
|
||||||
/// pick step, a selected line/arc goes straight to the convert prompt.
|
/// pick step, a selected line/arc goes straight to the convert prompt.
|
||||||
pub fn with_preselection(mut self, handles: &[Handle]) -> Self {
|
pub fn with_preselection(mut self, handles: &[Handle]) -> Self {
|
||||||
|
let multiple: Vec<_> = handles.iter().copied().filter(|handle| {
|
||||||
|
self.info.get(&handle.value()).is_some_and(|info| info.mesh_size.is_none())
|
||||||
|
}).collect();
|
||||||
|
if multiple.len() > 1 {
|
||||||
|
self.target = multiple.first().copied();
|
||||||
|
self.mode = if multiple.iter().any(|handle| self.info.get(&handle.value()).is_some_and(|info| info.convertible)) {
|
||||||
|
Mode::MultipleConvert
|
||||||
|
} else { Mode::Options };
|
||||||
|
self.multiple = multiple;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
for &h in handles {
|
for &h in handles {
|
||||||
let Some(info) = self.info.get(&h.value()).copied() else {
|
let Some(info) = self.info.get(&h.value()).copied() else {
|
||||||
continue;
|
continue;
|
||||||
@@ -103,6 +128,26 @@ impl PeditCommand {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_entities(mut self, entities: impl IntoIterator<Item = EntityType>) -> Self {
|
||||||
|
self.entities = entities.into_iter().map(|entity| (entity.common().handle.value(), entity)).collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multiple_result(&self, result: Option<CmdResult>) -> Option<CmdResult> {
|
||||||
|
match result {
|
||||||
|
Some(CmdResult::PeditOp { handle, op }) if !self.multiple.is_empty() =>
|
||||||
|
Some(CmdResult::PeditOp { handle, op: PeditOp::Multiple(self.multiple.clone(), Box::new(op)) }),
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vertex_count(&self) -> usize {
|
||||||
|
self.target.and_then(|handle| self.entities.get(&handle.value())).map_or(0, |entity| match entity {
|
||||||
|
EntityType::LwPolyline(polyline) => polyline.vertices.len(),
|
||||||
|
EntityType::Polyline2D(polyline) => polyline.vertices.len(),
|
||||||
|
_ => 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
fn mesh_size(&self) -> Option<(usize, usize)> {
|
fn mesh_size(&self) -> Option<(usize, usize)> {
|
||||||
let handle = self.target?;
|
let handle = self.target?;
|
||||||
self.info.get(&handle.value())?.mesh_size
|
self.info.get(&handle.value())?.mesh_size
|
||||||
@@ -148,13 +193,16 @@ impl CadCommand for PeditCommand {
|
|||||||
fn prompt(&self) -> String {
|
fn prompt(&self) -> String {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::PickTarget => {
|
Mode::PickTarget => {
|
||||||
t!("PEDIT Select polyline (or a line/arc to convert):").into_owned()
|
t!("PEDIT Select polyline (or a line/arc to convert) or [Multiple]:").into_owned()
|
||||||
}
|
}
|
||||||
|
Mode::MultipleGather => format!("PEDIT Select objects ({} selected, Enter when done):", self.multiple.len()),
|
||||||
|
Mode::MultipleConvert => t!("PEDIT Convert lines and arcs to polylines [Yes/No] <Yes>:").into_owned(),
|
||||||
Mode::ConvertPrompt(_) => t!(
|
Mode::ConvertPrompt(_) => t!(
|
||||||
"PEDIT Object is not a polyline. Turn it into one? [Yes/No] <Y>:"
|
"PEDIT Object is not a polyline. Turn it into one? [Yes/No] <Y>:"
|
||||||
)
|
)
|
||||||
.into_owned(),
|
.into_owned(),
|
||||||
Mode::AwaitWidth => t!("PEDIT Specify new width:").into_owned(),
|
Mode::AwaitWidth => t!("PEDIT Specify new width:").into_owned(),
|
||||||
|
Mode::AwaitLinetype => t!("PEDIT Enter polyline linetype generation option [ON/OFF]:").into_owned(),
|
||||||
Mode::JoinGather(list) => t!(
|
Mode::JoinGather(list) => t!(
|
||||||
"PEDIT Join Select objects to join (%{count} picked), Enter to merge:",
|
"PEDIT Join Select objects to join (%{count} picked), Enter to merge:",
|
||||||
count = list.len().saturating_sub(1)
|
count = list.len().saturating_sub(1)
|
||||||
@@ -170,6 +218,9 @@ impl CadCommand for PeditCommand {
|
|||||||
vertex = index + 1
|
vertex = index + 1
|
||||||
)
|
)
|
||||||
.into_owned(),
|
.into_owned(),
|
||||||
|
Mode::PolyVertex(index) => format!("PEDIT Vertex {} [Next/Previous/Insert/Move/eXit] <Next>:", index + 1),
|
||||||
|
Mode::PolyMove(index) => format!("PEDIT Specify new location for vertex {}:", index + 1),
|
||||||
|
Mode::PolyInsert(index) => format!("PEDIT Specify location after vertex {}:", index + 1),
|
||||||
Mode::Options => t!("PEDIT Enter option:").into_owned(),
|
Mode::Options => t!("PEDIT Enter option:").into_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,6 +228,8 @@ 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::PickTarget => vec![CmdOption::new("Multiple", "M")],
|
||||||
|
Mode::MultipleConvert => vec![CmdOption::new("Yes", "Y"), CmdOption::new("No", "N")],
|
||||||
Mode::Options if self.mesh_size().is_some() => {
|
Mode::Options if self.mesh_size().is_some() => {
|
||||||
let (closed_m, closed_n) = self.mesh_closed().unwrap_or((false, false));
|
let (closed_m, closed_n) = self.mesh_closed().unwrap_or((false, false));
|
||||||
vec![
|
vec![
|
||||||
@@ -205,8 +258,12 @@ impl CadCommand for PeditCommand {
|
|||||||
CmdOption::new(t!("Fit").as_ref(), "F"),
|
CmdOption::new(t!("Fit").as_ref(), "F"),
|
||||||
CmdOption::new(t!("Spline").as_ref(), "S"),
|
CmdOption::new(t!("Spline").as_ref(), "S"),
|
||||||
CmdOption::new(t!("Decurve").as_ref(), "D"),
|
CmdOption::new(t!("Decurve").as_ref(), "D"),
|
||||||
|
CmdOption::new(t!("Edit vertex").as_ref(), "E"),
|
||||||
|
CmdOption::new(t!("Ltype gen").as_ref(), "L"),
|
||||||
|
CmdOption::new(t!("Reverse").as_ref(), "R"),
|
||||||
|
CmdOption::new(t!("Undo").as_ref(), "U"),
|
||||||
CmdOption::new(t!("eXit").as_ref(), "X"),
|
CmdOption::new(t!("eXit").as_ref(), "X"),
|
||||||
],
|
].into_iter().filter(|option| self.multiple.is_empty() || option.keyword != "E").collect(),
|
||||||
Mode::MeshVertex(_) => vec![
|
Mode::MeshVertex(_) => vec![
|
||||||
CmdOption::new(t!("Next").as_ref(), "N"),
|
CmdOption::new(t!("Next").as_ref(), "N"),
|
||||||
CmdOption::new(t!("Previous").as_ref(), "P"),
|
CmdOption::new(t!("Previous").as_ref(), "P"),
|
||||||
@@ -222,6 +279,8 @@ impl CadCommand for PeditCommand {
|
|||||||
vec![CmdOption::new(t!("Yes").as_ref(), "Y"), CmdOption::new(t!("No").as_ref(), "N")]
|
vec![CmdOption::new(t!("Yes").as_ref(), "Y"), CmdOption::new(t!("No").as_ref(), "N")]
|
||||||
}
|
}
|
||||||
Mode::JoinGather(_) => vec![CmdOption::enter(t!("Join").as_ref())],
|
Mode::JoinGather(_) => vec![CmdOption::enter(t!("Join").as_ref())],
|
||||||
|
Mode::PolyVertex(_) => vec![CmdOption::new("Next", "N"), CmdOption::new("Previous", "P"), CmdOption::new("Insert", "I"), CmdOption::new("Move", "M"), CmdOption::new("Exit", "X")],
|
||||||
|
Mode::AwaitLinetype => vec![CmdOption::new("On", "ON"), CmdOption::new("Off", "OFF")],
|
||||||
Mode::MeshVertexMove(_) => vec![],
|
Mode::MeshVertexMove(_) => vec![],
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
}
|
}
|
||||||
@@ -234,10 +293,14 @@ impl CadCommand for PeditCommand {
|
|||||||
fn is_selection_gathering(&self) -> bool {
|
fn is_selection_gathering(&self) -> bool {
|
||||||
// Join uses the normal selection system, so single picks AND
|
// Join uses the normal selection system, so single picks AND
|
||||||
// window/crossing boxes both gather objects.
|
// window/crossing boxes both gather objects.
|
||||||
matches!(self.mode, Mode::JoinGather(_))
|
matches!(self.mode, Mode::JoinGather(_) | Mode::MultipleGather)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult {
|
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult {
|
||||||
|
if matches!(self.mode, Mode::MultipleGather) {
|
||||||
|
self.multiple = handles.into_iter().filter(|handle| self.info.get(&handle.value()).is_some_and(|info| info.mesh_size.is_none())).collect();
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
}
|
||||||
if let (Some(target), Mode::JoinGather(list)) = (self.target, &mut self.mode) {
|
if let (Some(target), Mode::JoinGather(list)) = (self.target, &mut self.mode) {
|
||||||
list.clear();
|
list.clear();
|
||||||
list.push(target);
|
list.push(target);
|
||||||
@@ -275,7 +338,8 @@ impl CadCommand for PeditCommand {
|
|||||||
// A Yes-conversion (or Break) replaced the entity — adopt the first
|
// A Yes-conversion (or Break) replaced the entity — adopt the first
|
||||||
// piece as the live target and carry its bookkeeping over.
|
// piece as the live target and carry its bookkeeping over.
|
||||||
if let Some(&nh) = new_handles.first() {
|
if let Some(&nh) = new_handles.first() {
|
||||||
self.info.remove(&old.value());
|
if self.multiple.is_empty() { self.info.remove(&old.value()); }
|
||||||
|
for handle in &mut self.multiple { if *handle == old { *handle = nh; } }
|
||||||
self.info.insert(
|
self.info.insert(
|
||||||
nh.value(),
|
nh.value(),
|
||||||
PeditTarget {
|
PeditTarget {
|
||||||
@@ -290,8 +354,13 @@ impl CadCommand for PeditCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inject_picked_entity(&mut self, entity: EntityType) {
|
||||||
|
self.entities.insert(entity.common().handle.value(), entity);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
self.multiple_history.push(self.pending_multiple.take().unwrap_or_else(|| self.multiple.clone()));
|
||||||
if let Some((m_direction, closed)) = self.pending_mesh_closed.take() {
|
if let Some((m_direction, closed)) = self.pending_mesh_closed.take() {
|
||||||
let previous = self.mesh_closed();
|
let previous = self.mesh_closed();
|
||||||
self.mesh_closed_history.push(previous);
|
self.mesh_closed_history.push(previous);
|
||||||
@@ -302,15 +371,35 @@ impl CadCommand for PeditCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn wants_text_input(&self) -> bool {
|
fn wants_text_input(&self) -> bool {
|
||||||
!matches!(self.mode, Mode::PickTarget | Mode::MeshVertexMove(_))
|
!matches!(self.mode, Mode::PickTarget | Mode::MeshVertexMove(_) | Mode::PolyMove(_) | Mode::PolyInsert(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
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 {
|
let vertex_count = self.vertex_count();
|
||||||
Mode::PickTarget => None,
|
let result = match &mut self.mode {
|
||||||
|
Mode::PickTarget => {
|
||||||
|
if matches!(up.as_str(), "M" | "MULTIPLE") { self.mode = Mode::MultipleGather; Some(CmdResult::NeedPoint) } else { None }
|
||||||
|
}
|
||||||
|
Mode::MultipleGather => None,
|
||||||
|
Mode::MultipleConvert => {
|
||||||
|
match up.as_str() {
|
||||||
|
"Y" | "YES" | "" => {
|
||||||
|
self.pending_multiple = Some(self.multiple.clone());
|
||||||
|
self.mode = Mode::Options;
|
||||||
|
Some(CmdResult::PeditOp { handle: *self.multiple.first()?, op: PeditOp::ConvertToPolyline })
|
||||||
|
}
|
||||||
|
"N" | "NO" => {
|
||||||
|
self.multiple.retain(|handle| self.info.get(&handle.value()).is_some_and(|info| info.is_poly));
|
||||||
|
self.target = self.multiple.first().copied();
|
||||||
|
self.mode = Mode::Options;
|
||||||
|
Some(if self.target.is_some() { CmdResult::NeedPoint } else { CmdResult::Cancel })
|
||||||
|
}
|
||||||
|
_ => Some(CmdResult::NeedPoint),
|
||||||
|
}
|
||||||
|
}
|
||||||
Mode::ConvertPrompt(handle) => {
|
Mode::ConvertPrompt(handle) => {
|
||||||
let handle = *handle;
|
let handle = *handle;
|
||||||
match up.as_str() {
|
match up.as_str() {
|
||||||
@@ -331,14 +420,31 @@ impl CadCommand for PeditCommand {
|
|||||||
.replace(',', ".")
|
.replace(',', ".")
|
||||||
.parse()
|
.parse()
|
||||||
.ok()
|
.ok()
|
||||||
.filter(|&v: &f64| v >= 0.0)?;
|
.filter(|&v: &f64| v.is_finite() && v >= 0.0)?;
|
||||||
self.mode = Mode::Options;
|
self.mode = Mode::Options;
|
||||||
Some(CmdResult::PeditOp {
|
Some(CmdResult::PeditOp {
|
||||||
handle,
|
handle,
|
||||||
op: PeditOp::SetWidth(w),
|
op: PeditOp::SetWidth(w),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Mode::AwaitLinetype => {
|
||||||
|
let enabled = match up.as_str() { "ON" => true, "OFF" => false, _ => return Some(CmdResult::NeedPoint) };
|
||||||
|
self.mode = Mode::Options;
|
||||||
|
Some(CmdResult::PeditOp { handle: self.target?, op: PeditOp::SetLinetypeGeneration(enabled) })
|
||||||
|
}
|
||||||
Mode::JoinGather(_) => None,
|
Mode::JoinGather(_) => None,
|
||||||
|
Mode::PolyMove(_) | Mode::PolyInsert(_) => None,
|
||||||
|
Mode::PolyVertex(index) => {
|
||||||
|
match up.as_str() {
|
||||||
|
"N" | "NEXT" => { if *index + 1 < vertex_count { *index += 1; } }
|
||||||
|
"P" | "PREVIOUS" => { *index = index.saturating_sub(1); }
|
||||||
|
"I" | "INSERT" => self.mode = Mode::PolyInsert(*index),
|
||||||
|
"M" | "MOVE" => self.mode = Mode::PolyMove(*index),
|
||||||
|
"X" | "EXIT" => self.mode = Mode::Options,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
Mode::MeshVertexMove(_) => None,
|
Mode::MeshVertexMove(_) => None,
|
||||||
Mode::MeshVertex(index) => {
|
Mode::MeshVertex(index) => {
|
||||||
let (m, n) = mesh_size?;
|
let (m, n) = mesh_size?;
|
||||||
@@ -442,6 +548,19 @@ impl CadCommand for PeditCommand {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
match up.as_str() {
|
match up.as_str() {
|
||||||
|
"E" | "EDIT" if self.multiple.is_empty() => { self.mode = Mode::PolyVertex(0); Some(CmdResult::NeedPoint) }
|
||||||
|
"R" | "REVERSE" => Some(CmdResult::PeditOp { handle, op: PeditOp::Reverse }),
|
||||||
|
"L" | "LTYPE" | "LTYPEGEN" => { self.mode = Mode::AwaitLinetype; Some(CmdResult::NeedPoint) }
|
||||||
|
"U" | "UNDO" if self.undo_count > 0 => {
|
||||||
|
self.undo_count -= 1;
|
||||||
|
self.mesh_closed_history.pop();
|
||||||
|
if let Some(previous) = self.multiple_history.pop() {
|
||||||
|
self.multiple = previous;
|
||||||
|
if !self.multiple.is_empty() { self.target = self.multiple.first().copied(); }
|
||||||
|
}
|
||||||
|
Some(CmdResult::UndoDocument)
|
||||||
|
}
|
||||||
|
"U" | "UNDO" => Some(CmdResult::NeedPoint),
|
||||||
"X" | "EXIT" => Some(CmdResult::Cancel),
|
"X" | "EXIT" => Some(CmdResult::Cancel),
|
||||||
"C" | "CLOSE" => Some(CmdResult::PeditOp {
|
"C" | "CLOSE" => Some(CmdResult::PeditOp {
|
||||||
handle,
|
handle,
|
||||||
@@ -475,21 +594,29 @@ impl CadCommand for PeditCommand {
|
|||||||
// Inline shorthand `W <value>`.
|
// Inline shorthand `W <value>`.
|
||||||
if let Some(rest) = up.strip_prefix("W ") {
|
if let Some(rest) = up.strip_prefix("W ") {
|
||||||
let w: f64 = rest.trim().replace(',', ".").parse().ok()?;
|
let w: f64 = rest.trim().replace(',', ".").parse().ok()?;
|
||||||
if w >= 0.0 {
|
if w.is_finite() && w >= 0.0 {
|
||||||
return Some(CmdResult::PeditOp {
|
return self.multiple_result(Some(CmdResult::PeditOp {
|
||||||
handle,
|
handle,
|
||||||
op: PeditOp::SetWidth(w),
|
op: PeditOp::SetWidth(w),
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
self.multiple_result(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||||
|
if let Mode::PolyMove(index) | Mode::PolyInsert(index) = self.mode {
|
||||||
|
if !point.is_finite() { return CmdResult::NeedPoint; }
|
||||||
|
let Some(handle) = self.target else { return CmdResult::Cancel; };
|
||||||
|
let insert = matches!(self.mode, Mode::PolyInsert(_));
|
||||||
|
self.mode = Mode::PolyVertex(index);
|
||||||
|
return CmdResult::PeditOp { handle, op: PeditOp::EditVertex { index, point, insert } };
|
||||||
|
}
|
||||||
if let Mode::MeshVertexMove(index) = self.mode {
|
if let Mode::MeshVertexMove(index) = self.mode {
|
||||||
let Some(handle) = self.target else {
|
let Some(handle) = self.target else {
|
||||||
return CmdResult::Cancel;
|
return CmdResult::Cancel;
|
||||||
@@ -507,6 +634,18 @@ impl CadCommand for PeditCommand {
|
|||||||
let mesh_size = self.mesh_size();
|
let mesh_size = self.mesh_size();
|
||||||
let vertex_default = self.mesh_vertex_default;
|
let vertex_default = self.mesh_vertex_default;
|
||||||
match &mut self.mode {
|
match &mut self.mode {
|
||||||
|
Mode::MultipleGather => {
|
||||||
|
self.target = self.multiple.first().copied();
|
||||||
|
if self.target.is_none() { return CmdResult::Cancel; }
|
||||||
|
self.mode = if self.multiple.iter().any(|handle| self.info.get(&handle.value()).is_some_and(|info| info.convertible)) {
|
||||||
|
Mode::MultipleConvert
|
||||||
|
} else { Mode::Options };
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
Mode::MultipleConvert => self.on_text_input("Y").unwrap_or(CmdResult::NeedPoint),
|
||||||
|
Mode::PolyVertex(_) => self.on_text_input("N").unwrap_or(CmdResult::NeedPoint),
|
||||||
|
Mode::PolyMove(_) | Mode::PolyInsert(_) => { self.mode = Mode::Options; CmdResult::NeedPoint }
|
||||||
|
Mode::AwaitLinetype => { self.mode = Mode::Options; CmdResult::NeedPoint }
|
||||||
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;
|
||||||
@@ -539,8 +678,12 @@ impl CadCommand for PeditCommand {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum PeditOp {
|
pub enum PeditOp {
|
||||||
|
Multiple(Vec<Handle>, Box<PeditOp>),
|
||||||
SetClosed(bool),
|
SetClosed(bool),
|
||||||
SetWidth(f64),
|
SetWidth(f64),
|
||||||
|
SetLinetypeGeneration(bool),
|
||||||
|
EditVertex { index: usize, point: DVec3, insert: bool },
|
||||||
|
Reverse,
|
||||||
/// Replace the picked Line/Arc with an equivalent LwPolyline (#263).
|
/// Replace the picked Line/Arc with an equivalent LwPolyline (#263).
|
||||||
ConvertToPolyline,
|
ConvertToPolyline,
|
||||||
Fit,
|
Fit,
|
||||||
@@ -560,6 +703,54 @@ pub enum PeditOp {
|
|||||||
|
|
||||||
pub fn apply_pedit(entity: &mut EntityType, op: &PeditOp) -> bool {
|
pub fn apply_pedit(entity: &mut EntityType, op: &PeditOp) -> bool {
|
||||||
match op {
|
match op {
|
||||||
|
PeditOp::Multiple(_, _) => false,
|
||||||
|
PeditOp::EditVertex { index, point, insert } => {
|
||||||
|
if !point.is_finite() { return false; }
|
||||||
|
match entity {
|
||||||
|
EntityType::LwPolyline(polyline) => {
|
||||||
|
let plane = crate::entities::curve::ocs_plane(polyline.normal, polyline.elevation);
|
||||||
|
let Some(local) = plane.project([point.x, point.y, point.z]) else { return false; };
|
||||||
|
if *index >= polyline.vertices.len() { return false; }
|
||||||
|
if *insert {
|
||||||
|
polyline.vertices.insert(index + 1, LwVertex::new(Vector2::new(local[0], local[1])));
|
||||||
|
} else { polyline.vertices[*index].location = Vector2::new(local[0], local[1]); }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
EntityType::Polyline2D(polyline) => {
|
||||||
|
let plane = crate::entities::curve::ocs_plane(polyline.normal, polyline.elevation);
|
||||||
|
let Some(local) = plane.project([point.x, point.y, point.z]) else { return false; };
|
||||||
|
if *index >= polyline.vertices.len() { return false; }
|
||||||
|
let location = Vector3::new(local[0], local[1], polyline.elevation);
|
||||||
|
if *insert {
|
||||||
|
polyline.vertices.insert(index + 1, acadrust::entities::polyline::Vertex2D::new(location));
|
||||||
|
} else { polyline.vertices[*index].location = location; }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PeditOp::Reverse => {
|
||||||
|
let Some(reversed) = super::reverse::ReverseCommand::reversed(entity) else { return false; };
|
||||||
|
*entity = reversed;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
PeditOp::SetLinetypeGeneration(enabled) => match entity {
|
||||||
|
EntityType::LwPolyline(polyline) => {
|
||||||
|
if polyline.plinegen == *enabled { return false; }
|
||||||
|
polyline.plinegen = *enabled;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
EntityType::Polyline2D(polyline) => {
|
||||||
|
let flag = acadrust::entities::polyline::PolylineFlags::LINETYPE_CONTINUOUS;
|
||||||
|
let bits = polyline.flags.bits();
|
||||||
|
if (bits & flag.bits() != 0) == *enabled { return false; }
|
||||||
|
polyline.flags = acadrust::entities::polyline::PolylineFlags::from_bits(
|
||||||
|
if *enabled { bits | flag.bits() } else { bits & !flag.bits() }
|
||||||
|
);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
PeditOp::SetClosed(closed) => match entity {
|
PeditOp::SetClosed(closed) => match entity {
|
||||||
EntityType::LwPolyline(p) => {
|
EntityType::LwPolyline(p) => {
|
||||||
p.is_closed = *closed;
|
p.is_closed = *closed;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ impl ReverseCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build a reversed copy of `entity`, or `None` for an unsupported type.
|
/// Build a reversed copy of `entity`, or `None` for an unsupported type.
|
||||||
fn reversed(entity: &EntityType) -> Option<EntityType> {
|
pub fn reversed(entity: &EntityType) -> Option<EntityType> {
|
||||||
match entity {
|
match entity {
|
||||||
EntityType::Line(line) => {
|
EntityType::Line(line) => {
|
||||||
let mut out = line.clone();
|
let mut out = line.clone();
|
||||||
|
|||||||
Reference in new issue
Block a user