From ce3b33f3068cbf8c8cef73bf108678c2550518dc Mon Sep 17 00:00:00 2001 From: Brett Lawson Date: Fri, 23 Oct 2026 15:00:55 -0700 Subject: [PATCH 1/3] claim: wire SmBrep::Transform into blend.hcr and the boolean.merge_p1 runner Co-Authored-By: Claude Opus 5.5 From dfc5ecc3c215545314fa3dc5e9755feaa282b896 Mon Sep 17 00:00:00 2001 From: Brett Lawson Date: Fri, 23 Oct 2026 15:31:13 -0700 Subject: [PATCH 2/3] wire: SmBrep::Transform and UpdateVertexEdgeIntervals stubs - blend::hcr: Hcr::with_transform transforms the scene copy with topology::brep::p5::transform_with_notifications (no scale, scaled tolerances) under the scene's notification environment, as SmHCR::ConstructorInit. - boolean.merge_p1 runner: the SmTemporaryBrepTransform probe applies and restores its shift through topology::brep::p5::transform (+2 pass). - topology::brep::p5: CombineCoincidentVertices ends with the ported topology.brep_p1 UpdateVertexEdgeIntervals (SER, as upstream) instead of the UNPORTED stub (+8 brep_p5, +13 topology_intersector_p2, +6 topology_intersector_p3; 3 brep_p10 cases now defer further on). - boolean.topology_intersector_p3 runner: handles and state are numbered by a post-merge re-walk of both B-reps (Canon::reachable, the shim's walk_all), so records a squeeze deleted, which the Rust arena keeps unlinked, read as unknown on both sides. Co-Authored-By: Claude Opus 5.5 --- crates/difftest/src/units/boolean_merge_p1.rs | 21 +-- .../units/boolean_topology_intersector_p3.rs | 133 +++++++++++++----- crates/solidcore/src/blend/hcr.rs | 40 +++--- crates/solidcore/src/topology/brep/p5.rs | 10 +- plan/units/blend.hcr.toml | 2 +- plan/units/boolean.merge_p1.toml | 2 +- .../boolean.topology_intersector_p2.toml | 2 +- .../boolean.topology_intersector_p3.toml | 2 +- plan/units/topology.brep_p10.toml | 2 +- plan/units/topology.brep_p5.toml | 2 +- 10 files changed, 144 insertions(+), 72 deletions(-) diff --git a/crates/difftest/src/units/boolean_merge_p1.rs b/crates/difftest/src/units/boolean_merge_p1.rs index 03f05ea3b1..dcb01c98ae 100644 --- a/crates/difftest/src/units/boolean_merge_p1.rs +++ b/crates/difftest/src/units/boolean_merge_p1.rs @@ -923,16 +923,17 @@ fn run_rust(i: &Inputs, b: &mut Brep, other: &Brep) -> Result, SolidErr 7 => { let mut other = other.clone(); // The native probe constructs and immediately destroys its shift. - let transform = |_: &mut Brep, _: bool, _: &_, _: Option<_>, _: bool| { - // UNPORTED(topology.brep_p5): SmBrep::Transform - Err(SolidError::unported( - "SmBrep::Transform", - "topology.brep_p5", - )) - }; - port::temporary_brep_transform(&k, &k, b, &other)? - .apply(b, &mut other, transform)? - .restore(Some(b), Some(&mut other), transform)?; + let shift = port::temporary_brep_transform(&k, &k, b, &other)?; + // SmBrep::Transform, both operands in the one kernel + let mut transform = + |brep: &mut Brep, _: bool, xfm: &_, scale: Option<_>, scale_tol: bool| { + solidcore::topology::brep::p5::transform(&mut k, brep, xfm, scale, scale_tol) + }; + shift.apply(b, &mut other, &mut transform)?.restore( + Some(b), + Some(&mut other), + &mut transform, + )?; } 8 | 9 => { let sec = EdgeuseId::from_index(u32::from(a) % b.edgeuses().len() as u32); diff --git a/crates/difftest/src/units/boolean_topology_intersector_p3.rs b/crates/difftest/src/units/boolean_topology_intersector_p3.rs index 92a219bd3d..d19b0de47e 100644 --- a/crates/difftest/src/units/boolean_topology_intersector_p3.rs +++ b/crates/difftest/src/units/boolean_topology_intersector_p3.rs @@ -13,7 +13,9 @@ //! Both sides produce one flat record: the pre-relation statuses, the merge's status //! and outputs (only on success: the port returns none with an error), then the state //! (counts, every vertex and edge tolerance, and the vertex, edge and face mates sorted -//! by handle). Handles are the p1 runner's `brep kind index` triples. +//! by handle). Handles are the p1 runner's `brep kind index` triples, numbered after the +//! merge by both sides' re-walk of the B-reps (the shim's `walk_all`, `Walk` here), so +//! records the merge deleted (kept unlinked in the Rust arena) are unknown on both. //! //! **Selectors.** Generated handles are selectors taken modulo the entity count of //! their kind; classification parameters are fractions of the classified edge's @@ -55,7 +57,9 @@ use solidcore::topology::edgeuse::p1 as eu1; use solidcore::topology::face::p4 as fp4; use solidcore::topology::loopuse as lu; use solidcore::topology::proto_topology::ProtoBrep; -use solidcore::topology::{Brep, EdgeId, EdgeuseId, FaceuseId, LoopuseId, VertexId, VertexuseId}; +use solidcore::topology::{ + Brep, EdgeId, EdgeuseId, FaceId, FaceuseId, LoopuseId, VertexId, VertexuseId, +}; use solidcore::{EntityRef, SolidError}; use solidcore_ffi::units::boolean_topology_intersector_p1 as ffi1; use solidcore_ffi::units::boolean_topology_intersector_p3::{self as ffi, Op}; @@ -65,6 +69,7 @@ use super::boolean_topology_intersector_p1::{ Two, H, K_BREP, K_EDGE, K_EDGE_CURVE, K_FACE, K_FACE_SURFACE, K_NULL, K_TRIM_CURVE, K_VERTEX, NULL_DEREF, TI_HANDLE, }; +use super::boolean_merge_p1::Canon; use super::topology_vertexuse::import; use crate::case::{CaseContext, CaseSource}; use crate::runner::default_cases; @@ -491,10 +496,58 @@ fn status(r: Result, o: &mut Vec) -> Result, So } } -fn put_list(o: &mut Vec, hs: &[H]) { +fn put_list(o: &mut Vec, walk: &Walk, hs: &[H]) { o.push(hs.len() as f64); for &h in hs { - put_h(o, Some(h)); + walk.put(o, Some(h)); + } +} + +/// The shim's `walk_all` after the merge: both B-reps re-walked +/// ([`Canon::reachable`], the shim's `walk`), so a vertex, edge or face handle (and an +/// edge curve's or face surface's) is its position in that walk, and a record the merge +/// deleted (unlinked, but kept in the Rust arena) is unknown, `-1 -3 -1`, as a deleted +/// object upstream. Without deletions the walk is the import order (the identity). +struct Walk([Option; 2]); + +impl Walk { + fn of(two: &Two) -> Self { + Self([0i8, 1].map(|b| two.brep(brep_h(b)).map(Canon::reachable))) + } + + /// The post-merge triple of `h` (see [`Walk`]). + fn triple(&self, h: Option) -> [f64; 3] { + let mut o = Vec::with_capacity(3); + self.put(&mut o, h); + [ + o.first().copied().unwrap_or(-1.0), + o.get(1).copied().unwrap_or(-3.0), + o.get(2).copied().unwrap_or(-1.0), + ] + } + + fn put(&self, o: &mut Vec, h: Option) { + let Some(h) = h else { + put_h(o, None); + return; + }; + let canon = usize::try_from(h.b) + .ok() + .and_then(|b| self.0.get(b)) + .and_then(Option::as_ref); + let at = match (canon, h.kind) { + (Some(c), K_VERTEX) => c.vertices.of(Some(VertexId::from_index(h.i))), + (Some(c), K_EDGE | K_EDGE_CURVE) => c.edges.of(Some(EdgeId::from_index(h.i))), + (Some(c), K_FACE | K_FACE_SURFACE) => c.faces.of(Some(FaceId::from_index(h.i))), + _ => { + put_h(o, Some(h)); + return; + } + }; + match u32::try_from(at) { + Ok(i) => put_h(o, Some(hd(h.b, h.kind, i))), + Err(_) => o.extend([-1.0, -3.0, -1.0]), + } } } @@ -575,7 +628,11 @@ fn new_class(k: &Kernel, c: &ClassR) -> Result, SolidErro Ok(cc) } -fn put_class_out(o: &mut Vec, cc: &CurveClassification) -> Result<(), SolidError> { +fn put_class_out( + o: &mut Vec, + walk: &Walk, + cc: &CurveClassification, +) -> Result<(), SolidError> { o.push(cc.size() as f64); for i in 0..cc.size() { let ivl = cc.at(i)?; @@ -583,29 +640,34 @@ fn put_class_out(o: &mut Vec, cc: &CurveClassification) -> Result<(), So o.push(ivl.interval.max()); for pc in [&ivl.start, &ivl.mid, &ivl.end] { o.push(f64::from(pc.point_class().code())); - put_h(o, pc.object()); + walk.put(o, pc.object()); } } Ok(()) } -/// The state after the merge (see the module docs). -fn put_state(o: &mut Vec, two: &Two, ti: &TopologyIntersector) -> Result<(), SolidError> { +/// The state after the merge (see the module docs), in the post-merge walk ([`Walk`]): +/// the records it reaches, in its order; mates sorted by their post-merge handles. +fn put_state( + o: &mut Vec, + two: &Two, + walk: &Walk, + ti: &TopologyIntersector, +) -> Result<(), SolidError> { for b in 0..2i8 { - let Some(br) = two.brep(brep_h(b)) else { + let canon = usize::try_from(b).ok().and_then(|i| walk.0.get(i)); + let (Some(br), Some(Some(c))) = (two.brep(brep_h(b)), canon) else { o.extend([0.0, 0.0, 0.0]); continue; }; - o.push(br.vertices().len() as f64); - o.push(br.edges().len() as f64); - o.push(br.faces().len() as f64); - for v in 0..br.vertices().len() { - let id = VertexId::from_index(u32::try_from(v).unwrap_or(u32::MAX)); - o.push(br.vertex(id)?.tolerance); + o.push(c.vertices.items.len() as f64); + o.push(c.edges.items.len() as f64); + o.push(c.faces.items.len() as f64); + for &v in &c.vertices.items { + o.push(br.vertex(v)?.tolerance); } - for e in 0..br.edges().len() { - let id = EdgeId::from_index(u32::try_from(e).unwrap_or(u32::MAX)); - o.push(br.edge(id)?.tolerance); + for &e in &c.edges.items { + o.push(br.edge(e)?.tolerance); } } for kind in [ @@ -613,12 +675,16 @@ fn put_state(o: &mut Vec, two: &Two, ti: &TopologyIntersector) -> Result ClassObjectKind::Edge, ClassObjectKind::Face, ] { - let mut objs = ti.mapped_brep_objects(kind)?; - objs.sort(); + let mut objs: Vec<_> = ti + .mapped_brep_objects(kind)? + .into_iter() + .map(|h| (walk.triple(Some(h)), walk.triple(ti.other_mate(Some(h))))) + .collect(); + objs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)); o.push(objs.len() as f64); - for h in objs { - put_h(o, Some(h)); - put_h(o, ti.other_mate(Some(h))); + for (h, mate) in objs { + o.extend(h); + o.extend(mate); } } Ok(()) @@ -702,9 +768,10 @@ fn run_rust_merge(i: &Inputs, p: Prepared) -> Result, SolidError> { }; let r = ti.merge_curve_on_surfaces(&mut k, &mut two, &input); if let Some(m) = status(r, &mut o)? { + let walk = Walk::of(&two); o.push(flag(m.deleted_topology)); - put_list(&mut o, &m.deleted_edges.unwrap_or_default()); - put_list(&mut o, &m.deleted_vertices.unwrap_or_default()); + put_list(&mut o, &walk, &m.deleted_edges.unwrap_or_default()); + put_list(&mut o, &walk, &m.deleted_vertices.unwrap_or_default()); } } MergeR::CurveClasses { @@ -722,11 +789,12 @@ fn run_rust_merge(i: &Inputs, p: Prepared) -> Result, SolidError> { let r = ti.merge_curve_classes(&mut k, &mut two, &mut cc1, &mut cc2, merge_singularities); if let Some(m) = status(r, &mut o)? { + let walk = Walk::of(&two); o.push(flag(m.topology_was_deleted)); - put_list(&mut o, &m.deleted_edges.unwrap_or_default()); - put_list(&mut o, &m.deleted_vertices.unwrap_or_default()); - put_class_out(&mut o, &cc1)?; - put_class_out(&mut o, &cc2)?; + put_list(&mut o, &walk, &m.deleted_edges.unwrap_or_default()); + put_list(&mut o, &walk, &m.deleted_vertices.unwrap_or_default()); + put_class_out(&mut o, &walk, &cc1)?; + put_class_out(&mut o, &walk, &cc2)?; } } MergeR::CoincidentSurfaces { s1, s2 } => { @@ -741,12 +809,13 @@ fn run_rust_merge(i: &Inputs, p: Prepared) -> Result, SolidError> { } let r = ti.merge_coincident_surfaces(&mut k, &mut two, s1, s2, Some(TI_HANDLE)); if let Some(m) = status(r, &mut o)? { - put_list(&mut o, &m.faces); - put_list(&mut o, &m.other_faces); + let walk = Walk::of(&two); + put_list(&mut o, &walk, &m.faces); + put_list(&mut o, &walk, &m.other_faces); } } } - put_state(&mut o, &two, &ti)?; + put_state(&mut o, &two, &Walk::of(&two), &ti)?; Ok(o) } diff --git a/crates/solidcore/src/blend/hcr.rs b/crates/solidcore/src/blend/hcr.rs index 6bf79d6035..96df22d6fb 100644 --- a/crates/solidcore/src/blend/hcr.rs +++ b/crates/solidcore/src/blend/hcr.rs @@ -29,10 +29,9 @@ //! `topology::brep::p3::copy_brep`) notifies through the caller's notification //! environment, naming the copy's objects by its `key_of` and `brep`'s by `source_key` //! (upstream objects are known by address), so [`Hcr::new`] and [`Hcr::with_transform`] -//! take the kernel mutably with both. The scene's transformation (`Transform`) and -//! its bounding box -//! (`SmBrep::CalculateBoundingBox`) belong to unported units, so [`Hcr::with_transform`] -//! stops there; [`Hcr::from_scene`] is the rest of `ConstructorInit` for a caller that +//! take the kernel mutably with both. The scene's transformation (`SmBrep::Transform`, +//! `topology::brep::p5::transform_with_notifications`) notifies through the same +//! environment. [`Hcr::from_scene`] is the rest of `ConstructorInit` for a caller that //! already holds the transformed scene and its box. //! - The pointer maps `m_pSilVertices`, `m_pSilEdges` and `m_pBackFacingEdges` are id //! sets; [`Hcr::silhouette_edges`] returns the ids in ascending order (upstream @@ -224,16 +223,22 @@ fn copy_brep( crate::topology::brep::p3::copy_brep(k, env, brep, source_key, None, None, true, true) } -// ---- unported callees --------------------------------------------------------------------- - -/// `SmBrep::Transform` (unit `topology.brep_p5`). -fn transform_brep(brep: &mut Brep, transform: &Axis2Placement) -> Result<()> { - let _ = (brep, transform); - // UNPORTED(topology.brep_p5): SmBrep::Transform - Err(SolidError::unported( - "SmBrep::Transform", - "topology.brep_p5", - )) +/// `SmBrep::Transform(crRotateNMove)` with its defaults (no scale, `bScaleTol` `TRUE`), +/// notifying through the scene copy's environment `env`. +fn transform_brep( + k: &mut Kernel, + env: &mut edge::NotifyEnv<'_>, + brep: &mut Brep, + transform: &Axis2Placement, +) -> Result<()> { + crate::topology::brep::p5::transform_with_notifications( + k, + brep, + transform, + None, + true, + Some(env), + ) } /// `SmBrep::FindAndSetInfiniteRegion()` with upstream's default arguments (no shell to @@ -529,8 +534,9 @@ impl Hcr { /// /// # Errors /// - /// Those of the scene copy (`topology::brep::p3::copy_brep`); [`SolidError::Unported`] - /// from the transform and box (units `topology.brep_p5`, `query.brep`). + /// Those of the scene copy (`topology::brep::p3::copy_brep`); the transform's and + /// `FindAndSetInfiniteRegion`'s failures are ignored as upstream, apart from + /// [`SolidError::Unported`] from their callees. pub fn with_transform( k: &mut Kernel, env: &mut edge::NotifyEnv<'_>, @@ -544,7 +550,7 @@ impl Hcr { let mut scene = copy_brep(k, env, brep, source_key)?; scene.set_editing_enabled(true); // transform the copied Brep to place the eye looking up the Z axis - ignore_status(transform_brep(&mut scene, &vt))?; + ignore_status(transform_brep(k, env, &mut scene, &vt))?; ignore_status(find_and_set_infinite_region(k, &mut scene))?; // [B23] // its status is ignored upstream (the box then stays empty) let scene_box = ignored(qbrep::calculate_bounding_box( diff --git a/crates/solidcore/src/topology/brep/p5.rs b/crates/solidcore/src/topology/brep/p5.rs index 280e8954cb..3c7a045687 100644 --- a/crates/solidcore/src/topology/brep/p5.rs +++ b/crates/solidcore/src/topology/brep/p5.rs @@ -416,7 +416,7 @@ pub fn combine_with_notifications( notifications.as_deref_mut(), ))?; } - set_vertex_point(k, b, survivor, target.point, notifications)?; + set_vertex_point(k, b, survivor, target.point, notifications.as_deref_mut())?; crate::error::ignore_status(vertex::set_tolerance( b, survivor, @@ -424,7 +424,8 @@ pub fn combine_with_notifications( true, true, ))?; - update_vertex_edge_intervals(b, survivor) + let env = notifications.ok_or_else(|| SolidError::null_pointer(OP))?; + crate::topology::brep::p1::update_vertex_edge_intervals(k, b, env, survivor) } /// `Notify(SM_NO_MERGE_IN_BREP, pSurvivingVertex, pVertexToDelete, this)` @@ -553,11 +554,6 @@ fn set_vertex_point( ) } -fn update_vertex_edge_intervals(_b: &mut Brep, _vertex: VertexId) -> Result<()> { - // UNPORTED(topology.brep_p1): SmBrep::UpdateVertexEdgeIntervals - missing("SmBrep::UpdateVertexEdgeIntervals", "topology.brep_p1") -} - /// Makes a wire edge over the caller's curve interval. Evaluates start before end, /// then needs the owning B-rep's infinite region; the owned curve drops on error. pub fn create_wire_edge_from_curve( diff --git a/plan/units/blend.hcr.toml b/plan/units/blend.hcr.toml index 8fbb5f328b..d214140867 100644 --- a/plan/units/blend.hcr.toml +++ b/plan/units/blend.hcr.toml @@ -9,4 +9,4 @@ tests = ["SM_API_test:TestSmCreateSilhouetteCurves", "prog_test:my_test_tsurface status = "partial" claimed_by = "" claimed_at = "" -notes = "EXACT SmSurfOfRevolution EXPORT (PR #304, shared graph exports carry revolution faces exactly; refreshed on main ca3bbd2b, 2026-10-21): difftest 434 cases, 393 pass, 0 failed, 41 deferred (topology.edge 30, intersect.surface_drop_lib_p3 8, surface.surface_p2 3), max_rel_err 0; 396 -> 393 pass: gen0043/gen0200/gen0245 (both sides errored before, nothing compared) now reach UNPORTED(topology.edge) SmEdge::GetVertex stubs in intersect/curve_class/p1.rs and p3.rs. Earlier: 14 fns; partial (PR #191): 434 cases (34 fixtures: hints 3/-1/big/fractional and fa >= fb / fa == fb, fixtures/blend.hcr), 394 pass, 0 fail, 40 deferred (topology.edge 27, topology.face_p3 7, surface.surface_p2 3, topology.vertex 2, intersect.surface_drop_lib_p3 1) after the maintainer merges of main with topology.brep_p1 and intersect.curve_class_p2 partial (SmCurveClassification::UpdatePointClass wired to curve_class_p2; its 35 deferrals now run: 28 pass, 7 move on to topology.edge / topology.vertex). With topology.brep_p1 alone: 366 pass, 68 deferred (GetInfiniteRegion, GetEdges, GetFaces, GetSurfaces, AddDeletedSurface wired to topology::brep::p1; the PR's m_bEditingEnabled co-port dropped for brep_p1's; the runner now sets each face surface's owner to its face as the upstream build does, without which GetSurfaces selected nothing and the 2 IsPointVisible cases failed; they now defer at surface.surface_p2 / topology.face_p3); before that merge 309 pass, 125 deferred (topology.brep_p1 67); --perturb SENSITIVE 309/309 (pre-merge); probe 3000 seeds clean (before the review round). The FFI guard accepts every int64 hint (C++: 0 none, 1 visible, other invisible) and either order of fa/fb (SmExtent1d swaps). The invisible-hint fixtures read the scene's infinite region (topology.brep_p1, wired at the merge). SmEdgeuse::EvaluateBinormal is wired to topology.edgeuse_p3 (on the scene itself, bUse3DOnly FALSE), and SmBrep::GetEdges without a filter to topology::brep::p1::edges. Of the 309 passes, 259 compare values (construction state, 1-interval classifications, single-piece ComputeEdgeVisibility, silhouette lists; status/flag records for the wire, lamina and listed early-outs) and 50 are both-errored (24 inexact_geometry, 25 rejected UB-169 guard cases, 1 rejected fixture). Classifications with 2 or more intervals: 21 generated (crossing wire-loop fixtures), all deferred: InsertPointClass splits through InsertAt / EditIntervalMin / EditIntervalMax (intersect.curve_class_p2, deferred before its merge; see the counts above); likewise the 13 multi-piece ComputeEdgeVisibility cases. The scene B-rep's SmBrep copy + Transform (topology.brep_p3) and its box (query.brep) are stubbed in Hcr::with_transform (FindAndSetInfiniteRegion is wired to topology.brep_p5 since PR #285); the runner imports the C++ scene and uses Hcr::from_scene. ComputeSilhouettes / ComputeGlobalVisibility / IsPointVisible start with topology.brep_p1 queries (only IsPointVisible is generated; it defers). SmFace::FindIfRectangularTrim is wired to topology.face_p1 (IsPointVisible's surface set; not reached while IsPointVisible defers on topology.brep_p1). CO-PORTED SmCurveClassification constructor (see intersect.curve_class_p4). Upstream defects UB-169, UB-170. hazards: ptr-hash (GetSilhouetteEdges sorted by id) Re-run after wiring stale stubs (PR #285): 434 cases, 396 pass, 0 fail, 38 deferred, max_rel_err 0. Main since: 14 fns; partial (PR #191): 434 cases (34 fixtures: hints 3/-1/big/fractional and fa >= fb / fa == fb, fixtures/blend.hcr), 394 pass, 0 fail, 40 deferred (topology.edge 27, topology.face_p3 7, surface.surface_p2 3, topology.vertex 2, intersect.surface_drop_lib_p3 1) after the maintainer merges of main with topology.brep_p1 and intersect.curve_class_p2 partial (SmCurveClassification::UpdatePointClass wired to curve_class_p2; its 35 deferrals now run: 28 pass, 7 move on to topology.edge / topology.vertex). With topology.brep_p1 alone: 366 pass, 68 deferred (GetInfiniteRegion, GetEdges, GetFaces, GetSurfaces, AddDeletedSurface wired to topology::brep::p1; the PR's m_bEditingEnabled co-port dropped for brep_p1's; the runner now sets each face surface's owner to its face as the upstream build does, without which GetSurfaces selected nothing and the 2 IsPointVisible cases failed; they now defer at surface.surface_p2 / topology.face_p3); before that merge 309 pass, 125 deferred (topology.brep_p1 67); --perturb SENSITIVE 309/309 (pre-merge); probe 3000 seeds clean (before the review round). The FFI guard accepts every int64 hint (C++: 0 none, 1 visible, other invisible) and either order of fa/fb (SmExtent1d swaps). The invisible-hint fixtures read the scene's infinite region (topology.brep_p1, wired at the merge). SmEdgeuse::EvaluateBinormal is wired to topology.edgeuse_p3 (on the scene itself, bUse3DOnly FALSE), and SmBrep::GetEdges without a filter to topology::brep::p1::edges. Of the 309 passes, 259 compare values (construction state, 1-interval classifications, single-piece ComputeEdgeVisibility, silhouette lists; status/flag records for the wire, lamina and listed early-outs) and 50 are both-errored (24 inexact_geometry, 25 rejected UB-169 guard cases, 1 rejected fixture). Classifications with 2 or more intervals: 21 generated (crossing wire-loop fixtures), all deferred: InsertPointClass splits through InsertAt / EditIntervalMin / EditIntervalMax (intersect.curve_class_p2, deferred before its merge; see the counts above); likewise the 13 multi-piece ComputeEdgeVisibility cases. The scene B-rep's SmBrep copy + Transform (topology.brep_p3) and its box (query.brep) are stubbed in Hcr::with_transform (FindAndSetInfiniteRegion is wired to topology.brep_p5 since PR #285); the runner imports the C++ scene and uses Hcr::from_scene. ComputeSilhouettes / ComputeGlobalVisibility / IsPointVisible start with topology.brep_p1 queries (only IsPointVisible is generated; it defers). SmFace::FindIfRectangularTrim is wired to topology.face_p1 (IsPointVisible's surface set; not reached while IsPointVisible defers on topology.brep_p1). CO-PORTED SmCurveClassification constructor (see intersect.curve_class_p4). Upstream defects UB-169, UB-170. hazards: ptr-hash (GetSilhouetteEdges sorted by id) Re-run after wiring stale stubs (PR #285): 434 cases, 396 pass, 0 fail, 38 deferred, max_rel_err 0. UPDATE (PR #204, topology.brep_p3): the UNPORTED(topology.brep_p3) copy constructor stub is wired to topology::brep::p3::copy_brep (no maps, bCopyAttributes and bAddAnalytics TRUE); Hcr::new and Hcr::with_transform take k: &mut Kernel, the caller's NotifyEnv and a source_key (the copy's objects under env.key_of, the source's under source_key, as api_brep_copy). The runner uses Hcr::from_scene only: report unchanged (434 cases, 394 pass, 0 fail, 40 deferred). API EXTENDED in sweep.topology_sweep (PR #222): Hcr::scene_brep_mut exposes the retained session scene for mutable topology-source borrows without cloning its identities. RE-RUN after intersect.ray_tracer wired RayTracer::add_surface_to_grid (the ray surfaces go through it): 434 cases, 396 pass, 0 fail, 38 deferred (topology.edge 37, topology.face_p2 1), max_rel_err 0; the CI report of main 03cfd79 was 386 pass, 48 deferred (surface.surface_p2 10): those 10 now run and match the C++. UPDATE 2026-10-23 (branch claude/edge-handles): blend::hcr::Geometry implements ClassGeometry::topology_entity / topology_handle for its vertex, edge and region handles, so the 37 SmEdge::GetVertex deferrals resolve (re-run after merging main with the ray-tracer change): PARTIAL blend.hcr cases=434 passed=431 failed=0 deferred=3 max_rel_err=0e0 with_values=403 no_values=28 vacuous=0 blocked_by=topology.face_p2(3)." +notes = "EXACT SmSurfOfRevolution EXPORT (PR #304, shared graph exports carry revolution faces exactly; refreshed on main ca3bbd2b, 2026-10-21): difftest 434 cases, 393 pass, 0 failed, 41 deferred (topology.edge 30, intersect.surface_drop_lib_p3 8, surface.surface_p2 3), max_rel_err 0; 396 -> 393 pass: gen0043/gen0200/gen0245 (both sides errored before, nothing compared) now reach UNPORTED(topology.edge) SmEdge::GetVertex stubs in intersect/curve_class/p1.rs and p3.rs. Earlier: 14 fns; partial (PR #191): 434 cases (34 fixtures: hints 3/-1/big/fractional and fa >= fb / fa == fb, fixtures/blend.hcr), 394 pass, 0 fail, 40 deferred (topology.edge 27, topology.face_p3 7, surface.surface_p2 3, topology.vertex 2, intersect.surface_drop_lib_p3 1) after the maintainer merges of main with topology.brep_p1 and intersect.curve_class_p2 partial (SmCurveClassification::UpdatePointClass wired to curve_class_p2; its 35 deferrals now run: 28 pass, 7 move on to topology.edge / topology.vertex). With topology.brep_p1 alone: 366 pass, 68 deferred (GetInfiniteRegion, GetEdges, GetFaces, GetSurfaces, AddDeletedSurface wired to topology::brep::p1; the PR's m_bEditingEnabled co-port dropped for brep_p1's; the runner now sets each face surface's owner to its face as the upstream build does, without which GetSurfaces selected nothing and the 2 IsPointVisible cases failed; they now defer at surface.surface_p2 / topology.face_p3); before that merge 309 pass, 125 deferred (topology.brep_p1 67); --perturb SENSITIVE 309/309 (pre-merge); probe 3000 seeds clean (before the review round). The FFI guard accepts every int64 hint (C++: 0 none, 1 visible, other invisible) and either order of fa/fb (SmExtent1d swaps). The invisible-hint fixtures read the scene's infinite region (topology.brep_p1, wired at the merge). SmEdgeuse::EvaluateBinormal is wired to topology.edgeuse_p3 (on the scene itself, bUse3DOnly FALSE), and SmBrep::GetEdges without a filter to topology::brep::p1::edges. Of the 309 passes, 259 compare values (construction state, 1-interval classifications, single-piece ComputeEdgeVisibility, silhouette lists; status/flag records for the wire, lamina and listed early-outs) and 50 are both-errored (24 inexact_geometry, 25 rejected UB-169 guard cases, 1 rejected fixture). Classifications with 2 or more intervals: 21 generated (crossing wire-loop fixtures), all deferred: InsertPointClass splits through InsertAt / EditIntervalMin / EditIntervalMax (intersect.curve_class_p2, deferred before its merge; see the counts above); likewise the 13 multi-piece ComputeEdgeVisibility cases. The scene B-rep's SmBrep copy + Transform (topology.brep_p3) and its box (query.brep) are stubbed in Hcr::with_transform (FindAndSetInfiniteRegion is wired to topology.brep_p5 since PR #285); the runner imports the C++ scene and uses Hcr::from_scene. ComputeSilhouettes / ComputeGlobalVisibility / IsPointVisible start with topology.brep_p1 queries (only IsPointVisible is generated; it defers). SmFace::FindIfRectangularTrim is wired to topology.face_p1 (IsPointVisible's surface set; not reached while IsPointVisible defers on topology.brep_p1). CO-PORTED SmCurveClassification constructor (see intersect.curve_class_p4). Upstream defects UB-169, UB-170. hazards: ptr-hash (GetSilhouetteEdges sorted by id) Re-run after wiring stale stubs (PR #285): 434 cases, 396 pass, 0 fail, 38 deferred, max_rel_err 0. Main since: 14 fns; partial (PR #191): 434 cases (34 fixtures: hints 3/-1/big/fractional and fa >= fb / fa == fb, fixtures/blend.hcr), 394 pass, 0 fail, 40 deferred (topology.edge 27, topology.face_p3 7, surface.surface_p2 3, topology.vertex 2, intersect.surface_drop_lib_p3 1) after the maintainer merges of main with topology.brep_p1 and intersect.curve_class_p2 partial (SmCurveClassification::UpdatePointClass wired to curve_class_p2; its 35 deferrals now run: 28 pass, 7 move on to topology.edge / topology.vertex). With topology.brep_p1 alone: 366 pass, 68 deferred (GetInfiniteRegion, GetEdges, GetFaces, GetSurfaces, AddDeletedSurface wired to topology::brep::p1; the PR's m_bEditingEnabled co-port dropped for brep_p1's; the runner now sets each face surface's owner to its face as the upstream build does, without which GetSurfaces selected nothing and the 2 IsPointVisible cases failed; they now defer at surface.surface_p2 / topology.face_p3); before that merge 309 pass, 125 deferred (topology.brep_p1 67); --perturb SENSITIVE 309/309 (pre-merge); probe 3000 seeds clean (before the review round). The FFI guard accepts every int64 hint (C++: 0 none, 1 visible, other invisible) and either order of fa/fb (SmExtent1d swaps). The invisible-hint fixtures read the scene's infinite region (topology.brep_p1, wired at the merge). SmEdgeuse::EvaluateBinormal is wired to topology.edgeuse_p3 (on the scene itself, bUse3DOnly FALSE), and SmBrep::GetEdges without a filter to topology::brep::p1::edges. Of the 309 passes, 259 compare values (construction state, 1-interval classifications, single-piece ComputeEdgeVisibility, silhouette lists; status/flag records for the wire, lamina and listed early-outs) and 50 are both-errored (24 inexact_geometry, 25 rejected UB-169 guard cases, 1 rejected fixture). Classifications with 2 or more intervals: 21 generated (crossing wire-loop fixtures), all deferred: InsertPointClass splits through InsertAt / EditIntervalMin / EditIntervalMax (intersect.curve_class_p2, deferred before its merge; see the counts above); likewise the 13 multi-piece ComputeEdgeVisibility cases. The scene B-rep's SmBrep copy + Transform (topology.brep_p3) and its box (query.brep) are stubbed in Hcr::with_transform (FindAndSetInfiniteRegion is wired to topology.brep_p5 since PR #285); the runner imports the C++ scene and uses Hcr::from_scene. ComputeSilhouettes / ComputeGlobalVisibility / IsPointVisible start with topology.brep_p1 queries (only IsPointVisible is generated; it defers). SmFace::FindIfRectangularTrim is wired to topology.face_p1 (IsPointVisible's surface set; not reached while IsPointVisible defers on topology.brep_p1). CO-PORTED SmCurveClassification constructor (see intersect.curve_class_p4). Upstream defects UB-169, UB-170. hazards: ptr-hash (GetSilhouetteEdges sorted by id) Re-run after wiring stale stubs (PR #285): 434 cases, 396 pass, 0 fail, 38 deferred, max_rel_err 0. UPDATE (PR #204, topology.brep_p3): the UNPORTED(topology.brep_p3) copy constructor stub is wired to topology::brep::p3::copy_brep (no maps, bCopyAttributes and bAddAnalytics TRUE); Hcr::new and Hcr::with_transform take k: &mut Kernel, the caller's NotifyEnv and a source_key (the copy's objects under env.key_of, the source's under source_key, as api_brep_copy). The runner uses Hcr::from_scene only: report unchanged (434 cases, 394 pass, 0 fail, 40 deferred). API EXTENDED in sweep.topology_sweep (PR #222): Hcr::scene_brep_mut exposes the retained session scene for mutable topology-source borrows without cloning its identities. RE-RUN after intersect.ray_tracer wired RayTracer::add_surface_to_grid (the ray surfaces go through it): 434 cases, 396 pass, 0 fail, 38 deferred (topology.edge 37, topology.face_p2 1), max_rel_err 0; the CI report of main 03cfd79 was 386 pass, 48 deferred (surface.surface_p2 10): those 10 now run and match the C++. UPDATE 2026-10-23 (branch claude/edge-handles): blend::hcr::Geometry implements ClassGeometry::topology_entity / topology_handle for its vertex, edge and region handles, so the 37 SmEdge::GetVertex deferrals resolve (re-run after merging main with the ray-tracer change): PARTIAL blend.hcr cases=434 passed=431 failed=0 deferred=3 max_rel_err=0e0 with_values=403 no_values=28 vacuous=0 blocked_by=topology.face_p2(3). UPDATE (PR #679): Hcr::with_transform transforms the scene copy through topology::brep::p5::transform_with_notifications under the scene's notification environment. The runner builds its scenes with Hcr::from_scene, so its counts are unchanged (434 cases, 431 pass, 3 deferred)." diff --git a/plan/units/boolean.merge_p1.toml b/plan/units/boolean.merge_p1.toml index 723b2ac446..ce533fe90d 100644 --- a/plan/units/boolean.merge_p1.toml +++ b/plan/units/boolean.merge_p1.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_poin status = "partial" claimed_by = "" claimed_at = "" -notes = "CO-PORTED in boolean.api_brep (PR #81): SmMergeOptions (fields, ctor, defaults, copy ctor, getters/setters) -> solidcore::boolean::merge::p1::MergeOptions (crates/solidcore/src/boolean/merge/p1.rs; extend it, keep boolean/merge.rs as the split root); 24 fns; largest: SmMerge::BooleanPostProcess, SmMerge::FixFoundFaceCoincidence, sm_FindBrepRegionOfOtherFace, SmMerge::PropagateOtherSrcRegionAttribs; cycle back-edges (call into later units; stub or co-port): blend.fillet_executive_p1(1); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit; PR #163: merge control, face coincidence fast path, mapped vertex copy with topology/attribute notification, cleanup and region-attribute propagation; cross-B-rep radial classification keeps operand ids separate. Difftest 248 cases (152 fixtures + 96 generated), 175 passed, 73 deferred, 0 failed, max_rel_err=0, no absolute floors. Deferred: query.brep(14), topology.brep_p1(33), topology.brep_p9(3), topology.edgeuse_p2(20), topology.face_p2(3). DeleteEdge/DeleteVertex/DeleteTopologicalVertex remain tagged back-edges; scoped translation waits for query.brep bounding boxes; intersector construction is supplied by its owner. Wired to topology.shell (PR #154): SmRegion::IsVoid in sm_EdgeBordersSolid, SmShell::IsWireShell/IsFaceuseShell in BooleanPostProcess, region propagation takes a RegionLookup for the source regions' IsVoid. The wire/face shell combine block of BooleanPostProcess is wired end to end (GetCommonVertices = the B-rep vertices with an other mate in the shared mates, as TopologyIntersector::common_vertices after PR #146; then ignored-status shell::transfer_children and shell::region_remove_shell; boolean_post_process takes &mut Kernel for the mark lock) and is unreachable today because brep_shells / brep_vertices are UNPORTED(topology.brep_p1). No UNPORTED(boolean.merge_p1) callers on main. Review fixes: SM_USE_OLDTOL vertex tolerance growth restored; face tolerance growth calls a topology.face_p3 stub with old tolerance + dist_between. GetEdges/GetShells use topology.brep_p1 stubs (including count-only queries); no arena-order stand-ins. Reuses public topology_traverser::face_edges with per-loop uniqueness, stale-vertex sanity bound and unconditional merged-domain restore. Mapped/new vertex notification identities derive from ids; new MakeShellVertex path calls the intersector Relate (boolean::topology_intersector::p1::Relations::relate_vertices, wired in PR #146; the other operand is now mutable). merge_p5 now uses shared mutable bidirectional mates and the real MergeVertex adapter. 16 directed unequal-tolerance cases pass. Independent-operand radial queries and mated face-region cases are covered; full step-off numerics remain deferred, two cross-operand wire-sector cases compare real topology. Relate wired, not certified (PR #146): Relations::relate_vertices follows MakeShellVertex. PR #183 back-edge: MergeVertex calls topology::brep::p9::make_shell_vertex with its existing arena ids and source point; VertexCopy destination identities cover vertices and vertexuses, preserving caller-owned notification state. [topology.edgeuse_p3 merged (PR #117): the step-offs of RadialSectorClassify may create UV trim curves, so radial_sector_classify_between / find_radial_sector_between take `&mut Brep` and the other operand as `Option<&mut Brep>` (`None` = the same B-rep, replacing the pointer-equality test), and find_brep_region_of_other_face takes both operands `&mut`. 248 cases, 177 pass, 0 failed, 71 deferred (topology.brep_p1 33, intersect.surface_drop_lib_p3 18, query.brep 14, topology.brep_p9 3, topology.face_p2 3); the 20 topology.edgeuse_p2 step-off deferrals now run, 2 pass and 18 defer further on SmSurface::DropCurve] UPDATE (PR #145, topology.brep_p1): GetEdges/GetShells call topology::brep::p1 (brep_shells and boolean_post_process take &mut Kernel: upstream GetShells takes SmNewMarkAndLock on the context); GetShells returns the empty list on a B-rep without regions and still stops at SmRegion::GetShells otherwise, so the 33 topology.brep_p1 cases continue to the next stub (topology.shell 26, boolean.topology_intersector_p3 4, topology.face_p2 3): 248 cases, 153 pass, 0 failed, 95 deferred (topology.shell 48, topology.edgeuse_p2 20, query.brep 14, topology.face_p2 6, boolean.topology_intersector_p3 4, topology.brep_p9 3). After topology.shell (#154) merged and topology.brep_p1's GetShells is wired to it (SmRegion::GetShells under the mark lock): 248 cases, 201 pass, 0 failed, 47 deferred (topology.edgeuse_p2 20, query.brep 14, topology.face_p2 6, boolean.topology_intersector_p3 4, topology.brep_p9 3). Rerun with PR #183 (topology.brep_p9 wired, 7da151aa): 248 cases, 203 pass, 0 failed, 45 deferred (intersect.surface_drop_lib_p3 18, query.brep 14, topology.face_p2 6, boolean.topology_intersector_p3 4, topology.brep_p2 3); the 3 former topology.brep_p9 deferrals reach MakeShellVertexTopology (topology.brep_p2), and 2 earlier topology.edgeuse_p2 step-offs now pass. Re-run in PR #149 (review 5578349117 round, merged with main through fb58ea76; topology.brep_p2's UNPORTED stubs wired): 248 cases, 206 pass, 0 fail, 42 deferred (intersect.surface_drop_lib_p3 18, query.brep 14, topology.face_p2 6, boolean.topology_intersector_p3 4); the 3 MakeShellVertexTopology deferrals pass. graph() now numbers every record in the C++ shim's walk order (Canon::of: regions, then GetEdges, then the per-kind stages), and op 3 reports the copied vertex by that number and reads its flags and attribute through its own key (plan/findings.md, topology.brep_p2). UPDATE (PR #204, topology.brep_p3; overseer decision): the shared difftest graph() / Canon::of (this unit's runner, used by others) number, compare and count only the records the walk reaches from the B-rep's roots, as the C++ export walk emits; records upstream deletes stay in the Rust arenas unlinked (topology.brep_p2's deviation) and were compared before. The unreached count is a report diagnostic (Output::diagnostics, unreached_records, never compared). Unit test graph_compares_only_reachable_records (an unlinked record leaves graph() unchanged, a still-linked extra vertex use changes it). See plan/findings.md Owner zone-tol cache (surface/owner-zone-tol): 248 cases, 212 pass, 0 fail, 36 deferred." +notes = "CO-PORTED in boolean.api_brep (PR #81): SmMergeOptions (fields, ctor, defaults, copy ctor, getters/setters) -> solidcore::boolean::merge::p1::MergeOptions (crates/solidcore/src/boolean/merge/p1.rs; extend it, keep boolean/merge.rs as the split root); 24 fns; largest: SmMerge::BooleanPostProcess, SmMerge::FixFoundFaceCoincidence, sm_FindBrepRegionOfOtherFace, SmMerge::PropagateOtherSrcRegionAttribs; cycle back-edges (call into later units; stub or co-port): blend.fillet_executive_p1(1); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit; PR #163: merge control, face coincidence fast path, mapped vertex copy with topology/attribute notification, cleanup and region-attribute propagation; cross-B-rep radial classification keeps operand ids separate. Difftest 248 cases (152 fixtures + 96 generated), 175 passed, 73 deferred, 0 failed, max_rel_err=0, no absolute floors. Deferred: query.brep(14), topology.brep_p1(33), topology.brep_p9(3), topology.edgeuse_p2(20), topology.face_p2(3). DeleteEdge/DeleteVertex/DeleteTopologicalVertex remain tagged back-edges; scoped translation waits for query.brep bounding boxes; intersector construction is supplied by its owner. Wired to topology.shell (PR #154): SmRegion::IsVoid in sm_EdgeBordersSolid, SmShell::IsWireShell/IsFaceuseShell in BooleanPostProcess, region propagation takes a RegionLookup for the source regions' IsVoid. The wire/face shell combine block of BooleanPostProcess is wired end to end (GetCommonVertices = the B-rep vertices with an other mate in the shared mates, as TopologyIntersector::common_vertices after PR #146; then ignored-status shell::transfer_children and shell::region_remove_shell; boolean_post_process takes &mut Kernel for the mark lock) and is unreachable today because brep_shells / brep_vertices are UNPORTED(topology.brep_p1). No UNPORTED(boolean.merge_p1) callers on main. Review fixes: SM_USE_OLDTOL vertex tolerance growth restored; face tolerance growth calls a topology.face_p3 stub with old tolerance + dist_between. GetEdges/GetShells use topology.brep_p1 stubs (including count-only queries); no arena-order stand-ins. Reuses public topology_traverser::face_edges with per-loop uniqueness, stale-vertex sanity bound and unconditional merged-domain restore. Mapped/new vertex notification identities derive from ids; new MakeShellVertex path calls the intersector Relate (boolean::topology_intersector::p1::Relations::relate_vertices, wired in PR #146; the other operand is now mutable). merge_p5 now uses shared mutable bidirectional mates and the real MergeVertex adapter. 16 directed unequal-tolerance cases pass. Independent-operand radial queries and mated face-region cases are covered; full step-off numerics remain deferred, two cross-operand wire-sector cases compare real topology. Relate wired, not certified (PR #146): Relations::relate_vertices follows MakeShellVertex. PR #183 back-edge: MergeVertex calls topology::brep::p9::make_shell_vertex with its existing arena ids and source point; VertexCopy destination identities cover vertices and vertexuses, preserving caller-owned notification state. [topology.edgeuse_p3 merged (PR #117): the step-offs of RadialSectorClassify may create UV trim curves, so radial_sector_classify_between / find_radial_sector_between take `&mut Brep` and the other operand as `Option<&mut Brep>` (`None` = the same B-rep, replacing the pointer-equality test), and find_brep_region_of_other_face takes both operands `&mut`. 248 cases, 177 pass, 0 failed, 71 deferred (topology.brep_p1 33, intersect.surface_drop_lib_p3 18, query.brep 14, topology.brep_p9 3, topology.face_p2 3); the 20 topology.edgeuse_p2 step-off deferrals now run, 2 pass and 18 defer further on SmSurface::DropCurve] UPDATE (PR #145, topology.brep_p1): GetEdges/GetShells call topology::brep::p1 (brep_shells and boolean_post_process take &mut Kernel: upstream GetShells takes SmNewMarkAndLock on the context); GetShells returns the empty list on a B-rep without regions and still stops at SmRegion::GetShells otherwise, so the 33 topology.brep_p1 cases continue to the next stub (topology.shell 26, boolean.topology_intersector_p3 4, topology.face_p2 3): 248 cases, 153 pass, 0 failed, 95 deferred (topology.shell 48, topology.edgeuse_p2 20, query.brep 14, topology.face_p2 6, boolean.topology_intersector_p3 4, topology.brep_p9 3). After topology.shell (#154) merged and topology.brep_p1's GetShells is wired to it (SmRegion::GetShells under the mark lock): 248 cases, 201 pass, 0 failed, 47 deferred (topology.edgeuse_p2 20, query.brep 14, topology.face_p2 6, boolean.topology_intersector_p3 4, topology.brep_p9 3). Rerun with PR #183 (topology.brep_p9 wired, 7da151aa): 248 cases, 203 pass, 0 failed, 45 deferred (intersect.surface_drop_lib_p3 18, query.brep 14, topology.face_p2 6, boolean.topology_intersector_p3 4, topology.brep_p2 3); the 3 former topology.brep_p9 deferrals reach MakeShellVertexTopology (topology.brep_p2), and 2 earlier topology.edgeuse_p2 step-offs now pass. Re-run in PR #149 (review 5578349117 round, merged with main through fb58ea76; topology.brep_p2's UNPORTED stubs wired): 248 cases, 206 pass, 0 fail, 42 deferred (intersect.surface_drop_lib_p3 18, query.brep 14, topology.face_p2 6, boolean.topology_intersector_p3 4); the 3 MakeShellVertexTopology deferrals pass. graph() now numbers every record in the C++ shim's walk order (Canon::of: regions, then GetEdges, then the per-kind stages), and op 3 reports the copied vertex by that number and reads its flags and attribute through its own key (plan/findings.md, topology.brep_p2). UPDATE (PR #204, topology.brep_p3; overseer decision): the shared difftest graph() / Canon::of (this unit's runner, used by others) number, compare and count only the records the walk reaches from the B-rep's roots, as the C++ export walk emits; records upstream deletes stay in the Rust arenas unlinked (topology.brep_p2's deviation) and were compared before. The unreached count is a report diagnostic (Output::diagnostics, unreached_records, never compared). Unit test graph_compares_only_reachable_records (an unlinked record leaves graph() unchanged, a still-linked extra vertex use changes it). See plan/findings.md Owner zone-tol cache (surface/owner-zone-tol): 248 cases, 212 pass, 0 fail, 36 deferred. UPDATE (PR #679): the runner's SmTemporaryBrepTransform probe applies and restores the shift through topology::brep::p5::transform. difftest 248 cases, 243 pass, 0 failed, 5 deferred (MergeCoincidentSurfaces 4, topology.face_p2 1); the 2 SmBrep::Transform deferrals pass." diff --git a/plan/units/boolean.topology_intersector_p2.toml b/plan/units/boolean.topology_intersector_p2.toml index 0cc11af3e4..e2c5cc324b 100644 --- a/plan/units/boolean.topology_intersector_p2.toml +++ b/plan/units/boolean.topology_intersector_p2.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_analytic_tsurf_creation", "prog_test:my_test_get_loo status = "partial" claimed_by = "" claimed_at = "" -notes = "partial (PR #203, on main's #146): difftest 905 cases (9 upstream-derived fixtures + 32 edge cases built in code + 800 generated two-B-rep scripts on part 1's script runner + 16 CloseIntersectionLoop edge cases + 48 seeded CloseIntersectionLoop scripts), 728 pass bit-identical (max_rel_err 0), 0 fail, 177 deferred (topology.face_p2 75: SmFace::GetTolerance (2 of them iir_negative_tol/iir_tiny_tol, formerly topology.brep_p2 SmBrep::GetMinEdgeBBoxSize before #149; 3 of them formerly topology.face_p4 GetUpwardLoopuses before #239) and query.brep 47: SmBrep::CalculateBoundingBox (41 in IntersectTopology/IntersectInsertRelate after SmBrep::GetFaces (wired to topology.brep_p1), 6 in CloseIntersectionLoop's BrepCurveSolve); topology.face_p4 31: SmFace::GetEdges/GetVertices/GetBrep (part 1's IntersectorBrep stubs, unwired since #239 landed); topology.face_p3 12: FaceIntersect; topology.brep_p3 8 / topology.brep_p1 4: SqueezeEdge / (since #149 wired SmBrep::Notify) m_pVertexListHead inside CombineCoincidentVertices (wired to topology.brep_p5); the 3 former m_bEditingEnabled deferrals pass since #285), 2 cases_both_errored (scripts cut at op 0), so 726 real passes; FUZZ-PASS 2000 (crashes/panics/timeouts 0). What passes cover (per op occurrence, non-deferred cases): RemoveRelationship 29 success / 35 SM_ERR; CheckForSqueezeVertices 75 success without a conflicting mate (empty lists) / 26 SM_ERR (existing relationship better), a conflict that combines defers inside CombineCoincidentVertices; DeleteFaces 68 success, all with an empty face list; FixGaps 119 success without a gap pair / 14 SM_ERR_NULL_POINTER (gap pair with an unmated vertex), a pair that combines defers inside CombineCoincidentVertices; SetRelationships 39 success on managers without definitions; FixupClassMates 56 success / 17 SM_ERR (sizes differ); FixupSplitVertexPair 57 success, 6 of them removing the interval; EdgeEdgeIntersect 136 success (9 more since main's topology.brep_p9), none with a non-empty first split list; EdgeFaceCoincidence 128 success, none imprinting; CloseIntersectionLoop 28 success without new topology / 19 success relating an edge pair (the single-common-edge drop) / 11 SM_ERR_NULL_POINTER (null or unmated vertex, no maps), the coincidence path defers (BrepCurveSolve, then MergeCurveClasses): no pass reaches BrepCurveSolve, IIREdgeBrep or makes new topology; IntersectTopology 1 success (the smTopoTest pair marked as not intersecting), IntersectInsertRelate none (SmFace::GetTolerance / SmBrep::CalculateBoundingBox). RegisterExistingSSI is now run by the port in part 1's script (op 16; a non-B-spline 3D curve, UB-184, is never registered). Also UB-175 [PSIRT] (~SmProtoTopologyManager frees proto edges before the vertices that still list them; the shim runs a verbatim IntersectInsertRelate copy that unlinks them first and answers -2, the port returns InvalidInput) and HANG-25 (IntersectInsertRelate at tolerance 0, refused by the FFI); op 47 also has a measured time-budget guard |approx| <= 0.1 (defined input, not UB; plan/findings.md). UNPORTED stubs added: topology.brep_p3 (DeleteFace), topology.brep_p4 (DeleteEdge/DeleteVertex), query.face_p1 (SmFace::Point3DClassify); part 1's stubs reused: topology.face_p4 (GetEdges/GetVertices/GetBrep), topology.face_p3 (FaceIntersect); CombineCoincidentVertices wired to topology.brep_p5 through IntersectorBrepP2::brep_combine_coincident_vertices (so check_for_squeeze_vertices and fix_gaps take &mut Kernel; follow-up to #203); MakeEdgeInFace goes through part 1's brep_p5 backend; GlueEdgesGeneral (topology.brep_p7, merged in #155) goes through IntersectorBrep::brep_glue_edges_general, so edge_face_coincidence takes &mut Kernel. SmBrep::GetFaces wired to topology.brep_p1 through IntersectorBrepP2::brep_faces (follow-up to #203). Back-edge wired: UNPORTED(boolean.topology_intersector_p2) RemoveRelationship in topology::proto_topology::p3 (GlueCoincidentBrepVertices) through the Mates trait (boolean::topology_intersector::p2::MapMates). CO-PORTED in boolean.topology_intersector_p2 (PR #281): SmTopologyIntersector::CloseIntersectionLoop (SmTopologyIntersector.cpp:6108-6354, called from SmMerge.cpp:2958) and its only callee SmTopologyIntersector::IIREdgeBrep (:2836-2971), both listed by no unit in harness/callgraph/units.json -> boolean::topology_intersector::p2::TopologyIntersector::{close_intersection_loop, iir_edge_brep}, wired into boolean.merge_p4's KernelManifoldIntersector in the same PR; IIREdgeBrep's MergeCurveClasses stays UNPORTED(boolean.topology_intersector_p3) (ported as TopologyIntersector::merge_curve_classes in draft #211). The two constructors were CO-PORTED in boolean.topology_intersector_p1 (PR #146) -> boolean::topology_intersector::p1::TopologyIntersector::{with_context, new}. Downstream: boolean.topology_intersector_p3 (#211) builds on this branch. 12 fns + 2 co-ported; largest: IntersectTopology, EdgeFaceCoincidence, FixupSplitVertexPair, IntersectInsertRelate; hazards: ptr-hash (none in this part's outputs), tbb (SM_USE_TBB blocks not built) Re-run in PR #149 (review 5578349117 round, merged with main through fb58ea76; topology.brep_p2's UNPORTED stubs wired): 841 cases, 666 pass, 0 fail, 175 deferred; its 2 SmBrep::Notify deferrals relabel to topology.brep_p1. UPDATE (PR #204, topology.brep_p3): the UNPORTED(topology.brep_p3) brep_delete_face stub is removed; DeleteFaces calls IntersectorBrep::brep_delete_face (boolean.topology_intersector_p1's hook, SmBrep::DeleteFace with the defaults FALSE, TRUE, NULL, NULL that this call passes), so delete_faces takes k: &mut Kernel. Still behind SmFace::GetEdges (topology.face_p4): report unchanged (841 cases, 666 pass, 0 fail, 175 deferred). Re-run in PR #204 (07c89fab, main through e5f3e441): 841 cases, 667 pass, 0 fail, 174 deferred; case by case the 8 SmBrep::SqueezeEdge (topology.brep_p3) deferrals relabel to topology.brep_p1 SmBrep::UpdateVertexEdgeIntervals (the squeeze is wired), no outcome change Owner zone-tol cache (surface/owner-zone-tol): 910 cases, 736 pass, 0 fail, 174 deferred. FaceIntersect wired (2026-10-23): face_face_intersect_curves runs through IntersectorBrep::face_intersect; 910 cases, 775 passes (was 773), 0 failed, 135 deferred (topology.face_p3 85 -> 0; now topology.face_p2 68, topology.edge 40, topology.brep_p1 13, query.face_p2 6, topology.vertex 6, query.face_p1 1, topology.edgeuse_p3 1)." +notes = "partial (PR #203, on main's #146): difftest 905 cases (9 upstream-derived fixtures + 32 edge cases built in code + 800 generated two-B-rep scripts on part 1's script runner + 16 CloseIntersectionLoop edge cases + 48 seeded CloseIntersectionLoop scripts), 728 pass bit-identical (max_rel_err 0), 0 fail, 177 deferred (topology.face_p2 75: SmFace::GetTolerance (2 of them iir_negative_tol/iir_tiny_tol, formerly topology.brep_p2 SmBrep::GetMinEdgeBBoxSize before #149; 3 of them formerly topology.face_p4 GetUpwardLoopuses before #239) and query.brep 47: SmBrep::CalculateBoundingBox (41 in IntersectTopology/IntersectInsertRelate after SmBrep::GetFaces (wired to topology.brep_p1), 6 in CloseIntersectionLoop's BrepCurveSolve); topology.face_p4 31: SmFace::GetEdges/GetVertices/GetBrep (part 1's IntersectorBrep stubs, unwired since #239 landed); topology.face_p3 12: FaceIntersect; topology.brep_p3 8 / topology.brep_p1 4: SqueezeEdge / (since #149 wired SmBrep::Notify) m_pVertexListHead inside CombineCoincidentVertices (wired to topology.brep_p5); the 3 former m_bEditingEnabled deferrals pass since #285), 2 cases_both_errored (scripts cut at op 0), so 726 real passes; FUZZ-PASS 2000 (crashes/panics/timeouts 0). What passes cover (per op occurrence, non-deferred cases): RemoveRelationship 29 success / 35 SM_ERR; CheckForSqueezeVertices 75 success without a conflicting mate (empty lists) / 26 SM_ERR (existing relationship better), a conflict that combines defers inside CombineCoincidentVertices; DeleteFaces 68 success, all with an empty face list; FixGaps 119 success without a gap pair / 14 SM_ERR_NULL_POINTER (gap pair with an unmated vertex), a pair that combines defers inside CombineCoincidentVertices; SetRelationships 39 success on managers without definitions; FixupClassMates 56 success / 17 SM_ERR (sizes differ); FixupSplitVertexPair 57 success, 6 of them removing the interval; EdgeEdgeIntersect 136 success (9 more since main's topology.brep_p9), none with a non-empty first split list; EdgeFaceCoincidence 128 success, none imprinting; CloseIntersectionLoop 28 success without new topology / 19 success relating an edge pair (the single-common-edge drop) / 11 SM_ERR_NULL_POINTER (null or unmated vertex, no maps), the coincidence path defers (BrepCurveSolve, then MergeCurveClasses): no pass reaches BrepCurveSolve, IIREdgeBrep or makes new topology; IntersectTopology 1 success (the smTopoTest pair marked as not intersecting), IntersectInsertRelate none (SmFace::GetTolerance / SmBrep::CalculateBoundingBox). RegisterExistingSSI is now run by the port in part 1's script (op 16; a non-B-spline 3D curve, UB-184, is never registered). Also UB-175 [PSIRT] (~SmProtoTopologyManager frees proto edges before the vertices that still list them; the shim runs a verbatim IntersectInsertRelate copy that unlinks them first and answers -2, the port returns InvalidInput) and HANG-25 (IntersectInsertRelate at tolerance 0, refused by the FFI); op 47 also has a measured time-budget guard |approx| <= 0.1 (defined input, not UB; plan/findings.md). UNPORTED stubs added: topology.brep_p3 (DeleteFace), topology.brep_p4 (DeleteEdge/DeleteVertex), query.face_p1 (SmFace::Point3DClassify); part 1's stubs reused: topology.face_p4 (GetEdges/GetVertices/GetBrep), topology.face_p3 (FaceIntersect); CombineCoincidentVertices wired to topology.brep_p5 through IntersectorBrepP2::brep_combine_coincident_vertices (so check_for_squeeze_vertices and fix_gaps take &mut Kernel; follow-up to #203); MakeEdgeInFace goes through part 1's brep_p5 backend; GlueEdgesGeneral (topology.brep_p7, merged in #155) goes through IntersectorBrep::brep_glue_edges_general, so edge_face_coincidence takes &mut Kernel. SmBrep::GetFaces wired to topology.brep_p1 through IntersectorBrepP2::brep_faces (follow-up to #203). Back-edge wired: UNPORTED(boolean.topology_intersector_p2) RemoveRelationship in topology::proto_topology::p3 (GlueCoincidentBrepVertices) through the Mates trait (boolean::topology_intersector::p2::MapMates). CO-PORTED in boolean.topology_intersector_p2 (PR #281): SmTopologyIntersector::CloseIntersectionLoop (SmTopologyIntersector.cpp:6108-6354, called from SmMerge.cpp:2958) and its only callee SmTopologyIntersector::IIREdgeBrep (:2836-2971), both listed by no unit in harness/callgraph/units.json -> boolean::topology_intersector::p2::TopologyIntersector::{close_intersection_loop, iir_edge_brep}, wired into boolean.merge_p4's KernelManifoldIntersector in the same PR; IIREdgeBrep's MergeCurveClasses stays UNPORTED(boolean.topology_intersector_p3) (ported as TopologyIntersector::merge_curve_classes in draft #211). The two constructors were CO-PORTED in boolean.topology_intersector_p1 (PR #146) -> boolean::topology_intersector::p1::TopologyIntersector::{with_context, new}. Downstream: boolean.topology_intersector_p3 (#211) builds on this branch. 12 fns + 2 co-ported; largest: IntersectTopology, EdgeFaceCoincidence, FixupSplitVertexPair, IntersectInsertRelate; hazards: ptr-hash (none in this part's outputs), tbb (SM_USE_TBB blocks not built) Re-run in PR #149 (review 5578349117 round, merged with main through fb58ea76; topology.brep_p2's UNPORTED stubs wired): 841 cases, 666 pass, 0 fail, 175 deferred; its 2 SmBrep::Notify deferrals relabel to topology.brep_p1. UPDATE (PR #204, topology.brep_p3): the UNPORTED(topology.brep_p3) brep_delete_face stub is removed; DeleteFaces calls IntersectorBrep::brep_delete_face (boolean.topology_intersector_p1's hook, SmBrep::DeleteFace with the defaults FALSE, TRUE, NULL, NULL that this call passes), so delete_faces takes k: &mut Kernel. Still behind SmFace::GetEdges (topology.face_p4): report unchanged (841 cases, 666 pass, 0 fail, 175 deferred). Re-run in PR #204 (07c89fab, main through e5f3e441): 841 cases, 667 pass, 0 fail, 174 deferred; case by case the 8 SmBrep::SqueezeEdge (topology.brep_p3) deferrals relabel to topology.brep_p1 SmBrep::UpdateVertexEdgeIntervals (the squeeze is wired), no outcome change Owner zone-tol cache (surface/owner-zone-tol): 910 cases, 736 pass, 0 fail, 174 deferred. FaceIntersect wired (2026-10-23): face_face_intersect_curves runs through IntersectorBrep::face_intersect; 910 cases, 775 passes (was 773), 0 failed, 135 deferred (topology.face_p3 85 -> 0; now topology.face_p2 68, topology.edge 40, topology.brep_p1 13, query.face_p2 6, topology.vertex 6, query.face_p1 1, topology.edgeuse_p3 1). UPDATE (PR #679, UpdateVertexEdgeIntervals wired in CombineCoincidentVertices): 910 cases, 788 pass, 0 failed, 122 deferred; the 13 UpdateVertexEdgeIntervals deferrals pass." diff --git a/plan/units/boolean.topology_intersector_p3.toml b/plan/units/boolean.topology_intersector_p3.toml index a9c31412b6..8a2d1936a5 100644 --- a/plan/units/boolean.topology_intersector_p3.toml +++ b/plan/units/boolean.topology_intersector_p3.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_bbu", "SM_API_test:TestSmSurfaceSurfaceFillet", "SM_ status = "partial" claimed_by = "" claimed_at = "" -notes = "PARTIAL (PR #211): MergeCurveClasses, MergeCurveOnSurfaces and MergeCoincidentSurfaces ported in full. query.face_p1 and topology.face_p4 (partial) are wired: SmFace::Point3DClassify, CalculateBoundingBox (bTight FALSE), GetEdges and GetUpwardLoopuses are IntersectorBrepP3 hooks the adapter implements with query::face::p1 / topology::face::p4; dCurveTolerance reaches the point classifications as SmZoneTol3d (zone_tol3d). One stub, for a call into a unit still todo: topology.brep_p4 DeleteEdge (#441). On main (#146, #203, #85, #137, and the curve_class topology wiring #347 / #365 merged); CheckForSqueezeVertices combines coincident vertices (#274), so IntersectorBrepP3 extends IntersectorBrepP2 and the merges take &mut Kernel. ClassGeometry::topology_handle_in lets the two-B-rep adapter name handles in main's curve_class helpers. difftest (linux/arm64 container): 271 cases (256 generated + 15 inline), 166 passed with values, 0 failed, 105 deferred (intersect.curve_class_p6 57, topology.vertex 27, intersect.surface_drop_curve 15, topology.brep_p3 6); --perturb SENSITIVE 166/166; --fuzz 2000 FUZZ-PASS (0 differs). Review r1 (tangency-monitor): the shim and runner refuse an SM_PC_UNKNOWN start or end of a face- or region-mid interval (MergeInterval leaves it without object; SmCurveClass.cpp:17659/:17672 dereference it); MergeCurveOnSurfaces takes UV trim curves (K_TRIM_CURVE, 3 inline cases, deferring at intersect.point_class's SmVertex::GetTolerance stub in MergeIntoObject); MergeCoincidentSurfaces' 3D edge-curve copy gets the edge curve's owner as upstream. The runner reuses the p1 runner's two-B-rep adapter and reorders imported vertex-use lists to upstream's list order (see plan/findings.md). Back-edges: blend.fillet_geom_p4's MergeCurveOnSurfaces stub retagged blend.fillet_executive_p1 (it runs on the executive's intersector); boolean.merge_p1's MergeCoincidentSurfaces stays UNPORTED(boolean.topology_intersector_p3) pending an IntersectorBrepP3 implementation for merge p4's ArenaPair (follow-up). 3 fns; largest: SmTopologyIntersector::MergeCurveClasses, SmTopologyIntersector::MergeCoincidentSurfaces, SmTopologyIntersector::MergeCurveOnSurfaces; unit-SCC #22 (220 units); 110 upstream test entry points reach this unit" +notes = "PARTIAL (PR #211): MergeCurveClasses, MergeCurveOnSurfaces and MergeCoincidentSurfaces ported in full. query.face_p1 and topology.face_p4 (partial) are wired: SmFace::Point3DClassify, CalculateBoundingBox (bTight FALSE), GetEdges and GetUpwardLoopuses are IntersectorBrepP3 hooks the adapter implements with query::face::p1 / topology::face::p4; dCurveTolerance reaches the point classifications as SmZoneTol3d (zone_tol3d). One stub, for a call into a unit still todo: topology.brep_p4 DeleteEdge (#441). On main (#146, #203, #85, #137, and the curve_class topology wiring #347 / #365 merged); CheckForSqueezeVertices combines coincident vertices (#274), so IntersectorBrepP3 extends IntersectorBrepP2 and the merges take &mut Kernel. ClassGeometry::topology_handle_in lets the two-B-rep adapter name handles in main's curve_class helpers. difftest (linux/arm64 container): 271 cases (256 generated + 15 inline), 166 passed with values, 0 failed, 105 deferred (intersect.curve_class_p6 57, topology.vertex 27, intersect.surface_drop_curve 15, topology.brep_p3 6); --perturb SENSITIVE 166/166; --fuzz 2000 FUZZ-PASS (0 differs). Review r1 (tangency-monitor): the shim and runner refuse an SM_PC_UNKNOWN start or end of a face- or region-mid interval (MergeInterval leaves it without object; SmCurveClass.cpp:17659/:17672 dereference it); MergeCurveOnSurfaces takes UV trim curves (K_TRIM_CURVE, 3 inline cases, deferring at intersect.point_class's SmVertex::GetTolerance stub in MergeIntoObject); MergeCoincidentSurfaces' 3D edge-curve copy gets the edge curve's owner as upstream. The runner reuses the p1 runner's two-B-rep adapter and reorders imported vertex-use lists to upstream's list order (see plan/findings.md). Back-edges: blend.fillet_geom_p4's MergeCurveOnSurfaces stub retagged blend.fillet_executive_p1 (it runs on the executive's intersector); boolean.merge_p1's MergeCoincidentSurfaces stays UNPORTED(boolean.topology_intersector_p3) pending an IntersectorBrepP3 implementation for merge p4's ArenaPair (follow-up). 3 fns; largest: SmTopologyIntersector::MergeCurveClasses, SmTopologyIntersector::MergeCoincidentSurfaces, SmTopologyIntersector::MergeCurveOnSurfaces; unit-SCC #22 (220 units); 110 upstream test entry points reach this unit UPDATE (PR #679, UpdateVertexEdgeIntervals wired in CombineCoincidentVertices): the runner numbers every post-merge handle and the state by a re-walk of both B-reps (boolean.merge_p1 Canon::reachable, the shim's walk_all), so records a squeeze deleted (unlinked, kept in the Rust arena) are unknown on both sides, as upstream. 271 cases, 172 pass, 0 failed, 99 deferred; the 6 UpdateVertexEdgeIntervals deferrals pass." diff --git a/plan/units/topology.brep_p10.toml b/plan/units/topology.brep_p10.toml index 663578e688..12b582cd1a 100644 --- a/plan/units/topology.brep_p10.toml +++ b/plan/units/topology.brep_p10.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_single_periodic_vertexuse", "SM_API_test:TestSmAdvan status = "partial" claimed_by = "" claimed_at = "" -notes = "#76 kernel-aware Surface dispatch (PR #313, 2026-10-21): 1404 cases, 922 pass, 0 failed, 482 deferred (surface.sphere 352, topology.face_p2 101, surface.surf_of_extrusion_p2 10, topology.face_p4 9, geom.tol 5, query.brep 5), max_rel_err 0; before 851 pass / 553 deferred. partial: cases=1404 passed=839 failed=0 deferred=565 max_rel_err=0 (surface.cone_p2 424: SmCone::IsNurbSurfaceCone in CopyAndAddAnalytics; topology.face_p2 63: SmFace::GetTolerance inside HealBrep; surface.cone_p1 41 / surface.surf_of_extrusion_p2 10: the Rust read of the file stops at the Surface::read_from_db dispatch gap (the cone_p1 label is a stale stub of surface/surface/p2.rs, not this unit's); topology.brep_p2 19: IsManifoldSolid, and SmBrep::Notify reached through the face use TestClosureSplitRegion of topology.brep_p6; topology.brep_p5 8: RemoveFaces / FindAndSetInfiniteRegion); --perturb SENSITIVE 839/839. Review fixes (PR #130): a non-SmBSplineCurve UV trim copy is not stored on the edge use (SM_CAST_PTR); crafted fixtures cover wire-edge/vertex shells, vertex loops, the analytic edge-curve end check and composite/B-spline UV curves (review_cases.json, 128 cases); adv_cases.json (192 review cases: manifold flags 3/-1, IGES empty shells, shared surfaces and curves with and without borrowed geometry, zone tolerance clamp limits, healer versions, pole/vertex edge uses mid-loop, relinked and overlapping loops, closure after relinking, a dropped UV curve, remove-all with editing disabled). Second review (after main merged topology.brep_p1 #145): dropped the duplicate co-ported m_bEditingEnabled (main's Brep field), new_with_infinite_region sets region_list_head (PreInsert); wired the topology.brep_p1 back-edges (sm_AddAttributes with TopologyEnv::all_attributes = rAllAttributes, HealBrep, GetRegions, GetFaces, GetWireEdges, CreateParallelProjectionCurves, sm_FindSideOfCurve) and topology.brep_p7's two UNPORTED(topology.brep_p10) sites (ReadFromFile, GlueEdgesGeneral). ProjectAndTrim and RemoveAllTopology (editing enabled) still defer at later units. 4 fns; cycle back-edges stubbed: topology.brep_p2 (IsManifoldSolid, MergeCurvesOnSurface, GetShellVertices, sm_GetPointFromSol), topology.brep_p4 (DeleteEdge, DeleteVertex), topology.brep_p5 (RemoveFaces, FindAndSetInfiniteRegion), topology.face_p4 (GetVertices, GetEdges), query.brep (CalculateBoundingBox). Harness-level deferral labels topology.brep_p10 (inexact .smb imports) remain in the topology.vertex, topology.edgeuse_p1/p2 and topology.loopuse runners (see findings) Batch integration (batch/clean-2026-10-19): RemoveFaces and FindAndSetInfiniteRegion() wired to topology::brep::p5 (remove_faces with the B-rep editing flag; find_and_set_infinite_region with the upstream defaults, which defers inside part 5). UPDATE after topology.brep_p2 (PR #149 follow-up): IsManifoldSolid (pBrepData->m_bManifold = IsManifoldSolid()), sm_GetPointFromSol and GetShellVertices are wired to topology::brep::p2; MergeCurvesOnSurface calls topology::brep::p2::merge_curves_on_surface (SmBrep.cpp:25074, header defaults) through an uninhabited MergeCurvesObjects world over the B-rep cache whose constructor is the UNPORTED(query.brep) stub: query.brep (todo) is the real blocker, as for topology.shell's shell_ray_sector (overseer ruling); test_closure_split_region takes the caller's NotifyEnv. 1404 cases, 851 pass, 0 fail, 553 deferred: 12 IsManifoldSolid deferrals now pass (201-16358 values each), 2 defer on topology.face_p2 SmFace::GetTolerance. Unit test healing_runs_and_manifold_is_computed (renamed) checks the UNSURE flag is computed (0 for a sheet). Re-run in PR #149 (review 5578349117 round, merged with main through fb58ea76; topology.brep_p2's UNPORTED stubs wired): 1404 cases, 851 pass, 0 fail, 553 deferred; 12 former topology.brep_p2 deferrals (SmBrep::IsManifoldSolid, Notify) pass (201-897 values), 2 defer on topology.face_p2 SmFace::GetTolerance, 5 Notify deferrals relabel to query.brep." +notes = "#76 kernel-aware Surface dispatch (PR #313, 2026-10-21): 1404 cases, 922 pass, 0 failed, 482 deferred (surface.sphere 352, topology.face_p2 101, surface.surf_of_extrusion_p2 10, topology.face_p4 9, geom.tol 5, query.brep 5), max_rel_err 0; before 851 pass / 553 deferred. partial: cases=1404 passed=839 failed=0 deferred=565 max_rel_err=0 (surface.cone_p2 424: SmCone::IsNurbSurfaceCone in CopyAndAddAnalytics; topology.face_p2 63: SmFace::GetTolerance inside HealBrep; surface.cone_p1 41 / surface.surf_of_extrusion_p2 10: the Rust read of the file stops at the Surface::read_from_db dispatch gap (the cone_p1 label is a stale stub of surface/surface/p2.rs, not this unit's); topology.brep_p2 19: IsManifoldSolid, and SmBrep::Notify reached through the face use TestClosureSplitRegion of topology.brep_p6; topology.brep_p5 8: RemoveFaces / FindAndSetInfiniteRegion); --perturb SENSITIVE 839/839. Review fixes (PR #130): a non-SmBSplineCurve UV trim copy is not stored on the edge use (SM_CAST_PTR); crafted fixtures cover wire-edge/vertex shells, vertex loops, the analytic edge-curve end check and composite/B-spline UV curves (review_cases.json, 128 cases); adv_cases.json (192 review cases: manifold flags 3/-1, IGES empty shells, shared surfaces and curves with and without borrowed geometry, zone tolerance clamp limits, healer versions, pole/vertex edge uses mid-loop, relinked and overlapping loops, closure after relinking, a dropped UV curve, remove-all with editing disabled). Second review (after main merged topology.brep_p1 #145): dropped the duplicate co-ported m_bEditingEnabled (main's Brep field), new_with_infinite_region sets region_list_head (PreInsert); wired the topology.brep_p1 back-edges (sm_AddAttributes with TopologyEnv::all_attributes = rAllAttributes, HealBrep, GetRegions, GetFaces, GetWireEdges, CreateParallelProjectionCurves, sm_FindSideOfCurve) and topology.brep_p7's two UNPORTED(topology.brep_p10) sites (ReadFromFile, GlueEdgesGeneral). ProjectAndTrim and RemoveAllTopology (editing enabled) still defer at later units. 4 fns; cycle back-edges stubbed: topology.brep_p2 (IsManifoldSolid, MergeCurvesOnSurface, GetShellVertices, sm_GetPointFromSol), topology.brep_p4 (DeleteEdge, DeleteVertex), topology.brep_p5 (RemoveFaces, FindAndSetInfiniteRegion), topology.face_p4 (GetVertices, GetEdges), query.brep (CalculateBoundingBox). Harness-level deferral labels topology.brep_p10 (inexact .smb imports) remain in the topology.vertex, topology.edgeuse_p1/p2 and topology.loopuse runners (see findings) Batch integration (batch/clean-2026-10-19): RemoveFaces and FindAndSetInfiniteRegion() wired to topology::brep::p5 (remove_faces with the B-rep editing flag; find_and_set_infinite_region with the upstream defaults, which defers inside part 5). UPDATE after topology.brep_p2 (PR #149 follow-up): IsManifoldSolid (pBrepData->m_bManifold = IsManifoldSolid()), sm_GetPointFromSol and GetShellVertices are wired to topology::brep::p2; MergeCurvesOnSurface calls topology::brep::p2::merge_curves_on_surface (SmBrep.cpp:25074, header defaults) through an uninhabited MergeCurvesObjects world over the B-rep cache whose constructor is the UNPORTED(query.brep) stub: query.brep (todo) is the real blocker, as for topology.shell's shell_ray_sector (overseer ruling); test_closure_split_region takes the caller's NotifyEnv. 1404 cases, 851 pass, 0 fail, 553 deferred: 12 IsManifoldSolid deferrals now pass (201-16358 values each), 2 defer on topology.face_p2 SmFace::GetTolerance. Unit test healing_runs_and_manifold_is_computed (renamed) checks the UNSURE flag is computed (0 for a sheet). Re-run in PR #149 (review 5578349117 round, merged with main through fb58ea76; topology.brep_p2's UNPORTED stubs wired): 1404 cases, 851 pass, 0 fail, 553 deferred; 12 former topology.brep_p2 deferrals (SmBrep::IsManifoldSolid, Notify) pass (201-897 values), 2 defer on topology.face_p2 SmFace::GetTolerance, 5 Notify deferrals relabel to query.brep. UPDATE (PR #679, UpdateVertexEdgeIntervals wired in CombineCoincidentVertices): 1404 cases, 1154 pass, 0 failed, 233 deferred (unchanged); the 3 UpdateVertexEdgeIntervals deferrals (read_Fillet_open_shell, read_Solids_fillet_ts, gen0115) now defer at intersect.curve_class_p4 GetLoopCrossings." diff --git a/plan/units/topology.brep_p5.toml b/plan/units/topology.brep_p5.toml index 5415dedc8e..3cf947927a 100644 --- a/plan/units/topology.brep_p5.toml +++ b/plan/units/topology.brep_p5.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_test_tsurf_cyl_creation_2d", "prog_test:my_test_tsurf_cyl status = "partial" claimed_by = "" claimed_at = "" -notes = "G10 (PR #637, 2026-10-23): face ChangeGeometry notification wired. difftest 797 cases, 787 pass, 0 failed, 10 deferred (topology.brep_p1 8, topology.brep_p4 2); the 2 former topology.face_p3 deferrals pass. PARTIAL (PR #194, functional source ecccb67b; official main 9a7c1ec0; Claude review fixes): all twelve owned editing control flows and pending-topology cleanup present; individual missing callees/types defer. Landed shell/region callees wired; stable shell, vertex and vertex-use record erasure remains the part 4 SmBrep destructor/storage integration gap. Native difftest: 797 cases (285 fixtures + 512 generated), 737 passed, 0 failed, 60 deferred (topology.brep_p1 10, topology.brep_p2 46, topology.brep_p4 2, topology.brep_p9 2), max_rel_err=0, no absolute floors. Meaningful successful topology-mutation paths certified: 0/12 operations (issue #210). Coverage is 409 point/tolerance-target-stage cases, 301 whole-operation refusals/empty/identity/alias no-ops, and 27 supporting notification scripts. Temporary-edge minimum-length threshold, successful topology mutation, nonidentity transforms, shared native geometry across distinct owning records and pending-reader integration remain uncertified. Sensitivity 737/737 detected; 4096 generated: 4375 cases, 4195 passed, 180 deferred, 0 failed. Formatting, both strict Clippy configurations, 1369 kernel tests, WASM, 3 native FFI tests and all twelve affected differential units pass; full green/partial sweep PASS: 205 units, 113 green, 92 partial, 0 failed. No open dependency PR bundled or other unit promoted. 14 fns; largest: SmBrep::RemoveTopologicalEdgesAndVertices, SmBrep::CombineCoincidentVertices, SmBrep::DeleteTopologicalVertex, SmBrep::GlueVertices; cycle back-edges (call into later units; stub or co-port): topology.brep_p3(8), topology.brep_p2(5), heal.stitch_p1(3), topology.brep_p9(3), heal.stitch_p2(2); unit-SCC #22 (220 units); hazards: ptr-iter; 200 upstream test entry points reach this unit Second review round (head e9df9346, merged official main with topology.brep_p1/p7 and heal.stitch_p1): isolated aliased CombineCoincidentVertices takes the native path (6 new fixtures, deferred at Notify; only the target stage refuses it, UB-191); Notify status ignored in combine as upstream; SqueezeFace unlock never hides its result; FFI guard refusals are rejected, not failed. Wired heal.stitch_p1 (GlueVertices, RemoveFaces, StitchAndOrient) and topology.brep_p7 (GlueVertices) into this unit. Calls into the now-partial topology.brep_p1 are still UNPORTED stubs pending a follow-up PR. Batch integration (batch/clean-2026-10-19): set_face_tolerance wired to topology::face::p3::set_tolerance (topology.face_p3). Re-run in PR #149 (review 5578349117 round, merged with main through fb58ea76; topology.brep_p2's UNPORTED stubs wired): 797 cases, 738 pass, 0 fail, 59 deferred (topology.brep_p1 38, topology.brep_p4 12, topology.brep_p3 7, topology.brep_p9 2); the 46 SmBrep::Notify deferrals: 1 pass, the rest relabel to brep_p1 GetVertices / UpdateVertexEdgeIntervals, brep_p4 DeleteVertex and brep_p3 TurnToNURBS. Re-run after wiring stale stubs (PR #285): 797 cases, 744 pass, 0 fail, 53 deferred, max_rel_err 0. UPDATE (PR #204, topology.brep_p3): the UNPORTED(topology.brep_p3) stubs SqueezeEdge, MakeWireEdgeVertex, DeleteFace, OrientTrimmedSurfaces and TurnToNURBS, and UNPORTED(topology.brep_p9) MakeShellVertex in create_wire_edge_from_curve, are wired to topology::brep::p3 / p9 through with_p3_notifications (the caller's environment, else a topology.brep_p2 ArenaNotifyState over the kernel context's callbacks lent with brep::p3::with_lent_context); make_manifold and stitch_and_orient take k: &mut Kernel. Case by case: no outcome change; the 7 TurnToNURBS deferrals relabel to topology.brep_p1 GetVertices (797 cases, 738 pass, 0 fail, 59 deferred: topology.brep_p1 45, topology.brep_p4 12, topology.brep_p9 2). UPDATE (PR #204, Codex review of 255d52b7): a plain entry point (no environment; the _with_notifications forms given None) runs its whole edit in one environment under the kernel's identities (plain_edit = topology::brep::p3::with_kernel_callbacks: the B-rep's serial in the kernel context's domain, so two B-reps' objects never share a key in one context; the callbacks lent to the edit; nested in another lent edit it is refused before anything changes). create_wire_edge_from_curve, make_manifold, remove_faces, stitch_and_orient and squeeze_face are plain_edit wrappers over *_in bodies; with_p3_notifications no longer falls back to ArenaNotifyState. UPDATE (PR #204, Codex re-check of 6b04141d): plain_edit's environment carries a default record for every existing object from the start (brep::p3::kernel_named_edit), so the notifications sent before any p3 edit (e.g. notify_edge_merge) are delivered. UPDATE (PR #204, merge of main #339): op 13 (CreateWireEdgeFromCurve) runs now that GetInfiniteRegion is wired; its 2 fixture cases (isolated_op13_editing_*) compared the vertex-shell vertex use MakeEdgeVertexTopology deletes upstream and the arena keeps unlinked; with graph() comparing reachable records only (boolean.merge_p1) they pass. API EXTENDED in sweep.topology_sweep (PR #222): make_edge_in_face_with_uv_curve preserves polymorphic UV curve kinds, including native SmBSplineCurve-derived lines; the existing B-spline-only Rust entry point delegates without changing behavior. UPDATE (PR #526): FindAndSetInfiniteRegion wired to topology.brep_p9 FindInfiniteRegion (through the caller's query::brep::BrepQuery) and topology.brep_p1 ResetInfiniteRegion; it takes the kernel and SetRegionVoidType. Re-run: 797 cases, 746 pass, 0 fail, 51 deferred (the 2 FindInfiniteRegion deferrals pass). UPDATE (wire/brep-p5-find-vertex-sector): MakeEdgeInFace wired to topology.face_p4 FindVertexSector and topology.brep_p9 MakeEdgeInFaceWithVU; make_edge_in_face(_with_uv_curve) take the kernel and an optional notification environment (None: plain edit). 797 cases, 746 pass, 51 deferred, 0 failed (unchanged). SweepStore transforms use owner-aware brep_p1 collections and brep_p3 NURBS conversion. Fixture import binds ownership metadata to its kernel without changing geometry values; unavailable face-p3 UV-trim construction remains deferred. UPDATE (PR #441): StitchAndOrient runs its two StitchFaces passes outside the edit lease (the stitch's glue/squeeze helpers notify through their own kernel-context edits, which the nested-edit guard refused, and the swallowed error left swept B-reps unstitched); the orientation pass keeps the caller's environment. The shell-vertex glue path still stops at remove_owned_vertex (plan/findings.md). UPDATE (oursland/solidcore#1): remove_owned_vertex is Brep::remove_from_vertex_list; the 36 heal.stitch_p2 differences it exposed were topology.brep_p7 GlueEdgesGeneral leaving the glued edge on the edge list (fixed); difftest 797 cases, 785 pass, 0 failed, 12 deferred." +notes = "G10 (PR #637, 2026-10-23): face ChangeGeometry notification wired. difftest 797 cases, 787 pass, 0 failed, 10 deferred (topology.brep_p1 8, topology.brep_p4 2); the 2 former topology.face_p3 deferrals pass. PARTIAL (PR #194, functional source ecccb67b; official main 9a7c1ec0; Claude review fixes): all twelve owned editing control flows and pending-topology cleanup present; individual missing callees/types defer. Landed shell/region callees wired; stable shell, vertex and vertex-use record erasure remains the part 4 SmBrep destructor/storage integration gap. Native difftest: 797 cases (285 fixtures + 512 generated), 737 passed, 0 failed, 60 deferred (topology.brep_p1 10, topology.brep_p2 46, topology.brep_p4 2, topology.brep_p9 2), max_rel_err=0, no absolute floors. Meaningful successful topology-mutation paths certified: 0/12 operations (issue #210). Coverage is 409 point/tolerance-target-stage cases, 301 whole-operation refusals/empty/identity/alias no-ops, and 27 supporting notification scripts. Temporary-edge minimum-length threshold, successful topology mutation, nonidentity transforms, shared native geometry across distinct owning records and pending-reader integration remain uncertified. Sensitivity 737/737 detected; 4096 generated: 4375 cases, 4195 passed, 180 deferred, 0 failed. Formatting, both strict Clippy configurations, 1369 kernel tests, WASM, 3 native FFI tests and all twelve affected differential units pass; full green/partial sweep PASS: 205 units, 113 green, 92 partial, 0 failed. No open dependency PR bundled or other unit promoted. 14 fns; largest: SmBrep::RemoveTopologicalEdgesAndVertices, SmBrep::CombineCoincidentVertices, SmBrep::DeleteTopologicalVertex, SmBrep::GlueVertices; cycle back-edges (call into later units; stub or co-port): topology.brep_p3(8), topology.brep_p2(5), heal.stitch_p1(3), topology.brep_p9(3), heal.stitch_p2(2); unit-SCC #22 (220 units); hazards: ptr-iter; 200 upstream test entry points reach this unit Second review round (head e9df9346, merged official main with topology.brep_p1/p7 and heal.stitch_p1): isolated aliased CombineCoincidentVertices takes the native path (6 new fixtures, deferred at Notify; only the target stage refuses it, UB-191); Notify status ignored in combine as upstream; SqueezeFace unlock never hides its result; FFI guard refusals are rejected, not failed. Wired heal.stitch_p1 (GlueVertices, RemoveFaces, StitchAndOrient) and topology.brep_p7 (GlueVertices) into this unit. Calls into the now-partial topology.brep_p1 are still UNPORTED stubs pending a follow-up PR. Batch integration (batch/clean-2026-10-19): set_face_tolerance wired to topology::face::p3::set_tolerance (topology.face_p3). Re-run in PR #149 (review 5578349117 round, merged with main through fb58ea76; topology.brep_p2's UNPORTED stubs wired): 797 cases, 738 pass, 0 fail, 59 deferred (topology.brep_p1 38, topology.brep_p4 12, topology.brep_p3 7, topology.brep_p9 2); the 46 SmBrep::Notify deferrals: 1 pass, the rest relabel to brep_p1 GetVertices / UpdateVertexEdgeIntervals, brep_p4 DeleteVertex and brep_p3 TurnToNURBS. Re-run after wiring stale stubs (PR #285): 797 cases, 744 pass, 0 fail, 53 deferred, max_rel_err 0. UPDATE (PR #204, topology.brep_p3): the UNPORTED(topology.brep_p3) stubs SqueezeEdge, MakeWireEdgeVertex, DeleteFace, OrientTrimmedSurfaces and TurnToNURBS, and UNPORTED(topology.brep_p9) MakeShellVertex in create_wire_edge_from_curve, are wired to topology::brep::p3 / p9 through with_p3_notifications (the caller's environment, else a topology.brep_p2 ArenaNotifyState over the kernel context's callbacks lent with brep::p3::with_lent_context); make_manifold and stitch_and_orient take k: &mut Kernel. Case by case: no outcome change; the 7 TurnToNURBS deferrals relabel to topology.brep_p1 GetVertices (797 cases, 738 pass, 0 fail, 59 deferred: topology.brep_p1 45, topology.brep_p4 12, topology.brep_p9 2). UPDATE (PR #204, Codex review of 255d52b7): a plain entry point (no environment; the _with_notifications forms given None) runs its whole edit in one environment under the kernel's identities (plain_edit = topology::brep::p3::with_kernel_callbacks: the B-rep's serial in the kernel context's domain, so two B-reps' objects never share a key in one context; the callbacks lent to the edit; nested in another lent edit it is refused before anything changes). create_wire_edge_from_curve, make_manifold, remove_faces, stitch_and_orient and squeeze_face are plain_edit wrappers over *_in bodies; with_p3_notifications no longer falls back to ArenaNotifyState. UPDATE (PR #204, Codex re-check of 6b04141d): plain_edit's environment carries a default record for every existing object from the start (brep::p3::kernel_named_edit), so the notifications sent before any p3 edit (e.g. notify_edge_merge) are delivered. UPDATE (PR #204, merge of main #339): op 13 (CreateWireEdgeFromCurve) runs now that GetInfiniteRegion is wired; its 2 fixture cases (isolated_op13_editing_*) compared the vertex-shell vertex use MakeEdgeVertexTopology deletes upstream and the arena keeps unlinked; with graph() comparing reachable records only (boolean.merge_p1) they pass. API EXTENDED in sweep.topology_sweep (PR #222): make_edge_in_face_with_uv_curve preserves polymorphic UV curve kinds, including native SmBSplineCurve-derived lines; the existing B-spline-only Rust entry point delegates without changing behavior. UPDATE (PR #526): FindAndSetInfiniteRegion wired to topology.brep_p9 FindInfiniteRegion (through the caller's query::brep::BrepQuery) and topology.brep_p1 ResetInfiniteRegion; it takes the kernel and SetRegionVoidType. Re-run: 797 cases, 746 pass, 0 fail, 51 deferred (the 2 FindInfiniteRegion deferrals pass). UPDATE (wire/brep-p5-find-vertex-sector): MakeEdgeInFace wired to topology.face_p4 FindVertexSector and topology.brep_p9 MakeEdgeInFaceWithVU; make_edge_in_face(_with_uv_curve) take the kernel and an optional notification environment (None: plain edit). 797 cases, 746 pass, 51 deferred, 0 failed (unchanged). SweepStore transforms use owner-aware brep_p1 collections and brep_p3 NURBS conversion. Fixture import binds ownership metadata to its kernel without changing geometry values; unavailable face-p3 UV-trim construction remains deferred. UPDATE (PR #441): StitchAndOrient runs its two StitchFaces passes outside the edit lease (the stitch's glue/squeeze helpers notify through their own kernel-context edits, which the nested-edit guard refused, and the swallowed error left swept B-reps unstitched); the orientation pass keeps the caller's environment. The shell-vertex glue path still stops at remove_owned_vertex (plan/findings.md). UPDATE (oursland/solidcore#1): remove_owned_vertex is Brep::remove_from_vertex_list; the 36 heal.stitch_p2 differences it exposed were topology.brep_p7 GlueEdgesGeneral leaving the glued edge on the edge list (fixed); difftest 797 cases, 785 pass, 0 failed, 12 deferred. UPDATE (wire: SmBrep::Transform into blend.hcr and the boolean.merge_p1 runner, PR #679): CombineCoincidentVertices calls the ported topology.brep_p1 UpdateVertexEdgeIntervals under the caller's notification environment (the stub is gone). difftest 797 cases, 795 pass, 0 failed, 2 deferred (topology.brep_p4 2); the 8 UpdateVertexEdgeIntervals deferrals pass." From 7efab767d38612d91e33f292d2dd6c3534411d22 Mon Sep 17 00:00:00 2001 From: Brett Lawson Date: Fri, 23 Oct 2026 15:58:31 -0700 Subject: [PATCH 3/3] difftest: sweep.topology_sweep test expects HCR past the wired scene transform The model fixture's hcr() now builds the transformed scene and stops at the fixture's missing retained HCR service instead of the SmBrep::Transform stub. Co-Authored-By: Claude Opus 5.5 --- .../src/units/sweep_topology_sweep.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/difftest/src/units/sweep_topology_sweep.rs b/crates/difftest/src/units/sweep_topology_sweep.rs index a2a436cf59..a97845eebe 100644 --- a/crates/difftest/src/units/sweep_topology_sweep.rs +++ b/crates/difftest/src/units/sweep_topology_sweep.rs @@ -847,14 +847,19 @@ mod tests { assert_ne!(model.k.context.current_mark(MarkType::Mark), old_mark); assert!(!model.k.context.is_mark_locked(MarkType::Mark)); model.transform(b, Axis2Placement::default())?; - // This fixture reaches HCR's separate, still-unavailable transform adapter. + // HCR's scene transform is wired (topology::brep::p5::transform_with_notifications): + // the scene is built, then this fixture has no service to retain it in. let hcr = model.hcr(b, Axis2Placement::default(), 0.001, 0.1, false, false, true); - assert!(matches!( - hcr, - Err(SolidError::Unported { - upstream: "SmBrep::Transform", unit: "topology.brep_p5" - }) - ), "{hcr:?}"); + assert!( + matches!( + hcr, + Err(SolidError::InvalidInput { + reason: "fixture has no retained HCR service", + .. + }) + ), + "{hcr:?}" + ); model.b[0].set_editing_enabled(false); model.k.context.set_doing_boolean(false); // query.brep (CalculateBoundingBox) is ported: the stitch now fails later,