Thanks @erkexzcx.
This commit is contained in:
1 parent
63accec16e
commit
b20fd3d9d6
5 files changed
+2585
-8
No files matched your search
@@ -317,6 +317,7 @@ mod mirror_planes;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod native_cad_import;
|
||||
mod path_scope;
|
||||
mod pleat_join;
|
||||
mod primitive_points;
|
||||
mod rim_cut;
|
||||
mod robustness;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,47 @@
|
||||
//! #1490: the owner's shelled battery case (`battery-tpu-case-v2.solidcraft`, format 21, saved
|
||||
//! at `wall = 2.5 mm`) with a pleat loft joined into a window in one end wall. The loft stands on
|
||||
//! the case's open rim and its end strips lie in the wall's inner face. At walls of 2.4–2.6 mm
|
||||
//! the join failed with "no result": every exact route was refused, and the last resort's
|
||||
//! nudge failed both ways about its one screw axis. The other screws are now tried too.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use super::run;
|
||||
use crate::Session;
|
||||
|
||||
fn open() -> Session {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/tests/fixtures/battery-tpu-case-v2.solidcraft");
|
||||
let mut s = Session::default();
|
||||
run(&mut s, "doc.open", json!({"path": path.to_string_lossy()}));
|
||||
s
|
||||
}
|
||||
|
||||
fn errors(s: &Session) -> Vec<(String, String)> {
|
||||
s.model.results.iter().filter_map(|r| Some((r.name.clone(), r.error.clone()?))).collect()
|
||||
}
|
||||
|
||||
fn case_volume(s: &mut Session) -> f64 {
|
||||
let m = run(s, "inspect.measure", json!({"bodies": ["Case"]}));
|
||||
m["total"]["volume_mm3"].as_f64().unwrap()
|
||||
}
|
||||
|
||||
/// The design rebuilds as saved, and at 2.4 and 2.6 mm, with both pleats and every round; the
|
||||
/// case grows with the wall between them.
|
||||
#[test]
|
||||
fn the_pleats_join_at_every_wall_from_2_4_to_2_6_mm() {
|
||||
let mut s = open();
|
||||
assert_eq!(errors(&s), Vec::new(), "as saved, at 2.5 mm");
|
||||
let at = case_volume(&mut s);
|
||||
let mut volumes = Vec::new();
|
||||
for wall in ["2.4 mm", "2.6 mm"] {
|
||||
run(&mut s, "parameters.change", json!({"name": "wall", "expression": wall}));
|
||||
assert_eq!(errors(&s), Vec::new(), "at {wall}");
|
||||
volumes.push(case_volume(&mut s));
|
||||
}
|
||||
// About 14.8 cm³ of wall per tenth of a millimetre here.
|
||||
let (thin, thick) = (volumes[0], volumes[1]);
|
||||
assert!(thin < at && at < thick, "{thin} < {at} < {thick}");
|
||||
assert!(((at - thin) - (thick - at)).abs() < 0.01 * (thick - thin), "{thin} {at} {thick}");
|
||||
}
|
||||
@@ -903,14 +903,15 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||
if !APART.with(|c| c.get()) {
|
||||
APART.with(|c| c.set(true));
|
||||
let c = b.solid.vertex_iter().fold(Vec3::ZERO, |acc, v| acc + from_p3(v.point())) * (1.0 / b.solid.vertex_iter().count().max(1) as f64);
|
||||
let translation = Vec3::new(0.61, -0.37, 0.71) * (size * 2e-6);
|
||||
let nudge = |sign: f64| {
|
||||
let translation = |screw: usize| SCREWS.get(screw).map_or(Vec3::ZERO, |s| Vec3::new(s.0[0], s.0[1], s.0[2]) * (size * 2e-6));
|
||||
let nudge = |screw: usize, sign: f64| {
|
||||
let axis = SCREWS.get(screw).map_or(Vec3::Z, |s| Vec3::new(s.1[0], s.1[1], s.1[2]));
|
||||
capture_boolean_diagnostics(|| {
|
||||
transform(b, translation * sign, c, Vec3::new(0.27, 0.83, -0.49), 2e-6 * sign)
|
||||
.and_then(|nb| Ok(boolean(a, &nb, op)?.map(|r| nudged(r, &nb, &counts))))
|
||||
transform(b, translation(screw) * sign, c, axis, 2e-6 * sign).and_then(|nb| Ok(boolean(a, &nb, op)?.map(|r| nudged(r, &nb, &counts))))
|
||||
})
|
||||
};
|
||||
let (mut r, mut diagnostics) = nudge(1.0);
|
||||
let mut used = 0;
|
||||
let (mut r, mut diagnostics) = nudge(used, 1.0);
|
||||
// A nudge that pushes a face into a curved face it only touched (a cut whose end lies
|
||||
// tangent to a round side) leaves a sliver of that face behind, a fraction of a
|
||||
// millimetre wide, which later features (a shell, a fillet) can't work with. The
|
||||
@@ -923,7 +924,7 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||
};
|
||||
let made = slivers.is_none_or(|n| n > 0 && n > sliver_faces(a, size).saturating_add(sliver_faces(b, size)));
|
||||
if !matches!(r, Ok(None)) && made {
|
||||
let (r2, diagnostics2) = nudge(-1.0);
|
||||
let (r2, diagnostics2) = nudge(used, -1.0);
|
||||
if let Ok(Some(body)) = &r2
|
||||
&& slivers.is_none_or(|n| sliver_faces(body, size) < n)
|
||||
&& check(body).0
|
||||
@@ -931,6 +932,26 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||
(r, diagnostics) = (r2, diagnostics2);
|
||||
}
|
||||
}
|
||||
// Neither way worked: the screw can turn the tool along a seam it lies in (a loft
|
||||
// standing in the rim of a shelled case with its ends in the wall's inner face, #1490).
|
||||
// The other screws are tried in turn, and the first result that checks is taken.
|
||||
let works = |r: &Result<Option<Body>>| match r {
|
||||
Ok(None) => true,
|
||||
Ok(Some(body)) => check(body).0,
|
||||
Err(_) => false,
|
||||
};
|
||||
if !works(&r) {
|
||||
'screws: for screw in 1..SCREWS.len() {
|
||||
for sign in [1.0, -1.0] {
|
||||
let (r2, diagnostics2) = nudge(screw, sign);
|
||||
if matches!(&r2, Ok(Some(body)) if check(body).0) {
|
||||
(r, diagnostics, used) = (r2, diagnostics2, screw);
|
||||
break 'screws;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let translation = translation(used);
|
||||
APART.with(|c| c.set(false));
|
||||
match r {
|
||||
Ok(Some(body)) => {
|
||||
@@ -972,6 +993,12 @@ fn boolean_whole(a: &Body, b: &Body, op: BoolOp) -> Result<Option<Body>> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The screw motions a tool is nudged by as a last resort: (direction of the move, axis of the
|
||||
/// turn), each tried both ways. The first is the one every nudge used before #1490; the others
|
||||
/// are asked only when it fails both ways.
|
||||
const SCREWS: [([f64; 3], [f64; 3]); 3] =
|
||||
[([0.61, -0.37, 0.71], [0.27, 0.83, -0.49]), ([-0.42, 0.68, 0.6], [0.77, -0.18, 0.61]), ([0.31, 0.52, -0.79], [-0.58, 0.41, 0.70])];
|
||||
|
||||
/// Faces of `b` narrower than 1/200 of `size` (twice their area over their boundary's
|
||||
/// length): the slivers a nudged tool leaves where it was pushed into a face it touched.
|
||||
fn sliver_faces(b: &Body, size: f64) -> usize {
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
"60": {"area":3443.328607,"bodies":1,"edges":39,"error":null,"faces":15,"outcome":"ok","rejected":0,"shells":1,"steps":["revolved section","cut sphere"],"unsupported":0,"volume":11779.204543},
|
||||
"61": {"area":5020.413153,"bodies":1,"edges":36,"error":"boolean Cut: the operation failed: no result","faces":15,"outcome":"failed","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","shell","rectangular pattern"],"unsupported":0,"volume":4906.628538},
|
||||
"62": {"area":8771.003732,"bodies":1,"edges":60,"error":null,"faces":23,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","cut cylinder","circular pattern","cut box","cut box","join cylinder"],"unsupported":2,"volume":40107.001728},
|
||||
"63": {"area":3060.37707,"bodies":1,"edges":57,"error":"boolean Union: implausible result volume ","faces":21,"outcome":"failed","rejected":1,"shells":1,"steps":["revolved section","join box","cut cylinder","hole","cut cylinder","join box"],"unsupported":0,"volume":6534.395036},
|
||||
"63": {"area":3198.797259,"bodies":1,"edges":84,"error":null,"faces":30,"outcome":"ok","rejected":1,"shells":1,"steps":["revolved section","join box","cut cylinder","hole","cut cylinder","join box","fillet"],"unsupported":0,"volume":7075.892751},
|
||||
"64": {"area":4088.335645,"bodies":1,"edges":36,"error":null,"faces":14,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","fillet","join cylinder"],"unsupported":0,"volume":8495.220295},
|
||||
"65": {"area":8442.175249,"bodies":1,"edges":33,"error":null,"faces":13,"outcome":"ok","rejected":0,"shells":1,"steps":["box","cut cylinder","fillet","rectangular pattern","circular pattern","circular pattern","fillet","cut box"],"unsupported":0,"volume":45117.305023},
|
||||
"66": {"area":9222.76638,"bodies":1,"edges":99,"error":null,"faces":37,"outcome":"ok","rejected":0,"shells":1,"steps":["extruded profile","join box","join box","join box","cut sphere","cut box"],"unsupported":1,"volume":35217.203139},
|
||||
@@ -122,6 +122,7 @@
|
||||
{"reason":"#391 after merging main with #1213: seed 41 now reaches its second fillet (built since #1213), and the end-snap fix again drops a sliver edge the cuts left (94 to 93 edges); its steps, outcome, faces and shells are unchanged, volume moves by 0.005 mm³ (5185.215737 to 5185.221068) and area by 0.0014 mm²","seeds":[41]},
|
||||
{"reason":"the chamfer bug is on main too, see #1390","seeds":[57]},
|
||||
{"reason":"#1390: a chamfer of a convex edge that would grow the part is refused (TooLarge). Seed 57's 0.5 mm chamfer on the sphere cut's rim, which grew the part since #391 reordered its steps, is now refused, so the run goes on to its later steps and ends ok (16017.21 mm³, 24 faces)","seeds":[57]},
|
||||
{"reason":"seed 15 now cuts around a sealed void","seeds":[15]}
|
||||
{"reason":"seed 15 now cuts around a sealed void","seeds":[15]},
|
||||
{"reason":"#1490: when the last-resort nudge fails both ways, two more screw axes are tried; seed 63's final box join (its face 0.00005 mm off the half-ring's cut face) now builds instead of being refused with an implausible volume, and the design goes on to its fillet","seeds":[63]}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user