mesh: divide a block edge that bends with a curved target by its length

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-16 20:13:29 -04:00
1 parent 5db21aba7b
commit 729a770d6c
2 files changed
+23 -5

No files matched your search

+6 -2
View File
@@ -202,8 +202,12 @@ fn squeezed_junction_elements_are_reported_not_meshed() {
p["min_quality"] = json!(0.3);
let model = s.model.clone();
let e = s.execute("mesh.hex.project", &p).unwrap_err().to_string();
// Smoothing leaves the worst element in the squeezed ring block (34) or in block 36.
assert!(e.contains("smallest scaled Jacobian") && (e.contains("worst in block 34") || e.contains("worst in block 36")), "{e}");
// Smoothing leaves the worst element in the squeezed ring block (34) or in block 36; with
// the cup's rim divided by its own length, the junction's corner (block 13) folds instead,
// and the validity gate refuses it.
let squeezed = e.contains("smallest scaled Jacobian") && (e.contains("worst in block 34") || e.contains("worst in block 36"));
let folded = e.contains("inverted, twisted or degenerate") && e.contains("block 13");
assert!(squeezed || folded, "{e}");
assert_eq!(s.model, model);
}
+17 -3
View File
@@ -514,11 +514,17 @@ pub fn project_mesh(t: &Topology, surface: &BoundarySurface, g: &dyn Geometry, o
// lengths). A class runs through every block its edges are parallel in, so its edges can
// differ a lot in length (a cup's wall, 4 mm thick, and its floor's radial edges, twice
// that); the longest edge alone would cut the thin blocks into flat slivers. No element is
// longer than [`MAX_STRETCH`] sizes, so a coarse size still follows the geometry.
// longer than [`MAX_STRETCH`] sizes, so a coarse size still follows the geometry. An edge
// that bends with a curved target is the exception: its elements' sides are chords of the
// surface, and one longer than the size cuts off more of the part than the size allows (a
// butterfly's rim shares its class with the shorter sides of its middle square), so a class
// takes at least the geometric mean of its bent edges' lengths in sizes (rounded,
// as the mean; an annulus's rim and bore balance each other as its straight edges do).
let (classes, nclass) = edge_classes(t);
let mut class_log = vec![(0.0f64, 0usize); nclass];
// Per class, its longest edge in element sizes.
let mut class_max = vec![0.0f64; nclass];
let mut class_bent = vec![(0.0f64, 0usize); nclass];
let mut seen: BTreeSet<[u32; 2]> = BTreeSet::new();
for (block, cls) in t.blocks.iter().zip(&classes) {
for d in 0..3 {
@@ -550,6 +556,12 @@ pub fn project_mesh(t: &Topology, surface: &BoundarySurface, g: &dyn Geometry, o
if let Some(m) = class_max.get_mut(cls[d]) {
*m = m.max(measured);
}
if len - dist(pa, pb) > tol
&& let Some(l) = class_bent.get_mut(cls[d])
{
l.0 += libm::log(measured);
l.1 += 1;
}
if let Some(l) = class_log.get_mut(cls[d]).filter(|_| len > tol) {
l.0 += libm::log(measured);
l.1 += 1;
@@ -615,9 +627,11 @@ pub fn project_mesh(t: &Topology, surface: &BoundarySurface, g: &dyn Geometry, o
.iter()
.zip(&class_max)
.zip(&class_arc)
.map(|((&(sum, k), &longest), &arc)| {
.zip(&class_bent)
.map(|(((&(sum, k), &longest), &arc), &(bent_sum, bent_k))| {
let mean = if k == 0 { 1.0 } else { libm::exp(sum / k as f64).round() };
(mean.max((longest / MAX_STRETCH).ceil()) as usize).max(arc).clamp(1, crate::block::MAX_DIVISIONS)
let bent = if bent_k == 0 { 0 } else { libm::exp(bent_sum / bent_k as f64).round().min(crate::block::MAX_DIVISIONS as f64) as usize };
(mean.max((longest / MAX_STRETCH).ceil()) as usize).max(arc).max(bent).clamp(1, crate::block::MAX_DIVISIONS)
})
.collect();
for &(b, d, n) in &opts.divisions {