use crate::{KernelRefusal, KernelStage, OrRefuse};
use super::*;
pub(super) fn extend_offset_carriers_past_open_hole_rims(
carriers: &mut [Carrier],
source: &BrepSolid,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
smooth_pairs: &HashSet<(usize, usize)>,
scale: f64,
) -> Result<HashSet<usize>, KernelRefusal> {
let weld_band = 2e-3f64.max(scale * 5e-5);
let source_edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut extended = HashSet::default();
'carrier: for index in 0..carriers.len() {
if !matches!(carriers[index].kind, OffsetFaceRole::Offset) {
continue;
}
if smooth_pairs
.iter()
.any(|(first, second)| *first == index || *second == index)
{
continue;
}
let Some(source_face) = source_faces
.iter()
.find(|face| face.id == carriers[index].source_face_id)
else {
continue;
};
let mut local_uses = HashMap::<u64, usize>::default();
for coedge in source_face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
*local_uses.entry(coedge.edge_id).or_default() += 1;
}
let mut rims = Vec::new();
for (loop_index, loop_record) in source_face.loops.iter().enumerate() {
for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
let Some(edge) = source_edge_by_id.get(&coedge.edge_id) else {
continue 'carrier;
};
if edge.degenerate {
continue;
}
match local_uses[&coedge.edge_id] {
2 => {}
1 if edge.start_vertex_id == edge.end_vertex_id => {
rims.push((loop_index, coedge_index, (*edge).clone()));
}
_ => continue 'carrier,
}
}
}
if rims.len() != 2 {
continue;
}
let carrier_face = carriers[index].solid.shells[0].faces[0].clone();
let surface = carrier_face.surface.clone();
let [su0, su1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [sv0, sv1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let v_mid = (sv0 + sv1) * 0.5;
if surface
.evaluate(su0, v_mid).or_refuse(KernelStage::Refine, "evaluate")?
.sub(surface.evaluate(su1, v_mid).or_refuse(KernelStage::Refine, "evaluate")?)
.length()
> weld_band
{
continue;
}
let mut rim_planes = Vec::new();
for (loop_index, coedge_index, rim_edge) in &rims {
let source_coedge = &source_face.loops[*loop_index].coedges[*coedge_index];
let [p0, p1] = source_coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let mut u_low = f64::MAX;
let mut u_high = f64::MIN;
let mut v_mean = 0.0f64;
for sample in 0..=32 {
let uv = source_coedge
.pcurve
.evaluate(p0 + (p1 - p0) * sample as f64 / 32.0).or_refuse(KernelStage::Refine, "evaluate")?;
u_low = u_low.min(uv.x);
u_high = u_high.max(uv.x);
v_mean += uv.y / 33.0;
}
let u_span = su1 - su0;
if (u_low - su0).abs() > u_span * 1e-3 || (u_high - su1).abs() > u_span * 1e-3 {
continue 'carrier;
}
let Some(opening) = source_faces.iter().find(|face| {
opening_set.contains(&face.id)
&& face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.any(|coedge| coedge.edge_id == rim_edge.id)
}) else {
continue 'carrier;
};
let Some((plane_point, plane_normal)) =
planar_surface_frame(&opening.surface, weld_band)?
else {
continue 'carrier;
};
let mut rim_loop_area = None;
let mut largest_other = 0.0f64;
for loop_record in &opening.loops {
let area = parameter_space_area(&FaceRecord {
id: 0,
surface: opening.surface.clone(),
same_sense: true,
loops: vec![loop_record.clone()],
name: None,
}).or_refuse(KernelStage::Refine, "parameter_space_area")?
.abs();
if loop_record
.coedges
.iter()
.any(|coedge| coedge.edge_id == rim_edge.id)
{
rim_loop_area = Some(area);
} else {
largest_other = largest_other.max(area);
}
}
let Some(rim_loop_area) = rim_loop_area else {
continue 'carrier;
};
if rim_loop_area >= largest_other {
continue 'carrier;
}
let image_coedge = &carrier_face.loops[*loop_index].coedges[*coedge_index];
let Some(image_edge) = carriers[index]
.solid
.edges
.iter()
.find(|edge| edge.id == image_coedge.edge_id)
else {
continue 'carrier;
};
let mut off_plane = 0.0f64;
for sample in 0..=32 {
let point = image_edge.curve.evaluate(
image_edge.t0 + (image_edge.t1 - image_edge.t0) * sample as f64 / 32.0,
).or_refuse(KernelStage::Refine, "evaluate")?;
off_plane = off_plane.max(point.sub(plane_point).dot(plane_normal).abs());
}
if off_plane <= weld_band {
continue 'carrier;
}
rim_planes.push((v_mean, plane_point, plane_normal));
}
rim_planes.sort_by(|first, second| first.0.total_cmp(&second.0));
let interior = surface.evaluate((su0 + su1) * 0.5, v_mid).or_refuse(KernelStage::Refine, "evaluate")?;
for (v_end, (_, plane_point, plane_normal)) in [(sv0, rim_planes[0]), (sv1, rim_planes[1])]
{
let interior_sign = interior.sub(plane_point).dot(plane_normal).signum();
let iso = surface.iso_curve_v(v_end).or_refuse(KernelStage::Refine, "iso_curve_v")?;
let [t0, t1] = iso.domain().or_refuse(KernelStage::Refine, "domain")?;
for sample in 0..=32 {
let point = iso.evaluate(t0 + (t1 - t0) * sample as f64 / 32.0).or_refuse(KernelStage::Refine, "evaluate")?;
if point.sub(plane_point).dot(plane_normal) * interior_sign > -weld_band {
continue 'carrier;
}
}
}
let winding = parameter_space_area(&carrier_face).or_refuse(KernelStage::Refine, "parameter_space_area")?;
let bottom_rim = surface.iso_curve_v(sv0).or_refuse(KernelStage::Refine, "iso_curve_v")?;
let top_rim = surface.iso_curve_v(sv1).or_refuse(KernelStage::Refine, "iso_curve_v")?;
let seam = surface.iso_curve_u(su0).or_refuse(KernelStage::Refine, "iso_curve_u")?;
let [bottom_t0, bottom_t1] = bottom_rim.domain().or_refuse(KernelStage::Refine, "domain")?;
let [top_t0, top_t1] = top_rim.domain().or_refuse(KernelStage::Refine, "domain")?;
let corner_bottom = surface.evaluate(su0, sv0).or_refuse(KernelStage::Refine, "evaluate")?;
let corner_top = surface.evaluate(su0, sv1).or_refuse(KernelStage::Refine, "evaluate")?;
let flat = |u: f64, v: f64| Vec3::new(u, v, 0.0);
let mut coedges = vec![
CoedgeRecord {
id: 1,
edge_id: 1,
forward: true,
pcurve: crate::make_line(flat(su0, sv0), flat(su1, sv0)).or_refuse(KernelStage::Refine, "make_line")?,
},
CoedgeRecord {
id: 2,
edge_id: 3,
forward: true,
pcurve: crate::make_line(flat(su1, sv0), flat(su1, sv1)).or_refuse(KernelStage::Refine, "make_line")?,
},
CoedgeRecord {
id: 3,
edge_id: 2,
forward: false,
pcurve: crate::make_line(flat(su1, sv1), flat(su0, sv1)).or_refuse(KernelStage::Refine, "make_line")?,
},
CoedgeRecord {
id: 4,
edge_id: 3,
forward: false,
pcurve: crate::make_line(flat(su0, sv1), flat(su0, sv0)).or_refuse(KernelStage::Refine, "make_line")?,
},
];
if winding < 0.0 {
coedges.reverse();
for coedge in &mut coedges {
coedge.forward = !coedge.forward;
coedge.pcurve = coedge.pcurve.reversed().or_refuse(KernelStage::Refine, "reversed")?;
}
}
os_debug!(
"carrier[{index}] src={} extended past opening planes: band v=({sv0:.4},{sv1:.4})",
carriers[index].source_face_id,
);
carriers[index].solid = BrepSolid {
mass_properties_cache: Default::default(),
cells: Default::default(),
id: carriers[index].solid.id,
vertices: vec![
VertexRecord {
id: 1,
point: corner_bottom,
},
VertexRecord {
id: 2,
point: corner_top,
},
],
edges: vec![
EdgeRecord {
id: 1,
curve: bottom_rim,
t0: bottom_t0,
t1: bottom_t1,
start_vertex_id: 1,
end_vertex_id: 1,
degenerate: false,
name: None,
},
EdgeRecord {
id: 2,
curve: top_rim,
t0: top_t0,
t1: top_t1,
start_vertex_id: 2,
end_vertex_id: 2,
degenerate: false,
name: None,
},
EdgeRecord {
id: 3,
curve: seam,
t0: sv0,
t1: sv1,
start_vertex_id: 1,
end_vertex_id: 2,
degenerate: false,
name: None,
},
],
shells: vec![ShellRecord {
id: 1,
faces: vec![FaceRecord {
id: carrier_face.id,
surface,
same_sense: carrier_face.same_sense,
loops: vec![LoopRecord { id: 1, coedges }],
name: carrier_face.name.clone(),
}],
}],
genus: 0,
kind: crate::BodyKind::Solid,
};
extended.insert(index);
}
Ok(extended)
}
pub(super) fn extend_fallshort_curved_carriers(
carriers: &mut [Carrier],
source: &BrepSolid,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
smooth_pairs: &HashSet<(usize, usize)>,
already_extended: &HashSet<usize>,
scale: f64,
distance: f64,
) -> Result<usize, KernelRefusal> {
let weld_band = 2e-3f64.max(scale * 5e-5);
let source_edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut extended = 0usize;
'carrier: for index in 0..carriers.len() {
if !matches!(carriers[index].kind, OffsetFaceRole::Offset)
|| already_extended.contains(&index)
{
continue;
}
if smooth_pairs
.iter()
.any(|(first, second)| *first == index || *second == index)
{
continue;
}
let Some(source_face) = source_faces
.iter()
.find(|face| face.id == carriers[index].source_face_id)
else {
continue;
};
if source_face.surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
continue;
}
let carrier_face = carriers[index].solid.shells[0].faces[0].clone();
let surface = carrier_face.surface.clone();
let [su0, su1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [sv0, sv1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let v_mid = (sv0 + sv1) * 0.5;
if surface
.evaluate(su0, v_mid).or_refuse(KernelStage::Refine, "evaluate")?
.sub(surface.evaluate(su1, v_mid).or_refuse(KernelStage::Refine, "evaluate")?)
.length()
> weld_band
{
continue;
}
let mut qualifies = false;
'rims: for (loop_index, loop_record) in source_face.loops.iter().enumerate() {
for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
let Some(edge) = source_edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some(opening) = source_faces.iter().find(|face| {
opening_set.contains(&face.id)
&& face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.any(|other| other.edge_id == coedge.edge_id)
}) else {
continue;
};
let Some((plane_point, mut plane_normal)) =
planar_surface_frame(&opening.surface, weld_band)?
else {
continue;
};
let Some(outward) = opening
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.find_map(|opening_coedge| {
let [p0, p1] = opening_coedge.pcurve.domain().ok()?;
let opening_uv =
opening_coedge.pcurve.evaluate((p0 + p1) * 0.5).ok()?;
face_normal(opening, opening_uv.x, opening_uv.y).ok()
})
else {
continue;
};
if plane_normal.dot(outward) < 0.0 {
plane_normal = plane_normal.scale(-1.0);
}
if edge.start_vertex_id == edge.end_vertex_id {
let mut rim_loop_area = None;
let mut largest_other = 0.0f64;
for loop_record in &opening.loops {
let area = parameter_space_area(&FaceRecord {
id: 0,
surface: opening.surface.clone(),
same_sense: true,
loops: vec![loop_record.clone()],
name: None,
}).or_refuse(KernelStage::Refine, "parameter_space_area")?
.abs();
if loop_record
.coedges
.iter()
.any(|other| other.edge_id == coedge.edge_id)
{
rim_loop_area = Some(area);
} else {
largest_other = largest_other.max(area);
}
}
let Some(rim_loop_area) = rim_loop_area else {
continue;
};
if rim_loop_area >= largest_other {
continue;
}
}
let Some(image_coedge) = carrier_face
.loops
.get(loop_index)
.and_then(|loop_record| loop_record.coedges.get(coedge_index))
else {
continue 'carrier;
};
let Some(image_edge) = carriers[index]
.solid
.edges
.iter()
.find(|edge| edge.id == image_coedge.edge_id)
else {
continue 'carrier;
};
let mut falls_short = true;
for sample in 0..=16 {
let point = image_edge.curve.evaluate(
image_edge.t0 + (image_edge.t1 - image_edge.t0) * sample as f64 / 16.0,
).or_refuse(KernelStage::Refine, "evaluate")?;
if point.sub(plane_point).dot(plane_normal) > -weld_band {
falls_short = false;
break;
}
}
if !falls_short {
continue;
}
let mut crosses = false;
'grid: for iu in 0..=16 {
for iv in 0..=16 {
let point = surface.evaluate(
su0 + (su1 - su0) * iu as f64 / 16.0,
sv0 + (sv1 - sv0) * iv as f64 / 16.0,
).or_refuse(KernelStage::Refine, "evaluate")?;
if point.sub(plane_point).dot(plane_normal) > weld_band {
crosses = true;
break 'grid;
}
}
}
if crosses {
qualifies = true;
break 'rims;
}
}
}
if !qualifies {
continue;
}
if rebuild_carrier_full_domain(
&mut carriers[index],
source_face,
weld_band,
distance,
index,
"a fall-short opening rim",
)? {
extended += 1;
}
}
Ok(extended)
}
pub(super) fn cut_rim_full_rectangle_source(
source: &BrepSolid,
face: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
distance: f64,
) -> Result<Option<BrepSolid>, KernelRefusal> {
if distance <= 0.0 || face.loops.len() != 1 {
return Ok(None);
}
let affine = face.surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")?;
let scale = crate::solid_scale(source);
let weld_band = 2e-3f64.max(scale * 5e-5);
let surface = &face.surface;
let [su0, su1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [sv0, sv1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let v_span = (sv1 - sv0).abs().max(1.0);
if surface
.evaluate(su0, (sv0 + sv1) * 0.5).or_refuse(KernelStage::Refine, "evaluate")?
.sub(surface.evaluate(su1, (sv0 + sv1) * 0.5).or_refuse(KernelStage::Refine, "evaluate")?)
.length()
<= weld_band
{
return Ok(None);
}
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let offsets = face_offsets(face);
let v_band = 1e-9 * v_span;
let u_band = 1e-9 * (su1 - su0).abs().max(1.0);
for coedge in face.loops.iter().flat_map(|record| &record.coedges) {
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((mate, mate_use)) = junction_mate(source_faces, face, coedge.edge_id) else {
continue;
};
if opening_set.contains(&mate.id) || !uses_are_tangent(face, coedge, mate, mate_use)? {
continue;
}
let [q0, q1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let mut on = [true; 4];
for sample in 0..=8 {
let uv = coedge.pcurve.evaluate(q0 + (q1 - q0) * sample as f64 / 8.0).or_refuse(KernelStage::Refine, "evaluate")?;
on[0] &= (uv.x - su0).abs() <= u_band;
on[1] &= (uv.x - su1).abs() <= u_band;
on[2] &= (uv.y - sv0).abs() <= v_band;
on[3] &= (uv.y - sv1).abs() <= v_band;
}
if !on.iter().any(|hit| *hit) {
os_debug!(
"cut-rim lane: source face {} keeps its trim — its TANGENT junction with retained \
face {} (edge {}) runs along no domain isoline, so a rectangle would lose it",
face.id,
mate.id,
coedge.edge_id
);
return Ok(None);
}
}
let mut qualifies = false;
for coedge in face.loops.iter().flat_map(|record| &record.coedges) {
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((opening, _)) = junction_mate(source_faces, face, coedge.edge_id)
.filter(|(mate, _)| opening_set.contains(&mate.id))
else {
continue;
};
let Some((plane_point, mut plane_normal)) = planar_surface_frame(&opening.surface, weld_band)? else {
continue;
};
let Some(outward) = opening
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.find_map(|opening_coedge| {
let [p0, p1] = opening_coedge.pcurve.domain().ok()?;
let opening_uv = opening_coedge.pcurve.evaluate((p0 + p1) * 0.5).ok()?;
face_normal(opening, opening_uv.x, opening_uv.y).ok()
})
else {
continue;
};
if plane_normal.dot(outward) < 0.0 {
plane_normal = plane_normal.scale(-1.0);
}
let [q0, q1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let mut at_v_end = true;
let mut deepest = f64::NEG_INFINITY;
for sample in 0..=16 {
let uv = coedge.pcurve.evaluate(q0 + (q1 - q0) * sample as f64 / 16.0).or_refuse(KernelStage::Refine, "evaluate")?;
if (uv.y - sv0).abs() > 1e-9 * v_span && (uv.y - sv1).abs() > 1e-9 * v_span {
at_v_end = false;
}
if let Ok(image) = offsets.at(uv.x, uv.y, -distance) {
deepest = deepest.max(-image.point.sub(plane_point).dot(plane_normal));
}
}
if at_v_end {
os_debug!("cut-rim lane: source face {} rim edge {} sits at a v end", face.id, coedge.edge_id);
continue;
}
os_debug!(
"cut-rim lane: source face {} rim edge {} image at most {deepest:.3e} inside the opening plane",
face.id,
coedge.edge_id
);
if affine && deepest <= weld_band {
continue;
}
let mut crosses = false;
'grid: for iu in 0..=16 {
for iv in 0..=16 {
let point = surface.evaluate(
su0 + (su1 - su0) * iu as f64 / 16.0,
sv0 + (sv1 - sv0) * iv as f64 / 16.0,
).or_refuse(KernelStage::Refine, "evaluate")?;
if point.sub(plane_point).dot(plane_normal) > weld_band {
crosses = true;
break 'grid;
}
}
}
if !crosses {
os_debug!(
"cut-rim lane: source face {} never crosses opening {} over its full domain",
face.id,
opening.id
);
continue;
}
qualifies = true;
break;
}
if !qualifies {
return Ok(None);
}
os_debug!(
"cut-rim lane: source face {} rebuilt over its full rectangle for the opening plane to truncate",
face.id
);
Ok(Some(full_rectangle_face(source, face)?))
}
pub(super) fn full_rectangle_face(source: &BrepSolid, face: &FaceRecord) -> Result<BrepSolid, KernelRefusal> {
let surface = &face.surface;
let [su0, su1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [sv0, sv1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let mut solid = source.clone();
let mut next_id = solid
.vertices
.iter()
.map(|vertex| vertex.id)
.chain(solid.edges.iter().map(|edge| edge.id))
.chain(solid.shells.iter().flat_map(|shell| &shell.faces).flat_map(|face| {
face.loops
.iter()
.flat_map(|loop_record| loop_record.coedges.iter().map(|coedge| coedge.id).chain([loop_record.id]))
}))
.max()
.unwrap_or(0)
+ 1;
let mut vertex_ids = [0u64; 4];
for (slot, (u, v)) in [(su0, sv0), (su1, sv0), (su1, sv1), (su0, sv1)].into_iter().enumerate() {
solid.vertices.push(VertexRecord {
id: next_id,
point: surface.evaluate(u, v).or_refuse(KernelStage::Refine, "evaluate")?,
});
vertex_ids[slot] = next_id;
next_id += 1;
}
let flat = |u: f64, v: f64| Vec3::new(u, v, 0.0);
let isolines = [
(surface.iso_curve_v(sv0).or_refuse(KernelStage::Refine, "iso_curve_v")?, vertex_ids[0], vertex_ids[1], flat(su0, sv0), flat(su1, sv0), true),
(surface.iso_curve_u(su1).or_refuse(KernelStage::Refine, "iso_curve_u")?, vertex_ids[1], vertex_ids[2], flat(su1, sv0), flat(su1, sv1), true),
(surface.iso_curve_v(sv1).or_refuse(KernelStage::Refine, "iso_curve_v")?, vertex_ids[3], vertex_ids[2], flat(su1, sv1), flat(su0, sv1), false),
(surface.iso_curve_u(su0).or_refuse(KernelStage::Refine, "iso_curve_u")?, vertex_ids[0], vertex_ids[3], flat(su0, sv1), flat(su0, sv0), false),
];
let mut coedges = Vec::with_capacity(4);
for (curve, start, end, from, to, forward) in isolines {
let [t0, t1] = curve.domain().or_refuse(KernelStage::Refine, "domain")?;
let edge_id = next_id;
next_id += 1;
solid.edges.push(EdgeRecord {
id: edge_id,
curve,
t0,
t1,
start_vertex_id: start,
end_vertex_id: end,
degenerate: false,
name: None,
});
coedges.push(CoedgeRecord {
id: next_id,
edge_id,
forward,
pcurve: crate::make_line(from, to).or_refuse(KernelStage::Refine, "make_line")?,
});
next_id += 1;
}
if parameter_space_area(face).or_refuse(KernelStage::Refine, "parameter_space_area")? < 0.0 {
coedges.reverse();
for coedge in &mut coedges {
coedge.forward = !coedge.forward;
coedge.pcurve = coedge.pcurve.reversed().or_refuse(KernelStage::Refine, "reversed")?;
}
}
let mut rebuilt = face.clone();
rebuilt.loops = vec![LoopRecord { id: next_id, coedges }];
for shell in &mut solid.shells {
for existing in &mut shell.faces {
if existing.id == face.id {
*existing = rebuilt.clone();
}
}
}
Ok(solid)
}
pub(super) fn open_profile_has_construction_headroom(face: &FaceRecord) -> Result<bool, KernelRefusal> {
let surface = &face.surface;
if surface.degree_u <= 1 || surface.degree_v <= 1 || surface.analytic().is_some()
|| face.loops.len() != 1 || face.loops[0].coedges.len() != 4
|| surface.control_points.iter().flatten().any(|point| point.w <= 0.0)
{
return Ok(false);
}
let (closed_u, closed_v) = surface.closed_directions().or_refuse(KernelStage::Refine, "closed_directions")?;
if closed_u || closed_v { return Ok(false); }
let [u0,u1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0,v1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let [a,b,c,d] = crate::offset_regularity::TrimRegion::from_face(face)?.bounds();
let eu = (u1-u0)*1e-9;
let ev = (v1-v0)*1e-9;
if a <= u0+eu || b >= u1-eu || (c-v0).abs() > ev || (d-v1).abs() > ev {
return Ok(false);
}
let mut sides = 0u8;
for coedge in &face.loops[0].coedges {
let controls = &coedge.pcurve.control_points;
if controls.iter().any(|p| p.w <= 0.0) { return Ok(false); }
let side = if controls.iter().all(|p| (p.x/p.w-a).abs() <= eu) { 1 }
else if controls.iter().all(|p| (p.x/p.w-b).abs() <= eu) { 2 }
else if controls.iter().all(|p| (p.y/p.w-c).abs() <= ev) { 4 }
else if controls.iter().all(|p| (p.y/p.w-d).abs() <= ev) { 8 }
else { return Ok(false); };
if sides & side != 0 { return Ok(false); }
sides |= side;
}
Ok(sides == 15)
}
pub(super) fn open_section_construction(
source: &BrepSolid,
face: &FaceRecord,
carrier: &BrepSolid,
extension: &crate::CarrierExtension,
distance: f64,
) -> Result<Option<BrepSolid>, KernelRefusal> {
if !extension.is_active() { return Ok(None); }
if face.surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
if face.loops.len() == 1
&& (face.loops[0].coedges.len() > 4 || outline_has_free_element(source, face)?)
{
return Ok(Some(full_rectangle_face(carrier, &carrier.shells[0].faces[0])?));
}
return Ok(None);
}
if (extension.u_min == 0.0 && extension.u_max == 0.0)
|| !open_profile_has_construction_headroom(face)?
{
return Ok(None);
}
let rectangle = full_rectangle_face(source, face)?;
let full_face = rectangle.shells.iter().flat_map(|shell| &shell.faces).find(|other| other.id == face.id).unwrap();
if carve_folded_support(&rectangle, full_face, distance)?.is_some() {
return Ok(None);
}
Ok(Some(full_rectangle_face(carrier, &carrier.shells[0].faces[0])?))
}
pub(super) fn open_profile_end_neighbours(
index: usize,
carriers: &[Carrier],
source_faces: &[&FaceRecord],
) -> Result<Option<Vec<usize>>, KernelRefusal> {
let Some(face) = source_faces.iter().find(|face| face.id == carriers[index].source_face_id) else { return Ok(None); };
if !open_profile_has_construction_headroom(face)? { return Ok(None); }
let [a,b,_,_] = crate::offset_regularity::TrimRegion::from_face(face)?.bounds();
let eu = (b-a)*1e-9;
let mut ends = Vec::new();
for coedge in &face.loops[0].coedges {
let Some((mate, mate_use)) = junction_mate(source_faces, face, coedge.edge_id) else { return Ok(None); };
if !mate.surface.is_affine().or_refuse(KernelStage::Select, "is_affine")? { return Ok(None); }
let Some((other, _)) = carriers.iter().enumerate().find(|(_, carrier)|
carrier.source_face_id == mate.id && matches!(carrier.kind, OffsetFaceRole::Offset))
else { return Ok(None); };
let at_end = [a,b].iter().any(|u| coedge.pcurve.control_points.iter().all(|p| (p.x/p.w-u).abs() <= eu));
if at_end {
ends.push(other);
} else if !uses_are_tangent(face, coedge, mate, mate_use)? {
return Ok(None);
}
}
Ok((ends.len() == 2).then_some(ends))
}
fn convex_sphere_source(face: &FaceRecord) -> Result<Option<crate::NurbsSurface>, KernelRefusal> {
let canonical = std::env::var("BREP_OFFSET_SPHERE_CHART_PARITY").as_deref() != Ok("0")
&& face.surface.analytic().and_then(|shape| shape.sphere_geometry()).is_some();
let turned = if canonical { face.surface.clone() }
else { crate::offset::transpose_offset_surface(&face.surface)? };
let Some((center, _)) = turned.analytic().and_then(|shape| shape.sphere_geometry()) else { return Ok(None); };
let [u0,u1] = face.surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0,v1] = face.surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let (u,v) = ((u0+u1)*0.5, (v0+v1)*0.5);
let (Ok(point), Ok(normal)) = (face.surface.evaluate(u,v), face.surface.normal(u,v)) else { return Ok(None); };
let outward = normal.scale(if face.same_sense { 1.0 } else { -1.0 });
Ok((outward.dot(point.sub(center)) > 0.0).then_some(turned))
}
fn rebuild_carrier_full_domain(
carrier: &mut Carrier,
source_face: &FaceRecord,
weld_band: f64,
distance: f64,
index: usize,
reason: &str,
) -> Result<bool, KernelRefusal> {
let carrier_face = carrier.solid.shells[0].faces[0].clone();
let mut surface = carrier_face.surface.clone();
let [mut su0, mut su1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [mut sv0, mut sv1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let v_mid = (sv0 + sv1) * 0.5;
if surface
.evaluate(su0, v_mid).or_refuse(KernelStage::Refine, "evaluate")?
.sub(surface.evaluate(su1, v_mid).or_refuse(KernelStage::Refine, "evaluate")?)
.length()
> weld_band
{
if distance > 0.0 && std::env::var("BREP_OFFSET_SPHERE_SECTION_REACH").as_deref() != Ok("0") {
if let Some(transposed_source) = convex_sphere_source(source_face)?
.filter(|_| source_face.surface.analytic().is_none())
{
let mut normalized = carrier.clone();
let face = &mut normalized.solid.shells[0].faces[0];
face.surface = crate::offset::transpose_offset_surface(&face.surface)?;
face.same_sense = !face.same_sense;
for coedge in face.loops.iter_mut().flat_map(|lp| &mut lp.coedges) {
for control in &mut coedge.pcurve.control_points {
std::mem::swap(&mut control.x, &mut control.y);
}
}
let mut source = source_face.clone();
source.surface = transposed_source;
source.same_sense = !source.same_sense;
if rebuild_carrier_full_domain(&mut normalized, &source, weld_band, distance, index, reason)? {
os_debug!("carrier[{index}] sphere section reach: normalized periodic V chart");
*carrier = normalized;
return Ok(true);
}
}
}
return Ok(false);
}
if let Some(crate::AnalyticSurface::Sphere { frame, radius }) =
crate::analytic_surface::recognize(&source_face.surface)
{
let mid = surface.evaluate((su0 + su1) * 0.5, (sv0 + sv1) * 0.5).or_refuse(KernelStage::Refine, "evaluate")?;
let measured = mid.sub(frame.origin).length();
let grown = radius + distance.abs();
let shrunk = (radius - distance.abs()).abs();
let target = if (measured - grown).abs() <= (measured - shrunk).abs() {
grown
} else {
shrunk
};
if (measured - target).abs() <= weld_band.max(distance.abs() * 0.5) && target > weld_band {
let full = crate::make_sphere_surface(frame.origin, target, frame.axis).or_refuse(KernelStage::Refine, "make_sphere_surface")?;
let [fu0, fu1] = full.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [fv0, fv1] = full.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let old_normal = surface.normal((su0 + su1) * 0.5, (sv0 + sv1) * 0.5).or_refuse(KernelStage::Refine, "normal")?;
let radial = mid.sub(frame.origin).normalized().or_refuse(KernelStage::Refine, "normalized")?;
let sample = full.evaluate((fu0 + fu1) * 0.5, (fv0 + fv1) * 0.5).or_refuse(KernelStage::Refine, "evaluate")?;
let new_normal = full.normal((fu0 + fu1) * 0.5, (fv0 + fv1) * 0.5).or_refuse(KernelStage::Refine, "normal")?;
let new_radial = sample.sub(frame.origin).normalized().or_refuse(KernelStage::Refine, "normalized")?;
if (old_normal.dot(radial) > 0.0) == (new_normal.dot(new_radial) > 0.0) {
os_debug!("carrier[{index}] rebuilt on the exact full offset sphere r={target:.6}");
surface = full;
(su0, su1, sv0, sv1) = (fu0, fu1, fv0, fv1);
} else {
os_debug!("carrier[{index}] sphere rebuild skipped: orientation mismatch");
}
} else {
os_debug!(
"carrier[{index}] sphere rebuild skipped: measured radius {measured:.6} \
matches neither grown nor shrunk offset of {radius:.6}"
);
}
}
let winding = parameter_space_area(&carrier_face).or_refuse(KernelStage::Refine, "parameter_space_area")?;
let pole_point = |iso: &crate::NurbsCurve| -> Result<Option<Vec3>, KernelRefusal> {
let [t0, t1] = iso.domain().or_refuse(KernelStage::Refine, "domain")?;
let anchor = iso.evaluate(t0).or_refuse(KernelStage::Refine, "evaluate")?;
let mut deviation = 0.0f64;
for sample in 1..=8 {
let point = iso.evaluate(t0 + (t1 - t0) * sample as f64 / 8.0).or_refuse(KernelStage::Refine, "evaluate")?;
deviation = deviation.max(point.sub(anchor).length());
}
os_debug!(" pole probe: max deviation {deviation:.6}");
Ok((deviation <= weld_band).then_some(anchor))
};
let bottom_iso = surface.iso_curve_v(sv0).or_refuse(KernelStage::Refine, "iso_curve_v")?;
let top_iso = surface.iso_curve_v(sv1).or_refuse(KernelStage::Refine, "iso_curve_v")?;
let bottom_pole = pole_point(&bottom_iso)?;
let top_pole = pole_point(&top_iso)?;
let corner_bottom = surface.evaluate(su0, sv0).or_refuse(KernelStage::Refine, "evaluate")?;
let corner_top = surface.evaluate(su0, sv1).or_refuse(KernelStage::Refine, "evaluate")?;
let seam = surface.iso_curve_u(su0).or_refuse(KernelStage::Refine, "iso_curve_u")?;
let v_end_edge = |id: u64,
iso: crate::NurbsCurve,
pole: Option<Vec3>,
vertex: u64|
-> Result<EdgeRecord, KernelRefusal> {
Ok(match pole {
Some(point) => EdgeRecord {
id,
curve: crate::make_line(point, point).or_refuse(KernelStage::Refine, "make_line")?,
t0: 0.0,
t1: 1.0,
start_vertex_id: vertex,
end_vertex_id: vertex,
degenerate: true,
name: None,
},
None => {
let [t0, t1] = iso.domain().or_refuse(KernelStage::Refine, "domain")?;
EdgeRecord {
id,
curve: iso,
t0,
t1,
start_vertex_id: vertex,
end_vertex_id: vertex,
degenerate: false,
name: None,
}
}
})
};
let flat = |u: f64, v: f64| Vec3::new(u, v, 0.0);
let mut coedges = vec![
CoedgeRecord {
id: 1,
edge_id: 1,
forward: true,
pcurve: crate::make_line(flat(su0, sv0), flat(su1, sv0)).or_refuse(KernelStage::Refine, "make_line")?,
},
CoedgeRecord {
id: 2,
edge_id: 3,
forward: true,
pcurve: crate::make_line(flat(su1, sv0), flat(su1, sv1)).or_refuse(KernelStage::Refine, "make_line")?,
},
CoedgeRecord {
id: 3,
edge_id: 2,
forward: false,
pcurve: crate::make_line(flat(su1, sv1), flat(su0, sv1)).or_refuse(KernelStage::Refine, "make_line")?,
},
CoedgeRecord {
id: 4,
edge_id: 3,
forward: false,
pcurve: crate::make_line(flat(su0, sv1), flat(su0, sv0)).or_refuse(KernelStage::Refine, "make_line")?,
},
];
if winding < 0.0 {
coedges.reverse();
for coedge in &mut coedges {
coedge.forward = !coedge.forward;
coedge.pcurve = coedge.pcurve.reversed().or_refuse(KernelStage::Refine, "reversed")?;
}
}
os_debug!(
"carrier[{index}] src={} extended to full domain past {reason} \
(poles: bottom={} top={})",
carrier.source_face_id,
bottom_pole.is_some(),
top_pole.is_some(),
);
carrier.solid = BrepSolid {
mass_properties_cache: Default::default(),
cells: Default::default(),
id: carrier.solid.id,
vertices: vec![
VertexRecord {
id: 1,
point: corner_bottom,
},
VertexRecord {
id: 2,
point: corner_top,
},
],
edges: vec![
v_end_edge(1, bottom_iso, bottom_pole, 1)?,
v_end_edge(2, top_iso, top_pole, 2)?,
EdgeRecord {
id: 3,
curve: seam,
t0: sv0,
t1: sv1,
start_vertex_id: 1,
end_vertex_id: 2,
degenerate: false,
name: None,
},
],
shells: vec![ShellRecord {
id: 1,
faces: vec![FaceRecord {
id: carrier_face.id,
surface,
same_sense: carrier_face.same_sense,
loops: vec![LoopRecord { id: 1, coedges }],
name: carrier_face.name.clone(),
}],
}],
genus: 0,
kind: crate::BodyKind::Solid,
};
Ok(true)
}
pub(super) fn rebuild_reflex_rim_carriers(
carriers: &mut [Carrier],
source: &BrepSolid,
source_faces: &[&FaceRecord],
smooth_pairs: &HashSet<(usize, usize)>,
scale: f64,
distance: f64,
) -> Result<HashSet<usize>, KernelRefusal> {
let weld_band = 2e-3f64.max(scale * 5e-5);
let mut rebuilt = HashSet::default();
for index in 0..carriers.len() {
if !matches!(carriers[index].kind, OffsetFaceRole::Offset) {
continue;
}
let Some(source_face) = source_faces
.iter()
.find(|face| face.id == carriers[index].source_face_id)
else {
continue;
};
if distance > 0.0 && std::env::var("BREP_OFFSET_SPHERE_SECTION_REACH").as_deref() != Ok("0")
&& face_reflex_miter_tan(source, source_face)?.is_some()
&& (source_face.surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")?
|| (source_face.surface.degree_u == 1 && source_face.surface.control_points.len() == 2))
{
let mut spherical_neighbour = false;
for neighbour in source_faces {
if !source_faces_adjacent(source_face, neighbour) {
continue;
}
if convex_sphere_source(neighbour)?.is_some() {
spherical_neighbour = true;
break;
}
}
if spherical_neighbour && rectangle_preserves_smooth_sides(
index, source_face, carriers, source_faces, smooth_pairs)?
{
let construction = if source_face.surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
carrier_solid(source, source_face.id, distance, 0.0)?
} else {
carriers[index].solid.clone()
};
carriers[index].solid = full_rectangle_face(
&construction, &construction.shells[0].faces[0])?;
os_debug!("carrier[{index}] src={} continued across cropped sphere junction", source_face.id);
rebuilt.insert(index);
continue;
}
}
if smooth_pairs
.iter()
.any(|(first, second)| *first == index || *second == index)
{
continue;
}
if source_face.surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
continue;
}
if face_reflex_miter_tan(source, source_face)?.is_none() {
continue;
}
if rebuild_carrier_full_domain(
&mut carriers[index],
source_face,
weld_band,
distance,
index,
"a reflex junction rim",
)? {
rebuilt.insert(index);
}
}
Ok(rebuilt)
}
fn rectangle_preserves_smooth_sides(
index: usize,
face: &FaceRecord,
carriers: &[Carrier],
source_faces: &[&FaceRecord],
smooth_pairs: &HashSet<(usize, usize)>,
) -> Result<bool, KernelRefusal> {
let [u0,u1] = face.surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0,v1] = face.surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
for &(first, second) in smooth_pairs {
let mate = if first == index { second } else if second == index { first } else { continue; };
let Some(other) = source_faces.iter().find(|other| other.id == carriers[mate].source_face_id) else { return Ok(false); };
for coedge in face.loops.iter().flat_map(|lp| &lp.coedges) {
if !other.loops.iter().flat_map(|lp| &lp.coedges).any(|other| other.edge_id == coedge.edge_id) {
continue;
}
let controls = &coedge.pcurve.control_points;
if controls.iter().any(|point| point.w <= 0.0) { return Ok(false); }
let [t0,t1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let a = coedge.pcurve.evaluate(t0).or_refuse(KernelStage::Refine, "evaluate")?;
let b = coedge.pcurve.evaluate(t1).or_refuse(KernelStage::Refine, "evaluate")?;
let eu = (u1-u0).abs()*1e-9;
let ev = (v1-v0).abs()*1e-9;
let u_side = [u0,u1].iter().any(|u| controls.iter().all(|p| (p.x/p.w-u).abs() <= eu))
&& (a.y.min(b.y)-v0).abs() <= ev && (a.y.max(b.y)-v1).abs() <= ev;
let v_side = [v0,v1].iter().any(|v| controls.iter().all(|p| (p.y/p.w-v).abs() <= ev))
&& (a.x.min(b.x)-u0).abs() <= eu && (a.x.max(b.x)-u1).abs() <= eu;
if !u_side && !v_side { return Ok(false); }
}
}
Ok(true)
}
pub(super) fn continued_sphere_neighbours(
index: usize,
carriers: &[Carrier],
source_faces: &[&FaceRecord],
scale: f64,
) -> Result<Option<Vec<usize>>, KernelRefusal> {
if std::env::var("BREP_OFFSET_SPHERE_SECTION_REACH").as_deref() == Ok("0") {
return Ok(None);
}
let own = &carriers[index];
let Some(source) = source_faces.iter().find(|face| face.id == own.source_face_id) else { return Ok(None); };
if (source.surface.analytic().is_some()
&& std::env::var("BREP_OFFSET_SPHERE_CHART_PARITY").as_deref() == Ok("0"))
|| own.solid.shells[0].faces[0].surface.analytic().and_then(|shape| shape.sphere_geometry()).is_none()
{
return Ok(None);
}
if convex_sphere_source(source)?.is_none() { return Ok(None); }
let mut neighbours = Vec::new();
for face in source_faces.iter().filter(|face| face.id != source.id && source_faces_adjacent(source, face)) {
let Some((other, _)) = carriers.iter().enumerate().find(|(_, carrier)|
carrier.source_face_id == face.id && matches!(carrier.kind, OffsetFaceRole::Offset))
else { return Ok(None); };
if !face.surface.is_affine().or_refuse(KernelStage::Select, "is_affine")? {
let surface = &face.surface;
if surface.degree_u != 1 || surface.degree_v != 2
|| surface.control_points.len() != 2 || surface.control_points[0].len() != 3
{
return Ok(None);
}
let rows = &surface.control_points;
let translation = rows[1][0].point().or_refuse(KernelStage::Select, "point")?
.sub(rows[0][0].point().or_refuse(KernelStage::Select, "point")?);
for column in 0..3 {
let a = rows[0][column];
let b = rows[1][column];
if a.w <= 0.0 || b.w <= 0.0 || (a.w-b.w).abs() > 1e-12
|| b.point().or_refuse(KernelStage::Select, "point")?
.sub(a.point().or_refuse(KernelStage::Select, "point")?)
.sub(translation).length() > scale*1e-9
{
return Ok(None);
}
}
}
neighbours.push(other);
}
Ok((neighbours.len() >= 2).then_some(neighbours))
}
pub(super) fn outside_continued_neighbours(
point: Vec3,
neighbours: &[usize],
carriers: &[Carrier],
tolerance: f64,
) -> Result<bool, KernelRefusal> {
for &index in neighbours {
let face = &carriers[index].solid.shells[0].faces[0];
let foot = project_point_to_surface(&face.surface, point).or_refuse(KernelStage::Select, "project_point_to_surface")?;
let normal = face.surface.normal(foot.u, foot.v).or_refuse(KernelStage::Select, "normal")?
.scale(if face.same_sense { 1.0 } else { -1.0 });
if point.sub(foot.point).dot(normal) > tolerance {
return Ok(true);
}
}
Ok(false)
}
pub(super) struct JunctionStation {
pub normal: Vec3,
pub mate_normal: Vec3,
pub tangent: Vec3,
}
impl JunctionStation {
pub fn is_convex(&self) -> bool {
self.normal.cross(self.mate_normal).dot(self.tangent) > 0.0
}
pub fn miter_tan(&self) -> f64 {
let cos_normals = self.normal.dot(self.mate_normal).clamp(-1.0, 1.0);
(cos_normals.acos() * 0.5).tan()
}
}
pub(super) fn junction_stations(
face: &FaceRecord,
coedge: &CoedgeRecord,
edge: &EdgeRecord,
mate: &FaceRecord,
) -> Result<Vec<JunctionStation>, KernelRefusal> {
let mut stations = Vec::new();
for fraction in [0.1f64, 0.3, 0.5, 0.7, 0.9] {
let t_sample = edge.t0 + (edge.t1 - edge.t0) * fraction;
let step = ((edge.t1 - edge.t0) * 1e-3).max(1e-9);
let before = edge.curve.evaluate(t_sample - step).or_refuse(KernelStage::Refine, "evaluate")?;
let after = edge.curve.evaluate(t_sample + step).or_refuse(KernelStage::Refine, "evaluate")?;
let mut tangent = after.sub(before);
if tangent.length() <= 1e-12 {
continue;
}
if !coedge.forward {
tangent = tangent.scale(-1.0);
}
let station = edge.curve.evaluate(t_sample).or_refuse(KernelStage::Refine, "evaluate")?;
let uv_this = {
let projection = project_point_to_surface(&face.surface, station).or_refuse(KernelStage::Refine, "project_point_to_surface")?;
Vec2 {
x: projection.u,
y: projection.v,
}
};
let uv_mate = {
let projection = project_point_to_surface(&mate.surface, station).or_refuse(KernelStage::Refine, "project_point_to_surface")?;
Vec2 {
x: projection.u,
y: projection.v,
}
};
let normal = face_normal(face, uv_this.x, uv_this.y)?;
let mate_normal = face_normal(mate, uv_mate.x, uv_mate.y)?;
let cross = normal.cross(mate_normal);
if cross.length() <= 1e-6 || normal.dot(mate_normal) >= SMOOTH_JUNCTION_COS {
continue;
}
stations.push(JunctionStation {
normal,
mate_normal,
tangent,
});
}
Ok(stations)
}
pub(super) fn junction_mate<'a>(
faces: &[&'a FaceRecord],
face: &FaceRecord,
edge_id: u64,
) -> Option<(&'a FaceRecord, &'a CoedgeRecord)> {
faces.iter().find_map(|other| {
if other.id == face.id {
return None;
}
other
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.find(|other_coedge| other_coedge.edge_id == edge_id)
.map(|other_coedge| (*other, other_coedge))
})
}
pub(super) fn face_reflex_miter_tan(source: &BrepSolid, face: &FaceRecord) -> Result<Option<f64>, KernelRefusal> {
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let faces = source
.shells
.iter()
.flat_map(|shell| &shell.faces)
.collect::<Vec<_>>();
let mut worst: Option<f64> = None;
for loop_record in &face.loops {
for coedge in &loop_record.coedges {
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((mate, _)) = junction_mate(&faces, face, coedge.edge_id) else {
continue;
};
for station in junction_stations(face, coedge, edge, mate)? {
if !station.is_convex() {
worst = Some(worst.unwrap_or(0.0).max(station.miter_tan()));
}
}
}
}
Ok(worst)
}
pub(super) fn outward_miter_scale(worst_tan: Option<f64>) -> f64 {
match worst_tan {
Some(tan) if tan > 1.0 + 1e-6 => (tan * 1.5).min(12.0),
_ => 1.0,
}
}
pub(super) fn outward_carrier_extension(
source: &BrepSolid,
face: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
amount: f64,
) -> Result<crate::CarrierExtension, KernelRefusal> {
let [u0, u1] = face.surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0, v1] = face.surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut uses: Vec<(Vec<Vec2>, bool, Option<f64>)> = Vec::new();
let mut low = Vec2 {
x: f64::INFINITY,
y: f64::INFINITY,
};
let mut high = Vec2 {
x: f64::NEG_INFINITY,
y: f64::NEG_INFINITY,
};
for coedge in face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
if edge_by_id
.get(&coedge.edge_id)
.is_some_and(|edge| edge.degenerate)
{
continue;
}
let [p0, p1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let samples = (0..=24)
.map(|sample| {
coedge
.pcurve
.evaluate(p0 + (p1 - p0) * sample as f64 / 24.0)
.map(|uv| Vec2 { x: uv.x, y: uv.y })
.or_refuse(KernelStage::Refine, "evaluate")
})
.collect::<Result<Vec<_>, KernelRefusal>>()?;
for uv in &samples {
low.x = low.x.min(uv.x);
low.y = low.y.min(uv.y);
high.x = high.x.max(uv.x);
high.y = high.y.max(uv.y);
}
let mate = junction_mate(source_faces, face, coedge.edge_id)
.filter(|(mate, _)| !opening_set.contains(&mate.id));
let (smooth, convex_tan) = match mate {
Some((mate, mate_use)) => {
let smooth = uses_are_tangent(face, coedge, mate, mate_use)?;
let convex_tan = match edge_by_id.get(&coedge.edge_id) {
Some(edge) if !smooth => junction_stations(face, coedge, edge, mate)?
.iter()
.filter(|station| station.is_convex())
.map(JunctionStation::miter_tan)
.fold(None, |worst: Option<f64>, tan| {
Some(worst.unwrap_or(0.0).max(tan))
}),
_ => None,
};
(smooth, convex_tan)
}
None => (false, None),
};
uses.push((samples, smooth, convex_tan));
}
if uses.is_empty() || !low.x.is_finite() || !high.x.is_finite() {
return Ok(crate::CarrierExtension::uniform(amount));
}
let worst_of = |first: Option<f64>, tan: Option<f64>| -> Option<f64> {
match (first, tan) {
(Some(a), Some(b)) => Some(a.max(b)),
(a, b) => a.or(b),
}
};
let face_worst = uses.iter().fold(None, |worst, (_, _, convex_tan)| {
worst_of(worst, *convex_tan)
});
let side = |axis: fn(&Vec2) -> f64, value: f64, span: f64| -> f64 {
let band = (span.abs() * 1e-5).max(1e-9);
let mut along = 0usize;
let mut smooth_along = 0usize;
let mut worst_along: Option<f64> = None;
for (samples, smooth, convex_tan) in &uses {
let on = samples
.iter()
.filter(|uv| (axis(uv) - value).abs() <= band)
.count();
if on * 10 >= samples.len() * 9 {
along += 1;
if *smooth {
smooth_along += 1;
}
worst_along = worst_of(worst_along, *convex_tan);
}
}
if along > 0 && along == smooth_along {
return 0.0;
}
let worst = if along > 0 { worst_along } else { face_worst };
amount * outward_miter_scale(worst)
};
Ok(crate::CarrierExtension {
u_min: side(|uv| uv.x, low.x, u1 - u0),
u_max: side(|uv| uv.x, high.x, u1 - u0),
v_min: side(|uv| uv.y, low.y, v1 - v0),
v_max: side(|uv| uv.y, high.y, v1 - v0),
})
}
pub(super) fn outward_wall_pad(
source: &BrepSolid,
opening: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
amount: f64,
) -> Result<f64, KernelRefusal> {
let (worst, _) =
opening_wall_extension_factor(source, opening, source_faces, opening_set, true)?;
Ok(amount * outward_miter_scale(worst))
}
pub(super) fn inward_wall_pad(
source: &BrepSolid,
opening: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
amount: f64,
) -> Result<Option<(f64, HashSet<u64>)>, KernelRefusal> {
let (worst, neighbors) =
opening_wall_extension_factor(source, opening, source_faces, opening_set, false)?;
Ok(worst.map(|scale| (amount * (scale * 1.5).min(12.0), neighbors)))
}
fn opening_wall_extension_factor(
source: &BrepSolid,
opening: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
convex: bool,
) -> Result<(Option<f64>, HashSet<u64>), KernelRefusal> {
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut worst: Option<f64> = None;
let mut neighbors = HashSet::default();
for coedge in opening
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((mate, _)) = junction_mate(source_faces, opening, coedge.edge_id)
.filter(|(mate, _)| !opening_set.contains(&mate.id))
else {
continue;
};
for station in junction_stations(opening, coedge, edge, mate)? {
if station.is_convex() != convex {
continue;
}
let sin_dihedral = station.normal.cross(station.mate_normal).length();
if sin_dihedral <= 1e-6 {
continue;
}
neighbors.insert(mate.id);
worst = Some(worst.unwrap_or(0.0).max(1.0 / sin_dihedral));
}
}
Ok((worst, neighbors))
}
pub(super) fn ruled_opening(face: &FaceRecord) -> Result<bool, KernelRefusal> {
let surface = &face.surface;
Ok(face.loops.len() == 1
&& !surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")?
&& surface.degree_v == 1
&& surface.control_points.len() >= 3
&& surface.control_points.iter().all(|row| row.len() == 2))
}
pub(super) fn extended_ruled_wall(
source: &BrepSolid,
face: &FaceRecord,
pad: f64,
tolerance: f64,
) -> Result<Result<RuledWall, String>, KernelRefusal> {
use crate::{NurbsSurface, SurfaceSide};
let surface = &face.surface;
let [u0, u1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0, v1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let loops = face
.loops
.iter()
.map(|record| {
record
.coedges
.iter()
.map(|coedge| coedge.pcurve.clone())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
if !crate::thicken::band::whole_face(&loops, [u0, u1], [v0, v1])? {
return Ok(Err("the opening's trim is not the whole face".into()));
}
let mut wall = standalone_face(source, face.id)?;
if wall.edges.iter().any(|edge| edge.degenerate) {
return Ok(Err("the opening has a degenerate edge".into()));
}
let (closed_u, closed_v) = surface.closed_directions().or_refuse(KernelStage::Refine, "closed_directions")?;
const STATIONS: usize = 8;
let side_growth = |grown: &NurbsSurface, side: SurfaceSide, delta: f64| -> Result<f64, KernelRefusal> {
let mut least = f64::INFINITY;
for index in 0..=STATIONS {
let fraction = index as f64 / STATIONS as f64;
let (old, new) = match side {
SurfaceSide::UMin | SurfaceSide::UMax => {
let v = v0 + (v1 - v0) * fraction;
let u = if matches!(side, SurfaceSide::UMin) { u0 } else { u1 };
let step = if matches!(side, SurfaceSide::UMin) { -delta } else { delta };
((u, v), (u + step, v))
}
SurfaceSide::VMin | SurfaceSide::VMax => {
let u = u0 + (u1 - u0) * fraction;
let v = if matches!(side, SurfaceSide::VMin) { v0 } else { v1 };
let step = if matches!(side, SurfaceSide::VMin) { -delta } else { delta };
((u, v), (u, v + step))
}
};
let from = grown.evaluate(old.0, old.1).or_refuse(KernelStage::Refine, "evaluate")?;
let to = grown.evaluate(new.0, new.1).or_refuse(KernelStage::Refine, "evaluate")?;
least = least.min(to.sub(from).length());
}
Ok(least)
};
let mut grown = surface.clone();
let mut deltas = [0.0f64; 4];
let mut reached = [0.0f64; 4];
for (slot, side) in [
SurfaceSide::UMin,
SurfaceSide::UMax,
SurfaceSide::VMin,
SurfaceSide::VMax,
]
.into_iter()
.enumerate()
{
if (side.is_u() && closed_u) || (!side.is_u() && closed_v) {
continue;
}
let mut speed = f64::INFINITY;
for index in 0..=STATIONS {
let fraction = index as f64 / STATIONS as f64;
let (u, v) = match side {
SurfaceSide::UMin => (u0, v0 + (v1 - v0) * fraction),
SurfaceSide::UMax => (u1, v0 + (v1 - v0) * fraction),
SurfaceSide::VMin => (u0 + (u1 - u0) * fraction, v0),
SurfaceSide::VMax => (u0 + (u1 - u0) * fraction, v1),
};
let derivatives = surface.derivatives(u, v, 1).or_refuse(KernelStage::Refine, "derivatives")?;
let along = if side.is_u() { derivatives[1][0] } else { derivatives[0][1] };
speed = speed.min(along.length());
}
if !(speed > 0.0) {
return Ok(Err(format!("the opening's {side:?} side has no speed to grow along")));
}
let mut delta = pad / speed;
let mut reach = pad;
let mut attempt = None;
for _ in 0..8 {
let candidate = match grown.extend_natural(side, delta) {
Ok(candidate) => candidate,
Err(crate::ExtendRefusal::ExcessiveGrowth { ratio, limit }) if ratio > limit => {
let shrink = limit / ratio * 0.95;
reach *= shrink;
delta *= shrink;
continue;
}
Err(refusal) => {
return Ok(Err(format!(
"the opening cannot be continued past its {side:?} side by {reach:.6}: {refusal}"
)));
}
};
let growth = side_growth(&candidate, side, delta)?;
if growth >= reach * (1.0 - 1e-9) {
attempt = Some((candidate, growth));
break;
}
delta *= (reach / growth.max(reach * 1e-3)) * 1.05;
}
let Some((candidate, growth)) = attempt else {
return Ok(Err(format!(
"the opening's {side:?} side does not reach {reach:.6} along its continuation"
)));
};
reached[slot] = growth;
grown = candidate;
deltas[slot] = delta;
}
let remap = |knots: &[f64], low: f64, high: f64, start: f64, end: f64| -> Vec<f64> {
knots
.iter()
.map(|knot| start + (knot - low) * (end - start) / (high - low))
.collect()
};
let surface = NurbsSurface::new(
grown.degree_u,
grown.degree_v,
remap(&grown.knots_u, u0 - deltas[0], u1 + deltas[1], u0, u1),
remap(&grown.knots_v, v0 - deltas[2], v1 + deltas[3], v0, v1),
grown.control_points.clone(),
).or_refuse(KernelStage::Refine, "new")?;
let wall_face = &mut wall.shells[0].faces[0];
wall_face.surface = surface.clone();
let uses = wall_face
.loops
.iter()
.flat_map(|record| &record.coedges)
.cloned()
.collect::<Vec<_>>();
for edge in &mut wall.edges {
let Some(coedge) = uses.iter().find(|coedge| coedge.edge_id == edge.id) else {
continue;
};
let image = crate::image_curve(&surface, &coedge.pcurve, tolerance, "offset_shell ruled wall").or_refuse(KernelStage::Refine, "image_curve")?;
let (mut first, mut last) = (image.t0, image.t1);
if !coedge.forward {
std::mem::swap(&mut first, &mut last);
}
let mut curve = image.curve;
if first > last {
let [start, end] = curve.domain().or_refuse(KernelStage::Refine, "domain")?;
curve = curve.reversed().or_refuse(KernelStage::Refine, "reversed")?;
first = start + end - first;
last = start + end - last;
}
let (start_point, end_point) = (curve.evaluate(first).or_refuse(KernelStage::Refine, "evaluate")?, curve.evaluate(last).or_refuse(KernelStage::Refine, "evaluate")?);
for vertex in &mut wall.vertices {
if vertex.id == edge.start_vertex_id {
vertex.point = start_point;
} else if vertex.id == edge.end_vertex_id {
vertex.point = end_point;
}
}
edge.curve = curve;
edge.t0 = first;
edge.t1 = last;
}
Ok(Ok(RuledWall { solid: wall, reached }))
}
pub(super) struct RuledWall {
pub solid: BrepSolid,
pub reached: [f64; 4],
}
pub(super) fn outward_fold_the_carve_missed(
face: &FaceRecord,
distance: f64,
) -> Result<Option<crate::offset_regularity::FoldSample>, KernelRefusal> {
let enabled = !std::env::var("BREP_OFFSET_SPHERE_FOLD_CERT")
.is_ok_and(|value| value == "0" || value == "off");
outward_fold_the_carve_missed_with_certificate(face, distance, enabled)
}
pub(super) fn regular_outward_sphere(face: &FaceRecord, distance: f64) -> Result<bool, KernelRefusal> {
if !distance.is_finite() || distance >= 0.0 {
return Ok(false);
}
let geometry = crate::analytic_surface::recognize(&face.surface)
.and_then(|shape| shape.sphere_geometry());
let Some((center, radius)) = geometry else { return Ok(false); };
if !radius.is_finite() || radius <= 0.0 {
return Ok(false);
}
let [u0, u1] = face.surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0, v1] = face.surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let (u, v) = ((u0 + u1) * 0.5, (v0 + v1) * 0.5);
let radial = face.surface.evaluate(u, v).or_refuse(KernelStage::Refine, "evaluate")?.sub(center);
let normal = face.surface.normal(u, v).or_refuse(KernelStage::Refine, "normal")?;
let sense = if face.same_sense { 1.0 } else { -1.0 };
let alignment = sense * normal.dot(radial) / radius;
if !alignment.is_finite() || alignment.abs() < 0.5 {
return Ok(false);
}
let factor = 1.0 - distance * alignment.signum() / radius;
Ok(factor.is_finite() && factor > COLLAPSE_FACTOR)
}
pub(super) fn outward_fold_the_carve_missed_with_certificate(
face: &FaceRecord,
distance: f64,
enabled: bool,
) -> Result<Option<crate::offset_regularity::FoldSample>, KernelRefusal> {
use crate::offset_regularity::{fold_sample_at, scan_offset_regularity, TrimRegion};
if distance >= 0.0 || face.surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
return Ok(None);
}
if enabled && regular_outward_sphere(face, distance)? {
return Ok(None);
}
let region = TrimRegion::from_face(face)?;
let displacement = [shell_displacement(face, distance)];
let scan = scan_offset_regularity(
&face.surface,
®ion,
&displacement,
COLLAPSE_FACTOR,
UNSEEN_FOLD_BUDGET,
)?;
let Some(mut worst) = scan.worst else {
return Ok(None);
};
let [u_low, u_high, v_low, v_high] = region.bounds();
let mut radius = [(u_high - u_low) / 64.0, (v_high - v_low) / 64.0];
let ratio = 0.5 * (5f64.sqrt() - 1.0);
for _ in 0..6 {
for along_u in [true, false] {
let (centre, low, high, reach) = if along_u {
(worst.u, u_low, u_high, radius[0])
} else {
(worst.v, v_low, v_high, radius[1])
};
let sample = |t: f64| {
let (u, v) = if along_u { (t, worst.v) } else { (worst.u, t) };
region
.contains(u, v)
.then(|| fold_sample_at(&face.surface, u, v, &displacement))
.flatten()
};
let (mut a, mut b) = ((centre - reach).max(low), (centre + reach).min(high));
for _ in 0..48 {
let (c, d) = (b - ratio * (b - a), a + ratio * (b - a));
match (sample(c), sample(d)) {
(Some(first), Some(second)) if first.factor < second.factor => b = d,
(Some(_), Some(_)) => a = c,
_ => break,
}
}
if let Some(polished) = sample(0.5 * (a + b)) {
if polished.factor < worst.factor {
worst = polished;
}
}
}
radius = [radius[0] * 0.5, radius[1] * 0.5];
}
Ok((worst.factor <= COLLAPSE_FACTOR).then_some(worst))
}
const UNSEEN_FOLD_BUDGET: crate::offset_regularity::ScanBudget =
crate::offset_regularity::ScanBudget {
per_span: 9,
steps_per_distance: 2.0,
max_nodes: 4096,
refine: true,
};
pub(super) fn fallshort_opening_reach(
source: &BrepSolid,
face: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
distance: f64,
) -> Result<Option<(bool, f64)>, KernelRefusal> {
if distance <= 0.0 || face.surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
return Ok(None);
}
let surface = &face.surface;
if surface.degree_v != 1 || surface.control_points.iter().any(|row| row.len() != 2) {
return Ok(None);
}
let [u0, u1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0, v1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let offsets = face_offsets(face);
let mut best: Option<(bool, f64)> = None;
for coedge in face.loops.iter().flat_map(|record| &record.coedges) {
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((opening, _)) = junction_mate(source_faces, face, coedge.edge_id)
.filter(|(mate, _)| opening_set.contains(&mate.id))
else {
continue;
};
if !surface_is_planar(&opening.surface, 1e-6 * crate::solid_scale(source).max(1.0))? {
continue;
}
let [ou0, ou1] = opening.surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [ov0, ov1] = opening.surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let (ou, ov) = ((ou0 + ou1) * 0.5, (ov0 + ov1) * 0.5);
let plane_point = opening.surface.evaluate(ou, ov).or_refuse(KernelStage::Refine, "evaluate")?;
let Ok(plane_normal) = face_normal(opening, ou, ov) else {
continue;
};
let [p0, p1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
for index in 0..=8 {
let uv = coedge.pcurve.evaluate(p0 + (p1 - p0) * index as f64 / 8.0).or_refuse(KernelStage::Refine, "evaluate")?;
let at_v_max = (uv.y - v1).abs() < (uv.y - v0).abs();
let u = uv.x.clamp(u0, u1);
let (near, far) = if at_v_max { (v0, v1) } else { (v1, v0) };
let image = |v: f64| offsets.at(u, v, -distance).map(|sample| sample.point).ok();
let (Some(from), Some(to)) = (image(near), image(far)) else {
continue;
};
let ruling = to.sub(from);
let length = ruling.length();
if length <= 1e-12 {
continue;
}
let direction = ruling.scale(1.0 / length);
let denominator = direction.dot(plane_normal);
if denominator.abs() <= 1e-9 {
continue;
}
let reach = plane_point.sub(to).dot(plane_normal) / denominator;
if reach <= 0.0 {
continue;
}
if best.is_none_or(|(_, worst)| reach > worst) {
best = Some((at_v_max, reach));
}
}
}
Ok(best)
}
pub(super) fn offset_falls_short_of_curved_opening(
source: &BrepSolid,
face: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
distance: f64,
) -> Result<Option<(u64, f64)>, KernelRefusal> {
if distance <= 0.0 {
return Ok(None);
}
let band = 2e-3f64.max(crate::solid_scale(source) * 5e-5);
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let offsets = face_offsets(face);
let mut worst: Option<(u64, f64)> = None;
for coedge in face.loops.iter().flat_map(|record| &record.coedges) {
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((opening, _)) = junction_mate(source_faces, face, coedge.edge_id)
.filter(|(mate, _)| opening_set.contains(&mate.id))
else {
continue;
};
if surface_is_planar(&opening.surface, band)? {
continue;
}
let [p0, p1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
for index in 0..=8 {
let uv = coedge.pcurve.evaluate(p0 + (p1 - p0) * index as f64 / 8.0).or_refuse(KernelStage::Refine, "evaluate")?;
let Ok(image) = offsets.at(uv.x, uv.y, -distance) else {
continue;
};
let foot = project_point_to_surface(&opening.surface, image.point).or_refuse(KernelStage::Refine, "project_point_to_surface")?;
let Ok(outward) = face_normal(opening, foot.u, foot.v) else {
continue;
};
let gap = -image.point.sub(foot.point).dot(outward);
if gap > band && worst.is_none_or(|(_, seen)| gap > seen) {
worst = Some((opening.id, gap));
}
}
}
Ok(worst)
}
pub(super) fn curved_opening_reach_source(
source: &BrepSolid,
face: &FaceRecord,
distance: f64,
) -> Result<Option<BrepSolid>, KernelRefusal> {
if distance <= 0.0 || face.loops.len() != 1 || face.loops[0].coedges.len() != 4
|| (face.surface.degree_u != 1 && face.surface.degree_v != 1)
{
return Ok(None);
}
let (closed_u, closed_v) = face.surface.closed_directions()
.or_refuse(KernelStage::Refine, "closed_directions")?;
if closed_u || closed_v {
return Ok(None);
}
let source_faces = source.shells.iter().flat_map(|shell| &shell.faces).collect::<Vec<_>>();
for coedge in &face.loops[0].coedges {
if let Some((mate, mate_use)) = junction_mate(&source_faces, face, coedge.edge_id) {
if uses_are_tangent(face, coedge, mate, mate_use)? {
return Ok(None);
}
}
}
let rectangle = full_rectangle_face(source, face)?;
let rebuilt = source_by_id_lookup(
&rectangle.shells.iter().flat_map(|shell| &shell.faces).collect::<Vec<_>>(),
face.id,
).unwrap();
let tolerance = 1e-7f64.max(crate::solid_scale(source) * 1e-9);
let grown = match extended_ruled_wall(&rectangle, rebuilt, 2.0 * distance, tolerance)? {
Ok(grown) => grown.solid,
Err(reason) => {
os_debug!("curved opening reach: source face {} declined: {reason}", face.id);
return Ok(None);
}
};
if carve_folded_support(&grown, &grown.shells[0].faces[0], distance)?.is_some() {
return Ok(None);
}
os_debug!("curved opening reach: source face {} uses its continued full rectangle", face.id);
Ok(Some(grown))
}
pub(super) fn source_by_id_lookup<'a>(
source_faces: &[&'a FaceRecord],
face_id: u64,
) -> Option<&'a FaceRecord> {
source_faces.iter().find(|face| face.id == face_id).copied()
}
pub(super) fn sample_separation(first: &[Vec3], second: &[Vec3]) -> f64 {
first
.iter()
.flat_map(|a| second.iter().map(move |b| a.sub(*b).length()))
.fold(f64::INFINITY, f64::min)
}
pub(super) fn opening_wall_seeds(
opening: &FaceRecord,
carrier: &FaceRecord,
source: &BrepSolid,
opening_set: &HashSet<u64>,
distance: f64,
) -> Result<Vec<Vec2>, KernelRefusal> {
let source_faces = source
.shells
.iter()
.flat_map(|shell| &shell.faces)
.collect::<Vec<_>>();
let mut seeds = Vec::new();
for opening_use in opening
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
let Some((neighbor, neighbor_use)) = source_faces.iter().find_map(|face| {
if face.id == opening.id || opening_set.contains(&face.id) {
return None;
}
face.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.find(|coedge| coedge.edge_id == opening_use.edge_id)
.map(|coedge| (*face, coedge))
}) else {
continue;
};
let [opening_start, opening_end] = opening_use.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let opening_uv = opening_use
.pcurve
.evaluate((opening_start + opening_end) * 0.5).or_refuse(KernelStage::Refine, "evaluate")?;
let original = opening.surface.evaluate(opening_uv.x, opening_uv.y).or_refuse(KernelStage::Refine, "evaluate")?;
let [neighbor_start, neighbor_end] = neighbor_use.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let neighbor_uv = neighbor_use
.pcurve
.evaluate((neighbor_start + neighbor_end) * 0.5).or_refuse(KernelStage::Refine, "evaluate")?;
let expected = original.add(
face_offsets(neighbor)
.normal(neighbor_uv.x, neighbor_uv.y).or_refuse(KernelStage::Refine, "normal")?
.scale(-distance),
);
let halfway = original.add(expected.sub(original).scale(0.5));
let projection = project_point_to_surface(&carrier.surface, halfway).or_refuse(KernelStage::Refine, "project_point_to_surface")?;
seeds.push(Vec2 {
x: projection.u,
y: projection.v,
});
}
Ok(seeds)
}
pub(super) fn sharp_side_continued_source(
source: &BrepSolid,
face: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
distance: f64,
extension: &crate::CarrierExtension,
) -> Result<Option<BrepSolid>, KernelRefusal> {
use crate::SurfaceSide;
let profile_reach = distance > 0.0
&& std::env::var("BREP_OFFSET_PROFILE_REACH").as_deref() != Ok("0");
if (distance >= 0.0 && !profile_reach) || face.loops.len() != 1 || face.loops[0].coedges.len() != 4 {
return Ok(None);
}
if !profile_reach && extension.u_min <= 0.0 && extension.u_max <= 0.0 {
return Ok(None);
}
let surface = &face.surface;
if surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
return Ok(None);
}
if profile_reach {
let plane_band = (crate::solid_scale(source).max(1.0) * 1e-9).max(1e-9);
let Ok(Some((origin, normal))) = planar_surface_frame(surface, plane_band) else {
return Ok(None);
};
for control in surface.control_points.iter().flatten() {
let Ok(point) = control.point() else { return Ok(None); };
if control.w <= 0.0 || point.sub(origin).dot(normal).abs() > plane_band {
return Ok(None);
}
}
}
let (closed_u, _) = surface.closed_directions().or_refuse(KernelStage::Refine, "closed_directions")?;
if closed_u {
return Ok(None);
}
let [u0, u1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0, v1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let band_u = 1e-9 * (u1 - u0).abs().max(1.0);
let band_v = 1e-9 * (v1 - v0).abs().max(1.0);
let mut sides = [usize::MAX; 4];
for (index, coedge) in face.loops[0].coedges.iter().enumerate() {
let [q0, q1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let mut on = [true; 4];
for sample in 0..=8 {
let uv = coedge.pcurve.evaluate(q0 + (q1 - q0) * sample as f64 / 8.0).or_refuse(KernelStage::Refine, "evaluate")?;
on[0] &= (uv.x - u0).abs() <= band_u;
on[1] &= (uv.x - u1).abs() <= band_u;
on[2] &= (uv.y - v0).abs() <= band_v;
on[3] &= (uv.y - v1).abs() <= band_v;
}
let Some(side) = on.iter().position(|hit| *hit) else {
return Ok(None);
};
sides[index] = side;
}
let mut seen = sides;
seen.sort_unstable();
if seen != [0, 1, 2, 3] {
return Ok(None);
}
let mut grow = [0.0f64; 2];
let mut opening_planes: [Option<(Vec3, Vec3)>; 2] = [None, None];
for (index, coedge) in face.loops[0].coedges.iter().enumerate() {
let side = sides[index];
if side > 1 {
continue;
}
let asked = if side == 0 { extension.u_min } else { extension.u_max };
if !profile_reach && asked <= 0.0 {
continue;
}
let Some((mate, mate_use)) = junction_mate(source_faces, face, coedge.edge_id) else {
continue;
};
if profile_reach {
if !opening_set.contains(&mate.id) { continue; }
let band = 2e-3f64.max(crate::solid_scale(source) * 5e-5);
let Some((point, _)) = planar_surface_frame(&mate.surface, band)? else { continue; };
let [ou0,ou1] = mate.surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [ov0,ov1] = mate.surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let normal = face_normal(mate, (ou0+ou1)*0.5, (ov0+ov1)*0.5)?;
let offsets = face_offsets(face);
let mut worst_gap = f64::NEG_INFINITY;
let u = if side == 0 { u0 } else { u1 };
for station in 0..=8 {
let v = v0 + (v1-v0)*station as f64/8.0;
let Ok(image) = offsets.at(u, v, -distance) else {
return Ok(None);
};
worst_gap = worst_gap.max(-image.point.sub(point).dot(normal));
}
if worst_gap <= band { continue; }
opening_planes[side] = Some((point, normal));
grow[side] = 2.0 * distance;
continue;
}
if opening_set.contains(&mate.id) || uses_are_tangent(face, coedge, mate, mate_use)? {
continue;
}
grow[side] = asked * 1.5;
}
if grow == [0.0, 0.0] {
return Ok(None);
}
const STATIONS: usize = 8;
let mut grown = surface.clone();
let mut reach = [u0, u1];
for (slot, side) in [SurfaceSide::UMin, SurfaceSide::UMax].into_iter().enumerate() {
let wanted = grow[slot];
if wanted <= 0.0 {
continue;
}
let u_end = if slot == 0 { u0 } else { u1 };
let sense = if slot == 0 { -1.0 } else { 1.0 };
let mut speed = f64::INFINITY;
for index in 0..=STATIONS {
let v = v0 + (v1 - v0) * index as f64 / STATIONS as f64;
speed = speed.min(surface.derivatives(u_end, v, 1).or_refuse(KernelStage::Refine, "derivatives")?[1][0].length());
}
if !(speed > 0.0) {
return Err(KernelRefusal::unsupported(KernelStage::Refine, refusals::SHARP_SIDE_CONTINUATION, format!(
"offset_shell: source face {}'s offset carrier cannot be built past its sharp {side:?} \
side: the side has no speed to continue along",
face.id
)));
}
let mut delta = wanted / speed;
let mut accepted = None;
for _ in 0..8 {
let candidate = grown.extend_natural(side, delta).map_err(|refusal| {
KernelRefusal::unsupported(
KernelStage::Refine,
refusals::SHARP_SIDE_CONTINUATION,
format!(
"offset_shell: source face {}'s offset carrier cannot be built past its sharp \
{side:?} side by {wanted:.6}: {refusal}",
face.id
),
)
})?;
let offsets = crate::OffsetEvaluator::new(
"offset_shell",
&candidate,
crate::OffsetNormal::Face { same_sense: face.same_sense },
);
let mut least = f64::INFINITY;
let mut opening_clearance = f64::INFINITY;
for index in 0..=STATIONS {
let v = v0 + (v1 - v0) * index as f64 / STATIONS as f64;
let start = offsets.at(u_end, v, -distance).or_refuse(KernelStage::Refine, "at")?.point;
let far = offsets.at(u_end + sense * delta, v, -distance).or_refuse(KernelStage::Refine, "at")?.point;
if let Some((point, normal)) = opening_planes[slot] {
opening_clearance = opening_clearance.min(far.sub(point).dot(normal));
}
let direction = far.sub(start);
let mut previous = start;
for step in 1..=16 {
let point = offsets.at(u_end + sense * delta * step as f64 / 16.0, v, -distance).or_refuse(KernelStage::Refine, "at")?.point;
if point.sub(previous).dot(direction) <= 0.0 {
return Err(KernelRefusal::unsupported(KernelStage::Refine, refusals::SHARP_SIDE_CONTINUATION, format!(
"offset_shell: source face {}'s offset carrier cannot be built past its \
sharp {side:?} side: the continued surface's offset turns back \
{:.3} of the way across the strip (a cusp of the offset)",
face.id,
step as f64 / 16.0
)));
}
previous = point;
}
least = least.min(direction.length());
}
let clears_opening = opening_clearance >= 0.25 * distance.abs();
if least >= wanted * (1.0 - 1e-9) && clears_opening {
accepted = Some(candidate);
break;
}
if !clears_opening {
delta *= 2.0;
} else {
delta *= (wanted / least.max(wanted * 1e-3)) * 1.05;
}
}
let Some(candidate) = accepted else {
return Err(KernelRefusal::unsupported(KernelStage::Refine, refusals::SHARP_SIDE_CONTINUATION, format!(
"offset_shell: source face {}'s offset carrier cannot be built past its sharp {side:?} \
side: its offset does not travel {wanted:.6} within eight refinements",
face.id
)));
};
grown = candidate;
reach[slot] = u_end + sense * delta;
}
let mut solid = source.clone();
let mut next_id = solid
.vertices
.iter()
.map(|vertex| vertex.id)
.chain(solid.edges.iter().map(|edge| edge.id))
.chain(solid.shells.iter().flat_map(|shell| &shell.faces).flat_map(|face| {
face.loops
.iter()
.flat_map(|loop_record| loop_record.coedges.iter().map(|coedge| coedge.id).chain([loop_record.id]))
}))
.max()
.unwrap_or(0)
+ 1;
let [ua, ub] = reach;
let corners = [(ua, v0), (ub, v0), (ub, v1), (ua, v1)];
let mut corner_ids = [0u64; 4];
for (slot, (u, v)) in corners.into_iter().enumerate() {
solid.vertices.push(VertexRecord { id: next_id, point: grown.evaluate(u, v).or_refuse(KernelStage::Refine, "evaluate")? });
corner_ids[slot] = next_id;
next_id += 1;
}
let side_edge = |side: usize| -> Result<(crate::NurbsCurve, u64, u64, Vec3, Vec3), KernelRefusal> {
let flat = |u: f64, v: f64| Vec3::new(u, v, 0.0);
Ok(match side {
0 => (grown.iso_curve_u(ua).or_refuse(KernelStage::Refine, "iso_curve_u")?, corner_ids[0], corner_ids[3], flat(ua, v0), flat(ua, v1)),
1 => (grown.iso_curve_u(ub).or_refuse(KernelStage::Refine, "iso_curve_u")?, corner_ids[1], corner_ids[2], flat(ub, v0), flat(ub, v1)),
2 => (grown.iso_curve_v(v0).or_refuse(KernelStage::Refine, "iso_curve_v")?, corner_ids[0], corner_ids[1], flat(ua, v0), flat(ub, v0)),
_ => (grown.iso_curve_v(v1).or_refuse(KernelStage::Refine, "iso_curve_v")?, corner_ids[3], corner_ids[2], flat(ua, v1), flat(ub, v1)),
})
};
let mut rebuilt = face.clone();
rebuilt.surface = grown.clone();
for (index, coedge) in rebuilt.loops[0].coedges.iter_mut().enumerate() {
let (curve, start, end, from, to) = side_edge(sides[index])?;
let [q0, q1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let (a, b) = (coedge.pcurve.evaluate(q0).or_refuse(KernelStage::Refine, "evaluate")?, coedge.pcurve.evaluate(q1).or_refuse(KernelStage::Refine, "evaluate")?);
let increasing = if sides[index] <= 1 { b.y > a.y } else { b.x > a.x };
let [t0, t1] = curve.domain().or_refuse(KernelStage::Refine, "domain")?;
let edge_id = next_id;
next_id += 1;
solid.edges.push(EdgeRecord {
id: edge_id,
curve,
t0,
t1,
start_vertex_id: start,
end_vertex_id: end,
degenerate: false,
name: None,
});
coedge.edge_id = edge_id;
coedge.forward = increasing;
coedge.pcurve = if increasing { crate::make_line(from, to).or_refuse(KernelStage::Refine, "make_line")? } else { crate::make_line(to, from).or_refuse(KernelStage::Refine, "make_line")? };
}
for shell in &mut solid.shells {
for existing in &mut shell.faces {
if existing.id == face.id {
*existing = rebuilt.clone();
}
}
}
os_debug!(
"sharp-side lane: source face {} continued to u = [{ua:.6}, {ub:.6}] from [{u0:.6}, {u1:.6}] for its \
sharp outward corner(s)",
face.id
);
Ok(Some(solid))
}
pub(super) fn apex_corner_reach(
source: &BrepSolid,
face: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
distance: f64,
) -> Result<[f64; 4], KernelRefusal> {
let mut reach = [0.0f64; 4];
if distance <= 0.0 {
return Ok(reach);
}
let edge_by_id = source.edges.iter().map(|edge| (edge.id, edge)).collect::<HashMap<_, _>>();
let point_of = |id: u64| source.vertices.iter().find(|vertex| vertex.id == id).map(|vertex| vertex.point);
let mut apex_reach = HashMap::<u64, f64>::default();
for other in source_faces {
if other.id == face.id || opening_set.contains(&other.id) {
continue;
}
for coedge in other.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if !edge.degenerate {
continue;
}
let Some(apex) = point_of(edge.start_vertex_id) else {
continue;
};
let mut farthest = 0.0f64;
for use_record in other.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
if let Some(other_edge) = edge_by_id.get(&use_record.edge_id) {
for id in [other_edge.start_vertex_id, other_edge.end_vertex_id] {
if let Some(point) = point_of(id) {
farthest = farthest.max(point.sub(apex).length());
}
}
}
}
let entry = apex_reach.entry(edge.start_vertex_id).or_insert(0.0);
*entry = entry.max(farthest + distance);
}
}
if apex_reach.is_empty() {
return Ok(reach);
}
let mut low = Vec2 { x: f64::INFINITY, y: f64::INFINITY };
let mut high = Vec2 { x: f64::NEG_INFINITY, y: f64::NEG_INFINITY };
for coedge in face.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
let [q0, q1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
for sample in 0..=8 {
let uv = coedge.pcurve.evaluate(q0 + (q1 - q0) * sample as f64 / 8.0).or_refuse(KernelStage::Refine, "evaluate")?;
low.x = low.x.min(uv.x);
low.y = low.y.min(uv.y);
high.x = high.x.max(uv.x);
high.y = high.y.max(uv.y);
}
}
let band_u = 1e-9 * (high.x - low.x).abs().max(1.0);
let band_v = 1e-9 * (high.y - low.y).abs().max(1.0);
for coedge in face.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let wanted = [edge.start_vertex_id, edge.end_vertex_id]
.iter()
.filter_map(|id| apex_reach.get(id))
.fold(0.0f64, |worst, reach| worst.max(*reach));
if wanted <= 0.0 {
continue;
}
let Some((mate, mate_use)) = junction_mate(source_faces, face, coedge.edge_id) else {
continue;
};
if opening_set.contains(&mate.id) || !uses_are_tangent(face, coedge, mate, mate_use)? {
continue;
}
let [q0, q1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let mut on = [true; 4];
for sample in 0..=8 {
let uv = coedge.pcurve.evaluate(q0 + (q1 - q0) * sample as f64 / 8.0).or_refuse(KernelStage::Refine, "evaluate")?;
on[0] &= (uv.x - low.x).abs() <= band_u;
on[1] &= (uv.x - high.x).abs() <= band_u;
on[2] &= (uv.y - low.y).abs() <= band_v;
on[3] &= (uv.y - high.y).abs() <= band_v;
}
if let Some(side) = on.iter().position(|hit| *hit) {
reach[side] = reach[side].max(wanted);
}
}
Ok(reach)
}
pub(super) fn reflex_side_widened_source(
source: &BrepSolid,
face: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
distance: f64,
extension: &crate::CarrierExtension,
) -> Result<Option<BrepSolid>, KernelRefusal> {
if distance <= 0.0 || face.loops.len() != 1 || face.loops[0].coedges.len() != 4 {
return Ok(None);
}
if extension.u_min <= 0.0 && extension.u_max <= 0.0 {
return Ok(None);
}
let surface = &face.surface;
if surface.is_affine().or_refuse(KernelStage::Refine, "is_affine")? {
return Ok(None);
}
let [u0, u1] = surface.domain_u().or_refuse(KernelStage::Refine, "domain_u")?;
let [v0, v1] = surface.domain_v().or_refuse(KernelStage::Refine, "domain_v")?;
let band_u = 1e-9 * (u1 - u0).abs().max(1.0);
let band_v = 1e-9 * (v1 - v0).abs().max(1.0);
let mut isolines = Vec::with_capacity(4);
for coedge in &face.loops[0].coedges {
let [q0, q1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let first = coedge.pcurve.evaluate(q0).or_refuse(KernelStage::Refine, "evaluate")?;
let (mut on_u, mut on_v) = (true, true);
for sample in 1..=8 {
let uv = coedge.pcurve.evaluate(q0 + (q1 - q0) * sample as f64 / 8.0).or_refuse(KernelStage::Refine, "evaluate")?;
on_u &= (uv.x - first.x).abs() <= band_u;
on_v &= (uv.y - first.y).abs() <= band_v;
}
isolines.push(match (on_u, on_v) {
(true, false) => (true, first.x),
(false, true) => (false, first.y),
_ => return Ok(None),
});
}
let u_sides = isolines.iter().filter(|(along_u, _)| *along_u).map(|(_, at)| *at).collect::<Vec<_>>();
let v_sides = isolines.iter().filter(|(along_u, _)| !*along_u).map(|(_, at)| *at).collect::<Vec<_>>();
if u_sides.len() != 2 || v_sides.len() != 2 {
return Ok(None);
}
let (ua, ub) = (u_sides[0].min(u_sides[1]), u_sides[0].max(u_sides[1]));
let (va, vb) = (v_sides[0].min(v_sides[1]), v_sides[0].max(v_sides[1]));
if ub - ua <= band_u || vb - va <= band_v {
return Ok(None);
}
let edge_by_id = source.edges.iter().map(|edge| (edge.id, edge)).collect::<HashMap<_, _>>();
let mut grow = [0.0f64; 2];
for (coedge, (along_u, at)) in face.loops[0].coedges.iter().zip(&isolines) {
if !*along_u {
continue;
}
let slot = if (*at - ua).abs() <= band_u { 0 } else { 1 };
let asked = if slot == 0 { extension.u_min } else { extension.u_max };
if asked <= 0.0 {
continue;
}
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((mate, mate_use)) = junction_mate(source_faces, face, coedge.edge_id) else {
continue;
};
if opening_set.contains(&mate.id) || uses_are_tangent(face, coedge, mate, mate_use)? {
continue;
}
let stations = junction_stations(face, coedge, edge, mate)?;
if stations.is_empty() || stations.iter().any(|station| station.is_convex()) {
continue;
}
grow[slot] = asked;
}
if grow == [0.0, 0.0] {
return Ok(None);
}
const STATIONS: usize = 8;
let offsets = crate::OffsetEvaluator::new(
"offset_shell",
surface,
crate::OffsetNormal::Face { same_sense: face.same_sense },
);
let mut reach = [ua, ub];
for slot in 0..2 {
let wanted = grow[slot];
if wanted <= 0.0 {
continue;
}
let u_end = reach[slot];
let sense = if slot == 0 { -1.0 } else { 1.0 };
let mut speed = f64::INFINITY;
for index in 0..=STATIONS {
let v = va + (vb - va) * index as f64 / STATIONS as f64;
speed = speed.min(surface.derivatives(u_end, v, 1).or_refuse(KernelStage::Refine, "derivatives")?[1][0].length());
}
if !(speed > 0.0) {
return Ok(None);
}
let mut delta = wanted / speed;
let mut accepted = None;
for _ in 0..8 {
let far_u = u_end + sense * delta;
if far_u < u0 + band_u || far_u > u1 - band_u {
return Ok(None);
}
let mut least = f64::INFINITY;
for index in 0..=STATIONS {
let v = va + (vb - va) * index as f64 / STATIONS as f64;
let start = offsets.at(u_end, v, -distance).or_refuse(KernelStage::Refine, "at")?.point;
let far = offsets.at(far_u, v, -distance).or_refuse(KernelStage::Refine, "at")?.point;
let direction = far.sub(start);
let mut previous = start;
for step in 1..=16 {
let point = offsets.at(u_end + sense * delta * step as f64 / 16.0, v, -distance).or_refuse(KernelStage::Refine, "at")?.point;
if point.sub(previous).dot(direction) <= 0.0 {
return Ok(None);
}
previous = point;
}
least = least.min(direction.length());
}
if least >= wanted * (1.0 - 1e-9) {
accepted = Some(far_u);
break;
}
delta *= (wanted / least.max(wanted * 1e-3)) * 1.05;
}
let Some(far_u) = accepted else {
return Ok(None);
};
reach[slot] = far_u;
}
let ua_source = ua;
let [ua, ub] = reach;
let mut solid = source.clone();
let mut next_id = solid
.vertices
.iter()
.map(|vertex| vertex.id)
.chain(solid.edges.iter().map(|edge| edge.id))
.chain(solid.shells.iter().flat_map(|shell| &shell.faces).flat_map(|face| {
face.loops
.iter()
.flat_map(|loop_record| loop_record.coedges.iter().map(|coedge| coedge.id).chain([loop_record.id]))
}))
.max()
.unwrap_or(0)
+ 1;
let corners = [(ua, va), (ub, va), (ub, vb), (ua, vb)];
let mut corner_ids = [0u64; 4];
for (slot, (u, v)) in corners.into_iter().enumerate() {
solid.vertices.push(VertexRecord { id: next_id, point: surface.evaluate(u, v).or_refuse(KernelStage::Refine, "evaluate")? });
corner_ids[slot] = next_id;
next_id += 1;
}
let mut rebuilt = face.clone();
for (coedge, (along_u, at)) in rebuilt.loops[0].coedges.iter_mut().zip(&isolines) {
let flat = |u: f64, v: f64| Vec3::new(u, v, 0.0);
let (curve, start, end, from, to, t0, t1) = if *along_u {
let low = (*at - ua_source).abs() <= band_u;
let u = if low { ua } else { ub };
let (start, end) = if low { (corner_ids[0], corner_ids[3]) } else { (corner_ids[1], corner_ids[2]) };
(surface.iso_curve_u(u).or_refuse(KernelStage::Refine, "iso_curve_u")?, start, end, flat(u, va), flat(u, vb), va, vb)
} else {
let low = (*at - va).abs() <= band_v;
let v = if low { va } else { vb };
let (start, end) = if low { (corner_ids[0], corner_ids[1]) } else { (corner_ids[3], corner_ids[2]) };
(surface.iso_curve_v(v).or_refuse(KernelStage::Refine, "iso_curve_v")?, start, end, flat(ua, v), flat(ub, v), ua, ub)
};
let [q0, q1] = coedge.pcurve.domain().or_refuse(KernelStage::Refine, "domain")?;
let (a, b) = (coedge.pcurve.evaluate(q0).or_refuse(KernelStage::Refine, "evaluate")?, coedge.pcurve.evaluate(q1).or_refuse(KernelStage::Refine, "evaluate")?);
let increasing = if *along_u { b.y > a.y } else { b.x > a.x };
let edge_id = next_id;
next_id += 1;
solid.edges.push(EdgeRecord {
id: edge_id,
curve,
t0,
t1,
start_vertex_id: start,
end_vertex_id: end,
degenerate: false,
name: None,
});
coedge.edge_id = edge_id;
coedge.forward = increasing;
coedge.pcurve = if increasing { crate::make_line(from, to).or_refuse(KernelStage::Refine, "make_line")? } else { crate::make_line(to, from).or_refuse(KernelStage::Refine, "make_line")? };
}
for shell in &mut solid.shells {
for existing in &mut shell.faces {
if existing.id == face.id {
*existing = rebuilt.clone();
}
}
}
os_debug!(
"reflex-side lane: source face {} widened to u = [{ua:.6}, {ub:.6}] for its reflex inward junction(s)",
face.id
);
Ok(Some(solid))
}