app
Board to Molecule
Public Made by Adomby adom
Turn any KiCad, Fusion/EAGLE, or Altium board into an Adom molecule: wings plus MP1-MP4 machine pins, written back in the board's own format.
main
Colby Knox
1.1.0: every molecule gets large machine pins on the 32 mm grid; gallery rebuilt; corrected notes on kicad-to-molecule
c794385
4d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
//! Altium lane probe.
//!
//! altium_probe summary <board.PcbDoc>
//! bbox, reader counts, and the plan for the board.
//! altium_probe padlib <molecule.PcbDoc> <template.PcbLib> <footprint> <out.PcbLib>
//! Copy the last pad record of a written molecule (an MP pad) into a copy of
//! a single-footprint PcbLib, moved to the footprint origin, so KiCad's own
//! Altium footprint importer (service-kicad altium fp) can parse it.
//! altium_probe strict <file>...
//! Validate compound files with the cfb crate's strict parser.
#![allow(dead_code)]
#[path = "../rs/src/plan.rs"]
mod plan;
#[path = "../rs/src/model.rs"]
mod model;
#[path = "../rs/src/altium/mod.rs"]
mod altium;
use anyhow::{bail, Context, Result};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
fn read_stream(cf: &mut cfb::CompoundFile<Cursor<Vec<u8>>>, p: &str) -> Result<Vec<u8>> {
let mut v = vec![];
cf.open_stream(p).with_context(|| p.to_string())?.read_to_end(&mut v)?;
Ok(v)
}
/// Byte ranges of the 6-subrecord pad records at the head of a record run.
fn pad_spans(d: &[u8], mut i: usize) -> Vec<(usize, usize)> {
let mut out = vec![];
while i < d.len() && d[i] == 2 {
let start = i;
i += 1;
for _ in 0..6 {
let n = u32::from_le_bytes(d[i..i + 4].try_into().unwrap()) as usize;
i += 4 + n;
}
out.push((start, i));
}
out
}
fn padlib(doc: &Path, lib: &Path, fp: &str, out: &Path) -> Result<()> {
let mut d = cfb::CompoundFile::open(Cursor::new(std::fs::read(doc)?))?;
let pads = read_stream(&mut d, "/Pads6/Data")?;
let spans = pad_spans(&pads, 0);
let &(s, e) = spans.last().context("no pads")?;
let mut rec = pads[s..e].to_vec();
// Subrecord 5 offset: type byte + four length-prefixed subrecords.
let mut o = 1;
for _ in 0..4 {
o += 4 + u32::from_le_bytes(rec[o..o + 4].try_into().unwrap()) as usize;
}
let s5 = o + 4;
rec[s5 + 7..s5 + 9].copy_from_slice(&0xFFFFu16.to_le_bytes());
rec[s5 + 13..s5 + 21].fill(0);
let mut l = cfb::CompoundFile::open(Cursor::new(std::fs::read(lib)?))?;
let path = format!("/{fp}/Data");
let data = read_stream(&mut l, &path)?;
let n = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize;
let lspans = pad_spans(&data, 4 + n);
if lspans.len() != 1 {
bail!("expected one pad in {fp}, found {}", lspans.len());
}
let (ls, le) = lspans[0];
let mut nd = data[..ls].to_vec();
nd.extend_from_slice(&rec);
nd.extend_from_slice(&data[le..]);
{
let mut st = l.open_stream(&path)?;
st.seek(SeekFrom::Start(0))?;
st.write_all(&nd)?;
st.set_len(nd.len() as u64)?;
}
l.flush()?;
std::fs::write(out, l.into_inner().into_inner())?;
println!("spliced {} byte pad record (library pad was {} bytes) into {}", rec.len(), le - ls, out.display());
Ok(())
}
fn main() -> Result<()> {
let a: Vec<String> = std::env::args().collect();
match a.get(1).map(|s| s.as_str()) {
Some("summary") if a.len() == 3 => {
let p = Path::new(&a[2]);
let bb = altium::outline_bbox(p)?;
let b = altium::read(p)?;
let pl = plan::make_plan("altium", &a[2], bb, false);
println!("bbox {:?} ({:.4} x {:.4} mm)", bb, bb.w(), bb.h());
println!("components {} pads {} vias {} tracks {}/{} silk {}/{} models {}",
b.components.len(), b.pad_count(), b.vias.len(), b.tracks_top.len(), b.tracks_bottom.len(),
b.silk_top.len(), b.silk_bottom.len(), b.models.len());
println!("meta {:?}", b.meta);
println!("plan {}", serde_json::to_string(&pl.to_json())?);
}
Some("strict") if a.len() >= 3 => {
for f in &a[2..] {
let r = cfb::CompoundFile::open_strict(Cursor::new(std::fs::read(f)?));
println!("{f}: {}", match r { Ok(_) => "strict OK".to_string(), Err(e) => format!("strict FAIL: {e}") });
}
}
Some("padlib") if a.len() == 6 => padlib(Path::new(&a[2]), Path::new(&a[3]), &a[4], Path::new(&a[5]))?,
_ => bail!("usage: altium_probe summary <PcbDoc> | padlib <molecule.PcbDoc> <template.PcbLib> <footprint> <out.PcbLib>"),
}
Ok(())
}