engine: geometry.faces.merge, faces meshed as one (virtual topology, #505)

geometry.faces.merge {name, faces} records a face merge in the model;
it is applied after the partitions whenever the design is opened or cut
again, undoes through the journal and is saved with the model. Meshers,
mesh.plan (features and rings see merge:<name>; a merged face is
prismatic when its B-rep faces are), checks and the viewport see one
face, drawn in the warm colour. geometry.faces.merge.delete removes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-16 11:09:11 -04:00
1 parent 22d709e604
commit ce02d263c7
17 files changed
+464 -22

No files matched your search

+9 -4
View File
@@ -113,14 +113,19 @@ fn canonical(d: V3) -> Option<V3> {
const AXES: [V3; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
/// Whether the faces `faces` (persistent names on `shape`) are all prismatic along `d`: planes
/// along or across it, cylinders about an axis along it.
/// along or across it, cylinders about an axis along it; a merged face on no one surface, when
/// each of its B-rep faces is.
fn faces_prismatic(shape: &Shape, surfaces: &[Option<FaceSurface>], faces: &[String], d: V3) -> bool {
let prismatic = |s: Option<FaceSurface>| match s {
Some(FaceSurface::Plane { normal, .. }) => parallel(normal, d) || perpendicular(normal, d),
Some(FaceSurface::Cylinder { axis, .. }) => parallel(axis, d),
_ => false,
};
faces.iter().all(|name| {
let Some(i) = shape.face_index(name) else { return true };
match surfaces.get(i as usize).copied().flatten() {
Some(FaceSurface::Plane { normal, .. }) => parallel(normal, d) || perpendicular(normal, d),
Some(FaceSurface::Cylinder { axis, .. }) => parallel(axis, d),
_ => false,
Some(s) => prismatic(Some(s)),
None => shape.member_surfaces(i).is_ok_and(|m| m.len() > 1 && m.into_iter().all(prismatic)),
}
})
}