fix(acis): complete curved solid faces
Prevent partial sweeps and reversed boundary sampling from dropping surface segments and connector corners.
This commit is contained in:
parent
f18ba87858
commit
2c5b7e76fb
2 changed files with 23 additions and 13 deletions
|
|
@ -374,9 +374,9 @@ fn loop_normal(pts: &[[f64; 3]]) -> [f64; 3] {
|
||||||
|
|
||||||
// ── Cone / cylinder face ─────────────────────────────────────────────────────
|
// ── Cone / cylinder face ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Build a cone/cylinder lateral surface as revolution faces. A plain closed
|
/// Build a pierced cone/cylinder lateral surface as revolution faces. A
|
||||||
/// revolution uses the parametric sampler; a pierced one stays here so its
|
/// single-loop face uses the parametric sampler, avoiding partial revolution
|
||||||
/// additional boundary loops can trim holes into the curved surface.
|
/// segments that can disappear when the profile crosses a coordinate plane.
|
||||||
fn cone_faces(
|
fn cone_faces(
|
||||||
sat: &SatDocument,
|
sat: &SatDocument,
|
||||||
face: &SatFace,
|
face: &SatFace,
|
||||||
|
|
@ -411,6 +411,9 @@ fn cone_faces(
|
||||||
};
|
};
|
||||||
|
|
||||||
let loops = collect_face_loops(sat, face, BOUNDARY_CHORD_FRAC);
|
let loops = collect_face_loops(sat, face, BOUNDARY_CHORD_FRAC);
|
||||||
|
if loops.len() == 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let arcs: Vec<(f64, f64)> = loops
|
let arcs: Vec<(f64, f64)> = loops
|
||||||
.iter()
|
.iter()
|
||||||
.map(|lp| cone_boundary_arc(lp, [cx, cy, cz], axis, udir, vdir))
|
.map(|lp| cone_boundary_arc(lp, [cx, cy, cz], axis, udir, vdir))
|
||||||
|
|
|
||||||
|
|
@ -1424,17 +1424,23 @@ pub(crate) fn append_coedge_points(
|
||||||
// The edge's own sense (relative to its curve) decides the ellipse
|
// The edge's own sense (relative to its curve) decides the ellipse
|
||||||
// winding; a reversed edge samples the opposite handedness.
|
// winding; a reversed edge samples the opposite handedness.
|
||||||
let reversed = matches!(edge.sense(), Sense::Reversed);
|
let reversed = matches!(edge.sense(), Sense::Reversed);
|
||||||
let mut sampled = sample_ellipse_arc(
|
// Orient the parameter range before sampling. Reversing an
|
||||||
|
// endpoint-exclusive sample afterwards would drop the traversal
|
||||||
|
// start and retain its end, duplicating the next coedge's point and
|
||||||
|
// cutting the connector corner out of the face boundary.
|
||||||
|
let (start, end) = if fwd {
|
||||||
|
(edge.start_param(), edge.end_param())
|
||||||
|
} else {
|
||||||
|
(edge.end_param(), edge.start_param())
|
||||||
|
};
|
||||||
|
let sampled = sample_ellipse_arc(
|
||||||
&ellipse,
|
&ellipse,
|
||||||
edge.start_param(),
|
start,
|
||||||
edge.end_param(),
|
end,
|
||||||
chord_frac,
|
chord_frac,
|
||||||
reversed,
|
reversed,
|
||||||
);
|
);
|
||||||
if !sampled.is_empty() {
|
if !sampled.is_empty() {
|
||||||
if !fwd {
|
|
||||||
sampled.reverse();
|
|
||||||
}
|
|
||||||
pts.extend(sampled);
|
pts.extend(sampled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1452,12 +1458,13 @@ pub(crate) fn append_coedge_points(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(x, y, z)| [x, y, z])
|
.map(|(x, y, z)| [x, y, z])
|
||||||
.collect();
|
.collect();
|
||||||
// Drop the shared end point so adjacent coedges don't double up.
|
// Orient the inclusive sample first, then drop the traversal end so
|
||||||
|
// the adjacent coedge contributes that shared point.
|
||||||
|
if !fwd {
|
||||||
|
sampled.reverse();
|
||||||
|
}
|
||||||
sampled.pop();
|
sampled.pop();
|
||||||
if sampled.len() >= 2 {
|
if sampled.len() >= 2 {
|
||||||
if !fwd {
|
|
||||||
sampled.reverse();
|
|
||||||
}
|
|
||||||
pts.extend(sampled);
|
pts.extend(sampled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue