mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-11 03:01:58 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc6b7172bc | |||
| 8936a33388 | |||
| 8c8f8a4820 |
@@ -24,7 +24,7 @@ const PLATE: &[u8] =
|
||||
const NOTCHED: &[u8] =
|
||||
include_bytes!("../../solidcore/tests/data/issue_2364_solidcraft_1080_notched_block.smb");
|
||||
|
||||
/// SolidCraft's rig set at the time of the kernel's check: every fix but six.
|
||||
/// SolidCraft's rig set at the time of the kernel's check: every fix but five.
|
||||
fn fixes() {
|
||||
let off = [
|
||||
"cone_unbounded_parameterized_as_nurb",
|
||||
@@ -32,7 +32,6 @@ fn fixes() {
|
||||
"fillet_round_on_round_as_definition",
|
||||
"fillet_overflow_crease_along_edge",
|
||||
"fillet_refuse_rounds_breaking_through",
|
||||
"shell_one_construction",
|
||||
];
|
||||
let on = solidcore::kernel::UpstreamFixes::NAMES
|
||||
.iter()
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ use crate::topology::{EdgeId, FaceId};
|
||||
#[non_exhaustive]
|
||||
pub enum OffsetFailure {
|
||||
/// The offset of `face` by `distance` folds: the wall is thicker than a radius of
|
||||
/// curvature of the face on its side (opt-in `fixes.shell_one_construction`).
|
||||
/// curvature of the face on its side (WR-718; every offset but the local operation).
|
||||
Folds {
|
||||
/// The face whose offset folds.
|
||||
face: FaceId,
|
||||
|
||||
-2
@@ -1487,8 +1487,6 @@ pub struct UpstreamFixes {
|
||||
pub plane_first_torus_section_exact: bool,
|
||||
/// A face made from exact pcurves keeps them as its edge uses' trims where upstream drops them and projects each edge's curve again later, a fit: the draft extrusion's curvature-keeping and tangent-plane walls ([`crate::primitive::primitive_creation::p2::PrimitiveCreationAccess::face_with_curves`]) pass theirs to `SmBrep::MakeFaceWithCurves` with `pbOptCrvOnSurf` set, and `SmBrep::CopyFaces` without the merge passes a face's own trims (`CreateCurvesFromFace`) on to its copy. And `MakeFaceWithCurves`'s keep step (`CreateUVTrimCurves(&uv, &orients)`) flips the orientations for a face made with its surface's orientation opposite: they are given along the loop, and `sm_FindBestUVCurve` matches them against the upward face use's edge uses, which run the other way there, so upstream's tangent test refuses every exact curve and each is projected again. A draft half disc's cone wall then keeps its seam pcurves on their edges to 1.2e-13, where upstream's are 2.4e-6 off. And an arc's draft wall on its cone covers just its face (from its seams' least angle to their greatest, exact), not the seams' padded reach, which `SmBrep::ShrinkGeometry` trimmed to the face where the pad passed a thousandth of the face's size, removing the exact trims to project the edges again; so a seam cut at a span end of the cone keeps its quartics (#2497) (WR-722, #2405; solidcore-native, no fork commit).
|
||||
pub keep_exact_pcurves: bool,
|
||||
/// Shell, offset and thicken as one construction (tangency-labs/solidcore#2350; solidcore-native, no fork commit; WR-718). Built so far: its first stage, S1, each face's offset sheet exact or certified ([`crate::offset::construction::sheets`]), run inside today's executive. A B-spline face whose offset folds (the wall is thicker than a radius of curvature on its side: `(1 - k1 d)(1 - k2 d) > 0` not certified on the face) is refused with [`crate::error::offset::OffsetFailure::Folds`] (a [`crate::SolidError::Failed`], status `SM_ERR`), naming the largest wall that works, instead of being built into a folded body or failing on its broken topology; and `ShellBrep`'s `ReplaceImplicitOffsets` replaces each implicit offset surface by a bicubic B-spline certified within the shell's tolerance of the exact offset at every parameter (Elber & Cohen's bound, from exact Bernstein products), instead of `N_SrfOffset`'s sampled fit. Offset faces and draft (`LocalOperation`) are not affected. Off: SMLib's behaviour.
|
||||
pub shell_one_construction: bool,
|
||||
/// A face's planar section with no approximation tolerance given (`SmBrep::CreatePlanarSectionCurves` and `SmSurface::CreatePlanarSectionCurves` through [`crate::topology::brep::planar_section::face_planar_section_curves`], `SmApiIntersectBrepWithPlane`) is traced to the face's tolerance, as the curves are to lie within it of the face, where upstream passes the null on and the surface intersector traces to its default, 1/1000 of the surface's size: a G^n round's section on a 40 x 30 x 20 box left the round by 5.2e-3 at a face tolerance of 1.6e-4 (WR-723, #2451; solidcore-native, no fork commit).
|
||||
pub planar_section_face_tolerance: bool,
|
||||
/// `SmFace::SwapUV` transposes each UV trim by swapping its control points' `u` and `v` ([`crate::nurbs::b_spline_curve::BSplineCurve::create_transposed_curve`]), where upstream mirrors it about the plane through the origin with x axis `(1, 1, 0)` unitised: the rounded unit vector makes `N_CrvTransform`'s matrix `I - 2nn^T` `-2.2e-16` on the diagonal and `1 + 2.2e-16` off it, so every coordinate of a swapped face's trims moves by up to an ulp and a trim's end on a domain bound lands past it (a unit box's three faces `StitchAndOrient` swaps end at `v = -2.2e-16` and `1 - 1.1e-16` on `[0, 1]`). Every operation that orients or reverses faces passes its trims through it, so their ends then lie in the face's domain to rounding as they are made (WR-729, #2479; solidcore-native, no fork commit).
|
||||
|
||||
+3
-4
@@ -1,7 +1,6 @@
|
||||
//! Shell, offset and thicken as one construction (tangency-labs/solidcore#2350): exact
|
||||
//! or certified face offsets (S1), mitres (S2), their arrangement (S3), the
|
||||
//! winding-number cavity rule (S4) and extraction (S5). Opt-in,
|
||||
//! `UpstreamFixes::shell_one_construction` (WR-718).
|
||||
//! winding-number cavity rule (S4) and extraction (S5) (WR-718).
|
||||
//!
|
||||
//! Upstream: none (solidcore-native; the design is SMLib's, approved for SMLib and moved
|
||||
//! to solidcore; the fork has no code of it).
|
||||
@@ -12,8 +11,8 @@
|
||||
//! | [`sheets`] | none (S1; replaces `N_SrfOffset` and the fold check) |
|
||||
//!
|
||||
//! Built so far: S1. The stages after it (S2 to S5) are not built; until they are,
|
||||
//! the flag runs S1 inside today's executive: B-spline faces whose offset folds are
|
||||
//! refused naming the largest wall that works
|
||||
//! S1 runs inside today's executive, for every offset but the local operation: B-spline
|
||||
//! faces whose offset folds are refused naming the largest wall that works
|
||||
//! ([`crate::error::offset::OffsetFailure::Folds`]), and every implicit offset surface is
|
||||
//! replaced by its certified B-spline instead of `N_SrfOffset`'s sampled one.
|
||||
//!
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! S1 of the one shell construction: each face's offset sheet, exact or certified.
|
||||
//!
|
||||
//! Upstream: none (not upstream; solidcore-native, tangency-labs/solidcore#2350 §1.1 and
|
||||
//! §6 step 1; opt-in `UpstreamFixes::shell_one_construction`, WR-718).
|
||||
//! §6 step 1; WR-718).
|
||||
//!
|
||||
//! | Rust | Upstream |
|
||||
//! |------------------------------|-------------------------------------------------------|
|
||||
|
||||
+23
-28
@@ -71,6 +71,8 @@
|
||||
//! The lamina clean-up's `DeleteFaces` runs through merge p4's arena intersector over
|
||||
//! the offset B-rep alone (both upstream operands are that B-rep); it notifies under
|
||||
//! `DELETE_FACES_NAMESPACE` rather than the caller's offset keys.
|
||||
//! `ReplaceImplicitOffsets` replaces each implicit offset by its certified net
|
||||
//! (`certified_offset`, WR-718), not by NLib's `N_SrfOffset` fit checked at samples.
|
||||
//!
|
||||
//! Cancellation: `replace_implicit_offsets` polls the kernel's cancel token (#2136, #2372) per face and inside each surface offset.
|
||||
|
||||
@@ -93,7 +95,6 @@ use crate::intersect::topology_solver::p1::TopologySolver;
|
||||
use crate::intersect::topology_solver::p2::TopologySolveOptions;
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::{Vec3, BIG_DOUBLE, EFF_ZERO, EFF_ZERO_SQRT, PI};
|
||||
use crate::nurbs::b_spline_surface_n_lib::p1::OffsetParamType;
|
||||
use crate::nurbs::b_spline_surface_n_lib::p2::OrientType;
|
||||
use crate::nurbs::knots::Side;
|
||||
use crate::offset::offset_geometry_creation::{OffsetGeometryCreation, SsiData};
|
||||
@@ -885,12 +886,15 @@ impl OffsetExecutive {
|
||||
self.extended_surfaces.insert(base_key, surface);
|
||||
}
|
||||
|
||||
/// Replaces implicit offset surfaces with approximations (`ReplaceImplicitOffsets`).
|
||||
/// Replaces implicit offset surfaces with certified nets (`ReplaceImplicitOffsets`).
|
||||
/// The unconditional initial shrink is `topology.brep_p4`'s `ShrinkGeometry`.
|
||||
///
|
||||
/// Every face with an `SmOffsetSurface` gets a cubic approximation of the offset of
|
||||
/// its extended (else its own) base, which must be a B-spline surface, within
|
||||
/// `approximation_tol_3d`. All approximations are built before any face is
|
||||
/// Every face with an `SmOffsetSurface` gets a bicubic B-spline within
|
||||
/// `approximation_tol_3d` of the exact offset of its extended (else its own) base at
|
||||
/// every parameter, in the base's parametrisation
|
||||
/// ([`crate::offset::construction::sheets::certified_offset`]; not upstream, WR-718:
|
||||
/// upstream fits NLib's `N_SrfOffset` and checks it at samples). The base must be a
|
||||
/// B-spline surface. All nets are built before any face is
|
||||
/// changed; each face is then notified (`PRE_EDIT`), given the new surface's
|
||||
/// natural domain and the surface (`SmBrep::ReplaceSurface`, old surface to the
|
||||
/// deleted list, UV trim curves rebuilt) and notified again (`POST_EDIT`), in
|
||||
@@ -898,7 +902,7 @@ impl OffsetExecutive {
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// `SM_ERR` for a base that is not a B-spline surface; those of the approximation
|
||||
/// `SM_ERR` for a base that is not a B-spline surface; those of `certified_offset`
|
||||
/// and of `ReplaceSurface` (`SER`). The notifications' statuses are not checked.
|
||||
pub fn replace_implicit_offsets(
|
||||
kernel: &mut Kernel,
|
||||
@@ -933,28 +937,19 @@ impl OffsetExecutive {
|
||||
let Some(nurb) = crate::topology::brep::p3::with_nurb(kernel, base)? else {
|
||||
return Err(SolidError::sm_err(OP));
|
||||
};
|
||||
let approximation = if kernel.fixes.shell_one_construction {
|
||||
// Not upstream (WR-718, #2350 S1): the offset certified within the
|
||||
// tolerance at every parameter, in the base's parametrisation
|
||||
let base_nurb = nurb.gw_nurb().ok_or_else(|| SolidError::null_pointer(OP))?;
|
||||
let made = crate::offset::construction::sheets::certified_offset(
|
||||
kernel,
|
||||
base_nurb,
|
||||
offset.offset_distance(),
|
||||
approximation_tol_3d,
|
||||
)?;
|
||||
crate::nurbs::b_spline_surface::p1::BSplineSurface::from_nurb(made.surface, false)
|
||||
} else {
|
||||
// use NLIB function to build offset surface approximation
|
||||
// (the cancel token polled inside, #2136 / #2372; none changes nothing)
|
||||
nurb.approximate_offset_surface_cancellable(
|
||||
offset.offset_distance(),
|
||||
approximation_tol_3d,
|
||||
OffsetParamType::Inherited,
|
||||
crate::nurbs::srf_offset::OffsetOptions::default(),
|
||||
kernel.cancel_token(),
|
||||
)?
|
||||
};
|
||||
// Not upstream (WR-718, #2350 S1): the offset certified within the tolerance
|
||||
// at every parameter, in the base's parametrisation, where upstream fits
|
||||
// NLib's `N_SrfOffset` and checks it at samples (the cancel token polled
|
||||
// inside, #2136 / #2372)
|
||||
let base_nurb = nurb.gw_nurb().ok_or_else(|| SolidError::null_pointer(OP))?;
|
||||
let made = crate::offset::construction::sheets::certified_offset(
|
||||
kernel,
|
||||
base_nurb,
|
||||
offset.offset_distance(),
|
||||
approximation_tol_3d,
|
||||
)?;
|
||||
let approximation =
|
||||
crate::nurbs::b_spline_surface::p1::BSplineSurface::from_nurb(made.surface, false);
|
||||
// remember face/surface pair
|
||||
fix.push((f, Surface::from(approximation)));
|
||||
}
|
||||
|
||||
@@ -243,9 +243,9 @@ fn make_shell_edges_exact(
|
||||
/// at the original B-rep's tolerance): the distance reaches a radius of curvature of the face
|
||||
/// (a round offset by its radius), so the offset is a line there, neither a face nor inside
|
||||
/// out, and building on lifts the faces beside it across it and grows their tolerances into
|
||||
/// an unsound body. An offset that folds (the distance passes the radius) is not refused
|
||||
/// here: its face comes out inside out, and the executive takes it away as upstream does
|
||||
/// (the lamina edge test). A base that is no NURB has nothing to prove.
|
||||
/// an unsound body. An offset that folds (the distance passes the radius) is
|
||||
/// [`refuse_folding_offset`]'s, which runs after it. A base that is no NURB has nothing to
|
||||
/// prove.
|
||||
fn refuse_singular_offset(
|
||||
kernel: &Kernel,
|
||||
original: &Brep,
|
||||
@@ -277,7 +277,7 @@ fn refuse_singular_offset(
|
||||
}
|
||||
}
|
||||
|
||||
/// Opt-in (WR-718, #2350 S1): refuses the offset of `face` (whose implicit offset
|
||||
/// Not upstream (WR-718, #2350 S1): refuses the offset of `face` (whose implicit offset
|
||||
/// surface is `offset`) when it folds on the face's parameter box
|
||||
/// ([`crate::offset::construction::sheets::certify_regular`]), naming the largest wall
|
||||
/// that works.
|
||||
@@ -1474,23 +1474,13 @@ impl OffsetExecutive {
|
||||
super::face::build_local(original, &self.local_faces, &self.local_surfaces, faceuse)?
|
||||
};
|
||||
// Not upstream (#2350): a face whose offset provably collapses (a round offset by
|
||||
// its radius) is refused, never built into an unsound body
|
||||
// its radius) is refused, never built into an unsound body; one whose offset folds
|
||||
// (WR-718, S1) is refused, naming the largest wall that works
|
||||
if self.local_faces.is_empty() && self.operation() != OffsetOperation::LocalOperation {
|
||||
if let Some(crate::surface::surface::SurfaceKind::OffsetSurface(offset)) =
|
||||
surface.as_ref().map(crate::surface::surface::Surface::kind)
|
||||
{
|
||||
refuse_singular_offset(kernel, original, id, offset, operation)?;
|
||||
}
|
||||
}
|
||||
// Not upstream (WR-718, #2350 S1), opt-in `Kernel::fixes.shell_one_construction`:
|
||||
// a face whose offset folds is refused, naming the largest wall that works
|
||||
if kernel.fixes.shell_one_construction
|
||||
&& self.local_faces.is_empty()
|
||||
&& self.operation() != OffsetOperation::LocalOperation
|
||||
{
|
||||
if let Some(crate::surface::surface::SurfaceKind::OffsetSurface(offset)) =
|
||||
surface.as_ref().map(crate::surface::surface::Surface::kind)
|
||||
{
|
||||
refuse_folding_offset(kernel, original, id, offset, operation)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,14 @@
|
||||
//! has replaced more groups than its bound allows (module deviations of
|
||||
//! `topology::proto_topology::p3`).
|
||||
//!
|
||||
//! Face 7 of the part bends with radius 0.97 towards the outset, so its offset folds at
|
||||
//! 2.5: the offset is now refused before anything is built, naming that radius as the
|
||||
//! largest wall that works (#2350 S1, D5), and the loop is no longer reached from this
|
||||
//! part. The bound stays (a hang fix is unconditional); no other known input reaches it.
|
||||
//!
|
||||
//! The solid is the upstream test file, read when the pinned upstream checkout is present
|
||||
//! (the test is skipped otherwise). The offset runs on its own thread: without the bound
|
||||
//! it never ends, and the test fails at the time budget instead of hanging the suite.
|
||||
//! (the test is skipped otherwise). The offset runs on its own thread, so a regression
|
||||
//! that hangs fails at the time budget instead of hanging the suite.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
@@ -20,7 +25,8 @@
|
||||
mod offset_fixture;
|
||||
|
||||
use offset_fixture::Notifications;
|
||||
use solidcore::error::SolidError;
|
||||
use solidcore::error::offset::OffsetFailure;
|
||||
use solidcore::error::{ErrorKind, Reason};
|
||||
use solidcore::geom::attribute::{AttributeStore, ObjectKey};
|
||||
use solidcore::geom::object::ObjectRef;
|
||||
use solidcore::offset::offset_executive::p1::{OffsetExecutive, OffsetOperation};
|
||||
@@ -62,12 +68,12 @@ fn extrusion_old(k: &mut Kernel) -> Option<Brep> {
|
||||
Some(b)
|
||||
}
|
||||
|
||||
/// Generous for the debug build on a loaded machine (the bounded call takes about 100 s
|
||||
/// there and 3 s in release).
|
||||
/// Generous for the debug build on a loaded machine (the bounded call took about 100 s
|
||||
/// there and 3 s in release; the refusal comes well before).
|
||||
const BUDGET: std::time::Duration = std::time::Duration::from_secs(1200);
|
||||
|
||||
#[test]
|
||||
fn hang_51_extrusion_old_rounded_outset_returns() {
|
||||
fn hang_51_extrusion_old_rounded_outset_is_refused_at_its_fold() {
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let mut k = Kernel::default();
|
||||
@@ -81,6 +87,7 @@ fn hang_51_extrusion_old_rounded_outset_returns() {
|
||||
exec.set_inset(false);
|
||||
exec.set_extend_convex_edges(false);
|
||||
exec.set_merge_results(true);
|
||||
let face7 = part.face_list()[7];
|
||||
let mut notifications = Notifications::new(&mut part);
|
||||
let result = exec
|
||||
.do_solid_offset(
|
||||
@@ -90,18 +97,26 @@ fn hang_51_extrusion_old_rounded_outset_returns() {
|
||||
&mut notifications.context(),
|
||||
)
|
||||
.map(|b| b.face_list().len());
|
||||
tx.send(Some(result)).unwrap();
|
||||
tx.send(Some((result, face7))).unwrap();
|
||||
});
|
||||
let result = match rx.recv_timeout(BUDGET) {
|
||||
Ok(None) => return,
|
||||
Ok(Some(result)) => result,
|
||||
Err(e) => panic!("the offset did not return within {BUDGET:?} (HANG-51): {e}"),
|
||||
};
|
||||
// the C++ has no result to compare with; the bound ends the call with its error
|
||||
match result {
|
||||
Err(SolidError::InvalidInput { reason, .. }) => {
|
||||
assert!(reason.contains("HANG-51"), "{reason}");
|
||||
}
|
||||
other => panic!("expected the HANG-51 bound's error, got {other:?}"),
|
||||
}
|
||||
let (result, face7) = result;
|
||||
let e = result.unwrap_err();
|
||||
assert_eq!(e.kind(), ErrorKind::OffsetFolds, "{e}");
|
||||
let Some(&Reason::Offset(OffsetFailure::Folds {
|
||||
face,
|
||||
distance,
|
||||
largest,
|
||||
})) = e.failure().map(|f| &f.reason)
|
||||
else {
|
||||
panic!("{e}");
|
||||
};
|
||||
assert_eq!(face, face7);
|
||||
assert_eq!(distance, 2.5);
|
||||
// face 7's least radius of curvature on the outset side, measured: 0.9689470076317032
|
||||
assert!((largest - 0.968_947_007_631_703_2).abs() < 1e-9, "largest {largest}");
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//! tangency-labs/solidcore#2350, S1 (opt-in `UpstreamFixes::shell_one_construction`,
|
||||
//! WR-718): each face's offset sheet exact or certified.
|
||||
//! tangency-labs/solidcore#2350, S1 (WR-718): each face's offset sheet exact or certified,
|
||||
//! on the default kernel.
|
||||
//!
|
||||
//! The issue's §3.1 parabolic sheet, the quadratic Bezier (-10, 0), (0, 10), (10, 0)
|
||||
//! extruded 20 (one span, and two spans with a knot at s = 1/2), thickened by `t`:
|
||||
//! towards the centre of curvature the solid is the tube formula's
|
||||
//! `20 (t L - t^2 pi / 4)` (L = 22.955871, the arc length; the tangent turns by pi/2),
|
||||
//! away from it `20 (t L + t^2 pi / 4)`. At t11 towards the centre the offset folds
|
||||
//! (the apex's radius of curvature is 10): with the flag the thicken is refused naming
|
||||
//! 10 (D5); without it SMLib's port fails on its broken topology (the pinned C++ builds
|
||||
//! a folded body of one span and crashes on two).
|
||||
//! (the apex's radius of curvature is 10): the thicken is refused naming 10 (D5), where
|
||||
//! SMLib's port failed on its broken topology (the pinned C++ builds a folded body of one
|
||||
//! span and crashes on two).
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
@@ -171,12 +171,6 @@ fn offset_face_error(k: &Kernel, b: &Brep, t: f64) -> f64 {
|
||||
worst
|
||||
}
|
||||
|
||||
fn kernel(flag: bool) -> Kernel {
|
||||
let mut k = Kernel::default();
|
||||
k.fixes.shell_one_construction = flag;
|
||||
k
|
||||
}
|
||||
|
||||
/// S1's gate: the offset face is the certified offset, within the shell's tolerance
|
||||
/// (1e-5) of distance `t` from the sheet at every parameter (sampled: 2.7e-6 at t2), so
|
||||
/// the solid is the tube formula's to the tolerance times its boundary area. Measured at
|
||||
@@ -187,7 +181,7 @@ fn kernel(flag: bool) -> Kernel {
|
||||
fn parabola_thickens_to_its_certified_offset() {
|
||||
for two in [false, true] {
|
||||
for (d, t, towards) in [(2.0, 2.0, true), (-2.0, 2.0, false), (5.0, 5.0, true)] {
|
||||
let mut k = kernel(true);
|
||||
let mut k = Kernel::default();
|
||||
let sheet = parabola_sheet(&mut k, two);
|
||||
let mut b = thicken(&mut k, sheet, d).unwrap();
|
||||
assert_eq!(b.face_list().len(), 6);
|
||||
@@ -212,10 +206,12 @@ fn parabola_thickens_to_its_certified_offset() {
|
||||
}
|
||||
}
|
||||
|
||||
/// t11 towards the centre folds at the apex (radius 10): the default kernel refuses it,
|
||||
/// naming 10 (D5), where SMLib's port failed on its broken topology.
|
||||
#[test]
|
||||
fn parabola_fold_is_refused_naming_its_apex_radius() {
|
||||
for two in [false, true] {
|
||||
let mut k = kernel(true);
|
||||
let mut k = Kernel::default();
|
||||
let sheet = parabola_sheet(&mut k, two);
|
||||
let err = thicken(&mut k, sheet, 11.0).unwrap_err();
|
||||
let Some(failure) = err.failure() else {
|
||||
@@ -249,26 +245,8 @@ fn parabola_fold_is_refused_naming_its_apex_radius() {
|
||||
}
|
||||
}
|
||||
|
||||
/// With the flag off the shell is SMLib's: t11 fails as the port does (not refused), and
|
||||
/// t2 builds on `N_SrfOffset`'s sampled fit.
|
||||
#[test]
|
||||
fn flag_off_keeps_smlibs_results() {
|
||||
for two in [false, true] {
|
||||
let mut k = kernel(false);
|
||||
let sheet = parabola_sheet(&mut k, two);
|
||||
let err = thicken(&mut k, sheet, 11.0).unwrap_err();
|
||||
assert_ne!(err.kind(), ErrorKind::OffsetFolds, "{err:?}");
|
||||
let mut k = kernel(false);
|
||||
let sheet = parabola_sheet(&mut k, two);
|
||||
let mut b = thicken(&mut k, sheet, 2.0).unwrap();
|
||||
assert!(matches!(brep_is_manifold_solid(&b), Ok(true)));
|
||||
let v = volume(&k, &mut b).unwrap();
|
||||
assert!((v - tube(2.0, true)).abs() <= 1e-6 * v, "{v}");
|
||||
}
|
||||
}
|
||||
|
||||
/// §3.1's closed-form shells on analytic faces, which S1 leaves to their exact offsets:
|
||||
/// with every opt-in fix on (this flag included) they keep their closed forms.
|
||||
/// with every opt-in fix on they keep their closed forms.
|
||||
#[test]
|
||||
fn analytic_shells_keep_their_closed_forms() {
|
||||
use offset_fixture::{planar_faces_at, shell};
|
||||
|
||||
@@ -46,14 +46,13 @@ const SLOT_BAR: &[u8] = include_bytes!("data/issue_2364_solidcraft_3369_slot_bar
|
||||
const PLATE: &[u8] = include_bytes!("data/issue_2364_solidcraft_5158_plate.smb");
|
||||
const NOTCHED: &[u8] = include_bytes!("data/issue_2364_solidcraft_1080_notched_block.smb");
|
||||
|
||||
/// SolidCraft's rig set at the time (#2364's check): every fix but the five it leaves off.
|
||||
/// SolidCraft's rig set at the time (#2364's check): every fix but the four it leaves off.
|
||||
fn kernel() -> Kernel {
|
||||
let mut f = UpstreamFixes::all();
|
||||
f.cone_unbounded_parameterized_as_nurb = false;
|
||||
f.ellipse_open_arc_keeps_end_angle = false;
|
||||
f.fillet_overflow_crease_along_edge = false;
|
||||
f.fillet_refuse_rounds_breaking_through = false;
|
||||
f.shell_one_construction = false;
|
||||
Kernel {
|
||||
fixes: f,
|
||||
..Kernel::default()
|
||||
|
||||
+1
-2
@@ -42,14 +42,13 @@ const BACK_TOP: Point3 = Point3::new(30.0, 0.0, 10.0);
|
||||
/// A point on the upright at the knife-edge tip.
|
||||
const TIP: Point3 = Point3::new(W + 21.0, 0.0, 5.0);
|
||||
|
||||
/// SolidCraft's rig set: every fix but the five it leaves off.
|
||||
/// SolidCraft's rig set: every fix but the four it leaves off.
|
||||
fn kernel() -> Kernel {
|
||||
let mut f = UpstreamFixes::all();
|
||||
f.cone_unbounded_parameterized_as_nurb = false;
|
||||
f.ellipse_open_arc_keeps_end_angle = false;
|
||||
f.fillet_overflow_crease_along_edge = false;
|
||||
f.fillet_refuse_rounds_breaking_through = false;
|
||||
f.shell_one_construction = false;
|
||||
Kernel {
|
||||
fixes: f,
|
||||
..Kernel::default()
|
||||
|
||||
@@ -291,6 +291,5 @@
|
||||
"fillet_section_end_trim_at_creases",
|
||||
"fillet_overflow_crease_along_edge",
|
||||
"plane_first_torus_section_exact",
|
||||
"keep_exact_pcurves",
|
||||
"shell_one_construction"
|
||||
"keep_exact_pcurves"
|
||||
]
|
||||
|
||||
@@ -8,8 +8,13 @@
|
||||
//! as a nested edit and, its status ignored, kept the faces: three extra offset faces
|
||||
//! at distance 5.6, five at 11.2. At 11.2 the extra faces made coincident proto edges
|
||||
//! that `ResolveOverlappingProtoEdges` rebuilt forever (C++ finishes in seconds; the
|
||||
//! port ran past 30 minutes). The face counts are the C++ reference's (difftest unit
|
||||
//! `offset.offset_executive_p2`); in release the full offset takes well under a second.
|
||||
//! port ran past 30 minutes). The C++ reference keeps 20 faces at 5.6 and 12 at 11.2.
|
||||
//!
|
||||
//! Face 2 bends with radius 3.96 towards the outset, so its offset folds at both
|
||||
//! distances: the offset is refused before anything is built, naming that radius as the
|
||||
//! largest wall that works (#2350 S1, D5), and neither the deletion nor the proto-edge
|
||||
//! loop is reached from this part. The deletion stays covered by
|
||||
//! `offset_shell_lamina_face_delete.rs`.
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
@@ -21,6 +26,8 @@
|
||||
mod offset_fixture;
|
||||
|
||||
use offset_fixture::Notifications;
|
||||
use solidcore::error::offset::OffsetFailure;
|
||||
use solidcore::error::{ErrorKind, Reason};
|
||||
use solidcore::geom::attribute::{AttributeStore, ObjectKey};
|
||||
use solidcore::geom::object::ObjectRef;
|
||||
use solidcore::offset::offset_executive::p1::{OffsetExecutive, OffsetOperation};
|
||||
@@ -29,7 +36,8 @@ use solidcore::topology::brep::p10::TopologyEnv;
|
||||
use solidcore::topology::brep::p7::read_from_file;
|
||||
use solidcore::topology::database_io::FileType;
|
||||
use solidcore::topology::topology::NotifyRecords;
|
||||
use solidcore::{Brep, EntityRef, Kernel};
|
||||
use solidcore::topology::FaceId;
|
||||
use solidcore::{Brep, EntityRef, Kernel, SolidError};
|
||||
|
||||
fn fillet2(k: &mut Kernel) -> Option<Brep> {
|
||||
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
@@ -62,46 +70,47 @@ fn fillet2(k: &mut Kernel) -> Option<Brep> {
|
||||
Some(b)
|
||||
}
|
||||
|
||||
/// `DoSolidOffset` of the part at `distance`, face 0 the shell face.
|
||||
fn offset(distance: f64, skip_stitch_merge: bool) -> Option<Brep> {
|
||||
/// `DoSolidOffset` of the part at `distance`, face 0 the shell face, and the part's
|
||||
/// face 2; `None` (the test is skipped) without the pinned upstream checkout.
|
||||
fn offset(distance: f64, skip_stitch_merge: bool) -> Option<(Result<Brep, SolidError>, FaceId)> {
|
||||
let mut k = Kernel::default();
|
||||
let mut part = fillet2(&mut k)?;
|
||||
let face = part.face_list()[0];
|
||||
let faces = part.face_list();
|
||||
let (face, face2) = (faces[0], faces[2]);
|
||||
let mut exec = OffsetExecutive::new(OffsetOperation::SolidOffset, distance, 1e-5);
|
||||
exec.set_inset(false);
|
||||
exec.set_extend_convex_edges(false);
|
||||
exec.set_merge_results(true);
|
||||
exec.set_shell_faces(&[face]);
|
||||
let mut notifications = Notifications::new(&mut part);
|
||||
Some(
|
||||
exec.do_solid_offset(
|
||||
&mut k,
|
||||
Some(&mut part),
|
||||
SolidOffsetOptions { skip_stitch_merge },
|
||||
&mut notifications.context(),
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
let result = exec.do_solid_offset(
|
||||
&mut k,
|
||||
Some(&mut part),
|
||||
SolidOffsetOptions { skip_stitch_merge },
|
||||
&mut notifications.context(),
|
||||
);
|
||||
Some((result, face2))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offsets_deleted_by_lamina_edge_test_are_gone_before_the_merge() {
|
||||
// C++: 20 faces at 5.6 and 12 at 11.2 (the port kept 23 and 17)
|
||||
for (distance, faces) in [(5.6, 20), (11.2, 12)] {
|
||||
let Some(b) = offset(distance, true) else {
|
||||
fn rounded_outsets_past_face_2s_radius_are_refused_naming_it() {
|
||||
for (distance, skip_stitch_merge) in [(5.6, true), (11.2, true), (11.2, false)] {
|
||||
let Some((result, face2)) = offset(distance, skip_stitch_merge) else {
|
||||
return;
|
||||
};
|
||||
assert_eq!(b.face_list().len(), faces, "distance {distance}");
|
||||
let e = result.unwrap_err();
|
||||
assert_eq!(e.kind(), ErrorKind::OffsetFolds, "{e}");
|
||||
let Some(&Reason::Offset(OffsetFailure::Folds {
|
||||
face,
|
||||
distance: d,
|
||||
largest,
|
||||
})) = e.failure().map(|f| &f.reason)
|
||||
else {
|
||||
panic!("{e}");
|
||||
};
|
||||
assert_eq!(face, face2);
|
||||
assert_eq!(d, distance);
|
||||
// face 2's least radius of curvature on the outset side, measured: 3.9579840524301826
|
||||
assert!((largest - 3.957_984_052_430_182_6).abs() < 1e-9, "largest {largest}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rounded_outset_at_11_2_finishes() {
|
||||
let Some(b) = offset(11.2, false) else {
|
||||
return;
|
||||
};
|
||||
assert_eq!(
|
||||
(b.face_list().len(), b.edges().len(), b.vertices().len()),
|
||||
(13, 40, 29)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
//! So the offset refuses it, naming why (#2350; owner ruling): a face whose offset is proved
|
||||
//! to collapse is refused before anything is built (`OffsetFailure::Collapses`, radius 2).
|
||||
//! Face 2's profile also bends with radius 1.53 towards the inset (at u = 2.68 of 0..4.37),
|
||||
//! so its offset folds: a folding face comes out inside out and the executive takes it
|
||||
//! away (`offset_shell_lamina_face_delete`'s inset 2.5), so it is not refused.
|
||||
//! so its offset folds, and face 2 comes before face 5: kept as a shell face only face 0,
|
||||
//! the inset is refused at face 2 (`OffsetFailure::Folds`, the largest wall 1.53, D5);
|
||||
//! kept with face 2 as well, at face 5's collapse.
|
||||
//!
|
||||
//! When #2350 builds the offset as one construction (exact offsets and mitres: the faces
|
||||
//! beside a collapsed round meet across it), this becomes the sound-body assertion it
|
||||
@@ -110,7 +111,7 @@ fn inset_by_the_rounds_radius_is_refused_naming_why() {
|
||||
return;
|
||||
};
|
||||
let faces = bp1::faces(&part, None).unwrap();
|
||||
let e = inset_by_two(&mut k, &mut part, &[faces[0]]).unwrap_err();
|
||||
let e = inset_by_two(&mut k, &mut part, &[faces[0], faces[2]]).unwrap_err();
|
||||
assert_eq!(e.kind(), ErrorKind::OffsetCollapses, "{e}");
|
||||
let OffsetFailure::Collapses {
|
||||
face,
|
||||
@@ -125,3 +126,26 @@ fn inset_by_the_rounds_radius_is_refused_naming_why() {
|
||||
assert!((radius - 2.0).abs() < 1e-9, "radius {radius}");
|
||||
assert!(e.to_string().contains("collapses"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inset_past_face_2s_radius_is_refused_naming_its_largest_wall() {
|
||||
let mut k = Kernel::default();
|
||||
let Some(mut part) = extrusion(&mut k) else {
|
||||
return;
|
||||
};
|
||||
let faces = bp1::faces(&part, None).unwrap();
|
||||
let e = inset_by_two(&mut k, &mut part, &[faces[0]]).unwrap_err();
|
||||
assert_eq!(e.kind(), ErrorKind::OffsetFolds, "{e}");
|
||||
let OffsetFailure::Folds {
|
||||
face,
|
||||
distance,
|
||||
largest,
|
||||
} = offset_failure(&e)
|
||||
else {
|
||||
panic!("{e}");
|
||||
};
|
||||
assert_eq!(face, faces[2]);
|
||||
assert_eq!(distance.abs(), 2.0);
|
||||
// face 2's least radius of curvature on the inset side, measured: 1.5331125921683275
|
||||
assert!((largest - 1.533_112_592_168_327_5).abs() < 1e-9, "largest {largest}");
|
||||
}
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
//! original surface than the offset distance (`BuildOffsetOfFaceuse`, cpp:3730-3759).
|
||||
//!
|
||||
//! `Boolean/extrudeHoles1.smb` outset by 24.7 keeping face 0 as a shell face (the
|
||||
//! `offset.offset_executive_p2` case `pi_extrudeHoles1_f0_k0.38_out_rnd`), and
|
||||
//! `Defeature/extrusion.smb` inset by 2.5 keeping face 0: one offset face has a lamina
|
||||
//! edge point nearer the original surface than 24.7 (resp. 2.5) less twice the
|
||||
//! `offset.offset_executive_p2` case `pi_extrudeHoles1_f0_k0.38_out_rnd`): one offset face
|
||||
//! has a lamina edge point nearer the original surface than 24.7 less twice the
|
||||
//! approximation tolerance, so upstream deletes it with
|
||||
//! `SmTopologyIntersector::DeleteFaces`. The port ran that deletion through the kernel
|
||||
//! context while the face builder held its notification callbacks, so the deletion
|
||||
//! failed as a nested edit, its status was ignored and the face was kept: the
|
||||
//! unstitched offset had one face and four edges more than the C++ reference.
|
||||
//!
|
||||
//! The expected counts are the pinned C++ reference's.
|
||||
//! The expected counts are the pinned C++ reference's. `Defeature/extrusion.smb` inset by
|
||||
//! 2.5 deleted its face 2 the same way; that face's offset folds (radius 1.53), so the
|
||||
//! offset is now refused before it is built (`offset_shell_extrusion_extend.rs`).
|
||||
#![allow(
|
||||
clippy::unwrap_used,
|
||||
clippy::expect_used,
|
||||
@@ -109,11 +110,3 @@ fn extrude_holes_outset_deletes_the_face_off_its_original() {
|
||||
};
|
||||
assert_eq!(counts, (24, 84), "faces, edges");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extrusion_inset_deletes_the_face_off_its_original() {
|
||||
let Some(counts) = unstitched_counts("Defeature/extrusion.smb", 2.5, 1e-4, true) else {
|
||||
return;
|
||||
};
|
||||
assert_eq!(counts, (14, 65), "faces, edges");
|
||||
}
|
||||
|
||||
@@ -1159,7 +1159,7 @@ fn a_body_s_circle_round_offsets_to_its_round_of_r_minus_d() {
|
||||
}
|
||||
|
||||
/// #2487: with every fix on the chamfered box offsets (it was refused with status 1001:
|
||||
/// the offset's fold certificate, `shell_one_construction`, raised the G2 rounds' rational
|
||||
/// the offset's fold certificate, S1's `certify_regular`, raised the G2 rounds' rational
|
||||
/// septic nets to degree 112, past its Bernstein forms' cap), to the same faces as with
|
||||
/// #2388's fix set.
|
||||
#[test]
|
||||
|
||||
Vendored
+22
-31
@@ -44,8 +44,7 @@ The other rules follow from that one:
|
||||
tolerance (§7).
|
||||
|
||||
§3 and §9 say where the code meets the rule today and where it does not: a round whose offset is
|
||||
taken from its net, a revolved B-spline generator offset by a fit, and the shell's default
|
||||
approximation of implicit offsets by sampling.
|
||||
taken from its net, and a revolved B-spline generator offset by a fit.
|
||||
|
||||
## 2. Scope
|
||||
|
||||
@@ -115,16 +114,12 @@ round. A bound not reached is `SolidError::NotConverged`.
|
||||
|
||||
An `OffsetSurface` face stays implicit through the plain offset (`offset_brep`,
|
||||
`offset_executive/p2.rs`). The shell replaces it with a net before its boolean
|
||||
(`OffsetExecutive::replace_implicit_offsets`, `offset_executive/p1.rs`):
|
||||
- with `UpstreamFixes::shell_one_construction` (off by default, `kernel.rs`): `certified_offset`
|
||||
within the shell's tolerance, in the base's parametrisation;
|
||||
- without it: `approximate_offset_surface_cancellable`, the port of `N_SrfOffset`, a fit checked at
|
||||
samples.
|
||||
|
||||
Both take the base through `with_nurb(..).gw_nurb()`, the base's net. For a B-spline face that is
|
||||
(`OffsetExecutive::replace_implicit_offsets`, `offset_executive/p1.rs`): `certified_offset`
|
||||
within the shell's tolerance, in the base's parametrisation. It takes the base through
|
||||
`with_nurb(..).gw_nurb()`, the base's net. For a B-spline face that is
|
||||
S. For a Gⁿ round, a radius law or a round on a B-spline support, it is B, so the shell's offset of
|
||||
such a round is the offset of its net. That breaks the one rule, and the ledger
|
||||
`crates/solidcore/tests/data/blend_definition_ledger/offset.md` lists it. The certified path also
|
||||
`crates/solidcore/tests/data/blend_definition_ledger/offset.md` lists it. The net also
|
||||
drops its bound: the face gets `BSplineSurface::from_nurb(made.surface, false)`, a bare net with no
|
||||
stored ε (`p1.rs`). Both are open (§9).
|
||||
|
||||
@@ -163,7 +158,7 @@ job (§5.1).
|
||||
| refusal | when | what it names | checked | file |
|
||||
|---|---|---|---|---|
|
||||
| `OffsetFailure::Collapses` | a NURB-based face's least radius on the displaced side is within the input's tolerance of \|d\| | the face, the distance and the radius | always, before the face's offset is built | `offset_executive/p2.rs`, `refuse_singular_offset`; `sheets.rs`, `proved_singular` |
|
||||
| `OffsetFailure::Folds` | the offset of a NURB-based face is not certified regular over its parameter box | the face, the distance and `largest`, the least radius on the displaced side | with `shell_one_construction` only | `p2.rs`, `refuse_folding_offset`; `sheets.rs`, `certify_regular` |
|
||||
| `OffsetFailure::Folds` | the offset of a NURB-based face is not certified regular over its parameter box | the face, the distance and `largest`, the least radius on the displaced side | always but in the local operation, after the collapse test | `p2.rs`, `refuse_folding_offset`; `sheets.rs`, `certify_regular` |
|
||||
| `DistanceUnresolved`, `ToleranceUnresolved` | \|d\| below the input's tolerance; an approximation tolerance in (0, 1e-12] | the distance and the tolerance | always | `p2.rs`, `check_resolvable` |
|
||||
| `OffsetFailure::WallsTouch`, `LoopsCross`, `EdgeVanishes` | an offset face's loops touch or cross, or a vertex moves past its edge's end | the face or the edge | per their flags | `error/offset.rs` |
|
||||
|
||||
@@ -188,12 +183,9 @@ no surface (§3; `Surface::offset_collapses`), an `SM_ERR` with `offset_missing_
|
||||
a refusal by name. `SheetClass` (`Regular`, `Inverted`, `Spine`, `sheets.rs`) names the three cases
|
||||
but nothing outside its tests calls it yet.
|
||||
|
||||
**A fold that is not refused.** Without `shell_one_construction` a folding B-spline face is built;
|
||||
its offset face comes out inside out and the executive's lamina-edge test takes it away, as
|
||||
upstream does (`refuse_singular_offset`'s doc, `p2.rs`). The test
|
||||
`crates/solidcore/tests/offset_shell_extrusion_extend.rs` records one: a face that bends with radius
|
||||
1.53 towards an inset of 2 is not refused, while the round of radius 2 beside it is refused as a
|
||||
collapse.
|
||||
**A fold that is not refused.** The local operation runs neither test: a folding B-spline face is
|
||||
built there, its offset face comes out inside out and the executive's lamina-edge test takes it
|
||||
away, as upstream does (`offset_face_prefix`, `p2.rs`).
|
||||
|
||||
**In ops**, `Folds` becomes `Error::WallFolds { largest }` (`solidcore-ops/src/offset.rs`,
|
||||
`status`); `Collapses` reaches the caller as a plain kernel status, so the radius it names is not
|
||||
@@ -209,7 +201,7 @@ ops, so a body with room that no sample finds is refused.
|
||||
|
||||
## 5. The construction
|
||||
|
||||

|
||||

|
||||
|
||||
*Figure 2. Read each row left to right: row 1 is the design in `offset/construction.rs`, of which
|
||||
only S1 exists; row 2 is what runs, with S1 called from inside it.*
|
||||
@@ -221,7 +213,7 @@ offset and thicken:
|
||||
|
||||
| stage | what it does | today |
|
||||
|---|---|---|
|
||||
| S1 | each face's offset sheet, exact (§3) or certified (§3.1), with its regularity proved (§4) | built: `construction/sheets.rs`; run inside the executive with `shell_one_construction` |
|
||||
| S1 | each face's offset sheet, exact (§3) or certified (§3.1), with its regularity proved (§4) | built: `construction/sheets.rs`; run inside the executive |
|
||||
| S2 | mitres: each sheet extended by the reach and cut against its neighbours' | open as a stage; the executive's mitre (§5.2) does this job today |
|
||||
| S3 | the arrangement of all the sheets: their intersections cut space into cells | open |
|
||||
| S4 | the cavity rule: a cell is kept by its winding number with respect to the offset sheets | open |
|
||||
@@ -306,8 +298,8 @@ stitch:
|
||||
1. **Guard.** `check_shell_radius` refuses |d| < 2 × the input's tolerance.
|
||||
2. **Offset.** `run_shell_offset` offsets every face but the open ones (`shell_faces`, copied
|
||||
unmoved) into a new body.
|
||||
3. **Implicit offsets** are replaced by nets (§3.2), fitted to the input's tolerance at samples: a
|
||||
target, not a proved bound at every parameter. Only the certified path proves its net's bound.
|
||||
3. **Implicit offsets** are replaced by nets (§3.2), certified within the input's tolerance at
|
||||
every parameter.
|
||||
4. **A sheet** (the input is no manifold solid, `create_offset_solid`) is merged with the offset
|
||||
and stitched: from a tenth of the input's tolerance, ×10 per pass, at most four passes and never
|
||||
past |d| / 2. With `merge_sheet_side_walls` the side walls of a thickened sheet's coplanar edges
|
||||
@@ -325,7 +317,7 @@ expectation (`finish`, a closed solid for a thicken).
|
||||
**The body tolerance is the input's.** The executive's approximation tolerance is the input's
|
||||
B-rep tolerance (`configured_executive`, `p2.rs`), the offset body is allocated with it
|
||||
(`allocate_offset_brep`), the merge B-rep takes the original's, the implicit offsets are certified
|
||||
or fitted within it, and the shell's boolean runs at it. Edge and vertex tolerances only grow from
|
||||
within it, and the shell's boolean runs at it. Edge and vertex tolerances only grow from
|
||||
there: with `offset_edges_keep_source_tolerance` an offset edge takes at least its source edge's
|
||||
tolerance (`keep_source_tolerances`, `p1.rs`). An offset never makes a body tolerance of its own.
|
||||
|
||||
@@ -341,8 +333,7 @@ A reviewer checks each of these on a change to offsets or shells.
|
||||
every parameter (`certified_offset`), never fitted and checked at samples. The fitted paths in
|
||||
§3 and §3.2 are known exceptions, not precedents.
|
||||
3. **No folded body.** A face whose offset folds or collapses is refused before its offset is
|
||||
built, naming the face, the distance and the radius; it is never built and trimmed. (The fold
|
||||
refusal is opt-in today, §4.2.)
|
||||
built, naming the face, the distance and the radius; it is never built and trimmed.
|
||||
4. **Typed refusals.** Every refusal is an `OffsetFailure` with its entity, passed through ops
|
||||
with its size (today only `Folds` is, §4.2).
|
||||
5. **The reach is derived.** A face's extension is its reach plus one offset, from its edges'
|
||||
@@ -358,17 +349,18 @@ A reviewer checks each of these on a change to offsets or shells.
|
||||
|
||||
## 9. Status
|
||||
|
||||
**Flags.** `shell_one_construction` (S1) is off by default (`kernel.rs`), and so are the other
|
||||
offset fixes named here; AGENTS.md §4b asks for them to be turned on and their old paths deleted.
|
||||
The collapse refusal is on for every offset but the local operation.
|
||||
**Flags.** S1 has none: the certified nets and the fold refusal are the default. The other
|
||||
offset fixes named here are off by default (`kernel.rs`); AGENTS.md §4b asks for them to be turned
|
||||
on and their old paths deleted. The collapse and fold refusals are on for every offset but the
|
||||
local operation.
|
||||
|
||||
**Tests.**
|
||||
|
||||
| part | tests |
|
||||
|---|---|
|
||||
| S1: certified offset, regularity, collapse | `offset/construction/sheets.rs` tests (`parabola_folds_past_its_apex_radius`, `rational_cylinder_folds_inside_its_radius`, `a_round_offset_by_its_radius_is_proved_to_collapse`) |
|
||||
| S1 in the shell | `tests/issue_2350_shell_offset_construction.rs` (`parabola_thickens_to_its_certified_offset`, `parabola_fold_is_refused_naming_its_apex_radius`, `flag_off_keeps_smlibs_results`, `analytic_shells_keep_their_closed_forms`) |
|
||||
| the collapse refusal | `tests/offset_shell_extrusion_extend.rs` (`inset_by_the_rounds_radius_is_refused_naming_why`) |
|
||||
| S1 in the shell | `tests/issue_2350_shell_offset_construction.rs` (`parabola_thickens_to_its_certified_offset`, `parabola_fold_is_refused_naming_its_apex_radius`, `analytic_shells_keep_their_closed_forms`) |
|
||||
| the collapse and fold refusals | `tests/offset_shell_extrusion_extend.rs` (`inset_by_the_rounds_radius_is_refused_naming_why`, `inset_past_face_2s_radius_is_refused_naming_its_largest_wall`); `tests/offset_fillet2_rounded_outset.rs`; `tests/hang_51_extrusion_old_offset.rs` |
|
||||
| rounds offset by their definition | `tests/wr669_exact_round_classes.rs` (`a_box_round_offsets_to_the_round_of_r_plus_d_on_the_offset_faces`, `the_offset_of_a_circle_round_is_its_round_of_r_plus_minus_d`); `tests/wr698_gn_round_is_its_definition.rs` (`the_offset_of_a_round_is_the_offset_of_its_definition`) |
|
||||
| the reach | `offset/offset_executive/reach.rs` tests (`signed_reach_is_the_foot_of_the_offsets_meeting`, `an_unmoved_face_is_met_where_it_stands`) |
|
||||
| the room test | `offset/wall_room.rs` tests (`an_open_block_has_room_up_to_its_half_width`, `a_witness_that_does_not_certify_is_passed_over`) |
|
||||
@@ -386,7 +378,6 @@ The collapse refusal is on for every offset but the local operation.
|
||||
- `SheetClass` used: an inverted analytic sheet refused by name (§4.2).
|
||||
- `Collapses` mapped through ops with its radius (§4.2).
|
||||
- Edges between unmoved faces kept by the local operation (§6).
|
||||
- The fold refusal on by default, with the cases whose results change.
|
||||
|
||||
## 10. If you're stuck
|
||||
|
||||
@@ -398,7 +389,7 @@ The collapse refusal is on for every offset but the local operation.
|
||||
An `OffsetSurface` on a round means the round has no definition the offset can read.
|
||||
- **Compare d with the radius.** Find the least radius of curvature on the displaced side
|
||||
(`proved_singular` returns it). A distance at it is a collapse, past it a fold; neither is a
|
||||
mitre bug. Turn on `shell_one_construction` to see the refusal.
|
||||
mitre bug.
|
||||
- **Check the reach.** A mitre that misses (`mate_edges` finds no curve to trim) on a face with a
|
||||
sharp sector angle: print `extension_reach` for the face; a meeting outside the extension, or
|
||||
one that only touches its side, is the cause. A local operation uses its fixed bound instead.
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
<path d="M40 105 L210 105 L210 190 L40 190 Z" fill="#dce9f5" stroke="#555555" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text transform="matrix(1 0 0 1 50 130)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="14" font-weight="bold" fill="#000000"><tspan x="0" y="0">S1 face offsets</tspan></text>
|
||||
<text transform="matrix(1 0 0 1 50 153)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="12" fill="#000000"><tspan x="0" y="0">exact or certified</tspan></text>
|
||||
<text transform="matrix(1 0 0 1 50 173)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="12" fill="#000000"><tspan x="0" y="0">built, opt-in</tspan></text>
|
||||
<text transform="matrix(1 0 0 1 50 173)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="12" fill="#000000"><tspan x="0" y="0">built</tspan></text>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M213 146.25 L226.5 146.25 C226.914 146.25 227.25 146.586 227.25 147 C227.25 147.414 226.914 147.75 226.5 147.75 L213 147.75 C212.586 147.75 212.25 147.414 212.25 147 C212.25 146.586 212.586 146.25 213 146.25" fill="#000000"/>
|
||||
@@ -49,7 +49,7 @@
|
||||
<text transform="matrix(1 0 0 1 820 130)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="14" font-weight="bold" fill="#000000"><tspan x="0" y="0">S5 extraction</tspan></text>
|
||||
<text transform="matrix(1 0 0 1 820 153)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="12" fill="#000000"><tspan x="0" y="0">the result's faces</tspan></text>
|
||||
<text transform="matrix(1 0 0 1 820 173)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="12" fill="#000000"><tspan x="0" y="0">open</tspan></text>
|
||||
<text transform="matrix(1 0 0 1 40 255)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="14" fill="#000000"><tspan x="0" y="0">Today's executive (offset_executive), which runs S1 under shell_one_construction</tspan></text>
|
||||
<text transform="matrix(1 0 0 1 40 255)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="14" fill="#000000"><tspan x="0" y="0">Today's executive (offset_executive), which runs S1</tspan></text>
|
||||
<path d="M40 270 L210 270 L210 355 L40 355 Z" fill="#f0f0f0" stroke="#555555" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text transform="matrix(1 0 0 1 50 295)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="14" font-weight="bold" fill="#000000"><tspan x="0" y="0">face offsets</tspan></text>
|
||||
<text transform="matrix(1 0 0 1 50 318)" x="0" y="0" xml:space="preserve" font-family="Helvetica" font-size="12" fill="#000000"><tspan x="0" y="0">analytic kinds exact,</tspan></text>
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
+2
-2
@@ -441,7 +441,7 @@
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
"text": "built, opt-in",
|
||||
"text": "built",
|
||||
"style": {
|
||||
"font_family": "Helvetica",
|
||||
"font_style": "Regular",
|
||||
@@ -1717,7 +1717,7 @@
|
||||
],
|
||||
"runs": [
|
||||
{
|
||||
"text": "Today's executive (offset_executive), which runs S1 under shell_one_construction",
|
||||
"text": "Today's executive (offset_executive), which runs S1",
|
||||
"style": {
|
||||
"font_family": "Helvetica",
|
||||
"font_style": "Regular",
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
Updates: WR-718
|
||||
|
||||
#### WR-718 (update, 2026-11-07). The fix is the default; its flag is retired
|
||||
|
||||
`Kernel::fixes.shell_one_construction` is gone and S1's path is the only one:
|
||||
- `OffsetExecutive::replace_implicit_offsets` (`crates/solidcore/src/offset/offset_executive/p1.rs`)
|
||||
replaces every implicit offset by `certified_offset`, a bicubic B-spline within the shell's
|
||||
tolerance of the exact offset at every parameter. Its `N_SrfOffset` branch is deleted.
|
||||
`turn_to_nurbs` (`topology/brep/p3.rs`) still fits `N_SrfOffset`; that is not this record's path.
|
||||
- `offset_face_prefix` (`offset_executive/p2.rs`) refuses a face whose offset is not certified
|
||||
regular on its parameter box for every offset but the local operation, after the collapse test.
|
||||
The refusal is `OffsetFailure::Folds { face, distance, largest }` (a `SolidError::Failed`, status
|
||||
`SM_ERR`; `Error::WallFolds` in ops). The record's `SolidError::ShellRefused` /
|
||||
`ShellRefusal::Fold` are no longer in the code.
|
||||
|
||||
The record's trigger, the parabolic sheet thickened 11 towards its centre, is refused naming 10 on
|
||||
the default kernel, one span and two; thickened 2 both ways and 5 towards the centre it builds
|
||||
within the shell's tolerance of the tube formula
|
||||
(`crates/solidcore/tests/issue_2350_shell_offset_construction.rs`, now on `Kernel::default()`).
|
||||
The test of the flag's off path is deleted with the path.
|
||||
Reference in New Issue
Block a user