Files
jyviko dc38349cbf Layer split stage 1: the B-rep record, native body file and Vec/Plane come from solidcore-record (#11496)
* Kernel: the B-rep record and the native body file come from solidcore-record

SolidCraft's portable B-rep record (`solidcraft_geom::brep`: brep.rs, topology, history,
envelope, part, curve2) and the native body file (`solidcraft_kernel::native`: the tagged binary
codec and the container) move to solidcore's `solidcore-record` crate: geometry as data, with
structural validation and no maths (solidcore's docs/layers.md, stage 1). Every caller uses
`solidcore_record::brep` and `solidcore_record::native`; the old copies are deleted.

What computes from coordinates stays with the kernel, in geom's `brep`:
- `brep::check`: the record's geometric checks (orthonormal frames, non-degenerate planes,
  unit revolution axes, non-singular placements). `check::validate` and
  `check::validate_built` are the record's validation and these together, and every caller of
  `Brep::validate` uses them, so what is accepted is unchanged;
- `brep::label`: a surface's natural ranges (a cone's apex), `curve_interval`,
  `edge_curve_interval` and `form_turns`;
- `brep::pcurve::eval`: a pcurve's point;
- `geometry::check`, `placement::moved` and `split_edges` become functions on the record.

The kernel's native module keeps the conversion between a body and its saved record
(metadata.rs) and checks the decoded record's geometry. `native/analytic.rs`, which no module
declared, is deleted.

solidcore-record is pinned to its rev on solidcore's `layer-record` branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT

* Geometry: Vec2, Vec3, Plane and the profile types come from solidcore-record

The plain data of vectors and points, sketch planes and planar profiles (`Seg2`, `Loop2`,
`Region2`) moves to `solidcore-record`, with its constants and component-wise operators only
(owner decision, solidcore layer split). Their geometry stays in geom as traits:
`Vec2Geom`, `Vec3Geom`, `PlaneGeom`, `Seg2Geom`, `Loop2Geom`, `Region2Geom`, `Seg2ArcFit`,
`Loop2ArcFit` and `Region2ArcFit`, in scope with `use solidcraft_geom::prelude::*`. Every caller
names the types from `solidcore_record`; geom no longer defines or re-exports them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT

* Kernel: pin solidcore-record to solidcore main f5e8e9a29

solidcore-record is merged on solidcore main (tangency-labs/solidcore#2519); its crate there is
identical to the rev pinned before but for its clippy configuration. The lock is regenerated
against the merged rev.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:04:26 -05:00

63 lines
3.6 KiB
Rust

//! The bracket example must cut two through holes, rather than successfully cutting empty
//! space beside the body. Check the shipped script and rebuild its saved design from scratch.
use std::f64::consts::PI;
use serde_json::Value;
use solidcore_record::Vec3;
use solidcraft_engine::{Session, io, kernel};
fn assert_two_through_holes(session: &Session, stage: &str) {
let errors: Vec<_> = session.model.results.iter().filter_map(|r| r.error.as_ref().map(|e| (&r.name, e))).collect();
assert!(errors.is_empty(), "{stage}: every example feature evaluates: {errors:?}");
let cut = session.doc.features.last().expect("the example ends with the hole cut");
let before = session.model.state_before(cut.id);
let after = session.model.state();
assert_eq!(before.bodies.len(), 1, "{stage}: one bracket before cutting the holes");
assert_eq!(after.bodies.len(), 1, "{stage}: the holes leave one bracket");
let uncut = &before.body("Bracket").expect("the uncut bracket").body;
let bracket = &after.body("Bracket").expect("the finished bracket").body;
// Measure the actual state before the final cut so the existing edge fillet cancels out.
// Both holes have diameter 8 mm and pass through the 6 mm base. Kernel measurements are
// tessellated, so the relative tolerance applies to the removed material, not the body.
let removed = kernel::measure(uncut).unwrap().volume - kernel::measure(bracket).unwrap().volume;
let expected = 2.0 * PI * 4.0_f64.powi(2) * 6.0;
assert!((removed - expected).abs() < expected * 1e-3, "{stage}: the two holes remove {removed} mm³, expected {expected} mm³");
let before_mesh = uncut.mesh(&solidcraft_kernel::MeshTolerance::chord(0.01)).unwrap();
let after_mesh = bracket.mesh(&solidcraft_kernel::MeshTolerance::chord(0.01)).unwrap();
let down = Vec3::new(0.0, 0.0, -1.0);
for y in [-15.0, -45.0] {
let center = Vec3::new(25.0, y, 1.0);
assert_eq!(before_mesh.ray_crossings(center, down), 2, "{stage}: hole at y={y} starts over the solid base");
// Rays through each opening must miss the entire bracket, including its top and
// bottom caps. Points away from the centre guard against a narrow slit or shifted cut.
for (dx, dy) in [(0.0, 0.0), (3.0, 0.0), (-3.0, 0.0), (0.0, 3.0), (0.0, -3.0)] {
let ray = center + Vec3::new(dx, dy, 0.0);
assert_eq!(after_mesh.ray_crossings(ray, down), 0, "{stage}: the hole at y={y} is open through both caps at ({dx}, {dy})");
}
// Material just outside each radius remains, so neither opening is an edge notch.
for (dx, dy) in [(5.0, 0.0), (-5.0, 0.0), (0.0, 5.0), (0.0, -5.0)] {
let ray = center + Vec3::new(dx, dy, 0.0);
assert_eq!(after_mesh.ray_crossings(ray, down), 2, "{stage}: material remains around the hole at y={y}, offset ({dx}, {dy})");
}
}
}
#[test]
fn bracket_example_cuts_two_through_holes_and_reopens() {
let script: Value = serde_json::from_str(include_str!("../../../examples/bracket.json")).unwrap();
let mut session = Session::default();
session.run_script(&script).unwrap();
assert_two_through_holes(&session, "script");
// No geometry cache is supplied: reopening the container re-evaluates the saved timeline.
let saved = io::write_design_container(&session.doc).unwrap();
let document = io::read_design(&saved).unwrap();
assert_eq!(document.features, session.doc.features, "saving preserves the example's feature parameters");
let reopened = Session::new(document);
assert_two_through_holes(&reopened, "reopened");
}