mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
kernel: dead code the ports left behind, deleted
Every lib warning under --features smlib outside the files agents are editing (assemble.rs, sweep_path.rs) pointed at code nothing calls any more: - body: shell_nesting; - boolean_record: of_pair and its Traced alias; - planar_regularize: the generated-wall regularization (and its branches in the certified one, which was its only other path); planar_sources: with_shell_planar_sources; - plane_fit: plane_through, EdgeSampler, planar_face, plane_frame (the patch is fill's); only loop_area is left; - polyhedron: solve3; prism: wall_along; offset: Surf::normal; - record: regions, region; shell_groups: groups, parents and their limits; - splitface: side_of_point, Analytic, analytic_face, face_sides, canonical; - spline_conv.rs, all of it (surfaces builds NURBS records); an unused import in tess/uv.rs. The tests of deleted items go with them: prism's wall_along test, the shell grouping test in body_lump_tests, and the generated-regularization assertion in regularization_protection_tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
@@ -1251,27 +1251,6 @@ impl Body {
|
||||
}
|
||||
Ok(contacts)
|
||||
}
|
||||
|
||||
/// For each shell: whether it bounds material (else a void or an open/wire shell), and the
|
||||
/// shell directly around it (the outer shell of the region it lies in; `None` in the open).
|
||||
pub(crate) fn shell_nesting(&self) -> (Vec<bool>, Vec<Option<usize>>) {
|
||||
let n = self.record.shells.len();
|
||||
let (mut material, mut parent) = (vec![false; n], vec![None; n]);
|
||||
for (r, region) in self.record.regions.iter().enumerate() {
|
||||
let around = if r == 0 { None } else { region.shells.first().map(|&s| s as usize) };
|
||||
for (k, &s) in region.shells.iter().enumerate() {
|
||||
let s = s as usize;
|
||||
if r > 0 && k == 0 {
|
||||
if let Some(m) = material.get_mut(s) {
|
||||
*m = !region.void;
|
||||
}
|
||||
} else if let Some(p) = parent.get_mut(s) {
|
||||
*p = around;
|
||||
}
|
||||
}
|
||||
}
|
||||
(material, parent)
|
||||
}
|
||||
}
|
||||
|
||||
/// Points of an edge's curve over its interval, within `chord` of it: lines by their ends, the
|
||||
|
||||
@@ -175,41 +175,6 @@ fn unowned_overlapping_and_touching_cavities_are_errors_not_dropped_geometry() {
|
||||
assert!(body.lumps().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_grouping_preserves_large_disconnected_patterns_and_bounds_hostile_work() {
|
||||
let mesh = cube(1.0).mesh(&crate::MeshTolerance::chord(0.01)).unwrap();
|
||||
let copies: Vec<_> = (0..512)
|
||||
.map(|i| {
|
||||
let mut m = mesh.clone();
|
||||
for p in &mut m.positions {
|
||||
p.x += f64::from(i) * 4.0;
|
||||
}
|
||||
m
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(shell_groups::groups(&copies, &vec![8.0; 512], 1e-8).unwrap().len(), 512);
|
||||
let excessive = vec![Mesh::default(); shell_groups::MAX_SHELLS + 1];
|
||||
assert!(shell_groups::groups(&excessive, &vec![1.0; excessive.len()], 1e-8).unwrap_err().to_string().contains("shell count"));
|
||||
let mut oversized = mesh.clone();
|
||||
oversized.positions.resize(shell_groups::MAX_POINTS + 1, Vec3::ZERO);
|
||||
assert!(shell_groups::groups(&[oversized], &[8.0], 1e-8).unwrap_err().to_string().contains("grouping limit"));
|
||||
// Two stacks of one slanted triangle, in parallel planes 1 µm apart: every pair's boxes
|
||||
// overlap but none touch, so all 7000² pairs need testing.
|
||||
let slanted = |dz: f64| Mesh {
|
||||
positions: vec![Vec3::new(0.0, 0.0, dz), Vec3::new(1.0, 0.0, 1.0 + dz), Vec3::new(0.0, 1.0, 1.0 + dz)],
|
||||
triangles: vec![[0, 1, 2]; 7000],
|
||||
..Mesh::default()
|
||||
};
|
||||
let (repeated, lifted) = (slanted(0.0), slanted(1e-3));
|
||||
assert!(shell_groups::groups(&[repeated, lifted], &[8.0, 8.0], 1e-8).unwrap_err().to_string().contains("boundary work"));
|
||||
let mut outer = cube(2.0).mesh(&crate::MeshTolerance::chord(0.01)).unwrap();
|
||||
let mut inner = mesh;
|
||||
outer.triangles = outer.triangles.iter().copied().cycle().take(4000).collect();
|
||||
inner.triangles = inner.triangles.iter().copied().cycle().take(4000).collect();
|
||||
inner.positions.resize(4000, Vec3::ZERO);
|
||||
assert!(shell_groups::groups(&[outer, inner], &[64.0, -8.0], 1e-8).unwrap_err().to_string().contains("containment work"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn translated_and_scaled_nested_voids_keep_material_membership() {
|
||||
let body = nested_island();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Composing the exact history of a multi-step operation (a boolean through several steps, a
|
||||
//! local modifier on a healed body): each step's history over its own operands is placed among
|
||||
//! the operation's operands ([`placed`], [`kept`]) and substituted into the next
|
||||
//! ([`through`], [`of_pair`], [`crate::record::after`]). The operands of a boolean are `[a, b]`:
|
||||
//! ([`through`], [`crate::record::after`]). The operands of a boolean are `[a, b]`:
|
||||
//! the target, then the tool. A step that cannot report its history leaves the result without
|
||||
//! one.
|
||||
|
||||
@@ -37,15 +37,6 @@ pub(crate) fn operand(body: &Body, operand: usize, counts: &[(usize, usize)]) ->
|
||||
body.clone().with_checked_history(kept(body, operand, counts.len()), counts)
|
||||
}
|
||||
|
||||
/// A body with its history over the operation's operands (`None`: not known).
|
||||
pub(crate) type Traced = (Body, Option<History>);
|
||||
|
||||
/// The history over the operation's operands of `result`, made from `x` and `y` (in that order)
|
||||
/// whose histories over them are given.
|
||||
pub(crate) fn of_pair(result: &Body, x: &Option<History>, y: &Option<History>) -> Option<History> {
|
||||
composed(result.history()?.clone().over(&[x.as_ref()?, y.as_ref()?]))
|
||||
}
|
||||
|
||||
/// `body` healed as the local modifiers take it (faces on one surface as one face, as after a
|
||||
/// join; faces split on purpose kept apart), with its history over `[body]`
|
||||
/// ([`crate::heal::heal`]), each face keeping the construction source its faces shared and the
|
||||
|
||||
@@ -119,7 +119,6 @@ mod smlib_sweeps_tests;
|
||||
mod smooth_edges;
|
||||
#[cfg(test)]
|
||||
mod sphere_lens_tests;
|
||||
mod spline_conv;
|
||||
mod splitface;
|
||||
mod step;
|
||||
mod step_in;
|
||||
|
||||
@@ -69,34 +69,6 @@ pub(crate) fn surf_of(b: &Brep, face: usize) -> Option<Surf> {
|
||||
}
|
||||
|
||||
impl Surf {
|
||||
/// Outward unit normal of the surface at `p` (on or near it).
|
||||
pub(crate) fn normal(&self, p: Vec3) -> Option<Vec3> {
|
||||
match *self {
|
||||
Surf::Plane { n, .. } => Some(n),
|
||||
Surf::Cylinder { o, a, convex, .. } => {
|
||||
let v = p - o;
|
||||
let radial = (v - a * v.dot(a)).normalized()?;
|
||||
Some(if convex { radial } else { -radial })
|
||||
}
|
||||
Surf::Sphere { c, convex, .. } => {
|
||||
let radial = (p - c).normalized()?;
|
||||
Some(if convex { radial } else { -radial })
|
||||
}
|
||||
Surf::Cone { v, a, half, convex } => {
|
||||
let w = p - v;
|
||||
let q = (w - a * w.dot(a)).normalized()?;
|
||||
let n = q * half.cos() - a * half.sin();
|
||||
Some(if convex { n } else { -n })
|
||||
}
|
||||
Surf::Torus { o, a, big, h, convex, .. } => {
|
||||
let w = p - o;
|
||||
let u = (w - a * w.dot(a)).normalized()?;
|
||||
let n = (p - (o + a * h + u * big)).normalized()?;
|
||||
Some(if convex { n } else { -n })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `other` is this surface, with the same outward side, within `tol`.
|
||||
pub(crate) fn same(&self, other: &Surf, tol: f64) -> bool {
|
||||
match (*self, *other) {
|
||||
|
||||
@@ -27,33 +27,6 @@ fn failed(e: impl std::fmt::Display) -> KernelError {
|
||||
|
||||
impl Body {
|
||||
pub(crate) fn regularize_certified_planar_faces(self) -> Result<Self> {
|
||||
self.regularize_planar_faces(false)
|
||||
}
|
||||
|
||||
/// Only newly produced Cut walls call this path. The temporary planes carry
|
||||
/// construction support, never persistent ancestor/source-role authority.
|
||||
pub(crate) fn regularize_generated_planar_faces(self) -> Result<Self> {
|
||||
let body = self.regularize_planar_faces(true)?;
|
||||
let sources = body.face_sources().map(|sources| {
|
||||
sources
|
||||
.iter()
|
||||
.map(|source| {
|
||||
let mut source = source.clone();
|
||||
if let Some(super::FaceSource::ExtrudeSide { plane, coalesced, .. }) = &mut source {
|
||||
if plane.is_some() && !*coalesced {
|
||||
source = None;
|
||||
} else {
|
||||
*plane = None;
|
||||
}
|
||||
}
|
||||
source
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
Ok(body.with_face_sources(sources.map(Arc::new)))
|
||||
}
|
||||
|
||||
fn regularize_planar_faces(self, generated: bool) -> Result<Self> {
|
||||
if self.regularization_protected {
|
||||
return Ok(self);
|
||||
}
|
||||
@@ -73,16 +46,7 @@ impl Body {
|
||||
.zip(sources)
|
||||
.map(|(actual, s)| {
|
||||
s.as_ref()
|
||||
.and_then(|source| {
|
||||
if generated {
|
||||
match source {
|
||||
super::FaceSource::ExtrudeSide { segment: solidcraft_geom::Seg2::Line { .. }, plane: Some(p), .. } => Some(*p),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
super::planar_sources::certified_plane(source)
|
||||
}
|
||||
})
|
||||
.and_then(super::planar_sources::certified_plane)
|
||||
.filter(|p| actual.is_some_and(|actual| super::planar_sources::same_support(p, &actual)))
|
||||
})
|
||||
.collect();
|
||||
@@ -97,8 +61,8 @@ impl Body {
|
||||
}
|
||||
}
|
||||
}
|
||||
// The edges between two faces of one shell on one certified plane (from one source,
|
||||
// unless the faces are generated walls), off every authored split line.
|
||||
// The edges between two faces of one shell on one certified plane, from one source,
|
||||
// off every authored split line.
|
||||
let mut eligible = Vec::new();
|
||||
let mut identity = 16_000_000usize;
|
||||
let mut work = 2_000_000usize;
|
||||
@@ -120,7 +84,7 @@ impl Body {
|
||||
};
|
||||
let Some(left) = identity.checked_sub(cost) else { return Ok(None) };
|
||||
identity = left;
|
||||
if !generated && sa != sb {
|
||||
if sa != sb {
|
||||
continue;
|
||||
}
|
||||
let Some(cost) = split_points.checked_mul(6).and_then(|n| n.checked_add(1)) else { return Ok(None) };
|
||||
@@ -160,25 +124,18 @@ impl Body {
|
||||
let (body, or) = crate::native::body_from_smlib_ordered(&r)?;
|
||||
let or = or.ok_or_else(|| failed("the result's record is numbered unlike SMLib's body"))?;
|
||||
let h = r.history().map_err(failed)?.history(std::slice::from_ref(&ob), &or).map_err(failed)?;
|
||||
// Each face keeps the source of the first face it is made of; a generated wall made
|
||||
// of several is coalesced.
|
||||
// Each face keeps the source of the first face it is made of.
|
||||
let mut metadata = 2_000_000usize;
|
||||
let mut output = Vec::with_capacity(h.faces.len());
|
||||
for f in &h.faces {
|
||||
let members: Vec<usize> =
|
||||
f.origins.iter().filter_map(|o| if let crate::history::FaceOrigin::Face { face, .. } = o { Some(*face) } else { None }).collect();
|
||||
let mut source = members.iter().min().and_then(|m| sources.get(*m)).cloned().flatten();
|
||||
let source = members.iter().min().and_then(|m| sources.get(*m)).cloned().flatten();
|
||||
if let Some(s) = &source {
|
||||
let Some(cost) = super::planar_sources::descriptor_cost(s) else { return Ok(None) };
|
||||
let Some(left) = metadata.checked_sub(cost) else { return Ok(None) };
|
||||
metadata = left;
|
||||
}
|
||||
if generated
|
||||
&& members.len() > 1
|
||||
&& let Some(super::FaceSource::ExtrudeSide { coalesced, .. }) = &mut source
|
||||
{
|
||||
*coalesced = true;
|
||||
}
|
||||
output.push(source);
|
||||
}
|
||||
let body = body
|
||||
|
||||
@@ -141,12 +141,6 @@ impl Body {
|
||||
self.map_planar_sources(&[input], None, None, &[], false)
|
||||
}
|
||||
|
||||
/// Generate distinct outer/inner roles from the exact original supports of
|
||||
/// an inside shell. Generated rims and curved walls are intentionally absent.
|
||||
pub(crate) fn with_shell_planar_sources(self, input: &Body, thickness: f64, open: &[Vec3]) -> Result<Self> {
|
||||
self.map_planar_sources(&[input], Some(thickness), None, open, false)
|
||||
}
|
||||
|
||||
/// Retain exact shell roles, and construction roles for Cut, only on unchanged
|
||||
/// supports without a competing coplanar tool. No imported or fitted role is inferred.
|
||||
pub(crate) fn with_preserved_boolean_planar_sources(self, input: &Body, tool: &Body, constructed: bool) -> Result<Self> {
|
||||
|
||||
@@ -1,27 +1,6 @@
|
||||
//! Planes fitted to points around a loop (Newell's method), for planar faces of polygons read
|
||||
//! from files and for patches filling a planar loop of edges, and points sampled along record
|
||||
//! edges (shared with the fill of a loop that is not planar).
|
||||
//! The area vector of a loop of points (Newell's method): its normal and the area it encloses.
|
||||
|
||||
use solidcraft_geom::Vec3;
|
||||
use solidcraft_geom::brep::eval::CurveEval;
|
||||
use solidcraft_geom::brep::{Brep, Curve, Edge, Surface};
|
||||
|
||||
use crate::Result;
|
||||
|
||||
/// The plane through points round a loop: its normal by Newell's method (turning with the
|
||||
/// loop, the plane's natural normal), through their centroid; `None` for fewer than three points
|
||||
/// or no area.
|
||||
pub(crate) fn plane_through(pts: &[Vec3]) -> Option<Surface> {
|
||||
if pts.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let z = loop_area(pts).normalized()?;
|
||||
let o = pts.iter().fold(Vec3::ZERO, |a, p| a + *p) / pts.len() as f64;
|
||||
let a = if z.x.abs() < 0.9 { Vec3::X } else { Vec3::Y };
|
||||
let x = (a - z * a.dot(z)).normalized()?;
|
||||
let y = z.cross(x);
|
||||
Some(Surface::Plane { origin: [o.x, o.y, o.z], u_axis: [x.x, x.y, x.z], v_axis: [y.x, y.y, y.z] })
|
||||
}
|
||||
|
||||
/// The area vector of a closed polygon (Newell's method): normal to the loop, as long as the area
|
||||
/// it encloses, pointing the way the loop runs round by the right-hand rule.
|
||||
@@ -34,88 +13,3 @@ pub(crate) fn loop_area(points: &[Vec3]) -> Vec3 {
|
||||
n * 0.5
|
||||
}
|
||||
|
||||
/// Points along a record edge, in the direction a loop runs along it, at `s ∈ [0, 1]` of its
|
||||
/// interval: the record curve's own points (an intersection curve traced as the record holds it).
|
||||
pub(crate) struct EdgeSampler {
|
||||
curve: CurveEval,
|
||||
range: (f64, f64),
|
||||
}
|
||||
|
||||
impl EdgeSampler {
|
||||
/// The sampler of `edge` run from its start (`forward`) or from its end.
|
||||
pub(crate) fn new(edge: &Edge, forward: bool) -> Option<EdgeSampler> {
|
||||
let [t0, t1] = edge.interval;
|
||||
let curve = CurveEval::new(&edge.curve).ok()?;
|
||||
(t0.is_finite() && t1.is_finite() && t0 < t1).then_some(EdgeSampler { curve, range: if forward { (t0, t1) } else { (t1, t0) } })
|
||||
}
|
||||
|
||||
pub(crate) fn at(&mut self, s: f64) -> Option<Vec3> {
|
||||
let (t0, t1) = self.range;
|
||||
let t = if s >= 1.0 { t1 } else { t0 + (t1 - t0) * s };
|
||||
self.curve.point(t).filter(|p| p.is_finite())
|
||||
}
|
||||
}
|
||||
|
||||
/// The plane of the closed loop `chain` of edges of `b` (each with the way the loop runs along
|
||||
/// it) when its edges are certainly in one plane, `None` otherwise (also when an edge cannot be
|
||||
/// sampled). The plane is fitted to points along the edges ([`plane_through`], its natural normal
|
||||
/// turning with the loop); the edges are certified within `tol` of it from their records: a line
|
||||
/// by its ends, a circle or ellipse by its frame, a polyline, B-spline or NURBS curve with
|
||||
/// positive weights by its points (the curve lies in their convex hull), an intersection curve
|
||||
/// only when one of its two surfaces is a plane that the loop's points lie on (the curve lies in
|
||||
/// that plane). Samples alone are never trusted: a spline can bulge out of the plane between them.
|
||||
pub(crate) fn planar_face(b: &Brep, chain: &[(u32, bool)], tol: f64) -> Result<Option<Surface>> {
|
||||
const SAMPLES: usize = 16;
|
||||
let edges: Vec<&Edge> = match chain.iter().map(|(e, _)| b.edges.get(*e as usize)).collect::<Option<_>>() {
|
||||
Some(e) => e,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let mut pts = Vec::with_capacity(chain.len() * SAMPLES);
|
||||
for (e, (_, forward)) in edges.iter().zip(chain) {
|
||||
let Some(mut sampler) = EdgeSampler::new(e, *forward) else { return Ok(None) };
|
||||
for k in 0..SAMPLES {
|
||||
let Some(p) = sampler.at(k as f64 / SAMPLES as f64) else { return Ok(None) };
|
||||
pts.push(p);
|
||||
}
|
||||
}
|
||||
let Some(plane) = plane_through(&pts) else { return Ok(None) };
|
||||
let Some((o, n)) = plane_frame(&plane) else { return Ok(None) };
|
||||
let on = |p: Vec3, o: Vec3, n: Vec3| (p - o).dot(n).abs() <= tol;
|
||||
let on_plane = |p: Vec3| on(p, o, n);
|
||||
if !pts.iter().all(|p| on_plane(*p)) {
|
||||
return Ok(None);
|
||||
}
|
||||
for e in &edges {
|
||||
let certified = match &e.curve {
|
||||
Curve::Line { start, end } => on_plane(Vec3::from(*start)) && on_plane(Vec3::from(*end)),
|
||||
Curve::Circle { frame, .. } | Curve::Ellipse { frame, .. } => {
|
||||
let z = Vec3::from(frame.x_axis).cross(Vec3::from(frame.y_axis));
|
||||
on_plane(Vec3::from(frame.origin)) && z.cross(n).len() <= 1e-9
|
||||
}
|
||||
Curve::Polyline { points } | Curve::Spline { points, .. } => points.iter().all(|p| on_plane(Vec3::from(*p))),
|
||||
Curve::RationalSpline { homogeneous, .. } => {
|
||||
homogeneous.iter().all(|h| h[3] > 0.0 && on_plane(Vec3::new(h[0] / h[3], h[1] / h[3], h[2] / h[3])))
|
||||
}
|
||||
// A support plane holds the loop when the loop's points lie on it.
|
||||
Curve::SurfaceIntersection { surfaces, .. } => {
|
||||
surfaces.iter().filter_map(|s| plane_frame(s)).any(|(qo, qn)| pts.iter().all(|p| on(*p, qo, qn)))
|
||||
}
|
||||
};
|
||||
if !certified {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Ok(Some(plane))
|
||||
}
|
||||
|
||||
/// A plane record's origin and unit normal (`None` for any other surface).
|
||||
fn plane_frame(s: &Surface) -> Option<(Vec3, Vec3)> {
|
||||
match s {
|
||||
Surface::Plane { origin, u_axis, v_axis } => Some((Vec3::from(*origin), Vec3::from(*u_axis).cross(Vec3::from(*v_axis)).normalized()?)),
|
||||
Surface::PlaneThrough { origin, u_point, v_point } => {
|
||||
let o = Vec3::from(*origin);
|
||||
Some((o, (Vec3::from(*u_point) - o).cross(Vec3::from(*v_point) - o).normalized()?))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,30 +205,6 @@ pub fn draft(b: &Body, faces: &[Vec3], neutral: &solidcraft_geom::Plane, pull: V
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn solve3_pub(m: [[f64; 3]; 3], r: [f64; 3]) -> Option<[f64; 3]> {
|
||||
solve3(m, r)
|
||||
}
|
||||
|
||||
fn solve3(m: [[f64; 3]; 3], r: [f64; 3]) -> Option<[f64; 3]> {
|
||||
let det = |a: [[f64; 3]; 3]| {
|
||||
a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1]) - a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
|
||||
+ a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0])
|
||||
};
|
||||
let d = det(m);
|
||||
if d.abs() < 1e-9 {
|
||||
return None;
|
||||
}
|
||||
let mut out = [0.0; 3];
|
||||
for (k, o) in out.iter_mut().enumerate() {
|
||||
let mut mk = m;
|
||||
for (row, rv) in mk.iter_mut().zip(r) {
|
||||
row[k] = rv;
|
||||
}
|
||||
*o = det(mk) / d;
|
||||
}
|
||||
Some(out)
|
||||
}
|
||||
|
||||
/// Move the faces at the given points along their outward normals by `distance` (negative: into
|
||||
/// the body), SMLib's offset of faces: the faces around them are extended or trimmed to meet them.
|
||||
pub fn offset_faces(b: &Body, at: &[Vec3], distance: f64) -> Result<Body> {
|
||||
|
||||
@@ -238,83 +238,6 @@ mod sheets {
|
||||
}
|
||||
}
|
||||
|
||||
/// Certify that a curved wall is swept along `d` (unit) from its surface record: a cylinder or
|
||||
/// a revolved line about an axis along `d`, or a spline net whose rows run along `d` (either
|
||||
/// parameter) with weights that separate into one factor per parameter. Seam vertices alone
|
||||
/// cannot establish this: a half-cylinder's seam normals can both be perpendicular to a
|
||||
/// transverse direction, silently filling its hole. A plane is no curved wall.
|
||||
pub(crate) fn wall_along(surface: &Surface, d: Vec3, tol: f64, remaining: &mut usize) -> bool {
|
||||
let v3 = |p: [f64; 3]| Vec3::new(p[0], p[1], p[2]);
|
||||
let along = |a: Vec3| a.normalized().is_some_and(|a| a.cross(d).len() < 1e-9);
|
||||
let points: Vec<Vec<(Vec3, f64)>> = match surface {
|
||||
Surface::Cylinder { frame, .. } => return along(v3(frame.x_axis).cross(v3(frame.y_axis))),
|
||||
Surface::PlacedRevolution { profile, axis, matrix, .. } => {
|
||||
let solidcraft_geom::brep::Curve::Line { start, end } = &**profile else { return false };
|
||||
let (Some(a), Some(m)) = (v3(*axis).normalized(), (v3(*end) - v3(*start)).normalized()) else { return false };
|
||||
if a.cross(m).len() > 1e-9 {
|
||||
return false; // A conical meridian is not a cylindrical wall.
|
||||
}
|
||||
// The axis placed by the column-major matrix (a direction: no translation).
|
||||
let c = |k: usize| matrix[0][k] * a.x + matrix[1][k] * a.y + matrix[2][k] * a.z;
|
||||
return c(3).abs() < 1e-12 && along(Vec3::new(c(0), c(1), c(2)));
|
||||
}
|
||||
Surface::Spline { points, .. } => points.iter().map(|r| r.iter().map(|p| (v3(*p), 1.0)).collect()).collect(),
|
||||
Surface::RationalSpline { homogeneous, .. } => {
|
||||
homogeneous.iter().map(|r| r.iter().map(|p| (Vec3::new(p[0] / p[3], p[1] / p[3], p[2] / p[3]), p[3])).collect()).collect()
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
let (rows, cols) = (points.len(), points.first().map_or(0, Vec::len));
|
||||
let count = rows.saturating_mul(cols);
|
||||
if rows < 2 || cols < 2 || count > 16_384 || points.iter().any(|r| r.len() != cols) {
|
||||
return false;
|
||||
}
|
||||
let Some(left) = remaining.checked_sub(count.saturating_mul(2)) else { return false };
|
||||
*remaining = left;
|
||||
if points.iter().flatten().any(|(p, w)| !w.is_finite() || *w <= 0.0 || !p.is_finite()) {
|
||||
return false;
|
||||
}
|
||||
let max_weight = points.iter().flatten().map(|p| p.1).fold(0.0, f64::max);
|
||||
if points.iter().flatten().any(|p| p.1 / max_weight < 1e-12) {
|
||||
return false;
|
||||
}
|
||||
// Either parameter can be the extrusion direction. Projected positions must
|
||||
// be invariant in that parameter; rational weights must also separate into
|
||||
// a product of one factor per parameter, otherwise mixed weights bend the wall.
|
||||
for transpose in [false, true] {
|
||||
let (groups, length) = if transpose { (cols, rows) } else { (rows, cols) };
|
||||
let at = |i: usize, j: usize| if transpose { points[j][i] } else { points[i][j] };
|
||||
let mut valid = true;
|
||||
let mut extended = false;
|
||||
for i in 0..groups {
|
||||
let (base, base_w) = at(i, 0);
|
||||
for j in 0..length {
|
||||
let (p, w) = at(i, j);
|
||||
let delta = p - base;
|
||||
let magnitude = [p.x.abs(), p.y.abs(), p.z.abs(), base.x.abs(), base.y.abs(), base.z.abs()].into_iter().fold(1.0, f64::max);
|
||||
if delta.cross(d).len() > tol * 1e-3 + magnitude * f64::EPSILON * 128.0 {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
extended |= delta.dot(d).abs() > tol * 1e-3;
|
||||
let lhs = w / max_weight * (at(0, 0).1 / max_weight);
|
||||
let rhs = base_w / max_weight * (at(0, j).1 / max_weight);
|
||||
if (lhs - rhs).abs() > 1e-12 * lhs.abs().max(rhs.abs()) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if valid && extended {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used, clippy::panic)]
|
||||
mod planar_tests {
|
||||
@@ -355,24 +278,6 @@ mod planar_tests {
|
||||
Region2 { outer: Loop2::polygon(&[a, Vec2::new(b.x, a.y), b, Vec2::new(a.x, b.y)]), holes: vec![] }
|
||||
}
|
||||
#[test]
|
||||
fn curved_prism_wall_requires_positive_separable_rational_weights() {
|
||||
let surface = |mixed: bool| {
|
||||
let weight = if mixed { 2.0 } else { 1.0 };
|
||||
Surface::RationalSpline {
|
||||
u_knots: vec![0., 0., 1., 1.],
|
||||
v_knots: vec![0., 0., 1., 1.],
|
||||
homogeneous: vec![
|
||||
vec![[0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 10.0, 1.0]],
|
||||
vec![[5.0, 0.0, 0.0, 1.0], [5.0 * weight, 0.0, 10.0 * weight, weight]],
|
||||
],
|
||||
}
|
||||
};
|
||||
assert!(wall_along(&surface(false), Vec3::Z, 1e-5, &mut 1000));
|
||||
assert!(!wall_along(&surface(true), Vec3::Z, 1e-5, &mut 1000), "parallel Euclidean controls alone do not certify a rational sweep");
|
||||
assert!(!wall_along(&surface(false), Vec3::Y, 1e-5, &mut 1000));
|
||||
assert!(!wall_along(&surface(false), Vec3::Z, 1e-5, &mut 0));
|
||||
}
|
||||
#[test]
|
||||
fn compound_planar_extrusion_normalizes_overlap_and_retains_islands() {
|
||||
let plane = Plane::named("XY").unwrap();
|
||||
let a = rectangle(Vec2::ZERO, Vec2::new(10.0, 10.0));
|
||||
|
||||
@@ -39,28 +39,6 @@ pub(crate) fn generated(origins: Vec<FaceOrigin>) -> FaceProv {
|
||||
FaceProv { origins, change: Change::Generated }
|
||||
}
|
||||
|
||||
/// Result faces that are each a region of input faces, given as the input faces under it and
|
||||
/// whether it is one input face untouched (`whole`). Kept when whole; Merged with several
|
||||
/// origins; Split when its one origin lies under other result faces too; Modified otherwise;
|
||||
/// Generated with no origin.
|
||||
pub(crate) fn regions(faces: Vec<(BTreeSet<FaceOrigin>, bool)>) -> Vec<FaceProv> {
|
||||
let mut faces: Vec<FaceProv> = faces.into_iter().map(|(origins, whole)| region(origins, whole)).collect();
|
||||
mark_shared_split(&mut faces);
|
||||
faces
|
||||
}
|
||||
|
||||
/// A result face that is a region of the input faces `origins` (see [`regions`]), before
|
||||
/// faces sharing an origin are marked Split.
|
||||
pub(crate) fn region(origins: BTreeSet<FaceOrigin>, whole: bool) -> FaceProv {
|
||||
let change = match origins.len() {
|
||||
0 => Change::Generated,
|
||||
1 if whole => Change::Kept,
|
||||
1 => Change::Modified,
|
||||
_ => Change::Merged,
|
||||
};
|
||||
FaceProv { origins: origins.into_iter().collect(), change }
|
||||
}
|
||||
|
||||
pub(crate) use solidcraft_geom::brep::history::mark_shared_split;
|
||||
|
||||
/// The origins of a result edge that is (a piece of, or the moved) edges `edges` of operand
|
||||
|
||||
@@ -36,7 +36,6 @@ fn stamped_shell_keeps_authored_painted_and_split_boundaries() {
|
||||
assert!(!mesh.contains(Vec3::new(0., 0., 12.)));
|
||||
let count = result.face_count();
|
||||
assert_eq!(result.clone().regularize_certified_planar_faces().unwrap().face_count(), count);
|
||||
assert_eq!(result.clone().regularize_generated_planar_faces().unwrap().face_count(), count);
|
||||
let placed = crate::transform(&result, Vec3::new(30., -40., 7.), Vec3::ZERO, Vec3::Z, 0.3).unwrap();
|
||||
assert!(placed.regularization_protected);
|
||||
for lump in placed.lumps().unwrap() {
|
||||
|
||||
@@ -5,12 +5,7 @@ use solidcraft_geom::{Aabb3, BoxedTriangle, Mesh, Vec3, find_close_pair};
|
||||
|
||||
use crate::{KernelError, Result};
|
||||
|
||||
pub(super) const MAX_SHELLS: usize = 1024;
|
||||
pub(super) const MAX_POINTS: usize = 100_000;
|
||||
pub(super) const MAX_TRIANGLES: usize = 100_000;
|
||||
const MAX_PAIR_WORK: usize = 40_000_000;
|
||||
const MAX_PROBES: usize = 2_000_000;
|
||||
const MAX_CONTAINMENT_WORK: usize = 40_000_000;
|
||||
|
||||
fn failed(detail: &str) -> KernelError {
|
||||
KernelError::Failed(format!("lumps: {detail}"))
|
||||
@@ -112,138 +107,3 @@ pub(super) fn meshes_meet(a: &Mesh, b: &Mesh, gap: f64) -> Result<bool> {
|
||||
Ok(b.positions.first().is_some_and(|p| a.contains(*p)) || a.positions.first().is_some_and(|p| b.contains(*p)))
|
||||
}
|
||||
|
||||
pub(super) fn groups(meshes: &[Mesh], volumes: &[f64], gap: f64) -> Result<Vec<Vec<usize>>> {
|
||||
let parent = parents(meshes, volumes, gap)?;
|
||||
let mut groups = Vec::new();
|
||||
for (i, v) in volumes.iter().enumerate() {
|
||||
if *v <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
let mut group = vec![i];
|
||||
group.extend(parent.iter().enumerate().filter_map(|(j, p)| (*p == Some(i)).then_some(j)));
|
||||
groups.push(group);
|
||||
}
|
||||
Ok(groups)
|
||||
}
|
||||
|
||||
/// The shell directly around each closed shell (`None` for one in the open): outward shells
|
||||
/// (positive volume) bound material, inward ones voids, and nesting alternates them.
|
||||
pub(super) fn parents(meshes: &[Mesh], volumes: &[f64], gap: f64) -> Result<Vec<Option<usize>>> {
|
||||
if meshes.is_empty() || meshes.len() != volumes.len() || meshes.len() > MAX_SHELLS {
|
||||
return Err(failed("shell count exceeds the grouping limit or is invalid"));
|
||||
}
|
||||
if !gap.is_finite() || gap <= 0.0 {
|
||||
return Err(failed("invalid shell clearance"));
|
||||
}
|
||||
let mut points = 0usize;
|
||||
let mut triangle_count = 0usize;
|
||||
let mut triangles = Vec::with_capacity(meshes.len());
|
||||
let mut bounds = Vec::with_capacity(meshes.len());
|
||||
for (mesh, volume) in meshes.iter().zip(volumes) {
|
||||
points = points.checked_add(mesh.positions.len()).ok_or_else(|| failed("point count exceeds the grouping limit"))?;
|
||||
triangle_count = triangle_count.checked_add(mesh.triangles.len()).ok_or_else(|| failed("triangle count exceeds the grouping limit"))?;
|
||||
if points > MAX_POINTS || triangle_count > MAX_TRIANGLES {
|
||||
return Err(failed("mesh exceeds the shell grouping limit"));
|
||||
}
|
||||
if !volume.is_finite()
|
||||
|| *volume == 0.0
|
||||
|| mesh.positions.is_empty()
|
||||
|| mesh.triangles.is_empty()
|
||||
|| mesh.positions.iter().any(|p| !p.is_finite())
|
||||
{
|
||||
return Err(failed("cannot determine a closed shell's orientation"));
|
||||
}
|
||||
let tris: Vec<_> = mesh.triangles.iter().map(|t| mesh.tri(t).ok_or_else(|| failed("invalid shell triangle"))).collect::<Result<_>>()?;
|
||||
if tris.iter().any(|[a, b, c]| unit((*b - *a).cross(*c - *a)).is_none()) {
|
||||
return Err(failed("degenerate shell triangle"));
|
||||
}
|
||||
triangles.push(tris.into_iter().map(|t| (t, box_of(&t))).collect::<Vec<BoxedTriangle>>());
|
||||
bounds.push(mesh.bounds());
|
||||
}
|
||||
let mut inside = vec![vec![false; meshes.len()]; meshes.len()];
|
||||
let mut pair_budget = MAX_PAIR_WORK;
|
||||
let mut probes = 0usize;
|
||||
let mut containment_work = 0usize;
|
||||
for (i, mesh) in meshes.iter().enumerate() {
|
||||
for j in i + 1..meshes.len() {
|
||||
let (Some(a_bounds), Some(b_bounds)) = (bounds.get(i), bounds.get(j)) else { return Err(failed("missing shell bounds")) };
|
||||
if boxes_separate(*a_bounds, *b_bounds, gap) {
|
||||
continue;
|
||||
}
|
||||
let (Some(a), Some(b)) = (triangles.get(i), triangles.get(j)) else { return Err(failed("missing shell triangles")) };
|
||||
if find_close_pair(a, b, gap, &mut pair_budget, |(x, _), (y, _)| triangles_meet(x, y, gap))
|
||||
.ok_or_else(|| failed("shell boundary work exceeds the grouping limit"))?
|
||||
{
|
||||
return Err(failed("shell boundaries intersect or touch; cavity ownership is ambiguous"));
|
||||
}
|
||||
for (inner, outer) in [(i, j), (j, i)] {
|
||||
let ib = bounds.get(inner).ok_or_else(|| failed("missing shell bounds"))?;
|
||||
let ob = bounds.get(outer).ok_or_else(|| failed("missing shell bounds"))?;
|
||||
// With boundary intersections already excluded, a connected shell
|
||||
// extending beyond another's bounds cannot be wholly contained.
|
||||
if ib.min.x < ob.min.x - gap
|
||||
|| ib.min.y < ob.min.y - gap
|
||||
|| ib.min.z < ob.min.z - gap
|
||||
|| ib.max.x > ob.max.x + gap
|
||||
|| ib.max.y > ob.max.y + gap
|
||||
|| ib.max.z > ob.max.z + gap
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let inner_mesh = if inner == i { mesh } else { meshes.get(inner).ok_or_else(|| failed("missing shell"))? };
|
||||
probes = probes.checked_add(inner_mesh.positions.len()).ok_or_else(|| failed("shell probes exceed the grouping limit"))?;
|
||||
if probes > MAX_PROBES {
|
||||
return Err(failed("shell probes exceed the grouping limit"));
|
||||
}
|
||||
let outer_mesh = meshes.get(outer).ok_or_else(|| failed("missing shell"))?;
|
||||
let probe_count = inner_mesh.positions.len();
|
||||
let work = probe_count
|
||||
.checked_mul(outer_mesh.triangles.len())
|
||||
.and_then(|n| n.checked_mul(3))
|
||||
.ok_or_else(|| failed("shell containment work exceeds the grouping limit"))?;
|
||||
containment_work = containment_work.checked_add(work).ok_or_else(|| failed("shell containment work exceeds the grouping limit"))?;
|
||||
if containment_work > MAX_CONTAINMENT_WORK {
|
||||
return Err(failed("shell containment work exceeds the grouping limit"));
|
||||
}
|
||||
let mut contained = None;
|
||||
for p in inner_mesh.positions.iter().copied() {
|
||||
let hit = outer_mesh.contains(p);
|
||||
if contained.is_some_and(|previous| previous != hit) {
|
||||
return Err(failed("shell containment is inconsistent; cavity ownership is ambiguous"));
|
||||
}
|
||||
contained = Some(hit);
|
||||
}
|
||||
if let Some(slot) = inside.get_mut(inner).and_then(|row| row.get_mut(outer)) {
|
||||
*slot = contained == Some(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut parent = Vec::with_capacity(meshes.len());
|
||||
for i in 0..meshes.len() {
|
||||
let row = inside.get(i).ok_or_else(|| failed("missing containment"))?;
|
||||
let candidates: Vec<_> = row.iter().enumerate().filter_map(|(j, hit)| hit.then_some(j)).collect();
|
||||
for (k, a) in candidates.iter().enumerate() {
|
||||
for b in candidates.iter().skip(k + 1) {
|
||||
if !inside.get(*a).and_then(|r| r.get(*b)).copied().unwrap_or(false)
|
||||
&& !inside.get(*b).and_then(|r| r.get(*a)).copied().unwrap_or(false)
|
||||
{
|
||||
return Err(failed("multiple overlapping shell owners"));
|
||||
}
|
||||
}
|
||||
}
|
||||
let owner = candidates.into_iter().min_by(|a, b| {
|
||||
volumes.get(*a).copied().unwrap_or(f64::INFINITY).abs().total_cmp(&volumes.get(*b).copied().unwrap_or(f64::INFINITY).abs())
|
||||
});
|
||||
let outward = volumes.get(i).is_some_and(|v| *v > 0.0);
|
||||
match owner {
|
||||
None if !outward => return Err(failed("inward shell has no enclosing material shell")),
|
||||
Some(p) if volumes.get(p).is_some_and(|v| (*v > 0.0) == outward) => {
|
||||
return Err(failed("nested shells do not alternate material and void"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
parent.push(owner);
|
||||
}
|
||||
Ok(parent)
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
//! geom's B-spline curves and surfaces as schema 4 records, weights kept (homogeneous control
|
||||
//! points carried over as they are). A whole tensor surface as a one-face sheet is
|
||||
//! `surfaces::tensor_sheet`.
|
||||
|
||||
use solidcraft_geom::brep::{Curve, Surface};
|
||||
use solidcraft_geom::bspline::{HPoint, NurbsSurface};
|
||||
|
||||
/// A homogeneous control point as the record's `[x*w, y*w, z*w, w]`.
|
||||
pub(crate) fn h4(h: HPoint) -> [f64; 4] {
|
||||
[h.x, h.y, h.z, h.w]
|
||||
}
|
||||
|
||||
/// A NURBS curve of knots and homogeneous control points.
|
||||
pub(crate) fn nurbs_curve(knots: &[f64], ctrl: &[HPoint]) -> Curve {
|
||||
Curve::RationalSpline { knots: knots.to_vec(), homogeneous: ctrl.iter().copied().map(h4).collect() }
|
||||
}
|
||||
|
||||
/// A NURBS surface of knots and homogeneous control points (`rows[i][j]`: i along u).
|
||||
pub(crate) fn nurbs_surface(u_knots: &[f64], v_knots: &[f64], rows: Vec<Vec<[f64; 4]>>) -> Surface {
|
||||
Surface::RationalSpline { u_knots: u_knots.to_vec(), v_knots: v_knots.to_vec(), homogeneous: rows }
|
||||
}
|
||||
|
||||
/// geom's tensor surface as a record surface.
|
||||
pub(crate) fn surface(s: &NurbsSurface) -> Surface {
|
||||
let [nu, nv] = s.count();
|
||||
let [ku, kv] = s.knots();
|
||||
nurbs_surface(ku, kv, (0..nu).map(|i| (0..nv).map(|j| h4(s.point(i, j))).collect()).collect())
|
||||
}
|
||||
@@ -22,7 +22,7 @@ use std::f64::consts::TAU;
|
||||
use solidcraft_geom::brep::eval::{CurveEval, SurfaceEval};
|
||||
use solidcraft_geom::brep::{self, Frame, Surface};
|
||||
use solidcraft_geom::tess::uv::Starts;
|
||||
use solidcraft_geom::{Aabb3, Plane, Seg2, UnitVec3, Vec2, Vec3};
|
||||
use solidcraft_geom::{Aabb3, Plane, Seg2, Vec2, Vec3};
|
||||
|
||||
use crate::body::Body;
|
||||
use crate::history::{EdgeOrigin, FaceOrigin, History};
|
||||
@@ -358,11 +358,6 @@ impl ToolSide {
|
||||
}
|
||||
}
|
||||
|
||||
/// Which side of a tool a point is on.
|
||||
pub(crate) fn side_of_point(tool: &SplitTool, near: &Body, p: Vec3) -> Result<f64> {
|
||||
Ok(ToolSide::of(tool, near)?.at(p))
|
||||
}
|
||||
|
||||
/// How many fields a tool is (a sketch tool: one per curve).
|
||||
pub(crate) fn field_count(tool: &SplitTool) -> Result<usize> {
|
||||
Ok(match tool {
|
||||
@@ -371,64 +366,6 @@ pub(crate) fn field_count(tool: &SplitTool) -> Result<usize> {
|
||||
})
|
||||
}
|
||||
|
||||
/// A plane or circular cylinder behind a face's surface, facing as the face does.
|
||||
pub(crate) enum Analytic {
|
||||
Plane { n: Vec3 },
|
||||
Cylinder { o: Vec3, axis: UnitVec3, r: f64, out: f64 },
|
||||
}
|
||||
|
||||
/// The plane or circular cylinder face `face` of `body` lies on (a B-spline whose control net is
|
||||
/// flat is a plane), with the face's orientation; none for other surfaces.
|
||||
pub(crate) fn analytic_face(body: &Body, face: usize) -> Result<Option<Analytic>> {
|
||||
let p = face_patch(body, face)?;
|
||||
let sign = if p.flip { -1.0 } else { 1.0 };
|
||||
let flat = |pts: &[Vec3]| -> Option<Vec3> {
|
||||
let o = *pts.first()?;
|
||||
let size = pts.iter().map(|q| q.dist(o)).fold(0.0, f64::max);
|
||||
let n = pts.iter().flat_map(|a| pts.iter().map(move |b| (*a - o).cross(*b - o))).max_by(|a, b| a.len().total_cmp(&b.len()))?.normalized()?;
|
||||
pts.iter().all(|q| (*q - o).dot(n).abs() <= size * 1e-9).then_some(n)
|
||||
};
|
||||
Ok(match &p.surface {
|
||||
Surface::Plane { u_axis, v_axis, .. } => v3(*u_axis).cross(v3(*v_axis)).normalized().map(|n| Analytic::Plane { n: n * sign }),
|
||||
Surface::PlaneThrough { origin, u_point, v_point } => {
|
||||
(v3(*u_point) - v3(*origin)).cross(v3(*v_point) - v3(*origin)).normalized().map(|n| Analytic::Plane { n: n * sign })
|
||||
}
|
||||
Surface::Cylinder { frame, radius } => {
|
||||
v3(frame.x_axis).cross(v3(frame.y_axis)).unit().map(|axis| Analytic::Cylinder { o: v3(frame.origin), axis, r: *radius, out: sign })
|
||||
}
|
||||
Surface::Spline { points, .. } => {
|
||||
let pts: Vec<Vec3> = points.iter().flatten().map(|q| v3(*q)).collect();
|
||||
// The net's normal, turned to the surface's own.
|
||||
let m = Measured::new(&p)?;
|
||||
flat(&pts).and_then(|n| {
|
||||
let (_, su, sv) = m.eval.derivs(p.domain[0][0], p.domain[1][0])?;
|
||||
let own = su.cross(sv);
|
||||
Some(Analytic::Plane { n: if own.dot(n) < 0.0 { -n } else { n } * sign })
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Which side of a tool each face of `body` lies on (in face order): the field at the centre of
|
||||
/// the face's triangle where it is largest, so well inside the face.
|
||||
pub(crate) fn face_sides(body: &Body, tool: &SplitTool) -> Result<Vec<f64>> {
|
||||
let side = ToolSide::of(tool, body)?;
|
||||
let mesh = body.mesh(&crate::MeshTolerance::chord((body.size() * 2e-3).max(1e-4)))?;
|
||||
let mut best = vec![0.0f64; body.face_count()];
|
||||
for (t, f) in mesh.triangles.iter().zip(&mesh.tri_face) {
|
||||
let Some([a, b, c]) = mesh.tri(t) else { continue };
|
||||
let x = side.at((a + b + c) * (1.0 / 3.0));
|
||||
if let Some(slot) = best.get_mut(*f as usize)
|
||||
&& x.is_finite()
|
||||
&& x.abs() > slot.abs()
|
||||
{
|
||||
*slot = x;
|
||||
}
|
||||
}
|
||||
Ok(best)
|
||||
}
|
||||
|
||||
/// The regions of a body cut by a tool: for each face (in face order), the region it lies in,
|
||||
/// and how many there are. Faces are in one region when they meet at an edge the tool does not
|
||||
/// run along; an edge along the tool (a split line) parts them.
|
||||
@@ -489,11 +426,6 @@ fn smlib_tools(groups: &[Vec<Patch>], reach: &Aabb3) -> Result<Vec<solidcraft_sm
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// The record's position of SMLib's face `i` (identity when `order` is empty).
|
||||
fn canonical(order: &[usize], i: usize) -> Option<usize> {
|
||||
if order.is_empty() { Some(i) } else { order.get(i).copied() }
|
||||
}
|
||||
|
||||
/// A result checked by SMLib and brought back with its history over `[body]`.
|
||||
fn brought_back(name: &str, body: &Body, ob: &solidcraft_smlib::history::Order, r: &solidcraft_smlib::Brep) -> Result<(Body, History)> {
|
||||
let check = r.check(solidcraft_smlib::CheckLevel::All).map_err(|e| failed(name, e))?;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! face's parameter box.
|
||||
|
||||
use solidcraft_geom::tess::EvalSurface;
|
||||
pub(crate) use solidcraft_geom::tess::uv::{Domain, degenerate, domain, project, unwrap};
|
||||
pub(crate) use solidcraft_geom::tess::uv::{Domain, degenerate, domain, unwrap};
|
||||
|
||||
use super::size::Rules;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user