Squash of PR #549 by @tsangiotis. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8f59410c33
commit
a880f8a4b0
|
|
@ -26,6 +26,11 @@ sync_github_name = false
|
|||
display_name = "by Truitt"
|
||||
sync_github_name = false
|
||||
|
||||
[people.tsangiotis]
|
||||
real_name = "Tasos Sangiotis"
|
||||
display_name = "Tasos Sangiotis"
|
||||
sync_github_name = false
|
||||
|
||||
[people.vangel76]
|
||||
sync_github_name = true
|
||||
|
||||
|
|
|
|||
|
|
@ -621,8 +621,15 @@ fn hatch(tags: &[Tag], t: &T) -> Option<EntityKind> {
|
|||
if verts.last().is_none_or(|l| !l.p.near(p0, 1e-9)) {
|
||||
verts.push(PolyVertex::new(p0));
|
||||
}
|
||||
// A full circle (0 to 360) starts and ends on the same point, which one
|
||||
// bulge can't span: it goes in as two half circles.
|
||||
let full = (sweep.abs() - std::f64::consts::TAU).abs() < 1e-9;
|
||||
let bulge = cadcraft_geom::arc_to_bulge(if full { sweep / 2.0 } else { sweep });
|
||||
if let Some(l) = verts.last_mut() {
|
||||
l.bulge = cadcraft_geom::arc_to_bulge(sweep);
|
||||
l.bulge = bulge;
|
||||
}
|
||||
if full {
|
||||
verts.push(PolyVertex::with_bulge(Vec2::polar(c, r, s + sweep / 2.0), bulge));
|
||||
}
|
||||
verts.push(PolyVertex::new(p1));
|
||||
}
|
||||
|
|
@ -633,32 +640,73 @@ fn hatch(tags: &[Tag], t: &T) -> Option<EntityKind> {
|
|||
let ratio = f(i + 4);
|
||||
let sa = f(i + 5).to_radians();
|
||||
let ea = f(i + 6).to_radians();
|
||||
let ccw = get(i + 7).filter(|t| t.code == 73).map(Tag::i64).unwrap_or(1) != 0;
|
||||
i += 8;
|
||||
let e = cadcraft_geom::Ellipse { center: c, major: m, ratio, start: sa, end: ea };
|
||||
// A clockwise edge stores its parameters mirrored, as an arc edge does: it
|
||||
// runs from -start clockwise to -end, the ccw sweep from -end to -start
|
||||
// walked backwards.
|
||||
let (s, e) = if ccw { (sa, ea) } else { (-ea, -sa) };
|
||||
let e = cadcraft_geom::Ellipse { center: c, major: m, ratio, start: s, end: e };
|
||||
let mut pts = Vec::new();
|
||||
e.tessellate(m.len() * 1e-3, &mut pts);
|
||||
if !ccw {
|
||||
pts.reverse();
|
||||
}
|
||||
verts.extend(pts.into_iter().map(PolyVertex::new));
|
||||
}
|
||||
4 => {
|
||||
// Spline edge: 94 degree, 73 rational, 74 periodic, 95 knots, 96 ctrl.
|
||||
let _deg = get(i).map(Tag::i64).unwrap_or(3);
|
||||
while get(i).is_some_and(|x| x.code != 96) {
|
||||
// Spline edge: 94 degree, 73 rational, 74 periodic, 95 knot count, 96
|
||||
// control point count, then the 40 knots, the 10/20 control points (each
|
||||
// with a 42 weight when rational), and 97 fit point count with 11/21 fit
|
||||
// points and 12/22, 13/23 end tangents (R2010+).
|
||||
let (mut degree, mut nk, mut nc) = (3, 0, 0);
|
||||
while let Some(x) = get(i).filter(|x| matches!(x.code, 94 | 73 | 74 | 95 | 96)) {
|
||||
let n = x.i64().clamp(0, 100_000) as usize;
|
||||
match x.code {
|
||||
94 => degree = n,
|
||||
95 => nk = n,
|
||||
96 => nc = n,
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let nc = get(i).map(Tag::i64).unwrap_or(0).clamp(0, 100_000) as usize;
|
||||
i += 1;
|
||||
let mut ctrl = Vec::new();
|
||||
for _ in 0..nc {
|
||||
if get(i).is_some_and(|t| t.code == 10) {
|
||||
ctrl.push(Vec2::new(f(i), f(i + 1)));
|
||||
i += 2;
|
||||
if get(i).is_some_and(|t| t.code == 42) {
|
||||
i += 1;
|
||||
}
|
||||
let mut knots = Vec::new();
|
||||
while knots.len() < nk && get(i).is_some_and(|x| x.code == 40) {
|
||||
knots.push(f(i));
|
||||
i += 1;
|
||||
}
|
||||
let (mut control, mut weights) = (Vec::new(), Vec::new());
|
||||
while control.len() < nc && get(i).is_some_and(|x| x.code == 10) {
|
||||
control.push(Vec2::new(f(i), f(i + 1)));
|
||||
i += 2;
|
||||
if get(i).is_some_and(|t| t.code == 42) {
|
||||
weights.push(f(i));
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
let sp = Spline::from_control(ctrl, 3);
|
||||
verts.extend(sp.tessellate(1e-3).into_iter().map(PolyVertex::new));
|
||||
let mut fit = Vec::new();
|
||||
if get(i).is_some_and(|x| x.code == 97) {
|
||||
let n = get(i).map(Tag::i64).unwrap_or(0).clamp(0, 100_000) as usize;
|
||||
i += 1;
|
||||
while fit.len() < n && get(i).is_some_and(|x| x.code == 11) {
|
||||
fit.push(Vec2::new(f(i), f(i + 1)));
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
if weights.len() != control.len() {
|
||||
weights.clear();
|
||||
}
|
||||
let sp = Spline { degree, knots, control, weights, ..Spline::default() };
|
||||
let sp = if sp.is_valid() {
|
||||
sp
|
||||
} else if sp.control.is_empty() && fit.len() > 1 {
|
||||
Spline::from_fit_points(&fit)
|
||||
} else {
|
||||
Spline::from_control(sp.control, degree)
|
||||
};
|
||||
let pts = sp.tessellate(1e-3);
|
||||
let skip = usize::from(verts.last().zip(pts.first()).is_some_and(|(l, p)| l.p.near(*p, 1e-9)));
|
||||
verts.extend(pts.into_iter().skip(skip).map(PolyVertex::new));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1762,6 +1762,36 @@ fn mirrored_hatch_extrusion_flips_boundary() {
|
|||
}
|
||||
}
|
||||
|
||||
/// The model extents of a one-loop solid HATCH whose edge boundary is `edges` (group 72 onwards).
|
||||
fn hatch_edge_extents(edges: &str) -> Bounds2 {
|
||||
let n = edges.matches("72\n").count();
|
||||
let text = format!(
|
||||
"0\nSECTION\n2\nENTITIES\n0\nHATCH\n8\n0\n10\n0\n20\n0\n30\n0\n210\n0\n220\n0\n230\n1\n2\nSOLID\n70\n1\n71\n0\n\
|
||||
91\n1\n92\n1\n93\n{n}\n{edges}97\n0\n75\n0\n76\n1\n98\n0\n0\nENDSEC\n0\nEOF\n"
|
||||
);
|
||||
read(text.as_bytes(), "h.dxf").unwrap().extents(&Space::Model)
|
||||
}
|
||||
|
||||
/// Issues #492, #493, #494: a full-circle arc edge, spline edges (knots come before the control
|
||||
/// points) and clockwise ellipse edges all keep their curve.
|
||||
#[test]
|
||||
fn hatch_edges_keep_full_circles_splines_and_clockwise_ellipses() {
|
||||
let near = |b: Bounds2, want: [f64; 4]| {
|
||||
let got = [b.min.x, b.min.y, b.max.x, b.max.y];
|
||||
assert!(got.iter().zip(want).all(|(g, w)| (g - w).abs() < 1e-3), "extents {got:?}, want {want:?}");
|
||||
};
|
||||
// #492: one arc edge from 0 to 360.
|
||||
near(hatch_edge_extents("72\n2\n10\n0\n20\n0\n40\n5\n50\n0\n51\n360\n73\n1\n"), [-5.0, -5.0, 5.0, 5.0]);
|
||||
// #493: a cubic Bezier through (0,0)..(4,0) peaking at y = 3, closed by a line.
|
||||
let spline = "72\n4\n94\n3\n73\n0\n74\n0\n95\n8\n96\n4\n40\n0\n40\n0\n40\n0\n40\n0\n40\n1\n40\n1\n40\n1\n40\n1\n\
|
||||
10\n0\n20\n0\n10\n0\n20\n4\n10\n4\n20\n4\n10\n4\n20\n0\n97\n0\n";
|
||||
near(hatch_edge_extents(&format!("{spline}72\n1\n10\n4\n20\n0\n11\n0\n21\n0\n")), [0.0, 0.0, 4.0, 3.0]);
|
||||
// #494: the clockwise half-ellipse from 0 to 180 is the lower half, closed by its diameter.
|
||||
let ellipse = |ccw| format!("72\n3\n10\n0\n20\n0\n11\n5\n21\n0\n40\n0.5\n50\n0\n51\n180\n73\n{ccw}\n72\n1\n10\n-5\n20\n0\n11\n5\n21\n0\n");
|
||||
near(hatch_edge_extents(&ellipse(0)), [-5.0, -2.5, 5.0, 0.0]);
|
||||
near(hatch_edge_extents(&ellipse(1)), [-5.0, 0.0, 5.0, 2.5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mirrored_text_extrusion_keeps_its_footprint() {
|
||||
// TEXT at OCS (-10,5) under extrusion (0,0,-1) starts at WCS (10,5) and runs towards -x (with
|
||||
|
|
|
|||
Loading…
Reference in New Issue