use serde::{Deserialize, Serialize};
use crate::descriptor::{Descriptor, placed_footprint, placed_height};
use crate::library::Library;
pub const STANDARDS_VERSION: u32 = 1;
pub const FILE: &str = "standards.ron";
pub const MULTIPLE_TOLERANCE: f32 = 0.01;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Standards {
pub version: u32,
#[serde(default)]
pub rules: Vec<Rule>,
}
impl Default for Standards {
fn default() -> Self {
Standards {
version: STANDARDS_VERSION,
rules: Vec::new(),
}
}
}
impl Standards {
pub fn parse(text: &str) -> Result<Standards, String> {
let s: Standards = ron::from_str(text).map_err(|e| format!("standards: parse: {e}"))?;
if s.version != STANDARDS_VERSION {
return Err(format!(
"standards: version {} is not the {STANDARDS_VERSION} this build reads",
s.version
));
}
for r in &s.rules {
r.validate()
.map_err(|e| format!("standards: rule {:?}: {e}", r.name))?;
}
Ok(s)
}
pub fn to_ron(&self) -> Result<String, String> {
ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
.map_err(|e| format!("standards: write: {e}"))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Dim {
Width,
Depth,
Height,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Rule {
pub name: String,
#[serde(default)]
pub kind: Option<String>,
pub dim: Dim,
#[serde(default)]
pub min: Option<f32>,
#[serde(default)]
pub max: Option<f32>,
#[serde(default)]
pub multiple_of: Option<f32>,
}
impl Rule {
fn validate(&self) -> Result<(), String> {
if self.min.is_none() && self.max.is_none() && self.multiple_of.is_none() {
return Err("has no min, max or multiple_of, so it says nothing".into());
}
for (what, v) in [
("min", self.min),
("max", self.max),
("multiple_of", self.multiple_of),
] {
if let Some(v) = v
&& (!v.is_finite() || v < 0.0)
{
return Err(format!("{what} {v} is not a finite length of zero or more"));
}
}
if self
.multiple_of
.is_some_and(|m| m <= 2.0 * MULTIPLE_TOLERANCE)
{
return Err(format!(
"multiple_of must be above {} m, or every length is within tolerance of it",
2.0 * MULTIPLE_TOLERANCE
));
}
if let (Some(lo), Some(hi)) = (self.min, self.max)
&& lo > hi
{
return Err(format!(
"min {lo} is above max {hi}, so nothing can meet it"
));
}
Ok(())
}
pub fn covers(&self, d: &Descriptor) -> bool {
self.kind.as_ref().is_none_or(|k| d.kind.contains(k))
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Why {
Unmeasured,
BelowMin(f32),
AboveMax(f32),
NotMultipleOf(f32),
}
#[derive(Clone, Debug, PartialEq)]
pub struct Breach {
pub piece: String,
pub rule: String,
pub dim: Dim,
pub found: Option<f32>,
pub why: Why,
}
fn measure(d: &Descriptor, dim: Dim) -> Option<f32> {
match dim {
Dim::Width => placed_footprint(d).map(|f| f.0),
Dim::Depth => placed_footprint(d).map(|f| f.1),
Dim::Height => placed_height(d),
}
}
pub fn check(library: &Library, standards: &Standards) -> Vec<Breach> {
let mut out = Vec::new();
for d in &library.descriptors {
for r in standards.rules.iter().filter(|r| r.covers(d)) {
let breach = |found, why| Breach {
piece: d.id.clone(),
rule: r.name.clone(),
dim: r.dim,
found,
why,
};
let Some(v) = measure(d, r.dim).filter(|v| v.is_finite()) else {
out.push(breach(None, Why::Unmeasured));
continue;
};
if let Some(lo) = r.min.filter(|&lo| v < lo) {
out.push(breach(Some(v), Why::BelowMin(lo)));
}
if let Some(hi) = r.max.filter(|&hi| v > hi) {
out.push(breach(Some(v), Why::AboveMax(hi)));
}
if let Some(m) = r.multiple_of {
let off = v - (v / m).round() * m;
if off.abs() > MULTIPLE_TOLERANCE || v < m - MULTIPLE_TOLERANCE {
out.push(breach(Some(v), Why::NotMultipleOf(m)));
}
}
}
}
out
}
pub fn idle<'a>(library: &Library, standards: &'a Standards) -> Vec<&'a str> {
standards
.rules
.iter()
.filter(|r| !library.descriptors.iter().any(|d| r.covers(d)))
.map(|r| r.name.as_str())
.collect()
}
pub const MEET_WITHIN: u32 = 64;
#[derive(Clone, Debug, PartialEq)]
pub struct Unnested {
pub rules: [String; 2],
pub dims: [Dim; 2],
pub modules: [f32; 2],
pub meet: Option<f32>,
}
fn on_module(v: f32, m: f32) -> bool {
(v - (v / m).round() * m).abs() <= MULTIPLE_TOLERANCE
}
pub fn unnested(standards: &Standards) -> Vec<Unnested> {
let across = |d: Dim| d != Dim::Height;
let modules: Vec<(&Rule, f32)> = standards
.rules
.iter()
.filter_map(|r| r.multiple_of.map(|m| (r, m)))
.collect();
let mut out = Vec::new();
for (i, &(a, ma)) in modules.iter().enumerate() {
for &(b, mb) in &modules[i + 1..] {
let (big, small) = if ma >= mb { (ma, mb) } else { (mb, ma) };
if across(a.dim) != across(b.dim) || on_module(big, small) {
continue;
}
let meet = (2..=MEET_WITHIN)
.map(|k| k as f32 * big)
.find(|&run| on_module(run, small));
out.push(Unnested {
rules: [a.name.clone(), b.name.clone()],
dims: [a.dim, b.dim],
modules: [ma, mb],
meet,
});
}
}
out
}