mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-31 14:27:02 -06:00
* 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>
116 lines
4.8 KiB
Rust
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);
|
|
}
|