style: rustfmt the tree so the CI lint check passes

This commit is contained in:
Karim Jerbi committed 2026-10-15 04:10:06 +01:00
1 parent 1317a7b4fd
commit 6025d8be44
128 files changed
+4097 -1676

No files matched your search

+9 -2
View File
@@ -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 {
+56 -17
View File
@@ -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"));
+3 -1
View File
@@ -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();
+3 -1
View File
@@ -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();
+4 -1
View File
@@ -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
+9 -2
View File
@@ -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"]
});
+68 -55
View File
@@ -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}");
}
+12 -4
View File
@@ -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()
+6 -3
View File
@@ -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) {
+68 -19
View File
@@ -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}");
+7 -5
View File
@@ -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(
"
",
)
}
}
}
+17 -4
View File
@@ -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"
]
});
+11 -6
View File
@@ -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"]
});
+9 -2
View File
@@ -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 -2
View File
@@ -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;
+26 -9
View File
@@ -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
View File
@@ -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 => {