forked from 1b-agent/opencadstudio-upstream
Extend selected polyline terminals using bounded kernel intersections
This commit is contained in:
1 parent
356c8b5a7b
commit
aa11b10c9a
2 files changed
+80
-8
No files matched your search
@@ -3216,7 +3216,7 @@ impl OpenCADStudio {
|
||||
apply_pedit, convert_to_polyline, PeditOp,
|
||||
};
|
||||
match &op {
|
||||
PeditOp::JoinSelection(handles) => {
|
||||
PeditOp::JoinSelection(handles, fuzz) => {
|
||||
let mut available = handles.iter().filter_map(|handle| {
|
||||
if self.tabs[i].scene.is_layer_locked(*handle) { return None; }
|
||||
self.tabs[i].scene.document.get_entity(*handle).cloned().map(|entity| (*handle, entity))
|
||||
@@ -3225,7 +3225,7 @@ impl OpenCADStudio {
|
||||
while !available.is_empty() {
|
||||
let (source_handle, source) = available.remove(0);
|
||||
let candidates = available.iter().map(|(handle, entity)| (*handle, entity)).collect::<Vec<_>>();
|
||||
if let Some((mut result, consumed)) = crate::modules::draw::modify::join::join_to_source(&source, &candidates) {
|
||||
if let Some((mut result, consumed)) = crate::modules::draw::modify::pedit::join_selection_extend(&source, &candidates, *fuzz) {
|
||||
*result.common_mut() = source.common().clone();
|
||||
available.retain(|(handle, _)| !consumed.contains(handle));
|
||||
changes.push((source_handle, result, consumed));
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::command::{CadCommand, CmdResult};
|
||||
use crate::t;
|
||||
|
||||
const TAU: f64 = std::f64::consts::TAU;
|
||||
static JOIN_FUZZ: std::sync::Mutex<f64> = std::sync::Mutex::new(0.0);
|
||||
|
||||
/// What PEDIT knows about a pickable entity, captured at dispatch.
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -58,6 +59,7 @@ pub struct PeditCommand {
|
||||
multiple: Vec<Handle>,
|
||||
multiple_history: Vec<Vec<Handle>>,
|
||||
pending_multiple: Option<Vec<Handle>>,
|
||||
join_fuzz: f64,
|
||||
info: HashMap<u64, PeditTarget>,
|
||||
mode: Mode,
|
||||
undo_count: usize,
|
||||
@@ -91,6 +93,7 @@ impl PeditCommand {
|
||||
multiple: Vec::new(),
|
||||
multiple_history: Vec::new(),
|
||||
pending_multiple: None,
|
||||
join_fuzz: *JOIN_FUZZ.lock().unwrap_or_else(|error| error.into_inner()),
|
||||
info,
|
||||
mode: Mode::PickTarget,
|
||||
undo_count: 0,
|
||||
@@ -235,7 +238,7 @@ impl CadCommand for PeditCommand {
|
||||
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::MultipleJoin => "PEDIT Enter fuzz distance <0>:".to_string(),
|
||||
Mode::MultipleJoin => format!("PEDIT Join type: Extend. Enter fuzz distance <{}>:", self.join_fuzz),
|
||||
Mode::MultipleConvert => t!("PEDIT Convert lines and arcs to polylines [Yes/No] <Yes>:").into_owned(),
|
||||
Mode::ConvertPrompt(_) => t!(
|
||||
"PEDIT Object is not a polyline. Turn it into one? [Yes/No] <Y>:"
|
||||
@@ -439,10 +442,12 @@ impl CadCommand for PeditCommand {
|
||||
Mode::MultipleGather => None,
|
||||
Mode::MultipleJoin => {
|
||||
let fuzz = text.trim().parse::<f64>().ok()?;
|
||||
if fuzz != 0.0 { return Some(CmdResult::ReportError("PEDIT: only zero fuzz distance is currently supported.".to_string())); }
|
||||
if !fuzz.is_finite() || fuzz < 0.0 { return Some(CmdResult::ReportError("PEDIT: fuzz distance must be nonnegative and finite.".to_string())); }
|
||||
self.join_fuzz = fuzz;
|
||||
*JOIN_FUZZ.lock().unwrap_or_else(|error| error.into_inner()) = fuzz;
|
||||
self.pending_multiple = Some(self.multiple.clone());
|
||||
self.mode = Mode::Options;
|
||||
return Some(CmdResult::PeditOp { handle: self.target?, op: PeditOp::JoinSelection(self.multiple.clone()) });
|
||||
return Some(CmdResult::PeditOp { handle: self.target?, op: PeditOp::JoinSelection(self.multiple.clone(), fuzz) });
|
||||
},
|
||||
Mode::MultipleConvert => {
|
||||
match up.as_str() {
|
||||
@@ -762,7 +767,7 @@ impl CadCommand for PeditCommand {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Mode::MultipleConvert => self.on_text_input("Y").unwrap_or(CmdResult::NeedPoint),
|
||||
Mode::MultipleJoin => self.on_text_input("0").unwrap_or(CmdResult::NeedPoint),
|
||||
Mode::MultipleJoin => self.on_text_input(&self.join_fuzz.to_string()).unwrap_or(CmdResult::NeedPoint),
|
||||
Mode::PolyWidthStart(_, width) | Mode::PolyWidthEnd(_, width) => { let value = width.to_string(); self.on_text_input(&value).unwrap_or(CmdResult::NeedPoint) }
|
||||
Mode::PolyVertex(_) | Mode::PolyRange { .. } => self.on_text_input("N").unwrap_or(CmdResult::NeedPoint),
|
||||
Mode::PolyMove(_) | Mode::PolyInsert(_) => { self.mode = Mode::Options; CmdResult::NeedPoint }
|
||||
@@ -800,7 +805,7 @@ impl CadCommand for PeditCommand {
|
||||
#[derive(Clone)]
|
||||
pub enum PeditOp {
|
||||
Multiple(Vec<Handle>, Box<PeditOp>),
|
||||
JoinSelection(Vec<Handle>),
|
||||
JoinSelection(Vec<Handle>, f64),
|
||||
SetClosed(bool),
|
||||
SetWidth(f64),
|
||||
SetVertexWidth { index: usize, start: f64, end: f64 },
|
||||
@@ -866,7 +871,7 @@ pub fn edit_vertex_range(entity: &EntityType, first: usize, last: usize, split:
|
||||
|
||||
pub fn apply_pedit(entity: &mut EntityType, op: &PeditOp) -> bool {
|
||||
match op {
|
||||
PeditOp::Multiple(_, _) | PeditOp::JoinSelection(_) | PeditOp::VertexRange { .. } => false,
|
||||
PeditOp::Multiple(_, _) | PeditOp::JoinSelection(_, _) | PeditOp::VertexRange { .. } => false,
|
||||
PeditOp::SetVertexWidth { index, start, end } => {
|
||||
if !start.is_finite() || !end.is_finite() || *start < 0.0 || *end < 0.0 { return false; }
|
||||
match entity {
|
||||
@@ -1318,3 +1323,70 @@ fn spline_smooth(p: &mut acadrust::LwPolyline) -> bool {
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["PEDIT"] }); // PeditCommand
|
||||
|
||||
|
||||
/// Extend straight terminal spans before reusing the ordinary source join.
|
||||
/// Curved terminal spans and unsupported representations remain unchanged.
|
||||
pub fn join_selection_extend(source: &EntityType, candidates: &[(Handle, &EntityType)], fuzz: f64) -> Option<(EntityType, Vec<Handle>)> {
|
||||
fn endpoint(entity: &EntityType, start: bool) -> Option<[[f64; 3]; 2]> {
|
||||
let (points, normal, elevation) = match entity {
|
||||
EntityType::LwPolyline(p) if !p.is_closed && p.vertices.len() >= 2 => {
|
||||
let n = p.vertices.len(); let segment = if start {0} else {n-2};
|
||||
if p.vertices[segment].bulge != 0.0 { return None; }
|
||||
let indices = if start {[1,0]} else {[n-2,n-1]};
|
||||
(indices.map(|i| [p.vertices[i].location.x,p.vertices[i].location.y]),p.normal.clone(),p.elevation)
|
||||
}
|
||||
EntityType::Polyline2D(p) if !p.is_closed() && p.vertices.len() >= 2 => {
|
||||
let n = p.vertices.len(); let segment = if start {0} else {n-2};
|
||||
if p.vertices[segment].bulge != 0.0 { return None; }
|
||||
let indices = if start {[1,0]} else {[n-2,n-1]};
|
||||
(indices.map(|i| [p.vertices[i].location.x,p.vertices[i].location.y]),p.normal.clone(),p.elevation)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
let plane = crate::entities::curve::ocs_plane(normal,elevation);
|
||||
Some(points.map(|point| plane.point_at(point)))
|
||||
}
|
||||
fn move_endpoint(entity: &mut EntityType, start: bool, point: [f64;3]) -> Option<()> {
|
||||
match entity {
|
||||
EntityType::LwPolyline(p) => {
|
||||
let local = crate::entities::curve::ocs_plane(p.normal.clone(),p.elevation).project(point)?;
|
||||
let index = if start {0} else {p.vertices.len().checked_sub(1)?};
|
||||
p.vertices[index].location = Vector2::new(local[0],local[1]);
|
||||
}
|
||||
EntityType::Polyline2D(p) => {
|
||||
let local = crate::entities::curve::ocs_plane(p.normal.clone(),p.elevation).project(point)?;
|
||||
let index = if start {0} else {p.vertices.len().checked_sub(1)?};
|
||||
p.vertices[index].location.x = local[0]; p.vertices[index].location.y = local[1];
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
let mut result = source.clone(); let mut consumed = Vec::new();
|
||||
loop {
|
||||
let mut progress = false;
|
||||
for (handle,candidate) in candidates {
|
||||
if consumed.contains(handle) {continue;}
|
||||
let mut joined = super::join::join_to_source(&result,&[(*handle,*candidate)]).map(|(entity,_)|entity);
|
||||
if joined.is_none() && fuzz > 0.0 && fuzz.is_finite() {
|
||||
'ends: for a_start in [false,true] { for b_start in [true,false] {
|
||||
let Some(a) = endpoint(&result,a_start) else {continue;};
|
||||
let Some(b) = endpoint(candidate,b_start) else {continue;};
|
||||
let Some(point) = cadkernel::space::endpoint_join::extend_line_ends(a,b,fuzz) else {continue;};
|
||||
let mut a = result.clone(); let mut b = (*candidate).clone();
|
||||
if move_endpoint(&mut a,a_start,point).is_none() || move_endpoint(&mut b,b_start,point).is_none() {continue;}
|
||||
if let Some((entity,_)) = super::join::join_to_source(&a,&[(*handle,&b)]) {
|
||||
joined = Some(entity); break 'ends;
|
||||
}
|
||||
}}
|
||||
}
|
||||
if let Some(mut entity) = joined {
|
||||
*entity.common_mut() = source.common().clone();
|
||||
result = entity; consumed.push(*handle); progress = true;
|
||||
}
|
||||
}
|
||||
if !progress {break;}
|
||||
}
|
||||
(!consumed.is_empty()).then_some((result,consumed))
|
||||
}
|
||||
Reference in new issue
Block a user