mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 10:56:51 -06:00
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
//! `angtof`).
|
||||
//!
|
||||
//! Entity lists use the DXF groups the file reader and writer use (through
|
||||
//! [`crate::cmd::file::entity_codec`]), so what `entget` shows is what a DXF file holds.
|
||||
//! [`crate::cmd::file::entity_codec`]), so what `entget` shows is what a DXF file holds, except
|
||||
//! for angles, which are radians in a list and degrees in a file.
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -185,6 +186,27 @@ fn point_group(code: i32) -> bool {
|
||||
(10..=18).contains(&code) || (110..=112).contains(&code) || code == 210 || (1010..=1013).contains(&code)
|
||||
}
|
||||
|
||||
/// Groups 50–58 are angles: degrees in a DXF file, radians in entity lists (DXF Reference,
|
||||
/// group code value types). `f` converts each one; an MTEXT's 50 after its column type (75) is
|
||||
/// a column height.
|
||||
fn convert_angles(kind: &str, tags: &mut [Tag], f: fn(f64) -> f64) {
|
||||
let mtext = kind.eq_ignore_ascii_case("MTEXT");
|
||||
for t in tags {
|
||||
if mtext && t.code == 75 {
|
||||
break;
|
||||
}
|
||||
if !(50..=58).contains(&t.code) {
|
||||
continue;
|
||||
}
|
||||
let v = match t.value {
|
||||
cadcraft_dxf::Value::Real(r) => r,
|
||||
cadcraft_dxf::Value::Int(i) => i as f64,
|
||||
_ => continue,
|
||||
};
|
||||
t.value = cadcraft_dxf::Value::Real(f(v));
|
||||
}
|
||||
}
|
||||
|
||||
fn tag_value(t: &Tag) -> Value {
|
||||
match &t.value {
|
||||
cadcraft_dxf::Value::Str(s) | cadcraft_dxf::Value::Hex(s) => Value::str(s),
|
||||
@@ -274,7 +296,8 @@ pub fn entget(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> {
|
||||
let h = ename(&a[0])?;
|
||||
let Ok(d) = ctx.s.doc() else { return Ok(Flow::Value(Value::Nil)) };
|
||||
let Some(e) = d.entity(h) else { return Ok(Flow::Value(Value::Nil)) };
|
||||
let (kind, tags) = (codec()?.groups)(d, e);
|
||||
let (kind, mut tags) = (codec()?.groups)(d, e);
|
||||
convert_angles(&kind, &mut tags, f64::to_radians);
|
||||
let layout = match space_of(d, h) {
|
||||
Some(Space::Paper(name)) => name,
|
||||
_ => "Model".into(),
|
||||
@@ -296,6 +319,7 @@ pub fn entget(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> {
|
||||
/// The entity a list describes, as the reader makes it; a missing layer (8) is the current one.
|
||||
fn entity_from_list(ctx: &Ctx<'_>, list: &Value) -> Result<(String, cadcraft_doc::Common, cadcraft_doc::EntityKind), LispError> {
|
||||
let (kind, mut tags) = list_to_groups(list)?;
|
||||
convert_angles(&kind, &mut tags, f64::to_degrees);
|
||||
if !tags.iter().any(|t| t.code == 8) {
|
||||
let layer = crate::sysvars::get(ctx.s, "CLAYER").and_then(|v| v.as_str().map(str::to_string)).unwrap_or_else(|| "0".into());
|
||||
tags.insert(0, Tag::s(8, layer));
|
||||
@@ -616,7 +640,8 @@ fn parse_filter_item(items: &[Value], i: &mut usize, depth: usize) -> Result<Fil
|
||||
fn matches_filter(d: &cadcraft_doc::Drawing, e: &Entity, f: &Filter) -> bool {
|
||||
let groups = || match crate::cmd::file::entity_codec() {
|
||||
Some(c) => {
|
||||
let (kind, tags) = (c.groups)(d, e);
|
||||
let (kind, mut tags) = (c.groups)(d, e);
|
||||
convert_angles(&kind, &mut tags, f64::to_radians);
|
||||
let mut l = vec![Value::cons(Value::Int(0), Value::str(&kind))];
|
||||
l.extend(groups_to_list(&tags));
|
||||
l
|
||||
|
||||
@@ -244,6 +244,61 @@ fn entity_lists_round_trip() {
|
||||
assert_eq!(ok(&mut s, "(handent \"ZZZZZZ\")"), "nil");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_lists_hold_angles_in_radians() {
|
||||
let mut s = session();
|
||||
let near = |s: &mut Session, expr: &str, want: &str| {
|
||||
let code = format!("(equal {expr} {want} 1e-9)");
|
||||
assert_eq!(ok(s, &code), "T", "{expr} is {}, not {want}", ok(s, expr));
|
||||
};
|
||||
// A DXF file holds an ARC's 50 and 51 in degrees; entget gives radians (#1165).
|
||||
s.script("ARC\nC\n0,0\n10,0\n0,10\n").unwrap();
|
||||
near(&mut s, "(cdr (assoc 50 (entget (entlast))))", "0.0");
|
||||
near(&mut s, "(cdr (assoc 51 (entget (entlast))))", "(/ pi 2)");
|
||||
// ssget filters compare against the values entget shows.
|
||||
assert_eq!(ok(&mut s, "(sslength (ssget \"X\" '((0 . \"ARC\") (-4 . \"<\") (51 . 2.0))))"), "1");
|
||||
// entmod takes radians.
|
||||
ok(&mut s, "(setq d (entget (entlast))) (entmod (subst (cons 51 pi) (assoc 51 d) d))");
|
||||
let end = s.doc().unwrap().model.iter().find_map(|e| match &e.kind {
|
||||
cadcraft_doc::EntityKind::Arc(a) => Some(a.end),
|
||||
_ => None,
|
||||
});
|
||||
assert!(end.is_some_and(|a| (a - std::f64::consts::PI).abs() < 1e-9), "{end:?}");
|
||||
|
||||
// TEXT rotation (50) and oblique angle (51), through entmake.
|
||||
ok(&mut s, "(entmake (list '(0 . \"TEXT\") '(10 0.0 0.0 0.0) '(40 . 2.5) '(1 . \"abc\") (cons 50 (/ pi 6)) '(51 . 0.25)))");
|
||||
let text = s.doc().unwrap().model.iter().find_map(|e| match &e.kind {
|
||||
cadcraft_doc::EntityKind::Text(t) => Some((t.rotation, t.oblique)),
|
||||
_ => None,
|
||||
});
|
||||
assert!(text.is_some_and(|(r, o)| (r - std::f64::consts::FRAC_PI_6).abs() < 1e-9 && (o - 0.25).abs() < 1e-9), "{text:?}");
|
||||
near(&mut s, "(cdr (assoc 50 (entget (entlast))))", "(/ pi 6)");
|
||||
near(&mut s, "(cdr (assoc 51 (entget (entlast))))", "0.25");
|
||||
|
||||
// HATCH pattern angle (52) and pattern line angle (53): ANSI31 at 0 has its lines at 45°.
|
||||
s.script("CIRCLE\n0,0\n10\n-HATCH\nP\nANSI31\n1\n0\n0,0\n\n").unwrap();
|
||||
assert_eq!(ok(&mut s, "(cdr (assoc 0 (entget (entlast))))"), "\"HATCH\"");
|
||||
near(&mut s, "(cdr (assoc 52 (entget (entlast))))", "0.0");
|
||||
near(&mut s, "(cdr (assoc 53 (entget (entlast))))", "(/ pi 4)");
|
||||
|
||||
// MTEXT: 50 before the column type (75) is the rotation, after it a column height.
|
||||
ok(
|
||||
&mut s,
|
||||
"(entmake (list '(0 . \"MTEXT\") '(10 0.0 0.0 0.0) '(40 . 2.5) '(41 . 10.0) '(1 . \"abc\") (cons 50 (/ pi 3)) '(75 . 2) '(76 . 2) '(79 . 0) '(48 . 10.0) '(49 . 1.0) '(50 . 30.0) '(50 . 20.0)))",
|
||||
);
|
||||
let mtext = s.doc().unwrap().model.iter().find_map(|e| match &e.kind {
|
||||
cadcraft_doc::EntityKind::MText(t) => Some((t.rotation, t.columns.as_ref().map(|c| c.heights.clone()))),
|
||||
_ => None,
|
||||
});
|
||||
assert!(
|
||||
mtext.as_ref().is_some_and(|(r, h)| (r - std::f64::consts::FRAC_PI_3).abs() < 1e-9 && h.as_deref() == Some(&[30.0, 20.0][..])),
|
||||
"{mtext:?}"
|
||||
);
|
||||
near(&mut s, "(cdr (assoc 50 (entget (entlast))))", "(/ pi 3)");
|
||||
let d = ok(&mut s, "(entget (entlast))");
|
||||
assert!(d.contains("(50 . 30.0) (50 . 20.0)"), "{d}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selection_sets_and_filters() {
|
||||
let mut s = session();
|
||||
|
||||
+3
-1
@@ -78,7 +78,9 @@ reached", malformed source is a reader error, and every built-in checks its argu
|
||||
- **No-ops:** `vl-load-com gc redraw`; `ver`
|
||||
|
||||
Entity lists use the DXF group codes of CADCraft's DXF files: what `entget` returns is what a
|
||||
DXF file holds for the object, plus `-1` (its name) and `410` (its layout).
|
||||
DXF file holds for the object, plus `-1` (its name) and `410` (its layout). Angles (groups 50–58)
|
||||
are in radians, as in AutoCAD, where the file holds degrees; `entmake`, `entmod` and `ssget`
|
||||
filters take radians too.
|
||||
|
||||
## Testing against the corpus
|
||||
|
||||
|
||||
Reference in New Issue
Block a user