forked from 1b-agent/opencadstudio-upstream
style: rustfmt the tree so the CI lint check passes
This commit is contained in:
1 parent
1317a7b4fd
commit
6025d8be44
128 files changed
+4097
-1676
No files matched your search
@@ -85,7 +85,12 @@ impl CadCommand for PasteSpecCommand {
|
||||
if self.placing_text {
|
||||
return None;
|
||||
}
|
||||
match text.trim().trim_start_matches('_').to_ascii_uppercase().as_str() {
|
||||
match text
|
||||
.trim()
|
||||
.trim_start_matches('_')
|
||||
.to_ascii_uppercase()
|
||||
.as_str()
|
||||
{
|
||||
"O" | "OBJECTS" if self.objects => Some(CmdResult::Dispatch("PASTECLIP".into())),
|
||||
"B" | "BLOCK" if self.objects => Some(CmdResult::Dispatch("PASTEBLOCK".into())),
|
||||
"T" | "TEXT" if self.text.is_some() => {
|
||||
@@ -123,7 +128,9 @@ impl CadCommand for PasteSpecCommand {
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["PASTESPEC"] });
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["PASTESPEC"]
|
||||
});
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -739,8 +739,12 @@ pub fn solve_line_line_ccp(
|
||||
candidates
|
||||
.into_iter()
|
||||
.min_by(|(t1_a, t2_a, r_a), (t1_b, t2_b, r_b)| {
|
||||
let dist_a = (*t1_a - hint1).length_squared() + (*t2_a - hint2).length_squared() + if *r_a > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_b = (*t1_b - hint1).length_squared() + (*t2_b - hint2).length_squared() + if *r_b > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_a = (*t1_a - hint1).length_squared()
|
||||
+ (*t2_a - hint2).length_squared()
|
||||
+ if *r_a > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_b = (*t1_b - hint1).length_squared()
|
||||
+ (*t2_b - hint2).length_squared()
|
||||
+ if *r_b > 50000.0 { 1e8 } else { 0.0 };
|
||||
dist_a.total_cmp(&dist_b)
|
||||
})
|
||||
.map(|(t1, t2, _)| (t1, t2))
|
||||
@@ -790,8 +794,12 @@ pub fn solve_circle_line_ccp(
|
||||
let t_a = i as f64 * dt;
|
||||
let t_b = (i + 1) as f64 * dt;
|
||||
|
||||
let Some((res_a, _, _, _)) = eval_t(t_a) else { continue; };
|
||||
let Some((res_b, _, _, _)) = eval_t(t_b) else { continue; };
|
||||
let Some((res_a, _, _, _)) = eval_t(t_a) else {
|
||||
continue;
|
||||
};
|
||||
let Some((res_b, _, _, _)) = eval_t(t_b) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if res_a.signum() != res_b.signum() {
|
||||
let mut lo = t_a;
|
||||
@@ -799,7 +807,9 @@ pub fn solve_circle_line_ccp(
|
||||
let mut best_t = (lo + hi) * 0.5;
|
||||
for _ in 0..16 {
|
||||
let mid = (lo + hi) * 0.5;
|
||||
let Some((res_m, _, _, _)) = eval_t(mid) else { break; };
|
||||
let Some((res_m, _, _, _)) = eval_t(mid) else {
|
||||
break;
|
||||
};
|
||||
best_t = mid;
|
||||
if res_m.abs() < 1e-8 {
|
||||
break;
|
||||
@@ -822,8 +832,12 @@ pub fn solve_circle_line_ccp(
|
||||
candidates
|
||||
.into_iter()
|
||||
.min_by(|(c_a, l_a, r_a), (c_b, l_b, r_b)| {
|
||||
let dist_a = (*c_a - hint_c).length_squared() + (*l_a - hint_l).length_squared() + if *r_a > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_b = (*c_b - hint_c).length_squared() + (*l_b - hint_l).length_squared() + if *r_b > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_a = (*c_a - hint_c).length_squared()
|
||||
+ (*l_a - hint_l).length_squared()
|
||||
+ if *r_a > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_b = (*c_b - hint_c).length_squared()
|
||||
+ (*l_b - hint_l).length_squared()
|
||||
+ if *r_b > 50000.0 { 1e8 } else { 0.0 };
|
||||
dist_a.total_cmp(&dist_b)
|
||||
})
|
||||
.map(|(pt_c, pt_l, _)| (pt_c, pt_l))
|
||||
@@ -873,8 +887,12 @@ pub fn solve_ellipse_line_ccp(
|
||||
let t_a = i as f64 * dt;
|
||||
let t_b = (i + 1) as f64 * dt;
|
||||
|
||||
let Some((res_a, _, _, _)) = eval_t(t_a) else { continue; };
|
||||
let Some((res_b, _, _, _)) = eval_t(t_b) else { continue; };
|
||||
let Some((res_a, _, _, _)) = eval_t(t_a) else {
|
||||
continue;
|
||||
};
|
||||
let Some((res_b, _, _, _)) = eval_t(t_b) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if res_a.signum() != res_b.signum() {
|
||||
let mut lo = t_a;
|
||||
@@ -882,7 +900,9 @@ pub fn solve_ellipse_line_ccp(
|
||||
let mut best_t = (lo + hi) * 0.5;
|
||||
for _ in 0..16 {
|
||||
let mid = (lo + hi) * 0.5;
|
||||
let Some((res_m, _, _, _)) = eval_t(mid) else { break; };
|
||||
let Some((res_m, _, _, _)) = eval_t(mid) else {
|
||||
break;
|
||||
};
|
||||
best_t = mid;
|
||||
if res_m.abs() < 1e-8 {
|
||||
break;
|
||||
@@ -905,8 +925,12 @@ pub fn solve_ellipse_line_ccp(
|
||||
candidates
|
||||
.into_iter()
|
||||
.min_by(|(e_a, l_a, r_a), (e_b, l_b, r_b)| {
|
||||
let dist_a = (*e_a - hint_e).length_squared() + (*l_a - hint_l).length_squared() + if *r_a > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_b = (*e_b - hint_e).length_squared() + (*l_b - hint_l).length_squared() + if *r_b > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_a = (*e_a - hint_e).length_squared()
|
||||
+ (*l_a - hint_l).length_squared()
|
||||
+ if *r_a > 50000.0 { 1e8 } else { 0.0 };
|
||||
let dist_b = (*e_b - hint_e).length_squared()
|
||||
+ (*l_b - hint_l).length_squared()
|
||||
+ if *r_b > 50000.0 { 1e8 } else { 0.0 };
|
||||
dist_a.total_cmp(&dist_b)
|
||||
})
|
||||
.map(|(pt_e, pt_l, _)| (pt_e, pt_l))
|
||||
@@ -4140,7 +4164,10 @@ mod acad_flow_tests {
|
||||
let preview = cmd.on_preview_wires_with_tangent(DVec3::new(2.0, 2.0, 0.0), None);
|
||||
assert!(!preview.is_empty(), "Live preview must generate arc wire");
|
||||
assert!(
|
||||
preview[0].tangent_geoms.iter().any(|g| matches!(g, TangentGeom::Arc { .. })),
|
||||
preview[0]
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.any(|g| matches!(g, TangentGeom::Arc { .. })),
|
||||
"Preview must produce an analytical arc geometry"
|
||||
);
|
||||
|
||||
@@ -4178,7 +4205,10 @@ mod acad_flow_tests {
|
||||
let preview = cmd.on_preview_wires_with_tangent(DVec3::new(1.0, 0.0, 0.0), Some(line));
|
||||
assert!(!preview.is_empty(), "Live preview must generate arc wire");
|
||||
assert!(
|
||||
preview[0].tangent_geoms.iter().any(|g| matches!(g, TangentGeom::Arc { .. })),
|
||||
preview[0]
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.any(|g| matches!(g, TangentGeom::Arc { .. })),
|
||||
"Preview must produce an analytical arc geometry"
|
||||
);
|
||||
|
||||
@@ -4212,7 +4242,10 @@ mod acad_flow_tests {
|
||||
let preview = cmd.on_preview_wires_with_tangent(DVec3::new(2.0, 2.0, 0.0), None);
|
||||
assert!(!preview.is_empty(), "Live preview must exist");
|
||||
assert!(
|
||||
preview[0].tangent_geoms.iter().any(|g| matches!(g, TangentGeom::Arc { .. })),
|
||||
preview[0]
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.any(|g| matches!(g, TangentGeom::Arc { .. })),
|
||||
"Preview must produce an arc, not a straight fallback line"
|
||||
);
|
||||
|
||||
@@ -4240,7 +4273,10 @@ mod acad_flow_tests {
|
||||
let preview = cmd.on_preview_wires_with_tangent(DVec3::new(1.0, 0.0, 0.0), Some(line));
|
||||
assert!(!preview.is_empty());
|
||||
assert!(
|
||||
preview[0].tangent_geoms.iter().any(|g| matches!(g, TangentGeom::Arc { .. })),
|
||||
preview[0]
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.any(|g| matches!(g, TangentGeom::Arc { .. })),
|
||||
"Preview must produce an arc, not a straight fallback line"
|
||||
);
|
||||
|
||||
@@ -4266,7 +4302,10 @@ mod acad_flow_tests {
|
||||
// Pick 1: Tangent on Line 1 (y = 0)
|
||||
cmd.on_point_with_tangent(DVec3::new(2.0, 0.0, 0.0), Some(line1));
|
||||
// Pick 2: Point in first quadrant
|
||||
cmd.on_point_with_tangent(DVec3::new(1.0 - (0.5_f64).sqrt(), 1.0 - (0.5_f64).sqrt(), 0.0), None);
|
||||
cmd.on_point_with_tangent(
|
||||
DVec3::new(1.0 - (0.5_f64).sqrt(), 1.0 - (0.5_f64).sqrt(), 0.0),
|
||||
None,
|
||||
);
|
||||
// Pick 3: Tangent on Line 2 (x = 0)
|
||||
let r3 = cmd.on_point_with_tangent(DVec3::new(0.0, 2.0, 0.0), Some(line2));
|
||||
let arc = committed_arc(r3.expect("two-line arc command must commit"));
|
||||
|
||||
@@ -264,7 +264,9 @@ impl HatchCommand {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finish_selected(mut self) -> CmdResult { self.on_enter() }
|
||||
pub fn finish_selected(mut self) -> CmdResult {
|
||||
self.on_enter()
|
||||
}
|
||||
|
||||
fn set_object_selection(&mut self, handles: Vec<Handle>) {
|
||||
let mut segments = Vec::new();
|
||||
|
||||
@@ -65,7 +65,9 @@ impl CadCommand for DistCommand {
|
||||
let ctx = unit_context();
|
||||
let sign = if ctx.angdir_cw { -1.0 } else { 1.0 };
|
||||
let turn = std::f64::consts::TAU;
|
||||
let angle_xy = (sign * (dy.atan2(dx) - ctx.angbase)).rem_euclid(turn).to_degrees();
|
||||
let angle_xy = (sign * (dy.atan2(dx) - ctx.angbase))
|
||||
.rem_euclid(turn)
|
||||
.to_degrees();
|
||||
// Angle from XY plane toward Z (elevation angle)
|
||||
let dist_xy = dx.hypot(dy);
|
||||
let angle_z = dz.atan2(dist_xy).to_degrees();
|
||||
|
||||
@@ -34,7 +34,10 @@ fn associative_option(on: bool) -> crate::command::CmdOption {
|
||||
|
||||
/// Typed `AS` / `ASSOCIATIVE` flips the Associative option.
|
||||
fn is_associative_keyword(text: &str) -> bool {
|
||||
matches!(text.trim().to_ascii_uppercase().as_str(), "AS" | "ASSOCIATIVE" | "_AS")
|
||||
matches!(
|
||||
text.trim().to_ascii_uppercase().as_str(),
|
||||
"AS" | "ASSOCIATIVE" | "_AS"
|
||||
)
|
||||
}
|
||||
|
||||
/// Associative arrays are built in world axes; a rotated working plane
|
||||
|
||||
@@ -56,7 +56,12 @@ impl CadCommand for ArrayEditCommand {
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
let array = self.array?;
|
||||
match text.trim().trim_start_matches('_').to_ascii_uppercase().as_str() {
|
||||
match text
|
||||
.trim()
|
||||
.trim_start_matches('_')
|
||||
.to_ascii_uppercase()
|
||||
.as_str()
|
||||
{
|
||||
"S" | "SOURCE" => Some(CmdResult::Dispatch(format!(
|
||||
"ARRAYEDIT_SOURCE:{}",
|
||||
array.value()
|
||||
@@ -75,4 +80,6 @@ impl CadCommand for ArrayEditCommand {
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["ARRAYEDIT"] });
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["ARRAYEDIT"]
|
||||
});
|
||||
@@ -1980,41 +1980,37 @@ impl CadCommand for FilletCommand {
|
||||
FilletEntity::Line(_) | FilletEntity::Arc(_),
|
||||
EntityType::LwPolyline(moved),
|
||||
other,
|
||||
) if !same_entity => {
|
||||
join_poly_fillet(
|
||||
poly,
|
||||
*seg_idx,
|
||||
moved,
|
||||
other,
|
||||
maybe_arc.as_ref(),
|
||||
)
|
||||
.map(|poly| {
|
||||
vec![
|
||||
(h1, vec![EntityType::LwPolyline(poly)]),
|
||||
(handle, Vec::new()),
|
||||
]
|
||||
})
|
||||
}
|
||||
) if !same_entity => join_poly_fillet(
|
||||
poly,
|
||||
*seg_idx,
|
||||
moved,
|
||||
other,
|
||||
maybe_arc.as_ref(),
|
||||
)
|
||||
.map(|poly| {
|
||||
vec![
|
||||
(h1, vec![EntityType::LwPolyline(poly)]),
|
||||
(handle, Vec::new()),
|
||||
]
|
||||
}),
|
||||
(
|
||||
FilletEntity::Line(_) | FilletEntity::Arc(_),
|
||||
FilletEntity::LwPoly { poly, seg_idx, .. },
|
||||
other,
|
||||
EntityType::LwPolyline(moved),
|
||||
) if !same_entity => {
|
||||
join_poly_fillet(
|
||||
poly,
|
||||
*seg_idx,
|
||||
moved,
|
||||
other,
|
||||
maybe_arc.as_ref(),
|
||||
)
|
||||
.map(|poly| {
|
||||
vec![
|
||||
(handle, vec![EntityType::LwPolyline(poly)]),
|
||||
(h1, Vec::new()),
|
||||
]
|
||||
})
|
||||
}
|
||||
) if !same_entity => join_poly_fillet(
|
||||
poly,
|
||||
*seg_idx,
|
||||
moved,
|
||||
other,
|
||||
maybe_arc.as_ref(),
|
||||
)
|
||||
.map(|poly| {
|
||||
vec![
|
||||
(handle, vec![EntityType::LwPolyline(poly)]),
|
||||
(h1, Vec::new()),
|
||||
]
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(replacements) = joined {
|
||||
@@ -2304,8 +2300,7 @@ enum ChamferStep {
|
||||
}
|
||||
|
||||
/// The refusal a no-trim CHAMFER prints for anything but two independent Lines.
|
||||
const NO_TRIM_CHAMFER_LINES_ONLY: &str =
|
||||
"No-trim chamfer supports two independent lines only.";
|
||||
const NO_TRIM_CHAMFER_LINES_ONLY: &str = "No-trim chamfer supports two independent lines only.";
|
||||
|
||||
pub struct ChamferCommand {
|
||||
dist1: f64,
|
||||
@@ -2672,9 +2667,7 @@ impl CadCommand for ChamferCommand {
|
||||
}
|
||||
Some(EntityType::LwPolyline(p)) => {
|
||||
if !self.trim {
|
||||
return CmdResult::ReportError(
|
||||
NO_TRIM_CHAMFER_LINES_ONLY.to_string(),
|
||||
);
|
||||
return CmdResult::ReportError(NO_TRIM_CHAMFER_LINES_ONLY.to_string());
|
||||
}
|
||||
self.step = ChamferStep::SecondPoly {
|
||||
h1: handle,
|
||||
@@ -2692,9 +2685,7 @@ impl CadCommand for ChamferCommand {
|
||||
if !self.trim {
|
||||
self.step = ChamferStep::First;
|
||||
self.resume_pick = None;
|
||||
return CmdResult::ReportError(
|
||||
NO_TRIM_CHAMFER_LINES_ONLY.to_string(),
|
||||
);
|
||||
return CmdResult::ReportError(NO_TRIM_CHAMFER_LINES_ONLY.to_string());
|
||||
}
|
||||
let h1 = *h1;
|
||||
let poly = poly.clone();
|
||||
@@ -2927,7 +2918,10 @@ mod tests {
|
||||
CmdResult::ReplaceManyContinue(_)
|
||||
));
|
||||
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", "T"]);
|
||||
// The host hands the restored document back; the cache follows it.
|
||||
let mut doc = codec::CadDocument::new();
|
||||
@@ -2941,10 +2935,7 @@ mod tests {
|
||||
fn no_trim_fillet_adds_only_an_arc_and_keeps_source_handles() {
|
||||
let first = Handle::new(1);
|
||||
let second = Handle::new(2);
|
||||
let original = vec![
|
||||
line(0.0, 0.0, 10.0, 0.0, 1),
|
||||
line(0.0, 0.0, 0.0, 10.0, 2),
|
||||
];
|
||||
let original = vec![line(0.0, 0.0, 10.0, 0.0, 1), line(0.0, 0.0, 0.0, 10.0, 2)];
|
||||
let mut command = FilletCommand::new(1.0, original);
|
||||
command.trim = false;
|
||||
assert!(matches!(
|
||||
@@ -2987,8 +2978,14 @@ mod tests {
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(source_lines[0], (Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0)));
|
||||
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[0],
|
||||
(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.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]
|
||||
@@ -3003,8 +3000,15 @@ mod tests {
|
||||
));
|
||||
|
||||
let mut multi = ChamferCommand::new(1.0, lines());
|
||||
assert!(matches!(multi.on_text_input("M"), Some(CmdResult::NeedPoint)));
|
||||
assert_eq!(keywords(&multi), ["D", "N"], "Multiple is not offered twice");
|
||||
assert!(matches!(
|
||||
multi.on_text_input("M"),
|
||||
Some(CmdResult::NeedPoint)
|
||||
));
|
||||
assert_eq!(
|
||||
keywords(&multi),
|
||||
["D", "N"],
|
||||
"Multiple is not offered twice"
|
||||
);
|
||||
multi.on_entity_pick(Handle::new(1), DVec3::new(5.0, 0.0, 0.0));
|
||||
match multi.on_entity_pick(Handle::new(2), DVec3::new(0.0, 5.0, 0.0)) {
|
||||
CmdResult::ReplaceManyContinue(replacements) => {
|
||||
@@ -3015,17 +3019,17 @@ mod tests {
|
||||
_ => panic!("multiple mode should keep the command active"),
|
||||
}
|
||||
assert_eq!(keywords(&multi), ["U", "D", "N"]);
|
||||
assert!(matches!(multi.on_text_input("U"), Some(CmdResult::UndoDocument)));
|
||||
assert!(matches!(
|
||||
multi.on_text_input("U"),
|
||||
Some(CmdResult::UndoDocument)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_trim_chamfer_adds_only_the_cut_line_and_keeps_source_handles() {
|
||||
let first = Handle::new(1);
|
||||
let second = Handle::new(2);
|
||||
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)];
|
||||
|
||||
let mut single = ChamferCommand::new(1.0, lines());
|
||||
single.dist2 = 1.0;
|
||||
@@ -3058,7 +3062,10 @@ mod tests {
|
||||
|
||||
let mut multi = ChamferCommand::new(1.0, lines());
|
||||
multi.dist2 = 1.0;
|
||||
assert!(matches!(multi.on_text_input("M"), Some(CmdResult::NeedPoint)));
|
||||
assert!(matches!(
|
||||
multi.on_text_input("M"),
|
||||
Some(CmdResult::NeedPoint)
|
||||
));
|
||||
multi.trim = false;
|
||||
assert!(matches!(
|
||||
multi.on_entity_pick(first, DVec3::new(5.0, 0.0, 0.0)),
|
||||
@@ -3082,12 +3089,18 @@ mod tests {
|
||||
fn no_trim_chamfer_refuses_polyline_corners() {
|
||||
for token in ["N", "NOTRIM", "NO TRIM", "TRIM=FALSE"] {
|
||||
let mut command = ChamferCommand::new(1.0, Vec::new());
|
||||
assert!(matches!(command.on_text_input(token), Some(CmdResult::NeedPoint)));
|
||||
assert!(matches!(
|
||||
command.on_text_input(token),
|
||||
Some(CmdResult::NeedPoint)
|
||||
));
|
||||
assert!(!command.trim, "{token}");
|
||||
}
|
||||
for token in ["T", "TRIM", "TRIM=TRUE"] {
|
||||
let mut command = ChamferCommand::new(1.0, Vec::new());
|
||||
assert!(matches!(command.on_text_input(token), Some(CmdResult::NeedPoint)));
|
||||
assert!(matches!(
|
||||
command.on_text_input(token),
|
||||
Some(CmdResult::NeedPoint)
|
||||
));
|
||||
assert!(command.trim, "{token}");
|
||||
}
|
||||
|
||||
|
||||
@@ -473,7 +473,10 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec<Handle>,
|
||||
let (Some(pa), Some(pb)) = (&ends[a], &ends[b]) else {
|
||||
continue;
|
||||
};
|
||||
if pa.iter().any(|p| pb.iter().any(|q| p.distance(*q) <= JOIN_EPS)) {
|
||||
if pa
|
||||
.iter()
|
||||
.any(|p| pb.iter().any(|q| p.distance(*q) <= JOIN_EPS))
|
||||
{
|
||||
let (ra, rb) = (root(&mut group, a), root(&mut group, b));
|
||||
group[ra] = rb;
|
||||
}
|
||||
@@ -483,7 +486,10 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec<Handle>,
|
||||
Default::default();
|
||||
for (index, entity) in entities.iter().enumerate() {
|
||||
if ends[index].is_some() {
|
||||
chains.entry(root(&mut group, index)).or_default().push(*entity);
|
||||
chains
|
||||
.entry(root(&mut group, index))
|
||||
.or_default()
|
||||
.push(*entity);
|
||||
}
|
||||
}
|
||||
let (mut removed, mut merged) = (Vec::new(), Vec::new());
|
||||
@@ -797,8 +803,10 @@ mod join_tests {
|
||||
line(o, o + size, o, o, 0.0),
|
||||
]
|
||||
};
|
||||
let lines: Vec<EntityType> =
|
||||
square(0.0, 10.0).into_iter().chain(square(2.0, 6.0)).collect();
|
||||
let lines: Vec<EntityType> = square(0.0, 10.0)
|
||||
.into_iter()
|
||||
.chain(square(2.0, 6.0))
|
||||
.collect();
|
||||
let lone = line(50.0, 0.0, 60.0, 0.0, 0.0);
|
||||
let mut pairs: Vec<(Handle, &EntityType)> = lines
|
||||
.iter()
|
||||
|
||||
@@ -302,7 +302,10 @@ pub fn nesting_chain(
|
||||
return chain;
|
||||
};
|
||||
for _ in 0..16 {
|
||||
let level = chain.last().expect("chain starts with the top level").clone();
|
||||
let level = chain
|
||||
.last()
|
||||
.expect("chain starts with the top level")
|
||||
.clone();
|
||||
let nearest = level_hits(document, &level)
|
||||
.into_iter()
|
||||
.filter_map(|(h, paths)| paths_distance(&paths, point).map(|d| (h, d)))
|
||||
@@ -525,7 +528,8 @@ impl RefEditOptionsCommand {
|
||||
if crossing {
|
||||
points().any(inside)
|
||||
|| paths.iter().any(|path| {
|
||||
path.windows(2).any(|s| segment_meets_box(s[0], s[1], lo, hi))
|
||||
path.windows(2)
|
||||
.any(|s| segment_meets_box(s[0], s[1], lo, hi))
|
||||
})
|
||||
} else {
|
||||
points().next().is_some() && points().all(inside)
|
||||
@@ -860,7 +864,6 @@ pub fn inverse_placement(t: &codec::types::Transform) -> Option<codec::types::Tr
|
||||
Some(codec::types::Transform::from_matrix(out))
|
||||
}
|
||||
|
||||
|
||||
/// Apply the session's forward transform to a definition-local entity so it
|
||||
/// appears at its correct world-space position.
|
||||
pub fn apply_insert_transform(entity: &mut EntityType, session: &RefEditSession) {
|
||||
|
||||
@@ -1780,7 +1780,10 @@ fn mode_prompt(command: &str) -> String {
|
||||
|
||||
fn mode_options() -> Vec<crate::command::CmdOption> {
|
||||
use crate::command::CmdOption;
|
||||
vec![CmdOption::new("Quick", "Q"), CmdOption::new("Standard", "S")]
|
||||
vec![
|
||||
CmdOption::new("Quick", "Q"),
|
||||
CmdOption::new("Standard", "S"),
|
||||
]
|
||||
}
|
||||
|
||||
/// The mOde option and its answer, shared by TRIM and EXTEND. Returns the
|
||||
@@ -5047,7 +5050,11 @@ mod tests {
|
||||
let geos = build_geos(&all);
|
||||
let curve_line = entity_curve_xy(&EntityType::Line(l1.clone())).unwrap();
|
||||
let ts_line = cut_params(&curve_line, Handle::new(1), &geos);
|
||||
assert!(!ts_line.is_empty(), "line must have cut point where tangent arc touches it: ts={:?}", ts_line);
|
||||
assert!(
|
||||
!ts_line.is_empty(),
|
||||
"line must have cut point where tangent arc touches it: ts={:?}",
|
||||
ts_line
|
||||
);
|
||||
|
||||
let curve_arc = entity_curve_xy(&EntityType::Arc(a.clone())).unwrap();
|
||||
let ts_arc = cut_params(&curve_arc, Handle::new(2), &geos);
|
||||
@@ -5056,9 +5063,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_trim_arc_tangent_to_two_lines() {
|
||||
use codec::entities::{Arc as CadArc, Line as LineEnt};
|
||||
use crate::command::{TangentObject, WorkingPlane};
|
||||
use crate::modules::draw::draw::arc::{solve_arc_3_inputs, ArcPick};
|
||||
use codec::entities::{Arc as CadArc, Line as LineEnt};
|
||||
|
||||
let mut l1 = LineEnt::new();
|
||||
l1.common.handle = Handle::new(1);
|
||||
@@ -5082,9 +5089,15 @@ mod tests {
|
||||
// Middle point: (1 - sqrt(0.5), 1 - sqrt(0.5)) -> radius ~ 1
|
||||
let mid = DVec3::new(1.0 - (0.5_f64).sqrt(), 1.0 - (0.5_f64).sqrt(), 0.0);
|
||||
let sol = solve_arc_3_inputs(
|
||||
ArcPick::Tangent { target: t1, hit: DVec3::new(2.0, 0.0, 0.0) },
|
||||
ArcPick::Tangent {
|
||||
target: t1,
|
||||
hit: DVec3::new(2.0, 0.0, 0.0),
|
||||
},
|
||||
ArcPick::Point(mid),
|
||||
ArcPick::Tangent { target: t2, hit: DVec3::new(0.0, 2.0, 0.0) },
|
||||
ArcPick::Tangent {
|
||||
target: t2,
|
||||
hit: DVec3::new(0.0, 2.0, 0.0),
|
||||
},
|
||||
WorkingPlane::default(),
|
||||
);
|
||||
|
||||
@@ -5135,9 +5148,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_trim_arc_tangent_angled_lines() {
|
||||
use codec::entities::{Arc as CadArc, Line as LineEnt};
|
||||
use crate::command::{TangentObject, WorkingPlane};
|
||||
use crate::modules::draw::draw::arc::{solve_arc_3_inputs, ArcPick};
|
||||
use codec::entities::{Arc as CadArc, Line as LineEnt};
|
||||
|
||||
// Two lines meeting at (0, 0): one along (cos(20 deg), sin(20 deg)), other along (cos(80 deg), sin(80 deg))
|
||||
let a1 = 20.0_f64.to_radians();
|
||||
@@ -5166,12 +5179,22 @@ mod tests {
|
||||
|
||||
// Midpoint between points at distance 20 on the bisector
|
||||
let bisector_angle = (a1 + a2) * 0.5;
|
||||
let mid = DVec3::new(15.0 * bisector_angle.cos(), 15.0 * bisector_angle.sin(), 0.0);
|
||||
let mid = DVec3::new(
|
||||
15.0 * bisector_angle.cos(),
|
||||
15.0 * bisector_angle.sin(),
|
||||
0.0,
|
||||
);
|
||||
|
||||
let sol = solve_arc_3_inputs(
|
||||
ArcPick::Tangent { target: t1, hit: DVec3::new(20.0 * dir1.x, 20.0 * dir1.y, 0.0) },
|
||||
ArcPick::Tangent {
|
||||
target: t1,
|
||||
hit: DVec3::new(20.0 * dir1.x, 20.0 * dir1.y, 0.0),
|
||||
},
|
||||
ArcPick::Point(mid),
|
||||
ArcPick::Tangent { target: t2, hit: DVec3::new(20.0 * dir2.x, 20.0 * dir2.y, 0.0) },
|
||||
ArcPick::Tangent {
|
||||
target: t2,
|
||||
hit: DVec3::new(20.0 * dir2.x, 20.0 * dir2.y, 0.0),
|
||||
},
|
||||
WorkingPlane::default(),
|
||||
);
|
||||
|
||||
@@ -5199,8 +5222,16 @@ mod tests {
|
||||
println!("angled ts_l2: {:?}", ts_l2);
|
||||
|
||||
// We expect l1 to have at least 2 cuts: the intersection with l2 at (0,0) and the tangent point with arc!
|
||||
assert!(ts_l1.len() >= 2, "Line 1 must have intersection and tangent cut: got {:?}", ts_l1);
|
||||
assert!(ts_l2.len() >= 2, "Line 2 must have intersection and tangent cut: got {:?}", ts_l2);
|
||||
assert!(
|
||||
ts_l1.len() >= 2,
|
||||
"Line 1 must have intersection and tangent cut: got {:?}",
|
||||
ts_l1
|
||||
);
|
||||
assert!(
|
||||
ts_l2.len() >= 2,
|
||||
"Line 2 must have intersection and tangent cut: got {:?}",
|
||||
ts_l2
|
||||
);
|
||||
|
||||
// Trimming l1 arm past the tangent point
|
||||
let click_l1 = 40.0 * dir1;
|
||||
@@ -5212,7 +5243,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_kernel_intersect_tangency() {
|
||||
use std::f64::consts::FRAC_PI_2;
|
||||
let line = Curve::Line(KernelLine { start: [-10.0, 0.0], end: [10.0, 0.0] });
|
||||
let line = Curve::Line(KernelLine {
|
||||
start: [-10.0, 0.0],
|
||||
end: [10.0, 0.0],
|
||||
});
|
||||
|
||||
// Case 1: Exact tangency at interior of arc
|
||||
let arc_exact = Curve::Arc(KernelArc {
|
||||
@@ -5275,14 +5309,18 @@ mod tests {
|
||||
println!("hits_end (-PI/2): {:?}", hits_end);
|
||||
|
||||
// Case 7: Large coordinates (e.g. 5000, 5005)
|
||||
let line_large = Curve::Line(KernelLine { start: [4990.0, 5000.0], end: [5010.0, 5000.0] });
|
||||
let line_large = Curve::Line(KernelLine {
|
||||
start: [4990.0, 5000.0],
|
||||
end: [5010.0, 5000.0],
|
||||
});
|
||||
let arc_large = Curve::Arc(KernelArc {
|
||||
centre: [5000.0, 5005.0],
|
||||
radius: 5.0,
|
||||
start_angle: -FRAC_PI_2,
|
||||
end_angle: 0.0,
|
||||
});
|
||||
let hits_large = kernel_intersect(&line_large, &arc_large, KernelTolerance::new(CUT_TOLERANCE));
|
||||
let hits_large =
|
||||
kernel_intersect(&line_large, &arc_large, KernelTolerance::new(CUT_TOLERANCE));
|
||||
println!("hits_large: {:?}", hits_large);
|
||||
|
||||
// Test with cut_params:
|
||||
@@ -5300,9 +5338,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_arc_tangent_line_precision_sweep() {
|
||||
use codec::entities::{Arc as CadArc, Line as LineEnt};
|
||||
use crate::command::{TangentObject, WorkingPlane};
|
||||
use crate::modules::draw::draw::arc::{solve_arc_3_inputs, ArcPick};
|
||||
use codec::entities::{Arc as CadArc, Line as LineEnt};
|
||||
|
||||
let mut max_dist = 0.0_f64;
|
||||
let mut failed_cuts = 0;
|
||||
@@ -5337,9 +5375,15 @@ mod tests {
|
||||
let mid = bisector * 30.0;
|
||||
|
||||
let sol = solve_arc_3_inputs(
|
||||
ArcPick::Tangent { target: t1, hit: DVec3::new(50.0, 0.0, 0.0) },
|
||||
ArcPick::Tangent {
|
||||
target: t1,
|
||||
hit: DVec3::new(50.0, 0.0, 0.0),
|
||||
},
|
||||
ArcPick::Point(mid),
|
||||
ArcPick::Tangent { target: t2, hit: dir2 * 50.0 },
|
||||
ArcPick::Tangent {
|
||||
target: t2,
|
||||
hit: dir2 * 50.0,
|
||||
},
|
||||
WorkingPlane::default(),
|
||||
);
|
||||
|
||||
@@ -5366,13 +5410,18 @@ mod tests {
|
||||
|
||||
if ts_l1.len() < 2 || ts_l2.len() < 2 {
|
||||
failed_cuts += 1;
|
||||
println!("FAILED at angle {angle_deg} deg: ts_l1={:?}, ts_l2={:?}", ts_l1, ts_l2);
|
||||
println!(
|
||||
"FAILED at angle {angle_deg} deg: ts_l1={:?}, ts_l2={:?}",
|
||||
ts_l1, ts_l2
|
||||
);
|
||||
}
|
||||
|
||||
// Check distance of arc endpoints to line
|
||||
let ep1 = [center.x + radius * sa.cos(), center.y + radius * sa.sin()];
|
||||
let d1 = (ep1[1] - 0.0).abs(); // distance to l1 (y=0)
|
||||
if d1 > max_dist { max_dist = d1; }
|
||||
if d1 > max_dist {
|
||||
max_dist = d1;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("Sweep results: failed_cuts={failed_cuts}, max_dist={max_dist:e}");
|
||||
|
||||
@@ -468,9 +468,9 @@ impl CadCommand for DashBlockCommand {
|
||||
BlockStep::Base => {
|
||||
CmdResult::ReportError("Point or option keyword required.".to_string())
|
||||
}
|
||||
BlockStep::Annotative | BlockStep::Orient | BlockStep::Mode => self
|
||||
.on_text_input("")
|
||||
.unwrap_or(CmdResult::NeedPoint),
|
||||
BlockStep::Annotative | BlockStep::Orient | BlockStep::Mode => {
|
||||
self.on_text_input("").unwrap_or(CmdResult::NeedPoint)
|
||||
}
|
||||
BlockStep::Objects => {
|
||||
let handles = std::mem::take(&mut self.handles);
|
||||
self.create(handles)
|
||||
@@ -801,8 +801,10 @@ impl DashBeditCommand {
|
||||
if lines.is_empty() {
|
||||
"No blocks found.".to_string()
|
||||
} else {
|
||||
lines.join("
|
||||
")
|
||||
lines.join(
|
||||
"
|
||||
",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +206,10 @@ impl AdjustCommand {
|
||||
fn apply(&self, prop: Prop, value: u8) -> CmdResult {
|
||||
if let Subject::Underlay { .. } = self.subject {
|
||||
if let Ok(mut last) = LAST_UNDERLAY.lock() {
|
||||
last.0 = UNDERLAY_OPTIONS.iter().position(|p| *p == prop).unwrap_or(0);
|
||||
last.0 = UNDERLAY_OPTIONS
|
||||
.iter()
|
||||
.position(|p| *p == prop)
|
||||
.unwrap_or(0);
|
||||
match prop {
|
||||
Prop::Fade => last.1 = value,
|
||||
Prop::Contrast => last.2 = value,
|
||||
@@ -341,8 +344,12 @@ impl CadCommand for AdjustCommand {
|
||||
t!("Value must be an integer between 0 and 100.").into_owned(),
|
||||
)),
|
||||
(None, Subject::Underlay { .. }) => Some(CmdResult::ReportError(
|
||||
crate::tf!("Requires an integer between {min} and {max}.", min = 0, max = 100)
|
||||
.into_owned(),
|
||||
crate::tf!(
|
||||
"Requires an integer between {min} and {max}.",
|
||||
min = 0,
|
||||
max = 100
|
||||
)
|
||||
.into_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -359,5 +366,11 @@ impl CadCommand for AdjustCommand {
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["IMAGEADJUST", "-IMAGEADJUST", "PDFADJUST", "DWFADJUST", "DGNADJUST"]
|
||||
names: &[
|
||||
"IMAGEADJUST",
|
||||
"-IMAGEADJUST",
|
||||
"PDFADJUST",
|
||||
"DWFADJUST",
|
||||
"DGNADJUST"
|
||||
]
|
||||
});
|
||||
@@ -292,7 +292,9 @@ impl CadCommand for MvsetupCommand {
|
||||
let count = if text.is_empty() {
|
||||
2
|
||||
} else {
|
||||
text.parse::<u32>().ok().filter(|count| (1..=100).contains(count))?
|
||||
text.parse::<u32>()
|
||||
.ok()
|
||||
.filter(|count| (1..=100).contains(count))?
|
||||
};
|
||||
if self.step == Step::Columns {
|
||||
self.columns = count;
|
||||
@@ -325,9 +327,7 @@ impl CadCommand for MvsetupCommand {
|
||||
factor: self.paper_units / model_units,
|
||||
});
|
||||
}
|
||||
Step::FirstCorner | Step::SecondCorner | Step::Delete | Step::Viewports => {
|
||||
return None
|
||||
}
|
||||
Step::FirstCorner | Step::SecondCorner | Step::Delete | Step::Viewports => return None,
|
||||
}
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
@@ -357,7 +357,10 @@ impl CadCommand for MvsetupCommand {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
CmdResult::ReplaceManyContinue(
|
||||
erased.into_iter().map(|handle| (handle, Vec::new())).collect(),
|
||||
erased
|
||||
.into_iter()
|
||||
.map(|handle| (handle, Vec::new()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
Step::Viewports if self.picked.is_empty() => CmdResult::Cancel,
|
||||
@@ -371,4 +374,6 @@ impl CadCommand for MvsetupCommand {
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["MVSETUP"] });
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["MVSETUP"]
|
||||
});
|
||||
@@ -74,7 +74,12 @@ impl CadCommand for CompareCommand {
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
match text.trim().trim_start_matches('_').to_ascii_uppercase().as_str() {
|
||||
match text
|
||||
.trim()
|
||||
.trim_start_matches('_')
|
||||
.to_ascii_uppercase()
|
||||
.as_str()
|
||||
{
|
||||
"" | "N" | "NEXT" => Some(self.show(true)),
|
||||
"P" | "PREVIOUS" => Some(self.show(false)),
|
||||
"X" | "EXIT" => Some(CmdResult::Cancel),
|
||||
@@ -101,4 +106,6 @@ impl CadCommand for CompareCommand {
|
||||
}
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["COMPARE"] });
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &["COMPARE"]
|
||||
});
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
mod cascade;
|
||||
mod count_palette;
|
||||
pub mod dview;
|
||||
mod file_tabs;
|
||||
mod graphic_attributes_palette;
|
||||
mod layout_tabs;
|
||||
pub mod dview;
|
||||
pub mod limits;
|
||||
mod orbit;
|
||||
mod ortho;
|
||||
@@ -19,8 +19,8 @@ mod tile_horiz;
|
||||
mod tile_vert;
|
||||
mod tool_palettes;
|
||||
pub mod ucs_cmd;
|
||||
pub mod view_cmd;
|
||||
mod ucs_icon;
|
||||
pub mod view_cmd;
|
||||
mod view_front;
|
||||
mod view_iso;
|
||||
mod view_right;
|
||||
|
||||
@@ -96,7 +96,11 @@ impl CadCommand for UcsCommand {
|
||||
[origin, on_x] => {
|
||||
let x = *on_x - *origin;
|
||||
let d = pt - *origin;
|
||||
(*origin, x, d - x * (d.dot(x) / x.length_squared().max(1.0e-300)))
|
||||
(
|
||||
*origin,
|
||||
x,
|
||||
d - x * (d.dot(x) / x.length_squared().max(1.0e-300)),
|
||||
)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
@@ -139,7 +143,8 @@ impl CadCommand for UcsCommand {
|
||||
"ELIMINAR" | "E" => "DELETE",
|
||||
"3" | "3PUNTOS" | "3 PUNTOS" => "3POINT",
|
||||
value => value,
|
||||
}.to_owned();
|
||||
}
|
||||
.to_owned();
|
||||
if Self::is_zero_arg(&up) {
|
||||
Some(CmdResult::Dispatch(format!("UCS {up}")))
|
||||
} else if Self::takes_value(&up) || Self::takes_point(&up) {
|
||||
@@ -225,13 +230,19 @@ mod tests {
|
||||
#[test]
|
||||
fn bare_ucs_accepts_origin_then_x_then_xy_without_option() {
|
||||
let mut cmd = UcsCommand::new();
|
||||
assert!(matches!(cmd.on_point(DVec3::new(10.0, 20.0, 0.0)), CmdResult::NeedPoint));
|
||||
assert!(matches!(
|
||||
cmd.on_point(DVec3::new(10.0, 20.0, 0.0)),
|
||||
CmdResult::NeedPoint
|
||||
));
|
||||
assert_eq!(cmd.points.len(), 1);
|
||||
assert!(matches!(cmd.on_point(DVec3::new(11.0, 20.0, 0.0)), CmdResult::NeedPoint));
|
||||
assert!(matches!(
|
||||
cmd.on_point(DVec3::new(11.0, 20.0, 0.0)),
|
||||
CmdResult::NeedPoint
|
||||
));
|
||||
match cmd.on_point(DVec3::new(10.0, 21.0, 0.0)) {
|
||||
CmdResult::Dispatch(command) => assert_eq!(
|
||||
command, "UCS 3POINTW 10,20,0|11,20,0|10,21,0"
|
||||
),
|
||||
CmdResult::Dispatch(command) => {
|
||||
assert_eq!(command, "UCS 3POINTW 10,20,0|11,20,0|10,21,0")
|
||||
}
|
||||
_ => panic!("three clicks must dispatch the new UCS"),
|
||||
}
|
||||
}
|
||||
@@ -240,7 +251,10 @@ mod tests {
|
||||
fn initial_coordinate_is_delegated_to_point_pipeline() {
|
||||
let mut cmd = UcsCommand::new();
|
||||
assert!(cmd.on_text_input("10,20,0").is_none());
|
||||
assert!(matches!(cmd.on_point(DVec3::new(10.0, 20.0, 0.0)), CmdResult::NeedPoint));
|
||||
assert!(matches!(
|
||||
cmd.on_point(DVec3::new(10.0, 20.0, 0.0)),
|
||||
CmdResult::NeedPoint
|
||||
));
|
||||
assert_eq!(cmd.points.len(), 1);
|
||||
}
|
||||
|
||||
@@ -262,7 +276,10 @@ mod tests {
|
||||
assert!(matches!(result, Some(CmdResult::Dispatch(s)) if s == "UCS WORLD"));
|
||||
}
|
||||
let mut cmd = UcsCommand::new();
|
||||
assert!(matches!(cmd.on_text_input("Guardar"), Some(CmdResult::NeedPoint)));
|
||||
assert!(matches!(
|
||||
cmd.on_text_input("Guardar"),
|
||||
Some(CmdResult::NeedPoint)
|
||||
));
|
||||
let result = cmd.on_text_input("MiPlano");
|
||||
assert!(matches!(result, Some(CmdResult::Dispatch(s)) if s == "UCS SAVE MiPlano"));
|
||||
}
|
||||
|
||||
+124
-33
@@ -33,8 +33,9 @@ pub fn set_ucs_view(on: bool) {
|
||||
}
|
||||
|
||||
/// The standard orthographic and isometric view presets.
|
||||
pub const PRESETS: [&str; 10] =
|
||||
["TOP", "BOTTOM", "FRONT", "BACK", "LEFT", "RIGHT", "SWISO", "SEISO", "NEISO", "NWISO"];
|
||||
pub const PRESETS: [&str; 10] = [
|
||||
"TOP", "BOTTOM", "FRONT", "BACK", "LEFT", "RIGHT", "SWISO", "SEISO", "NEISO", "NWISO",
|
||||
];
|
||||
|
||||
/// The orthographic UCS an orthographic preset sets while UCSORTHO is on:
|
||||
/// its name, X and Y axes and ORTHOGRAPHIC type, as the reference sets them.
|
||||
@@ -152,17 +153,27 @@ fn keyword(input: &str, options: &[&'static str]) -> Option<&'static str> {
|
||||
.find(|word| word.chars().any(|c| c.is_ascii_uppercase()))
|
||||
.unwrap_or(option);
|
||||
let capitals: String = word.chars().filter(char::is_ascii_uppercase).collect();
|
||||
let reach = word.rfind(|c: char| c.is_ascii_uppercase()).map_or(1, |at| at + 1);
|
||||
let reach = word
|
||||
.rfind(|c: char| c.is_ascii_uppercase())
|
||||
.map_or(1, |at| at + 1);
|
||||
input == capitals || (word.to_ascii_uppercase().starts_with(&input) && input.len() >= reach)
|
||||
})
|
||||
}
|
||||
|
||||
/// The names in `names` that one of the comma-separated wildcard patterns matches.
|
||||
pub fn matching<'a>(names: impl IntoIterator<Item = &'a str>, patterns: &str) -> Vec<&'a str> {
|
||||
let patterns: Vec<&str> = patterns.split(',').map(str::trim).filter(|p| !p.is_empty()).collect();
|
||||
let patterns: Vec<&str> = patterns
|
||||
.split(',')
|
||||
.map(str::trim)
|
||||
.filter(|p| !p.is_empty())
|
||||
.collect();
|
||||
names
|
||||
.into_iter()
|
||||
.filter(|name| patterns.iter().any(|pattern| crate::io::xref_model::wildcard_match(name, pattern)))
|
||||
.filter(|name| {
|
||||
patterns
|
||||
.iter()
|
||||
.any(|pattern| crate::io::xref_model::wildcard_match(name, pattern))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -176,23 +187,36 @@ pub fn view_listing(views: &[(String, bool)]) -> String {
|
||||
}
|
||||
out.push_str("\nView name Space");
|
||||
for (name, paper) in views {
|
||||
out.push_str(&format!("\n{:<35}{}", format!("\"{name}\""), if *paper { "P" } else { "M" }));
|
||||
out.push_str(&format!(
|
||||
"\n{:<35}{}",
|
||||
format!("\"{name}\""),
|
||||
if *paper { "P" } else { "M" }
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
impl DashViewCommand {
|
||||
pub fn new(names: ViewNames) -> Self {
|
||||
Self { names, step: Step::Main }
|
||||
Self {
|
||||
names,
|
||||
step: Step::Main,
|
||||
}
|
||||
}
|
||||
|
||||
/// The command back at its settings prompt, after a setting was applied.
|
||||
pub fn settings(names: ViewNames) -> Self {
|
||||
Self { names, step: Step::Settings }
|
||||
Self {
|
||||
names,
|
||||
step: Step::Settings,
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, name: &str) -> Option<&ViewInfo> {
|
||||
self.names.views.iter().find(|view| view.name.eq_ignore_ascii_case(name))
|
||||
self.names
|
||||
.views
|
||||
.iter()
|
||||
.find(|view| view.name.eq_ignore_ascii_case(name))
|
||||
}
|
||||
|
||||
fn end(message: &str) -> CmdResult {
|
||||
@@ -200,19 +224,35 @@ impl DashViewCommand {
|
||||
}
|
||||
|
||||
fn list_views(&self, pattern: &str) -> String {
|
||||
let pattern = if pattern.trim().is_empty() { "*" } else { pattern };
|
||||
let names: Vec<&str> = self.names.views.iter().map(|view| view.name.as_str()).collect();
|
||||
let found: Vec<(String, bool)> =
|
||||
matching(names, pattern).into_iter().map(|name| (name.to_string(), false)).collect();
|
||||
let pattern = if pattern.trim().is_empty() {
|
||||
"*"
|
||||
} else {
|
||||
pattern
|
||||
};
|
||||
let names: Vec<&str> = self
|
||||
.names
|
||||
.views
|
||||
.iter()
|
||||
.map(|view| view.name.as_str())
|
||||
.collect();
|
||||
let found: Vec<(String, bool)> = matching(names, pattern)
|
||||
.into_iter()
|
||||
.map(|name| (name.to_string(), false))
|
||||
.collect();
|
||||
view_listing(&found)
|
||||
}
|
||||
|
||||
fn value_prompt(&self, setting: Setting, view: &str) -> String {
|
||||
let info = self.view(view).cloned().unwrap_or_default();
|
||||
match setting {
|
||||
Setting::Background => "Specify background type [Color/Gradient/Image/None]<None>:".into(),
|
||||
Setting::Background => {
|
||||
"Specify background type [Color/Gradient/Image/None]<None>:".into()
|
||||
}
|
||||
Setting::Categorize => {
|
||||
format!("Enter category name or * for none, or [?]: <\"{}\">", info.category)
|
||||
format!(
|
||||
"Enter category name or * for none, or [?]: <\"{}\">",
|
||||
info.category
|
||||
)
|
||||
}
|
||||
Setting::LayerSnapshot => "Enter an option [Save/Delete] <Cancel>:".into(),
|
||||
Setting::LiveSection => format!(
|
||||
@@ -229,7 +269,10 @@ impl DashViewCommand {
|
||||
/// Hand a setting to the inline handler, which applies it and starts the
|
||||
/// settings prompt again.
|
||||
fn apply(setting: Setting, view: &str, value: &str) -> CmdResult {
|
||||
CmdResult::Dispatch(format!("-VIEW >SET {}\u{1f}{view}\u{1f}{value}", setting.verb()))
|
||||
CmdResult::Dispatch(format!(
|
||||
"-VIEW >SET {}\u{1f}{view}\u{1f}{value}",
|
||||
setting.verb()
|
||||
))
|
||||
}
|
||||
|
||||
fn on_setting_value(&mut self, setting: Setting, view: String, text: &str) -> CmdResult {
|
||||
@@ -302,7 +345,11 @@ impl DashViewCommand {
|
||||
}
|
||||
|
||||
fn value_listing(&self, setting: Setting, pattern: &str) -> String {
|
||||
let pattern = if pattern.trim().is_empty() { "*" } else { pattern };
|
||||
let pattern = if pattern.trim().is_empty() {
|
||||
"*"
|
||||
} else {
|
||||
pattern
|
||||
};
|
||||
match setting {
|
||||
Setting::Categorize => {
|
||||
let mut categories: Vec<&str> = self
|
||||
@@ -401,7 +448,18 @@ impl CadCommand for DashViewCommand {
|
||||
if let Some(preset) = PRESETS.iter().find(|preset| **preset == word) {
|
||||
return Some(CmdResult::Dispatch(format!("-VIEW >PRESET {preset}")));
|
||||
}
|
||||
match keyword(t, &["?", "Delete", "Orthographic", "Restore", "Save", "sEttings", "Window"]) {
|
||||
match keyword(
|
||||
t,
|
||||
&[
|
||||
"?",
|
||||
"Delete",
|
||||
"Orthographic",
|
||||
"Restore",
|
||||
"Save",
|
||||
"sEttings",
|
||||
"Window",
|
||||
],
|
||||
) {
|
||||
Some("?") => self.step = Step::List,
|
||||
Some("Delete") => self.step = Step::Delete,
|
||||
Some("Orthographic") => self.step = Step::Orthographic,
|
||||
@@ -415,7 +473,9 @@ impl CadCommand for DashViewCommand {
|
||||
}
|
||||
Step::Orthographic => {
|
||||
match keyword(t, &["Top", "Bottom", "Front", "BAck", "Left", "Right"]) {
|
||||
Some(face) => CmdResult::Dispatch(format!("-VIEW >PRESET {}", face.to_ascii_uppercase())),
|
||||
Some(face) => {
|
||||
CmdResult::Dispatch(format!("-VIEW >PRESET {}", face.to_ascii_uppercase()))
|
||||
}
|
||||
None => {
|
||||
self.step = Step::Orthographic;
|
||||
CmdResult::ReportError("Invalid option keyword.".into())
|
||||
@@ -424,7 +484,12 @@ impl CadCommand for DashViewCommand {
|
||||
}
|
||||
Step::List => CmdResult::Measurement(self.list_views(t)),
|
||||
Step::Delete => {
|
||||
let names: Vec<&str> = self.names.views.iter().map(|view| view.name.as_str()).collect();
|
||||
let names: Vec<&str> = self
|
||||
.names
|
||||
.views
|
||||
.iter()
|
||||
.map(|view| view.name.as_str())
|
||||
.collect();
|
||||
if matching(names, t).is_empty() {
|
||||
Self::end("No matching view names found.")
|
||||
} else {
|
||||
@@ -450,11 +515,20 @@ impl CadCommand for DashViewCommand {
|
||||
self.step = Step::WindowFirst(t.to_string());
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::WindowFirst(_) | Step::WindowSecond(..) => Self::end("Invalid window specification."),
|
||||
Step::WindowFirst(_) | Step::WindowSecond(..) => {
|
||||
Self::end("Invalid window specification.")
|
||||
}
|
||||
Step::Settings => {
|
||||
match keyword(
|
||||
t,
|
||||
&["Background", "Categorize", "Layer snapshot", "live Section", "Ucs", "Visual style"],
|
||||
&[
|
||||
"Background",
|
||||
"Categorize",
|
||||
"Layer snapshot",
|
||||
"live Section",
|
||||
"Ucs",
|
||||
"Visual style",
|
||||
],
|
||||
) {
|
||||
Some("Background") => self.step = Step::SettingView(Setting::Background),
|
||||
Some("Categorize") => self.step = Step::SettingView(Setting::Categorize),
|
||||
@@ -496,7 +570,9 @@ impl CadCommand for DashViewCommand {
|
||||
CmdResult::ReportMeasurement(listing)
|
||||
}
|
||||
Step::Ucs => match keyword(t, &["Yes", "No"]) {
|
||||
Some(answer) => CmdResult::Dispatch(format!("-VIEW >UCSVIEW {}", u8::from(answer == "Yes"))),
|
||||
Some(answer) => {
|
||||
CmdResult::Dispatch(format!("-VIEW >UCSVIEW {}", u8::from(answer == "Yes")))
|
||||
}
|
||||
None => {
|
||||
self.step = Step::Ucs;
|
||||
CmdResult::ReportError("Invalid option keyword.".into())
|
||||
@@ -520,13 +596,23 @@ impl CadCommand for DashViewCommand {
|
||||
// pick well inside a viewport selects nothing, as in the
|
||||
// reference.
|
||||
let tol = self.names.sheet_pick_tolerance.max(0.0);
|
||||
let hit = self.names.sheet_viewports.iter().flatten().rev().find(|(_, [x0, y0, x1, y1])| {
|
||||
let outside = pt.x < x0 - tol || pt.x > x1 + tol || pt.y < y0 - tol || pt.y > y1 + tol;
|
||||
let inside = pt.x > x0 + tol && pt.x < x1 - tol && pt.y > y0 + tol && pt.y < y1 - tol;
|
||||
!outside && !inside
|
||||
});
|
||||
let hit = self.names.sheet_viewports.iter().flatten().rev().find(
|
||||
|(_, [x0, y0, x1, y1])| {
|
||||
let outside = pt.x < x0 - tol
|
||||
|| pt.x > x1 + tol
|
||||
|| pt.y < y0 - tol
|
||||
|| pt.y > y1 + tol;
|
||||
let inside = pt.x > x0 + tol
|
||||
&& pt.x < x1 - tol
|
||||
&& pt.y > y0 + tol
|
||||
&& pt.y < y1 - tol;
|
||||
!outside && !inside
|
||||
},
|
||||
);
|
||||
match hit {
|
||||
Some((handle, _)) => CmdResult::Dispatch(format!("-VIEW >RESTOREVP {name}\u{1f}{handle:X}")),
|
||||
Some((handle, _)) => {
|
||||
CmdResult::Dispatch(format!("-VIEW >RESTOREVP {name}\u{1f}{handle:X}"))
|
||||
}
|
||||
None => {
|
||||
self.step = Step::RestoreViewport(name);
|
||||
CmdResult::NeedPoint
|
||||
@@ -542,12 +628,17 @@ impl CadCommand for DashViewCommand {
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
match std::mem::replace(&mut self.step, Step::Main) {
|
||||
Step::Main | Step::Delete | Step::Restore | Step::RestoreViewport(_) | Step::Save | Step::WindowName => {
|
||||
CmdResult::Dispatch("-VIEW >END".into())
|
||||
}
|
||||
Step::Main
|
||||
| Step::Delete
|
||||
| Step::Restore
|
||||
| Step::RestoreViewport(_)
|
||||
| Step::Save
|
||||
| Step::WindowName => CmdResult::Dispatch("-VIEW >END".into()),
|
||||
Step::Orthographic => CmdResult::Dispatch("-VIEW >PRESET TOP".into()),
|
||||
Step::List => CmdResult::Measurement(self.list_views("*")),
|
||||
Step::WindowFirst(_) | Step::WindowSecond(..) => Self::end("Invalid window specification."),
|
||||
Step::WindowFirst(_) | Step::WindowSecond(..) => {
|
||||
Self::end("Invalid window specification.")
|
||||
}
|
||||
// Enter at the settings prompt goes back to the main prompt.
|
||||
Step::Settings => CmdResult::NeedPoint,
|
||||
Step::SettingView(_) | Step::Ucs => {
|
||||
|
||||
Reference in new issue
Block a user