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
2.12.1: Z-up correction for STEP-sourced 3D models; generic delivery folders (PR #46 review)
7 files changed
+60−20
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.12.0"+version = "2.12.1" dependencies = [ "altium-codec", "anyhow",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.12.0"+version = "2.12.1" edition = "2021" description = "adom-lbr generator / EDA translator: the canonical adom-lbr JSON is the hub, with native converters to Altium (.SchLib/.PcbLib/.IntLib), EAGLE/Fusion (.lbr), and KiCad. Altium output is written natively — no Altium needed at conversion time."
VERSION+1−1@@ -1 +1 @@-2.12.0+2.12.1
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.12.0",+ "version": "2.12.1", "type": "app", "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad 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.", "dependencies": {},
page.json+9−12@@ -1,8 +1,8 @@ { "slug": "adom-lbr",- "version": "2.12.0",+ "version": "2.12.1", "type": "app",- "description": "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.",+ "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad 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.", "dependencies": {}, "tags": [ "eda",@@ -52,24 +52,21 @@ ], "files": [ "bin/**",- "assets/**", "SKILL.md",- "skills/**", "README.md", "install.sh", "uninstall.sh",- "screenshots/**",- "dockbar.json"+ "screenshots/**" ],- "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools — KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion — through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.",+ "discovery_pitch": "Convert electronic-component symbols + footprints between any two EDA tools \u2014 KiCad, Altium (written natively, no Altium needed), and EAGLE/Fusion \u2014 through one canonical adom-lbr JSON, silk/courtyard/fab layers preserved. Plus a Manager to browse and inspect any wiki part.", "sample_prompts": [ { "prompt": "Convert this KiCad symbol and footprint to an Altium SchLib and PcbLib",- "label": "KiCad → Altium"+ "label": "KiCad \u2192 Altium" }, { "prompt": "Import this Altium footprint into the adom-lbr JSON",- "label": "Altium → JSON"+ "label": "Altium \u2192 JSON" }, { "prompt": "Bundle this part into a single Altium IntLib",@@ -77,15 +74,15 @@ }, { "prompt": "Convert this Altium SchLib and PcbLib to a KiCad symbol and footprint",- "label": "Altium → KiCad"+ "label": "Altium \u2192 KiCad" }, { "prompt": "Export this adom-lbr JSON to a KiCad .kicad_sym and .kicad_mod",- "label": "JSON → KiCad"+ "label": "JSON \u2192 KiCad" }, { "prompt": "Open the adom-lbr Manager to browse wiki components and view the layer stackup", "label": "Browse parts" } ]-}+}
src/main.rs+44−1@@ -2360,6 +2360,14 @@ fn build_3d_viewer(dir: &std::path::Path, mpn: &str) -> Result<String, String> { } let viewer_js = ensure_model_viewer()?;+ // STEP-sourced GLBs are usually Z-up while glTF is Y-up, so a chip stands+ // on its edge (barrett's PR #46 catch). Chips are flat: whichever bbox+ // axis is thinnest is the height. Thinnest on Z => Z-up model => pitch it+ // -90deg; thinnest on Y => already Y-up, leave it alone.+ let orientation = match glb_flat_axis(&bytes) {+ Some('z') => " orientation=\"0deg -90deg 0deg\"",+ _ => "",+ }; let poster = ["-3d-iso-named.png", "-3d-iso.png", "-3d-iso-lg.png"] .iter().map(|s| dir.join(format!("{mpn}{s}"))).find(|p| p.exists()) .and_then(|p| fs::read(&p).ok())@@ -2367,11 +2375,46 @@ fn build_3d_viewer(dir: &std::path::Path, mpn: &str) -> Result<String, String> { .unwrap_or_default(); Ok(format!( "<script type=\"module\">{viewer_js}</script>\n\- <model-viewer src=\"data:model/gltf-binary;base64,{model}\"{poster} camera-controls auto-rotate shadow-intensity=\"1\" style=\"position:absolute;inset:0;width:100%;height:100%;background:#161c27\"></model-viewer>",+ <model-viewer src=\"data:model/gltf-binary;base64,{model}\"{poster}{orientation} camera-controls auto-rotate shadow-intensity=\"1\" style=\"position:absolute;inset:0;width:100%;height:100%;background:#161c27\"></model-viewer>", model = b64(&bytes) )) } +/// Which axis is the model THINNEST along, from the union of the JSON chunk's+/// VEC3 float accessor min/max bounds. For chip-shaped parts the thinnest axis+/// is the height, which tells us whether the model is Y-up or Z-up.+fn glb_flat_axis(bytes: &[u8]) -> Option<char> {+ if bytes.len() < 20 || &bytes[0..4] != b"glTF" {+ return None;+ }+ let json_len = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]) as usize;+ let end = (20 + json_len).min(bytes.len());+ let v: serde_json::Value = serde_json::from_slice(&bytes[20..end]).ok()?;+ let (mut lo, mut hi) = ([f64::MAX; 3], [f64::MIN; 3]);+ let mut seen = false;+ for acc in v.get("accessors")?.as_array()? {+ // quantized meshes store positions as ints (KHR_mesh_quantization), so+ // accept ANY VEC3 accessor that carries min/max bounds+ if acc.get("type").and_then(|t| t.as_str()) != Some("VEC3") {+ continue;+ }+ let (Some(mn), Some(mx)) = (acc.get("min").and_then(|m| m.as_array()), acc.get("max").and_then(|m| m.as_array())) else { continue };+ if mn.len() != 3 || mx.len() != 3 { continue; }+ for i in 0..3 {+ lo[i] = lo[i].min(mn[i].as_f64().unwrap_or(0.0));+ hi[i] = hi[i].max(mx[i].as_f64().unwrap_or(0.0));+ }+ seen = true;+ }+ if !seen { return None; }+ let ext = [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]];+ let thin = if ext[0] <= ext[1] && ext[0] <= ext[2] { 'x' } else if ext[1] <= ext[2] { 'y' } else { 'z' };+ // only meaningful when the part is clearly flat (height under half the next axis)+ let mut sorted = ext; sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());+ if sorted[0] > sorted[1] * 0.6 { return None; }+ Some(thin)+}+ /// GLB magic + JSON chunk scan for the Draco extension requirement. fn glb_is_draco(bytes: &[u8]) -> bool { if bytes.len() < 20 || &bytes[0..4] != b"glTF" {
src/manage.html+3−3@@ -613,7 +613,7 @@ document.getElementById('modal-cancel').onclick=hideModal; document.getElementById('modal-go').onclick=()=>{if(modalGo)modalGo();}; function selOpt(sel,attr,cb){document.querySelectorAll('#modal-opts .opt').forEach(o=>o.onclick=()=>{document.querySelectorAll('#modal-opts .opt').forEach(x=>x.classList.remove('sel'));o.classList.add('sel');o.querySelector('input').checked=true;cb(o.dataset[attr]);});} // recommended destination parents for delivered projects (editable in the box)-const DEST_FOLDERS=['/home/barrett-land/Documents/KiCAD/Projects','/home/barrett-land/Documents/Adom/Molecules/Other Molecules','/home/barrett-land/Documents/Adom/Electrical','/home/barrett-land/Downloads'];+const DEST_FOLDERS=['~/Documents/KiCad/Projects','~/Documents/Adom/Molecules','~/Documents/Adom/Electrical','~/Downloads']; function openAddLib(){ const slugs=[...selected]; if(!slugs.length)return; const plan=buildPlan(slugs,false);@@ -636,7 +636,7 @@ function openAddProj(){ document.getElementById('modal-sub').textContent=`schematic + ${slugs.length} component(s) — symbols & footprints resolved so it opens clean`; document.getElementById('modal-opts').innerHTML=`<div class="opt sel" data-mode="personal"><input type="radio" name="mode" checked> Integrate into my libraries</div><div class="opt" data-mode="project"><input type="radio" name="mode"> Project-only library</div>`; // editable destination + recommended-folder presets- document.getElementById('modal-dest').innerHTML=`<label>Deliver project to</label><div class="destrow"><select id="destsel">${DEST_FOLDERS.map(f=>`<option value="${f}">${f.replace('/home/barrett-land','~')}</option>`).join('')}</select><input id="destpath" spellcheck="false"></div>`;+ document.getElementById('modal-dest').innerHTML=`<label>Deliver project to</label><div class="destrow"><select id="destsel">${DEST_FOLDERS.map(f=>`<option value="${f}">${f}</option>`).join('')}</select><input id="destpath" spellcheck="false"></div>`; const destpath=document.getElementById('destpath'), destsel=document.getElementById('destsel'); destpath.value=DEST_FOLDERS[0]+'/'+boardSlug; destsel.onchange=()=>{destpath.value=destsel.value+'/'+boardSlug;};@@ -667,7 +667,7 @@ async function doApply(plan,eda,project,board,dest){ clearInterval(poll); const r=p.result||{}, pf=(r.project&&r.project.files||[]); go.textContent=`✓ Added ${r.count||0}`;- const folder=(r.project&&r.project.folder||'').replace('/home/barrett-land','~');+ const folder=(r.project&&r.project.folder||'').replace(/\/home\/[^/]+/,'~'); bar.innerHTML=`<b>✓ Done.</b> Merged ${r.count||0} symbol(s) into your libraries.`+ (pf.length?`<br>Delivered ${pf.length} project file(s) → <span class="lib">${folder}</span>`:'')+ ((r.errors&&r.errors.length)?`<br><span style="color:#f87171">${r.errors.length} issue(s): ${r.errors.slice(0,2).join('; ')}</span>`:'');