From da54869cd044f2aa6c70ecf4717987e91cd4df7b Mon Sep 17 00:00:00 2001 From: Erlis Vidal Date: Fri, 23 Oct 2026 20:54:20 -0400 Subject: [PATCH 1/2] claim: B-rep ray fires and BrepQuery::classify on the single-pass live solve Co-Authored-By: Claude Opus 5.5 (1M context) From 5a8b8470bf873a42defae403f62af4b1827b0a22 Mon Sep 17 00:00:00 2001 From: Erlis Vidal Date: Fri, 23 Oct 2026 21:00:15 -0400 Subject: [PATCH 2/2] wire: B-rep ray fires and BrepQuery::classify on the live solve topology::brep::p2::ray_fire and BrepQuery::classify's boundary solve classified faces read-only, so an edge use without a UV trim stopped them at SmEdgeuse::GetOrCreateUVTrimCurve (unprepared read-only topology), where upstream makes the trim inside the loop walk. Both now solve through query::brep::ray::solve_mutable_brep_point (#613's single-pass live solve on the caller's B-rep), as region nesting already does. A classifier's shared target keeps the read-only boundary solve: its copy is not the caller's B-rep. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/solidcore/src/query/brep.rs | 107 ++++++++++++++++++----- crates/solidcore/src/topology/brep/p2.rs | 10 +-- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/crates/solidcore/src/query/brep.rs b/crates/solidcore/src/query/brep.rs index f640885cb..abde0a1ed 100644 --- a/crates/solidcore/src/query/brep.rs +++ b/crates/solidcore/src/query/brep.rs @@ -79,6 +79,7 @@ mod ray; +pub(crate) use ray::solve_mutable_brep_point; #[cfg(test)] pub(crate) use ray::PreparationRequest; pub use ray::RayPreparation; @@ -453,7 +454,7 @@ impl<'a> BrepQuery<'a> { tolerance, None, |b, point, options| { - ray::solve_mutable_brep_point(k, b, &self.identity, cache, point, options) + solve_mutable_brep_point(k, b, &self.identity, cache, point, options) }, ) } @@ -511,30 +512,39 @@ impl<'a> BrepQuery<'a> { // Intersect the point with the boundary elements of the Brep if do_boundary_intersections { - let b = target.shared(); - let objects = BrepObjects { - preparation: None, - brep: b, - brep_id, - cache: Some(self.cache(k, b)?), - keys, - owner_of, + let options = TopologySolveOptions { + operation: SolverOperation::Intersect, + requested: SolutionRequested::Single, + tolerance_3d: point_zone_tol_3d, + best_answer_so_far_sq: 0.0, + opt_vectors: &[], }; // XSects point with the Brep verts, edges, and faces returning lowest dim // topology intersection found. - let solutions = TopologySolver::brep_point_solve( - k, - &objects, - Some(CacheObject::Brep), - point, - TopologySolveOptions { - operation: SolverOperation::Intersect, - requested: SolutionRequested::Single, - tolerance_3d: point_zone_tol_3d, - best_answer_so_far_sq: 0.0, - opt_vectors: &[], - }, - )?; + let solutions = match &mut target { + Target::Own(b) => { + let cache = self.cache(k, b)?; + solve_mutable_brep_point(k, b, &self.identity, cache, point, options)? + } + Target::Shared { .. } => { + let b = target.shared(); + let objects = BrepObjects { + preparation: None, + brep: b, + brep_id, + cache: Some(self.cache(k, b)?), + keys, + owner_of, + }; + TopologySolver::brep_point_solve( + k, + &objects, + Some(CacheObject::Brep), + point, + options, + )? + } + }; // when the point is on a Brep Face if let Some(sol) = solutions.get(0) { @@ -1128,6 +1138,59 @@ mod tests { assert_eq!(format!("{b:?}"), before); } + /// A unit box from `create_box` without its UV trims (upstream's classification + /// makes the ones its loop walks reach), and a query under the box's own keys + /// (`notify_ref`, as `create_box` owns its geometry). + fn box_without_trims(k: &mut Kernel) -> (Brep, BrepQuery<'static>) { + fn keys(o: CacheObject) -> ObjectRef { + use crate::topology::loopuse::notify_ref; + match o { + CacheObject::Edge(e) | CacheObject::Curve(e) => notify_ref(EntityRef::Edge(e)), + CacheObject::Face(f) | CacheObject::Surface(f) => notify_ref(EntityRef::Face(f)), + CacheObject::Vertex(v) => notify_ref(EntityRef::Vertex(v)), + _ => notify_ref(EntityRef::Brep(BrepId::from_index(0))), + } + } + fn no_owner(_: ObjectRef) -> Option { + None + } + let mut b = + crate::primitive::api_primitives::create_box(k, Point3::ZERO, 1.0, 1.0, 1.0).unwrap(); + let uses: Vec<_> = b.edgeuses().ids().collect(); + for u in uses { + b.edgeuse_mut(u).unwrap().uv_trim_curve = None; + } + let query = BrepQuery::new(BrepIdentity { + brep_id: BrepId::from_index(0), + keys: &keys, + owner_of: &no_owner, + }); + (b, query) + } + + fn trim_count(b: &Brep) -> usize { + b.edgeuses() + .iter() + .filter(|(_, u)| u.uv_trim_curve.is_some()) + .count() + } + + #[test] + fn ray_fires_make_missing_uv_trims_on_the_callers_brep() { + let mut k = Kernel::default(); + let (mut b, query) = box_without_trims(&mut k); + let solid = b + .regions() + .ids() + .find(|&r| Some(r) != b.infinite_region()) + .map(EntityRef::Region); + let inside = query + .point_3d_classify(&k, &mut b, Point3::new(0.5, 0.5, 0.5), 1e-6, false) + .unwrap(); + assert_eq!(inside.object(), solid); + assert!(trim_count(&b) > 0, "the trims are made on the box itself"); + } + #[test] fn lazy_brep_edits_the_callers_brep_or_a_copy() { let mut b = Brep::new(); diff --git a/crates/solidcore/src/topology/brep/p2.rs b/crates/solidcore/src/topology/brep/p2.rs index 3829d927e..560fe4889 100644 --- a/crates/solidcore/src/topology/brep/p2.rs +++ b/crates/solidcore/src/topology/brep/p2.rs @@ -91,7 +91,6 @@ use crate::intersect::curve_class::ClassificationInit; use crate::intersect::point_class::{ ClassGeometry, ClassObject, PointClassType, PointClassification, }; -use crate::intersect::topology_solver::p1::TopologySolver; use crate::intersect::topology_solver::p2::TopologySolveOptions; use crate::kernel::Kernel; use crate::math::{Point2, Point3, Vec3, BIG_DOUBLE, EFF_ZERO, EFF_ZERO_SQRT, PI}; @@ -105,7 +104,7 @@ use crate::query::face::p2::{curve_on_classify, CurveOnClassifyObjects}; use crate::surface::surface::{Surface, SurfaceKind}; use crate::surface::trim_srf_cache::TrimSrfCache; use crate::topology::brep::p1; -use crate::topology::brep_cache::{self, BrepCache, BrepObjects, CacheObject, CacheSource}; +use crate::topology::brep_cache::{self, BrepCache, CacheObject, CacheSource}; use crate::topology::edge::{self, AttributeFilter, NotifyEnv, SetCurveOptions}; use crate::topology::edgeuse::p1::{self as eu1, UvTrimCurveArg}; use crate::topology::edgeuse::p2 as eu2; @@ -1347,15 +1346,12 @@ pub fn ray_fire( ray_zone_tol_3d, answer_limit, |b, point, options| { - let objects = BrepObjects { - preparation: None, - brep: b, + let identity = crate::query::brep::BrepIdentity { brep_id, - cache: Some(cache), keys, owner_of, }; - TopologySolver::brep_point_solve(k, &objects, Some(CacheObject::Brep), point, options) + crate::query::brep::solve_mutable_brep_point(k, b, &identity, cache, point, options) }, ) }