#![allow(clippy::wildcard_imports)]
use super::*;
pub(super) fn check_unknown_payloads(ir: &CadIr, findings: &mut Vec<Finding>) {
for record in &ir.unknowns {
let Some(data) = &record.data else { continue };
let hash = Sha256::digest(data)
.iter()
.fold(String::new(), |mut acc, byte| {
use std::fmt::Write as _;
let _ = write!(acc, "{byte:02x}");
acc
});
if data.len() as u64 != record.byte_len || hash != record.sha256 {
findings.push(Finding {
check: Check::PayloadIntegrity,
severity: Severity::Error,
message: "preserved payload length or hash does not match its record".into(),
entity: Some(record.id.0.clone()),
});
}
}
}
pub(super) fn check_tessellations(ir: &CadIr, findings: &mut Vec<Finding>) {
for mesh in &ir.model.tessellations {
if mesh
.vertices
.iter()
.any(|point| !point.x.is_finite() || !point.y.is_finite() || !point.z.is_finite())
{
findings.push(Finding {
check: Check::Tessellation,
severity: Severity::Error,
message: "contains a non-finite tessellation vertex".into(),
entity: Some(mesh.id.clone()),
});
}
if mesh
.triangles
.iter()
.flatten()
.any(|index| *index as usize >= mesh.vertices.len())
{
findings.push(Finding {
check: Check::Tessellation,
severity: Severity::Error,
message: "contains an out-of-range tessellation index".into(),
entity: Some(mesh.id.clone()),
});
}
if mesh
.normals
.iter()
.any(|normal| !normal.x.is_finite() || !normal.y.is_finite() || !normal.z.is_finite())
{
findings.push(Finding {
check: Check::Tessellation,
severity: Severity::Error,
message: "contains a non-finite tessellation normal".into(),
entity: Some(mesh.id.clone()),
});
}
if mesh.channels.iter().any(|channel| {
channel.data.len() != channel.item_size as usize * channel.count as usize
}) {
findings.push(Finding {
check: Check::Tessellation,
severity: Severity::Error,
message: "contains a malformed tessellation channel".into(),
entity: Some(mesh.id.clone()),
});
}
}
}
pub(super) fn degenerate(v: &Vector3) -> bool {
v.norm() <= f64::EPSILON
}
pub(super) fn check_bounds(ir: &CadIr, findings: &mut Vec<Finding>) {
for (id, tolerance) in ir
.model
.vertices
.iter()
.map(|entity| (&entity.id.0, entity.tolerance))
.chain(
ir.model
.edges
.iter()
.map(|entity| (&entity.id.0, entity.tolerance)),
)
.chain(
ir.model
.faces
.iter()
.map(|entity| (&entity.id.0, entity.tolerance)),
)
{
if tolerance.is_some_and(nonpositive) {
findings.push(Finding {
check: Check::Tolerances,
severity: Severity::Error,
message: "topology tolerance is not positive and finite".into(),
entity: Some(id.clone()),
});
} else if tolerance.is_some_and(|value| value > 1.0e6) {
findings.push(Finding {
check: Check::Tolerances,
severity: Severity::Warning,
message: "topology tolerance is outside a sane canonical range".into(),
entity: Some(id.clone()),
});
}
}
for s in &ir.model.surfaces {
match &s.geometry {
SurfaceGeometry::Plane { normal, u_axis, .. } => {
if degenerate(normal) {
bounds_err(findings, &s.id.0, "plane normal is degenerate");
}
if degenerate(u_axis) {
bounds_err(findings, &s.id.0, "plane u axis is degenerate");
}
}
SurfaceGeometry::Cylinder {
axis,
ref_direction,
radius,
..
} => {
if degenerate(axis) {
bounds_err(findings, &s.id.0, "cylinder axis is degenerate");
}
if degenerate(ref_direction) {
bounds_err(
findings,
&s.id.0,
"cylinder reference direction is degenerate",
);
}
if nonpositive(*radius) {
bounds_err(findings, &s.id.0, "cylinder radius is not positive");
}
}
SurfaceGeometry::Cone {
axis,
ref_direction,
radius,
..
} => {
if degenerate(axis) {
bounds_err(findings, &s.id.0, "cone axis is degenerate");
}
if degenerate(ref_direction) {
bounds_err(findings, &s.id.0, "cone reference direction is degenerate");
}
if *radius < 0.0 {
bounds_err(findings, &s.id.0, "cone radius is negative");
}
}
SurfaceGeometry::Sphere {
axis,
ref_direction,
radius,
..
} => {
if degenerate(axis) {
bounds_err(findings, &s.id.0, "sphere axis is degenerate");
}
if degenerate(ref_direction) {
bounds_err(
findings,
&s.id.0,
"sphere reference direction is degenerate",
);
}
if radius.abs() <= f64::EPSILON {
bounds_err(findings, &s.id.0, "sphere radius is zero");
}
}
SurfaceGeometry::Torus {
axis,
ref_direction,
major_radius,
minor_radius,
..
} => {
if degenerate(axis) {
bounds_err(findings, &s.id.0, "torus axis is degenerate");
}
if degenerate(ref_direction) {
bounds_err(findings, &s.id.0, "torus reference direction is degenerate");
}
if nonpositive(*major_radius) || minor_radius.abs() <= f64::EPSILON {
bounds_err(
findings,
&s.id.0,
"torus major radius is not positive or minor radius is zero",
);
}
}
SurfaceGeometry::Nurbs(n) => {
let expected = (n.u_count as usize) * (n.v_count as usize);
if n.control_points.len() != expected {
bounds_err(
findings,
&s.id.0,
"NURBS surface pole count does not match u_count*v_count",
);
}
check_knots(findings, &s.id.0, &n.u_knots, "u");
check_knots(findings, &s.id.0, &n.v_knots, "v");
}
SurfaceGeometry::Unknown { .. } => {}
}
}
for c in &ir.model.curves {
match &c.geometry {
CurveGeometry::Line { direction, .. } => {
if degenerate(direction) {
bounds_err(findings, &c.id.0, "line direction is degenerate");
}
}
CurveGeometry::Circle { axis, radius, .. } => {
if degenerate(axis) {
bounds_err(findings, &c.id.0, "circle axis is degenerate");
}
if nonpositive(*radius) {
bounds_err(findings, &c.id.0, "circle radius is not positive");
}
}
CurveGeometry::Ellipse {
major_radius,
minor_radius,
..
} => {
if nonpositive(*major_radius) || nonpositive(*minor_radius) {
bounds_err(findings, &c.id.0, "ellipse radius is not positive");
}
}
CurveGeometry::Parabola {
axis,
major_direction,
focal_distance,
..
} => {
if degenerate(axis) || degenerate(major_direction) {
bounds_err(findings, &c.id.0, "parabola frame is degenerate");
}
if nonpositive(*focal_distance) {
bounds_err(findings, &c.id.0, "parabola focal distance is not positive");
}
}
CurveGeometry::Hyperbola {
axis,
major_direction,
major_radius,
minor_radius,
..
} => {
if degenerate(axis) || degenerate(major_direction) {
bounds_err(findings, &c.id.0, "hyperbola frame is degenerate");
}
if nonpositive(*major_radius) || nonpositive(*minor_radius) {
bounds_err(findings, &c.id.0, "hyperbola radius is not positive");
}
}
CurveGeometry::Nurbs(n) => {
if n.control_points.len() < (n.degree as usize + 1) {
bounds_err(
findings,
&c.id.0,
"NURBS curve has too few poles for its degree",
);
}
check_knots(findings, &c.id.0, &n.knots, "");
}
CurveGeometry::Unknown { .. } => {}
}
}
}
pub(super) fn check_knots(findings: &mut Vec<Finding>, id: &str, knots: &[f64], dir: &str) {
if knots.windows(2).any(|w| w[1] < w[0]) {
let label = if dir.is_empty() {
"knot vector is not non-decreasing".to_string()
} else {
format!("{dir}-knot vector is not non-decreasing")
};
bounds_err(findings, id, &label);
}
}
pub(super) fn bounds_err(findings: &mut Vec<Finding>, id: &str, msg: &str) {
findings.push(Finding {
check: Check::Bounds,
severity: Severity::Error,
message: msg.to_string(),
entity: Some(id.to_string()),
});
}