forked from 1b-agent/opencadstudio-upstream
fix(fillet): keep no-trim for every pair and the Trim prompt; share TRIMMODE with CHAMFER
#1692 limited no-trim FILLET to two lines and refused arcs and polyline segments, which #1558 already filleted without trimming (only the arc is added). It also turned T/N at the object prompts into silent toggles, so the Trim option no longer reached its Trim/No trim prompt and the choice was not saved. Keep the arc bookkeeping it added (the new arc can be picked next, and stays on the picked layer), restore every pair and the Trim prompt. CHAMFER's new no-trim mode starts from and saves the same TRIMMODE value as FILLET instead of always starting trimmed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6f2d43820f
commit
b9ef8c50ed
1 file changed
+19
-92
@@ -794,16 +794,6 @@ impl FilletEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The refusal a no-trim FILLET prints for anything but two independent Lines.
|
|
||||||
const NO_TRIM_LINES_ONLY: &str = "No-trim fillet supports two independent lines only.";
|
|
||||||
|
|
||||||
/// No-trim FILLET is defined for two independent Lines only: the addition path
|
|
||||||
/// keeps both sources untouched, which the arc / polyline branches cannot
|
|
||||||
/// express without falling back to trimming.
|
|
||||||
fn no_trim_pair_supported(first: &FilletEntity, second: &FilletEntity) -> bool {
|
|
||||||
matches!(first, FilletEntity::Line(_)) && matches!(second, FilletEntity::Line(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute FILLET between two entities (Line, Arc, or LwPolyline segment).
|
/// Compute FILLET between two entities (Line, Arc, or LwPolyline segment).
|
||||||
/// Returns (trimmed_e1, trimmed_e2, optional_fillet_arc).
|
/// Returns (trimmed_e1, trimmed_e2, optional_fillet_arc).
|
||||||
/// For same-poly corner fillet the two returned entities are identical (the rebuilt poly).
|
/// For same-poly corner fillet the two returned entities are identical (the rebuilt poly).
|
||||||
@@ -1489,8 +1479,8 @@ enum FilletStep {
|
|||||||
|
|
||||||
pub struct FilletCommand {
|
pub struct FilletCommand {
|
||||||
radius: f64,
|
radius: f64,
|
||||||
/// Whether the picked source entities are shortened to the tangent points.
|
/// Whether the picked source entities are shortened to the tangent points
|
||||||
/// No-trim is defined for two independent Lines only.
|
/// (the shared TRIMMODE, changed through the Trim option).
|
||||||
trim: bool,
|
trim: bool,
|
||||||
step: FilletStep,
|
step: FilletStep,
|
||||||
all_entities: Vec<EntityType>,
|
all_entities: Vec<EntityType>,
|
||||||
@@ -1658,22 +1648,11 @@ impl CadCommand for FilletCommand {
|
|||||||
}
|
}
|
||||||
opts.push(CmdOption::new("Polyline", "P"));
|
opts.push(CmdOption::new("Polyline", "P"));
|
||||||
opts.push(CmdOption::new("Radius", "R"));
|
opts.push(CmdOption::new("Radius", "R"));
|
||||||
opts.push(if self.trim {
|
opts.push(CmdOption::new("Trim", "T"));
|
||||||
CmdOption::new("No trim", "N")
|
|
||||||
} else {
|
|
||||||
CmdOption::new("Trim", "T")
|
|
||||||
});
|
|
||||||
opts
|
opts
|
||||||
}
|
}
|
||||||
FilletStep::Second { .. } => {
|
FilletStep::Second { .. } => {
|
||||||
vec![
|
vec![CmdOption::new("Radius", "R"), CmdOption::new("Trim", "T")]
|
||||||
CmdOption::new("Radius", "R"),
|
|
||||||
if self.trim {
|
|
||||||
CmdOption::new("No trim", "N")
|
|
||||||
} else {
|
|
||||||
CmdOption::new("Trim", "T")
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
FilletStep::TrimMode => {
|
FilletStep::TrimMode => {
|
||||||
vec![CmdOption::new("Trim", "T"), CmdOption::new("No trim", "N")]
|
vec![CmdOption::new("Trim", "T"), CmdOption::new("No trim", "N")]
|
||||||
@@ -1738,14 +1717,6 @@ impl CadCommand for FilletCommand {
|
|||||||
FilletStep::First | FilletStep::Second { .. } => {
|
FilletStep::First | FilletStep::Second { .. } => {
|
||||||
let t = text.trim();
|
let t = text.trim();
|
||||||
let upper = t.to_uppercase();
|
let upper = t.to_uppercase();
|
||||||
if matches!(upper.as_str(), "N" | "NOTRIM" | "NO TRIM" | "TRIM=FALSE") {
|
|
||||||
self.trim = false;
|
|
||||||
return Some(CmdResult::NeedPoint);
|
|
||||||
}
|
|
||||||
if matches!(upper.as_str(), "T" | "TRIM" | "TRIM=TRUE") {
|
|
||||||
self.trim = true;
|
|
||||||
return Some(CmdResult::NeedPoint);
|
|
||||||
}
|
|
||||||
if matches!(self.step, FilletStep::First) && upper == "P" {
|
if matches!(self.step, FilletStep::First) && upper == "P" {
|
||||||
self.step = FilletStep::Polyline;
|
self.step = FilletStep::Polyline;
|
||||||
return Some(CmdResult::NeedPoint);
|
return Some(CmdResult::NeedPoint);
|
||||||
@@ -1849,11 +1820,6 @@ impl CadCommand for FilletCommand {
|
|||||||
other => FilletEntity::from_entity(other),
|
other => FilletEntity::from_entity(other),
|
||||||
});
|
});
|
||||||
if let Some(e) = e1 {
|
if let Some(e) = e1 {
|
||||||
// Do not let no-trim silently fall through to the normal
|
|
||||||
// trimming path for a first object it cannot preserve.
|
|
||||||
if !self.trim && !matches!(e, FilletEntity::Line(_)) {
|
|
||||||
return CmdResult::ReportError(NO_TRIM_LINES_ONLY.to_string());
|
|
||||||
}
|
|
||||||
self.step = FilletStep::Second {
|
self.step = FilletStep::Second {
|
||||||
h1: handle,
|
h1: handle,
|
||||||
e1: e,
|
e1: e,
|
||||||
@@ -1888,15 +1854,9 @@ impl CadCommand for FilletCommand {
|
|||||||
if let Some(e2) = e2 {
|
if let Some(e2) = e2 {
|
||||||
match compute_fillet_entities(&e1, click1, &e2, click, self.radius) {
|
match compute_fillet_entities(&e1, click1, &e2, click, self.radius) {
|
||||||
Some((new_e1, new_e2, maybe_arc)) => {
|
Some((new_e1, new_e2, maybe_arc)) => {
|
||||||
// In no-trim mode only two independent Lines are
|
// No trim: the objects stay as they are; only the
|
||||||
// supported. Reuse the solver for the same arc
|
// arc is added (#1558).
|
||||||
// branch, then discard its shortened-line results.
|
|
||||||
if !self.trim {
|
if !self.trim {
|
||||||
if !no_trim_pair_supported(&e1, &e2) {
|
|
||||||
self.step = FilletStep::First;
|
|
||||||
self.resume_second = None;
|
|
||||||
return CmdResult::ReportError(NO_TRIM_LINES_ONLY.to_string());
|
|
||||||
}
|
|
||||||
return maybe_arc
|
return maybe_arc
|
||||||
.map(|arc| self.continue_after_addition(arc))
|
.map(|arc| self.continue_after_addition(arc))
|
||||||
.unwrap_or(CmdResult::NeedPoint);
|
.unwrap_or(CmdResult::NeedPoint);
|
||||||
@@ -1915,10 +1875,6 @@ impl CadCommand for FilletCommand {
|
|||||||
}
|
}
|
||||||
None => CmdResult::NeedPoint,
|
None => CmdResult::NeedPoint,
|
||||||
}
|
}
|
||||||
} else if !self.trim {
|
|
||||||
self.step = FilletStep::First;
|
|
||||||
self.resume_second = None;
|
|
||||||
CmdResult::ReportError(NO_TRIM_LINES_ONLY.to_string())
|
|
||||||
} else {
|
} else {
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
}
|
}
|
||||||
@@ -1991,9 +1947,6 @@ impl CadCommand for FilletCommand {
|
|||||||
// For non-LwPoly, skip same-entity hover.
|
// For non-LwPoly, skip same-entity hover.
|
||||||
let _ = h1;
|
let _ = h1;
|
||||||
if let Some(e2) = e2 {
|
if let Some(e2) = e2 {
|
||||||
if !self.trim && !no_trim_pair_supported(&e1, &e2) {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
if let Some((new_e1, new_e2, maybe_arc)) =
|
if let Some((new_e1, new_e2, maybe_arc)) =
|
||||||
compute_fillet_entities(&e1, click1, &e2, click, self.radius)
|
compute_fillet_entities(&e1, click1, &e2, click, self.radius)
|
||||||
{
|
{
|
||||||
@@ -2198,8 +2151,9 @@ const NO_TRIM_CHAMFER_LINES_ONLY: &str =
|
|||||||
pub struct ChamferCommand {
|
pub struct ChamferCommand {
|
||||||
dist1: f64,
|
dist1: f64,
|
||||||
dist2: f64,
|
dist2: f64,
|
||||||
/// Whether the picked source entities are shortened to the chamfer points.
|
/// Whether the picked source entities are shortened to the chamfer points
|
||||||
/// No-trim is defined for two independent Lines only.
|
/// (the TRIMMODE FILLET shares). No-trim is defined for two independent
|
||||||
|
/// Lines only.
|
||||||
trim: bool,
|
trim: bool,
|
||||||
step: ChamferStep,
|
step: ChamferStep,
|
||||||
all_entities: Vec<EntityType>,
|
all_entities: Vec<EntityType>,
|
||||||
@@ -2224,7 +2178,7 @@ impl ChamferCommand {
|
|||||||
Self {
|
Self {
|
||||||
dist1: dist as f64,
|
dist1: dist as f64,
|
||||||
dist2: defaults::get_chamfer_dist2(),
|
dist2: defaults::get_chamfer_dist2(),
|
||||||
trim: true,
|
trim: defaults::get_fillet_trim(),
|
||||||
step: ChamferStep::First,
|
step: ChamferStep::First,
|
||||||
all_entities,
|
all_entities,
|
||||||
entity_index,
|
entity_index,
|
||||||
@@ -2443,10 +2397,12 @@ impl CadCommand for ChamferCommand {
|
|||||||
let upper = t.to_uppercase();
|
let upper = t.to_uppercase();
|
||||||
if matches!(upper.as_str(), "N" | "NOTRIM" | "NO TRIM" | "TRIM=FALSE") {
|
if matches!(upper.as_str(), "N" | "NOTRIM" | "NO TRIM" | "TRIM=FALSE") {
|
||||||
self.trim = false;
|
self.trim = false;
|
||||||
|
defaults::set_fillet_trim(false);
|
||||||
return Some(CmdResult::NeedPoint);
|
return Some(CmdResult::NeedPoint);
|
||||||
}
|
}
|
||||||
if matches!(upper.as_str(), "T" | "TRIM" | "TRIM=TRUE") {
|
if matches!(upper.as_str(), "T" | "TRIM" | "TRIM=TRUE") {
|
||||||
self.trim = true;
|
self.trim = true;
|
||||||
|
defaults::set_fillet_trim(true);
|
||||||
return Some(CmdResult::NeedPoint);
|
return Some(CmdResult::NeedPoint);
|
||||||
}
|
}
|
||||||
if matches!(self.step, ChamferStep::First) {
|
if matches!(self.step, ChamferStep::First) {
|
||||||
@@ -2804,16 +2760,16 @@ mod tests {
|
|||||||
1.0,
|
1.0,
|
||||||
vec![line(0.0, 0.0, 10.0, 0.0, 1), line(0.0, 0.0, 0.0, 10.0, 2)],
|
vec![line(0.0, 0.0, 10.0, 0.0, 1), line(0.0, 0.0, 0.0, 10.0, 2)],
|
||||||
);
|
);
|
||||||
assert_eq!(keywords(&command), ["P", "R", "N"]);
|
assert_eq!(keywords(&command), ["P", "R", "T"]);
|
||||||
assert!(command.on_text_input("U").is_none(), "nothing to undo yet");
|
assert!(command.on_text_input("U").is_none(), "nothing to undo yet");
|
||||||
command.on_entity_pick(Handle::new(1), DVec3::new(5.0, 0.0, 0.0));
|
command.on_entity_pick(Handle::new(1), DVec3::new(5.0, 0.0, 0.0));
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
command.on_entity_pick(Handle::new(2), DVec3::new(0.0, 5.0, 0.0)),
|
command.on_entity_pick(Handle::new(2), DVec3::new(0.0, 5.0, 0.0)),
|
||||||
CmdResult::ReplaceManyContinue(_)
|
CmdResult::ReplaceManyContinue(_)
|
||||||
));
|
));
|
||||||
assert_eq!(keywords(&command), ["U", "P", "R", "N"]);
|
assert_eq!(keywords(&command), ["U", "P", "R", "T"]);
|
||||||
assert!(matches!(command.on_text_input("U"), Some(CmdResult::UndoDocument)));
|
assert!(matches!(command.on_text_input("U"), Some(CmdResult::UndoDocument)));
|
||||||
assert_eq!(keywords(&command), ["P", "R", "N"]);
|
assert_eq!(keywords(&command), ["P", "R", "T"]);
|
||||||
// The host hands the restored document back; the cache follows it.
|
// The host hands the restored document back; the cache follows it.
|
||||||
let mut doc = codec::CadDocument::new();
|
let mut doc = codec::CadDocument::new();
|
||||||
let _ = doc.add_entity(line(0.0, 0.0, 20.0, 0.0, 7));
|
let _ = doc.add_entity(line(0.0, 0.0, 20.0, 0.0, 7));
|
||||||
@@ -2831,8 +2787,7 @@ mod tests {
|
|||||||
line(0.0, 0.0, 0.0, 10.0, 2),
|
line(0.0, 0.0, 0.0, 10.0, 2),
|
||||||
];
|
];
|
||||||
let mut command = FilletCommand::new(1.0, original);
|
let mut command = FilletCommand::new(1.0, original);
|
||||||
assert!(matches!(command.on_text_input("N"), Some(CmdResult::NeedPoint)));
|
command.trim = false;
|
||||||
assert!(!command.trim);
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
command.on_entity_pick(first, DVec3::new(5.0, 0.0, 0.0)),
|
command.on_entity_pick(first, DVec3::new(5.0, 0.0, 0.0)),
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
@@ -2877,34 +2832,6 @@ mod tests {
|
|||||||
assert_eq!(source_lines[1], (Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 10.0, 0.0)));
|
assert_eq!(source_lines[1], (Vector3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 10.0, 0.0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn no_trim_fillet_refuses_non_line_first_picks() {
|
|
||||||
let mut arc = ArcEnt::new();
|
|
||||||
arc.center = Vector3::new(10.0, 10.0, 0.0);
|
|
||||||
arc.radius = 5.0;
|
|
||||||
arc.start_angle = std::f64::consts::PI;
|
|
||||||
arc.end_angle = std::f64::consts::PI * 1.5;
|
|
||||||
arc.common.handle = Handle::new(2);
|
|
||||||
let mut command = FilletCommand::new(
|
|
||||||
1.0,
|
|
||||||
vec![
|
|
||||||
line(0.0, 10.0, 20.0, 10.0, 1),
|
|
||||||
EntityType::Arc(arc),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
assert!(matches!(command.on_text_input("N"), Some(CmdResult::NeedPoint)));
|
|
||||||
assert!(matches!(
|
|
||||||
command.on_entity_pick(Handle::new(2), DVec3::new(10.0, 5.0, 0.0)),
|
|
||||||
CmdResult::ReportError(_)
|
|
||||||
));
|
|
||||||
assert!(matches!(command.step, FilletStep::First));
|
|
||||||
assert!(matches!(
|
|
||||||
command.on_entity_pick(Handle::new(1), DVec3::new(5.0, 10.0, 0.0)),
|
|
||||||
CmdResult::NeedPoint
|
|
||||||
));
|
|
||||||
assert!(matches!(command.step, FilletStep::Second { .. }));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn chamfer_multiple_keeps_going_and_single_ends() {
|
fn chamfer_multiple_keeps_going_and_single_ends() {
|
||||||
let lines = || vec![line(0.0, 0.0, 10.0, 0.0, 1), line(0.0, 0.0, 0.0, 10.0, 2)];
|
let lines = || vec![line(0.0, 0.0, 10.0, 0.0, 1), line(0.0, 0.0, 0.0, 10.0, 2)];
|
||||||
@@ -2943,7 +2870,7 @@ mod tests {
|
|||||||
|
|
||||||
let mut single = ChamferCommand::new(1.0, lines());
|
let mut single = ChamferCommand::new(1.0, lines());
|
||||||
single.dist2 = 1.0;
|
single.dist2 = 1.0;
|
||||||
assert!(matches!(single.on_text_input("N"), Some(CmdResult::NeedPoint)));
|
single.trim = false;
|
||||||
assert!(!single.trim);
|
assert!(!single.trim);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
single.on_entity_pick(first, DVec3::new(5.0, 0.0, 0.0)),
|
single.on_entity_pick(first, DVec3::new(5.0, 0.0, 0.0)),
|
||||||
@@ -2973,7 +2900,7 @@ mod tests {
|
|||||||
let mut multi = ChamferCommand::new(1.0, lines());
|
let mut multi = ChamferCommand::new(1.0, lines());
|
||||||
multi.dist2 = 1.0;
|
multi.dist2 = 1.0;
|
||||||
assert!(matches!(multi.on_text_input("M"), Some(CmdResult::NeedPoint)));
|
assert!(matches!(multi.on_text_input("M"), Some(CmdResult::NeedPoint)));
|
||||||
assert!(matches!(multi.on_text_input("N"), Some(CmdResult::NeedPoint)));
|
multi.trim = false;
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
multi.on_entity_pick(first, DVec3::new(5.0, 0.0, 0.0)),
|
multi.on_entity_pick(first, DVec3::new(5.0, 0.0, 0.0)),
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
@@ -3011,7 +2938,7 @@ mod tests {
|
|||||||
poly.add_point(codec::types::Vector2::new(10.0, 10.0));
|
poly.add_point(codec::types::Vector2::new(10.0, 10.0));
|
||||||
poly.common.handle = Handle::new(3);
|
poly.common.handle = Handle::new(3);
|
||||||
let mut command = ChamferCommand::new(1.0, vec![EntityType::LwPolyline(poly)]);
|
let mut command = ChamferCommand::new(1.0, vec![EntityType::LwPolyline(poly)]);
|
||||||
assert!(matches!(command.on_text_input("N"), Some(CmdResult::NeedPoint)));
|
command.trim = false;
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
command.on_entity_pick(Handle::new(3), DVec3::new(5.0, 0.0, 0.0)),
|
command.on_entity_pick(Handle::new(3), DVec3::new(5.0, 0.0, 0.0)),
|
||||||
CmdResult::ReportError(_)
|
CmdResult::ReportError(_)
|
||||||
|
|||||||
Reference in new issue
Block a user