mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 12:01:10 +00:00
dc38349cbf
* 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>
63 lines
3.6 KiB
Rust
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");
|
|
}
|