Rename SolveCraft to SolidCraft everywhere, with compatibility shims (#389)

* WIP deep rename (not for push)

* WIP2

* WIP3

* WIP4 wxs fix

* rustfmt

* Rename the remaining identifiers in merged crash-report code

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-12 17:17:01 -04:00
1 parent 27415e4dd7
commit 296683229d
464 files changed
+3408 -2956

No files matched your search

+6 -6
View File
@@ -9,7 +9,7 @@
use std::collections::HashMap;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Body, Solid, from_p3, p3, v3};
@@ -1177,7 +1177,7 @@ fn chamfer_tool_sides(cur: &Body, p: Vec3, s: f64, sides: Option<(ChamferSide, b
let ext = (s * 2.0).max(size * 0.01) * (1.0 + 0.093 * (k % 5) as f64);
// Cross-section at p0 - d*ext, in a plane with normal d.
let base = f.p0 - f.d * ext;
let plane = solvecraft_geom::Plane::from_normal(base, f.d).ok_or_else(|| KernelError::Failed("chamfer plane".into()))?;
let plane = solidcraft_geom::Plane::from_normal(base, f.d).ok_or_else(|| KernelError::Failed("chamfer plane".into()))?;
let a = base + f.t1 * s1;
let b = base + f.t2 * s2;
let ab = (a - b).normalized().ok_or_else(|| KernelError::Failed("chamfer".into()))?;
@@ -1194,15 +1194,15 @@ fn chamfer_tool_sides(cur: &Body, p: Vec3, s: f64, sides: Option<(ChamferSide, b
return Err(unsupported("chamfers ending at a concave corner"));
}
}
let loc: Vec<solvecraft_geom::Vec2> = [a_ext, b_ext, e_far].iter().map(|q| plane.to_local(*q)).collect();
let mut lp = solvecraft_geom::Loop2::polygon(&loc);
let loc: Vec<solidcraft_geom::Vec2> = [a_ext, b_ext, e_far].iter().map(|q| plane.to_local(*q)).collect();
let mut lp = solidcraft_geom::Loop2::polygon(&loc);
// The chamfer plane is swept from the segment a → b: first, or last once turned round.
let mut chamfer_segment = 0;
if lp.signed_area() < 0.0 {
lp = lp.reversed();
chamfer_segment = lp.segs.len().saturating_sub(1);
}
let region = solvecraft_geom::Region2 { outer: lp, holes: vec![] };
let region = solidcraft_geom::Region2 { outer: lp, holes: vec![] };
let body = crate::build::extrude(&plane, &[region], 0.0, len + 2.0 * ext)?.pop().ok_or_else(|| KernelError::Failed("chamfer tool".into()))?;
let face = body.history().and_then(|h| h.faces.iter().position(|f| f.origins == [FaceOrigin::Profile { segment: chamfer_segment }]));
Ok(Cutter { body, face, edge })
@@ -1211,7 +1211,7 @@ fn chamfer_tool_sides(cur: &Body, p: Vec3, s: f64, sides: Option<(ChamferSide, b
#[cfg(test)]
mod tests {
use super::*;
use solvecraft_geom::{Loop2, Plane, Region2, Vec2};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
#[test]
fn chamfer_extension_cannot_cross_a_hole_and_reenter_the_face() {
+5 -5
View File
@@ -17,7 +17,7 @@
use std::collections::{HashMap, HashSet};
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::blend::{plane_normal, unsupported, vtx};
@@ -1142,7 +1142,7 @@ mod tests {
#[test]
fn sloped_basin_rim_fillets() {
use solvecraft_geom::{Loop2, Plane, Vec2};
use solidcraft_geom::{Loop2, Plane, Vec2};
let rectangle = |x0, y0, x1, y1| Loop2::polygon(&[Vec2::new(x0, y0), Vec2::new(x1, y0), Vec2::new(x1, y1), Vec2::new(x0, y1)]);
let slab = box_solid(Vec3::ZERO, Vec3::new(100.0, 60.0, 30.0)).unwrap();
let pocket =
@@ -1170,7 +1170,7 @@ mod tests {
#[test]
fn sloped_rim_fillets_cannot_extend_outside_the_material() {
use solvecraft_geom::{Loop2, Plane, Vec2};
use solidcraft_geom::{Loop2, Plane, Vec2};
for margin in [0.5, 1.0] {
let rectangle = |x0, y0, x1, y1| Loop2::polygon(&[Vec2::new(x0, y0), Vec2::new(x1, y0), Vec2::new(x1, y1), Vec2::new(x0, y1)]);
let slab = box_solid(Vec3::ZERO, Vec3::new(100.0, 60.0, 30.0)).unwrap();
@@ -1190,7 +1190,7 @@ mod tests {
#[test]
fn chamfers_meet_on_unequal_sloped_rim_walls() {
use solvecraft_geom::{Loop2, Plane, Vec2};
use solidcraft_geom::{Loop2, Plane, Vec2};
for margin in [1.0, 4.0] {
let rectangle = |x0, y0, x1, y1| Loop2::polygon(&[Vec2::new(x0, y0), Vec2::new(x1, y0), Vec2::new(x1, y1), Vec2::new(x0, y1)]);
let slab = box_solid(Vec3::ZERO, Vec3::new(100.0, 60.0, 30.0)).unwrap();
@@ -1229,7 +1229,7 @@ mod tests {
#[test]
fn equal_slope_rim_loops_cannot_escape_the_face() {
use solvecraft_geom::{Loop2, Plane, Vec2};
use solidcraft_geom::{Loop2, Plane, Vec2};
let rectangle = |x0, y0, x1, y1| Loop2::polygon(&[Vec2::new(x0, y0), Vec2::new(x1, y0), Vec2::new(x1, y1), Vec2::new(x0, y1)]);
let slab = box_solid(Vec3::ZERO, Vec3::new(100.0, 60.0, 30.0)).unwrap();
let pocket =
+1 -1
View File
@@ -9,7 +9,7 @@
use std::collections::HashMap;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::blend::{plane_normal, rebuild_face, unsupported, vtx};
+13 -13
View File
@@ -1,5 +1,5 @@
use serde::Serialize;
use solvecraft_geom::{Mesh, Vec3};
use solidcraft_geom::{Mesh, Vec3};
use truck_meshalgo::prelude::*;
use truck_modeling as mt;
@@ -42,12 +42,12 @@ pub struct Body {
#[derive(Clone, Debug, PartialEq)]
pub enum FaceSource {
ExtrudeSide { segment: solvecraft_geom::Seg2, plane: Option<solvecraft_geom::Plane>, coalesced: bool },
CertifiedExtrudeSide { producer: u64, sketch: u64, curve: String, plane: Option<solvecraft_geom::Plane> },
ExtrudeCap { high: bool, region: solvecraft_geom::Region2, plane: solvecraft_geom::Plane },
CertifiedExtrudeCap { producer: u64, sketch: u64, high: bool, outer: Vec<String>, holes: Vec<Vec<String>>, plane: solvecraft_geom::Plane },
Box { producer: Option<u64>, axis: u8, high: bool, plane: solvecraft_geom::Plane },
ShellPlanar { producer: Option<u64>, source: Box<FaceSource>, inner: bool, plane: solvecraft_geom::Plane },
ExtrudeSide { segment: solidcraft_geom::Seg2, plane: Option<solidcraft_geom::Plane>, coalesced: bool },
CertifiedExtrudeSide { producer: u64, sketch: u64, curve: String, plane: Option<solidcraft_geom::Plane> },
ExtrudeCap { high: bool, region: solidcraft_geom::Region2, plane: solidcraft_geom::Plane },
CertifiedExtrudeCap { producer: u64, sketch: u64, high: bool, outer: Vec<String>, holes: Vec<Vec<String>>, plane: solidcraft_geom::Plane },
Box { producer: Option<u64>, axis: u8, high: bool, plane: solidcraft_geom::Plane },
ShellPlanar { producer: Option<u64>, source: Box<FaceSource>, inner: bool, plane: solidcraft_geom::Plane },
Imported { item: u64, face: u64, payload_sha256: Option<String>, producer: Option<u64> },
}
@@ -55,7 +55,7 @@ pub type CapSignature = (Vec<String>, Vec<Vec<String>>);
type FaceSources = std::sync::Arc<Vec<Option<FaceSource>>>;
impl FaceSource {
fn plane_mut(&mut self) -> Option<&mut solvecraft_geom::Plane> {
fn plane_mut(&mut self) -> Option<&mut solidcraft_geom::Plane> {
match self {
Self::ExtrudeSide { plane, .. } | Self::CertifiedExtrudeSide { plane, .. } => plane.as_mut(),
Self::ExtrudeCap { plane, .. } | Self::CertifiedExtrudeCap { plane, .. } | Self::Box { plane, .. } | Self::ShellPlanar { plane, .. } => {
@@ -494,7 +494,7 @@ impl Body {
}
/// An exact construction/import plane; never inferred from tessellation.
pub fn face_plane(&self, index: usize) -> Option<solvecraft_geom::Plane> {
pub fn face_plane(&self, index: usize) -> Option<solidcraft_geom::Plane> {
if index >= 50_000 {
return None;
}
@@ -510,7 +510,7 @@ impl Body {
}
/// Exact current planar surface for an explicitly fixed snapshot, without source lineage.
pub fn planar_face(&self, index: usize) -> Option<solvecraft_geom::Plane> {
pub fn planar_face(&self, index: usize) -> Option<solidcraft_geom::Plane> {
if index >= 50_000 {
return None;
}
@@ -519,7 +519,7 @@ impl Body {
mt::Surface::Plane(plane) => {
let u = plane.u_axis();
let v = plane.v_axis();
solvecraft_geom::Plane::new(from_p3(plane.origin()), Vec3::new(u.x, u.y, u.z), Vec3::new(v.x, v.y, v.z))
solidcraft_geom::Plane::new(from_p3(plane.origin()), Vec3::new(u.x, u.y, u.z), Vec3::new(v.x, v.y, v.z))
}
_ => None,
});
@@ -610,7 +610,7 @@ impl Body {
let mut sources = sources.as_ref().clone();
for source in sources.iter_mut().flatten() {
if let Some(plane) = source.plane_mut() {
*plane = solvecraft_geom::Plane::new(
*plane = solidcraft_geom::Plane::new(
vector(plane.origin) + translation,
vector(plane.x),
vector(plane.y) * if reflected { -1. } else { 1. },
@@ -741,7 +741,7 @@ impl Body {
/// Characteristic size (bounding box diagonal) for tolerances.
pub fn size(&self) -> f64 {
let mut b = solvecraft_geom::Aabb3::EMPTY;
let mut b = solidcraft_geom::Aabb3::EMPTY;
if let Some(m) = &self.mesh {
for p in &m.positions {
b.add(*p);
+1 -1
View File
@@ -224,7 +224,7 @@ fn shortcut_clearance_refuses_curved_contact_hidden_between_mesh_vertices() {
}
fn certified_lump_witness() -> Body {
use solvecraft_geom::{Loop2, Plane, Region2, Vec2};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
// The current trimmed lower prism: a rectangular tool with a circular stop
// removed. Its cap footprint differs materially from the original sketch.
let region = Region2 {
+1 -1
View File
@@ -158,7 +158,7 @@ mod tests {
#[test]
fn exact_operations_and_failed_calls_have_no_retry_report() {
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
let body = crate::box_solid(Vec3::ZERO, Vec3::new(10., 10., 10.)).unwrap();
let other = crate::box_solid(Vec3::new(5., 0., 0.), Vec3::new(15., 10., 10.)).unwrap();
let (result, diagnostics) = capture_boolean_diagnostics(|| crate::boolean(&body, &other, crate::BoolOp::Union));
+7 -7
View File
@@ -4,7 +4,7 @@
use std::collections::BTreeSet;
use solvecraft_geom::{Mesh, Vec3};
use solidcraft_geom::{Mesh, Vec3};
use crate::body::Body;
use crate::history::{Change, EdgeOrigin, FaceOrigin, History, Side};
@@ -427,7 +427,7 @@ fn faceted_tool() {
/// sewn as one face (coalesced), which comes from the operand walls under it in every slab.
#[test]
fn coalesced_stepped_walls() {
use solvecraft_geom::{Loop2, Plane, Region2, Vec2};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
for curved in [true, false] {
let (outer, inner) = if curved {
(Loop2::circle(Vec2::ZERO, 10.0), Loop2::circle(Vec2::ZERO, 5.0))
@@ -475,12 +475,12 @@ fn regularized_step_cut() {
fn bore_union_of_a_bore_in_two_arcs_has_no_history() {
let hole = crate::cylinder(Vec3::new(50.0, 40.0, -5.0), Vec3::Z, 10.0, 30.0).unwrap();
let floor = boolean(&boxed(0.0, 0.0, 0.0, 100.0, 80.0, 10.0), &hole, BoolOp::Cut).unwrap().unwrap();
let centre = solvecraft_geom::Vec2::new(50.0, 40.0);
let annulus = solvecraft_geom::Region2 {
outer: solvecraft_geom::Loop2::circle(centre, 20.0),
holes: vec![solvecraft_geom::Loop2::circle(centre, 10.0).reversed()],
let centre = solidcraft_geom::Vec2::new(50.0, 40.0);
let annulus = solidcraft_geom::Region2 {
outer: solidcraft_geom::Loop2::circle(centre, 20.0),
holes: vec![solidcraft_geom::Loop2::circle(centre, 10.0).reversed()],
};
let ring = crate::extrude(&solvecraft_geom::Plane::XY, &[annulus], -5.0, 2.0).unwrap().remove(0);
let ring = crate::extrude(&solidcraft_geom::Plane::XY, &[annulus], -5.0, 2.0).unwrap().remove(0);
let r = crate::coplanar::bore_union(&floor, &ring, &|_| true).expect("the bores are recut");
assert!(r.history().is_none());
}
+1 -1
View File
@@ -7,7 +7,7 @@
use std::collections::HashMap;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling as mt;
use crate::Result;
+1 -1
View File
@@ -3,7 +3,7 @@
//! cylinder recut from silently removing an additional material boundary that
//! the fallback's coarse material samples do not see.
use crate::body::{Body, from_p3};
use solvecraft_geom::{Plane, Seg2, Vec3};
use solidcraft_geom::{Plane, Seg2, Vec3};
use std::collections::HashSet;
use truck_modeling as mt;
+2 -2
View File
@@ -1,6 +1,6 @@
#![allow(clippy::unwrap_used, clippy::panic)]
use crate::*;
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
fn fixture() -> (Body, Body, Vec3, f64) {
let slope = 1f64.to_radians().tan();
@@ -62,7 +62,7 @@ fn thin_connected_web_between_bore_stations_survives_union() {
let direct = crate::coplanar::bore_union(&target, &ring, &accept);
eprintln!("direct fallback result={}", direct.is_some());
let public = boolean(&target, &ring, BoolOp::Union);
if let Ok(dir) = std::env::var("SOLVECRAFT_BORE_AUDIT_DIR") {
if let Ok(dir) = solidcraft_geom::env::var("BORE_AUDIT_DIR").ok_or(()) {
let dir = std::path::Path::new(&dir);
std::fs::create_dir_all(dir).unwrap();
std::fs::write(dir.join("target-with-web.step"), step_export(&[&target], "target with web").unwrap()).unwrap();
+2 -2
View File
@@ -1,7 +1,7 @@
//! Building bodies: extrude and revolve planar regions, primitives.
use mt::builder;
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use truck_modeling as mt;
use crate::body::{Body, Solid, from_p3, p3, v3};
@@ -136,7 +136,7 @@ fn region_ok(r: &Region2) -> Result<()> {
}
Seg2::Cubic { p0, p1, p2, p3 } => p0.is_finite() && p1.is_finite() && p2.is_finite() && p3.is_finite(),
Seg2::Conic { a, apex, b, w } => a.is_finite() && apex.is_finite() && b.is_finite() && w.is_finite() && w > 1e-9,
Seg2::Bezier { n, p } => (4..=solvecraft_geom::MAX_BEZIER_DEGREE as u8).contains(&n) && p.iter().all(|q| q.is_finite()),
Seg2::Bezier { n, p } => (4..=solidcraft_geom::MAX_BEZIER_DEGREE as u8).contains(&n) && p.iter().all(|q| q.is_finite()),
};
if !ok {
return Err(KernelError::Invalid("non-finite profile".into()));
+8 -8
View File
@@ -13,7 +13,7 @@
use std::collections::HashMap;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Body, Solid, from_p3, p3, v3};
@@ -232,14 +232,14 @@ fn slab_beyond(n: Vec3, d: f64, depth: f64, extent: f64) -> Result<Body> {
let u = n.any_perp();
let w = n.cross(u);
let o = n * d;
let pl = solvecraft_geom::Plane::new(o, u, w).ok_or_else(|| KernelError::Failed("slab plane".into()))?;
let sq = solvecraft_geom::Loop2::polygon(&[
solvecraft_geom::Vec2::new(-extent, -extent),
solvecraft_geom::Vec2::new(extent, -extent),
solvecraft_geom::Vec2::new(extent, extent),
solvecraft_geom::Vec2::new(-extent, extent),
let pl = solidcraft_geom::Plane::new(o, u, w).ok_or_else(|| KernelError::Failed("slab plane".into()))?;
let sq = solidcraft_geom::Loop2::polygon(&[
solidcraft_geom::Vec2::new(-extent, -extent),
solidcraft_geom::Vec2::new(extent, -extent),
solidcraft_geom::Vec2::new(extent, extent),
solidcraft_geom::Vec2::new(-extent, extent),
]);
let r = solvecraft_geom::Region2 { outer: sq, holes: vec![] };
let r = solidcraft_geom::Region2 { outer: sq, holes: vec![] };
crate::extrude(&pl, &[r], 0.0, depth)?.pop().ok_or_else(|| KernelError::Failed("slab".into()))
}
+1 -1
View File
@@ -8,7 +8,7 @@
use std::collections::HashMap;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Solid, from_p3, p3};
+2 -2
View File
@@ -11,7 +11,7 @@
use std::collections::{BTreeSet, HashMap};
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Body, Solid, from_p3, p3};
@@ -301,7 +301,7 @@ pub fn delete_faces(body: &Body, faces: &[usize]) -> Result<Body> {
#[cfg(test)]
mod tests {
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use super::delete_faces;
use crate::{BoolOp, boolean, box_solid, chamfer, cylinder, fillet};
+1 -1
View File
@@ -5,7 +5,7 @@ use crate::numbered::NumberedRegion;
use crate::offset::{Surf, surf_of};
use crate::record::Operand;
use crate::{KernelError, Result, guard};
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use truck_modeling::{self as mt, Transformed, builder};
fn fail(s: &str) -> KernelError {
+1 -1
View File
@@ -1,5 +1,5 @@
use crate::{FaceSource, extrude, transform_matrix};
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
fn assert_exact_planes(body: &crate::Body) {
let mesh = body.tessellate(0.001).unwrap();
+2 -2
View File
@@ -1,7 +1,7 @@
//! Free-form profile edges: Béziers (cubic and higher) as B-spline curves and conics as rational quadratic
//! NURBS curves (exact ellipses, parabolas and hyperbolas from sketches).
use solvecraft_geom::{Plane, Seg2};
use solidcraft_geom::{Plane, Seg2};
use truck_modeling as mt;
use crate::body::p3;
@@ -21,7 +21,7 @@ pub(crate) fn edge(plane: &Plane, s: &Seg2, a: &mt::Vertex, b: &mt::Vertex) -> O
Some(mt::Edge::new(a, b, mt::Curve::NurbsCurve(curve)))
}
Seg2::Bezier { n, ref p } => {
let inner = p.get(1..(n as usize).clamp(1, solvecraft_geom::MAX_BEZIER_DEGREE))?;
let inner = p.get(1..(n as usize).clamp(1, solidcraft_geom::MAX_BEZIER_DEGREE))?;
Some(mt::builder::bezier(a, b, inner.iter().map(|q| p3(plane.to_world(*q))).collect()))
}
Seg2::Line { .. } | Seg2::Arc { .. } => None,
+2 -2
View File
@@ -1,9 +1,9 @@
//! Robustness checks on random inputs (`solvecraft-cli fuzz`, docs/robustness.md): a small
//! Robustness checks on random inputs (`solidcraft-cli fuzz`, docs/robustness.md): a small
//! seeded random generator, and the boolean volume identity on random pairs of primitives
//! placed on a coarse grid, so coincident faces and tangencies come up often.
use serde::Serialize;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use crate::{Body, BoolOp, boolean, box_solid, cylinder, measure, sphere, torus};
+1 -1
View File
@@ -3,7 +3,7 @@
use std::collections::{BTreeSet, HashMap};
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Solid, from_p3};
+3 -3
View File
@@ -1,7 +1,7 @@
//! Helical sweeps (coils, springs) and lofts to a point (pyramids, cones).
use mt::builder;
use solvecraft_geom::{Loop2, Mesh, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Mesh, Plane, Region2, Seg2, Vec2, Vec3};
use truck_modeling as mt;
use crate::body::{Body, Solid, p3};
@@ -118,7 +118,7 @@ fn smooth_helix(plane: &Plane, profile: &NumberedLoop, o: Vec3, k: Vec3, pitch:
}
let n = ((turns * STATIONS_PER_TURN).ceil() as usize).clamp(4, 4800);
let total = std::f64::consts::TAU * turns;
let at = |i: usize, q: solvecraft_geom::Vec2| {
let at = |i: usize, q: solidcraft_geom::Vec2| {
let a = total * i as f64 / n as f64;
let w = plane.to_world(q);
o + rot(w - o, k, a) + k * (pitch * a / std::f64::consts::TAU)
@@ -251,7 +251,7 @@ pub fn loft_to_point(plane: &Plane, lp: &Loop2, apex: Vec3) -> Result<Body> {
#[cfg(test)]
mod tests {
use super::*;
use solvecraft_geom::Vec2;
use solidcraft_geom::Vec2;
#[test]
fn coil_volume_is_section_times_helix_length() {
+1 -1
View File
@@ -1,4 +1,4 @@
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use super::{Change, EdgeOrigin, EdgeProv, FaceOrigin, FaceProv, History, Side};
use crate::{Body, box_solid, transform};
+2 -2
View File
@@ -1,6 +1,6 @@
use super::*;
use crate::{Body, BoolOp, ExportBody, StepHeader, boolean, box_solid, cylinder, fillet, iges_export_bodies, measure, sphere, torus};
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
fn rel(a: f64, b: f64) -> f64 {
(a - b).abs() / b.abs().max(1e-9)
@@ -146,7 +146,7 @@ fn trimmed_surfaces_sew_into_a_solid() {
fn a_drill_point_round_trips_through_iges() {
// A blind hole with a cone tip: the tip's face closes at its apex (its seam runs out to the
// apex and straight back).
use solvecraft_geom::{Loop2, Plane, Region2, Vec2};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
let plate = box_solid(Vec3::new(-20.0, -15.0, 0.0), Vec3::new(20.0, 15.0, 10.0)).unwrap();
let profile =
Region2 { outer: Loop2::polygon(&[Vec2::new(0.0, 2.0), Vec2::new(4.0, 5.0), Vec2::new(4.0, 11.0), Vec2::new(0.0, 11.0)]), holes: vec![] };
+1 -1
View File
@@ -1,6 +1,6 @@
//! The B-rep kernel boundary.
//!
//! Everything SolveCraft does with solids goes through this crate: building bodies from planar
//! Everything SolidCraft does with solids goes through this crate: building bodies from planar
//! regions (extrude, revolve) and primitives, booleans, fillets and chamfers, tessellation,
//! measurement and STEP export and import. The implementation uses the `truck` crates (Apache-2.0; see
//! `plan/adr/0001-geometry-kernel.md`) plus our own local operations (edge fillets and
+1 -1
View File
@@ -3,7 +3,7 @@
//! directly from the body's faces (the band's walls continue the walls below, which a boolean
//! union can't join: their faces would coincide).
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Body, Solid, from_p3, p3, v3};
+1 -1
View File
@@ -7,7 +7,7 @@
use std::collections::HashMap;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Solid, from_p3, p3, v3};
+1 -1
View File
@@ -1,5 +1,5 @@
use serde::Serialize;
use solvecraft_geom::{Aabb3, Mesh, Vec3};
use solidcraft_geom::{Aabb3, Mesh, Vec3};
use crate::Result;
use crate::body::Body;
+5 -5
View File
@@ -3,7 +3,7 @@
use std::collections::BTreeMap;
use solvecraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use crate::{Body, KernelError, Result};
@@ -72,7 +72,7 @@ fn weld(mesh: &crate::meshbody::TriMesh, tolerance: f64) -> Result<Body> {
if mesh.triangles.len() > MAX_TRIANGLES || mesh.positions.len() > MAX_TRIANGLES * 3 {
return Err(invalid("mesh exceeds the conversion budget"));
}
let bounds = mesh.positions.iter().fold(solvecraft_geom::Aabb3::EMPTY, |mut b, p| {
let bounds = mesh.positions.iter().fold(solidcraft_geom::Aabb3::EMPTY, |mut b, p| {
b.add(*p);
b
});
@@ -131,7 +131,7 @@ fn weld(mesh: &crate::meshbody::TriMesh, tolerance: f64) -> Result<Body> {
fn extrusion(mesh: &crate::meshbody::TriMesh, axis: Vec3, tolerance: f64) -> Result<Option<Body>> {
let (lo, hi) = mesh.positions.iter().map(|p| p.dot(axis)).fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), h| (lo.min(h), hi.max(h)));
let height = hi - lo;
let bounds = mesh.positions.iter().fold(solvecraft_geom::Aabb3::EMPTY, |mut b, p| {
let bounds = mesh.positions.iter().fold(solidcraft_geom::Aabb3::EMPTY, |mut b, p| {
b.add(*p);
b
});
@@ -226,7 +226,7 @@ fn extrusion(mesh: &crate::meshbody::TriMesh, axis: Vec3, tolerance: f64) -> Res
}
let p = plane.to_local(*p);
if !boundary_segments.iter().any(|&(a, b)| {
solvecraft_geom::Seg2::Line { a, b }.point_at(solvecraft_geom::Seg2::Line { a, b }.closest_param(p)).dist(p) <= epsilon * 3.
solidcraft_geom::Seg2::Line { a, b }.point_at(solidcraft_geom::Seg2::Line { a, b }.closest_param(p)).dist(p) <= epsilon * 3.
}) {
return Ok(None);
}
@@ -377,7 +377,7 @@ fn fit_loop(points: &[Vec2], tolerance: f64, epsilon: f64) -> Loop2 {
}
let remove = (0..n).find(|&i| {
let (Some(a), Some(b), Some(c)) = (points.get((i + n - 1) % n), points.get(i), points.get((i + 1) % n)) else { return false };
let edge = solvecraft_geom::Seg2::Line { a: *a, b: *c };
let edge = solidcraft_geom::Seg2::Line { a: *a, b: *c };
edge.point_at(edge.closest_param(*b)).dist(*b) <= linear_tolerance
});
let Some(index) = remove else { break };
+1 -1
View File
@@ -4,7 +4,7 @@
use std::collections::HashMap;
use std::sync::Arc;
use solvecraft_geom::{Mesh, Vec3};
use solidcraft_geom::{Mesh, Vec3};
use crate::body::Body;
use crate::{KernelError, Result};
+1 -1
View File
@@ -2,7 +2,7 @@
//! face and edge origin checked against the geometry of the operands (in tests only: the history
//! itself is recorded, never sampled), and the exact origins asserted where the outcome is known.
use solvecraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use crate::body::Body;
use crate::boolean_record::tests::{self, Reach, changes, to_edge, to_face};
+1 -1
View File
@@ -6,7 +6,7 @@
use std::collections::BTreeSet;
use solvecraft_geom::{Loop2, Region2, Seg2};
use solidcraft_geom::{Loop2, Region2, Seg2};
/// The loops of `regions` in profile order: each region's outer loop, then its holes.
pub(crate) fn profile_loops(regions: &[Region2]) -> impl Iterator<Item = &Loop2> {
+2 -2
View File
@@ -9,7 +9,7 @@
use std::collections::HashMap;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling::{self as mt, builder};
use crate::body::{Body, Solid, from_p3, p3};
@@ -1056,7 +1056,7 @@ pub(crate) fn offset_body(b: &Body, shift: impl Fn(usize, Vec3) -> f64) -> Resul
/// outward normal by −h·tan(angle), so planes tilt and cylinders become cones. The other
/// faces must be caps square to `pull`; the topology is kept, and the result's history is over
/// `[b]` (each face and edge the one it was moved from).
pub(crate) fn draft_walls(b: &Body, chosen: &[usize], neutral: &solvecraft_geom::Plane, pull: Vec3, angle: f64) -> Result<Body> {
pub(crate) fn draft_walls(b: &Body, chosen: &[usize], neutral: &solidcraft_geom::Plane, pull: Vec3, angle: f64) -> Result<Body> {
let faces: Vec<mt::Face> = b.solid.face_iter().cloned().collect();
let size = b.size();
let tol = (size * 1e-7).max(1e-9);
+20 -20
View File
@@ -1,7 +1,7 @@
//! Operations on bodies: booleans and rigid transforms.
use serde::{Deserialize, Serialize};
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling as mt;
use std::collections::HashMap;
@@ -51,7 +51,7 @@ fn halton(mut i: usize, b: usize) -> f64 {
/// Fraction of sample points where `result` disagrees with `op` applied to `a` and `b`
/// (membership by ray parity). Samples cover the overlap box densely and the union box lightly.
fn mismatch(a: &solvecraft_geom::Mesh, b: &solvecraft_geom::Mesh, result: &solvecraft_geom::Mesh, op: BoolOp) -> Option<f64> {
fn mismatch(a: &solidcraft_geom::Mesh, b: &solidcraft_geom::Mesh, result: &solidcraft_geom::Mesh, op: BoolOp) -> Option<f64> {
if a.triangles.len() + b.triangles.len() + result.triangles.len() > 60_000 {
return None;
}
@@ -64,7 +64,7 @@ fn mismatch(a: &solvecraft_geom::Mesh, b: &solvecraft_geom::Mesh, result: &solve
// operands' boundaries, where ray parity cannot classify membership reliably.
let gap = all.diagonal().max(1.0) * 1e-9;
if hi.x - lo.x > gap && hi.y - lo.y > gap && hi.z - lo.z > gap {
boxes.push((solvecraft_geom::Aabb3 { min: lo, max: hi }, 900));
boxes.push((solidcraft_geom::Aabb3 { min: lo, max: hi }, 900));
}
let (ia_idx, ib_idx, ir_idx) = (a.inside_index(), b.inside_index(), result.inside_index());
let (mut n, mut bad) = (0usize, 0usize);
@@ -89,12 +89,12 @@ fn mismatch(a: &solvecraft_geom::Mesh, b: &solvecraft_geom::Mesh, result: &solve
/// How one body sits relative to another, from sample points: (fraction of `b`'s samples
/// inside `a`, fraction of `a`'s samples inside `b`).
fn overlap(a: &solvecraft_geom::Mesh, b: &solvecraft_geom::Mesh) -> Option<(f64, f64)> {
fn overlap(a: &solidcraft_geom::Mesh, b: &solidcraft_geom::Mesh) -> Option<(f64, f64)> {
if a.triangles.len() + b.triangles.len() > 80_000 {
return None;
}
let (ia, ib) = (a.inside_index(), b.inside_index());
let frac = |inner: &solvecraft_geom::Mesh, inner_i: &solvecraft_geom::InsideIndex, outer_i: &solvecraft_geom::InsideIndex| -> Option<f64> {
let frac = |inner: &solidcraft_geom::Mesh, inner_i: &solidcraft_geom::InsideIndex, outer_i: &solidcraft_geom::InsideIndex| -> Option<f64> {
let bx = inner.bounds();
let s = bx.size();
let (mut n, mut hit) = (0usize, 0usize);
@@ -120,8 +120,8 @@ fn trivial(
a: &Body,
b: &Body,
op: BoolOp,
ma: &solvecraft_geom::Mesh,
mb: &solvecraft_geom::Mesh,
ma: &solidcraft_geom::Mesh,
mb: &solidcraft_geom::Mesh,
counts: &[(usize, usize)],
) -> Option<Option<Body>> {
let (b_in_a, a_in_b) = overlap(ma, mb)?;
@@ -286,14 +286,14 @@ fn planar_surfaces_match(a: &Body, b: &Body, result: &Body) -> bool {
/// Smallest side of the box where the bodies' vertex boxes overlap (infinite when they don't).
fn meeting_size(a: &Body, b: &Body) -> f64 {
let bbox = |x: &Body| {
let mut bb = solvecraft_geom::Aabb3::EMPTY;
let mut bb = solidcraft_geom::Aabb3::EMPTY;
for v in x.solid.vertex_iter() {
bb.add(from_p3(v.point()));
}
bb
};
let (ba, bb) = (bbox(a), bbox(b));
let s = solvecraft_geom::Aabb3 { min: ba.min.max(bb.min), max: ba.max.min(bb.max) }.size();
let s = solidcraft_geom::Aabb3 { min: ba.min.max(bb.min), max: ba.max.min(bb.max) }.size();
if s.x > 0.0 && s.y > 0.0 && s.z > 0.0 { s.x.min(s.y).min(s.z) } else { f64::INFINITY }
}
@@ -660,7 +660,7 @@ pub fn union_apart(bodies: &[Body]) -> Result<Option<Body>> {
return Ok(None);
}
let pad = Vec3::new(1.0, 1.0, 1.0) * (2.0 * tol);
boxes.push(solvecraft_geom::Aabb3 { min: bb.min - pad, max: bb.max + pad });
boxes.push(solidcraft_geom::Aabb3 { min: bb.min - pad, max: bb.max + pad });
}
// Sweep along x: only boxes whose x ranges overlap are compared.
let mut order: Vec<usize> = (0..boxes.len()).collect();
@@ -851,7 +851,7 @@ pub fn transform(body: &Body, translate: Vec3, origin: Vec3, axis: Vec3, angle:
/// Split a body with a plane: the parts on the positive and the negative side of the plane
/// (only the non-empty ones).
pub fn split_by_plane(body: &Body, plane: &solvecraft_geom::Plane) -> Result<Vec<Body>> {
pub fn split_by_plane(body: &Body, plane: &solidcraft_geom::Plane) -> Result<Vec<Body>> {
let mut out = Vec::new();
for positive in [true, false] {
out.extend(half_space(body, plane, positive)?);
@@ -861,26 +861,26 @@ pub fn split_by_plane(body: &Body, plane: &solvecraft_geom::Plane) -> Result<Vec
/// The part of a body on one side of a plane (`None` when nothing is there): its intersection
/// with [`half_box`] (whose history it carries, over the body and the box).
pub(crate) fn half_space(body: &Body, plane: &solvecraft_geom::Plane, positive: bool) -> Result<Option<Body>> {
pub(crate) fn half_space(body: &Body, plane: &solidcraft_geom::Plane, positive: bool) -> Result<Option<Body>> {
boolean(body, &half_box(body, plane, positive)?, BoolOp::Intersect)
}
/// A box covering everything of `body` on one side of a plane, its start cap in the plane.
pub(crate) fn half_box(body: &Body, plane: &solvecraft_geom::Plane, positive: bool) -> Result<Body> {
pub(crate) fn half_box(body: &Body, plane: &solidcraft_geom::Plane, positive: bool) -> Result<Body> {
body.require_brep("split")?;
let size = body.size();
let mut bb = solvecraft_geom::Aabb3::EMPTY;
let mut bb = solidcraft_geom::Aabb3::EMPTY;
for v in body.solid.vertex_iter() {
bb.add(crate::body::from_p3(v.point()));
}
let reach = (bb.diagonal() + plane.origin.dist(bb.center())) * 2.0 + size;
let c = plane.to_local(bb.center());
let sq = solvecraft_geom::Region2 {
outer: solvecraft_geom::Loop2::polygon(&[
solvecraft_geom::Vec2::new(c.x - reach, c.y - reach),
solvecraft_geom::Vec2::new(c.x + reach, c.y - reach),
solvecraft_geom::Vec2::new(c.x + reach, c.y + reach),
solvecraft_geom::Vec2::new(c.x - reach, c.y + reach),
let sq = solidcraft_geom::Region2 {
outer: solidcraft_geom::Loop2::polygon(&[
solidcraft_geom::Vec2::new(c.x - reach, c.y - reach),
solidcraft_geom::Vec2::new(c.x + reach, c.y - reach),
solidcraft_geom::Vec2::new(c.x + reach, c.y + reach),
solidcraft_geom::Vec2::new(c.x - reach, c.y + reach),
]),
holes: vec![],
};
+5 -5
View File
@@ -3,7 +3,7 @@
use super::{Body, from_p3};
use crate::history::FaceOrigin;
use crate::{Result, guard};
use solvecraft_geom::Plane;
use solidcraft_geom::Plane;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::sync::Arc;
use truck_modeling as mt;
@@ -83,8 +83,8 @@ fn merged_face(faces: &[&mt::Face], members: &[usize], plane: Plane, eligible: &
let edge = boundary.get(index)?.clone();
let da = from_p3(edge.front().point()) - origin;
let db = from_p3(edge.back().point()) - origin;
let a = solvecraft_geom::Vec2::new(da.dot(plane.x), da.dot(plane.y));
let b = solvecraft_geom::Vec2::new(db.dot(plane.x), db.dot(plane.y));
let a = solidcraft_geom::Vec2::new(da.dot(plane.x), da.dot(plane.y));
let b = solidcraft_geom::Vec2::new(db.dot(plane.x), db.dot(plane.y));
let term = a.cross(b);
area += term;
magnitude += term.abs();
@@ -200,7 +200,7 @@ impl Body {
.and_then(|source| {
if generated {
match source {
super::FaceSource::ExtrudeSide { segment: solvecraft_geom::Seg2::Line { .. }, plane: Some(p), .. } => Some(*p),
super::FaceSource::ExtrudeSide { segment: solidcraft_geom::Seg2::Line { .. }, plane: Some(p), .. } => Some(*p),
_ => None,
}
} else {
@@ -327,7 +327,7 @@ mod tests {
#[test]
fn alternate_adjacency_path_cannot_remove_protected_internal_edge() {
let vertices: Vec<_> =
(0..9).map(|i| mt::builder::vertex(super::super::p3(solvecraft_geom::Vec3::new((i % 3) as f64, (i / 3) as f64, 0.)))).collect();
(0..9).map(|i| mt::builder::vertex(super::super::p3(solidcraft_geom::Vec3::new((i % 3) as f64, (i / 3) as f64, 0.)))).collect();
let mut edges = HashMap::<(usize, usize), mt::Edge>::new();
let mut faces = Vec::new();
for (x, y) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
+2 -2
View File
@@ -2,7 +2,7 @@
//! No tessellated face names, centroids, or nearest-plane choices are used.
use super::{Body, FaceSource, from_p3};
use crate::{KernelError, Result, guard};
use solvecraft_geom::{Plane, Vec3};
use solidcraft_geom::{Plane, Vec3};
use std::sync::Arc;
use truck_modeling as mt;
@@ -463,7 +463,7 @@ mod tests {
#[test]
fn pocket_cut_removes_internal_wall_faces_in_actual_topology() {
use solvecraft_geom::{Loop2, Region2, Vec2};
use solidcraft_geom::{Loop2, Region2, Vec2};
let region = Region2 {
outer: Loop2::polygon(&[Vec2::new(-30., -27.), Vec2::new(30., -27.), Vec2::new(30., 27.), Vec2::new(-30., 27.)]),
holes: vec![],
+5 -5
View File
@@ -5,7 +5,7 @@
use std::collections::{BTreeSet, HashMap};
use solvecraft_geom::{Mesh, Vec3};
use solidcraft_geom::{Mesh, Vec3};
use truck_modeling::{self as mt, builder};
use crate::body::{Body, Solid, edge_order, from_p3, p3};
@@ -415,17 +415,17 @@ fn sliced_planar_boolean(a: &Body, b: &Body, op: BoolOp) -> Option<Result<Option
let tol = (a.size() * 1e-3).max(1e-3);
let infos = a.faces(tol).ok()?;
let mesh = a.tessellate(tol).ok()?;
let mut curved: HashMap<u32, solvecraft_geom::Aabb3> = HashMap::new();
let mut curved: HashMap<u32, solidcraft_geom::Aabb3> = HashMap::new();
for (t, fi) in mesh.triangles.iter().zip(&mesh.tri_face) {
if infos.get(*fi as usize)?.plane_normal.is_some() {
continue;
}
let bx = curved.entry(*fi).or_insert(solvecraft_geom::Aabb3::EMPTY);
let bx = curved.entry(*fi).or_insert(solidcraft_geom::Aabb3::EMPTY);
for p in mesh.tri(t)? {
bx.add(p);
}
}
let mut tool = solvecraft_geom::Aabb3::EMPTY;
let mut tool = solidcraft_geom::Aabb3::EMPTY;
for p in pb.iter().flat_map(|p| &p.v) {
tool.add(*p);
}
@@ -481,7 +481,7 @@ fn sliced_planar_boolean(a: &Body, b: &Body, op: BoolOp) -> Option<Result<Option
let mut outside: Vec<Traced> = Vec::new();
for (slot, (cut, keep_positive)) in [(2, (lo, true)), (4, (hi, false))] {
let Some(x) = cut else { continue };
let plane = solvecraft_geom::Plane::new(axis * x, u, w).ok_or_else(|| fail("plane"))?;
let plane = solidcraft_geom::Plane::new(axis * x, u, w).ok_or_else(|| fail("plane"))?;
let (away, kept) = (crate::ops::half_box(&middle.0, &plane, !keep_positive)?, crate::ops::half_box(&middle.0, &plane, keep_positive)?);
let (h_away, h_kept) = (recorded::kept(&away, slot, n), recorded::kept(&kept, slot + 1, n));
if let Some(o) = crate::ops::boolean(&middle.0, &away, BoolOp::Intersect)? {
+1 -1
View File
@@ -5,7 +5,7 @@
use std::collections::HashMap;
use solvecraft_geom::{Plane, Vec3};
use solidcraft_geom::{Plane, Vec3};
use truck_modeling::{self as mt, builder};
use crate::body::{Body, Solid, from_p3, p3, v3};
+1 -1
View File
@@ -6,7 +6,7 @@
//! Stepped extrusions (case floors and walls) use sections between every cap level, so a
//! circular boss can meet the rim without shifting either operand off its exact plane.
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use truck_modeling as mt;
use crate::body::{Body, from_p3};
+2 -2
View File
@@ -1,7 +1,7 @@
//! Whole-span certificates for analytic planar prism boundaries.
//! Samples propose a circle; only positive rational Bernstein bounds accept it.
use crate::body::from_p3;
use solvecraft_geom::{Plane, Seg2, Vec2};
use solidcraft_geom::{Plane, Seg2, Vec2};
use truck_modeling::{self as mt, BoundedCurve, Invertible, ParametricCurve};
const MAX_DEGREE: usize = 32;
@@ -334,7 +334,7 @@ mod tests {
let cap = builder::try_attach_plane(&[wire]).unwrap();
let body = crate::body::Body::new(builder::tsweep(&cap, mt::Vector3::new(0., 0., 1.))).unwrap();
assert!(body.validity().is_empty());
let tool = crate::box_solid(solvecraft_geom::Vec3::new(-1., -4., 0.5), solvecraft_geom::Vec3::new(12., 7., 1.)).unwrap();
let tool = crate::box_solid(solidcraft_geom::Vec3::new(-1., -4., 0.5), solidcraft_geom::Vec3::new(12., 7., 1.)).unwrap();
assert!(
crate::prism::prism_boolean(&body, &tool, crate::BoolOp::Cut).is_none(),
"an actual extruded aliased spline cap must reach the generic route"
+1 -1
View File
@@ -7,7 +7,7 @@
use std::collections::{BTreeSet, HashMap};
use solvecraft_geom::{Plane, Region2, Vec2, Vec3};
use solidcraft_geom::{Plane, Region2, Vec2, Vec3};
use truck_modeling as mt;
use super::{Index, Section, Tag, dist_to, inside};
+3 -3
View File
@@ -1,7 +1,7 @@
//! Independent current-geometry intervals for uniform prisms.
//! These are not mesh measurements or certificates of construction history.
use crate::body::from_p3;
use solvecraft_geom::{Plane, Seg2, Vec2, Vec3};
use solidcraft_geom::{Plane, Seg2, Vec2, Vec3};
use truck_modeling as mt;
#[derive(Clone, Copy, Debug)]
@@ -750,7 +750,7 @@ mod tests {
use mt::builder;
fn drilled(radius: f64, shift: f64) -> crate::Body {
use solvecraft_geom::{Loop2, Region2};
use solidcraft_geom::{Loop2, Region2};
let region = Region2 {
outer: Loop2::polygon(&[Vec2::ZERO, Vec2::new(20., 0.), Vec2::new(20., 20.), Vec2::new(0., 20.)]),
holes: vec![Loop2::circle(Vec2::new(10. + shift, 10.), radius)],
@@ -874,7 +874,7 @@ mod tests {
let arc = Seg2::Arc { center: Vec2::ZERO, radius: 6., start: 0., sweep: std::f64::consts::TAU };
let retraced = CapLoop { segments: vec![arc, arc.reversed(), arc], area: Interval::enclosing(36. * std::f64::consts::PI, 1e-8).unwrap() };
assert!(circle(&retraced, 1e-6).is_none());
let region = solvecraft_geom::Loop2::polygon(&[Vec2::ZERO, Vec2::new(20., 0.), Vec2::new(20., 20.), Vec2::new(0., 20.)]);
let region = solidcraft_geom::Loop2::polygon(&[Vec2::ZERO, Vec2::new(20., 0.), Vec2::new(20., 20.), Vec2::new(0., 20.)]);
let doubled = CapLoop { segments: region.segs.iter().chain(&region.segs).copied().collect(), area: Interval::enclosing(800., 1e-8).unwrap() };
assert!(separated_material(&[doubled], 1e-6, &mut 16_000_000).is_none());
let thin = crate::box_solid(Vec3::ZERO, Vec3::new(20., 20., 1e-5)).unwrap();
+3 -3
View File
@@ -10,7 +10,7 @@
use std::collections::BTreeSet;
use solvecraft_geom::{Mesh, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_geom::{Mesh, Plane, Region2, Seg2, Vec2, Vec3};
use crate::Result;
use crate::body::Body;
@@ -396,7 +396,7 @@ pub fn compose(prov: &mut History, operand: usize, tool: &[FaceOrigin]) {
#[cfg(test)]
mod tests {
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use super::{Change, FaceOrigin, trace};
use crate::{BoolOp, boolean, box_solid, cylinder};
@@ -457,7 +457,7 @@ mod tests {
#[test]
fn provenance_of_sweeps_blends_and_shells() {
use solvecraft_geom::{Loop2, Plane, Region2, Vec2};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
use super::{History, blend_origins, compose, extrude_origins, offset_origins, revolve_origins};
let plane = Plane::named("XY").unwrap();
+1 -1
View File
@@ -3,7 +3,7 @@
use std::f64::consts::{FRAC_PI_2, PI, TAU};
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use crate::body::Body;
use crate::build::PathSeg;
@@ -1,5 +1,5 @@
use super::*;
use solvecraft_geom::Plane;
use solidcraft_geom::Plane;
fn stamped_stock() -> Body {
crate::box_solid(Vec3::new(-20., -15., 0.), Vec3::new(20., 15., 24.)).unwrap().with_box_certificate(7).unwrap()
+1 -1
View File
@@ -7,7 +7,7 @@
use std::collections::HashMap;
use mt::builder;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use truck_modeling as mt;
use crate::body::{Body, Solid, p3, v3};
+1 -1
View File
@@ -1,7 +1,7 @@
//! Group disconnected material regions without moving a cavity to another enclosing shell.
//! A valid shell hierarchy alternates material and void at every nesting level.
use solvecraft_geom::{Aabb3, Mesh, Vec3};
use solidcraft_geom::{Aabb3, Mesh, Vec3};
use crate::{KernelError, Result};
+2 -2
View File
@@ -1,5 +1,5 @@
//! Display-only tangent joins. Unknown geometry retains its existing presentation.
use solvecraft_geom::{Mesh, Vec3};
use solidcraft_geom::{Mesh, Vec3};
use truck_modeling as mt;
use crate::body::{Body, from_p3};
@@ -219,7 +219,7 @@ fn classify(body: &Body, mesh: &Mesh, mut work: usize) -> (Vec<bool>, Vec<bool>)
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2};
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2};
fn circle(radius: f64, count: usize) -> Loop2 {
let step = std::f64::consts::TAU / count as f64;
+1 -1
View File
@@ -9,7 +9,7 @@
use std::collections::HashMap;
use mt::{BoundedCurve, InnerSpace, MetricSpace, ParametricCurve, ParametricSurface, ParametricSurface3D, SearchNearestParameter};
use solvecraft_geom::{Plane, Seg2, Vec2, Vec3};
use solidcraft_geom::{Plane, Seg2, Vec2, Vec3};
use truck_meshalgo::tessellation::RobustMeshableShape;
use truck_modeling as mt;
+4 -4
View File
@@ -1,4 +1,4 @@
use solvecraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use super::*;
use crate::{BoolOp, boolean, box_solid, cylinder, extrude, fillet, measure, revolve, sphere, step_export, torus};
@@ -10,8 +10,8 @@ fn rel(a: f64, b: f64) -> f64 {
#[test]
#[ignore = "manual STEP import timing; run without concurrent builds"]
fn benchmark_independent_step_solids() {
let count = std::env::var("SOLVECRAFT_STEP_BENCH_COUNT").ok().map(|s| s.parse::<usize>().unwrap().clamp(1, 512)).unwrap_or(48);
let kind = std::env::var("SOLVECRAFT_STEP_BENCH_KIND").unwrap_or_else(|_| "cylinder".into());
let count = solidcraft_geom::env::var("STEP_BENCH_COUNT").map(|s| s.parse::<usize>().unwrap().clamp(1, 512)).unwrap_or(48);
let kind = solidcraft_geom::env::var("STEP_BENCH_KIND").unwrap_or_else(|| "cylinder".into());
let bodies: Vec<_> = (0..count)
.map(|i| {
let at = Vec3::new((i % 8) as f64 * 60.0, (i / 8) as f64 * 60.0, 0.0);
@@ -23,7 +23,7 @@ fn benchmark_independent_step_solids() {
})
.collect();
let text = step_export(&bodies.iter().collect::<Vec<_>>(), "independent bodies").unwrap();
if let Ok(path) = std::env::var("SOLVECRAFT_STEP_BENCH_FIXTURE") {
if let Ok(path) = solidcraft_geom::env::var("STEP_BENCH_FIXTURE").ok_or(()) {
std::fs::write(path, &text).unwrap();
}
let start = std::time::Instant::now();
+4 -4
View File
@@ -11,7 +11,7 @@
use std::collections::HashMap;
use std::fmt::Write as _;
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use crate::body::Body;
use crate::step_in::p21::{self, Param};
@@ -1501,8 +1501,8 @@ mod tests {
#[test]
fn assembly_names_colours_round_trip() {
use crate::{box_solid, cylinder, measure, step_import, step_validate};
let bx = box_solid(solvecraft_geom::Vec3::ZERO, solvecraft_geom::Vec3::new(10.0, 20.0, 5.0)).unwrap();
let pin = cylinder(solvecraft_geom::Vec3::ZERO, solvecraft_geom::Vec3::Z, 2.0, 8.0).unwrap();
let bx = box_solid(solidcraft_geom::Vec3::ZERO, solidcraft_geom::Vec3::new(10.0, 20.0, 5.0)).unwrap();
let pin = cylinder(solidcraft_geom::Vec3::ZERO, solidcraft_geom::Vec3::Z, 2.0, 8.0).unwrap();
let rot90 = [[0.0, 1.0, 0.0, 0.0], [-1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [50.0, 0.0, 0.0, 1.0]];
let shift = [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 5.0, 1.0]];
let products = vec![
@@ -1530,7 +1530,7 @@ mod tests {
let m: Vec<_> = imp.bodies.iter().map(|b| measure(&b.body).unwrap()).collect();
assert!((m[1].bbox.min.z - 5.0).abs() < 1e-6 && (m[1].bbox.max.z - 13.0).abs() < 1e-6);
// Rotated 90° about Z and moved 50 along X: centre at (50, 0).
assert!(m[2].centroid.dist(solvecraft_geom::Vec3::new(50.0, 0.0, 4.0)) < 1e-3, "{:?}", m[2].centroid);
assert!(m[2].centroid.dist(solidcraft_geom::Vec3::new(50.0, 0.0, 4.0)) < 1e-3, "{:?}", m[2].centroid);
// Recursion and bad placements are errors.
let cyc = vec![ExportProduct {
name: "A".into(),
+1 -1
View File
@@ -1,7 +1,7 @@
//! What surface a face lies on (plane, cylinder, cone, sphere, torus), for construction
//! geometry: axes through round faces, centres of spheres and tori.
use solvecraft_geom::Vec3;
use solidcraft_geom::Vec3;
use crate::Body;
use crate::offset::{Surf, surf_of};
+2 -2
View File
@@ -6,7 +6,7 @@
use std::collections::HashMap;
use mt::{BoundedCurve, InnerSpace, MetricSpace, ParametricCurve, ParametricSurface3D, SearchNearestParameter};
use solvecraft_geom::{Plane, Region2, Vec3};
use solidcraft_geom::{Plane, Region2, Vec3};
use truck_modeling as mt;
use crate::body::{Body, from_p3, p3};
@@ -612,7 +612,7 @@ pub fn extend(body: &Body, at: &[Vec3], distance: f64) -> Result<Body> {
mod tests {
use super::*;
use crate::{box_solid, cylinder, measure};
use solvecraft_geom::{Loop2, Vec2};
use solidcraft_geom::{Loop2, Vec2};
fn rel(a: f64, b: f64) -> f64 {
(a - b).abs() / b.abs().max(1e-9)
+2 -2
View File
@@ -4,7 +4,7 @@
//! between stations, mitred at the corners.
use mt::builder;
use solvecraft_geom::{Plane, Region2, Vec2, Vec3};
use solidcraft_geom::{Plane, Region2, Vec2, Vec3};
use truck_modeling as mt;
use crate::body::{Body, p3};
@@ -298,7 +298,7 @@ fn smooth_walls(plane: &Plane, profile: &NumberedLoop, st: &[Station], expected:
#[cfg(test)]
mod tests {
use solvecraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use super::sweep_path;
+8 -8
View File
@@ -1,6 +1,6 @@
use std::f64::consts::PI;
use solvecraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use solidcraft_geom::{Loop2, Plane, Region2, Vec2, Vec3};
use crate::*;
@@ -130,7 +130,7 @@ fn chamfer_and_step() {
let b = box_solid(Vec3::ZERO, Vec3::new(20.0, 20.0, 20.0)).unwrap();
let c = chamfer(&b, &[Vec3::new(20.0, 10.0, 20.0)], 2.0).unwrap();
assert!(rel(measure(&c).unwrap().volume, 8000.0 - 2.0 * 20.0) < 1e-4);
let s = step_export(&[&c], "SolveCraft").unwrap();
let s = step_export(&[&c], "SolidCraft").unwrap();
assert!(s.starts_with("ISO-10303-21;"));
assert!(s.contains("MANIFOLD_SOLID_BREP") || s.contains("CLOSED_SHELL"));
}
@@ -668,7 +668,7 @@ fn connected_logo_join_preserves_exact_faces_at_edge_contact() {
let tops =
logo.faces(0.01).unwrap().iter().filter(|f| f.plane_normal.is_some_and(|n| n.z > 0.999999) && (f.centroid.z - 20.0).abs() < 1e-9).count();
assert_eq!(tops, 1);
let text = step_export(&[&logo], "SolveCraft").unwrap();
let text = step_export(&[&logo], "SolidCraft").unwrap();
let imported = step_import(&text).unwrap();
assert_eq!(imported.bodies.len(), 1);
let roundtrip = &imported.bodies.first().unwrap().body;
@@ -1407,7 +1407,7 @@ fn circle_loop_fillets_and_chamfers() {
#[test]
fn trimmed_revolved_faces_measure_exactly() {
use solvecraft_geom::Seg2;
use solidcraft_geom::Seg2;
use std::f64::consts::FRAC_PI_2;
// A slot with concave ends sketched on the plane x = 12.5, cut 4 deep into a revolved shaft
// of radius 12.5 (the cut faces' triangulation spans wide angles unless refined).
@@ -1463,7 +1463,7 @@ fn shell_with_cylindrical_faces() {
#[test]
fn draft_of_a_round_ended_pocket() {
use solvecraft_geom::Seg2;
use solidcraft_geom::Seg2;
use std::f64::consts::FRAC_PI_2;
let block = box_solid(Vec3::ZERO, Vec3::new(80.0, 60.0, 25.0)).unwrap();
let segs = vec![
@@ -1610,7 +1610,7 @@ fn fillet_where_a_branch_pipe_meets_a_main_pipe() {
#[test]
fn fillet_an_open_curved_edge() {
use solvecraft_geom::Seg2;
use solidcraft_geom::Seg2;
// A D-shaped block: the round top edge only, ending at the flat wall's sharp corners.
let d = Loop2 {
segs: vec![
@@ -1740,7 +1740,7 @@ fn fillet_three_edges_at_a_corner() {
#[test]
fn shell_with_spherical_and_conical_faces() {
// A cylinder capped by a hemisphere (a dome), revolved, opened at the bottom.
use solvecraft_geom::Seg2;
use solidcraft_geom::Seg2;
let xz = Plane::new(Vec3::ZERO, Vec3::X, Vec3::new(0.0, 0.0, -1.0)).unwrap();
let segs = vec![
Seg2::Line { a: Vec2::new(0.0, 0.0), b: Vec2::new(10.0, 0.0) },
@@ -1952,7 +1952,7 @@ fn measure_of_a_body_with_many_faces() {
}
/// The time it takes (run alone, the suite's other tests share the machine):
/// `cargo test --release -p solvecraft-kernel perf_measure_5000_faces -- --ignored`.
/// `cargo test --release -p solidcraft-kernel perf_measure_5000_faces -- --ignored`.
#[test]
#[ignore]
fn perf_measure_5000_faces() {
+1 -1
View File
@@ -2,7 +2,7 @@
use std::f64::consts::PI;
use solvecraft_geom::{Plane, Vec2, Vec3};
use solidcraft_geom::{Plane, Vec2, Vec3};
use crate::{BoolOp, cylinder, faceted_boolean, sweep_helix_mesh};
+1 -1
View File
@@ -8,7 +8,7 @@
use std::collections::{BTreeMap, HashMap};
use serde::Serialize;
use solvecraft_geom::{Mesh, Vec3};
use solidcraft_geom::{Mesh, Vec3};
use crate::Result;
use crate::body::{Body, from_p3};
+6 -6
View File
@@ -1,4 +1,4 @@
//! T-spline sheets: a T-spline surface (`solvecraft_geom::tspline`) as a surface body of NURBS
//! T-spline sheets: a T-spline surface (`solidcraft_geom::tspline`) as a surface body of NURBS
//! faces. A T-mesh without T-junctions becomes one clamped tensor face; otherwise each Bézier
//! element is a face, and the faces share their edges and vertices — a side that meets two
//! neighbours is split at the T-point between them — so the sheet is one sewn open shell.
@@ -6,8 +6,8 @@
use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use solvecraft_geom::bspline::{BezierPatch, HPoint, NurbsSurface, SplineError, bezier_segment};
use solvecraft_geom::tspline::TSpline;
use solidcraft_geom::bspline::{BezierPatch, HPoint, NurbsSurface, SplineError, bezier_segment};
use solidcraft_geom::tspline::TSpline;
use truck_modeling as mt;
use crate::body::Body;
@@ -156,9 +156,9 @@ fn patch_faces(patches: &[BezierPatch]) -> Result<Vec<mt::Face>> {
#[cfg(test)]
mod tests {
use solvecraft_geom::Vec3;
use solvecraft_geom::bspline::NurbsSurface;
use solvecraft_geom::tspline::{Cut, TSpline};
use solidcraft_geom::Vec3;
use solidcraft_geom::bspline::NurbsSurface;
use solidcraft_geom::tspline::{Cut, TSpline};
use truck_modeling as mt;
use super::tspline_sheet;