app
Adom Library
Public Made by Adomby adom
adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.
← Commit history
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())-}