Project faces split by hidden seams as a whole (#352)

Projecting a cylinder's wall or the face of a round along a circular edge
failed with "face projection has an open or invalid boundary": these
surfaces are stored in halves joined by hidden seams, and the projection
dropped the seams but kept only the picked half. It now projects the whole
patch, the face as drawn, through the same surface_patch the viewport uses
to highlight it.
This commit is contained in:
Pablo López authored and GitHub committed 2026-10-12 14:29:08 -04:00
1 parent 2ebceb6918
commit 511d105dcb
2 files changed
+33

No files matched your search

+6
View File
@@ -9,7 +9,13 @@ use crate::{DocError, Result};
type Key = (i64, i64, i64);
/// Boundary of the picked face as drawn: a closed surface cut into pieces (a cylinder or a
/// round's torus in halves) projects as the whole patch, since its seams are hidden.
pub(super) fn face_edges(mesh: &Mesh, face: u32, plane: &Plane) -> Result<Vec<usize>> {
let patch = mesh.surface_patch(face as usize);
if patch.len() > 1 {
return family_edges(mesh, &patch.into_iter().collect(), plane);
}
let edges = mesh.face_edges(face).into_iter().filter(|e| !mesh.seams.get(*e).copied().unwrap_or(false)).collect();
boundary_edges(mesh, edges, plane)
}
@@ -499,3 +499,30 @@ fn project_and_include_from_other_bodies() {
assert!(row["error"].is_null(), "{row}");
assert!(row["warning"].as_str().is_some_and(|w| w.contains("lost")), "{row}");
}
/// A cylinder's wall and the torus of a rounded rim are drawn as one face but stored in halves
/// joined by hidden seams; projecting one projects the whole face, not an open half.
#[test]
fn faces_split_by_hidden_seams_project_whole() {
let mut s = Session::default();
run(&mut s, "sketch.create", json!({"plane":"XY"}));
run(&mut s, "sketch.circle.center", json!({"center":[0,0],"radius":20}));
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.extrude", json!({"distance":10}));
run(&mut s, "sketch.create", json!({"plane":"XZ"}));
run(&mut s, "sketch.project", json!({"refs":[{"face":[20,0,5]}]}));
let wall = run(&mut s, "sketch.inspect", json!({}));
let heights: Vec<f64> =
wall["curves"].as_array().unwrap().iter().flat_map(|c| [c["start_at"][1].as_f64().unwrap(), c["end_at"][1].as_f64().unwrap()]).collect();
assert!(heights.iter().any(|y| y.abs() < 1e-6) && heights.iter().any(|y| (y.abs() - 10.).abs() < 1e-6), "{wall}");
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.fillet", json!({"edges":[[20,0,10]],"radius":3}));
run(&mut s, "sketch.create", json!({"plane":"XY"}));
let t = 0.5f64.sqrt() * 3.;
run(&mut s, "sketch.project", json!({"refs":[{"face":[17. + t, 0, 7. + t]}]}));
let round = run(&mut s, "sketch.inspect", json!({}));
let mut radii: Vec<f64> = round["curves"].as_array().unwrap().iter().map(|c| c["radius"].as_f64().unwrap()).collect();
radii.sort_by(f64::total_cmp);
assert_eq!(radii.len(), 4, "{round}");
assert!(radii[..2].iter().all(|r| (r - 17.).abs() < 1e-6) && radii[2..].iter().all(|r| (r - 20.).abs() < 1e-6), "{round}");
}