use super::*;
const SUPPLIED_STATIONS: usize = 64;
const CARRIER_INCIDENCE: f64 = 1e-9;
impl<'a> SolidBuilder<'a> {
pub(super) fn supplied_loop_pcurve(
&mut self,
surface: &NurbsSurface,
surface_ref: usize,
edge_id: u64,
forward: bool,
pcurve_tol: f64,
) -> Result<Option<NurbsCurve>, String> {
if !supplied_pcurves_enabled() {
return Ok(None);
}
let edge = self.edge_record(edge_id);
if edge.degenerate {
return Ok(None);
}
let Some(&curve_ref) = self.curve_ref_of_edge.get(&edge_id) else {
return Ok(None);
};
let bundle = self.resolver.supplied_pcurves(curve_ref);
if bundle.is_empty() {
return Ok(None);
}
let Some(supplied) = bundle
.iter()
.find(|candidate| candidate.basis_ref == surface_ref)
else {
return Ok(None);
};
self.supplied_report.offered += 1;
let edge = self.edge_record(edge_id).clone();
let span = edge.t1 - edge.t0;
let mut cursor: Option<(f64, f64)> = None;
let mut stations = Vec::with_capacity(SUPPLIED_STATIONS + 1);
let mut worst_vs_curve = 0.0f64;
for index in 0..=SUPPLIED_STATIONS {
let fraction = index as f64 / SUPPLIED_STATIONS as f64;
let edge_fraction = if forward { fraction } else { 1.0 - fraction };
let point = edge.curve.evaluate(edge.t0 + span * edge_fraction)?;
let (uv, on_trim) = supplied.snap(point, cursor)?;
cursor = Some(uv);
worst_vs_curve = worst_vs_curve.max(on_trim.sub(point).length());
if std::env::var("BREP_DEBUG_SUPPLIED_STATIONS").is_ok_and(|v| v == format!("{curve_ref}")) {
let (raw_u, raw_v) = supplied.surface.invert(point);
eprintln!(
" station {index:3} edge=({:.6},{:.6},{:.6}) invert=({:?},{:.6}) snap=({:.6},{:.6}) trim=({:.6},{:.6},{:.6}) gap={:.6e}",
point.x, point.y, point.z,
raw_u.map(|u| format!("{u:.6}")), raw_v,
uv.0, uv.1,
on_trim.x, on_trim.y, on_trim.z,
on_trim.sub(point).length()
);
}
stations.push(on_trim);
}
let traversal_start = if forward {
edge.start_vertex_id
} else {
edge.end_vertex_id
};
let traversal_end = if forward {
edge.end_vertex_id
} else {
edge.start_vertex_id
};
let scale = 1.0 + stations
.iter()
.fold(0.0f64, |extent, point| extent.max(point.length()));
let endpoint_band = (1e-6 * scale).max(IMPORT_IDENTITY_TOLERANCE);
let start_gap = stations[0]
.sub(self.vertex_point(traversal_start))
.length();
let end_gap = stations[SUPPLIED_STATIONS]
.sub(self.vertex_point(traversal_end))
.length();
if start_gap > endpoint_band || end_gap > endpoint_band {
self.supplied_report.declined_endpoints += 1;
supplied_trace(format_args!(
"decline endpoints curve=#{curve_ref} basis=#{surface_ref} start_gap={start_gap:.6e} end_gap={end_gap:.6e} band={endpoint_band:.6e} worst_vs_curve={worst_vs_curve:.6e}"
));
return Ok(None);
}
let parameters: Vec<f64> = (0..=SUPPLIED_STATIONS)
.map(|index| index as f64 / SUPPLIED_STATIONS as f64)
.collect();
let trim = crate::interpolate_curve(&stations, 3, ¶meters)?;
let sample_trim = |fraction: f64| trim.evaluate(fraction);
let Ok(pcurve) = crate::build_pcurve_on_surface_stations(
surface,
&sample_trim,
&[],
pcurve_tol,
SUPPLIED_STATIONS,
3,
513,
) else {
self.supplied_report.declined_failed += 1;
return Ok(None);
};
let band = crate::KernelTolerances::for_scale(scale, 1e-7).pcurve_consistency;
let measure = |candidate: &NurbsCurve| {
crate::measure_edge_against_pcurve_image(surface, candidate, &edge, forward, band)
};
let measured = measure(&pcurve);
let derived = crate::fit_pcurve_on_surface_range(
surface,
&edge.curve,
edge.t0,
edge.t1,
forward,
pcurve_tol,
)
.ok();
let derived_deviation = derived
.as_ref()
.and_then(|fit| measure(&fit.curve).ok())
.map(|measured| measured.deviation());
let curve_is_on_carrier = derived
.as_ref()
.is_some_and(|fit| fit.report.off_surface <= CARRIER_INCIDENCE * scale);
if supplied_tracing() {
let (u_span, v_span) = supplied.span_in_periods();
let span = |value: Option<f64>| {
value.map_or_else(|| "-".to_string(), |value| format!("{value:.6}"))
};
supplied_trace(format_args!(
"offer curve=#{curve_ref} basis=#{surface_ref} curve_off_carrier={:.6e} \
gate={:.6e} supplied={:.6e} snap={worst_vs_curve:.6e} scale={scale:.6e} \
band={band:.6e} corroboration={:.6e} branches={} curve3d={} surface={} \
uspan={} vspan={}",
derived_deviation.unwrap_or(f64::NAN),
CARRIER_INCIDENCE * scale,
measured.as_ref().map(|m| m.deviation()).unwrap_or(f64::NAN),
self.corroboration(&bundle, supplied, surface_ref, &edge, forward),
bundle.len(),
self.curve3d_kind(curve_ref),
self.entity_kind(surface_ref),
span(u_span),
span(v_span),
));
}
match measured {
Ok(measured) if measured.within_band() && !curve_is_on_carrier => {
self.supplied_report.used += 1;
self.supplied_report.worst_supplied_vs_curve = self
.supplied_report
.worst_supplied_vs_curve
.max(worst_vs_curve);
Ok(Some(pcurve))
}
Ok(measured) if measured.within_band() => {
self.supplied_report.declined_curve_on_carrier += 1;
supplied_trace(format_args!(
"decline curve-on-carrier curve=#{curve_ref} basis=#{surface_ref} supplied={:.6e} curve_off_carrier={:.6e} gate={:.6e}",
measured.deviation(),
derived_deviation.unwrap_or(f64::NAN),
CARRIER_INCIDENCE * scale
));
Ok(None)
}
other => {
self.supplied_report.declined_residual += 1;
if let Ok(measured) = other {
self.supplied_report.worst_declined_residual = self
.supplied_report
.worst_declined_residual
.max(measured.deviation());
self.supplied_report.declined_band = band;
supplied_trace(format_args!(
"decline residual curve=#{curve_ref} basis=#{surface_ref} deviation={:.6e} band={band:.6e} worst_vs_curve={worst_vs_curve:.6e}",
measured.deviation()
));
} else {
supplied_trace(format_args!(
"decline measure-failed curve=#{curve_ref} basis=#{surface_ref} worst_vs_curve={worst_vs_curve:.6e}"
));
}
Ok(None)
}
}
}
fn corroboration(
&self,
bundle: &[SuppliedPcurve],
supplied: &SuppliedPcurve,
surface_ref: usize,
edge: &EdgeRecord,
forward: bool,
) -> f64 {
let Some(other) = bundle
.iter()
.find(|candidate| candidate.basis_ref != surface_ref)
else {
return f64::NAN;
};
let domain = edge.t1 - edge.t0;
let mut worst = 0.0f64;
let mut cursor: Option<(f64, f64)> = None;
let mut other_cursor: Option<(f64, f64)> = None;
for index in 0..=SUPPLIED_STATIONS {
let fraction = index as f64 / SUPPLIED_STATIONS as f64;
let edge_fraction = if forward { fraction } else { 1.0 - fraction };
let Ok(point) = edge.curve.evaluate(edge.t0 + domain * edge_fraction) else {
return f64::NAN;
};
let (Ok((uv, on_trim)), Ok((other_uv, on_other))) =
(supplied.snap(point, cursor), other.snap(point, other_cursor))
else {
return f64::NAN;
};
cursor = Some(uv);
other_cursor = Some(other_uv);
worst = worst.max(on_other.sub(on_trim).length());
}
worst
}
fn curve3d_kind(&self, curve_ref: usize) -> String {
let Ok(entity) = self.resolver.get(curve_ref) else {
return "?".to_string();
};
let curve = ["SURFACE_CURVE", "SEAM_CURVE", "INTERSECTION_CURVE"]
.iter()
.find_map(|wrapper| entity.find(wrapper))
.and_then(|args| args.get(1))
.and_then(|value| value.as_ref_id().ok());
curve.map_or_else(|| "?".to_string(), |id| self.entity_kind(id))
}
fn entity_kind(&self, id: usize) -> String {
const PREFERRED: [&str; 16] = [
"LINE",
"CIRCLE",
"ELLIPSE",
"PARABOLA",
"HYPERBOLA",
"POLYLINE",
"TRIMMED_CURVE",
"B_SPLINE_CURVE_WITH_KNOTS",
"B_SPLINE_CURVE",
"PLANE",
"CYLINDRICAL_SURFACE",
"CONICAL_SURFACE",
"SPHERICAL_SURFACE",
"TOROIDAL_SURFACE",
"B_SPLINE_SURFACE_WITH_KNOTS",
"B_SPLINE_SURFACE",
];
let Ok(entity) = self.resolver.get(id) else {
return "?".to_string();
};
for name in PREFERRED {
if entity.has(name) {
return name.to_string();
}
}
entity
.records
.first()
.map_or_else(|| "?".to_string(), |(name, _)| name.clone())
}
pub(super) fn report_supplied_pcurves(&self) {
if std::env::var("BREP_DEBUG_SUPPLIED_PCURVE").is_err() {
return;
}
let report = &self.supplied_report;
eprintln!(
"SUPPLIED PCURVE offered={} used={} declined(endpoints={} residual={} failed={} curve_on_carrier={}) worst_supplied_vs_curve={:.6e} worst_declined_residual={:.6e} band={:.6e}",
report.offered,
report.used,
report.declined_endpoints,
report.declined_residual,
report.declined_failed,
report.declined_curve_on_carrier,
report.worst_supplied_vs_curve,
report.worst_declined_residual,
report.declined_band,
);
}
}
fn supplied_trace(args: std::fmt::Arguments<'_>) {
if supplied_tracing() {
eprintln!("SUPPLIED PCURVE {args}");
}
}
fn supplied_tracing() -> bool {
debug_flag!(live "BREP_DEBUG_SUPPLIED_PCURVE")
}
fn supplied_pcurves_enabled() -> bool {
std::env::var("BREP_SUPPLIED_PCURVES").is_ok_and(|value| value != "0")
}