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 + footp
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
//! Native Altium `.SchLib` WRITER — pure Rust, no Altium. The inverse of the
//! reader: emits an OLE2/CFB container whose streams and records are
//! byte-structurally identical to what real Altium produces (verified by
//! re-reading with this crate's parser and by byte-diffing pin records against
//! manufacturer `.SchLib` files).
//!
//! Container: `/FileHeader` (component list) · `/Storage` (icon placeholder) ·
//! `/\x01Sh33tJ5` (the constant "7262" marker) · `/<part>` storage · `/<part>/Data`
//! (the record stream). Every stream is framed `<u16 len><u16 flags><payload>`.
use anyhow::Result;
use std::io::{Cursor, Write};
/// A schematic pin to emit, in Altium SCH internal units (1 unit = 2.54/2560 mm).
/// `x`/`y` are the pin's connection-end location; Altium Y is up. `rotation` is
/// 0/90/180/270 degrees.
pub struct WritePin {
pub name: String,
pub designator: String,
pub x: i32,
pub y: i32,
pub length: i32,
pub rotation: i32,
}
/// The fixed 26-byte prefix of an Altium pin record (record type `0x02`),
/// captured byte-for-byte from a real manufacturer `.SchLib`. Bytes [14] (pin
/// conglomerate / orientation), [15..17] (length), [17..19] (X) and [19..21] (Y)
/// are overwritten per pin; the rest (owner-part id, electrical defaults, the
/// trailing sentinels) carry through unchanged — which is why generated pins are
/// byte-identical to Altium's own.
const PIN_PREFIX: [u8; 26] = [
0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x04, 0x3a, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
fn put_rec(out: &mut Vec<u8>, flags: u16, payload: &[u8]) {
out.extend_from_slice(&(payload.len() as u16).to_le_bytes());
out.extend_from_slice(&flags.to_le_bytes());
out.extend_from_slice(payload);
}
/// Altium length-prefixed (1-byte) string.
fn put_pstr(out: &mut Vec<u8>, s: &str) {
let b = s.as_bytes();
let n = b.len().min(255);
out.push(n as u8);
out.extend_from_slice(&b[..n]);
}
/// A text record (`|KEY=VAL|…`) — payload is the text plus a trailing NUL, the
/// way Altium frames its FileHeader and component header records.
fn put_text(out: &mut Vec<u8>, flags: u16, body: &str) {
let mut p = body.as_bytes().to_vec();
p.push(0);
put_rec(out, flags, &p);
}
/// Deterministic 8-uppercase-letter UniqueID from an index (Altium wants a
/// stable A–Z token per primitive; exact value is cosmetic).
fn uid(i: usize) -> String {
let mut s = String::new();
let mut n = i + 1;
for _ in 0..8 {
s.push((b'A' + (n % 26) as u8) as char);
n = n / 26 + 1;
}
s
}
/// Emit a native `.SchLib` for one component. `body` is the symbol body
/// rectangle (x1,y1,x2,y2) in Altium SCH units. `params` become hidden
/// `RECORD=41` parameters (Manufacturer / MPN / "LCSC Part #" / …) — exactly the
/// fields Altium surfaces in the BOM.
pub fn to_schlib_bytes(
part: &str,
description: &str,
body: (i32, i32, i32, i32),
pins: &[WritePin],
params: &[(String, String)],
) -> Result<Vec<u8>> {
// ----- component Data stream -----
let mut d = Vec::new();
put_text(&mut d, 0, &format!(
"|RECORD=1|LibReference={part}|ComponentDescription={description}|PartCount=1|DisplayModeCount=1|IndexInSheet=-1|OwnerPartId=-1|CurrentPartId=1|LibraryPath=*|SourceLibraryName=*|SheetPartFileName=*|TargetFileName=*|UniqueID={}|AreaColor=11599871|Color=128|PartIDLocked=T|AllPinCount={}",
uid(0), pins.len()));
for (k, (name, val)) in params.iter().enumerate() {
put_text(&mut d, 0, &format!(
"|RECORD=41|IndexInSheet={k}|OwnerPartId=-1|Color=8388608|FontID=1|IsHidden=T|Text={val}|Name={name}|UniqueID={}",
uid(100 + k)));
}
let (lx, ly, cx, cy) = body;
put_text(&mut d, 0, &format!(
"|RECORD=14|IsNotAccesible=T|OwnerPartId=1|Location.X={lx}|Location.Y={ly}|Corner.X={cx}|Corner.Y={cy}|LineWidth=1|Color=128|AreaColor=11599871|IsSolid=T|UniqueID={}",
uid(50)));
for p in pins {
let mut h = PIN_PREFIX.to_vec();
h[14] = (h[14] & !0x03) | (((p.rotation.rem_euclid(360) / 90) & 0x03) as u8);
h[15..17].copy_from_slice(&(p.length as i16).to_le_bytes());
h[17..19].copy_from_slice(&(p.x as i16).to_le_bytes());
h[19..21].copy_from_slice(&(p.y as i16).to_le_bytes());
let mut pl = h;
put_pstr(&mut pl, &p.name);
put_pstr(&mut pl, &p.designator);
put_pstr(&mut pl, "0");
put_pstr(&mut pl, "0");
put_pstr(&mut pl, "");
put_rec(&mut d, 0x0100, &pl);
}
// ----- FileHeader (the component list) -----
let mut fh = Vec::new();
put_text(&mut fh, 0, &format!(
"|HEADER=Protel for Windows - Schematic Library Editor Binary File Version 5.0|Weight=80|MinorVersion=3|UniqueID=ADOMLBRX|FontIdCount=2|Size1=10|FontName1=Times New Roman|Size2=10|Underline2=T|FontName2=Times New Roman|UseMBCS=T|IsBOC=T|SheetStyle=9|BorderOn=T|SheetNumberSpaceSize=12|AreaColor=16317695|SnapGridOn=T|SnapGridSize=10|VisibleGridOn=T|VisibleGridSize=10|CustomX=18000|CustomY=18000|UseCustomSheet=T|ReferenceZonesOn=T|Display_Unit=0|CompCount=1|LibRef0={part}|CompDescr0={description}|PartCount0=1|AlwaysShowCD=T"));
let mut storage = Vec::new();
put_text(&mut storage, 0, "|HEADER=Icon storage");
// ----- assemble the OLE2/CFB container -----
let mut comp = cfb::CompoundFile::create(Cursor::new(Vec::new()))?;
comp.create_stream("/FileHeader")?.write_all(&fh)?;
comp.create_stream("/Storage")?.write_all(&storage)?;
comp.create_stream("/\u{1}Sh33tJ5")?.write_all(b"7262")?;
comp.create_storage(&format!("/{part}"))?;
comp.create_stream(&format!("/{part}/Data"))?.write_all(&d)?;
comp.flush()?;
Ok(comp.into_inner().into_inner())
}