Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions crates/difftest/known_mismatches.toml
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,48 @@ why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-
unit = "topology.brep_p10"
case = "gen0117"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [ints.ints[166] cpp 1 rust 0]"

[[mismatch]]
unit = "heal.heal_data_p1"
case = "file_plane_circ"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [values.out[215] cpp -1.23456e-61 rust 0e0]"

[[mismatch]]
unit = "heal.heal_data_p1"
case = "file_plane_circ_rebuild_reverse"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [values.out[215] cpp -1.23456e-61 rust 0e0]"

[[mismatch]]
unit = "topology.brep_p1"
case = "plane_circ_heal"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [values.out[134] cpp 1e0 rust 0e0]"

[[mismatch]]
unit = "topology.brep_p10"
case = "read_Fillet_bottle"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [ints.dbl_bits[2437] cpp 4533227276183232325 rust 4533227276183193973]"

[[mismatch]]
unit = "topology.brep_p10"
case = "read_Fillet_cyl_cyl_shell"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [ints.dbl_bits[351] cpp 4541087340294538906 rust 4541087340294541262]"

[[mismatch]]
unit = "topology.brep_p10"
case = "read_Fillet_fillet0227"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [ints.dbl_bits[587] cpp 4584643767240576693 rust 4584643767240344909]"

[[mismatch]]
unit = "topology.brep_p10"
case = "read_Solids_extrusion2"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [ints.dbl_bits[3949] cpp 4566519071875143604 rust 4566519071875138963]"

[[mismatch]]
unit = "topology.brep_p10"
case = "gen0040"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [values.dbls.len cpp 1921 rust 1785]"

[[mismatch]]
unit = "topology.brep_p10"
case = "gen0165"
why = "ClassifyLoops' loop bisector builds missing UV trim curves on the real B-rep upstream (CurveOnClassify -> IntersectTSBound -> ComputeUVPoint(bOKToMakeUVTrimCurve=TRUE); SetUVTrimCurve resets vertex/face gaps), the port runs it on a copy (plan/findings.md: ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in the port) [ints.ints[170] cpp 1 rust 0]"
9 changes: 1 addition & 8 deletions crates/difftest/src/units/intersect_curve_class_p4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,14 +835,7 @@ fn build_pool(k: &Kernel, s: &Script) -> Result<Pool, HarnessError> {
}
if let Some(face) = &s.face {
let (mut b, f) = face_brep(k, face)?;
let is_closed = match &face.surf {
Some(FaceSurfIn::Revolution { sweep, .. }) => (*sweep - 360.0).abs() < 1e-6,
None => false,
};
if is_closed {
solidcore::topology::face::p3::create_uv_trim_curves(k, &mut b, f, true, None)
.map_err(|e| harness(format!("create_uv_trim_curves: {e}")))?;
}

// the down face use's loops, so that edge mates (an edge's end vertex, the end edge
// use of a vertex use's UV sector) exist as upstream
hfp::add_down_side(&mut b, f)?;
Expand Down
95 changes: 90 additions & 5 deletions crates/solidcore/src/intersect/curve_class/p7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2137,19 +2137,49 @@ impl<O: ClassObject, G: ClassGeometryMut<O>> FaceQueries<O> for Mutable<'_, G> {
src_zone_tol_3d: f64,
uv_space_okay: bool,
) -> Result<EndInside> {
// (not yet wired on the mutable path: the read-only deferral)
ReadOnly(&*self.0).find_vertex_sector(
let unresolved = || {
// DISPATCH GAP: the adapter cannot resolve the face or vertex handle to its
// `Brep` entity (`ClassGeometry::topology_entity`).
SolidError::unported("SmFace::FindVertexSector", "topology.face_p4")
};
let v = {
let geometry = &*self.0;
let (b, _) = resolve_face(geometry, face).ok_or_else(unresolved)?;
same_brep_vertex(geometry, b, vertex).ok_or_else(unresolved)??
};
let Some(v) = v else {
return Ok(EndInside::default());
};
let Some((b, f)) = resolve_face_mut(self.0, face) else {
return ReadOnly(&*self.0).find_vertex_sector(
k,
face,
vertex,
curve,
uv_curve,
from,
to,
direction,
src_zone_tol_3d,
uv_space_okay,
);
};
let before = Tolerances::of(b);
let r = find_vertex_sector_in(
k,
face,
vertex,
b,
f,
v,
curve,
uv_curve,
from,
to,
direction,
src_zone_tol_3d,
uv_space_okay,
)
)?;
before.check_unchanged(b, "SmFace::FindVertexSector")?;
Ok(r)
}
fn edge_curve_on_classify(
&mut self,
Expand Down Expand Up @@ -2385,6 +2415,61 @@ fn same_brep_edge<O: ClassObject>(
Some(Ok(core::ptr::eq(b, edge_brep).then_some(e)))
}

/// The vertex behind vertex handle `vertex` when it is one of B-rep `b`'s: `Some(None)`
/// for a vertex of another B-rep (no vertex use on a face of `b`), `None` for a handle the
/// adapter cannot resolve.
fn same_brep_vertex<O: ClassObject>(
geometry: &impl ClassGeometry<O>,
b: &Brep,
vertex: O,
) -> Option<Result<Option<VertexId>>> {
let (vertex_brep, entity) = geometry.topology_entity(vertex)?;
let EntityRef::Vertex(v) = entity else {
return Some(Err(SolidError::invalid_input(
OP_FACE,
"vertex class object not a vertex",
)));
};
Some(Ok(core::ptr::eq(b, vertex_brep).then_some(v)))
}

/// [`crate::topology::face::p4::find_vertex_sector`] of face `f` of `b`.
#[allow(clippy::too_many_arguments)]
fn find_vertex_sector_in(
k: &Kernel,
b: &mut Brep,
f: FaceId,
v: VertexId,
curve: &Curve,
uv_curve: Option<&Curve>,
from: f64,
to: f64,
direction: OrientType,
src_zone_tol_3d: f64,
uv_space_okay: bool,
) -> Result<EndInside> {
let sector = crate::topology::face::p4::find_vertex_sector(
k,
b,
f,
crate::topology::face::p4::FindVertexSectorArgs {
vertex: v,
curve,
uv_curve,
param_at_vertex: from,
param_ivl_end: to,
orientation: direction,
zone_tol_3d: src_zone_tol_3d,
validate_sector: true,
uv_space_okay,
},
)?;
Ok(EndInside {
inside: sector.vertexuse.is_some(),
possible_problem: sector.possible_problem,
})
}

/// [`qf1::edge_curve_on_classify`] of face `f` of `b` (an edge of another B-rep has no
/// edge use on this face: not inside, no problem, as upstream's edge use walk finds).
#[allow(clippy::too_many_arguments)]
Expand Down
28 changes: 4 additions & 24 deletions crates/solidcore/src/primitive/api_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -922,14 +922,6 @@ mod tests {
));
}

/// The unit of the first call a construction stops at.
fn deferred_at<T>(r: &Result<T>) -> Option<&'static str> {
match r {
Err(SolidError::Unported { unit, .. }) => Some(*unit),
_ => None,
}
}

#[test]
fn complete_solids_construct_and_partial_solids_report_dependencies() {
let k = &mut Kernel::default();
Expand All @@ -954,22 +946,10 @@ mod tests {
assert_eq!((faces, edges, vertices), counts);
assert!(crate::topology::brep::p2::is_manifold_solid(&b).unwrap());
}
// partial solids sweep a profile (topology sweep) past SmBrep::DeleteEdge and
// the axis' SmFace::CurveOnClassify, until SmFace::FindVertexSector (sphere,
// cylinder); the torus, whose point classification runs and whose later ray
// fires classify against the prepared trimmed surface cache, completes
for (r, unit) in [
(
create_partial_sphere(k, o(), 5.0, 0.0, 90.0),
"topology.face_p4",
),
(
create_partial_cylinder(k, o(), 5.0, 10.0, 0.0, 90.0),
"topology.face_p4",
),
] {
assert_eq!(deferred_at(&r), Some(unit));
}
let sphere = create_partial_sphere(k, o(), 5.0, 0.0, 90.0).unwrap();
assert!(crate::topology::brep::p2::is_manifold_solid(&sphere).unwrap());
let cylinder = create_partial_cylinder(k, o(), 5.0, 10.0, 0.0, 90.0).unwrap();
assert!(crate::topology::brep::p2::is_manifold_solid(&cylinder).unwrap());
let torus = create_partial_torus(k, o(), 10.0, 2.0, 0.0, 90.0).unwrap();
assert!(crate::topology::brep::p2::is_manifold_solid(&torus).unwrap());
// edges without an edge use on their face
Expand Down
34 changes: 34 additions & 0 deletions plan/findings.md
Original file line number Diff line number Diff line change
Expand Up @@ -18660,3 +18660,37 @@ the big face is used), and the start cap came out as 2 faces.
every other caller were already fresh or unused (the swung-surface loop drops its list).
- **Result.** Both known-mismatch entries are removed and both cases match C++ bit for
bit. sweep.api_surfaces is green (882/882).

## Wiring FindVertexSector brings 9 more cases to the loop-bisector-on-a-copy mismatch (2026-10-24)

`SmFace::FindVertexSector` is now wired on the mutable curve-classification path
(`intersect::curve_class::p7::Mutable::find_vertex_sector`, unit `topology.face_p4`). Nine
heal-path cases that used to defer there now run on into `SetProps_Stage3` →
`ClassifyLoops` → `sms_FindAndClassifyLoopBisector`, and they disagree in the same way as
the cases in "ClassifyLoops' loop bisector builds UV trim curves upstream, on a copy in
the port" above. They are listed in `crates/difftest/known_mismatches.toml` under that
entry's reason:

| unit | cases | first mismatch |
|---|---|---|
| `heal.heal_data_p1` | `file_plane_circ`, `file_plane_circ_rebuild_reverse` | `values.out[215]`: cpp `-1.23456e-61` (cache reset), rust `0` |
| `topology.brep_p1` | `plane_circ_heal` | `values.out[134]`: cpp 1, rust 0 |
| `topology.brep_p10` | `read_Fillet_bottle`, `read_Fillet_cyl_cyl_shell`, `read_Fillet_fillet0227`, `read_Solids_extrusion2` | `ints.dbl_bits`, max relative difference 7e-7 |
| `topology.brep_p10` | `gen0040` | `values.dbls.len`: cpp 1921, rust 1785 |
| `topology.brep_p10` | `gen0165` | `ints.ints[170]`: cpp 1, rust 0 |

- **Checked (port instrumented, not committed).**
- In all nine cases `find_and_classify_loop_bisector_into` runs.
- In eight of them the wired `FindVertexSector` is reached from the bisector's
`CurveOnClassify` (backtraces).
- The two `plane_circ` cases show the same signature as the `plane_square` entries.
- **`read_Fillet_cyl_cyl_shell`, `read_Fillet_bottle`, `read_Fillet_fillet0227`.** Here
`HasSeamProblem`'s seam classification also reaches `FindVertexSector`. It returns
`SM_ERR_INVALID_INPUT` from `EvaluateNormalAtSingularity` (the normal sum at a
singularity does not unitize). `CurveOnClassify`'s status is ignored there, as upstream
does. I have not confirmed that the C++ errors at the same point, so for
`read_Fillet_cyl_cyl_shell`, where the bisector reaches no `FindVertexSector`, the seam
path is a second possible source.
- **Fix.** The same as the entry above: thread `&mut Brep` through `ClassifyLoops` to the
bisector. When that lands, re-run these nine cases and remove their known-mismatch
entries.
Loading
Loading