← Commit history

Delete src/altium_pcblib_write.rs

Colby Knox ·cc72e1ef2d ·1mo ago ·parent 841465f
1 file changed −188
src/altium_pcblib_write.rsdeleted−188
@@ -1,188 +0,0 @@-//! Native Altium `.PcbLib` WRITER — pure Rust, no Altium. The inverse of the-//! reader. Strategy: take a real `.PcbLib` as a structural TEMPLATE (its-//! `/Library/*` board/layer/stackup blob is hard to synthesize and easy to keep-//! verbatim), then:-//!   * rebuild the footprint `/<fp>/Data` stream from the part's pads — each pad-//!     record cloned byte-for-byte from a template pad and patched at the-//!     documented geometry offsets (so pads are byte-conformant to Altium's own),-//!   * rename the footprint storage + its `Parameters` + the library's-//!     `ComponentParamsTOC`, and length-safe-patch `/Library/Data`.-//!-//! NB: not yet round-tripped through a real Altium install (none available);-//! validated by re-reading with this crate's parser + byte-conformance of pad-//! records against manufacturer `.PcbLib` files. Labeled experimental upstream.--use anyhow::{anyhow, Result};-use std::io::{Cursor, Read, Write};--/// A pad to emit, in Altium internal units (1 unit = 1/10000 mil = 2.54e-6 mm).-/// `x`/`y` are the pad centre; Altium Y is up. `hole` 0 = SMD. `shape`: 1 round,-/// 2 rect, 9 rounded-rect.-pub struct WritePad {-    pub designator: String,-    pub x: i32,-    pub y: i32,-    pub xsize: u32,-    pub ysize: u32,-    pub hole: u32,-    pub shape: u8,-    pub rotation: f64,-    pub layer: u8,-}--fn u32at(d: &[u8], o: usize) -> usize {-    u32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]) as usize-}--/// Pull a footprint storage name + a template pad's 6 sub-blocks out of a-/// footprint `Data` stream.-fn template_pad(fdata: &[u8]) -> Result<Vec<Vec<u8>>> {-    let nbl = u32at(fdata, 0);-    let mut i = 4 + nbl;-    while i < fdata.len() {-        let t = fdata[i];-        let mut j = i + 1;-        let nsub = match t { 0x02 => 6, 0x05 => 2, _ => 1 };-        let mut blocks = Vec::with_capacity(nsub);-        for _ in 0..nsub {-            if j + 4 > fdata.len() { return Err(anyhow!("truncated record")); }-            let bl = u32at(fdata, j);-            j += 4;-            if j + bl > fdata.len() { return Err(anyhow!("truncated sub-block")); }-            blocks.push(fdata[j..j + bl].to_vec());-            j += bl;-        }-        if t == 0x02 { return Ok(blocks); }-        i = j;-    }-    Err(anyhow!("no pad record in template footprint"))-}--/// Emit one pad record: clone the template's 6 sub-blocks, patch sub-block 1-/// (designator) + sub-block 5 (geometry) per the documented byte map.-fn emit_pad(tmpl: &[Vec<u8>], p: &WritePad) -> Vec<u8> {-    let mut blocks: Vec<Vec<u8>> = tmpl.to_vec();-    // sub-block 1 = designator (Pascal string)-    let db = p.designator.as_bytes();-    let n = db.len().min(255);-    let mut des = vec![n as u8];-    des.extend_from_slice(&db[..n]);-    blocks[0] = des;-    // sub-block 5 = main pad data-    let b5 = &mut blocks[4];-    if b5.len() >= 49 {-        b5[0] = p.layer;-        b5[13..17].copy_from_slice(&p.x.to_le_bytes());-        b5[17..21].copy_from_slice(&p.y.to_le_bytes());-        for off in [21usize, 29, 37] { // top / mid / bottom x,y sizes (SMD: all equal)-            b5[off..off + 4].copy_from_slice(&p.xsize.to_le_bytes());-            b5[off + 4..off + 8].copy_from_slice(&p.ysize.to_le_bytes());-        }-        b5[45..49].copy_from_slice(&p.hole.to_le_bytes());-        if b5.len() > 51 { b5[49] = p.shape; b5[50] = p.shape; b5[51] = p.shape; }-        if b5.len() >= 60 { b5[52..60].copy_from_slice(&p.rotation.to_le_bytes()); }-    }-    let mut out = vec![0x02u8];-    for b in &blocks {-        out.extend_from_slice(&(b.len() as u32).to_le_bytes());-        out.extend_from_slice(b);-    }-    out-}--fn framed(payload: &[u8]) -> Vec<u8> {-    let mut out = Vec::with_capacity(payload.len() + 5);-    out.extend_from_slice(&(payload.len() as u16).to_le_bytes());-    out.extend_from_slice(&0u16.to_le_bytes());-    out.extend_from_slice(payload);-    out-}-fn text_payload(s: &str) -> Vec<u8> { let mut p = s.as_bytes().to_vec(); p.push(0); p }--fn build_params(fp: &str, height: &str, descr: &str) -> Vec<u8> {-    framed(&text_payload(&format!(-        "|PATTERN={fp}|HEIGHT={height}|DESCRIPTION={descr}|ITEMGUID=|REVISIONGUID=")))-}-fn build_toc(fp: &str, npads: usize, height: &str, descr: &str) -> Vec<u8> {-    framed(&text_payload(&format!(-        "Name={fp}|Pad Count={npads}|Height={height}|Description={descr}")))-}--/// Length-preserving byte replacement (only safe when from.len()==to.len()).-fn raw_replace(buf: &[u8], from: &[u8], to: &[u8]) -> Vec<u8> {-    if from.len() != to.len() || from.is_empty() { return buf.to_vec(); }-    let mut out = buf.to_vec();-    let mut i = 0;-    while i + from.len() <= out.len() {-        if &out[i..i + from.len()] == from {-            out[i..i + from.len()].copy_from_slice(to);-            i += from.len();-        } else { i += 1; }-    }-    out-}--/// Emit a native `.PcbLib` for one footprint, using `template` (a real `.PcbLib`)-/// for the library/board/layer housekeeping. The footprint storage is rebuilt-/// from `pads`; the rest is cloned + renamed.-pub fn to_pcblib_bytes(-    template: &[u8],-    fpname: &str,-    description: &str,-    height: &str,-    pads: &[WritePad],-) -> Result<Vec<u8>> {-    let mut v = template.to_vec();-    if v.len() < 1024 * 1024 { v.resize(1024 * 1024, 0xFF); }-    let mut c = cfb::CompoundFile::open(Cursor::new(v))?;-    let tfp = c.read_root_storage()-        .find(|e| e.is_storage() && !e.name().starts_with('\u{1}') && e.name() != "Library" && e.name() != "FileVersionInfo")-        .map(|e| e.name().to_string())-        .ok_or_else(|| anyhow!("no footprint storage in template"))?;--    let mut tdata = Vec::new();-    c.open_stream(format!("/{tfp}/Data"))?.read_to_end(&mut tdata)?;-    let tmplpad = template_pad(&tdata)?;--    // new footprint Data: name block + pad records-    let mut nd = Vec::new();-    let nb_name = fpname.as_bytes();-    let nn = nb_name.len().min(255);-    let mut nb = vec![nn as u8];-    nb.extend_from_slice(&nb_name[..nn]);-    nd.extend_from_slice(&(nb.len() as u32).to_le_bytes());-    nd.extend_from_slice(&nb);-    for p in pads { nd.extend_from_slice(&emit_pad(&tmplpad, p)); }--    // rewrite container, renaming the footprint storage + patching name refs-    let entries: Vec<_> = c.walk().map(|e| (e.path().to_path_buf(), e.is_storage())).collect();-    let mut out = cfb::CompoundFile::create(Cursor::new(Vec::new()))?;-    let old_root = format!("/{tfp}");-    let new_root = format!("/{fpname}");-    for (p, is_stg) in &entries {-        let ps = p.to_string_lossy().to_string();-        if ps == "/" { continue; }-        let nps = if ps == old_root || ps.starts_with(&format!("{old_root}/")) {-            ps.replacen(&old_root, &new_root, 1)-        } else { ps.clone() };-        if *is_stg {-            let _ = out.create_storage(&nps);-        } else {-            let mut buf = Vec::new();-            c.open_stream(&ps)?.read_to_end(&mut buf)?;-            if ps == format!("/{tfp}/Data") {-                buf = nd.clone();-            } else if ps == format!("/{tfp}/Parameters") {-                buf = build_params(fpname, height, description);-            } else if ps == "/Library/ComponentParamsTOC/Data" {-                buf = build_toc(fpname, pads.len(), height, description);-            } else if ps == "/Library/Data" {-                // only length-safe; otherwise leave the template's refs intact-                buf = raw_replace(&buf, tfp.as_bytes(), fpname.as_bytes());-            }-            out.create_stream(&nps)?.write_all(&buf)?;-        }-    }-    out.flush()?;-    Ok(out.into_inner().into_inner())-}