Thanks @SychPL.
This commit is contained in:
1 parent
c38992fc09
commit
3eae8c34f6
3 files changed
+49
-5
No files matched your search
@@ -360,11 +360,14 @@ enum Clash {
|
|||||||
Blend,
|
Blend,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_clash(sel: &Selection, size: Size) -> Option<Clash> {
|
/// What a blend at `size` runs into first, if anything. Curved faces are meshed to within the
|
||||||
|
/// chord: they count when their mesh reaches into a wedge by more than twice the chord, or, when
|
||||||
|
/// `strict`, when it comes within the chord of one (the surface may then be inside it).
|
||||||
|
fn first_clash(sel: &Selection, size: Size, strict: bool) -> Option<Clash> {
|
||||||
let wedges: Vec<Option<Wedge>> = sel.frames.iter().map(|f| f.as_ref().and_then(|f| f.wedge(size))).collect();
|
let wedges: Vec<Option<Wedge>> = sel.frames.iter().map(|f| f.as_ref().and_then(|f| f.wedge(size))).collect();
|
||||||
// Planar faces are exact in the mesh; curved ones are within the chord.
|
// Planar faces are exact in the mesh; curved ones are within the chord.
|
||||||
let flat = (sel.size * 1e-7).max(1e-9);
|
let flat = (sel.size * 1e-7).max(1e-9);
|
||||||
let curved = sel.chord * 2.0;
|
let curved = if strict { -sel.chord } else { sel.chord * 2.0 };
|
||||||
// Planar faces beside an edge lie in its wedge's side planes: outside it means further.
|
// Planar faces beside an edge lie in its wedge's side planes: outside it means further.
|
||||||
let out = (sel.size * 1e-6).max(1e-8);
|
let out = (sel.size * 1e-6).max(1e-8);
|
||||||
for (i, (f, w)) in sel.frames.iter().zip(&wedges).enumerate() {
|
for (i, (f, w)) in sel.frames.iter().zip(&wedges).enumerate() {
|
||||||
@@ -433,8 +436,10 @@ fn misfit_in(sel: &Selection, size: Size) -> Result<Option<(Clash, Option<f64>)>
|
|||||||
if sel.frames.iter().all(Option::is_none) || !size.value().is_finite() || size.value() <= 0.0 {
|
if sel.frames.iter().all(Option::is_none) || !size.value().is_finite() || size.value() <= 0.0 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let Some(clash) = first_clash(sel, size) else { return Ok(None) };
|
let Some(clash) = first_clash(sel, size, false) else { return Ok(None) };
|
||||||
// Bisection on the size's steps: `hi` doesn't fit, `lo` does (or is 0).
|
// Bisection on the size's steps: `hi` doesn't fit, `lo` does (or is 0). Strictly: a size
|
||||||
|
// offered as fitting must fit, though a curved face that only touches the wedge at it would
|
||||||
|
// not be refused (a hole whose rim the round's band just reaches, #324).
|
||||||
let asked = size.value();
|
let asked = size.value();
|
||||||
let q = step(asked);
|
let q = step(asked);
|
||||||
let (mut lo, mut hi) = (0u64, (asked / q).ceil() as u64);
|
let (mut lo, mut hi) = (0u64, (asked / q).ceil() as u64);
|
||||||
@@ -443,7 +448,7 @@ fn misfit_in(sel: &Selection, size: Size) -> Result<Option<(Clash, Option<f64>)>
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let mid = lo + (hi - lo) / 2;
|
let mid = lo + (hi - lo) / 2;
|
||||||
if first_clash(sel, size.scaled(mid as f64 * q / asked)).is_none() {
|
if first_clash(sel, size.scaled(mid as f64 * q / asked), true).is_none() {
|
||||||
lo = mid;
|
lo = mid;
|
||||||
} else {
|
} else {
|
||||||
hi = mid;
|
hi = mid;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//! #324: a round on a block's top edge that would overlap a through hole is refused, and the
|
||||||
|
//! largest radius the refusal offers fits.
|
||||||
|
|
||||||
|
use solidcraft_geom::Vec3;
|
||||||
|
|
||||||
|
use crate::{Body, BoolOp, build::box_solid, build::cylinder, ops::boolean};
|
||||||
|
|
||||||
|
/// A 40 x 20 x 10 block with a through hole of radius 3 at (20, `y`).
|
||||||
|
fn block_with_hole(y: f64) -> Body {
|
||||||
|
let block = box_solid(Vec3::new(0.0, 0.0, 0.0), Vec3::new(40.0, 20.0, 10.0)).unwrap();
|
||||||
|
let hole = cylinder(Vec3::new(20.0, y, -1.0), Vec3::Z, 3.0, 12.0).unwrap();
|
||||||
|
boolean(&block, &hole, BoolOp::Cut).unwrap().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The round on the top edge at y = 20 covers the top face from y = 20 - r, and the hole's
|
||||||
|
/// wall reaches y = 17.5: a radius over 2.5 runs into the hole. The refusal of 4 mm offered
|
||||||
|
/// 2.721 mm, which was refused the same way: the curved wall's mesh, with its tolerance, was
|
||||||
|
/// first seen inside the round's region 0.22 mm past where the wall really enters it.
|
||||||
|
#[test]
|
||||||
|
fn the_radius_offered_beside_a_hole_fits() {
|
||||||
|
let b = block_with_hole(14.5);
|
||||||
|
let edge = Vec3::new(10.0, 20.0, 10.0);
|
||||||
|
let m = crate::fillet(&b, &[edge], 4.0).unwrap_err().to_string();
|
||||||
|
let offered: f64 = m
|
||||||
|
.split("the largest radius that fits is ")
|
||||||
|
.nth(1)
|
||||||
|
.and_then(|s| s.split(' ').next())
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or_else(|| panic!("no radius offered: {m}"));
|
||||||
|
assert!(offered > 2.0 && offered <= 2.5, "{m}");
|
||||||
|
let r = crate::fillet(&b, &[edge], offered).unwrap();
|
||||||
|
assert!(r.validity().is_empty(), "{:?}", r.validity());
|
||||||
|
// The block, the hole and the round's corner: the round stays clear of the hole.
|
||||||
|
let want = 8000.0 - std::f64::consts::PI * 9.0 * 10.0 - 40.0 * offered * offered * (1.0 - std::f64::consts::FRAC_PI_4);
|
||||||
|
let v = r.volume_at(0.005).unwrap();
|
||||||
|
assert!((v - want).abs() < 0.05, "{v} against {want}");
|
||||||
|
}
|
||||||
@@ -56,6 +56,8 @@ mod issue_1038_tests;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod issue_1080_tests;
|
mod issue_1080_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod issue_324_tests;
|
||||||
|
#[cfg(test)]
|
||||||
mod issue_616_tests;
|
mod issue_616_tests;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod issue_674_tests;
|
mod issue_674_tests;
|
||||||
|
|||||||
Reference in new issue
Block a user