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
Altium body height from the STEP z-extent (wiki #22, the culling field)
2 files changed
+58−2
crates/altium-codec/src/lib.rs+14@@ -47,6 +47,16 @@ mod tests { } } + #[test]+ fn overall_height_comes_from_the_step_z_extent() {+ // 1.2 mm of z-extent must render as Altium writes it: 47.2441mil+ // (the exact value Altium itself derived for the CN809S kernel test).+ let step = b"ISO-10303-21;\nDATA;\n#1=CARTESIAN_POINT('',(0.,0.,0.));\n#2=CARTESIAN_POINT('',(1.,1.,1.2));\nENDSEC;\n";+ let h = pcblib::step_z_extent_mil(step).expect("extent");+ assert!((h - 47.2441).abs() < 0.001, "1.2mm must read as 47.2441 mil, got {h}");+ assert_eq!(pcblib::step_z_extent_mil(b"no points here"), None);+ }+ #[test] fn embedded_step_models_land_in_the_store_and_the_body_record() { // wiki adom-lbr#22: the Altium lane shipped an empty model store, so@@ -82,6 +92,10 @@ mod tests { let fd = read(&mut comp, "/WITHMODEL/Data"); let ftxt = String::from_utf8_lossy(&fd).to_string(); assert!(ftxt.contains("MODEL.EMBED=TRUE") && ftxt.contains("V7_LAYER=MECHANICAL1"), "body record missing");+ // Altium CULLS a zero-height body (the live A/B on adom-lbr#22 proved+ // OVERALLHEIGHT=0mil was the one field between bare pads and a rendered+ // part), so a synthetic STEP with no parseable points takes the floor.+ assert!(ftxt.contains("OVERALLHEIGHT=10.0000mil"), "floor height must be written, never 0: {ftxt}"); let id = md.split("ID={").nth(1).unwrap().split('}').next().unwrap(); assert!(ftxt.contains(&format!("MODELID={{{id}}}")), "body must reference the store's MODELID"); // primitive count includes the body
crates/altium-codec/src/pcblib.rs+44−2@@ -174,6 +174,9 @@ struct ModelRef<'a> { em: &'a EmbeddedModel, guid: String, checksum: u32,+ /// Height actually written: the model's own field, else the STEP z-extent,+ /// else a 10 mil floor. Never zero, because Altium culls a zero-height body.+ height_mil: f64, } pub fn encode_pcblib(fp: &Footprint) -> Result<Vec<u8>> {@@ -209,6 +212,11 @@ pub fn encode_pcblib_many_with_models(fps: &[Footprint], models: &[Option<Embedd h.sum() }; let guid = model_guid(&em.name, &em.step);+ let height_mil = if em.overall_height_mil > 0.0 {+ em.overall_height_mil+ } else {+ step_z_extent_mil(&em.step).filter(|h| *h > 0.0).unwrap_or(10.0)+ }; // The store prints the checksum SIGNED and the body record // prints it UNSIGNED; both renderings of one u32 (vendor // fixture: -1067402024 and 3227565272).@@ -219,7 +227,7 @@ pub fn encode_pcblib_many_with_models(fps: &[Footprint], models: &[Option<Embedd let mut enc = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default()); std::io::Write::write_all(&mut enc, &em.step)?; store.push((rec, enc.finish()?));- refs.push(Some(ModelRef { em, guid, checksum }));+ refs.push(Some(ModelRef { em, guid, checksum, height_mil })); } None => refs.push(None), }@@ -599,6 +607,40 @@ fn harvest_pad_data_block(tpl: &BTreeMap<String, Vec<u8>>) -> Result<Vec<u8>> { } /// Create any missing parent storages, then write `path` as a stream.+/// Overall height of a STEP body in mils, from the z-extent of its+/// CARTESIAN_POINT entities (wiki part models are millimetre-unit STEP).+/// Altium CULLS a body whose OVERALLHEIGHT is 0, so this is what keeps an+/// embedded model visible (adom-lbr#22: the one field between bare pads and+/// a rendered part).+pub fn step_z_extent_mil(step: &[u8]) -> Option<f64> {+ let txt = String::from_utf8_lossy(step);+ let (mut zmin, mut zmax) = (f64::MAX, f64::MIN);+ let mut found = false;+ let mut rest: &str = &txt;+ while let Some(i) = rest.find("CARTESIAN_POINT") {+ rest = &rest[i + 15..];+ // shapes: CARTESIAN_POINT('name',(x,y,z)) with arbitrary spacing+ let Some(open) = rest.find('(') else { break };+ let Some(coords_open) = rest[open + 1..].find('(') else { break };+ let coords_start = open + 1 + coords_open + 1;+ let Some(coords_len) = rest[coords_start..].find(')') else { break };+ let coords = &rest[coords_start..coords_start + coords_len];+ let parts: Vec<&str> = coords.split(',').collect();+ if parts.len() == 3 {+ if let Ok(z) = parts[2].trim().parse::<f64>() {+ zmin = zmin.min(z);+ zmax = zmax.max(z);+ found = true;+ }+ }+ rest = &rest[coords_start + coords_len..];+ }+ if !found || zmax <= zmin {+ return None;+ }+ Some((zmax - zmin) / 0.0254)+}+ /// Deterministic model GUID (8-4-4-4-12 uppercase) hashed from the model name /// and payload, so identical inputs encode identically. fn model_guid(name: &str, step: &[u8]) -> String {@@ -639,7 +681,7 @@ fn body_record(fp: &Footprint, m: &ModelRef) -> Vec<u8> { ]; let txt = format!( "V7_LAYER=MECHANICAL1|NAME= |KIND=0|SUBPOLYINDEX=-1|UNIONINDEX=0|ARCRESOLUTION=0.5mil|ISSHAPEBASED=FALSE|CAVITYHEIGHT=0mil|STANDOFFHEIGHT=0mil|OVERALLHEIGHT={oh}mil|BODYPROJECTION=0|ARCRESOLUTION=0.5mil|BODYCOLOR3D=8421504|BODYOPACITY3D=1.000|IDENTIFIER=|TEXTURE=|TEXTURECENTERX=0mil|TEXTURECENTERY=0mil|TEXTURESIZEX=0mil|TEXTURESIZEY=0mil|TEXTUREROTATION= 0.00000000000000E+0000|MODELID={{{id}}}|MODEL.CHECKSUM={ck}|MODEL.EMBED=TRUE|MODEL.NAME={name}|MODEL.2D.X=0mil|MODEL.2D.Y=0mil|MODEL.2D.ROTATION=0.000|MODEL.3D.ROTX={rx:.3}|MODEL.3D.ROTY={ry:.3}|MODEL.3D.ROTZ={rz:.3}|MODEL.3D.DZ={dz}mil|MODEL.MODELTYPE=1|MODEL.MODELSOURCE=Undefined",- oh = m.em.overall_height_mil as i64,+ oh = format!("{:.4}", m.height_mil), id = m.guid, ck = m.checksum, name = m.em.name,