main
Colby Knox board-to-molecule 1.0.0: Rust source, six before/after examples (KiCad, Fusion, Altium), Adom 3D viewer readme eb9ea91 4d ago
//! KiCad 9+ embedded files: carry the machine-pin STEP inside the .kicad_pcb.
//!
//! KiCad stores each file as base64 of a zstd frame plus a checksum. We write a
//! valid zstd frame made of raw (stored) blocks, which needs no compressor, and a
//! SHA-256 checksum, which KiCad still accepts from older files and verifies.
//! A board with its pin models embedded renders the pins on any machine, including
//! service-kicad, which only ever sees the .kicad_pcb bytes.

use base64::Engine;
use sha2::{Digest, Sha256};

const MAX_BLOCK: usize = 128 * 1024;

/// A single zstd frame (single segment, known content size) of raw blocks.
pub fn zstd_raw_frame(data: &[u8]) -> Vec<u8> {
    let mut out = Vec::with_capacity(data.len() + data.len() / MAX_BLOCK * 3 + 16);
    out.extend_from_slice(&[0x28, 0xB5, 0x2F, 0xFD]);
    // Frame header descriptor: FCS field 8 bytes (flag 3), single segment, no checksum, no dict.
    out.push(0b1110_0000);
    out.extend_from_slice(&(data.len() as u64).to_le_bytes());
    if data.is_empty() {
        out.extend_from_slice(&[1, 0, 0]); // last, raw, size 0
        return out;
    }
    let mut chunks = data.chunks(MAX_BLOCK).peekable();
    while let Some(c) = chunks.next() {
        let last = chunks.peek().is_none() as u32;
        let hdr = last | ((c.len() as u32) << 3); // block type 0 = raw
        out.extend_from_slice(&hdr.to_le_bytes()[..3]);
        out.extend_from_slice(c);
    }
    out
}

/// One `(file ...)` entry for an `(embedded_files ...)` block, KiCad's own layout.
pub fn file_entry(name: &str, data: &[u8], indent: &str) -> String {
    let b64 = base64::engine::general_purpose::STANDARD.encode(zstd_raw_frame(data));
    let sum = hex(&Sha256::digest(data));
    let mut s = format!("{indent}(file\n{indent}\t(name \"{name}\")\n{indent}\t(type model)\n{indent}\t(data");
    let n = b64.len();
    let mut first = 0;
    while first < n {
        let end = (first + 76).min(n);
        s.push('\n');
        if first == 0 {
            s.push('|');
        }
        s.push_str(&b64[first..end]);
        if end == n {
            s.push('|');
        }
        first = end;
    }
    s.push_str(&format!("\n{indent}\t)\n{indent}\t(checksum \"{sum}\")\n{indent})"));
    s
}

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn frame_layout() {
        let data = vec![7u8; MAX_BLOCK + 10];
        let f = zstd_raw_frame(&data);
        assert_eq!(&f[..4], &[0x28, 0xB5, 0x2F, 0xFD]);
        assert_eq!(u64::from_le_bytes(f[5..13].try_into().unwrap()), data.len() as u64);
        // first block: not last, raw, full size
        let h = u32::from_le_bytes([f[13], f[14], f[15], 0]);
        assert_eq!(h & 1, 0);
        assert_eq!((h >> 3) as usize, MAX_BLOCK);
        assert_eq!(f.len(), 13 + 3 + MAX_BLOCK + 3 + 10);
    }
}