mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 18:45:55 +00:00
A round mitred to one that overflows goes first with it (fork 9ba2b12); flags() and warnings
overflow_first takes the mitred pairs of the stopped call (solidcore#2045's mitred_edges) and grows the rounds that go first through every mitre, as the capi's. The flag test reads UpstreamFixes::flags (solidcore#2024), and the check's own filter of SMLib's warning gives way to AssertReport::is_warning (solidcore#2023). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk
This commit is contained in:
@@ -501,12 +501,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_upstream_fix_is_on() {
|
||||
let f = format!("{:?}", fixes());
|
||||
let off: Vec<&str> =
|
||||
f.split([',', '{', '}']).filter_map(|kv| kv.trim().strip_suffix(": false")).filter(|name| !HELD_BACK.contains(name)).collect();
|
||||
let f = fixes();
|
||||
let flags: Vec<(&str, bool)> = f.flags().collect();
|
||||
let off: Vec<&str> = flags.iter().filter(|(name, on)| !on && !HELD_BACK.contains(name)).map(|(name, _)| *name).collect();
|
||||
assert!(off.is_empty(), "UpstreamFixes flags are off: {off:?}");
|
||||
for name in HELD_BACK {
|
||||
assert!(f.contains(&format!("{name}: false")), "{name} is on or gone: drop it from HELD_BACK");
|
||||
assert!(flags.contains(&(*name, false)), "{name} is on or gone: drop it from HELD_BACK");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -285,10 +285,13 @@ fn blend_failure(s: &FilletSession<'_>, e: SolidError, corner: bool) -> Error {
|
||||
/// `round_edges`' `overflow_first` argument: with it the session stops, nothing built, when blends
|
||||
/// of several edges overflow their faces across cliffs (`SetOverflowFirst`) or (`classify_only`)
|
||||
/// always (`SetClassifyOnly`), and `stopped` gets the edges whose blends overflow
|
||||
/// (`GetOverflowEdges`; the capi's status `OVERFLOW_FIRST`, the call's failure then meaning only that).
|
||||
/// (`GetOverflowEdges`; the capi's status `OVERFLOW_FIRST`, the call's failure then meaning only that)
|
||||
/// and `mitred` the edges of each two rounds mitred at a face's concave corner, two by two
|
||||
/// (`GetMitredEdges`), when the caller asks.
|
||||
struct OverflowFirst<'a> {
|
||||
classify_only: bool,
|
||||
stopped: &'a mut Option<Vec<EdgeId>>,
|
||||
mitred: Option<&'a mut Vec<EdgeId>>,
|
||||
}
|
||||
|
||||
/// `round_edges` and `chamfer_offsets` (with equal offsets): `edges` of `b` blended in place,
|
||||
@@ -365,6 +368,9 @@ fn blend_edges_stopping(
|
||||
&& s.error_info().code == FilletErrorType::OverflowFirst as i32
|
||||
{
|
||||
*o.stopped = Some(s.overflow_edges());
|
||||
if let Some(m) = o.mitred {
|
||||
*m = s.mitred_edges();
|
||||
}
|
||||
}
|
||||
Ok(Err(Err(blend_failure(&s, e, corner))))
|
||||
}
|
||||
@@ -553,8 +559,11 @@ const OVERFLOW_GOES_FIRST: bool = true;
|
||||
/// nothing was built in) go first: on a fresh copy of `body`, the picked edges tagged with their
|
||||
/// places ([`Picked`]), those are rounded, and each other picked edge is then what is left of it,
|
||||
/// the edges carrying its tag, rounded at its size. A picked edge with nothing left is gone: its
|
||||
/// points are flagged in `dropped`. Where none overflows on its own, or all do, the edges are
|
||||
/// rounded together as before. The result is the new body. Unlike the capi, the tags come off it
|
||||
/// points are flagged in `dropped`. Two rounds mitred at a face's concave corner (`mitred`, the edges
|
||||
/// of each such pair two by two, as the stopped call found them in `w`) meet in their mitre, each run
|
||||
/// on past the corner: one goes first with the other, so a round mitred to one that overflows goes
|
||||
/// first too (fork 9ba2b12). Where none overflows on its own, or all go first, the edges are rounded
|
||||
/// together as before. The result is the new body. Unlike the capi, the tags come off it
|
||||
/// (nothing reads them after the call, and a later call's copy would carry them on).
|
||||
#[allow(clippy::too_many_arguments)] // the capi's overflow_first
|
||||
fn overflow_first(
|
||||
@@ -564,6 +573,7 @@ fn overflow_first(
|
||||
tolerance: f64,
|
||||
section: Section<'_>,
|
||||
picked: &[EdgeId],
|
||||
mitred: &[EdgeId],
|
||||
each: &[f64],
|
||||
w: &mut Brep,
|
||||
dropped: &mut [bool],
|
||||
@@ -581,10 +591,10 @@ fn overflow_first(
|
||||
Ok(_) => return Ok(Err(Error::NoMatch { point: 0 })),
|
||||
Err(m) => return Ok(Err(edge_miss(m))),
|
||||
};
|
||||
let (mut lead, mut lead_sizes, mut rest) = (Vec::new(), Vec::new(), Vec::new());
|
||||
let mut goes_first = vec![false; picked.len()];
|
||||
for (i, ((&e, &to), &size)) in picked.iter().zip(&again).zip(each).enumerate() {
|
||||
let mut stopped = None;
|
||||
let alone = OverflowFirst { classify_only: true, stopped: &mut stopped };
|
||||
let alone = OverflowFirst { classify_only: true, stopped: &mut stopped, mitred: None };
|
||||
match blend_edges_stopping(k, w, &[e], &[size], section, None, &[], None, Some(alone))? {
|
||||
Err(Err(why)) if stopped.is_none() => return Ok(Err(why)),
|
||||
Err(Ok(_)) => return Ok(Err(Error::Unsupported)),
|
||||
@@ -592,7 +602,30 @@ fn overflow_first(
|
||||
Err(Err(_)) => {}
|
||||
}
|
||||
fresh.add_attribute(EntityRef::Edge(to), Attribute::application(PICKED_ID, Behavior::Copy, Picked(i)))?;
|
||||
if stopped.is_some_and(|over| !over.is_empty()) {
|
||||
if let Some(flag) = goes_first.get_mut(i) {
|
||||
*flag = stopped.is_some_and(|over| !over.is_empty());
|
||||
}
|
||||
}
|
||||
// A round mitred to one that goes first goes with it, through every mitre.
|
||||
let place = |e: &EdgeId| picked.iter().position(|p| p == e);
|
||||
let pairs: Vec<(usize, usize)> = mitred.as_chunks::<2>().0.iter().filter_map(|[a, b]| Some((place(a)?, place(b)?))).collect();
|
||||
let mut grew = true;
|
||||
while grew {
|
||||
grew = false;
|
||||
for &(a, b) in &pairs {
|
||||
if goes_first.get(a) != goes_first.get(b) {
|
||||
for i in [a, b] {
|
||||
if let Some(flag) = goes_first.get_mut(i) {
|
||||
*flag = true;
|
||||
}
|
||||
}
|
||||
grew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
let (mut lead, mut lead_sizes, mut rest) = (Vec::new(), Vec::new(), Vec::new());
|
||||
for (i, ((&to, &size), &first)) in again.iter().zip(each).zip(&goes_first).enumerate() {
|
||||
if first {
|
||||
lead.push(to);
|
||||
lead_sizes.push(size);
|
||||
} else {
|
||||
@@ -766,11 +799,11 @@ fn blend_call(
|
||||
// first, on a fresh copy, and the others then round what is left of their edges. An edge
|
||||
// the overflow removes whole is gone (`dropped`); one it cuts keeps the rest.
|
||||
let may_go_first = OVERFLOW_GOES_FIRST && back.is_empty() && open == Corners::Filled && picked.len() > 1;
|
||||
let mut stopped = None;
|
||||
let first = may_go_first.then_some(OverflowFirst { classify_only: false, stopped: &mut stopped });
|
||||
let (mut stopped, mut mitred) = (None, Vec::new());
|
||||
let first = may_go_first.then_some(OverflowFirst { classify_only: false, stopped: &mut stopped, mitred: Some(&mut mitred) });
|
||||
let done = blend_edges_stopping(k, &mut w, &picked, &each, section, None, &back, (open == Corners::Open).then_some(&mut patches), first)?;
|
||||
if stopped.is_some() {
|
||||
return Ok(match overflow_first(k, body, points, tolerance, section, &picked, &each, &mut w, dropped)? {
|
||||
return Ok(match overflow_first(k, body, points, tolerance, section, &picked, &mitred, &each, &mut w, dropped)? {
|
||||
Ok(fresh) => finish(fresh, solid_in)?,
|
||||
Err(e) => Err(e),
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! [`check`] fails with "not supported yet" while a test it reaches is unported in solidcore.
|
||||
|
||||
use solidcore::error::EntityRef;
|
||||
use solidcore::geom::assert_array::{AssertArray, AssertReport, labels, obj_type_string};
|
||||
use solidcore::geom::assert_array::{AssertArray, AssertReport, obj_type_string};
|
||||
use solidcore::geom::attribute::{AttributeStore, ObjectKey};
|
||||
use solidcore::geom::tol::{SmallTopoType, brep_zone_tol_3d, context_xsect_tol_3d};
|
||||
use solidcore::heal::gap::GapFunction;
|
||||
@@ -261,18 +261,11 @@ fn report_line(r: &AssertReport) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
/// The capi's `advisory()`: `SmBrep` test 21, "Suspicious Shell Count" (a body of several faces
|
||||
/// with one shell each), is a warning. `SmBrep::AssertValid` reports it without failing, and
|
||||
/// faces that meet nowhere (a sheet's faces offset apart across a sharp edge) rightly make such a
|
||||
/// body.
|
||||
fn advisory(r: &AssertReport) -> bool {
|
||||
r.reporting_type == labels::BREP_TYPE && r.list_index == 0 && r.test_index == 21
|
||||
}
|
||||
|
||||
/// The capi's `collect()`: appends the failed reports of `list`, one line each, and counts them;
|
||||
/// SMLib's warnings ([`advisory`]) are neither.
|
||||
/// SMLib's warnings ([`AssertReport::is_warning`]: `SmBrep` test 21, "Suspicious Shell Count", a
|
||||
/// body of several faces with one shell each, is one) are neither.
|
||||
fn collect(list: &AssertArray, out: &mut String, failures: &mut usize) {
|
||||
for r in list.reports().iter().filter(|r| !r.ok && !advisory(r)) {
|
||||
for r in list.reports().iter().filter(|r| !r.ok && !r.is_warning()) {
|
||||
*failures += 1;
|
||||
out.push_str(&report_line(r));
|
||||
out.push('\n');
|
||||
|
||||
@@ -397,7 +397,7 @@ fn a_sealed_void_measures_its_volume() {
|
||||
/// Two 10 mm squares apart in z = 0, one body of two faces with a shell each (as the top and
|
||||
/// front of a box offset apart across their edge make, kernel case
|
||||
/// `surfaces/cov-offset-two-faces-across-a-sharp-edge-come-apart`): `SmBrep` test 21,
|
||||
/// "Suspicious Shell Count", warns of it, and the capi's check (`advisory`) counts no warning.
|
||||
/// "Suspicious Shell Count", warns of it, and the capi's check counts no warning.
|
||||
#[test]
|
||||
fn faces_apart_with_a_shell_each_check_valid() {
|
||||
use solidcore::geom::axis2_placement::Axis2Placement;
|
||||
|
||||
Reference in New Issue
Block a user