use crate::{KernelRefusal, KernelStage, OrRefuse};
use super::*;
struct BandWrapPair {
depart_index: usize,
arrive_at_max: bool,
v: f64,
vertex_id: u64,
}
fn band_wrap_pair(
loop_record: &LoopRecord,
edges: &HashMap<u64, &EdgeRecord>,
u_domain: [f64; 2],
u_eps: f64,
v_eps: f64,
) -> Result<Option<BandWrapPair>, KernelRefusal> {
let count = loop_record.coedges.len();
if count == 0 {
return Ok(None);
}
let mut found: Option<BandWrapPair> = None;
for index in 0..count {
let arrive = &loop_record.coedges[index];
let depart = &loop_record.coedges[(index + 1) % count];
let [_, a1] = arrive.pcurve.domain().or_refuse(KernelStage::Sew, "domain")?;
let [d0, _] = depart.pcurve.domain().or_refuse(KernelStage::Sew, "domain")?;
let arrive_end = arrive.pcurve.evaluate(a1).or_refuse(KernelStage::Sew, "evaluate")?;
let depart_start = depart.pcurve.evaluate(d0).or_refuse(KernelStage::Sew, "evaluate")?;
let arrive_at_max = (arrive_end.x - u_domain[1]).abs() <= u_eps;
let arrive_at_min = (arrive_end.x - u_domain[0]).abs() <= u_eps;
let depart_at_max = (depart_start.x - u_domain[1]).abs() <= u_eps;
let depart_at_min = (depart_start.x - u_domain[0]).abs() <= u_eps;
if !((arrive_at_max && depart_at_min) || (arrive_at_min && depart_at_max)) {
continue;
}
if (arrive_end.y - depart_start.y).abs() > v_eps || found.is_some() {
return Ok(None);
}
let arrive_edge = edges
.get(&arrive.edge_id)
.ok_or_else(|| KernelRefusal::internal(KernelStage::Sew, "boolean.rim", "band seam: loop references unknown edge"))?;
let depart_edge = edges
.get(&depart.edge_id)
.ok_or_else(|| KernelRefusal::internal(KernelStage::Sew, "boolean.rim", "band seam: loop references unknown edge"))?;
let arrive_vertex = if arrive.forward {
arrive_edge.end_vertex_id
} else {
arrive_edge.start_vertex_id
};
let depart_vertex = if depart.forward {
depart_edge.start_vertex_id
} else {
depart_edge.end_vertex_id
};
if arrive_vertex != depart_vertex {
return Ok(None);
}
found = Some(BandWrapPair {
depart_index: (index + 1) % count,
arrive_at_max,
v: 0.5 * (arrive_end.y + depart_start.y),
vertex_id: arrive_vertex,
});
}
Ok(found)
}
pub(super) fn insert_periodic_band_seam_edges(
solid: &mut BrepSolid,
known_band_faces: &HashSet<u64>,
) -> Result<usize, KernelRefusal> {
let edges_by_id: HashMap<u64, &EdgeRecord> =
solid.edges.iter().map(|edge| (edge.id, edge)).collect();
let vertex_points: HashMap<u64, Vec3> = solid
.vertices
.iter()
.map(|vertex| (vertex.id, vertex.point))
.collect();
let mut next_edge_id = solid.edges.iter().map(|edge| edge.id).max().unwrap_or(0) + 1;
let mut next_coedge_id = solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.flat_map(|face| &face.loops)
.flat_map(|loop_record| &loop_record.coedges)
.map(|coedge| coedge.id)
.max()
.unwrap_or(0)
+ 1;
struct SeamPlan {
shell_index: usize,
face_index: usize,
edge: EdgeRecord,
merged_loop: LoopRecord,
}
let debug_face: Option<u64> = std::env::var("BREP_DEBUG_BAND_FACE")
.ok()
.and_then(|v| v.parse().ok());
macro_rules! band_dbg {
($face:expr, $($arg:tt)*) => {
if debug_face == Some($face.id) {
eprintln!("band_seams[{}]: {}", $face.id, format!($($arg)*));
}
};
}
let mut plans: Vec<SeamPlan> = Vec::new();
for (shell_index, shell) in solid.shells.iter().enumerate() {
'faces: for (face_index, face) in shell.faces.iter().enumerate() {
if face.loops.len() != 2 {
continue;
}
let (closed_u, closed_v) = match face.surface.closed_directions() {
Ok(value) => value,
Err(_) => continue,
};
if !closed_u {
band_dbg!(face, "skip: not closed_u");
continue;
}
let Ok([u0, u1]) = face.surface.domain_u() else {
continue;
};
let Ok([v0, v1]) = face.surface.domain_v() else {
continue;
};
let u_eps = (u1 - u0).abs() * 1e-6;
let v_eps = (v1 - v0).abs() * 1e-6;
for coedge in face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
let Some(edge) = edges_by_id.get(&coedge.edge_id) else {
continue 'faces;
};
if edge.degenerate {
band_dbg!(face, "skip: degenerate edge {}", edge.id);
continue 'faces;
}
let Ok([d0, d1]) = coedge.pcurve.domain() else {
continue 'faces;
};
let (Ok(start), Ok(middle), Ok(end)) = (
coedge.pcurve.evaluate(d0),
coedge.pcurve.evaluate((d0 + d1) * 0.5),
coedge.pcurve.evaluate(d1),
) else {
continue 'faces;
};
for extreme in [u0, u1] {
if (start.x - extreme).abs() <= u_eps
&& (middle.x - extreme).abs() <= u_eps
&& (end.x - extreme).abs() <= u_eps
&& (end.y - start.y).abs() > v_eps
{
band_dbg!(face, "skip: existing seam-like column coedge (edge {})", coedge.edge_id);
continue 'faces;
}
}
}
let Ok(Some(pair_a)) =
band_wrap_pair(&face.loops[0], &edges_by_id, [u0, u1], u_eps, v_eps)
else {
band_dbg!(face, "skip: band_wrap_pair loop0 -> None");
continue;
};
let Ok(Some(pair_b)) =
band_wrap_pair(&face.loops[1], &edges_by_id, [u0, u1], u_eps, v_eps)
else {
band_dbg!(face, "skip: band_wrap_pair loop1 -> None");
continue;
};
if pair_a.arrive_at_max == pair_b.arrive_at_max
|| pair_a.vertex_id == pair_b.vertex_id
|| (pair_a.v - pair_b.v).abs() <= v_eps * 10.0
{
band_dbg!(face, "skip: rim pair mismatch (same_extreme={} same_vertex={} dv={:.3e})",
pair_a.arrive_at_max == pair_b.arrive_at_max,
pair_a.vertex_id == pair_b.vertex_id,
(pair_a.v - pair_b.v).abs());
continue;
}
let v_mid = 0.5 * (pair_a.v + pair_b.v);
let inset = (u1 - u0) * 1e-3;
let interior_at = |u: f64, v: f64| -> bool {
matches!(
parameter_point_in_face(face, Vec2 { x: u, y: v }, 1e-9),
Ok(PolygonClass::Inside)
)
};
let plain = known_band_faces.contains(&face.id)
|| (interior_at(u0 + inset, v_mid) && interior_at(u1 - inset, v_mid));
let (mut foot_a, mut foot_b) = (pair_a.v, pair_b.v);
let mut wrapped = false;
if !plain
&& closed_v
&& std::env::var("BREP_BAND_SEAM_WRAP").as_deref() != Ok("0")
{
let (va, vb) = (pair_a.v.min(pair_b.v), pair_a.v.max(pair_b.v));
let rim_at_top = (vb - v1).abs() <= v_eps * 10.0;
let rim_at_bottom = (va - v0).abs() <= v_eps * 10.0;
if rim_at_top ^ rim_at_bottom {
let wrapped_mid = if rim_at_top {
0.5 * (v0 + va)
} else {
0.5 * (vb + v1)
};
if interior_at(u0 + inset, wrapped_mid) && interior_at(u1 - inset, wrapped_mid)
{
let wrap_foot = |v: f64| -> f64 {
if rim_at_top && (v - vb).abs() <= v_eps * 10.0 {
v0
} else if rim_at_bottom && (v - va).abs() <= v_eps * 10.0 {
v1
} else {
v
}
};
foot_a = wrap_foot(pair_a.v);
foot_b = wrap_foot(pair_b.v);
wrapped = true;
}
}
}
if !plain && !wrapped {
band_dbg!(face, "skip: interior probe failed (plain=false wrapped=false, closed_v={closed_v}, v_a={:.6} v_b={:.6})", pair_a.v, pair_b.v);
continue;
}
let (Some(&point_a), Some(&point_b)) = (
vertex_points.get(&pair_a.vertex_id),
vertex_points.get(&pair_b.vertex_id),
) else {
continue;
};
let on_meridian = |v: f64, point: Vec3| -> bool {
face.surface
.evaluate(u0, v)
.map(|at| at.sub(point).length() <= 1e-6 * (1.0 + point.length()))
.unwrap_or(false)
};
if !on_meridian(pair_a.v, point_a) || !on_meridian(pair_b.v, point_b) {
band_dbg!(face, "skip: wrap vertex not on seam meridian");
continue;
}
let Ok(iso) = face.surface.iso_curve_u(u0) else {
continue;
};
let seam_matches = |v: f64, point: Vec3| -> bool {
iso.evaluate(v)
.map(|at| at.sub(point).length() <= 1e-6 * (1.0 + point.length()))
.unwrap_or(false)
};
if !seam_matches(pair_a.v, point_a) || !seam_matches(pair_b.v, point_b) {
band_dbg!(face, "skip: iso seam curve does not match wrap vertices");
continue;
}
let (v_bottom, bottom_vertex, v_top, top_vertex) = if foot_a < foot_b {
(foot_a, pair_a.vertex_id, foot_b, pair_b.vertex_id)
} else {
(foot_b, pair_b.vertex_id, foot_a, pair_a.vertex_id)
};
let edge = EdgeRecord {
id: next_edge_id,
curve: iso,
t0: v_bottom,
t1: v_top,
start_vertex_id: bottom_vertex,
end_vertex_id: top_vertex,
degenerate: false,
name: None,
};
next_edge_id += 1;
let rotate = |loop_record: &LoopRecord, start: usize| -> Vec<CoedgeRecord> {
let mut coedges = loop_record.coedges.clone();
coedges.rotate_left(start);
coedges
};
let column_a = if pair_a.arrive_at_max { u1 } else { u0 };
let column_b = if pair_a.arrive_at_max { u0 } else { u1 };
let (Ok(pcurve_up), Ok(pcurve_down)) = (
crate::make_line(
Vec3::new(column_a, foot_a, 0.0),
Vec3::new(column_a, foot_b, 0.0),
),
crate::make_line(
Vec3::new(column_b, foot_b, 0.0),
Vec3::new(column_b, foot_a, 0.0),
),
) else {
continue;
};
let mut coedges = rotate(&face.loops[0], pair_a.depart_index);
coedges.push(CoedgeRecord {
id: next_coedge_id,
edge_id: edge.id,
forward: foot_a < foot_b,
pcurve: pcurve_up,
});
next_coedge_id += 1;
coedges.extend(rotate(&face.loops[1], pair_b.depart_index));
coedges.push(CoedgeRecord {
id: next_coedge_id,
edge_id: edge.id,
forward: foot_b < foot_a,
pcurve: pcurve_down,
});
next_coedge_id += 1;
if std::env::var("BREP_DEBUG_BOOL").is_ok() {
eprintln!(
"band_seams: face {} gets seam (wrapped={wrapped} feet=[{foot_a:.6},{foot_b:.6}])",
face.id
);
}
plans.push(SeamPlan {
shell_index,
face_index,
edge,
merged_loop: LoopRecord {
id: face.loops[0].id,
coedges,
},
});
}
}
drop(edges_by_id);
let inserted = plans.len();
for plan in plans {
solid.edges.push(plan.edge);
let face = &mut solid.shells[plan.shell_index].faces[plan.face_index];
face.loops = vec![plan.merged_loop];
}
Ok(inserted)
}
fn covering_rim_subcurve(
curve: &NurbsCurve,
start_fraction: f64,
end_fraction: f64,
) -> Result<NurbsCurve, KernelRefusal> {
let [start, end] = curve.domain().or_refuse(KernelStage::Sew, "domain")?;
let first = start + (end - start) * start_fraction;
let second = start + (end - start) * end_fraction;
let epsilon = (1e-9 * (end - start)).max(2e-9);
let mut result = curve.clone();
if first > start + epsilon && first < end - epsilon {
result = result.split(first).or_refuse(KernelStage::Sew, "split")?.1;
}
let domain = result.domain().or_refuse(KernelStage::Sew, "domain")?;
if second < domain[1] - epsilon && second > domain[0] + epsilon {
result = result.split(second).or_refuse(KernelStage::Sew, "split")?.0;
}
Ok(result)
}
fn split_operand_edges(
solid: &mut BrepSolid,
splits: &HashMap<u64, Vec<f64>>,
) -> Result<(), KernelRefusal> {
let mut next_vertex_id = solid.vertices.iter().map(|v| v.id).max().unwrap_or(0) + 1;
let mut next_edge_id = solid.edges.iter().map(|e| e.id).max().unwrap_or(0) + 1;
let mut next_coedge_id = solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.flat_map(|face| &face.loops)
.flat_map(|loop_record| &loop_record.coedges)
.map(|coedge| coedge.id)
.max()
.unwrap_or(0)
+ 1;
let mut pieces: HashMap<u64, Vec<EdgeRecord>> = HashMap::default();
let mut new_vertices: Vec<VertexRecord> = Vec::new();
let mut source_spans: HashMap<u64, (f64, f64)> = HashMap::default();
for edge in &solid.edges {
let Some(parameters) = splits.get(&edge.id) else {
continue;
};
let ptol = (edge.t1 - edge.t0).abs() * 1e-10;
let mut partition: Vec<f64> = parameters
.iter()
.copied()
.filter(|parameter| *parameter > edge.t0 + ptol && *parameter < edge.t1 - ptol)
.collect();
partition.sort_by(f64::total_cmp);
partition.dedup_by(|a, b| (*a - *b).abs() <= ptol);
partition.insert(0, edge.t0);
partition.push(edge.t1);
if partition.len() <= 2 {
continue;
}
let mut vertex_ids = vec![edge.start_vertex_id];
for parameter in &partition[1..partition.len() - 1] {
let point = edge.curve.evaluate(*parameter).or_refuse(KernelStage::Sew, "evaluate")?;
let id = next_vertex_id;
next_vertex_id += 1;
new_vertices.push(VertexRecord { id, point });
vertex_ids.push(id);
}
vertex_ids.push(edge.end_vertex_id);
let mut edge_pieces = Vec::new();
for index in 0..partition.len() - 1 {
let name = edge.name.as_ref().map(|name| {
if index == 0 {
name.clone()
} else {
format!("{name}_{index}")
}
});
edge_pieces.push(EdgeRecord {
id: next_edge_id,
curve: edge.curve.clone(),
t0: partition[index],
t1: partition[index + 1],
start_vertex_id: vertex_ids[index],
end_vertex_id: vertex_ids[index + 1],
degenerate: false,
name,
});
next_edge_id += 1;
}
source_spans.insert(edge.id, (edge.t0, edge.t1));
pieces.insert(edge.id, edge_pieces);
}
if pieces.is_empty() {
return Ok(());
}
solid.vertices.extend(new_vertices);
solid.edges.retain(|edge| !pieces.contains_key(&edge.id));
for edge_pieces in pieces.values() {
solid.edges.extend(edge_pieces.iter().cloned());
}
for face in solid
.shells
.iter_mut()
.flat_map(|shell| shell.faces.iter_mut())
{
for loop_record in &mut face.loops {
let mut rebuilt = Vec::new();
for coedge in &loop_record.coedges {
let Some(edge_pieces) = pieces.get(&coedge.edge_id) else {
rebuilt.push(coedge.clone());
continue;
};
let (ot0, ot1) = source_spans[&coedge.edge_id];
let span = ot1 - ot0;
let indices: Vec<usize> = if coedge.forward {
(0..edge_pieces.len()).collect()
} else {
(0..edge_pieces.len()).rev().collect()
};
for index in indices {
let piece = &edge_pieces[index];
let (start_fraction, end_fraction) = if coedge.forward {
((piece.t0 - ot0) / span, (piece.t1 - ot0) / span)
} else {
((ot1 - piece.t1) / span, (ot1 - piece.t0) / span)
};
rebuilt.push(CoedgeRecord {
id: next_coedge_id,
edge_id: piece.id,
forward: coedge.forward,
pcurve: covering_rim_subcurve(&coedge.pcurve, start_fraction, end_fraction)?,
});
next_coedge_id += 1;
}
}
loop_record.coedges = rebuilt;
}
}
Ok(())
}
struct RimCrossing {
edge_id: u64,
t_split: f64,
}
fn plan_rim_seam_crossing(
face: &FaceRecord,
loop_record: &LoopRecord,
edges_by_id: &HashMap<u64, &EdgeRecord>,
u0: f64,
u1: f64,
u_eps: f64,
) -> Result<Option<(RimCrossing, f64, f64)>, KernelRefusal> {
let period = u1 - u0;
let sample_count = 24usize;
struct Sampled {
edge_id: u64,
forward: bool,
raw: Vec<Vec2>,
}
let mut coedges: Vec<Sampled> = Vec::with_capacity(loop_record.coedges.len());
let (mut vmin, mut vmax) = (f64::INFINITY, f64::NEG_INFINITY);
let mut net_folded = 0.0f64;
let mut prev_x: Option<f64> = None;
for coedge in &loop_record.coedges {
let [d0, d1] = coedge.pcurve.domain().or_refuse(KernelStage::Sew, "domain")?;
let mut raw = Vec::with_capacity(sample_count + 1);
for index in 0..=sample_count {
let parameter = d0 + (d1 - d0) * index as f64 / sample_count as f64;
let evaluated = coedge.pcurve.evaluate(parameter).or_refuse(KernelStage::Sew, "evaluate")?;
raw.push(Vec2 {
x: evaluated.x,
y: evaluated.y,
});
vmin = vmin.min(evaluated.y);
vmax = vmax.max(evaluated.y);
if let Some(px) = prev_x {
let du = evaluated.x - px;
net_folded += du - period * (du / period).round();
}
prev_x = Some(evaluated.x);
}
coedges.push(Sampled {
edge_id: coedge.edge_id,
forward: coedge.forward,
raw,
});
}
if let (Some(first), Some(last)) = (
coedges.first().and_then(|c| c.raw.first()),
coedges.last().and_then(|c| c.raw.last()),
) {
let du = first.x - last.x;
net_folded += du - period * (du / period).round();
}
if (net_folded.abs() - period).abs() > 2e-2 * period {
return Ok(None);
}
let mut exit: Option<(usize, f64)> = None; for (index, sampled) in coedges.iter().enumerate() {
let below = sampled.raw.iter().any(|p| p.x < u0 - u_eps);
let above = sampled.raw.iter().any(|p| p.x > u1 + u_eps);
if below && above {
return Ok(None); }
if below || above {
let seam = if above { u1 } else { u0 };
if exit.is_some() {
return Ok(None);
}
exit = Some((index, seam));
}
}
let Some((exit_index, seam)) = exit else {
return Ok(None);
};
let coedge = &loop_record.coedges[exit_index];
let [d0, d1] = coedge.pcurve.domain().or_refuse(KernelStage::Sew, "domain")?;
let mut sign_changes = 0usize;
let sampled = &coedges[exit_index];
for pair in sampled.raw.windows(2) {
if (pair[0].x - seam) * (pair[1].x - seam) < 0.0 {
sign_changes += 1;
}
}
if sign_changes != 1 {
return Ok(None);
}
let x_at = |s: f64| -> Result<f64, KernelRefusal> { Ok(coedge.pcurve.evaluate(s).or_refuse(KernelStage::Sew, "evaluate")?.x - seam) };
let (mut lo, mut hi) = (d0, d1);
let mut flo = x_at(lo)?;
for _ in 0..60 {
let mid = 0.5 * (lo + hi);
let fmid = x_at(mid)?;
if fmid == 0.0 {
lo = mid;
hi = mid;
break;
}
if (flo < 0.0) != (fmid < 0.0) {
hi = mid;
} else {
lo = mid;
flo = fmid;
}
}
let s_star = 0.5 * (lo + hi);
let v_cross = coedge.pcurve.evaluate(s_star).or_refuse(KernelStage::Sew, "evaluate")?.y;
let fraction = (s_star - d0) / (d1 - d0);
let edge = edges_by_id
.get(&sampled.edge_id)
.ok_or_else(|| KernelRefusal::internal(KernelStage::Sew, "boolean.rim", "covering rim: unknown edge"))?;
if edge.degenerate {
return Ok(None);
}
let t_split = if sampled.forward {
edge.t0 + fraction * (edge.t1 - edge.t0)
} else {
edge.t1 - fraction * (edge.t1 - edge.t0)
};
let span = (edge.t1 - edge.t0).abs();
let end_tol = span * 1e-4;
if t_split <= edge.t0 + end_tol || t_split >= edge.t1 - end_tol {
return Ok(None);
}
let point_edge = edge.curve.evaluate(t_split).or_refuse(KernelStage::Sew, "evaluate")?;
let point_surface = face.surface.evaluate(seam, v_cross).or_refuse(KernelStage::Sew, "evaluate")?;
if point_edge.sub(point_surface).length() > 1e-6 * (1.0 + point_edge.length()) {
return Ok(None);
}
Ok(Some((
RimCrossing {
edge_id: sampled.edge_id,
t_split,
},
vmin,
vmax,
)))
}
fn normalize_covering_rim_strips(solid: &mut BrepSolid) -> Result<Vec<u64>, KernelRefusal> {
if std::env::var("BREP_COVERING_RIM_NORMALIZE").as_deref() == Ok("0") {
return Ok(Vec::new());
}
let debug = std::env::var("BREP_DEBUG_BOOL").is_ok();
let edges_by_id: HashMap<u64, &EdgeRecord> =
solid.edges.iter().map(|edge| (edge.id, edge)).collect();
let mut splits: HashMap<u64, Vec<f64>> = HashMap::default();
let mut normalized_faces: Vec<u64> = Vec::new();
for shell in &solid.shells {
for face in &shell.faces {
if face.loops.len() != 2 {
continue;
}
if !matches!(face.surface.closed_directions(), Ok((true, false))) {
continue;
}
let (Ok([u0, u1]), Ok([v0, v1])) =
(face.surface.domain_u(), face.surface.domain_v())
else {
continue;
};
let period = u1 - u0;
if !(period > 0.0) {
continue;
}
let u_eps = period * 1e-6;
let v_span = (v1 - v0).abs().max(1e-30);
let (Ok(Some((cross0, lo0, hi0))), Ok(Some((cross1, lo1, hi1)))) = (
plan_rim_seam_crossing(face, &face.loops[0], &edges_by_id, u0, u1, u_eps),
plan_rim_seam_crossing(face, &face.loops[1], &edges_by_id, u0, u1, u_eps),
) else {
continue;
};
if cross0.edge_id == cross1.edge_id {
continue;
}
let disjoint = hi0 <= lo1 + 1e-6 * v_span || hi1 <= lo0 + 1e-6 * v_span;
if !disjoint {
continue;
}
for cross in [&cross0, &cross1] {
splits.entry(cross.edge_id).or_default().push(cross.t_split);
}
normalized_faces.push(face.id);
if debug {
eprintln!(
"covering_rim: face {} plans rim splits on edges {} @ {:.6}, {} @ {:.6}",
face.id, cross0.edge_id, cross0.t_split, cross1.edge_id, cross1.t_split
);
}
}
}
drop(edges_by_id);
if normalized_faces.is_empty() {
return Ok(Vec::new());
}
split_operand_edges(solid, &splits)?;
let normalized_set: HashSet<u64> = normalized_faces.iter().copied().collect();
for face in solid
.shells
.iter_mut()
.flat_map(|shell| shell.faces.iter_mut())
{
if !normalized_set.contains(&face.id) {
continue;
}
let Ok([u0, u1]) = face.surface.domain_u() else {
continue;
};
let period = u1 - u0;
let fold_eps = period * 1e-6;
for loop_record in &mut face.loops {
for coedge in &mut loop_record.coedges {
let [d0, d1] = coedge.pcurve.domain().or_refuse(KernelStage::Sew, "domain")?;
let mut lo = f64::INFINITY;
let mut hi = f64::NEG_INFINITY;
for index in 0..=16 {
let parameter = d0 + (d1 - d0) * index as f64 / 16.0;
let x = coedge.pcurve.evaluate(parameter).or_refuse(KernelStage::Sew, "evaluate")?.x;
lo = lo.min(x);
hi = hi.max(x);
}
let shift = if lo >= u1 - fold_eps {
-period
} else if hi <= u0 + fold_eps {
period
} else {
0.0
};
if shift != 0.0 {
let mut control_points = coedge.pcurve.control_points.clone();
for cp in &mut control_points {
cp.x += shift * cp.w;
}
coedge.pcurve = NurbsCurve::new(
coedge.pcurve.degree,
coedge.pcurve.knots.clone(),
control_points,
).or_refuse(KernelStage::Sew, "csg.boolean.rim")?;
}
}
}
}
Ok(normalized_faces)
}
pub(super) fn normalize_operand_band_seams(solid: &mut BrepSolid) -> Result<(), KernelRefusal> {
let has_candidate = solid.shells.iter().flat_map(|shell| &shell.faces).any(|face| {
face.loops.len() == 2
&& matches!(face.surface.closed_directions(), Ok((true, _)))
});
if !has_candidate {
return Ok(());
}
let backup = solid.clone();
let baseline_issues = backup.validate().len();
let debug = std::env::var("BREP_DEBUG_BOOL").is_ok();
let normalized_faces = normalize_covering_rim_strips(solid).unwrap_or_else(|error| {
if debug {
eprintln!("covering_rim: ERROR {error}");
}
*solid = backup.clone();
Vec::new()
});
let known_band_faces: HashSet<u64> = normalized_faces.iter().copied().collect();
let attempt = insert_periodic_band_seam_edges(solid, &known_band_faces);
let all_seamed = |solid: &BrepSolid| -> bool {
normalized_faces.iter().all(|&id| {
solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.find(|face| face.id == id)
.map(|face| face.loops.len() == 1)
.unwrap_or(false)
})
};
if let Ok(count) = attempt {
let issues = solid.validate().len();
if issues <= baseline_issues && all_seamed(solid) {
if debug && (count > 0 || !normalized_faces.is_empty()) {
eprintln!(
"band_seams: inserted {count} seam edge(s); covering-rim normalized {} face(s)",
normalized_faces.len()
);
}
return Ok(());
}
}
*solid = backup.clone();
match insert_periodic_band_seam_edges(solid, &HashSet::default()) {
Ok(0) => {}
Ok(count) => {
let issues = solid.validate().len();
if issues > baseline_issues {
if debug {
eprintln!(
"band_seams: REVERTED {count} insertion(s) (validation {baseline_issues} -> {issues})"
);
}
*solid = backup;
} else if debug {
eprintln!("band_seams: inserted {count} seam edge(s)");
}
}
Err(error) => {
if debug {
eprintln!("band_seams: ERROR {error}");
}
*solid = backup;
}
}
Ok(())
}