Merge pull request #670 from brett19/fix/cone-trim-with-domain

wire: SmCone::TrimWithDomain through the inherited SmSurfOfRevolution body
This commit is contained in:
bherbruck
2026-10-23 21:41:44 +00:00
committed by GitHub
4 changed files with 97 additions and 47 deletions
+43 -38
View File
@@ -240,6 +240,48 @@ pub struct Cone {
pub(crate) cylinder: bool,
}
/// The cone's `ConvertUVFromNURBSToSTEP` on its base parts (the virtual the
/// inherited `UpdateAnalyticalDomain` calls); see [`Cone::convert_uv_from_nurbs_to_step`].
pub(crate) fn cone_convert_uv_from_nurbs_to_step(
rev: &SurfOfRevolution,
inside_out: bool,
k: &Kernel,
nurbs_uv: Point2,
) -> Result<Point2> {
// swap NurbPoint when needed
let step_ivl = rev.anal_uv_domain.v_interval();
let nurb_domain = rev.base.natural_uv_domain();
let (nurb_uv, nurb_ivl) = if rev.swap_uv {
(
Point2::new(nurbs_uv.y, nurbs_uv.x),
nurb_domain.u_interval(),
)
} else {
(nurbs_uv, nurb_domain.v_interval())
};
// convert sweep coordinate
let circle = rev
.polar_curve
.as_ref()
.ok_or(SolidError::null_pointer(OP_TO_STEP))?;
let (x, _exact) = rev.polar.convert_to_polar_parameter(k, circle, nurb_uv.x)?;
// convert linear coordinate
let y = if rev.is_bounded()? {
if inside_out {
step_ivl.min() + ((nurb_uv.y - nurb_ivl.max()) / -nurb_ivl.length() * step_ivl.length())
} else {
step_ivl.min() + ((nurb_uv.y - nurb_ivl.min()) / nurb_ivl.length() * step_ivl.length())
}
} else if inside_out {
-nurb_uv.y
} else {
nurb_uv.y
};
Ok(Point2::new(x, y))
}
/// The cone's `CreateIsoParametricCurve` on its base parts (the virtual the
/// inherited NURB rebuild calls).
pub(crate) fn cone_iso_curve(
@@ -1343,44 +1385,7 @@ impl Cone {
///
/// `SM_ERR_NULL_POINTER` without polar curve; those of the polar conversion.
pub fn convert_uv_from_nurbs_to_step(&self, k: &Kernel, nurbs_uv: Point2) -> Result<Point2> {
// swap NurbPoint when needed
let step_ivl = self.rev.anal_uv_domain.v_interval();
let nurb_domain = self.rev.base.natural_uv_domain();
let (nurb_uv, nurb_ivl) = if self.rev.swap_uv {
(
Point2::new(nurbs_uv.y, nurbs_uv.x),
nurb_domain.u_interval(),
)
} else {
(nurbs_uv, nurb_domain.v_interval())
};
// convert sweep coordinate
let circle = self
.rev
.polar_curve
.as_ref()
.ok_or(SolidError::null_pointer(OP_TO_STEP))?;
let (x, _exact) = self
.rev
.polar
.convert_to_polar_parameter(k, circle, nurb_uv.x)?;
// convert linear coordinate
let y = if self.rev.is_bounded()? {
if self.inside_out {
step_ivl.min()
+ ((nurb_uv.y - nurb_ivl.max()) / -nurb_ivl.length() * step_ivl.length())
} else {
step_ivl.min()
+ ((nurb_uv.y - nurb_ivl.min()) / nurb_ivl.length() * step_ivl.length())
}
} else if self.inside_out {
-nurb_uv.y
} else {
nurb_uv.y
};
Ok(Point2::new(x, y))
cone_convert_uv_from_nurbs_to_step(&self.rev, self.inside_out, k, nurbs_uv)
}
// ---- evaluation -----------------------------------------------------------------
+52 -1
View File
@@ -10,6 +10,7 @@
//! | [`Cone::rebuild_step_from_nurb_parameters`] | `SmCone::RebuildSTEPFromNURBParameters` |
//! | [`ellipse_trim_intervals`] | `sm_GetEllipseTrimIntervals` (file static) |
//! | [`Cone::reparameterize`] | `SmSurfOfRevolution::Reparameterize` (inherited; its polar converter rebuild calls the cone's iso curves) |
//! | [`Cone::trim_with_domain`] | `SmSurfOfRevolution::TrimWithDomain` (inherited; its conversions and NURB rebuilds call the cone's virtuals) |
//! | [`Cone::base_mut`] | `GetGwNurbPointer` (non-`const`, inherited) |
//!
//! Deviations:
@@ -22,7 +23,9 @@
//! error status through `SER` from a function returning `SmBoolean`
//! (`IsNurbSurfaceCone`: a failed iso curve) the error is returned; upstream's caller
//! would read that status as `TRUE` with a `NULL` cone.
//! - **Uninitialised reads.** `sm_GetEllipseTrimIntervals` ignores the status of
//! - **Uninitialised reads.** A failed `ConvertUVFromNURBSToSTEP` in
//! [`Cone::trim_with_domain`] (upstream ignores the status and reads the corner it may
//! not have written) returns the error. `sm_GetEllipseTrimIntervals` ignores the status of
//! `smgu_LinePlaneIntersect` and then reads its parameter: a seam line parallel to
//! the ellipse plane (or of zero length) returns the error here. A failed
//! `ConvertUVFromNURBSToSTEP` (upstream then uses a default-constructed domain) and
@@ -228,6 +231,26 @@ impl Cone {
})
}
/// Trims to the NURB domain `new_nurb_uv_domain` (the inherited
/// `SmSurfOfRevolution::TrimWithDomain`): its body with the cone's
/// `ConvertUVFromNURBSToSTEP` (in `UpdateAnalyticalDomain`) and iso curves (in
/// `MakeNurb`, `Reparameterize` and `RebuildPolarConverter`) as its virtual calls.
///
/// # Errors
///
/// Those of [`crate::surface::surf_of_revolution::SurfOfRevolution::trim_with_domain`]
/// and of [`Self::convert_uv_from_nurbs_to_step`] (upstream ignores that status and
/// reads the corner it may not have written).
pub fn trim_with_domain(&mut self, k: &Kernel, new_nurb_uv_domain: &Extent2d) -> Result<()> {
let inside_out = self.inside_out;
self.rev.trim_with_domain_using(
k,
new_nurb_uv_domain,
&|s, k, uv| super::p1::cone_convert_uv_from_nurbs_to_step(s, inside_out, k, uv),
&|s, k, p, t| Ok(super::p1::cone_iso_curve(s, inside_out, k, p, t, None, false)?.curve),
)
}
/// Rebuilds the polar converter with the cone's iso curves
/// (`RebuildPolarConverter`, which calls the virtual `CreateIsoParametricCurve`).
fn rebuild_cone_polar_converter(&mut self, k: &Kernel) -> Result<()> {
@@ -1129,6 +1152,34 @@ mod tests {
}
}
#[test]
fn trim_with_domain_keeps_the_shape() {
let k = kernel();
for (swap, inside) in [(false, false), (true, false), (false, true), (true, true)] {
let mut cone = canonical(&k, 2.0, 1.0, 3.0, swap, inside);
let d = cone.rev().base().natural_uv_domain();
let (lo, hi) = (d.min(), d.max());
let trim = Extent2d::from_bounds(
lo.x + 0.25 * (hi.x - lo.x),
lo.y + 0.125 * (hi.y - lo.y),
lo.x + 0.75 * (hi.x - lo.x),
lo.y + 0.5 * (hi.y - lo.y),
);
cone.trim_with_domain(&k, &trim).unwrap();
let nd = cone.rev().base().natural_uv_domain();
assert!(nd.is_contained_by(&trim, 1e-12) && trim.is_contained_by(&nd, 1e-12));
let p = cone.rev().base().evaluate_point(trim.max()).unwrap();
let back = cone.convert_uv_from_nurbs_to_step(&k, trim.max()).unwrap();
let q = cone.evaluate_step_point(back).unwrap();
assert!((p - q).length() < 1e-9);
// the cone through the trimmed corner keeps its radius at that height
let r = (p.x * p.x + p.y * p.y).sqrt();
assert!((r - cone.radius(p.z)).abs() < 1e-9);
// a domain outside the current one is rejected
assert!(cone.trim_with_domain(&k, &d).is_err());
}
}
#[test]
fn rejects_non_cones() {
let k = kernel();
+1 -7
View File
@@ -2031,13 +2031,7 @@ impl Surface {
SurfaceKind::OffsetSurface(s) => s.trim_with_domain(k, trim_domain),
SurfaceKind::SrfInVolume(s) => s.trim_with_domain(k, trim_domain),
SurfaceKind::Plane(s) => s.trim_with_domain(trim_domain),
SurfaceKind::Cone(_) => {
// DISPATCH GAP (#76): keep the cone's MakeNurb/UV/iso overrides during trim.
Err(SolidError::unported(
"SurfaceKind::Cone::TrimWithDomain callbacks",
"surface.surface_p2",
))
}
SurfaceKind::Cone(s) => s.trim_with_domain(k, trim_domain),
SurfaceKind::Sphere(s) => s.trim_with_domain(k, trim_domain),
SurfaceKind::Torus(s) => s.trim_with_domain(k, trim_domain),
SurfaceKind::SurfOfRevolution(s) => s.trim_with_domain(k, trim_domain),
+1 -1
View File
@@ -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 = "G10 (PR #637, 2026-10-23): SetSurface's SmFace::Notify(SM_NO_CHANGE_GEOMETRY) wired (set_surface: empty face environment, null data; set_surface_notified: caller's environment with the surfaces' identities); Surface::split_at makes a missing extrusion NURB (GetOrCreateGwNurbPointer). difftest 2312 cases, 2202 pass, 0 failed, 110 deferred (cpp-crash-guard:UB-137 98, surface.surface_p2 12); all 209 topology.face_p3 deferrals pass. PARTIAL (PR #148): difftest 2312 cases (2260 built-in on the 15 pt_TestFiles B-reps (every op, UV trim curves as read and rebuilt; seam ops with 7 face-property variants on the periodic files) + 36 review crash reproducers + 16 generated), 807 pass bit-identical (max_rel_err 0), 0 failed, 0 both-errored (real passes 807), 1505 deferred (topology.face_p3 1100: SmFace::Notify of every edit, CreateUVTrimCurves; topology.face_p4 307: GetUpwardLoopuses/GetBrep/HasSeamProblem; cpp-crash-guard:UB-137 98: seam calls the shim keeps from upstream's SplitAtSeam crash, never comparable; the guard skips ~99.5% valid MoveSeam+split calls and must be replaced when face_p4 lands); --perturb SENSITIVE 807/807. NormalizedEvaluate/GetOrCreateUVTrimCurve wired to topology.edgeuse_p3 in PR #117 (288 deferred cases now pass). No passing case for UvDomain (with surface), Flip, SetSurface, LoopRoundTrip, RemoveUv, SwapUv, Shrink (all behind face_p3/face_p4); passes are early returns, error paths and guards. The shim normalises analytic geometry to B-splines, so MoveSeam's CopyAndAddAnalytics branch and SwapUV's conic mirrors are not compared. Seam ops take HasSeamProblem's classifications from the C++ shim (Probe op); MergeIntervals wired to intersect.curve_class_p5 (#150); IsCrossedByEdge/HasNearMisses wired to intersect.curve_class_p7 (#65). Back-edges wired: trimming_tools (FlipFaceOrientation, SplitAtSeam), edge, face_p2 points_in_face, mass.integrator (CreateOrientedTrimBoundary); not wirable yet: surface.surface_p3 RemoveUVTrimCurves of owner face (no B-rep there), proto_topology_p2 (ProtoBrep handle). Was: 14 fns; largest: SmFace::MoveSeam, SmFace::SplitAtSeam, SmFace::CreateOrientedTrimBoundary, SmFace::CalculateUVDomainFromUVTrimCurves; cycle back-edges (call into later units; stub or co-port): heal.gap(1), topology.brep_p2(1); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit CalculateUVDomainFromUVTrimCurves wired into boolean.topology_intersector_p1 (PR #146). [topology.face_p3 wired (PR #139): 2312 cases, 990 pass, 0 failed, 1322 deferred (topology.face_p4 1044, topology.face_p3 180 at the justified SetSurface CHANGE_GEOMETRY Notify stub, cpp-crash-guard UB-137 98)] UPDATE after topology.brep_p2 (PR #149): the UNPORTED(topology.brep_p2) SmBrep::ReplaceSurface stub of MoveSeam is wired to topology::brep::p2::replace_surface as SmFace.cpp:17602 (ReplaceSurface(this, pJoinedSurface, FALSE, NULL, FALSE): OldSurface::Delete, no modified list, no UV trim curves); still unreached (the preceding SmFace::GetBrep is topology.face_p4): re-run 2312 cases, 519 pass, 0 fail, 1793 deferred, unchanged. Re-run after merging #145 5b1b28ae (main through e1ae2640) into #149: 2312 cases, 807 pass, 0 fail, 1505 deferred (topology.face_p3 1100, topology.face_p4 307, cpp-crash-guard:UB-137 98); ReplaceSurface still unreached. Owner zone-tol cache (surface/owner-zone-tol): 2312 cases, 1967 pass, 0 fail, 345 deferred."
notes = "Cone TrimWithDomain (PR #670, 2026-10-23): the Cone arm of Surface::trim_with_domain runs the inherited SmSurfOfRevolution::TrimWithDomain with the cone ConvertUVFromNURBSToSTEP and iso curves (Cone::trim_with_domain); difftest 2312 cases, 2214 pass, 0 failed, 98 deferred (cpp-crash-guard:UB-137 98); the 12 surface.surface_p2 deferrals pass. G10 (PR #637, 2026-10-23): SetSurface's SmFace::Notify(SM_NO_CHANGE_GEOMETRY) wired (set_surface: empty face environment, null data; set_surface_notified: caller's environment with the surfaces' identities); Surface::split_at makes a missing extrusion NURB (GetOrCreateGwNurbPointer). difftest 2312 cases, 2202 pass, 0 failed, 110 deferred (cpp-crash-guard:UB-137 98, surface.surface_p2 12); all 209 topology.face_p3 deferrals pass. PARTIAL (PR #148): difftest 2312 cases (2260 built-in on the 15 pt_TestFiles B-reps (every op, UV trim curves as read and rebuilt; seam ops with 7 face-property variants on the periodic files) + 36 review crash reproducers + 16 generated), 807 pass bit-identical (max_rel_err 0), 0 failed, 0 both-errored (real passes 807), 1505 deferred (topology.face_p3 1100: SmFace::Notify of every edit, CreateUVTrimCurves; topology.face_p4 307: GetUpwardLoopuses/GetBrep/HasSeamProblem; cpp-crash-guard:UB-137 98: seam calls the shim keeps from upstream's SplitAtSeam crash, never comparable; the guard skips ~99.5% valid MoveSeam+split calls and must be replaced when face_p4 lands); --perturb SENSITIVE 807/807. NormalizedEvaluate/GetOrCreateUVTrimCurve wired to topology.edgeuse_p3 in PR #117 (288 deferred cases now pass). No passing case for UvDomain (with surface), Flip, SetSurface, LoopRoundTrip, RemoveUv, SwapUv, Shrink (all behind face_p3/face_p4); passes are early returns, error paths and guards. The shim normalises analytic geometry to B-splines, so MoveSeam's CopyAndAddAnalytics branch and SwapUV's conic mirrors are not compared. Seam ops take HasSeamProblem's classifications from the C++ shim (Probe op); MergeIntervals wired to intersect.curve_class_p5 (#150); IsCrossedByEdge/HasNearMisses wired to intersect.curve_class_p7 (#65). Back-edges wired: trimming_tools (FlipFaceOrientation, SplitAtSeam), edge, face_p2 points_in_face, mass.integrator (CreateOrientedTrimBoundary); not wirable yet: surface.surface_p3 RemoveUVTrimCurves of owner face (no B-rep there), proto_topology_p2 (ProtoBrep handle). Was: 14 fns; largest: SmFace::MoveSeam, SmFace::SplitAtSeam, SmFace::CreateOrientedTrimBoundary, SmFace::CalculateUVDomainFromUVTrimCurves; cycle back-edges (call into later units; stub or co-port): heal.gap(1), topology.brep_p2(1); unit-SCC #22 (220 units); 317 upstream test entry points reach this unit CalculateUVDomainFromUVTrimCurves wired into boolean.topology_intersector_p1 (PR #146). [topology.face_p3 wired (PR #139): 2312 cases, 990 pass, 0 failed, 1322 deferred (topology.face_p4 1044, topology.face_p3 180 at the justified SetSurface CHANGE_GEOMETRY Notify stub, cpp-crash-guard UB-137 98)] UPDATE after topology.brep_p2 (PR #149): the UNPORTED(topology.brep_p2) SmBrep::ReplaceSurface stub of MoveSeam is wired to topology::brep::p2::replace_surface as SmFace.cpp:17602 (ReplaceSurface(this, pJoinedSurface, FALSE, NULL, FALSE): OldSurface::Delete, no modified list, no UV trim curves); still unreached (the preceding SmFace::GetBrep is topology.face_p4): re-run 2312 cases, 519 pass, 0 fail, 1793 deferred, unchanged. Re-run after merging #145 5b1b28ae (main through e1ae2640) into #149: 2312 cases, 807 pass, 0 fail, 1505 deferred (topology.face_p3 1100, topology.face_p4 307, cpp-crash-guard:UB-137 98); ReplaceSurface still unreached. Owner zone-tol cache (surface/owner-zone-tol): 2312 cases, 1967 pass, 0 fail, 345 deferred."