Offsets go through solidcore's simple calls; ignored tests cite solidcore issues

`shell_brep_simple`, `offset_brep_simple` and `local_operation_simple` carry the same options and
the source's attributes, so the 120 lines of key domains and notification stores in core/offset.rs
go (gap-audit D2). The held-flags comment now says what each flag's fork code does. Ignored tests cite
solidcore#1980, #1462, #1973 and #1981 instead of local files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk
This commit is contained in:
brett19
2026-10-31 03:01:40 -07:00
parent f01b877570
commit 9b73dfe749
5 changed files with 41 additions and 166 deletions
+26 -15
View File
@@ -254,22 +254,33 @@ pub(crate) fn fixes() -> UpstreamFixes {
f
}
/// Opt-in fixes left off because the C++ fork (`SMLIB_REV`) does not carry them yet, so turning
/// them on would part this backend from the C++ one. `precise_properties_trim_base_v`
/// (solidcore#1664, WR-563), `bspline_trim_keeps_rational_end_spans` (solidcore#1711, WR-575) and
/// `fillet_blend_curve_revolved_on_arc` (solidcore#1759, WR-581, an own design waiting for the
/// fork's fix) have no fork commit. `temporary_radius_setup_errors` (solidcore#644, #458), `ellipse_step_interval_checks`
/// (solidcore#686, #428) are the port's own fixes with no fork commit; the C++ build keeps
/// upstream's behaviour there.
/// `corner_patch_face_side_twist` (WR-613, fork 7fd9fb0) ports a fork commit that is not
/// published.
/// Opt-in fixes left off because the C++ fork (`SMLIB_REV`) does not behave as they do, so turning
/// them on would part this backend from the C++ one. Each was read in the fork's source at 2fc375f.
///
/// `boolean_union_refuses_solid_leak` (solidcore#1794, WR-619) is solidcore's own design;
/// `curve_loop_start_after_previous_end` (WR-96), `loop_use_reports_open_gaps` (WR-93),
/// `face_points_span_domain` (WR-86),
/// `drop_curve_v_domain_degeneracy` (WR-100) and `mirror_curve_keeps_euclidean_control_points`
/// (WR-98) are the port's fixes of upstream defects: none has a fork commit, and the C++ build
/// keeps upstream's behaviour there.
/// Upstream's defect is still there: `precise_properties_trim_base_v` (WR-563: `SmODEAreaIFEO` starts
/// at the domain's lowest v), `fillet_blend_curve_revolved_on_arc` (WR-581: `CalcTorusFilletGeom` takes
/// circular and linear sections only), `ellipse_step_interval_checks` (WR-211: `AdjustSTEPInterval`
/// tests no inverted or collapsed interval), `boolean_union_refuses_solid_leak` (WR-619: STEP 6 deletes
/// without testing regions), `curve_loop_start_after_previous_end` (WR-96), `loop_use_reports_open_gaps`
/// (WR-93), `face_points_span_domain` (WR-86), `drop_curve_v_domain_degeneracy` (WR-100),
/// `mirror_curve_keeps_euclidean_control_points` (WR-98), `geometric_g2_continuity` (WR-380: a normal
/// compared with itself), `fillet_corner_variable_radius_rail_on_opposite_fillet` (WR-645: no test of
/// the rail against the opposite fillet), `sphere_from_arcs_common_centre` (WR-103: `rOrigin` refers to
/// the origin it is compared with), `dupin_bottom_hyperbola_axis` (WR-69: k2 for both axes),
/// `extent3d_contains_both_segment_endpoints` (WR-3: `ContainsLineSeg3d` ors its two points),
/// `gap_samples_use_trimmed_spans` (WR-89: `BuildSampleSet` spans from the first knot),
/// `twist_simple_duw_sign` (WR-46: `EvaluateSimple`'s Duw y is -per cos) and
/// `translation_sweep_regularize` (solidcore#1937: nothing regularizes after the sweep).
///
/// The fork deals with it another way: `bspline_trim_keeps_rational_end_spans` (WR-575: the fork keeps
/// a short span of a UV curve, and still removes a 3D conic's) and `corner_patch_face_side_twist`
/// (WR-613: the fork refines the patch, `corner_blend_coons_patch_tangent_to_sides`).
///
/// The fork carries on softly where these refuse: `temporary_radius_setup_errors` (#458: a scope guard
/// cannot report), `fillet_corner_statuses` (`sm_TestG1Continuity`: no tangent is no tangency),
/// `fillet_edge_curve_domain_statuses` (`sm_CheckEdgeCurveDomains`: a failed trim keeps the edge's
/// curve), `offset_tangent_gap_statuses` (`HaveTangentGaps`: a failed sample shows no gap) and
/// `ssi_alignment_statuses` (`ALIGN_DIR_WITH_PREVIOUS_POINT`: no angle, no alignment).
///
/// On since `SMLIB_REV` eaa998a: `fillet_trim_inside_by_best_dot` (fork de4dae6),
/// `fillet_half_degree_crease` (2e8758b), `fillet_split_edge_keeps_near_half` (ceb3272) and
+2 -2
View File
@@ -515,7 +515,7 @@ fn rounds_at_a_concave_face_corner_are_mitred() {
/// The back and left top edges are each trimmed 8 by those rounds: (0, 30)-(32, 30) and (0, 8)-(0, 30)
/// at z = 10, and each piece keeps its edge's attribute (`Behavior::Copy`), as on the fork.
#[test]
#[ignore = "solidcore bug: SubtractRemovalRegion loses the attributes of target edges it replaces (next-pin/bugs/cliff-overflow-loses-edge-attributes.md)"]
#[ignore = "solidcore#1980 (PR #1977): SubtractRemovalRegion loses the attributes of target edges it replaces"]
fn an_edge_tag_crosses_a_cliff_overflow() {
use solidcore::blend::fillet_executive::session::FilletSession;
use solidcore::boolean::api_brep::boolean_difference;
@@ -562,7 +562,7 @@ fn an_edge_tag_crosses_a_cliff_overflow() {
/// 21000 - (50 d² + 4/3 d³) = 19265.666666667 (the fork, and the same body through the flat
/// arrays). Refused: error 3, Corner Edge Computation Failure.
#[test]
#[ignore = "solidcore bug: next-pin/bugs/chamfer-boolean-body-keep-operand-uv-trims.md"]
#[ignore = "solidcore#1981 (PR #1996)"]
fn pocket_chamfer_on_a_boolean_body() {
use solidcore::blend::fillet_executive::session::FilletSession;
use solidcore::boolean::api_brep::boolean_difference;
+1 -1
View File
@@ -646,7 +646,7 @@ mod history {
/// Two overlapping squares combined as regions of one plane.
#[test]
#[ignore = "solidcore's boolean_2d carries no attributes through its merge (#1462's scope; write-up review-1031/bugs/boolean2d-drops-history.md, not filed yet)"]
#[ignore = "solidcore#1462 (PR #1977): boolean_2d carries no attributes through its merge"]
fn a_boolean2d_has_the_cpp_history() {
use crate::sweeps::Curve;
let square = |x: f64, y: f64, s: f64, operand: usize| {
+11 -147
View File
@@ -18,10 +18,9 @@
use solidcore::EntityRef;
use solidcore::SolidError;
use solidcore::curve::curve::Curve;
use solidcore::geom::attribute::{Attribute, AttributeStore, Behavior, ObjectKey, ids};
use solidcore::geom::attribute::{Attribute, Behavior, ids};
use solidcore::geom::axis2_placement::Axis2Placement;
use solidcore::geom::object::{ObjectRef, Shape};
use solidcore::geom::object::Shape;
use solidcore::geom::vector3d::{Point3, Vec3};
use solidcore::intersect::point_class::PointClassType;
use solidcore::kernel::Kernel;
@@ -30,23 +29,17 @@ use solidcore::nurbs::b_spline_curve::ControlPointForm;
use solidcore::nurbs::b_spline_surface::p1::SurfParamType;
use solidcore::nurbs::b_spline_surface_n_lib::p2::OrientType;
use solidcore::nurbs::knots::Side;
use solidcore::offset::offset_executive::p2::{
OffsetNotifications, OffsetOptions, ShellCopyIdentities, ShellOptions, api_offset_brep_full, api_shell_brep_full,
};
use solidcore::offset::offset_executive::p2::{OffsetOptions, ShellOptions, local_operation_simple, offset_brep_simple, shell_brep_simple};
use solidcore::offset::offset_faces::{OffsetFacesExecutive, OffsetFacesRefusal};
use solidcore::query::api_queries::{BrepRef, face_bounding_box, face_classify_uv, face_closest_point_in_shape, face_internal_point};
use solidcore::query::api_queries::{face_bounding_box, face_classify_uv, face_closest_point_in_shape, face_internal_point};
use solidcore::query::brep::{BrepIdentity, BrepQuery, LiveBrepObjects};
use solidcore::surface::cone::Cone;
use solidcore::surface::surface::{Surface, SurfaceKind};
use solidcore::sweep::topology_sweep::store::SweepIdentities;
use solidcore::topology::brep::local_operation::local_operation;
use solidcore::topology::brep::{p1 as brep_p1, p2 as brep_p2};
use solidcore::topology::face::p2 as face_p2;
use solidcore::topology::topology::{NotifyRecords, Topology};
use solidcore::topology::{Brep, BrepId, EdgeId, FaceId, FaceuseId, RegionId};
use solidcore::topology::{Brep, BrepId, FaceId, FaceuseId};
use super::blend::{Miss, on_copy, p3, pick, points_ok, status as blend_status, tolerance_ok};
use super::boolean::{domain, named};
use super::{Body, answer};
use crate::{Error, Result};
@@ -279,149 +272,21 @@ fn leaves_cavity(k: &mut Kernel, b: &mut Brep, open: &[FaceId], wall: f64) -> so
Ok(false)
}
// Each B-rep the offset executive touches (the source, its working copy, the result, the merge's
// bodies) names its objects in its own key domain ([`named`]), as solidcore's offset fixture does,
// so their notification records never collide. Curves and lists have domains of their own.
const SOURCE: u64 = domain(1);
const RESULT: u64 = domain(2);
const WORKING: u64 = domain(3);
/// The merge's B-rep `brep`: the domains of its objects, its curves and its lists.
fn merge_domain(brep: BrepId, which: u64) -> u64 {
domain(16 + u64::from(brep.index()) * 3 + which)
}
fn source_key(e: EntityRef) -> ObjectRef {
named(e, SOURCE)
}
fn result_key(e: EntityRef) -> ObjectRef {
named(e, RESULT)
}
fn working_key(e: EntityRef) -> ObjectRef {
named(e, WORKING)
}
fn curve_in(domain: u64, edge: EdgeId, type_code: i64) -> ObjectRef {
ObjectRef { key: ObjectKey(domain + u64::from(edge.index())), type_code }
}
fn result_curve(edge: EdgeId, type_code: i64) -> ObjectRef {
curve_in(domain(4), edge, type_code)
}
fn source_curve(edge: EdgeId, type_code: i64) -> ObjectRef {
curve_in(domain(5), edge, type_code)
}
fn working_curve(edge: EdgeId, type_code: i64) -> ObjectRef {
curve_in(domain(6), edge, type_code)
}
fn lists_in(domain: u64, region: RegionId) -> [ObjectRef; 2] {
[0, 1].map(|list| ObjectRef { key: ObjectKey(domain + u64::from(region.index()) * 2 + list), type_code: 0 })
}
fn source_lists(result: bool, region: RegionId) -> [ObjectRef; 2] {
lists_in(domain(if result { 8 } else { 7 }), region)
}
fn working_lists(region: RegionId) -> [ObjectRef; 2] {
lists_in(domain(9), region)
}
fn merge_curve(brep: BrepId, edge: EdgeId, type_code: i64) -> ObjectRef {
ObjectRef { key: ObjectKey(merge_domain(brep, 1) + u64::from(edge.index())), type_code }
}
/// Identities of the B-reps the merge makes, disjoint from the keys above.
struct MergeBodies;
impl SweepIdentities for MergeBodies {
fn topology(&self, brep: BrepId, entity: EntityRef) -> ObjectRef {
named(entity, merge_domain(brep, 0))
}
fn owned_auxiliary_objects(&self, brep: BrepId, model: &Brep) -> Vec<ObjectKey> {
model.edges().iter().filter(|(_, e)| e.curve.is_some()).map(|(edge, _)| merge_curve(brep, edge, 0).key).collect()
}
fn curve(&self, brep: BrepId, edge: EdgeId, curve: &Curve) -> ObjectRef {
merge_curve(brep, edge, curve.type_code())
}
fn new_curve(&self, brep: BrepId, edge: EdgeId, type_code: i64) -> solidcore::Result<ObjectRef> {
Ok(merge_curve(brep, edge, type_code))
}
fn source_lists(&self, brep: BrepId, region: RegionId) -> solidcore::Result<[ObjectRef; 2]> {
Ok(lists_in(merge_domain(brep, 2), region))
}
}
/// The notification stores of one offset call on `source` (SM_API's context keeps them for the
/// process; each call here has its own).
struct Notifications {
records: NotifyRecords,
attributes: AttributeStore,
}
impl Notifications {
/// Registers `source` (B-rep 0 of the source domain), its objects and the reserved result.
fn new(source: &mut Brep) -> Notifications {
source.set_key(source_key(EntityRef::Brep(ONE)).key);
let mut records = NotifyRecords::new();
records.insert(source.key(), Topology::new());
records.insert(result_key(EntityRef::Brep(ONE)).key, Topology::new());
let entities = source
.shells()
.iter()
.map(|(id, _)| EntityRef::Shell(id))
.chain(source.faces().iter().map(|(id, _)| EntityRef::Face(id)))
.chain(source.faceuses().iter().map(|(id, _)| EntityRef::Faceuse(id)))
.chain(source.edges().iter().map(|(id, _)| EntityRef::Edge(id)))
.chain(source.edgeuses().iter().map(|(id, _)| EntityRef::Edgeuse(id)))
.chain(source.vertices().iter().map(|(id, _)| EntityRef::Vertex(id)))
.chain(source.vertexuses().iter().map(|(id, _)| EntityRef::Vertexuse(id)))
.chain(source.loopuses().iter().map(|(id, _)| EntityRef::Loopuse(id)))
.chain(source.regions().iter().map(|(id, _)| EntityRef::Region(id)));
for e in entities {
records.insert(source_key(e).key, Topology::new());
}
Notifications { records, attributes: AttributeStore::default() }
}
fn context(&mut self) -> OffsetNotifications<'_> {
OffsetNotifications {
records: &mut self.records,
attributes: &mut self.attributes,
original_key_of: &source_key,
result_key_of: &result_key,
original_curve_ref: &source_curve,
result_curve_ref: &result_curve,
boolean_source_lists: &source_lists,
result_brep: result_key(EntityRef::Brep(ONE)),
merge_identities: Some(&MergeBodies),
}
}
}
/// `SmApiShellBrepFull(b, distance, TRUE, TRUE, TRUE, open, r)`: convex edges extended, results
/// merged, side walls made at a sheet's edges, and (as the fork does, 5448b9a) a thickened
/// sheet's coplanar side walls one face.
fn shell_brep_full(k: &mut Kernel, mut b: Brep, distance: f64, open: &[FaceId]) -> solidcore::Result<Brep> {
let mut n = Notifications::new(&mut b);
fn shell_brep_full(k: &mut Kernel, b: &Brep, distance: f64, open: &[FaceId]) -> solidcore::Result<Brep> {
let options = ShellOptions {
offset: OffsetOptions { distance, extend_convex_edges: true, merge_results: true },
create_offset_solid: true,
merge_sheet_side_walls: true,
};
let faces: Vec<_> = open.iter().map(|&f| Some((ONE, f))).collect();
let copy = ShellCopyIdentities { key_of: &working_key, curve_ref: &working_curve, boolean_source_lists: &working_lists };
api_shell_brep_full(k, Some(BrepRef { brep: &b, handle: ONE }), options, &faces, &copy, &mut n.context())
shell_brep_simple(k, b, options, open)
}
/// `SmApiOffsetBrepFull(b, distance, TRUE, TRUE, r)`.
fn offset_brep_full(k: &mut Kernel, b: &mut Brep, distance: f64) -> solidcore::Result<Brep> {
let mut n = Notifications::new(b);
let options = OffsetOptions { distance, extend_convex_edges: true, merge_results: true };
api_offset_brep_full(k, Some(b), options, &mut n.context())
offset_brep_simple(k, b, OffsetOptions { distance, extend_convex_edges: true, merge_results: true })
}
/// `finish`: the result must have faces, and a solid input must give a closed manifold solid.
@@ -459,7 +324,7 @@ pub(crate) fn shell(body: &Body, points: &[[f64; 3]], tolerance: f64, thickness:
if thickness < 0.0 && solid_in && !leaves_cavity(k, &mut w, &picked, -thickness)? {
return Ok(Err(Error::NoRoom));
}
match called(shell_brep_full(k, w, thickness, &picked))? {
match called(shell_brep_full(k, &w, thickness, &picked))? {
Ok(r) => finish(r, solid_in),
Err(e) => Ok(Err(e)),
}
@@ -528,7 +393,7 @@ pub(crate) fn thicken(body: &Body, thickness: f64, symmetric: bool) -> Result<Bo
Err(e) => return Ok(Err(e)),
};
}
match called(shell_brep_full(k, w, thickness, &[]))? {
match called(shell_brep_full(k, &w, thickness, &[]))? {
Ok(r) => finish(r, true),
Err(e) => Ok(Err(e)),
}
@@ -787,8 +652,7 @@ pub(crate) fn draft(
// `moved_faces(b, status(b->LocalOperation(faces, surfaces)))`, its marks on the picked
// faces (`mark_moved`).
mark_moved(&mut w, &picked)?;
let mut notes = Notifications::new(&mut w);
if let Err(e) = called(local_operation(k, &mut w, &picked, surfaces, &mut notes.context()))? {
if let Err(e) = called(local_operation_simple(k, &mut w, &picked, surfaces))? {
return Ok(Err(e));
}
if !moved_faces(&mut w)? {
+1 -1
View File
@@ -775,7 +775,7 @@ mod history {
}
#[test]
#[ignore = "solidcore's stitch in the shell drops the flat sheet's edge tags (write-up review-1031/bugs/thicken-flat-sheet-edge-tags.md, not filed yet)"]
#[ignore = "solidcore#1973 (PR #1977): the stitch in the shell drops the flat sheet's edge tags"]
fn tags_cross_a_thicken_of_a_flat_sheet() {
thicken_parity(false);
}