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.
master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
<!doctype html><html lang=en><head><meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1"><title>__TITLE__</title>
<style>
:root{--bg:#0d1117;--edge:#30363d;--ink:#e6edf3;--mut:#8b949e;--acc:#00b8b0}
*{box-sizing:border-box;margin:0}html,body{height:100%}
body{background:var(--bg);color:var(--ink);font-family:'Satoshi',system-ui,sans-serif;display:flex;flex-direction:column}
.bar{display:flex;flex-wrap:wrap;align-items:center;gap:7px;padding:8px 11px;border-bottom:1px solid var(--edge);flex:none}
.bar .h{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--mut);font-weight:700}
.bar .sp{flex:1}
.lyrtog{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--mut);background:#1a2130;border:1px solid var(--edge);border-radius:999px;padding:3px 9px;cursor:pointer;user-select:none}
.lyrtog.on{color:var(--ink);border-color:#485162}.lyrtog:not(.on){opacity:.5}
.lyrtog .d{width:8px;height:8px;border-radius:50%}
.act{font-size:11px;color:var(--acc);background:rgba(0,184,176,.1);border:1px solid rgba(0,184,176,.4);border-radius:999px;padding:3px 11px;cursor:pointer;font-family:'Satoshi'}
.act:hover{background:rgba(0,184,176,.18)}.act:disabled{opacity:.5;cursor:default}
.act.plain{color:var(--ink);background:#1a2130;border-color:var(--edge)}.act.plain:hover{border-color:var(--acc);background:#1a2130}
.stage{flex:1;min-height:0;position:relative;overflow:hidden;background:var(--bg)}
.stage canvas{width:100%;height:100%;display:block;outline:none;touch-action:none}
.ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--mut);font-size:12px}
</style></head><body>
<div class=bar><span class=h>3D model</span><span class=sp></span>
<button class="lyrtog on" data-mesh="solder"><span class=d style="background:#c9ccd1"></span>Solder</button>
<button class="lyrtog on" data-mesh="copper"><span class=d style="background:#d8a24a"></span>Copper</button>
<button class="lyrtog on" data-mesh="fr4"><span class=d style="background:#2f7d4f"></span>Board</button>
<button class=act id=play title="Watch it get placed, soldered, and powered on">▶ Lifecycle</button>
<button class="act plain" id=reset>Reset</button>
</div>
<div class=stage id=stage><div class=ph id=ph>loading 3D…</div></div>
<script>__VIEWERJS_INLINE__</script>
<script>
var MPN=__MPNJSON__, GLB="__GLB__", MARKS=__MARKS__;
var v=null, B=(window.Adom3DViewer&&Adom3DViewer.BABYLON)||{};
function rmPh(){var p=document.getElementById('ph');if(p)p.remove();}
function isHelper(n){return /arcrotate|background|skybox|indicator|helper|ground|viewcube|viewerroot|laseretch|dbg/i.test(n||'');}
function realMeshes(s){return (s.meshes||[]).filter(function(m){return m.getTotalVertices&&m.getTotalVertices()>0 && !isHelper(m.name);});}
function byName(s,re){return realMeshes(s).filter(function(m){return re.test(m.name||'');});}
var RE={solder:/solder/i, copper:/copper/i, fr4:/fr4/i, chip:/LQFP|chip/i};
function boundsOf(mm){var lo=null,hi=null;mm.forEach(function(m){try{m.computeWorldMatrix(true);var bb=m.getBoundingInfo().boundingBox;lo=lo?B.Vector3.Minimize(lo,bb.minimumWorld):bb.minimumWorld.clone();hi=hi?B.Vector3.Maximize(hi,bb.maximumWorld):bb.maximumWorld.clone();}catch(_){}});return (lo&&hi)?{lo:lo,hi:hi}:null;}
try{
v=Adom3DViewer.init(document.getElementById('stage'),{modelUrl:GLB,zUp:true,showGround:false,showViewCube:true,initialViewMode:"perspective"});
// wait until the mesh count is STABLE (all parts loaded), not just the first primitive
var prev=-1, stable=0, tries=0, iv=setInterval(function(){
tries++;
try{
var s=v.getScene(), n=realMeshes(s).length;
stable=(n>0&&n===prev)?stable+1:0; prev=n;
if(n>0&&stable>=3){ clearInterval(iv); try{v.frameModel&&v.frameModel();}catch(_){} try{v.goHome&&v.goHome();}catch(_){} rmPh(); onReady(s); }
}catch(_){}
if(tries>100){ clearInterval(iv); var p=document.getElementById('ph'); if(p)p.textContent='3D: model did not load'; }
},200);
}catch(err){ var p=document.getElementById('ph'); if(p)p.textContent='3D unavailable: '+err; }
// the GLB places the solder dots with a small gap above the copper pads; seat them
function seatSolder(s){
try{
var cop=boundsOf(byName(s,RE.copper)), sol=byName(s,RE.solder), sb=boundsOf(sol);
if(!cop||!sb)return;
var gap=sb.lo.z-cop.hi.z; // solder bottom minus pad top
if(gap>0){ sol.forEach(function(m){ try{var p=m.getAbsolutePosition(); m.setAbsolutePosition(new B.Vector3(p.x,p.y,p.z-gap*1.05));}catch(_){} }); }
}catch(_){}
}
// a chip sits flush on its board; the fixed-light cast shadow reads as a baked smudge, so drop it
function disableShadows(s){
try{ s.lights&&s.lights.forEach(function(l){ l.shadowEnabled=false; }); }catch(_){}
try{ var sg=v.getShadowGenerator&&v.getShadowGenerator(); if(sg&&sg.getShadowMap&&sg.getShadowMap()) sg.getShadowMap().renderList=[]; }catch(_){}
try{ (s.meshes||[]).forEach(function(m){ m.receiveShadows=false; }); }catch(_){}
}
function onReady(s){
seatSolder(s);
disableShadows(s);
try{ if(MARKS && MARKS.length) addEtch(s, MARKS); }catch(_){}
// layer toggles by part name (fr4 / copper / solder_paste / LQFP-chip)
[].slice.call(document.querySelectorAll('.lyrtog')).forEach(function(b){
b.addEventListener('click',function(){
var on=b.classList.toggle('on'), re=RE[b.getAttribute('data-mesh')];
byName(s,re).forEach(function(m){ m.setEnabled(on); });
});
});
var pb=document.getElementById('play'); if(pb) pb.addEventListener('click',function(){ playLifecycle(s); });
var rb=document.getElementById('reset'); if(rb) rb.addEventListener('click',function(){ try{v.goHome&&v.goHome();}catch(_){} });
if((location.hash||'').indexOf('play')>=0) setTimeout(function(){playLifecycle(s);},500);
if((location.hash||'').indexOf('low')>=0){ try{var c=v.getCamera(); if(c){c.beta=1.46; c.radius=c.radius*0.32;}}catch(_){} }
if((location.hash||'').indexOf('demo')>=0){
try{var c=v.getCamera(); if(c){c.beta=1.05; c.radius=c.radius*1.12;}}catch(_){}
var reps=6,i=0; (function loop(){ if(i++>=reps)return; playLifecycle(s); setTimeout(loop,9800); })();
}
if((location.hash||'').indexOf('vid')>=0){ // single play from a nice angle, delayed so recording can start first
try{var c=v.getCamera(); if(c){c.beta=1.08; c.radius=c.radius*0.9;}}catch(_){}
setTimeout(function(){ playLifecycle(s); }, 3800);
}
}
// ---- laser-etch the MPN onto the chip top (world up is +Z) ----
function addEtch(s, lines){
if(!B.DynamicTexture || !B.MeshBuilder) return;
var chipList=byName(s,RE.chip);
var bnd=boundsOf(chipList); if(!bnd) return;
var w=bnd.hi.x-bnd.lo.x, h=bnd.hi.y-bnd.lo.y, cx=(bnd.lo.x+bnd.hi.x)/2, cy=(bnd.lo.y+bnd.hi.y)/2, topz=bnd.hi.z;
if(!(w>0&&h>0)) return;
var side=Math.min(w,h)*0.62, W=1024, H=1024; // sit inside the body, not over the leads
var L=lines.slice(), maxc=1; L.forEach(function(x){maxc=Math.max(maxc,String(x).length);});
var fs=Math.min((W*0.86)/(maxc*0.62), (H*0.6)/L.length); // fit text to the plane (width + height)
var tex=new B.DynamicTexture("etchTex",{width:W,height:H},s,true), ctx=tex.getContext();
ctx.clearRect(0,0,W,H); ctx.fillStyle="rgba(207,210,214,0.78)"; ctx.textAlign="center"; ctx.textBaseline="middle";
ctx.font="400 "+Math.round(fs)+"px monospace"; // thin, like a real laser mark
for(var i=0;i<L.length;i++) ctx.fillText(String(L[i]), W/2, H*(i+1)/(L.length+1));
tex.update();
var mat=new B.StandardMaterial("etchMat",s);
mat.diffuseTexture=tex; tex.hasAlpha=true; mat.useAlphaFromDiffuseTexture=true;
mat.emissiveColor=new B.Color3(0.66,0.67,0.69); mat.disableLighting=true; mat.backFaceCulling=false;
var pl=B.MeshBuilder.CreatePlane("laserEtch",{size:side,sideOrientation:2},s);
var dz=bnd.hi.z-bnd.lo.z; // chip height
pl.position=new B.Vector3(cx, cy, topz+dz*0.02); // just skimming the chip top face
pl.material=mat; pl.isPickable=false;
// attach the etch to the chip body so it moves with the chip (e.g. during the animation)
var body=null; chipList.forEach(function(m){ if(!body||m.getTotalVertices()>body.getTotalVertices())body=m; });
if(body){ try{ pl.setParent(body); }catch(_){} }
}
// ---- lifecycle animation: lift -> toggle solder -> place -> reflow -> power-on ----
var animing=false;
function setEmis(mm,r,g,b){mm.forEach(function(m){try{if(m.material&&m.material.emissiveColor&&B.Color3)m.material.emissiveColor=new B.Color3(r,g,b);}catch(_){}});}
function tween(dur,upd,done){var t0=performance.now();(function st(now){var t=Math.min(1,(now-t0)/dur);upd(t);if(t<1)requestAnimationFrame(st);else if(done)done();})(t0);}
function easeIO(t){return t<0.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2;}
function playLifecycle(s){
if(animing||!B.Vector3)return; animing=true;
var pb=document.getElementById('play'); if(pb)pb.disabled=true;
var chip=byName(s,RE.chip), sol=byName(s,RE.solder), solBtn=document.querySelector('.lyrtog[data-mesh="solder"]');
var cb=boundsOf(chip); var lift=cb?Math.max(cb.hi.x-cb.lo.x, cb.hi.y-cb.lo.y)*0.55:0.004;
chip.forEach(function(m){ if(!m.__oz){m.computeWorldMatrix(true); m.__op=m.getAbsolutePosition().clone(); m.__oz=1;} });
function place(f){ chip.forEach(function(m){ try{m.setAbsolutePosition(new B.Vector3(m.__op.x, m.__op.y, m.__op.z+lift*f));}catch(_){} }); }
function solOn(on){ sol.forEach(function(m){m.setEnabled(on);}); if(solBtn)solBtn.classList.toggle('on',on); }
function fin(){ place(0); setEmis(chip,0,0,0); setEmis(sol,0,0,0); animing=false; if(pb)pb.disabled=false; }
place(0); solOn(true);
tween(1000, function(t){ place(easeIO(t)); }, function(){ // 1) lift the chip up
setTimeout(function(){ solOn(false); // 2) solder off
setTimeout(function(){ solOn(true); // solder back on
setTimeout(function(){
tween(1400, function(t){ place(1-easeIO(t)); }, function(){ // 3) lower onto the solder
tween(1600, function(t){ var g=Math.sin(t*Math.PI); setEmis(sol,0.95*g,0.30*g,0); setEmis(chip,0.26*g,0.08*g,0); }, function(){ setEmis(sol,0,0,0); setEmis(chip,0,0,0); // 4) slow reflow glow
tween(1700, function(t){ var p=Math.sin(t*Math.PI); setEmis(chip,0,0.5*p,0.48*p); }, fin); // 5) slow power-on pulse
});
});
}, 700);
}, 800);
}, 700);
});
}
// cross-highlight bus: 3D joins as a listener (per-lead highlight needs named leads)
window.addEventListener('message',function(e){var d=e.data||{};if(d.type!=='lbr-hover')return;});
function ph(){parent.postMessage({type:'readme-height',height:Math.max(360,document.body.scrollHeight||420)},'*');}
window.addEventListener('load',ph);setTimeout(ph,400);
</script>
</body></html>