forked from 1b-agent/opencadstudio-upstream
docs(3d): explain pierced-face outer-loop selection
ACIS stores no outer-vs-hole loop flag, so plane_face derives the outer boundary as the largest-area loop and winds holes opposite. Document why this is robust: try_attach_plane takes its plane normal from the first (outer) wire, making the choice self-consistent without relying on the analytic face normal. No behaviour change. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
249922e8d9
commit
3139a73753
1 file changed
+11
-6
@@ -198,9 +198,11 @@ fn build_face_group(
|
||||
/// boundary edges (circles) are sampled into line segments, which keeps the
|
||||
/// wire planar so `try_attach_plane` can fit the plane.
|
||||
fn plane_face(sat: &SatDocument, face: &SatFace) -> Option<Face> {
|
||||
// Outer boundary first, then inner hole loops — `try_attach_plane` fits the
|
||||
// plane from the outer wire and cuts the rest as holes, so a pierced face
|
||||
// (e.g. a wall with a window opening) renders with the opening. (#123)
|
||||
// A pierced face (e.g. a wall with a window opening) has one outer boundary
|
||||
// loop plus an inner loop per hole. `try_attach_plane` fits the plane from
|
||||
// the first wire and cuts the rest as holes, letting truck's mesher trim
|
||||
// them. ACIS records no outer-vs-hole flag — the kernel classifies loops at
|
||||
// runtime from geometry — so we derive the outer boundary here. (#123)
|
||||
let loops = collect_face_loops(sat, face, BOUNDARY_SEGS);
|
||||
if loops.is_empty() {
|
||||
return None;
|
||||
@@ -239,9 +241,12 @@ fn plane_face(sat: &SatDocument, face: &SatFace) -> Option<Face> {
|
||||
if loops[outer_idx].len() < 3 {
|
||||
return None;
|
||||
}
|
||||
// `try_attach_plane` only cuts an inner wire as a hole when it winds the
|
||||
// *opposite* way to the outer boundary. ACIS doesn't guarantee that, so
|
||||
// reverse any hole loop whose winding matches the outer's. (#123)
|
||||
// `try_attach_plane` fits its plane normal from the first (outer) wire, so
|
||||
// feeding the outer in its sampled order makes truck read it as CCW. truck
|
||||
// then cuts an inner wire as a hole only when it winds the *opposite* way,
|
||||
// so reverse any hole loop whose area normal agrees with the outer's. The
|
||||
// outer normal has the largest magnitude (largest area), keeping the sign
|
||||
// test robust against sampling noise on small holes. (#123)
|
||||
let outer_n = loop_normal(&loops[outer_idx]);
|
||||
let mut wires: Vec<Wire> = Vec::new();
|
||||
wires.push(build_wire(&loops[outer_idx], false)?);
|
||||
|
||||
Reference in new issue
Block a user