diff --git a/crates/difftest/src/units/sweep_trans_sweep_geometry.rs b/crates/difftest/src/units/sweep_trans_sweep_geometry.rs index cee5aa27a..0d4a52525 100644 --- a/crates/difftest/src/units/sweep_trans_sweep_geometry.rs +++ b/crates/difftest/src/units/sweep_trans_sweep_geometry.rs @@ -11,8 +11,9 @@ use solidcore::curve::ellipse::Ellipse; use solidcore::curve::hermite_curve::HermiteCurve; use solidcore::geom::axis2_placement::Axis2Placement; use solidcore::geom::extent1d::Extent1d; +use solidcore::geom::attribute::AttributeStore; use solidcore::geom::extent2d::Extent2d; -use solidcore::geom::tol::zone_tol3d; +use solidcore::geom::tol::{zone_tol3d, USE_DEFAULT}; use solidcore::kernel::Kernel; use solidcore::math::{Vec2, Vec3}; use solidcore::nurbs::b_spline_curve::BSplineCurve; @@ -25,8 +26,13 @@ use solidcore::surface::plane::Plane; use solidcore::surface::surface::Surface; use solidcore::sweep::topology_sweep::SweepGeometryCreation; use solidcore::sweep::trans_sweep_geometry::TranslationalSweepGeometry; -use solidcore::topology::{Brep, Edgeuse, EdgeuseOwner, Vertexuse, VertexuseOwner}; -use solidcore::{Result, SolidError}; +use solidcore::topology::brep::p3::new_brep; +use solidcore::topology::brep::p8::{create_face_from_surface, FaceBuildEnv}; +use solidcore::topology::edge::NotifyEnv; +use solidcore::topology::topology_traverser::Marks; +use solidcore::topology::{Brep, Edgeuse, EdgeuseOwner, FaceId, Vertexuse, VertexuseOwner}; +use solidcore::{EntityRef, Result, SolidError}; +use std::collections::BTreeMap; use solidcore_ffi::units::sweep_trans_sweep_geometry as ffi; /// Translational sweep differential runner. @@ -205,8 +211,28 @@ fn surface(k: &Kernel, o: &mut Vec, s: &Surface) -> Result<()> { } Ok(()) } +/// `SmBrep::CreateFaceFromSurface(surface, domain, face)` in the infinite region. +fn create_face(k: &mut Kernel, b: &mut Brep, s: Surface, domain: &Extent2d) -> Result { + let mut records = BTreeMap::new(); + let mut attributes = AttributeStore::default(); + let key_of = super::topology_brep_p8::key_of; + let mut env = NotifyEnv { + records: &mut records, + attributes: &mut attributes, + context: None, + key_of: &key_of, + }; + let mut marks = Marks::default(); + let this_face = |f: FaceId| key_of(EntityRef::Face(f)); + let mut fenv = FaceBuildEnv { + env: &mut env, + marks: &mut marks, + this_face: &this_face, + }; + create_face_from_surface(k, b, Some(s), domain, &mut fenv) +} fn rust(i: &Input) -> Result> { - let k = Kernel::default(); + let mut k = Kernel::default(); let sweep = TranslationalSweepGeometry { sweep_vector: v(i.sweep), base: SweepGeometryCreation { @@ -345,14 +371,13 @@ fn rust(i: &Input) -> Result> { Vec3::new(0., 0., 2.), )?) }; - let sh = b.add_shell()?; - let f = b.add_face(sh)?; - { - let f = b.face_mut(f)?; - f.uv_domain = s.natural_uv_domain(); - f.surface = Some(s); - f.tolerance = zone_tol3d(&k, i.tolerances[3]); - } + // The native fixture makes the face with `SmBrep::CreateFaceFromSurface` + // over the natural domain in a `new SmBrep()` (with its infinite region); + // a failed call's status is the outcome. + let mut b = new_brep(&mut k, USE_DEFAULT, USE_DEFAULT)?; + let domain = s.natural_uv_domain(); + let f = create_face(&mut k, &mut b, s, &domain)?; + b.face_mut(f)?.tolerance = zone_tol3d(&k, i.tolerances[3]); let (s, t) = sweep.face_sweep_same(&k, &b, f)?; o.push(t); surface(&k, &mut o, &s)?; diff --git a/harness/cpp/shim/sweep_trans_sweep_geometry.cpp b/harness/cpp/shim/sweep_trans_sweep_geometry.cpp index 03bd0c85b..9a30c6b32 100644 --- a/harness/cpp/shim/sweep_trans_sweep_geometry.cpp +++ b/harness/cpp/shim/sweep_trans_sweep_geometry.cpp @@ -260,18 +260,25 @@ SC_EXPORT int32_t sc_sweep_trans_sweep_geometry_eval(const double *in, return SC_ERR_ARG; input = s; } + // A failed CreateFaceFromSurface (e.g. a swept surface that degenerates to + // a curve) is a defined outcome: its status is compared, as the Rust side + // makes the face the same way. st = b->CreateFaceFromSurface(input, input->GetNaturalUVDomain(), f); - if (st != SM_SUCCESS || !f) + if (st != SM_SUCCESS) + o.push_back(status(st)); + else if (!f) return SC_ERR_ARG; - f->SetTolerance(in[10], FALSE, FALSE); - SmSurface *s = nullptr; - double tol = 0; - st = sweep.FaceSweepSame(ctx, f, s, tol); - std::unique_ptr clean(s); - o.push_back(status(st)); - if (st == SM_SUCCESS) { - o.push_back(tol); - put_surface(o, s); + else { + f->SetTolerance(in[10], FALSE, FALSE); + SmSurface *s = nullptr; + double tol = 0; + st = sweep.FaceSweepSame(ctx, f, s, tol); + std::unique_ptr clean(s); + o.push_back(status(st)); + if (st == SM_SUCCESS) { + o.push_back(tol); + put_surface(o, s); + } } } return sc_copy_out(o.data(), int64_t(o.size()), out, cap, len); diff --git a/plan/findings.md b/plan/findings.md index 5ab32b172..ed2f432bb 100644 --- a/plan/findings.md +++ b/plan/findings.md @@ -18023,6 +18023,85 @@ labels. Read the first as the proposal to give the runner's `StaticHandle` a `natural_uv_domain`, and the second as the missing face-aware `SurfaceSolveContext` (global solves on a face's surface should use that face's trimmed surface cache). +## sweep.trans_sweep_geometry: op 8 makes its face with CreateFaceFromSurface (PR #692, 2026-10-23) + +The `FaceSweepSame` op (op 8) used to build its input face differently on each side: the +shim calls `SmBrep::CreateFaceFromSurface(surface, natural domain)` on a `new SmBrep()`, +while the Rust runner pushed a bare face record (no loop, no infinite region) and set its +surface by hand. When `CreateFaceFromSurface` failed, the shim returned `SC_ERR_ARG` and +the case was recorded as a rejected "native fixture constructor", so 11 cases never +compared anything. All 11 failed with `SM_ERR` (1001) inside `MakeFaceWithCurves`: the +B-spline extrusion of a 1e-9-scale or near-degenerate profile curve degenerates to a +curve (`nanoscale_fixture_{0,1,2,4,5}_8`, `gen0147`, `gen0256`, `gen0337`, `gen0386`, +`gen0405`, `gen0471`). + +Now the Rust runner makes the face with `topology::brep::p8::create_face_from_surface` +on `topology::brep::p3::new_brep(k, USE_DEFAULT, USE_DEFAULT)` (the B-rep needs its +infinite region, as `new SmBrep()` has), and the shim returns a failed +`CreateFaceFromSurface` status as the case's output instead of rejecting. All 11 cases +compare `[1001]` on both sides. No FFI guard was removed: `SC_ERR_ARG` is still +returned when the profile curve constructor fails, when the profile is not a +`SmBSplineCurve`, when `SmBSplineSurface::CreateLinearSweep` fails, and when +`CreateFaceFromSurface` succeeds without a face. + +- **Still rejected (op 8):** `rejected_constructor_0_8` and `rejected_constructor_2_8`. + Their profile curve constructor fails (a 1e-15 line segment, a 1e-15 radius circle) + before any face exists, exactly like their op 5-7 siblings, so the sweep is never + reached. The unit keeps 8 rejected fixtures, all curve-constructor failures. +- **New deferral:** `gen0411` (op 8, a circle profile of radius about 1.5e-6 extruded + into a closed B-spline surface) passed before, against the bare face record, and now + defers on `topology.face_p2` ("SmFace handle of a surface's face", + `surface/surface_cache/p1.rs` `SurfaceCache::face`). Natively `CreateFaceFromSurface` + succeeds. In Rust, `MakeFaceWithCurves` splits the closed face (`split_closed` -> + `find_and_set_infinite_region` -> `find_infinite_region`), which ray-fires the shell; + the line/surface solve on the now face-owned surface builds a trimmed surface cache + that needs the owning face's caller handle. That is the shell ray-fire gap that the + open face-geometry-for-shell-ray-queries work addresses. It is the unit's only + deferral, so the unit goes from green to partial. + +Before: 788 cases, 769 pass, 0 deferred, 19 rejected. After: 788 cases, 779 pass +(the 11 re-covered cases included), 0 failed, 1 deferred (`gen0411`), 8 rejected, +max_rel_err 0. + +## Small fixture rejections explained: fillet_geom_p3, merge_p4, offset_executive_p2 (PR #692, 2026-10-23) + +These rejections had no reason recorded in `plan/`. None is a wrong guard, so their +behaviour is unchanged. + +- **blend.fillet_geom_p3 `regress_corner_edge_on_edge` (1 case).** The shim refuses + (`SC_ERR_ARG`) `CalcCornerEdgeGeom` on an edge whose type is computed by another + unit's calculator (`Model::other_unit_edge`: cross section, fillet x side face, on + edge, on extended edge, fillet end, setback rail, rail extension). This fixture is an + `SM_FE_ON_EDGE` edge on the shim's `network` model: `CalcEdgeOnEdge` + (`blend.fillet_geom_p6`) crashed natively on it in fuzzing, since the shim does not + build the solver data that calculator reads. The guard covers every other-unit edge + type, not only the crashing one; those calculators are tested in their own units. + The other five `regress_*` rejections are documented in `plan/upstream-issues.md` + (UB-211 `regress_on_vertex_unclassified`, UB-185 `regress_mate_cycle`, UB-208 + `regress_recalc_no_dir_all_*`, UB-210 `regress_fif_no_curve_no_corner`). +- **blend.fillet_geom_p3 `regress_recalc_analytic_default` (1 case).** `ReCalcFilletGeom` + with `analytic = TRUE` on a geometry that is not `SM_FG_TANGENT_ROLLOVER` runs + `CalcConeFilletGeom`/`CalcTorusFilletGeom` (`blend.fillet_solver_p2`), which read + solver data the shim's base `SmFilletSolver` does not build; it crashed natively. The + shim refuses every analytic recalculation except on a tangent rollover. +- **boolean.merge_p4 `plane-domain-constructor-rejection`, `gen0008`, `gen0013` (3 + cases).** An operand of kind 3 is a `SmPlane(point, normal)`, whose natural UV domain + is `±SM_INFINITE_PARAMETER` (1234567). The shim makes its face with + `CreateFaceFromSurface` on `[0, u] x [0, v]`; when `u` or `v` exceeds 1234567 (2e6 in + the fixture; `v` = 1889531 and 1242370 in the generated `other` operands at the 1e6 + scale), upstream's domain-containment check returns `SM_ERR`. The shim then returns + `[-1]`. The Rust operands are imported from the native export, so there is no Rust + operand to merge and nothing to compare. +- **offset.offset_executive_p2 `review_sheet_*` (9 cases).** The native plane-sheet + fixture (a unit square made with `CreateFaceFromSurface` on a B-rep whose tolerance + is 0.9 or 1000) is refused with `SM_ERR` by `representable_topology`: with a + tolerance that large compared with the edges, upstream merges or drops boundary + vertices and edges, so the result is not 1 face, 1 loop, 4 edges, 8 edge uses and 4 + vertices. The Rust fixture builds that 4-edge sheet by hand and cannot represent the + collapsed topology, so the offset operation is never run on either side. The runner + test `incomplete_native_sheets_are_rejected_without_hiding_valid_controls` pins the + 9 rejections and the 4 compared controls (tolerance 0.5, and 1000 on a 1e4-scale + sheet). ## blend.fillet_intersector `rv_next_clipped_step`: fixed, the wrong `PointOnPlaneSolve` override (2026-10-23, follows the open entry above) - **Root cause.** Not the offset-surface extension state. `SmFilletSolver::StepSolve`'s clipped diff --git a/plan/units/sweep.trans_sweep_geometry.toml b/plan/units/sweep.trans_sweep_geometry.toml index 273a13526..1b673f058 100644 --- a/plan/units/sweep.trans_sweep_geometry.toml +++ b/plan/units/sweep.trans_sweep_geometry.toml @@ -9,4 +9,4 @@ tests = ["prog_test:my_sweep_null_region_counts", "prog_test:my_test_dosweep_nul status = "green" claimed_by = "" claimed_at = "" -notes = "GREEN (PR #565, extrusion recognition, 2026-10-23): 788 cases, 769 pass, 0 failed, 0 deferred, 19 rejected native fixtures, max_rel_err 0. The 24 remaining stale extrusion-dispatch deferrals now compare exactly: recognised copies retain lazy state, evaluation/iso queries prepare temporary NURBs, and non-identity transforms build the NURB before transforming as upstream does. No tolerance, fixture rejection or case-count changes. #76 kernel-aware Surface dispatch (PR #313, 2026-10-21): 788 cases, 728 pass, 0 failed, 41 deferred (surface.sphere 41), max_rel_err 0; before 719 pass / 50 deferred. PARTIAL (PR #127): 788 cases, 719 bit-identical passes, 50 deferred on surface.cone_p2, 19 rejected native fixtures (not parity passes); zero failures, no absolute floors. All 11 owned functions plus GetEndCoordSystem ported; compiled SM_SWEEP_CHECKS-disabled behavior. Shared SweepGeometryCreation and SweepCheckType; native lines/circles/non-circular ellipses/Hermite/2D and rational B-splines/planes; constructor-failure guard regressions and Cartesian op/kind/flag coverage; ordered boundaries, endpoint aliases, tolerances, runtime types and evaluations compared. No incoming UNPORTED back-edges. 11 fns; largest: SmTranslationalSweepGeometry::CheckCurveSweep, SmTranslationalSweepGeometry::EdgeSweepHigher, SmTranslationalSweepGeometry::EdgeSweepSame, SmTranslationalSweepGeometry::FaceSweepSame; unit-SCC #22 (220 units); 45 upstream test entry points reach this unit" +notes = "GREEN (PR #692 merged with main after #685, CI 2026-10-24): PASS sweep.trans_sweep_geometry cases=788 passed=780 failed=0 deferred=0 rejected=8 max_rel_err=0; gen0411 passes now that #685 prepares face-owned surfaces for shell ray fires. Earlier: PARTIAL (PR #692, CreateFaceFromSurface op 8 fixture, 2026-10-23): 788 cases, 779 pass, 0 failed, 1 deferred (topology.face_p2: gen0411, shell ray fire on a face-owned surface while CreateFaceFromSurface splits a closed extrusion), 8 rejected (profile-curve constructor failures), max_rel_err 0. FaceSweepSame now makes its face with CreateFaceFromSurface on both sides and compares a failed status (11 cases, all SM_ERR 1001) instead of rejecting them; see findings.md. GREEN (PR #565, extrusion recognition, 2026-10-23): 788 cases, 769 pass, 0 failed, 0 deferred, 19 rejected native fixtures, max_rel_err 0. The 24 remaining stale extrusion-dispatch deferrals now compare exactly: recognised copies retain lazy state, evaluation/iso queries prepare temporary NURBs, and non-identity transforms build the NURB before transforming as upstream does. No tolerance, fixture rejection or case-count changes. #76 kernel-aware Surface dispatch (PR #313, 2026-10-21): 788 cases, 728 pass, 0 failed, 41 deferred (surface.sphere 41), max_rel_err 0; before 719 pass / 50 deferred. PARTIAL (PR #127): 788 cases, 719 bit-identical passes, 50 deferred on surface.cone_p2, 19 rejected native fixtures (not parity passes); zero failures, no absolute floors. All 11 owned functions plus GetEndCoordSystem ported; compiled SM_SWEEP_CHECKS-disabled behavior. Shared SweepGeometryCreation and SweepCheckType; native lines/circles/non-circular ellipses/Hermite/2D and rational B-splines/planes; constructor-failure guard regressions and Cartesian op/kind/flag coverage; ordered boundaries, endpoint aliases, tolerances, runtime types and evaluations compared. No incoming UNPORTED back-edges. 11 fns; largest: SmTranslationalSweepGeometry::CheckCurveSweep, SmTranslationalSweepGeometry::EdgeSweepHigher, SmTranslationalSweepGeometry::EdgeSweepSame, SmTranslationalSweepGeometry::FaceSweepSame; unit-SCC #22 (220 units); 45 upstream test entry points reach this unit"