mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 10:35:58 +00:00
kernel: solids an SMLib boolean leaves touching come back as separate solids
The fork's booleans now hand out closed non-manifold results (SMLIB_REV e87e9b3, ABI 3): solids touching each other along an edge or at a vertex, which the C API refused as "not a closed solid". The SMLib → schema 4 mapping now gives each closed boundary its own record shell: the faces sharing one runtime shell on the empty side and one on the material side, so solids that share their empty side's runtime shell get a shell each. An edge or vertex faces of more than one shell use is split into one copy per shell (same curve, interval and point), so every shell is a closed manifold boundary; regions list each shell as before. The history follows: an Order's `pieces` give each record edge of a split SMLib edge with the faces it bounds, and each piece keeps the edge's origins. SMLib's own check refuses the radial order of faces round an edge solids share, so a non-manifold result is checked as the separate solids it becomes (imported as given). A solid touching itself along an edge (one shell using an edge more than twice) is still refused, now as "not supported yet: a solid touching itself along an edge". Tests: cubes sharing an edge or a vertex unite into two shells of 1000 mm³ each, every face and edge's history verified against the geometry; two opposite quarters cut off a slab leave two solids. Of the pinch tests run through SMLib, cut_leaving_pieces_meeting_along_a_line now passes; the other nine are solids touching themselves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
This commit is contained in:
@@ -585,6 +585,35 @@ mod smlib {
|
||||
assert_eq!(meets(h).len(), 4, "{}", show(h));
|
||||
}
|
||||
|
||||
/// Cubes sharing an edge, and cubes sharing a vertex: SMLib's union touches itself there;
|
||||
/// it comes back as two closed shells, the shared edge and vertices split into one copy per
|
||||
/// cube, each copy with its edge's history (verified against the geometry).
|
||||
#[test]
|
||||
fn cubes_touching_along_an_edge_or_at_a_vertex_unite_into_two_solids() {
|
||||
let a = boxed(0.0, 0.0, 0.0, 10.0, 10.0, 10.0);
|
||||
for (name, b) in [("edge", boxed(10.0, 10.0, 0.0, 20.0, 20.0, 10.0)), ("vertex", boxed(10.0, 10.0, 10.0, 20.0, 20.0, 20.0))] {
|
||||
let r = through_smlib(&a, &b, BoolOp::Union);
|
||||
assert_eq!(r.solid.boundaries().len(), 2, "{name}");
|
||||
assert_eq!((r.face_count(), r.edge_count()), (12, 24), "{name}");
|
||||
let v = crate::measure(&r).unwrap().volume;
|
||||
assert!((v - 2000.0).abs() < 1e-6 * 2000.0, "{name}: {v}");
|
||||
let h = verified(&r, &[&a, &b], 0.02);
|
||||
assert!(h.faces.iter().all(|f| f.origins.len() == 1), "{name}: {}", show(h));
|
||||
}
|
||||
}
|
||||
|
||||
/// Pieces of a cut meeting along a line (two opposite quarters of a slab cut away) come
|
||||
/// back as separate solids rather than a refusal.
|
||||
#[test]
|
||||
fn a_cut_leaving_pieces_along_a_line_gives_two_solids() {
|
||||
let a = boxed(0.0, 0.0, 0.0, 20.0, 20.0, 10.0);
|
||||
let once = through_smlib(&a, &boxed(10.0, -1.0, -1.0, 21.0, 10.0, 11.0), BoolOp::Cut);
|
||||
let r = through_smlib(&once, &boxed(-1.0, 10.0, -1.0, 10.0, 21.0, 11.0), BoolOp::Cut);
|
||||
assert_eq!(r.solid.boundaries().len(), 2);
|
||||
let v = crate::measure(&r).unwrap().volume;
|
||||
assert!((v - 2000.0).abs() < 1e-6 * 2000.0, "{v}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disjoint_intersection_is_empty() {
|
||||
let a = boxed(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
|
||||
|
||||
@@ -35,7 +35,18 @@ mod smlib {
|
||||
BoolOp::Intersect => BooleanOp::Intersect,
|
||||
};
|
||||
let Some(r) = ta.boolean(&tb, kind).map_err(|e| failed("operation", e))? else { return Ok(None) };
|
||||
let check = r.check(CheckLevel::All).map_err(|e| failed("check", e))?;
|
||||
// Solids touching each other along an edge or at a vertex come back as one non-manifold
|
||||
// body, which the export splits into separate solids: SMLib's check is of those, imported
|
||||
// as given (its check of the touching arrangement itself refuses the radial order of
|
||||
// faces round an edge they share).
|
||||
let manifold = r.is_manifold_solid().map_err(|e| failed("check", e))?;
|
||||
let check = if manifold {
|
||||
r.check(CheckLevel::All).map_err(|e| failed("check", e))?
|
||||
} else {
|
||||
let record = r.to_native().map_err(|e| failed("export", e))?;
|
||||
let split = solidcraft_smlib::Brep::from_record(&record, true).map_err(|e| failed("the separate solids", e))?;
|
||||
split.check(CheckLevel::All).map_err(|e| failed("check", e))?
|
||||
};
|
||||
if !check.is_valid() {
|
||||
// Whether an operand failed it already says whose the fault is.
|
||||
let operands: Vec<&str> = [("the first operand", &sa), ("the second operand", &sb)]
|
||||
|
||||
@@ -1 +1 @@
|
||||
ffcf56d195d1d0c98c5190dd993d61732f099642
|
||||
e87e9b3bdc348b00de5238963f387bfa4ba337d6
|
||||
|
||||
+157
-53
@@ -34,7 +34,7 @@ use solidcraft_geom::Vec3;
|
||||
use solidcraft_geom::brep as b;
|
||||
|
||||
use crate::flat::{self, *};
|
||||
use crate::history::Order;
|
||||
use crate::history::{Order, Piece};
|
||||
use crate::import::Arrays;
|
||||
use crate::{Brep, Result, error, ffi, init};
|
||||
|
||||
@@ -576,6 +576,7 @@ enum Loop {
|
||||
}
|
||||
|
||||
/// One edge before renumbering, along its record's parameter.
|
||||
#[derive(Clone)]
|
||||
struct Edge {
|
||||
curve: b::Curve,
|
||||
interval: [f64; 2],
|
||||
@@ -727,66 +728,59 @@ fn native_ordered(arrays: &Arrays) -> Res<(b::Brep, Report, Order)> {
|
||||
face_uses.get_mut(f).ok_or_else(|| invalid("face"))?.push((s, same));
|
||||
}
|
||||
}
|
||||
let mut shell_of: Vec<Option<u32>> = vec![None; n_shells];
|
||||
let mut shells: Vec<Vec<(u32, bool)>> = Vec::new();
|
||||
for s in 0..n_shells {
|
||||
if shell_of.get(s).copied().flatten().is_some() {
|
||||
continue;
|
||||
}
|
||||
let uses = shell_faceuses.get(s).ok_or_else(|| invalid("shell"))?;
|
||||
let (f0, _) = faceuse(uses.start).map_err(|_| invalid("empty shell"))?;
|
||||
let mate = match face_uses.get(f0).map(Vec::as_slice) {
|
||||
Some([(a, _), (b, _)]) => {
|
||||
if *a == s {
|
||||
*b
|
||||
} else {
|
||||
*a
|
||||
}
|
||||
}
|
||||
_ => return Err(invalid("a face without its two uses")),
|
||||
};
|
||||
let side = if mate == s {
|
||||
s
|
||||
} else {
|
||||
let (rs, rm) = (region_of.get(s).copied().unwrap_or(0), region_of.get(mate).copied().unwrap_or(0));
|
||||
match (material(rs), material(rm)) {
|
||||
(false, true) => s,
|
||||
(true, false) => mate,
|
||||
_ => return Err(unsupported("a shell between two material regions or two empty ones")),
|
||||
}
|
||||
};
|
||||
let mut oriented = Vec::new();
|
||||
for fu in shell_faceuses.get(side).ok_or_else(|| invalid("shell"))?.clone() {
|
||||
// Each record shell is one closed boundary between an empty region and a material one: the
|
||||
// faces with the same runtime shell on the empty side and on the material side, as the empty
|
||||
// side uses them. Solids touching along an edge or at a vertex share their empty side's
|
||||
// runtime shell but not their material side's, so each gets its own record shell (the edges
|
||||
// and vertices they share are split below). A sheet's one shell holds both uses of each
|
||||
// face: the `same` one is kept.
|
||||
let mut groups: Vec<((usize, usize), Vec<(u32, bool)>)> = Vec::new();
|
||||
for (s, uses) in shell_faceuses.iter().enumerate() {
|
||||
for fu in uses.clone() {
|
||||
let (f, same) = faceuse(fu)?;
|
||||
// A sheet lists both uses of each face in its one shell: keep the `same` one.
|
||||
if mate == s && !same {
|
||||
continue;
|
||||
}
|
||||
if !face_uses.get(f).is_some_and(|u| u.iter().any(|(o, _)| *o == mate)) {
|
||||
return Err(unsupported("a shell whose faces' other sides are in different shells"));
|
||||
}
|
||||
let key = match face_uses.get(f).map(Vec::as_slice) {
|
||||
Some([(a, _), (b, _)]) if a == b => {
|
||||
if !same {
|
||||
continue;
|
||||
}
|
||||
(*a, *a)
|
||||
}
|
||||
Some([(a, _), (b, _)]) => {
|
||||
let other = if *a == s { *b } else { *a };
|
||||
let (here, there) = (region_of.get(s).copied().unwrap_or(0), region_of.get(other).copied().unwrap_or(0));
|
||||
match (material(here), material(there)) {
|
||||
(false, true) => (s, other),
|
||||
(true, false) => continue,
|
||||
_ => return Err(unsupported("a face between two material regions or two empty ones")),
|
||||
}
|
||||
}
|
||||
_ => return Err(invalid("a face without its two uses")),
|
||||
};
|
||||
let agrees = faces.get(f).ok_or_else(|| invalid("face"))?.agrees;
|
||||
oriented.push((index(f)?, same == agrees));
|
||||
}
|
||||
let id = index(shells.len())?;
|
||||
shells.push(oriented);
|
||||
for k in [s, mate] {
|
||||
if let Some(slot) = shell_of.get_mut(k) {
|
||||
*slot = Some(id);
|
||||
}
|
||||
let at = match groups.iter().position(|(k, _)| *k == key) {
|
||||
Some(at) => at,
|
||||
None => {
|
||||
groups.push((key, Vec::new()));
|
||||
groups.len() - 1
|
||||
}
|
||||
};
|
||||
groups.get_mut(at).ok_or_else(|| invalid("shell"))?.1.push((index(f)?, same == agrees));
|
||||
}
|
||||
}
|
||||
let mut regions = Vec::with_capacity(region_shells.len());
|
||||
for (r, block) in region_shells.iter().enumerate() {
|
||||
let mut listed = Vec::new();
|
||||
for s in block.clone() {
|
||||
let id = shell_of.get(s).copied().flatten().ok_or_else(|| invalid("shell"))?;
|
||||
if !listed.contains(&id) {
|
||||
listed.push(id);
|
||||
for (g, ((empty, solid), _)) in groups.iter().enumerate() {
|
||||
let id = index(g)?;
|
||||
if (*empty == s || *solid == s) && !listed.contains(&id) {
|
||||
listed.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
regions.push(b::Region { shells: listed, void: r > 0 && !material(r) });
|
||||
}
|
||||
let shells: Vec<Vec<(u32, bool)>> = groups.into_iter().map(|(_, faces)| faces).collect();
|
||||
// Shells renumbered in first appearance walking the regions.
|
||||
let mut order: Vec<u32> = Vec::new();
|
||||
for r in ®ions {
|
||||
@@ -818,7 +812,10 @@ struct Parts {
|
||||
/// the edges and then the vertex loops, with each face's pcurves; validated. With it, the
|
||||
/// canonical index of each array face and edge.
|
||||
fn canonical(parts: Parts, n_vertices: usize, flat: View, tolerance: f64) -> Res<(b::Brep, Order)> {
|
||||
let Parts { faces, edges, shells, regions } = parts;
|
||||
let Parts { mut faces, mut edges, shells, regions } = parts;
|
||||
let n_edges = edges.len();
|
||||
let (edge_source, vertex_source) = split_shared(&mut faces, &mut edges, &shells, n_vertices)?;
|
||||
let n_vertices = vertex_source.len();
|
||||
let mut face_new: Vec<Option<u32>> = vec![None; faces.len()];
|
||||
let (mut face_order, mut direction) = (Vec::new(), Vec::new());
|
||||
for shell in &shells {
|
||||
@@ -865,7 +862,7 @@ fn canonical(parts: Parts, n_vertices: usize, flat: View, tolerance: f64) -> Res
|
||||
return Err(unsupported("wire edges from SMLib"));
|
||||
}
|
||||
if edge_uses.iter().any(|u| u.len() > 2) {
|
||||
return Err(unsupported("the radial order of an edge with more than two uses"));
|
||||
return Err(unsupported("a solid touching itself along an edge"));
|
||||
}
|
||||
let mut vertex_new: Vec<Option<u32>> = vec![None; n_vertices];
|
||||
let mut vertex_order = Vec::new();
|
||||
@@ -934,7 +931,13 @@ fn canonical(parts: Parts, n_vertices: usize, flat: View, tolerance: f64) -> Res
|
||||
face_records.push(b::Face { surface: face.surface.clone(), domain: face.domain, loops });
|
||||
}
|
||||
let positions = flat.f(VERTEX_POSITION);
|
||||
let vertices = vertex_order.iter().map(|&v| Ok(b::Vertex { point: v3(positions, v as usize, "vertex")?, tolerance })).collect::<Res<Vec<_>>>()?;
|
||||
let vertices = vertex_order
|
||||
.iter()
|
||||
.map(|&v| {
|
||||
let at = vertex_source.get(v as usize).copied().ok_or_else(|| invalid("vertex"))?;
|
||||
Ok(b::Vertex { point: v3(positions, at, "vertex")?, tolerance })
|
||||
})
|
||||
.collect::<Res<Vec<_>>>()?;
|
||||
let shells = shells
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
@@ -952,10 +955,111 @@ fn canonical(parts: Parts, n_vertices: usize, flat: View, tolerance: f64) -> Res
|
||||
let placed = |new: &[Option<u32>]| {
|
||||
new.iter().map(|i| i.map(|i| i as usize).ok_or_else(|| invalid("an entity outside the canonical walk"))).collect::<Res<Vec<_>>>()
|
||||
};
|
||||
let order = Order { faces: placed(&face_new)?, edges: placed(&edge_new)? };
|
||||
let edge_new = placed(&edge_new)?;
|
||||
// SMLib's edges split where solids touch: every piece, with the faces it bounds.
|
||||
let mut pieces = Vec::new();
|
||||
for (e, &canonical) in edge_new.iter().enumerate() {
|
||||
let edge = edge_source.get(e).copied().ok_or_else(|| invalid("edge"))?;
|
||||
if edge_source.iter().filter(|s| **s == edge).count() > 1 {
|
||||
let record = brep.edges.get(canonical).ok_or_else(|| invalid("edge"))?;
|
||||
let faces: std::collections::BTreeSet<usize> = record.uses.iter().map(|u| u.face as usize).collect();
|
||||
pieces.push(Piece { edge, canonical, faces: faces.into_iter().collect() });
|
||||
}
|
||||
}
|
||||
pieces.sort_by_key(|p| (p.edge, p.canonical));
|
||||
let mut edges_out = Vec::with_capacity(n_edges);
|
||||
for e in 0..n_edges {
|
||||
edges_out
|
||||
.push(pieces.iter().find(|p| p.edge == e).map_or_else(|| edge_new.get(e).copied().ok_or_else(|| invalid("edge")), |p| Ok(p.canonical))?);
|
||||
}
|
||||
let order = Order { faces: placed(&face_new)?, edges: edges_out, pieces };
|
||||
Ok((brep, order))
|
||||
}
|
||||
|
||||
/// Edges and vertices that solids touching each other share (faces of more than one shell use
|
||||
/// them), split into one copy per shell, so each shell is a closed manifold boundary of its own.
|
||||
/// The geometry is unchanged. Returns, for every edge and vertex after the split, the array edge
|
||||
/// and vertex it is (a copy's original).
|
||||
fn split_shared(faces: &mut [Face], edges: &mut Vec<Edge>, shells: &[Vec<(u32, bool)>], n_vertices: usize) -> Res<(Vec<usize>, Vec<usize>)> {
|
||||
let mut shell_of = vec![None; faces.len()];
|
||||
for (k, shell) in shells.iter().enumerate() {
|
||||
for &(f, _) in shell {
|
||||
*shell_of.get_mut(f as usize).ok_or_else(|| invalid("face"))? = Some(k);
|
||||
}
|
||||
}
|
||||
let mut edge_source: Vec<usize> = (0..edges.len()).collect();
|
||||
// Edges: the first shell to use one keeps it, every other gets a copy.
|
||||
let mut edge_shell: Vec<Option<usize>> = vec![None; edges.len()];
|
||||
let mut copies: HashMap<(u32, usize), u32> = HashMap::new();
|
||||
for (f, face) in faces.iter_mut().enumerate() {
|
||||
let Some(k) = shell_of.get(f).copied().flatten() else { continue };
|
||||
for l in &mut face.loops {
|
||||
let Loop::Edges(uses) = l else { continue };
|
||||
for (e, _) in uses.iter_mut() {
|
||||
let slot = edge_shell.get_mut(*e as usize).ok_or_else(|| invalid("edge"))?;
|
||||
match slot {
|
||||
None => *slot = Some(k),
|
||||
Some(owner) if *owner == k => {}
|
||||
Some(_) => {
|
||||
let copy = match copies.get(&(*e, k)) {
|
||||
Some(c) => *c,
|
||||
None => {
|
||||
let c = index(edges.len())?;
|
||||
edges.push(edges.get(*e as usize).ok_or_else(|| invalid("edge"))?.clone());
|
||||
edge_source.push(*e as usize);
|
||||
edge_shell.push(Some(k));
|
||||
copies.insert((*e, k), c);
|
||||
c
|
||||
}
|
||||
};
|
||||
*e = copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Vertices: likewise, through the edges (each now in one shell) and the vertex loops.
|
||||
let mut vertex_source: Vec<usize> = (0..n_vertices).collect();
|
||||
let mut vertex_shell: Vec<Option<usize>> = vec![None; n_vertices];
|
||||
let mut vertex_copies: HashMap<(u32, usize), u32> = HashMap::new();
|
||||
let mut place = |v: &mut u32, k: usize| -> Res<()> {
|
||||
let slot = vertex_shell.get_mut(*v as usize).ok_or_else(|| invalid("vertex"))?;
|
||||
match slot {
|
||||
None => *slot = Some(k),
|
||||
Some(owner) if *owner == k => {}
|
||||
Some(_) => {
|
||||
let copy = match vertex_copies.get(&(*v, k)) {
|
||||
Some(c) => *c,
|
||||
None => {
|
||||
let c = index(vertex_source.len())?;
|
||||
vertex_source.push(*v as usize);
|
||||
vertex_shell.push(Some(k));
|
||||
vertex_copies.insert((*v, k), c);
|
||||
c
|
||||
}
|
||||
};
|
||||
*v = copy;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
for (e, edge) in edges.iter_mut().enumerate() {
|
||||
let Some(k) = edge_shell.get(e).copied().flatten() else { continue };
|
||||
for v in &mut edge.vertices {
|
||||
place(v, k)?;
|
||||
}
|
||||
}
|
||||
for (f, face) in faces.iter_mut().enumerate() {
|
||||
let Some(k) = shell_of.get(f).copied().flatten() else { continue };
|
||||
for l in &mut face.loops {
|
||||
if let Loop::Vertex(v) = l {
|
||||
place(v, k)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((edge_source, vertex_source))
|
||||
}
|
||||
|
||||
/// A face's pcurves, edge loop by edge loop, from the plain-record routine on the face's
|
||||
/// records, each loop moved by whole periods into the face's domain.
|
||||
fn pcurves(surface: &b::Surface, domain: [[f64; 2]; 2], loops: &[Loop], edges: &[b::Edge], tolerance: f64) -> Res<Vec<Vec<b::Curve2>>> {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use std::ffi::{c_char, c_void};
|
||||
|
||||
/// `SMLIB_ABI_VERSION` this crate was written against.
|
||||
pub const ABI_VERSION: i32 = 2;
|
||||
pub const ABI_VERSION: i32 = 3;
|
||||
|
||||
pub const OK: i32 = 0;
|
||||
pub const ERR_EXCEPTION: i32 = -1000;
|
||||
|
||||
+48
-10
@@ -41,10 +41,26 @@ pub struct Raw {
|
||||
|
||||
/// Where one body's faces and edges go in canonical order: `faces[i]` is the canonical index of
|
||||
/// SMLib's face i, likewise for edges. Empty means SMLib's order is the canonical one.
|
||||
///
|
||||
/// An SMLib edge solids touch along (non-manifold) becomes one record edge per solid:
|
||||
/// `edges[i]` is the first of them, and `pieces` lists every piece of such an edge, the first
|
||||
/// too, with the canonical faces it bounds.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Order {
|
||||
pub faces: Vec<usize>,
|
||||
pub edges: Vec<usize>,
|
||||
pub pieces: Vec<Piece>,
|
||||
}
|
||||
|
||||
/// One record edge of an SMLib edge split where solids touch along it.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Piece {
|
||||
/// The SMLib edge.
|
||||
pub edge: usize,
|
||||
/// The record edge.
|
||||
pub canonical: usize,
|
||||
/// The canonical faces it bounds, ascending.
|
||||
pub faces: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Brep {
|
||||
@@ -122,7 +138,23 @@ impl Raw {
|
||||
}
|
||||
Ok(at)
|
||||
};
|
||||
let (fat, eat) = (place(&result.faces, self.faces.len())?, place(&result.edges, self.edges.len())?);
|
||||
let fat = place(&result.faces, self.faces.len())?;
|
||||
// Each raw edge's record edges with the faces each bounds where it is split (`None`: as
|
||||
// SMLib has them); together a permutation of the result's edges.
|
||||
let n_edges = self.edges.len() + result.pieces.len() - result.pieces.iter().map(|p| p.edge).collect::<BTreeSet<_>>().len();
|
||||
let mut targets: Vec<(usize, usize, Option<&[usize]>)> = Vec::with_capacity(n_edges);
|
||||
for i in 0..self.edges.len() {
|
||||
let mut split = result.pieces.iter().filter(|p| p.edge == i).peekable();
|
||||
if split.peek().is_none() {
|
||||
targets.push((i, map(&result.edges, i)?, None));
|
||||
} else {
|
||||
targets.extend(split.map(|p| (i, p.canonical, Some(p.faces.as_slice()))));
|
||||
}
|
||||
}
|
||||
let distinct: BTreeSet<usize> = targets.iter().map(|t| t.1).collect();
|
||||
if targets.len() != n_edges || distinct.len() != n_edges || targets.iter().any(|t| t.1 >= n_edges) {
|
||||
return Err(bad("the result's order is no permutation"));
|
||||
}
|
||||
|
||||
let mut faces: Vec<Option<FaceProv>> = vec![None; self.faces.len()];
|
||||
for (f, at) in self.faces.iter().zip(&fat) {
|
||||
@@ -131,14 +163,20 @@ impl Raw {
|
||||
}
|
||||
let faces: Vec<FaceProv> = faces.into_iter().collect::<Option<_>>().ok_or(bad("the result's order is no permutation"))?;
|
||||
|
||||
let mut edges: Vec<Option<EdgeProv>> = vec![None; self.edges.len()];
|
||||
for (e, at) in self.edges.iter().zip(&eat) {
|
||||
let bounds: BTreeSet<usize> = e
|
||||
.faces
|
||||
.iter()
|
||||
.map(|f| fat.get(*f).copied().ok_or(bad("an edge lies on a face outside the result")))
|
||||
.collect::<std::result::Result<_, _>>()?;
|
||||
let bounds: Vec<usize> = bounds.into_iter().collect();
|
||||
let mut edges: Vec<Option<EdgeProv>> = vec![None; n_edges];
|
||||
for &(i, at, split) in &targets {
|
||||
let e = self.edges.get(i).ok_or(bad("an edge lies outside the result"))?;
|
||||
let bounds: Vec<usize> = match split {
|
||||
Some(faces) => faces.to_vec(),
|
||||
None => {
|
||||
let bounds: BTreeSet<usize> = e
|
||||
.faces
|
||||
.iter()
|
||||
.map(|f| fat.get(*f).copied().ok_or(bad("an edge lies on a face outside the result")))
|
||||
.collect::<std::result::Result<_, _>>()?;
|
||||
bounds.into_iter().collect()
|
||||
}
|
||||
};
|
||||
let origins = if e.origins.is_empty() {
|
||||
// New: where its two faces meet, each side what that face came from.
|
||||
let side = |f: usize| faces.get(f).and_then(|p| Side::new(p.origins.iter().copied()));
|
||||
@@ -157,7 +195,7 @@ impl Raw {
|
||||
.collect::<std::result::Result<_, Failed>>()?;
|
||||
set.into_iter().collect()
|
||||
};
|
||||
edges[*at] = Some(EdgeProv { faces: bounds, origins });
|
||||
*edges.get_mut(at).ok_or(bad("the result's order is no permutation"))? = Some(EdgeProv { faces: bounds, origins });
|
||||
}
|
||||
let edges: Vec<EdgeProv> = edges.into_iter().collect::<Option<_>>().ok_or(bad("the result's order is no permutation"))?;
|
||||
Ok(History { operands: operands.len(), faces, edges })
|
||||
|
||||
@@ -60,9 +60,9 @@ fn renumbers_into_canonical_order() {
|
||||
let raw = Raw::decode(RECORDS).unwrap();
|
||||
// Operand 0's SMLib face 2 is its canonical face 5, edge 7 its edge 0; the result's faces
|
||||
// and edges are listed backwards.
|
||||
let a = Order { faces: vec![1, 2, 5, 0, 3, 4], edges: vec![1, 2, 3, 4, 5, 6, 7, 0] };
|
||||
let b = Order { faces: vec![0, 1, 2, 3, 4], edges: vec![0, 1, 2, 3] };
|
||||
let result = Order { faces: vec![2, 1, 0], edges: vec![2, 1, 0] };
|
||||
let a = Order { faces: vec![1, 2, 5, 0, 3, 4], edges: vec![1, 2, 3, 4, 5, 6, 7, 0], pieces: vec![] };
|
||||
let b = Order { faces: vec![0, 1, 2, 3, 4], edges: vec![0, 1, 2, 3], pieces: vec![] };
|
||||
let result = Order { faces: vec![2, 1, 0], edges: vec![2, 1, 0], pieces: vec![] };
|
||||
let h = raw.history(&[a, b], &result).unwrap();
|
||||
assert_eq!(h.faces[2].origins, vec![face(0, 5)]);
|
||||
assert_eq!(h.faces[0].origins, vec![face(0, 1), face(1, 0)]);
|
||||
@@ -79,10 +79,10 @@ fn bad_orders_are_errors() {
|
||||
// An origin on an operand that isn't there.
|
||||
assert!(raw.history(std::slice::from_ref(&ok), &ok).is_err());
|
||||
// An operand order too short for an origin.
|
||||
assert!(raw.history(&[Order { faces: vec![0], edges: vec![] }, ok.clone()], &ok).is_err());
|
||||
assert!(raw.history(&[Order { faces: vec![0], edges: vec![], pieces: vec![] }, ok.clone()], &ok).is_err());
|
||||
// A result order that isn't a permutation.
|
||||
assert!(raw.history(&[ok.clone(), ok.clone()], &Order { faces: vec![0, 0, 1], edges: vec![] }).is_err());
|
||||
assert!(raw.history(&[ok.clone(), ok.clone()], &Order { faces: vec![0, 1, 3], edges: vec![] }).is_err());
|
||||
assert!(raw.history(&[ok.clone(), ok.clone()], &Order { faces: vec![0, 0, 1], edges: vec![], pieces: vec![] }).is_err());
|
||||
assert!(raw.history(&[ok.clone(), ok.clone()], &Order { faces: vec![0, 1, 3], edges: vec![], pieces: vec![] }).is_err());
|
||||
}
|
||||
|
||||
/// A box with its edge 0 (between faces 4 and 5) filleted, as SMLib reports it: the fillet face
|
||||
|
||||
Reference in New Issue
Block a user