split_faces: faces chosen by position stay chosen when a neighbour's split cuts their edges
A face whose edges were cut by an earlier split got a new identity and was skipped. Chosen faces are now tracked by position through each split. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
f8a4ffbc67
commit
69d7bcfe7d
1 file changed
+58
-45
@@ -416,11 +416,12 @@ fn inside(poly: &[(f64, f64)], q: (f64, f64)) -> bool {
|
|||||||
|
|
||||||
/// Split one face (index `fi` in `faces`) by one field; `faces` is updated (the face replaced
|
/// Split one face (index `fi` in `faces`) by one field; `faces` is updated (the face replaced
|
||||||
/// by its pieces, boundary edges cut in every face).
|
/// by its pieces, boundary edges cut in every face).
|
||||||
fn split_one(faces: &mut Vec<mt::Face>, fi: usize, g: &Field, size: f64) -> Result<usize> {
|
/// Returns how many lines split it and how many faces now stand in its place (at `fi`).
|
||||||
let Some(face) = faces.get(fi).cloned() else { return Ok(0) };
|
fn split_one(faces: &mut Vec<mt::Face>, fi: usize, g: &Field, size: f64, new_edges: &mut Vec<mt::Edge>) -> Result<(usize, usize)> {
|
||||||
|
let Some(face) = faces.get(fi).cloned() else { return Ok((0, 1)) };
|
||||||
let chains = trace(&face, g, size)?;
|
let chains = trace(&face, g, size)?;
|
||||||
if chains.is_empty() {
|
if chains.is_empty() {
|
||||||
return Ok(0);
|
return Ok((0, 1));
|
||||||
}
|
}
|
||||||
let tol = size * 1e-7 + 1e-9;
|
let tol = size * 1e-7 + 1e-9;
|
||||||
let snap = size * 1e-3;
|
let snap = size * 1e-3;
|
||||||
@@ -440,6 +441,7 @@ fn split_one(faces: &mut Vec<mt::Face>, fi: usize, g: &Field, size: f64) -> Resu
|
|||||||
let Some(&(p0, uv0)) = ch.pts.first() else { continue };
|
let Some(&(p0, uv0)) = ch.pts.first() else { continue };
|
||||||
let v = mt::Vertex::new(p0);
|
let v = mt::Vertex::new(p0);
|
||||||
let e = mt::Edge::new_unchecked(&v, &v, curve);
|
let e = mt::Edge::new_unchecked(&v, &v, curve);
|
||||||
|
new_edges.push(e.clone());
|
||||||
let uv: Vec<(f64, f64)> = ch.pts.iter().map(|x| x.1).collect();
|
let uv: Vec<(f64, f64)> = ch.pts.iter().map(|x| x.1).collect();
|
||||||
let area: f64 = (0..uv.len())
|
let area: f64 = (0..uv.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
@@ -532,6 +534,7 @@ fn split_one(faces: &mut Vec<mt::Face>, fi: usize, g: &Field, size: f64) -> Resu
|
|||||||
};
|
};
|
||||||
let (Some(w1), Some(w2)) = (ws.get(wa_i), ws.get(wb_i)) else { continue };
|
let (Some(w1), Some(w2)) = (ws.get(wa_i), ws.get(wb_i)) else { continue };
|
||||||
let slit = mt::Edge::new_unchecked(&va, &vb, fit(&pts, size)?);
|
let slit = mt::Edge::new_unchecked(&va, &vb, fit(&pts, size)?);
|
||||||
|
new_edges.push(slit.clone());
|
||||||
let mut joined = from(w1, &va);
|
let mut joined = from(w1, &va);
|
||||||
joined.push(slit.clone());
|
joined.push(slit.clone());
|
||||||
joined.extend(from(w2, &vb));
|
joined.extend(from(w2, &vb));
|
||||||
@@ -572,6 +575,7 @@ fn split_one(faces: &mut Vec<mt::Face>, fi: usize, g: &Field, size: f64) -> Resu
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let split = mt::Edge::new_unchecked(&va, &vb, fit(&pts, size)?);
|
let split = mt::Edge::new_unchecked(&va, &vb, fit(&pts, size)?);
|
||||||
|
new_edges.push(split.clone());
|
||||||
let run = |from: usize, to: usize| -> Vec<mt::Edge> {
|
let run = |from: usize, to: usize| -> Vec<mt::Edge> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let mut i = from;
|
let mut i = from;
|
||||||
@@ -612,8 +616,9 @@ fn split_one(faces: &mut Vec<mt::Face>, fi: usize, g: &Field, size: f64) -> Resu
|
|||||||
made += 1;
|
made += 1;
|
||||||
}
|
}
|
||||||
if made == 0 {
|
if made == 0 {
|
||||||
return Ok(0);
|
return Ok((0, 1));
|
||||||
}
|
}
|
||||||
|
let count = pieces.len();
|
||||||
// Neighbours take the cut edges.
|
// Neighbours take the cut edges.
|
||||||
let rest: Vec<mt::Face> = faces.iter().enumerate().filter(|(k, _)| *k != fi).map(|(_, f)| recut(f, &all_cuts)).collect();
|
let rest: Vec<mt::Face> = faces.iter().enumerate().filter(|(k, _)| *k != fi).map(|(_, f)| recut(f, &all_cuts)).collect();
|
||||||
let mut out = Vec::with_capacity(rest.len() + pieces.len());
|
let mut out = Vec::with_capacity(rest.len() + pieces.len());
|
||||||
@@ -621,19 +626,12 @@ fn split_one(faces: &mut Vec<mt::Face>, fi: usize, g: &Field, size: f64) -> Resu
|
|||||||
out.extend(pieces);
|
out.extend(pieces);
|
||||||
out.extend(rest.iter().skip(fi).cloned());
|
out.extend(rest.iter().skip(fi).cloned());
|
||||||
*faces = out;
|
*faces = out;
|
||||||
Ok(made)
|
Ok((made, count))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split the given faces (indices in `body`'s face order) where the tool meets them. The solid
|
fn fields(tool: &SplitTool) -> Result<Vec<Field>> {
|
||||||
/// is unchanged; faces the tool misses stay whole.
|
Ok(match tool {
|
||||||
pub fn split_faces(body: &Body, faces: &[usize], tool: &SplitTool) -> Result<Body> {
|
SplitTool::Plane(p) => vec![Field::Plane { o: p.origin, n: p.normal() }],
|
||||||
body.require_brep("split face")?;
|
|
||||||
let size = body.size();
|
|
||||||
let fields: Vec<Field> = match tool {
|
|
||||||
SplitTool::Plane(p) => {
|
|
||||||
let n = p.normal();
|
|
||||||
vec![Field::Plane { o: p.origin, n }]
|
|
||||||
}
|
|
||||||
SplitTool::Face { body: other, face } => {
|
SplitTool::Face { body: other, face } => {
|
||||||
other.require_brep("a split tool")?;
|
other.require_brep("a split tool")?;
|
||||||
let f = other.solid.face_iter().nth(*face).ok_or_else(|| KernelError::Invalid(format!("the tool body has no face {face}")))?;
|
let f = other.solid.face_iter().nth(*face).ok_or_else(|| KernelError::Invalid(format!("the tool body has no face {face}")))?;
|
||||||
@@ -645,44 +643,59 @@ pub fn split_faces(body: &Body, faces: &[usize], tool: &SplitTool) -> Result<Bod
|
|||||||
}
|
}
|
||||||
curves.iter().map(|c| Field::Curve { plane: *plane, pts: c.clone() }).collect()
|
curves.iter().map(|c| Field::Curve { plane: *plane, pts: c.clone() }).collect()
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The body's shells with the chosen faces split by each field in turn, the new edges, and
|
||||||
|
/// how many lines split something.
|
||||||
|
fn imprint(body: &Body, faces: &[usize], fields: &[Field], size: f64) -> Result<(Vec<Vec<mt::Face>>, Vec<mt::Edge>, usize)> {
|
||||||
|
let mut out_shells = Vec::new();
|
||||||
|
let mut new_edges = Vec::new();
|
||||||
|
let mut base = 0;
|
||||||
|
let mut made = 0;
|
||||||
|
for sh in body.solid.boundaries() {
|
||||||
|
let mut fs: Vec<mt::Face> = sh.face_iter().cloned().collect();
|
||||||
|
// Which faces (by position) are to be split: the chosen ones and, later, their pieces.
|
||||||
|
let mut target: Vec<bool> = (0..fs.len()).map(|k| faces.contains(&(base + k))).collect();
|
||||||
|
base += fs.len();
|
||||||
|
for g in fields {
|
||||||
|
let mut k = 0;
|
||||||
|
while k < fs.len() {
|
||||||
|
if !target.get(k).copied().unwrap_or(false) {
|
||||||
|
k += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (m, pieces) = split_one(&mut fs, k, g, size, &mut new_edges)?;
|
||||||
|
made += m;
|
||||||
|
// The pieces stand at k..k + pieces; positions after shift.
|
||||||
|
let mut t: Vec<bool> = target.iter().take(k).copied().collect();
|
||||||
|
t.extend(std::iter::repeat_n(true, pieces));
|
||||||
|
t.extend(target.iter().skip(k + 1).copied());
|
||||||
|
target = t;
|
||||||
|
k += pieces;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out_shells.push(fs);
|
||||||
|
}
|
||||||
|
Ok((out_shells, new_edges, made))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split the given faces (indices in `body`'s face order) where the tool meets them. The solid
|
||||||
|
/// is unchanged; faces the tool misses stay whole.
|
||||||
|
pub fn split_faces(body: &Body, faces: &[usize], tool: &SplitTool) -> Result<Body> {
|
||||||
|
body.require_brep("split face")?;
|
||||||
|
let size = body.size();
|
||||||
|
let fields = fields(tool)?;
|
||||||
let total = body.solid.face_iter().count();
|
let total = body.solid.face_iter().count();
|
||||||
if faces.iter().any(|f| *f >= total) {
|
if faces.iter().any(|f| *f >= total) {
|
||||||
return Err(KernelError::Invalid("no such face".into()));
|
return Err(KernelError::Invalid("no such face".into()));
|
||||||
}
|
}
|
||||||
guard("split face", || {
|
guard("split face", || {
|
||||||
let shells: Vec<mt::Shell> = body.solid.boundaries().clone();
|
let (shells, _, made) = imprint(body, faces, &fields, size)?;
|
||||||
// Faces to split, by shell, tracked as faces are replaced by pieces.
|
|
||||||
let mut out_shells = Vec::new();
|
|
||||||
let mut base = 0;
|
|
||||||
let mut made = 0;
|
|
||||||
for sh in shells {
|
|
||||||
let mut fs: Vec<mt::Face> = sh.face_iter().cloned().collect();
|
|
||||||
let mut targets: Vec<mt::FaceID> =
|
|
||||||
faces.iter().filter(|f| **f >= base && **f < base + fs.len()).filter_map(|f| fs.get(*f - base).map(mt::Face::id)).collect();
|
|
||||||
base += fs.len();
|
|
||||||
for g in &fields {
|
|
||||||
let mut next_targets = Vec::new();
|
|
||||||
for id in targets.clone() {
|
|
||||||
let Some(fi) = fs.iter().position(|f| f.id() == id) else { continue };
|
|
||||||
let before: Vec<mt::FaceID> = fs.iter().map(mt::Face::id).collect();
|
|
||||||
made += split_one(&mut fs, fi, g, size)?;
|
|
||||||
// The face and any pieces of it stay targets for the next curve.
|
|
||||||
for f in &fs {
|
|
||||||
if !before.contains(&f.id()) || f.id() == id {
|
|
||||||
next_targets.push(f.id());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Recut neighbours change ids too: keep pieces only.
|
|
||||||
targets = next_targets;
|
|
||||||
}
|
|
||||||
out_shells.push(mt::Shell::from(fs));
|
|
||||||
}
|
|
||||||
if made == 0 {
|
if made == 0 {
|
||||||
return Err(KernelError::Invalid("the tool does not cross the selected faces".into()));
|
return Err(KernelError::Invalid("the tool does not cross the selected faces".into()));
|
||||||
}
|
}
|
||||||
let solid = mt::Solid::new_unchecked(out_shells);
|
let solid = mt::Solid::new_unchecked(shells.into_iter().map(mt::Shell::from).collect());
|
||||||
Ok(Body { solid: std::sync::Arc::new(solid), mesh: None, color: body.color, paint: None })
|
Ok(Body { solid: std::sync::Arc::new(solid), mesh: None, color: body.color, paint: None })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user