forked from 1b-agent/fieldcraft-upstream
mesh: the one-to-one sweep names a body that ends at several levels (#444)
The crank ends at 8, 16, 20 and 22 mm, so no one-to-one sweep takes it. The sweep met one of those caps as a linking face and reported "linking face has no top side". A plane parallel to the source that only part of a ring reaches is now named as what it is, with extrude pointed to. The arm's profile alone sweeps as well as extrude does. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
0b7dda95f7
commit
9a30c7d9ed
2 files changed
+52
-2
No files matched your search
@@ -821,6 +821,38 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The one-to-one sweep and the crank (#444). The arm's profile extruded alone is a
|
||||
/// one-to-one sweep (one source, one target) and sweeps all-hex as well as extrude does. The
|
||||
/// crank itself ends at several levels (pocket floor at 8 mm, arm 16, pedal boss 20, spindle
|
||||
/// boss 22), so no one-to-one sweep takes it: the sweep says so, naming a cap only part of a
|
||||
/// ring reaches, where it reported a "linking face with no top side"; extrude meshes it.
|
||||
#[test]
|
||||
fn the_sweep_takes_the_arm_profile_and_names_the_crank_levels() {
|
||||
let session = |script: &str| {
|
||||
let bytes = fieldcraft_geometry::script_design(script).unwrap();
|
||||
let (dir, path) = crate::tests::design_file(&bytes);
|
||||
let mut s = Session::default();
|
||||
s.execute("geometry.open", &json!({"path": path})).unwrap();
|
||||
(dir, s)
|
||||
};
|
||||
let quality = |s: &mut Session, method: &str| -> crate::Result<f64> {
|
||||
let r = s.execute("mesh.hex.generate", &json!({"body": "CrankArm", "size": 3, "method": method}))?;
|
||||
assert_eq!(r["bodies"][0]["method"], method, "{r}");
|
||||
Ok(r["bodies"][0]["quality"]["min_scaled_jacobian"].as_f64().unwrap())
|
||||
};
|
||||
// The script up to the arm's extrusion: the profile alone.
|
||||
let end = CRANK_ARM.find("{\"command\": \"solid.cylinder\"").unwrap();
|
||||
let profile = format!("{}]}}", CRANK_ARM[..end].trim_end().trim_end_matches(','));
|
||||
let (_dir, mut s) = session(&profile);
|
||||
let (sweep, extrude) = (quality(&mut s, "sweep").unwrap(), quality(&mut s, "extrude").unwrap());
|
||||
assert!(sweep > 0.3 && sweep > 0.95 * extrude, "sweep {sweep}, extrude {extrude}");
|
||||
|
||||
let (_dir, mut s) = session(CRANK_ARM);
|
||||
let e = quality(&mut s, "sweep").unwrap_err().to_string();
|
||||
assert!(e.contains("is parallel to the source and only part of the ring reaches it") && e.contains("method `extrude`"), "{e}");
|
||||
assert!(quality(&mut s, "extrude").unwrap() > 0.35);
|
||||
}
|
||||
|
||||
/// `file.sample NAME`, checked clean: ready, no errors or warnings.
|
||||
fn open(name: &str) -> Session {
|
||||
let mut s = Session::default();
|
||||
|
||||
@@ -33,7 +33,7 @@ use super::region;
|
||||
use super::{Ctx, Raw, project_to_face, project_to_faces};
|
||||
use crate::quad::transfinite_grid;
|
||||
use crate::size::ideal_divisions;
|
||||
use crate::vec3::{P, sub};
|
||||
use crate::vec3::{P, dot, sub};
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// The most source faces tried.
|
||||
@@ -118,7 +118,10 @@ fn topology(brep: &Brep, s: usize) -> std::result::Result<Topology, String> {
|
||||
run.push((e, fwd));
|
||||
k += 1;
|
||||
}
|
||||
ring.push(link(brep, f, &run)?);
|
||||
ring.push(link(brep, f, &run).map_err(|e| match cap(brep, s, f) {
|
||||
Some(why) => why,
|
||||
None => e,
|
||||
})?);
|
||||
}
|
||||
used.extend(ring.iter().map(|l| l.face));
|
||||
front = ring.iter().flat_map(|l| l.top.iter().map(move |&(e, fwd)| (e, fwd, l.face))).collect();
|
||||
@@ -140,6 +143,21 @@ fn topology(brep: &Brep, s: usize) -> std::result::Result<Topology, String> {
|
||||
Ok(Topology { source: s, target, rings })
|
||||
}
|
||||
|
||||
/// Why face `f`, reached from a front swept from source `s`, cannot be a linking face when it is
|
||||
/// a plane parallel to the source: a cap that only part of the ring reaches. The body then ends
|
||||
/// at several levels (a step, a boss, a pocket floor), which no one-to-one sweep takes; saying so
|
||||
/// names the reason where the linking-face split would only report the symptom (#444).
|
||||
fn cap(brep: &Brep, s: usize, f: usize) -> Option<String> {
|
||||
let (_, ns) = brep.faces.get(s)?.plane?;
|
||||
let (_, nf) = brep.faces.get(f)?.plane?;
|
||||
(dot(ns, nf).abs() > 1.0 - 1e-9).then(|| {
|
||||
format!(
|
||||
"face {} is parallel to the source and only part of the ring reaches it: the body ends at more than one level (a step, a boss or a pocket), and a one-to-one sweep has one target face; method `extrude` meshes such bodies level by level",
|
||||
brep.face_tag(f)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the front's edges are exactly one loop of face `t`.
|
||||
fn is_loop_of(brep: &Brep, t: usize, front: &[(usize, bool, usize)]) -> bool {
|
||||
let edges: BTreeSet<usize> = front.iter().map(|x| x.0).collect();
|
||||
|
||||
Reference in new issue
Block a user