forked from 1b-agent/opencadstudio-upstream
fix(3d): pick pierced face outer loop by area, not list order
A planar ACIS face lists its loops outer-first only by convention; a pierced wall lists its window holes before the wall edge. plane_face trusted loops[0] as the outer boundary and oriented every other loop relative to it, so on a face with two or more holes one real hole wound the same way as the true outer and truck's winding-number trim filled it in. Select the outer boundary as the largest-area loop (it encloses the rest) and wind all others opposite. (#123) Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
36d5891572
commit
249922e8d9
1 file changed
+21
-7
@@ -202,7 +202,7 @@ fn plane_face(sat: &SatDocument, face: &SatFace) -> Option<Face> {
|
||||
// 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)
|
||||
let loops = collect_face_loops(sat, face, BOUNDARY_SEGS);
|
||||
if loops.first().map_or(true, |l| l.len() < 3) {
|
||||
if loops.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let build_wire = |pts: &[[f64; 3]], reverse: bool| -> Option<Wire> {
|
||||
@@ -225,14 +225,30 @@ fn plane_face(sat: &SatDocument, face: &SatFace) -> Option<Face> {
|
||||
.collect();
|
||||
Some(edges.into())
|
||||
};
|
||||
// The outer boundary is the loop that encloses the rest — i.e. the one with
|
||||
// the largest area. ACIS does not guarantee it comes first in the face's
|
||||
// loop list (a pierced wall lists its window holes before the wall edge),
|
||||
// so pick it by area rather than trusting index 0. (#123)
|
||||
let outer_idx = (0..loops.len())
|
||||
.max_by(|&a, &b| {
|
||||
vlen(loop_normal(&loops[a]))
|
||||
.partial_cmp(&vlen(loop_normal(&loops[b])))
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
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)
|
||||
let outer_n = loop_normal(&loops[0]);
|
||||
let outer_n = loop_normal(&loops[outer_idx]);
|
||||
let mut wires: Vec<Wire> = Vec::new();
|
||||
let outer = build_wire(&loops[0], false)?;
|
||||
wires.push(outer);
|
||||
for lp in &loops[1..] {
|
||||
wires.push(build_wire(&loops[outer_idx], false)?);
|
||||
for (i, lp) in loops.iter().enumerate() {
|
||||
if i == outer_idx {
|
||||
continue;
|
||||
}
|
||||
let same = vdot(loop_normal(lp), outer_n) > 0.0;
|
||||
if let Some(w) = build_wire(lp, same) {
|
||||
wires.push(w);
|
||||
@@ -470,5 +486,3 @@ fn vnorm(a: [f64; 3]) -> [f64; 3] {
|
||||
[a[0] / l, a[1] / l, a[2] / l]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user