fusible 0.10.0

Electronic linter: a fuse that blows to block firmware code generation
//! SMT pad shapes and trace segment layer resolution.

use super::copper::CopperLayer;
use super::elements::{PcbSmtPadElm, PcbTracePoint};
use super::geometry::PadShape;
use super::math::{finite_or, required};
use super::sites::TraceSegment;

/// Returns the copper shape of an SMT pad.
pub(super) fn smtpad_shape(pad: &PcbSmtPadElm) -> Result<PadShape, String> {
    let rot = finite_or(pad.ccw_rotation, 0.0, "ccw_rotation")?;
    let w = || required(pad.width, "width");
    let h = || required(pad.height, "height");
    match pad.shape.as_deref() {
        Some("rect") => Ok(PadShape::Rect {
            w: w()?,
            h: h()?,
            rot,
        }),
        Some("rotated_rect") => Ok(PadShape::Rect {
            w: w()?,
            h: h()?,
            rot: required(pad.ccw_rotation, "ccw_rotation")?,
        }),
        Some("circle") => Ok(PadShape::Circle {
            d: required(pad.radius, "radius")? * 2.0,
        }),
        Some("pill") | Some("oval") => Ok(PadShape::Pill {
            w: w()?,
            h: h()?,
            rot,
        }),
        Some("rotated_pill") => Ok(PadShape::Pill {
            w: w()?,
            h: h()?,
            rot: required(pad.ccw_rotation, "ccw_rotation")?,
        }),
        Some(other) => Err(format!("unsupported pad shape '{other}'")),
        None => Err("missing shape".to_string()),
    }
}

/// Returns the layer and width of a wire route point, or `None` for a via.
fn route_wire(p: &PcbTracePoint) -> Result<Option<(CopperLayer, f64)>, String> {
    match p.route_type.as_deref() {
        Some("wire") => Ok(Some((
            CopperLayer::parse(p.layer.as_deref())?,
            required(p.width, "wire width")?,
        ))),
        Some("via") => Ok(None),
        Some(other) => Err(format!("unsupported route_type '{other}'")),
        None => Err("route point missing route_type".to_string()),
    }
}

/// Returns the straight trace segment between two consecutive route points.
pub(super) fn trace_segment(
    p1: &PcbTracePoint,
    p2: &PcbTracePoint,
) -> Result<TraceSegment, String> {
    let (layer, width) = match (route_wire(p1)?, route_wire(p2)?) {
        (Some((l1, w)), Some((l2, _))) if l1 == l2 => (l1, w),
        (Some(_), Some(_)) => return Err("wire changes layer without a via".to_string()),
        (Some(a), None) | (None, Some(a)) => a,
        (None, None) => return Err("segment between two vias has no layer".to_string()),
    };
    Ok(TraceSegment {
        layer,
        width,
        from: [p1.x, p1.y],
        to: [p2.x, p2.y],
    })
}