mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-10 18:55:14 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 74aac4598e | |||
| ec4b968763 |
+22
-5
@@ -474,12 +474,19 @@ fn plan_at(b: &brep::Brep, tol: f64, turn: Option<size::Turn>) -> Result<(TessPl
|
||||
.map(|(fi, r)| refused.take(fi, r).flatten())
|
||||
.collect();
|
||||
let face_h = |f: usize| sizes.get(f).and_then(Option::as_ref).map(|s| s.h);
|
||||
// A flat face asks for no size (infinite): an edge between flat faces is whole, one beside a
|
||||
// curved face takes the curved face's size, so both meet at the same nodes. An edge that
|
||||
// bounds no face keeps the longest side.
|
||||
let edge_h: Vec<f64> = edge_faces
|
||||
.iter()
|
||||
.map(|fs| {
|
||||
fs.iter()
|
||||
.filter_map(|&f| face_h(f))
|
||||
.fold(rules.max_len, f64::min)
|
||||
if fs.is_empty() {
|
||||
rules.max_len
|
||||
} else {
|
||||
fs.iter()
|
||||
.filter_map(|&f| face_h(f))
|
||||
.fold(f64::INFINITY, f64::min)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let jobs: Vec<(usize, f64)> = edge_h.into_iter().enumerate().collect();
|
||||
@@ -2124,8 +2131,15 @@ fn plan_face(
|
||||
// refined where triangles leave the surface, are too long or bend too much, then shaped by
|
||||
// Ruppert refinement.
|
||||
let step = [h / speed[0], h / speed[1]];
|
||||
// A flat face with no size asked of it is its boundary nodes alone: no interior seeds, no
|
||||
// refinement.
|
||||
let bare = matches!(s, EvalSurface::Plane { .. }) && h.is_infinite();
|
||||
let delaunay = || -> std::result::Result<_, FaceError> {
|
||||
let seeds = cdt::lattice(bbox, step, &polys, speed);
|
||||
let seeds = if bare {
|
||||
Vec::new()
|
||||
} else {
|
||||
cdt::lattice(bbox, step, &polys, speed)
|
||||
};
|
||||
let eval = |u: f64, v: f64| sag_eval(s, u, v);
|
||||
let sag = cdt::Sag {
|
||||
eval: &eval,
|
||||
@@ -2136,7 +2150,7 @@ fn plan_face(
|
||||
};
|
||||
let fill = cdt::Fill {
|
||||
seeds: &seeds,
|
||||
refine: Some((sag, MAX_REFINE)),
|
||||
refine: if bare { None } else { Some((sag, MAX_REFINE)) },
|
||||
shape: !matches!(s, EvalSurface::Plane { .. }),
|
||||
lenient: lenient == Lenient::Yes,
|
||||
};
|
||||
@@ -2150,6 +2164,9 @@ fn plan_face(
|
||||
// The lenient fill is the constrained Delaunay alone: the caller names the face in the stats.
|
||||
let (extra, triangles) = if lenient == Lenient::Yes {
|
||||
delaunay()?
|
||||
} else if bare {
|
||||
stats.planar += 1;
|
||||
delaunay()?
|
||||
} else {
|
||||
// A point of the box outside the face's loops is not the face's (as for the size
|
||||
// samples above); one they cannot tell about, past MAX_PERIODS, refuses the face.
|
||||
|
||||
+15
-7
@@ -1,7 +1,9 @@
|
||||
//! Target triangle size: one side length per face, so triangles come out about equilateral
|
||||
//! (well-shaped triangles shade evenly) and as small as the surface needs.
|
||||
//!
|
||||
//! A face's size is the smallest of the body's longest side, the length over which the surface
|
||||
//! A flat face (a plane, or a spline lying in one) has no size: any triangulation of its
|
||||
//! boundary is exact, so it gets no interior cells and its straight edges no extra nodes.
|
||||
//! A curved face's size is the smallest of the body's longest side, the length over which the surface
|
||||
//! normal turns the caller's facet angle (when one is given, see [`Turn`]), and the chord that
|
||||
//! sags the tolerance; an edge takes the smaller size of its two faces, so a curved face and
|
||||
//! its flat neighbour meet at the same spacing.
|
||||
@@ -141,7 +143,7 @@ pub(crate) fn face_size(s: &EvalSurface, dom: &Domain, rules: &Rules) -> FaceSiz
|
||||
};
|
||||
if let EvalSurface::Plane { du, dv, .. } = s {
|
||||
return plain(
|
||||
rules.max_len,
|
||||
f64::INFINITY,
|
||||
[
|
||||
kernel::record_vec3(*du).len(),
|
||||
kernel::record_vec3(*dv).len(),
|
||||
@@ -153,7 +155,8 @@ pub(crate) fn face_size(s: &EvalSurface, dom: &Domain, rules: &Rules) -> FaceSiz
|
||||
};
|
||||
let mut unevaluated: Vec<[f64; 2]> = Vec::new();
|
||||
// A flat spline (sheet-metal walls, chamfers): no curvature to look for, only the speeds.
|
||||
let samples = if planar(s) { 3 } else { SAMPLES };
|
||||
let flat = planar(s);
|
||||
let samples = if flat { 3 } else { SAMPLES };
|
||||
// The point, the unit normal (`None` at a pole, where it is undefined) and the speeds.
|
||||
let normal = |u: f64, v: f64| -> Result<(Vec3, Option<Vec3>, [f64; 2]), [f64; 2]> {
|
||||
let (p, su, sv) = s.derivs(u, v).map(kernel::record_triple).ok_or([u, v])?;
|
||||
@@ -203,7 +206,12 @@ pub(crate) fn face_size(s: &EvalSurface, dom: &Domain, rules: &Rules) -> FaceSiz
|
||||
[1.0, 1.0]
|
||||
};
|
||||
FaceSize {
|
||||
h: rules.for_curvature(k_max),
|
||||
// A flat face is exact for any triangulation of its boundary: no length cap.
|
||||
h: if flat {
|
||||
f64::INFINITY
|
||||
} else {
|
||||
rules.for_curvature(k_max)
|
||||
},
|
||||
speed: speed.map(|x| if x.is_finite() && x > 0.0 { x } else { 1.0 }),
|
||||
unevaluated,
|
||||
}
|
||||
@@ -348,7 +356,7 @@ mod tests {
|
||||
&solidcore::eval::uv::domain(&patch(0.0)),
|
||||
&rules,
|
||||
);
|
||||
assert_eq!(h, rules.max_len);
|
||||
assert_eq!(h, f64::INFINITY);
|
||||
assert!(
|
||||
(speed[0] - 4.0).abs() < 1e-9 && (speed[1] - 3.0).abs() < 1e-9,
|
||||
"{speed:?}"
|
||||
@@ -387,7 +395,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_plane_takes_the_longest_side() {
|
||||
fn a_plane_asks_for_no_side() {
|
||||
let s = EvalSurface::Plane {
|
||||
origin: solidcore::Vec3::ZERO,
|
||||
du: solidcore::Vec3::X,
|
||||
@@ -396,7 +404,7 @@ mod tests {
|
||||
let rules = Rules::new(0.01, 100.0, THIRTY);
|
||||
assert_eq!(
|
||||
face_size(&s, &solidcore::eval::uv::domain(&s), &rules).h,
|
||||
20.0
|
||||
f64::INFINITY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-26
@@ -155,31 +155,17 @@ fn ok<T>(r: Result<T>) -> T {
|
||||
|
||||
const PI: f64 = std::f64::consts::PI;
|
||||
|
||||
/// Smallest corner angle (degrees) over the mesh.
|
||||
fn min_angle(m: &Mesh) -> f64 {
|
||||
let mut least = 180.0f64;
|
||||
for t in &m.triangles {
|
||||
let [a, b, c] = t.map(|i| m.positions[i as usize]);
|
||||
for (p, q, r) in [(a, b, c), (b, c, a), (c, a, b)] {
|
||||
let (u, v) = (q - p, r - p);
|
||||
if let (Some(u), Some(v)) = (u.normalized(), v.normalized()) {
|
||||
least = least.min(u.dot(v).clamp(-1.0, 1.0).acos().to_degrees());
|
||||
}
|
||||
}
|
||||
}
|
||||
least
|
||||
}
|
||||
|
||||
/// A plane is exact for any triangulation of its boundary: it asks for no size, a box's edges
|
||||
/// are whole, and each face is its four corners, two triangles. Whatever the box's size.
|
||||
#[test]
|
||||
fn a_box_is_a_regular_grid_on_every_face() {
|
||||
// 40 × 30 × 20: sides at most 1/5 of the diagonal (10.77 mm), so 4 × 3 × 2 cells.
|
||||
let b = fixture("box_40x30x20");
|
||||
let (m, s) = plan_body(&b, 0.05);
|
||||
assert_eq!(s.planar, 6);
|
||||
assert_watertight(&m, "box");
|
||||
let cells = 2 * (4 * 3 + 4 * 2 + 3 * 2);
|
||||
assert_eq!(m.triangles.len(), 2 * cells, "two triangles per grid cell");
|
||||
assert!(min_angle(&m) > 40.0, "{}", min_angle(&m));
|
||||
fn a_box_is_twelve_triangles_whatever_its_size() {
|
||||
for name in ["box_10x6x4", "box_40x30x20"] {
|
||||
let b = fixture(name);
|
||||
let (m, s) = plan_body(&b, 0.05);
|
||||
assert_eq!(s.planar, 6, "{name}");
|
||||
assert_watertight(&m, name);
|
||||
assert_eq!(m.triangles.len(), 12, "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2155,8 +2141,8 @@ fn the_spline_coil_keeps_its_triangles() {
|
||||
let name = "smlib_coil_lower_half";
|
||||
let (b, size) = (fixture(name), size_of(name));
|
||||
for (t, want) in [
|
||||
(MeshTolerance::display_of(size), 30_776),
|
||||
(MeshTolerance::export_of(size), 30_808),
|
||||
(MeshTolerance::display_of(size), 30_630),
|
||||
(MeshTolerance::export_of(size), 30_662),
|
||||
] {
|
||||
let (p, _) = plan(&b, &t, size).unwrap();
|
||||
let (m, _) = p.evaluate();
|
||||
|
||||
Reference in New Issue
Block a user