mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-10 18:55:14 +00:00
Compare commits
3 Commits
main
...
cancel-check
| Author | SHA1 | Date | |
|---|---|---|---|
| b08773ea16 | |||
| d38e8e39b2 | |||
| c5a33b2f71 |
@@ -1046,7 +1046,14 @@ impl Body {
|
||||
if let Some(b) = *slot {
|
||||
return Ok(b);
|
||||
}
|
||||
let (lo, hi) = self.smlib()?.bounds().map_err(|e| KernelError::smlib("SMLib bounds", e))?;
|
||||
// Under a token (an evaluation that can be cancelled), measured on a copy the token can
|
||||
// stop part way: a large body's tight box takes a good part of a second, and a call
|
||||
// stopped part way may leave half-built caches on the body it reads.
|
||||
let measured = match solidcraft_smlib::stop::current() {
|
||||
Some(_) => self.smlib()?.try_clone().and_then(solidcraft_smlib::Brep::bounds_owned),
|
||||
None => self.smlib()?.bounds(),
|
||||
};
|
||||
let (lo, hi) = measured.map_err(|e| KernelError::smlib("SMLib bounds", e))?;
|
||||
let b = Aabb3 { min: v3(lo), max: v3(hi) };
|
||||
*slot = Some(b);
|
||||
Ok(b)
|
||||
|
||||
@@ -183,8 +183,8 @@ pub fn check_kept(body: &Body) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
let step = body.unchecked.as_deref().and_then(|u| u.take(&body.record).map(|r| (u, r)));
|
||||
let (what, check) = match &step {
|
||||
Some((u, r)) => (u.what.as_str(), check_result(r, u.how)),
|
||||
let (what, check) = match step {
|
||||
Some((u, r)) => (u.what.as_str(), check_result_owned(r, u.how)),
|
||||
None => ("SMLib", body.smlib()?.check(solidcraft_smlib::CheckLevel::All).map_err(|e| format!("check: {e}"))),
|
||||
};
|
||||
verdict(what, check)
|
||||
@@ -199,8 +199,26 @@ fn verdict(what: &str, check: std::result::Result<solidcraft_smlib::CheckReport,
|
||||
}
|
||||
}
|
||||
|
||||
/// [`check_kept`] of a body, ready to run on another thread ([`detach_kept`]).
|
||||
pub struct KeptCheck(Kept);
|
||||
/// [`check_result`] of a step's result nobody else reads, which it takes: the token over this
|
||||
/// thread's work stops it ([`solidcraft_smlib::Brep::check_owned`]).
|
||||
fn check_result_owned(r: solidcraft_smlib::Brep, how: Checked) -> std::result::Result<solidcraft_smlib::CheckReport, String> {
|
||||
fn failed(what: &'static str) -> impl Fn(&dyn std::fmt::Display) -> String {
|
||||
move |e| format!("{what}: {e}")
|
||||
}
|
||||
match how {
|
||||
Checked::Combined if !r.is_manifold_solid().map_err(|e| failed("check")(&e))? => {
|
||||
let record = r.to_native().map_err(|e| failed("export")(&e))?;
|
||||
drop(r);
|
||||
let split = solidcraft_smlib::Brep::from_record(&record, true).map_err(|e| failed("the separate solids")(&e))?;
|
||||
split.check_owned(solidcraft_smlib::CheckLevel::All).map_err(|e| failed("check")(&e))
|
||||
}
|
||||
_ => r.check_owned(solidcraft_smlib::CheckLevel::All).map_err(|e| failed("check")(&e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// [`check_kept`] of a body, ready to run on another thread ([`detach_kept`]), under the token
|
||||
/// of the work that detached it: stopping that work stops the check too.
|
||||
pub struct KeptCheck(Kept, Option<solidcraft_smlib::stop::Stop>);
|
||||
|
||||
enum Kept {
|
||||
/// A mesh body: nothing to check.
|
||||
@@ -215,22 +233,31 @@ enum Kept {
|
||||
/// form is copied now, so the check reads nothing the operations after it use and can run on
|
||||
/// another thread beside them ([`KeptCheck::run`]).
|
||||
pub fn detach_kept(body: &Body) -> KeptCheck {
|
||||
let stop = solidcraft_smlib::stop::current();
|
||||
if body.is_mesh() {
|
||||
return KeptCheck(Kept::Nothing);
|
||||
return KeptCheck(Kept::Nothing, stop);
|
||||
}
|
||||
if let Some((u, result)) = body.unchecked.as_deref().and_then(|u| u.take(&body.record).map(|r| (u, r))) {
|
||||
return KeptCheck(Kept::Step { what: u.what.clone(), how: u.how, result });
|
||||
return KeptCheck(Kept::Step { what: u.what.clone(), how: u.how, result }, stop);
|
||||
}
|
||||
KeptCheck(Kept::Copy(body.smlib().and_then(|b| b.try_clone().map_err(|e| KernelError::smlib("SMLib copy", e)))))
|
||||
KeptCheck(Kept::Copy(body.smlib().and_then(|b| b.try_clone().map_err(|e| KernelError::smlib("SMLib copy", e)))), stop)
|
||||
}
|
||||
|
||||
impl KeptCheck {
|
||||
/// The check: [`check_kept`]'s result for the body it was detached from.
|
||||
pub fn run(self) -> Result<()> {
|
||||
match self.0 {
|
||||
let KeptCheck(kept, stop) = self;
|
||||
solidcraft_smlib::stop::scoped(stop, || kept.run())
|
||||
}
|
||||
}
|
||||
|
||||
impl Kept {
|
||||
fn run(self) -> Result<()> {
|
||||
match self {
|
||||
Kept::Nothing => Ok(()),
|
||||
Kept::Step { what, how, result } => verdict(&what, check_result(&result, how)),
|
||||
Kept::Copy(copy) => verdict("SMLib", copy?.check(solidcraft_smlib::CheckLevel::All).map_err(|e| format!("check: {e}"))),
|
||||
// The step's result and the copy are the check's own: the work's token stops it.
|
||||
Kept::Step { what, how, result } => verdict(&what, check_result_owned(result, how)),
|
||||
Kept::Copy(copy) => verdict("SMLib", copy?.check_owned(solidcraft_smlib::CheckLevel::All).map_err(|e| format!("check: {e}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,6 +278,21 @@ mod tests {
|
||||
assert_eq!(step(), Step::Check);
|
||||
}
|
||||
|
||||
/// A check detached under a token runs under it on whatever thread runs it (the feature
|
||||
/// check beside the evaluation): stopping the work stops the check (solidcore#3247).
|
||||
#[test]
|
||||
fn a_detached_check_stops_with_the_work_that_detached_it() {
|
||||
let body = crate::cylinder(solidcore_record::Vec3::new(0.0, 0.0, 0.0), solidcore_record::Vec3::Z, 3.0, 10.0).unwrap();
|
||||
let stop = solidcraft_smlib::stop::Stop::new();
|
||||
let detached = stop.within(|| detach_kept(&body));
|
||||
stop.stop();
|
||||
let r = crate::par::kernel_thread("check").spawn(move || detached.run()).unwrap().join().unwrap();
|
||||
assert!(r.as_ref().is_err_and(|e| e.to_string().contains("cancelled")), "{r:?}");
|
||||
let free = solidcraft_smlib::stop::Stop::new();
|
||||
let detached = free.within(|| detach_kept(&body));
|
||||
assert!(detached.run().is_ok(), "a token never stopped changes nothing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_mode_reaches_the_kernels_worker_threads() {
|
||||
let items = [0, 1, 2, 3];
|
||||
|
||||
@@ -55,12 +55,38 @@ impl Brep {
|
||||
pub fn check(&self, level: CheckLevel) -> Result<CheckReport> {
|
||||
core::check(self.core(), level)
|
||||
}
|
||||
|
||||
/// [`Brep::check`] of a body nobody else reads, which it takes: the token over this thread's
|
||||
/// work ([`crate::stop`]) stops it ([`crate::Error::Cancelled`]). A check left part way may
|
||||
/// leave the body's caches half-built, so it runs only on a body it consumes.
|
||||
pub fn check_owned(self, level: CheckLevel) -> Result<CheckReport> {
|
||||
core::check_with(self.core(), level, true)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A stopped token stops the check of a body the check owns, and never the check of a body
|
||||
/// others read (solidcore#3247): stopped part way, it could leave that body's caches half-built.
|
||||
#[test]
|
||||
fn a_stopped_token_stops_an_owned_check_and_never_a_shared_one() {
|
||||
let b = Brep::make_box([0.0; 3], [10.0, 20.0, 30.0]).unwrap();
|
||||
let mine = b.try_clone().unwrap();
|
||||
let stop = crate::stop::Stop::new();
|
||||
stop.stop();
|
||||
let shared = stop.within(|| b.check(CheckLevel::All));
|
||||
assert!(shared.is_ok_and(|r| r.is_valid()));
|
||||
let owned = stop.within(|| mine.check_owned(CheckLevel::All));
|
||||
assert!(matches!(owned, Err(crate::Error::Cancelled("check"))), "{owned:?}");
|
||||
let mine = b.try_clone().unwrap();
|
||||
assert!(mine.check_owned(CheckLevel::All).is_ok_and(|r| r.is_valid()), "no token, no stop");
|
||||
let mine = b.try_clone().unwrap();
|
||||
let boxed = stop.within(|| mine.bounds_owned());
|
||||
assert!(matches!(boxed, Err(crate::Error::Cancelled("bounds"))), "{boxed:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_box_is_valid_at_every_level() {
|
||||
let b = Brep::make_box([1.0, 2.0, 3.0], [10.0, 20.0, 30.0]).unwrap();
|
||||
|
||||
@@ -53,7 +53,9 @@ pub(crate) mod trim;
|
||||
mod trim_tests;
|
||||
|
||||
pub(crate) use convert::{from_arrays, to_arrays};
|
||||
pub(crate) use measure::{area, bounds, check, classify_point, edge_face_distance, is_manifold_solid, mass_properties, solid_count};
|
||||
pub(crate) use measure::{
|
||||
area, bounds, bounds_with, check, check_with, classify_point, edge_face_distance, is_manifold_solid, mass_properties, solid_count,
|
||||
};
|
||||
|
||||
/// A solidcore body. Its own lock: solidcore's operations take `&mut Brep`, even to measure.
|
||||
pub(crate) struct Body(pub(crate) Mutex<solidcore::topology::Brep>);
|
||||
@@ -476,7 +478,7 @@ pub(crate) fn run_on<T: dump::Answer>(
|
||||
) -> Result<T> {
|
||||
let stop = crate::stop::token();
|
||||
if stop.as_ref().is_some_and(CancelToken::is_cancelled) {
|
||||
return Err(Error::Solidcore(format!("{call}: cancelled")));
|
||||
return Err(Error::Cancelled(call));
|
||||
}
|
||||
call_on(call, inputs, stop, f)
|
||||
}
|
||||
@@ -519,9 +521,14 @@ fn call_on<T: dump::Answer>(
|
||||
};
|
||||
drop(in_flight);
|
||||
// Stopped, it is no failure to look into, and solidcore may have left the kernel part way.
|
||||
// No result either, even one solidcore finished first: the caller never has to ask whether
|
||||
// it came in time (as solidcore-ops' session answers).
|
||||
if stop.is_some_and(|s| s.is_cancelled()) {
|
||||
put_kernel(None);
|
||||
return r;
|
||||
return match r {
|
||||
Err(Error::Internal(e)) => Err(Error::Internal(e)),
|
||||
_ => Err(Error::Cancelled(call)),
|
||||
};
|
||||
}
|
||||
let why = match &r {
|
||||
Ok(v) => v.refusal(),
|
||||
@@ -566,6 +573,23 @@ pub(crate) fn with_body<T: dump::Answer>(
|
||||
call_on(call, &[("brep", body)], None, |k| f(k, &mut body.0.lock().unwrap_or_else(PoisonError::into_inner)))
|
||||
}
|
||||
|
||||
/// [`with_body`] on a body only this call reads (one the caller consumes): the token over this
|
||||
/// thread's work stops it, since what a stopped call leaves half-changed is never read again.
|
||||
pub(crate) fn with_own_body<T: dump::Answer>(
|
||||
call: &'static str,
|
||||
body: &Body,
|
||||
f: impl FnOnce(&mut Kernel, &mut solidcore::topology::Brep) -> std::result::Result<T, solidcore::SolidError>,
|
||||
) -> Result<T> {
|
||||
if body.0.is_poisoned() {
|
||||
return Err(Error::Internal(format!("{call}: the body was left unfinished by an earlier panic")));
|
||||
}
|
||||
let stop = crate::stop::token();
|
||||
if stop.as_ref().is_some_and(CancelToken::is_cancelled) {
|
||||
return Err(Error::Cancelled(call));
|
||||
}
|
||||
call_on(call, &[("brep", body)], stop, |k| f(k, &mut body.0.lock().unwrap_or_else(PoisonError::into_inner)))
|
||||
}
|
||||
|
||||
/// An axis-aligned box from its lower corner (`SmApiCreateBox`, as `smlib_make_box` checks it:
|
||||
/// every edge length positive).
|
||||
pub(crate) fn make_box(origin: [f64; 3], size: [f64; 3]) -> Result<Body> {
|
||||
|
||||
@@ -159,7 +159,13 @@ pub(crate) fn area(body: &Body, relative_accuracy: f64) -> Result<f64> {
|
||||
|
||||
/// `smlib_brep_bounds`: `SmBrep::CalculateTightBoundingBox`.
|
||||
pub(crate) fn bounds(body: &Body) -> Result<([f64; 3], [f64; 3])> {
|
||||
with_body("bounds", body, |k, b| {
|
||||
bounds_with(body, false)
|
||||
}
|
||||
|
||||
/// [`bounds`], stopped by the token over this thread's work when `stoppable` (a body the
|
||||
/// caller consumes: [`super::with_own_body`]).
|
||||
pub(crate) fn bounds_with(body: &Body, stoppable: bool) -> Result<([f64; 3], [f64; 3])> {
|
||||
let run = |k: &mut Kernel, b: &mut solidcore::topology::Brep| {
|
||||
let x = brep_bounding_box(k, b, true)?;
|
||||
let (lo, hi) = (x.min(), x.max());
|
||||
let (lo, hi) = ([lo.x, lo.y, lo.z], [hi.x, hi.y, hi.z]);
|
||||
@@ -167,7 +173,8 @@ pub(crate) fn bounds(body: &Body) -> Result<([f64; 3], [f64; 3])> {
|
||||
return Err(invalid("no box"));
|
||||
}
|
||||
Ok((lo, hi))
|
||||
})
|
||||
};
|
||||
if stoppable { super::with_own_body("bounds", body, run) } else { with_body("bounds", body, run) }
|
||||
}
|
||||
|
||||
/// `smlib_brep_edge_face_distance`: SMLib's own edge/face gap (`SmEdgeuse::CalcMaxEdgeFaceGap`'s
|
||||
@@ -303,12 +310,18 @@ impl<T> super::dump::Answer for std::result::Result<T, Refusal> {
|
||||
/// owners' identities the check cannot know, fails the check with "not supported yet": the check
|
||||
/// never reports a body valid on fewer tests than the C++ kernel runs.
|
||||
pub(crate) fn check(body: &Body, level: CheckLevel) -> Result<CheckReport> {
|
||||
check_with(body, level, false)
|
||||
}
|
||||
|
||||
/// [`check`], stopped by the token over this thread's work when `stoppable` (a body the caller
|
||||
/// consumes: [`super::with_own_body`]).
|
||||
pub(crate) fn check_with(body: &Body, level: CheckLevel, stoppable: bool) -> Result<CheckReport> {
|
||||
let lv = match level {
|
||||
CheckLevel::Basic => SolidcoreCheckLevel::Level0,
|
||||
CheckLevel::Geometry => SolidcoreCheckLevel::Level1,
|
||||
CheckLevel::All => SolidcoreCheckLevel::Level2,
|
||||
};
|
||||
let c = match with_body("check", body, |k, b| {
|
||||
let checked = |k: &mut Kernel, b: &mut solidcore::topology::Brep| {
|
||||
let naming = b.naming();
|
||||
if naming == NamingScheme::Unknown {
|
||||
return Ok(Err(Refusal::Naming(naming)));
|
||||
@@ -317,7 +330,9 @@ pub(crate) fn check(body: &Body, level: CheckLevel) -> Result<CheckReport> {
|
||||
Err(SolidError::Unported { upstream, unit }) => Ok(Err(Refusal::Unported(upstream, unit))),
|
||||
r => r.map(Ok),
|
||||
}
|
||||
})? {
|
||||
};
|
||||
let ran = if stoppable { super::with_own_body("check", body, checked) } else { with_body("check", body, checked) };
|
||||
let c = match ran? {
|
||||
Ok(c) => c,
|
||||
Err(Refusal::Unported(upstream, unit)) => {
|
||||
return Err(Error::Solidcore(format!("smlib_brep_check: {upstream}: not supported yet: solidcore has not ported it (unit {unit})")));
|
||||
|
||||
@@ -90,6 +90,9 @@ pub enum Error {
|
||||
/// A step of SMLib solidcore has not ported yet, refused rather than built another way: what
|
||||
/// is missing, and the solidcore issue that ports it.
|
||||
Unported(&'static str),
|
||||
/// The work's token was stopped ([`stop::Stop::stop`]) before or during the call: no result,
|
||||
/// even one solidcore finished first. Never a failure of the operation on its input.
|
||||
Cancelled(&'static str),
|
||||
}
|
||||
|
||||
/// Why a blend has no room, as solidcore names its refusal (`NoRoomReason`): the reasons a
|
||||
@@ -227,6 +230,7 @@ impl std::fmt::Display for Error {
|
||||
Error::Solidcore(e) => write!(f, "solidcore: {e}"),
|
||||
Error::Internal(e) => write!(f, "solidcore failed internally in {e}"),
|
||||
Error::Unported(what) => write!(f, "not supported yet: {what}"),
|
||||
Error::Cancelled(call) => write!(f, "{call}: cancelled"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,12 @@ impl Brep {
|
||||
core::bounds(self.core())
|
||||
}
|
||||
|
||||
/// [`Brep::bounds`] of a body nobody else reads, which it takes: the token over this
|
||||
/// thread's work ([`crate::stop`]) stops it ([`crate::Error::Cancelled`]).
|
||||
pub fn bounds_owned(self) -> Result<([f64; 3], [f64; 3])> {
|
||||
core::bounds_with(self.core(), true)
|
||||
}
|
||||
|
||||
/// How far edge `edge` (its curve over its interval) lies from the surface of face `face`,
|
||||
/// over that surface's own domain and not beyond it, in mm: SMLib's own edge/face gap, the
|
||||
/// one it sizes an edge's tolerance by. Indices are in the body's edge and face order (as
|
||||
|
||||
+54
@@ -163,3 +163,57 @@ fn a_rod_cut_by_the_union_of_two_counter_wound_channel_sweeps_is_valid() {
|
||||
.unwrap();
|
||||
graded("by the union", &r);
|
||||
}
|
||||
|
||||
/// Cancel stops the full check of a long coil cut (solidcore#3247): a 20-turn square coil cut
|
||||
/// from a cylinder, the case of tangency-labs/solidcraft#3962, whose check takes seconds (most
|
||||
/// of the cut's time, in the edges' curve analyses). Cancelled from another thread part way,
|
||||
/// the check returns `Cancelled` within 500 ms (a few ms on a desktop; the bound leaves room
|
||||
/// for shared CI runners). nextest runs each test in a process of its own, so no other test's
|
||||
/// call holds the session's kernel meanwhile.
|
||||
#[test]
|
||||
fn the_check_of_a_long_coil_cut_stops_on_cancel() {
|
||||
use solidcore_ops::cancel::{within, CancelToken};
|
||||
use solidcore_ops::sweep::{screw_sweep, Curve};
|
||||
use std::time::{Duration, Instant};
|
||||
let corners = [
|
||||
[19.0, 0.0, 12.0],
|
||||
[32.0, 0.0, 12.0],
|
||||
[32.0, 0.0, 25.0],
|
||||
[19.0, 0.0, 25.0],
|
||||
];
|
||||
let square: Vec<Curve> = (0..4)
|
||||
.map(|i| Curve::Line {
|
||||
start: corners[i],
|
||||
end: corners[(i + 1) % 4],
|
||||
})
|
||||
.collect();
|
||||
let coil = screw_sweep(
|
||||
&square,
|
||||
[0.0; 3],
|
||||
[0.0, 0.0, 1.0],
|
||||
15.24,
|
||||
20.0,
|
||||
None,
|
||||
15.24e-4,
|
||||
)
|
||||
.unwrap();
|
||||
let stock = solidcore_ops::make_cylinder([0.0; 3], 20.0, 304.5).unwrap();
|
||||
let cut = boolean(stock, coil, BooleanOp::Subtract).unwrap().unwrap();
|
||||
let token = CancelToken::new();
|
||||
let watched = token.clone();
|
||||
let worker = std::thread::spawn(move || {
|
||||
let r = within(&watched, || cut.check(CheckLevel::All));
|
||||
(r.map(|_| ()), Instant::now())
|
||||
});
|
||||
std::thread::sleep(Duration::from_millis(300));
|
||||
let cancelled = Instant::now();
|
||||
token.cancel();
|
||||
let (r, returned) = worker.join().unwrap();
|
||||
let took = returned.saturating_duration_since(cancelled);
|
||||
eprintln!("the check returned {took:?} after Cancel");
|
||||
assert_eq!(r, Err(Error::Cancelled));
|
||||
assert!(
|
||||
took < Duration::from_millis(500),
|
||||
"returned {took:?} after Cancel"
|
||||
);
|
||||
}
|
||||
|
||||
+5
-1
@@ -121,6 +121,7 @@ use crate::topology::brep_cache::{
|
||||
};
|
||||
use crate::topology::{edge, face, vertex, Brep, BrepId, NamingScheme, RegionId};
|
||||
|
||||
const OP_TIGHT_BOX: &str = "query::brep::calculate_tight_bounding_box";
|
||||
const OP_CLASSIFY: &str = "query::brep::BrepQuery::point_3d_classify";
|
||||
const OP_EVAL_POINT: &str = "query::brep::evaluate_surface_point";
|
||||
const OP_EVAL_NORMAL: &str = "query::brep::evaluate_surface_normal";
|
||||
@@ -268,8 +269,10 @@ pub fn calculate_tight_bounding_box<'a>(
|
||||
brep_bbox = brep_bbox.union(&v_bbox);
|
||||
}
|
||||
|
||||
// Union all of the Tight Edge bounding boxes.
|
||||
// Union all of the Tight Edge bounding boxes. (not upstream) cooperative cancellation
|
||||
// once per edge and face (solidcore#3247).
|
||||
for e in bp1::edges(b, None)? {
|
||||
k.check_cancelled(OP_TIGHT_BOX)?;
|
||||
// (CalculateBoundingBox initialises the box first)
|
||||
let mut edge_bbox = ignored(crate::error::passed_on(
|
||||
qe::calculate_bounding_box(k, b, e, NORMAL_BOX, false),
|
||||
@@ -294,6 +297,7 @@ pub fn calculate_tight_bounding_box<'a>(
|
||||
// face).
|
||||
let mut face_bbox = Extent3d::new();
|
||||
for f in bp1::faces(b, None)? {
|
||||
k.check_cancelled(OP_TIGHT_BOX)?;
|
||||
if let Some(loose) = ignored(crate::error::passed_on(
|
||||
qf1::calculate_bounding_box(k, lb.get(), f, false),
|
||||
k.fixes.brep_edit_statuses_pass_on,
|
||||
|
||||
@@ -329,6 +329,8 @@ pub fn find_infinite_region(
|
||||
break;
|
||||
}
|
||||
for edge_id in &edges {
|
||||
// (not upstream) cooperative cancellation once per edge (solidcore#3247)
|
||||
k.check_cancelled(OP)?;
|
||||
let record = b.edge(*edge_id)?;
|
||||
let curve = required(record.curve.as_ref(), "ray target edge without curve")?;
|
||||
let big_tolerance = record.tolerance > 1.01 * b.tolerance();
|
||||
|
||||
@@ -317,6 +317,10 @@ fn validate_and_update_body(
|
||||
|
||||
// for every face->edgeuse
|
||||
for eu in edgeuses {
|
||||
// (not upstream) cooperative cancellation once per edgeuse (solidcore#3247)
|
||||
k.check_cancelled(
|
||||
"topology::brep::validation::assert_valid::validate_and_update_body",
|
||||
)?;
|
||||
let mate = eu1::mate(b, eu)?;
|
||||
|
||||
// skip marked edgeuses
|
||||
@@ -520,6 +524,9 @@ fn collect_regions(
|
||||
/// `SmBrep::AssertSubTopology(*this)`: `AssertValid(SM_NO_WALK)` of every region,
|
||||
/// shell, face, face loop, edge and vertex of the B-rep, in that order.
|
||||
fn sub_topology(k: &mut Kernel, b: &mut Brep, cx: &mut Cx<'_>) -> Result<bool> {
|
||||
// (not upstream) cooperative cancellation once per entity: a check of a large body's
|
||||
// geometry runs for seconds, the edges' curve analyses most of it (solidcore#3247)
|
||||
const OP: &str = "topology::brep::validation::assert_valid::sub_topology";
|
||||
let regions = p1::regions(b, None)?;
|
||||
let shells = p1::shells(k, b, None)?;
|
||||
let faces = p1::faces(b, None)?;
|
||||
@@ -536,18 +543,23 @@ fn sub_topology(k: &mut Kernel, b: &mut Brep, cx: &mut Cx<'_>) -> Result<bool> {
|
||||
rtn &= shell::region(b, cx, r)?;
|
||||
}
|
||||
for s in shells {
|
||||
k.check_cancelled(OP)?;
|
||||
rtn &= shell::shell(k, b, cx, &shell_index, s)?;
|
||||
}
|
||||
for f in faces {
|
||||
k.check_cancelled(OP)?;
|
||||
rtn &= face::face(k, b, cx, f)?;
|
||||
}
|
||||
for l in face_loops {
|
||||
k.check_cancelled(OP)?;
|
||||
rtn &= loops::loop_(k, b, cx, l)?;
|
||||
}
|
||||
for e in edges {
|
||||
k.check_cancelled(OP)?;
|
||||
rtn &= edge::edge(k, b, cx, e)?;
|
||||
}
|
||||
for v in vertices {
|
||||
k.check_cancelled(OP)?;
|
||||
rtn &= vertex::vertex(k, b, cx, v)?;
|
||||
}
|
||||
Ok(rtn)
|
||||
|
||||
@@ -68,6 +68,9 @@ pub fn contains_coincident_topology(
|
||||
level: u8,
|
||||
keys: &dyn Fn(EntityRef) -> crate::geom::object::ObjectRef,
|
||||
) -> Result<CoincidenceResult> {
|
||||
// (not upstream) cooperative cancellation once per vertex, edge and face of each search
|
||||
// (solidcore#3247)
|
||||
const OP: &str = "topology::brep::validation::coincidence::contains_coincident_topology";
|
||||
profile_scope!(k, "validation::coincidence");
|
||||
if level > 4 {
|
||||
return Err(invalid("invalid assert test level"));
|
||||
@@ -94,6 +97,7 @@ pub fn contains_coincident_topology(
|
||||
{
|
||||
profile_scope!(k, "validation::coincidence::vertex_vertex");
|
||||
for (i, &v) in vertices.iter().enumerate() {
|
||||
k.check_cancelled(OP)?;
|
||||
let point = b.vertex(v)?.point;
|
||||
let mut previous = i;
|
||||
for j in point_index
|
||||
@@ -157,6 +161,7 @@ pub fn contains_coincident_topology(
|
||||
{
|
||||
profile_scope!(k, "validation::coincidence::edge_edge");
|
||||
for (i, &e) in edges.iter().enumerate() {
|
||||
k.check_cancelled(OP)?;
|
||||
let v1 = required(edge::vertex(b, e)?)?;
|
||||
let v2 = required(edge::other_vertex(b, e, Some(v1))?)?;
|
||||
let candidates: Vec<_> = endpoint_groups
|
||||
@@ -214,6 +219,7 @@ pub fn contains_coincident_topology(
|
||||
let mut caches = FaceCaches::new();
|
||||
let mut face_boxes = Vec::new();
|
||||
for &f in &faces {
|
||||
k.check_cancelled(OP)?;
|
||||
face_boxes.push(ignored(query_face::calculate_bounding_box(
|
||||
k, &mut *b, f, false,
|
||||
))?);
|
||||
@@ -232,6 +238,7 @@ pub fn contains_coincident_topology(
|
||||
{
|
||||
profile_scope!(k, "validation::coincidence::face_face");
|
||||
for (i, &f) in faces.iter().enumerate() {
|
||||
k.check_cancelled(OP)?;
|
||||
let key = outer_keys.get(i).ok_or_else(|| invalid("face candidate"))?;
|
||||
let mut found = None;
|
||||
for &j in outer_groups
|
||||
@@ -312,6 +319,7 @@ pub fn contains_coincident_topology(
|
||||
{
|
||||
profile_scope!(k, "validation::coincidence::vertex_edge");
|
||||
for &e in &edges {
|
||||
k.check_cancelled(OP)?;
|
||||
let bbox = ignored(crate::query::edge::calculate_bounding_box(
|
||||
k,
|
||||
b,
|
||||
|
||||
@@ -418,6 +418,9 @@ pub fn validate_pointers(
|
||||
}
|
||||
let faces = p1::faces(b, None)?;
|
||||
for &f in &faces {
|
||||
// (not upstream) cooperative cancellation once per face: checking a face's trim
|
||||
// evaluates its curves and surface (solidcore#3247)
|
||||
k.check_cancelled("topology::brep::validation::pointers::validate_pointers")?;
|
||||
let tol = b.face(f)?.tolerance;
|
||||
let checked =
|
||||
crate::topology::trimming_tools::check_face(k, b, f, tol, r.array.as_deref_mut(), walk);
|
||||
|
||||
Reference in New Issue
Block a user