Files
cadcraft/crates/engine/tests/it/solid_command.rs
T
2ez4gcxandClaude Opus 5.5 29584fa4c4 SOLID: draw 2D solids, chained as AutoCAD chains them (#2489)
* SOLID: draw 2D solids, chained as AutoCAD chains them

CADCraft had the SOLID entity but no command to draw it. As AutoCAD
2024 asks from AutoLISP: first, second, third point, then "fourth
point or <exit>" (Enter makes a triangle); further solids take the
previous one's last two corners and ask their third and fourth points
until Enter. Corners stay in the order given.

The command (alias SO) has a JSON form taking the points chained the
same way. solid_chains_its_corners_as_autocad pins seven measured
calls to 1e-9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* SOLID: offer it in the Draw menu

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:46:16 -04:00

116 lines
4.8 KiB
Rust

//! SOLID (2D solids) as AutoCAD 2024 draws them from AutoLISP's `command` (accoreconsole, every
//! solid read back with `entget`).
use cadcraft_engine::Session;
use cadcraft_engine::doc::EntityKind;
use cadcraft_engine::geom::Vec2;
use serde_json::json;
/// The corners of every solid, in drawing order.
fn solids(s: &Session) -> Vec<[Vec2; 4]> {
s.doc()
.unwrap()
.model
.iter()
.filter_map(|e| match &e.kind {
EntityKind::Solid(so) => Some(so.corners.map(|c| Vec2::new(c.x, c.y))),
_ => None,
})
.collect()
}
#[test]
fn solid_chains_its_corners_as_autocad() {
let p = |x: f64, y: f64| (x, y);
// (points, Enters after them, each solid as indexes into the points)
let cases: [(Vec<(f64, f64)>, usize, Vec<[usize; 4]>); 7] = [
// Enter at the fourth point: a triangle repeating its third corner.
(
vec![p(19.451648928934738, -21.25585202371201), p(34.61693931374715, -3.839951210926415), p(14.55170247613551, -36.00688291519149)],
1,
vec![[0, 1, 2, 2]],
),
// Two solids, the second on the first one's last two corners.
(
vec![
p(612336.9748522255, 2398742.6897319513),
p(612351.3507889743, 2398794.3456611084),
p(612371.7043989347, 2398771.9517326546),
p(612379.1755178814, 2398787.9912080113),
p(612308.5250187548, 2398780.014694856),
p(612356.1118879627, 2398726.474738389),
],
1,
vec![[0, 1, 2, 3], [2, 3, 4, 5]],
),
// A solid, then a triangle.
(
vec![
p(15.962585354071173, 3.3526103822549587),
p(-0.8095657933516947, 13.123790321338404),
p(-19.412229808271626, -27.60123059493223),
p(-33.66123919971652, 12.18177290955613),
p(14.002713873827737, -8.925735622823005),
],
1,
vec![[0, 1, 2, 3], [2, 3, 4, 4]],
),
// Three solids, the last a triangle.
(
vec![
p(29.3005193409103, -24.874809106460187),
p(-21.460879071052776, 35.356609837704795),
p(-31.347336242570112, -21.756568658673665),
p(-39.08169761088666, 23.588062542950155),
p(23.0763904721503, 10.219805555956789),
p(-22.701751810515756, 22.76615204356692),
p(4.338066830902321, -37.50391578060281),
],
1,
vec![[0, 1, 2, 3], [2, 3, 4, 5], [4, 5, 6, 6]],
),
// Enter twice: the triangle, then Enter at the third point ends the command.
(
vec![p(612325.6793323631, 2398757.396299568), p(612370.1009101832, 2398796.2224137546), p(612378.8199480829, 2398804.1981022176)],
2,
vec![[0, 1, 2, 2]],
),
// Corners stay in the order given (a "Z" order makes a rectangle).
(vec![p(0.0, 0.0), p(30.0, 0.0), p(0.0, 20.0), p(30.0, 20.0)], 1, vec![[0, 1, 2, 3]]),
// Enter after two points: nothing.
(vec![p(-34769.59558954922, 81960.0236700466), p(-35927.22173307072, 79264.64372697374)], 1, vec![]),
];
let same = |a: Vec2, b: (f64, f64)| (a.x - b.0).abs() <= 1e-9 * b.0.abs().max(1.0) && (a.y - b.1).abs() <= 1e-9 * b.1.abs().max(1.0);
for (pts, enters, want) in cases {
let mut s = Session::new();
let args: Vec<String> = pts.iter().map(|(x, y)| format!("'({x:?} {y:?})")).chain(std::iter::repeat_n("\"\"".to_string(), enters)).collect();
s.cmdline(&format!("(command \"_.SOLID\" {})", args.join(" "))).unwrap();
// One Enter leaves the command at "Specify third point", as AutoCAD's does (its log then
// shows the measuring routine ending it).
if s.running.is_some() {
assert!(s.prompt_text().contains("Specify third point"), "{pts:?}: {}", s.prompt_text());
s.cancel();
}
let got = solids(&s);
assert_eq!(got.len(), want.len(), "{pts:?}: {got:?}");
for (g, w) in got.iter().zip(&want) {
assert!(g.iter().zip(w).all(|(c, &i)| same(*c, pts[i])), "{pts:?}: {got:?}");
}
}
}
#[test]
fn solid_json_chains_the_same_way() {
let mut s = Session::new();
s.execute("solid", &json!({ "points": [[0, 0], [10, 0], [0, 5], [10, 5], [0, 10]] })).unwrap();
let got = solids(&s);
let v = |x: f64, y: f64| Vec2::new(x, y);
assert_eq!(got, vec![[v(0.0, 0.0), v(10.0, 0.0), v(0.0, 5.0), v(10.0, 5.0)], [v(0.0, 5.0), v(10.0, 5.0), v(0.0, 10.0), v(0.0, 10.0)]]);
assert!(s.execute("solid", &json!({ "points": [[0, 0], [1, 0]] })).is_err());
// The alias.
s.cmdline("so 20,0 30,0 20,5").unwrap();
s.cmdline("").unwrap();
s.cmdline("").unwrap();
assert_eq!(solids(&s).len(), 3);
}