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

163 lines
7.9 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Solve times of an n×n grid (origin corner fixed, every row and column edge horizontal or
//! vertical with a distance), started at the solution and ±1 mm off it, and of one held drag
//! frame on a strip of rectangles. Run with
//! `cargo run --release -p solidcraft-sketch --example solve_bench`; results go in docs/perf.md.
use std::time::Instant;
use solidcore_record::Vec2;
use solidcraft_sketch::{
Analysis, Budget, ConstraintKind, Hold, PatternAlignment, Sketch, SketchPattern, SketchPatternKind, SolveReport, solve, solve_with,
};
/// A small deterministic generator for the start offsets.
struct Lcg(u64);
impl Lcg {
/// In [-1, 1).
fn next(&mut self) -> f64 {
self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
((self.0 >> 11) as f64 / (1u64 << 53) as f64) * 2.0 - 1.0
}
}
fn grid(n: usize, jitter: f64) -> Result<Sketch, Box<dyn std::error::Error>> {
let mut sk = Sketch::new();
let mut rnd = Lcg(12345);
let mut id = vec![vec![0; n]; n];
for (r, row) in id.iter_mut().enumerate() {
for (c, slot) in row.iter_mut().enumerate() {
let j = if r == 0 && c == 0 { Vec2::ZERO } else { Vec2::new(rnd.next(), rnd.next()) * jitter };
*slot = sk.add_point(Vec2::new(c as f64 * 10.0, r as f64 * 10.0) + j, None)?;
}
}
sk.add_constraint(ConstraintKind::Fix { p: id[0][0] }, None)?;
for r in 0..n {
for c in 0..n {
for (dr, dc, horizontal) in [(0, 1, true), (1, 0, false)] {
let (Some(&p), Some(&q)) = (id.get(r).and_then(|x| x.get(c)), id.get(r + dr).and_then(|x| x.get(c + dc))) else { continue };
let l = sk.add_line_pts(p, q, None)?;
sk.add_constraint(if horizontal { ConstraintKind::Horizontal { l } } else { ConstraintKind::Vertical { l } }, None)?;
sk.add_constraint(ConstraintKind::Distance { p, q, value: 10.0 }, None)?;
}
}
}
Ok(sk)
}
/// `n` rectangles side by side sharing their vertical edges: horizontal and vertical sides, every
/// width dimensioned, the first height too. Returns the sketch and its first corner.
fn strip(n: usize) -> Result<(Sketch, usize), Box<dyn std::error::Error>> {
let mut sk = Sketch::new();
let bottom: Vec<usize> = (0..=n).map(|i| sk.add_point(Vec2::new(10.0 + i as f64 * 20.0, 10.0), None)).collect::<Result<_, _>>()?;
let top: Vec<usize> = (0..=n).map(|i| sk.add_point(Vec2::new(10.0 + i as f64 * 20.0, 25.0), None)).collect::<Result<_, _>>()?;
for i in 0..=n {
let l = sk.add_line_pts(bottom[i], top[i], None)?;
sk.add_constraint(ConstraintKind::Vertical { l }, None)?;
if i == 0 {
sk.add_constraint(ConstraintKind::Length { l, value: 15.0 }, None)?;
}
}
for i in 0..n {
let b = sk.add_line_pts(bottom[i], bottom[i + 1], None)?;
let t = sk.add_line_pts(top[i], top[i + 1], None)?;
sk.add_constraint(ConstraintKind::Horizontal { l: b }, None)?;
sk.add_constraint(ConstraintKind::Horizontal { l: t }, None)?;
sk.add_constraint(ConstraintKind::Length { l: b, value: 20.0 }, None)?;
}
Ok((sk, bottom[0]))
}
/// A slot (two arcs joined by two lines) copied `count` times around the origin by a circular
/// pattern. Returns the sketch and the seed's first arc start point.
fn slot_pattern(count: usize) -> Result<(Sketch, usize), Box<dyn std::error::Error>> {
let mut sk = Sketch::new();
let center = sk.add_point(Vec2::ZERO, None)?;
sk.add_constraint(ConstraintKind::Fix { p: center }, None)?;
let (c0, c1) = (Vec2::new(200.0, 0.0), Vec2::new(210.0, 0.0));
let p = [c0 + Vec2::new(0.0, -2.0), c0 + Vec2::new(0.0, 2.0), c1 + Vec2::new(0.0, -2.0), c1 + Vec2::new(0.0, 2.0)];
let pts: Vec<usize> = p.iter().map(|q| sk.add_point(*q, None)).collect::<Result<_, _>>()?;
let left = sk.add_arc(c0, p[1], p[0], [None, Some(pts[1]), Some(pts[0])], None)?;
let right = sk.add_arc(c1, p[2], p[3], [None, Some(pts[2]), Some(pts[3])], None)?;
let bottom = sk.add_line_pts(pts[0], pts[2], None)?;
let top = sk.add_line_pts(pts[1], pts[3], None)?;
let entities = [left, right, bottom, top].iter().filter_map(|i| sk.curves.get(*i).map(|c| c.id.clone())).collect();
let center_id = sk.points.get(center).map(|q| q.id.clone()).ok_or("missing center")?;
let kind = SketchPatternKind::Circular { center: center_id, count, angle: 360.0, alignment: PatternAlignment::Right, reversed: false };
let id = sk.fresh("pat")?;
sk.patterns.push(SketchPattern { id, entities, kind: kind.clone(), count_expression: None, instances: vec![], expressions: Default::default() });
sk.update_pattern(0, kind)?;
Ok((sk, pts[1]))
}
/// A hash of the whole sketch, every number to the bit, to compare solver output between builds.
fn fingerprint(sk: &Sketch) -> u64 {
format!("{sk:?}").bytes().fold(0xcbf29ce484222325, |h, b| (h ^ u64::from(b)).wrapping_mul(0x100000001b3))
}
/// Best of `reps` solves of a copy of `sk` (ms) and the last report.
fn time(sk: &Sketch, reps: usize, f: impl Fn(&mut Sketch) -> SolveReport) -> (f64, SolveReport) {
let mut best = f64::MAX;
let mut rep = None;
for _ in 0..reps {
let mut k = sk.clone();
let t = Instant::now();
let r = f(&mut k);
best = best.min(t.elapsed().as_secs_f64() * 1e3);
rep = Some(r);
}
(best, rep.unwrap_or_else(|| solve(&mut sk.clone())))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("| grid | points | start | ms | status | dof | iterations |");
println!("|---|---|---|---|---|---|---|");
for n in [10usize, 15, 20] {
for jitter in [0.0, 1.0] {
let sk = grid(n, jitter)?;
let (ms, rep) = time(&sk, 5, solve);
let start = if jitter == 0.0 { "at solution" } else { "±1 mm" };
println!("| {n}×{n} | {} | {start} | {ms:.2} | {:?} | {} | {} |", n * n, rep.status, rep.dof, rep.iterations);
}
}
println!();
println!("| strip | points | held drag frame ms | full solve ms |");
println!("|---|---|---|---|");
for n in [30usize, 100] {
let (mut sk, corner) = strip(n)?;
if let Some(p) = sk.points.get_mut(corner) {
p.pos -= Vec2::new(0.5, 0.0);
}
let hold = Hold { points: vec![corner], radii: vec![] };
let (frame, rep) = time(&sk, 5, |k| solve_with(k, &hold, None, Analysis::StatusOnly, Some(&mut Budget::frame())));
let (full, _) = time(&sk, 5, solve);
println!("| {n} rectangles | {} | {frame:.2} ({:?}) | {full:.2} |", sk.points.len(), rep.status);
}
println!();
println!("| circular slot pattern | curves | points | drag frame on another line ms | drag frame on the seed ms | output |");
println!("|---|---|---|---|---|---|");
for count in [100usize, 500] {
let (mut sk, seed) = slot_pattern(count)?;
let a = sk.add_point(Vec2::new(-500.0, -500.0), None)?;
let b = sk.add_point(Vec2::new(-480.0, -500.0), None)?;
let l = sk.add_line_pts(a, b, None)?;
sk.add_constraint(ConstraintKind::Horizontal { l }, None)?;
let drag = |point: usize| -> (f64, u64) {
let mut start = sk.clone();
if let Some(p) = start.points.get_mut(point) {
p.pos += Vec2::new(0.5, 0.3);
}
let hold = Hold { points: vec![point], radii: vec![] };
let frame = |k: &mut Sketch| solve_with(k, &hold, None, Analysis::StatusOnly, Some(&mut Budget::frame()));
let (ms, _) = time(&start, 5, frame);
frame(&mut start);
(ms, fingerprint(&start))
};
let (other, other_out) = drag(b);
// Solving the whole pattern takes seconds at 500 copies.
let (on_seed, seed_out) = if count <= 100 { drag(seed) } else { (f64::NAN, 0) };
println!("| {count} slots | {} | {} | {other:.2} | {on_seed:.2} | {other_out:016x} {seed_out:016x} |", sk.curves.len(), sk.points.len());
}
Ok(())
}