molecule
Drone Motor Carrier
Public Made by Adomby adom
Passive baseplate for the single-motor drone test cell: mounts the BLDC motor, passes its three phases through, routes two load-cell channels, and nests the RP2350A/ADS1220 acquisition molecule and a PiProbe over a 96 x 96 mm machine-pin datum. 104 x 104 mm, 4-layer.
← Commit history
Publish 1.0.0
7 files changed
+2119
.gitignoreadded+6@@ -0,0 +1,6 @@+*.step+viewer/adom-3d-viewer.min.js+viewer/sch-root.html+viewer/layout.html+viewer/board-detailed.glb+src/
README.htmladded+709@@ -0,0 +1,709 @@+<meta charset="utf-8">+<title>Feeder Reader — Feeder Pin Input Module</title>+<style>+ :root{ --bg:#151b23; --card:#1b232d; --ink:#e8eef5; --dim:#9fb0c3; --dim2:#7f92a6;+ --line:rgba(255,255,255,.10); --acc:#22c55e; }+ *{box-sizing:border-box}+ body{margin:0;background:var(--bg);color:var(--ink);+ font-family:ui-sans-serif,-apple-system,"Segoe UI",Inter,system-ui,sans-serif;+ font-size:15px;line-height:1.62;-webkit-font-smoothing:antialiased}+ .wrap{max-width:960px;margin:0 auto;padding:0 24px}+ a{color:#7fb2ff;text-decoration:none} a:hover{text-decoration:underline}+ h2{font-size:13px;letter-spacing:.15em;text-transform:uppercase;color:var(--dim2);+ margin:56px 0 18px;font-weight:700}+ h3{font-size:16px;margin:0;font-weight:650}+ p{margin:0 0 15px}+ .lede{font-size:17px;line-height:1.65;color:#cfdae7}+ .mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px}+ .dot{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:7px;vertical-align:0}++ /* ---------- viewer ---------- */+ .viewer-card{width:100%;background:#2a3340;border-bottom:1px solid var(--line);position:relative}+ .viewer-card iframe{width:100%;border:0;display:block}+ .viewer-hint{position:absolute;left:0;right:0;bottom:0;padding:7px 20px;font-size:11px;+ color:#8fa2b6;background:linear-gradient(transparent,rgba(10,15,22,.75));pointer-events:none}++ /* ---------- title ---------- */+ .title{padding:38px 0 6px}+ .title h1{font-size:38px;line-height:1.12;margin:0 0 6px;font-weight:680;letter-spacing:-.015em}+ .title .sub{font-size:15px;color:var(--dim);margin-bottom:18px}+ .facts{display:flex;flex-wrap:wrap;gap:7px;margin-bottom:20px}+ .facts span{background:var(--card);border:1px solid var(--line);border-radius:7px;+ padding:5px 10px;font-size:12px;color:#cfdae7}+ .facts span b{color:#fff;font-weight:600}+ .prov{background:rgba(59,130,246,.09);border:1px solid rgba(59,130,246,.28);+ border-radius:10px;padding:13px 15px;font-size:13px;color:#b9c7d7;margin:6px 0 8px}+ .prov b{color:#dce6f2}++ /* ---------- generic blocks ---------- */+ .grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}+ .card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:17px 19px}+ .card h3{margin-bottom:8px}+ .card p:last-child{margin-bottom:0}+ .sec{background:var(--card);border:1px solid var(--line);border-left:3px solid var(--c);+ border-radius:10px;padding:15px 17px;margin-bottom:11px}+ .sec-h{display:flex;align-items:center;gap:0;margin-bottom:7px}+ .sec-h .count{margin-left:auto;font-size:11.5px;color:var(--dim2);+ font-variant-numeric:tabular-nums}+ .sec p{margin:0 0 10px;font-size:14px;color:#c2d0de}+ .chips{display:flex;flex-wrap:wrap;gap:5px}+ .chip{background:rgba(255,255,255,.05);border:1px solid var(--line);border-radius:6px;+ padding:3px 8px;font:600 11.5px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;color:#dce6f2}+ .chip i{font-style:normal;color:var(--dim2);font-weight:400}++ table{width:100%;border-collapse:collapse;font-size:13.5px}+ th{text-align:left;font-size:11px;letter-spacing:.09em;text-transform:uppercase;+ color:var(--dim2);font-weight:700;padding:0 10px 8px 0;border-bottom:1px solid var(--line)}+ td{padding:8px 10px 8px 0;border-bottom:1px solid rgba(255,255,255,.05);vertical-align:top}+ td.num{font-variant-numeric:tabular-nums;color:var(--dim);width:34px}+ td.refs{color:var(--dim2);font-size:11.5px}+ .tag{display:inline-block;font:700 9.5px/1 inherit;letter-spacing:.06em;padding:3px 5px;+ border-radius:4px;vertical-align:1px;margin-left:6px}+ .tag.dnp{background:rgba(239,68,68,.16);color:#f8a6a6;border:1px solid rgba(239,68,68,.3)}++ figure{margin:0 0 18px}+ figure img{width:100%;display:block;background:#0b1220;border:1px solid var(--line);border-radius:10px}+ figcaption{font-size:12px;color:var(--dim2);margin-top:8px}+ .legend{display:flex;flex-wrap:wrap;gap:12px;margin:12px 0 0;font-size:12px;color:#c2d0de}+ .legend span{display:flex;align-items:center}++ .note{background:rgba(245,165,36,.08);border:1px solid rgba(245,165,36,.26);+ border-radius:10px;padding:13px 15px;font-size:13.5px;color:#e5d3b4;margin:0 0 14px}+ .note b{color:#f5d9a8}+ ul{margin:0 0 15px;padding-left:20px} li{margin-bottom:6px}+ footer{margin:64px 0 60px;padding-top:20px;border-top:1px solid var(--line);+ font-size:12px;color:var(--dim2)}+ @media (max-width:720px){ .grid2{grid-template-columns:1fr} .title h1{font-size:29px} }+</style>++<!-- ============================ VIEWER (the centrepiece) ============================ -->+<div class="viewer-card">+ <iframe id="board-viewer" title="Feeder Reader interactive board viewer"+ sandbox="allow-scripts" data-viewer-src="<meta charset="utf-8">+<title>Feeder Reader — interactive board viewer</title>+<meta name="viewport" content="width=device-width, initial-scale=1">+<style>+ :root{ --bg:#2a3340; --ink:#e8eef5; --dim:#9fb0c3; --line:rgba(255,255,255,.14); }+ *{box-sizing:border-box}+ html,body{margin:0;height:100%;background:var(--bg);color:var(--ink);+ font-family:ui-sans-serif,-apple-system,"Segoe UI",Inter,system-ui,sans-serif;overflow:hidden}+ #app{position:absolute;inset:0}+ canvas{outline:none;display:block;width:100%;height:100%;touch-action:none}+ #status{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);color:var(--dim);+ font-size:13px;letter-spacing:.02em;text-align:center;pointer-events:none}+ .panel{position:absolute;background:rgba(16,22,31,.82);backdrop-filter:blur(9px);+ border:1px solid var(--line);border-radius:12px}++ /* mode switch */+ #modes{top:14px;left:50%;transform:translateX(-50%);display:flex;gap:2px;padding:3px;z-index:6}+ #modes button{appearance:none;background:transparent;border:0;color:var(--dim);cursor:pointer;+ font:600 12px/1 inherit;padding:8px 14px;border-radius:9px;letter-spacing:.02em}+ #modes button.on{background:rgba(255,255,255,.13);color:#fff}++ /* caption card */+ #caption{left:20px;bottom:64px;max-width:min(430px,42vw);padding:14px 16px;z-index:5;+ opacity:0;transition:opacity .45s ease;pointer-events:none}+ #caption.show{opacity:1}+ #caption .eyebrow{font:700 10px/1 inherit;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin-bottom:7px}+ #caption .head{font:650 17px/1.3 inherit;margin-bottom:9px;color:#fff}+ #caption .rows{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;font-size:12px;margin-bottom:9px}+ #caption .rows b{color:var(--dim);font-weight:500;white-space:nowrap}+ #caption .rows span{color:#dce6f2;font-variant-numeric:tabular-nums}+ #caption .note{font-size:12px;line-height:1.5;color:#b9c7d7}+ #caption .chip{display:inline-block;width:8px;height:8px;border-radius:2px;margin-right:7px;vertical-align:1px}+++ /* legend + layers */+ #legend{left:20px;top:64px;padding:11px 13px;z-index:5;font-size:11.5px}+ #legend h3{margin:0 0 8px;font:700 10px/1 inherit;letter-spacing:.14em;text-transform:uppercase;color:var(--dim)}+ #legend .row{display:flex;align-items:center;gap:8px;padding:3px 0;cursor:pointer;color:#c6d3e1}+ #legend .row:hover{color:#fff}+ #legend .row.off{opacity:.38}+ #legend .sw{width:9px;height:9px;border-radius:2px;flex:none}+ #legend .n{margin-left:auto;color:#7f92a6;font-variant-numeric:tabular-nums;font-size:10.5px}++ /* buttons + scrubber */+ .btn-row{position:absolute;right:20px;bottom:20px;display:flex;gap:7px;z-index:6}+ .btn{appearance:none;background:rgba(16,22,31,.82);border:1px solid var(--line);color:var(--dim);+ border-radius:9px;padding:8px 12px;font:600 11.5px/1 inherit;cursor:pointer;backdrop-filter:blur(9px)}+ .btn:hover{color:#fff}+ .btn.on{background:rgba(255,255,255,.15);color:#fff}+ #topbar{position:absolute;left:0;right:0;top:0;height:3px;background:rgba(255,255,255,.08);cursor:pointer;z-index:7}+ #topbar-fill{height:100%;width:0;background:linear-gradient(90deg,#3b82f6,#22c55e)}+ /* ticks only — the chapter NAME already leads the caption card, so labels here just+ collided with the mode switch */+ #marks{position:absolute;left:0;right:0;top:3px;height:6px;z-index:7;pointer-events:none}+ #marks i{position:absolute;top:0;width:1px;height:6px;background:rgba(255,255,255,.32)}+ #tip{position:absolute;pointer-events:none;z-index:8;padding:8px 10px;max-width:230px;+ background:rgba(10,15,22,.94);border:1px solid var(--line);border-radius:9px;+ font-size:11.5px;line-height:1.45;color:#dce6f2;opacity:0;transition:opacity .12s}+ #tip .t{font-weight:650;color:#fff;margin-bottom:3px}+ #tip .m{color:#8fa2b6;font-size:10.5px;font-variant-numeric:tabular-nums}+ @media (max-width:760px){ #legend{display:none!important} #caption{max-width:calc(100vw - 40px);bottom:58px} }+ /* embedded in a short frame the legend (top-left) and the caption (bottom-left) collide —+ the caption is the one that carries the narrative, so the legend yields */+ @media (max-height:640px){ #legend{display:none} }+</style>++<div id="app"></div>+<div id="status">loading board…</div>+<div id="topbar"><div id="topbar-fill"></div></div>+<div id="marks"></div>+<div class="panel" id="modes"></div>+<div class="panel" id="legend"><h3>Sections</h3><div id="legend-list"></div></div>+<div class="panel" id="caption"><div class="eyebrow"></div><div class="head"></div><div class="rows"></div><div class="note"></div></div>+<div class="btn-row">+ <button class="btn" id="btn-power" style="display:none">⏻ Power</button>+ <button class="btn on" id="btn-play">⏸ Pause</button>+ <button class="btn" id="btn-replay">↺ Replay</button>+ <button class="btn on" id="btn-ground">Ground</button>+ <button class="btn on" id="btn-labels">Labels</button>+</div>+<div id="tip"></div>++<script>+(async () => {+const statusEl = document.getElementById('status');++// Wiki blobs render SANDBOXED (no allow-same-origin) → localStorage/sessionStorage throw on+// ACCESS, which aborts the bundle's SceneBuilder (no lights/IBL/shadows). Shim before load.+for (const k of ['localStorage','sessionStorage']) {+ try { window[k] && window[k].getItem('x'); }+ catch (e) { try { Object.defineProperty(window,k,{configurable:true,value:{getItem:()=>null,setItem:()=>{},removeItem:()=>{},clear:()=>{},key:()=>null,length:0}}); } catch(_){} }+}+// Blob storage serves .js as application/octet-stream + nosniff → <script src> is refused.+// Fetch as text, inject inline.+if (!window.Adom3DViewer) {+ try {+ const code = await (await fetch('https://wiki.adom.inc/blob/component/drone-motor-carrier/viewer/adom-3d-viewer.min.js?cap=__WIKICAP__')).text();+ const s = document.createElement('script'); s.textContent = code; document.head.appendChild(s);+ } catch (e) { statusEl.textContent = 'viewer bundle fetch failed: ' + e.message; return; }+}+const V = window.Adom3DViewer;+if (!V) { statusEl.textContent = 'Adom3DViewer bundle failed to load'; return; }+const B = V.BABYLON;++try {+// ---------------------------------------------------------------- config + index+const CFG = await (await fetch('https://wiki.adom.inc/blob/component/drone-motor-carrier/viewer/viewer-config.json?cap=__WIKICAP__',{cache:'no-store'})).json();+const IDX = await (await fetch('https://wiki.adom.inc/blob/component/drone-motor-carrier/viewer/board-index.json?cap=__WIKICAP__',{cache:'no-store'})).json();+const BLOCKS = CFG.blocks;+const BCOL = Object.fromEntries(BLOCKS.map(b => [b.id, b.color]));+const BLAB = Object.fromEntries(BLOCKS.map(b => [b.id, b.label]));++// ---------------------------------------------------------------- scene+const viewer = V.init(document.getElementById('app'), { zUp:true, showViewCube:true, showGround:true });+await viewer.loadModel('https://wiki.adom.inc/blob/component/drone-motor-carrier/viewer/board-detailed.glb?cap=__WIKICAP__');+const scene = viewer.getScene();+scene.animationGroups.slice().forEach(g => g.dispose()); // we drive all motion++// Match Colby's current native viewer: flat #2a3340, no skybox mesh, soft radial ground.+const bgBot = new B.Color3(0.165,0.200,0.251);+const Color4 = scene.clearColor && scene.clearColor.constructor;+let _fade=null;+const groundFadeTex = () => {+ if (_fade) return _fade;+ const t=new B.DynamicTexture('groundFadeTex',256,scene,true), c=t.getContext();+ const g=c.createRadialGradient(128,128,6,128,128,128);+ g.addColorStop(0,'rgba(255,255,255,1)'); g.addColorStop(.55,'rgba(255,255,255,.6)'); g.addColorStop(1,'rgba(255,255,255,0)');+ c.fillStyle=g; c.fillRect(0,0,256,256); t.update(); t.hasAlpha=true; _fade=t; return t;+};+const matchColby = () => {+ if (Color4) scene.clearColor = new Color4(bgBot.r,bgBot.g,bgBot.b,1);+ scene.environmentIntensity = 1.0;+ scene.meshes.filter(m=>/^Background/.test(m.name)).forEach(m=>m.setEnabled(false));+ const gnd = scene.getMeshByName('shadowGround');+ if (gnd && gnd.material) {+ const m = gnd.material;+ if (!(m.baseTexture && m.baseTexture.name==='groundFadeTex')) {+ if ('baseTexture' in m) m.baseTexture = groundFadeTex();+ m.alpha=0.2; m.transparencyMode=2; m.backFaceCulling=false;+ }+ gnd.receiveShadows = true;+ }+};+matchColby();++// ---------------------------------------------------------------- group meshes+// Every mesh is named "<block>|<ref>|<i>" (transforms baked at export, so a plain+// position offset is a rigid move — see bake2.py).+const parts = new Map(); // ref -> {ref, blk, meshes[], home:Vector3, centre}+const byBlock = new Map(); // blk -> [part]+for (const m of scene.meshes) {+ const nm = (m.name||'').split('_primitive')[0];+ if (nm.indexOf('|') < 0) continue;+ const [blk, ref] = nm.split('|');+ m.rotationQuaternion = null; // glTF quaternion would override Euler+ let p = parts.get(ref);+ if (!p) { p = { ref, blk, meshes:[], home:m.position.clone() }; parts.set(ref, p); (byBlock.get(blk)||byBlock.set(blk,[]).get(blk)).push(p); }+ p.meshes.push(m);+}+for (const p of parts.values()) {+ let lo=null, hi=null;+ for (const m of p.meshes) {+ m.computeWorldMatrix(true);+ const 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();+ }+ p.lo=lo; p.hi=hi; p.centre=lo.add(hi).scale(0.5); p.size=hi.subtract(lo);+}+const blockBox = blk => {+ let lo=null,hi=null;+ for (const p of (byBlock.get(blk)||[])) {+ lo = lo ? B.Vector3.Minimize(lo,p.lo) : p.lo.clone();+ hi = hi ? B.Vector3.Maximize(hi,p.hi) : p.hi.clone();+ }+ // fold in footprints that have no 3D body (feeder pads, switches, jumpers…)+ for (const f of Object.values(IDX.fps)) {+ if (f.blk!==blk || f.has3d) continue;+ const v=new B.Vector3(f.x,f.y,0.0015);+ lo = lo ? B.Vector3.Minimize(lo,v) : v.clone(); hi = hi ? B.Vector3.Maximize(hi,v) : v.clone();+ }+ return lo ? {lo,hi,c:lo.add(hi).scale(.5),s:hi.subtract(lo)} : null;+};+const SUB = ['PCB','COPPER','MASK','SILK'].map(r=>parts.get(r)).filter(Boolean);+const COMPONENTS = [...parts.values()].filter(p => p.blk!=='substrate');+statusEl.textContent = '';++// ---------------------------------------------------------------- camera+const cam = scene.activeCamera;+const home = { a:-Math.PI/2 - 0.55, b:0.92, r:0.165, t:new B.Vector3(0,0,0.004) };+const setCam = (o,ease) => {+ const tgt = { alpha:o.a, beta:o.b, radius:o.r, target:o.t };+ if (!ease) { cam.alpha=tgt.alpha; cam.beta=tgt.beta; cam.radius=tgt.radius; cam.target.copyFrom(tgt.target); return; }+ camGoal = tgt;+};+let camGoal = null, userHold = 0;+setCam(home,false);+cam.lowerRadiusLimit = 0.03; cam.upperRadiusLimit = 1.2; cam.wheelDeltaPercentage = 0.02;+// Depth-precision fix: the bundle's camera sets minZ = radius/1000, which makes one 24-bit+// depth step ~10 um at board-viewing distance — exactly the gap between mask/silk/copper+// sheets, so coplanar-ish layers shimmer. radius/100 gives ~1 um steps and the near plane is+// still only 0.3 mm out at the closest zoom. (Bake-side the layers are also nudged apart.)+scene.onBeforeRenderObservable.add(() => { cam.minZ = Math.max(cam.radius / 100, 2e-4); });+cam.panningSensibility = 6000;+// The camera is "yours for 5 s": any drag/wheel pauses the scripted chase (assembly only),+// which then eases back — same convention as the single-component Timeline viewer. In static+// mode there is no scripted camera at all, so input is simply never fought.+['pointerdown','wheel'].forEach(ev => scene.getEngine().getRenderingCanvas()+ .addEventListener(ev, () => { userHold = 5; }, {passive:true}));++// ---------------------------------------------------------------- helpers+const emissive = (col, alpha) => {+ const m = new B.StandardMaterial('em'+Math.random(), scene);+ m.emissiveColor = col; m.diffuseColor = new B.Color3(0,0,0); m.specularColor = new B.Color3(0,0,0);+ m.disableLighting = true; m.alpha = alpha==null?1:alpha; if (alpha!=null) m.alphaMode = 1;+ return m;+};+const hex2c3 = h => new B.Color3(parseInt(h.slice(1,3),16)/255, parseInt(h.slice(3,5),16)/255, parseInt(h.slice(5,7),16)/255);++// Section outline boxes (wireframe) — one per block, hidden by default.+const outlines = new Map();+for (const b of BLOCKS) {+ const bb = blockBox(b.id); if (!bb) continue;+ const pad = 0.0018;+ const box = B.MeshBuilder.CreateBox('outline_'+b.id, {+ width: Math.max(bb.s.x,0.002)+pad*2, height: Math.max(bb.s.y,0.002)+pad*2, depth: Math.max(bb.s.z,0.002)+pad*2 }, scene);+ box.position.copyFrom(bb.c);+ const mt = emissive(hex2c3(b.color)); mt.wireframe = true; mt.alpha = .45; mt.alphaMode = 1;+ box.material = mt; box.isPickable = false; box.setEnabled(false);+ outlines.set(b.id, box);+}++// Static-mode visual function (AUTHOR-SPECIFIED, see viewer-config): e.g. the rail LEDs+// lighting when "Power" is toggled. Purely illustrative of the powered state.+const FN = (CFG.modes.static && CFG.modes.static.functions && CFG.modes.static.functions.power_leds) || null;+const glows = new Map();+let powerOn = !!(FN && FN.default);+for (const ref of (FN ? FN.refs : [])) {+ const f = IDX.fps[ref]; if (!f) continue;+ const d = B.MeshBuilder.CreateBox('glow_'+ref, {width:.0028,height:.0028,depth:.0009}, scene);+ d.position.set(f.x, f.y, 0.0022); d.material = emissive(hex2c3(FN.color), .95); d.isPickable=false; d.setEnabled(false);+ glows.set(ref, d);+}++// DOM labels for sections+const labelLayer = document.createElement('div');+labelLayer.style.cssText = 'position:absolute;inset:0;pointer-events:none;z-index:4';+document.body.appendChild(labelLayer);+const labels = new Map();+for (const b of BLOCKS) {+ const bb = blockBox(b.id); if (!bb) continue;+ const el = document.createElement('div');+ el.style.cssText = 'position:absolute;transform:translate(-50%,-50%);font:650 10.5px/1 ui-sans-serif,system-ui;'++ 'letter-spacing:.03em;padding:3px 7px;border-radius:6px;white-space:nowrap;opacity:0;transition:opacity .3s;'++ 'background:rgba(10,15,22,.82);border:1px solid '+b.color+'66;color:'+b.color;+ el.textContent = b.label;+ labelLayer.appendChild(el); labels.set(b.id, {el, pos:bb.c.clone()});+}+let labelsOn = true;+// Only ONE section label is drawn at a time — the block the current chapter is about.+// (Blocks overlap heavily in plan view: feeder-io alone spans the whole board, so every+// centroid lands near the middle and all eight labels stack on top of each other.)+let focusBlk = null, hoverBlk = null;++// ---------------------------------------------------------------- visibility control+const setPartVis = (p, v) => { for (const m of p.meshes) m.visibility = v; };+const showAllComponents = v => COMPONENTS.forEach(p => setPartVis(p, v));+const layerOn = { PCB:true, COPPER:true, SILK:true, MASK:true };+const blockOn = Object.fromEntries(BLOCKS.map(b=>[b.id,true]));+const applyLayers = () => {+ for (const p of SUB) { const on = layerOn[p.ref]!==false; p.meshes.forEach(m=>m.setEnabled(on)); }+ for (const p of COMPONENTS) { const on = blockOn[p.blk]!==false; p.meshes.forEach(m=>m.setEnabled(on)); }+};++// ---------------------------------------------------------------- legend+const legendList = document.getElementById('legend-list');+const counts = {};+for (const f of Object.values(IDX.fps)) counts[f.blk] = (counts[f.blk]||0)+1;+for (const b of BLOCKS) {+ const row = document.createElement('div'); row.className='row';+ row.innerHTML = `<span class="sw" style="background:${b.color}"></span><span>${b.label}</span><span class="n">${counts[b.id]||0}</span>`;+ row.onclick = () => { blockOn[b.id] = !blockOn[b.id]; row.classList.toggle('off', !blockOn[b.id]); applyLayers(); };+ row.onmouseenter = () => { hoverBlk = b.id; outlines.get(b.id)?.setEnabled(true); };+ row.onmouseleave = () => { hoverBlk = null; if (focusBlk!==b.id) outlines.get(b.id)?.setEnabled(false); };+ legendList.appendChild(row);+}+const subRow = document.createElement('div'); subRow.className='row';+subRow.innerHTML = `<span class="sw" style="background:#b78227"></span><span>Copper &amp; silkscreen</span><span class="n">4</span>`;+subRow.onclick = () => { const on = !(layerOn.COPPER && layerOn.SILK); layerOn.COPPER=layerOn.SILK=on; subRow.classList.toggle('off',!on); applyLayers(); };+legendList.appendChild(subRow);++// ---------------------------------------------------------------- caption+const capEl = document.getElementById('caption');+const showCap = (c) => {+ if (!c) { capEl.classList.remove('show'); return; }+ const dot = c.block ? `<span class="chip" style="background:${BCOL[c.block]}"></span>` : '';+ capEl.querySelector('.eyebrow').innerHTML = dot + (c.eyebrow||'');+ capEl.querySelector('.head').textContent = c.head||'';+ capEl.querySelector('.rows').innerHTML = (c.rows||[]).map(r=>`<b>${r[0]}</b><span>${r[1]}</span>`).join('');+ capEl.querySelector('.note').textContent = c.note||'';+ capEl.classList.add('show');+};+let lastCapKey = null;+const showCapOnce = (key,c) => { if (key!==lastCapKey) { lastCapKey=key; showCap(c); } };++// ---------------------------------------------------------------- modes+const A = CFG.modes.assembly, S = CFG.modes.static;+// Assembly schedule — the GROUP is the unit: all of a grouping's parts fall together+// (tiny in-group stagger for texture) while that grouping's caption explains it.+const T = A.timing;+const sched = []; // {p, t, blk} — individual part drops+const groups = []; // {id, t0, t1} — caption/camera windows+let tA = (A.intro && A.intro.d) || 6;+for (const bid of A.order) {+ const ps = (byBlock.get(bid)||[]).slice().sort((p,q)=>p.centre.x-q.centre.x);+ ps.forEach((p,i)=> sched.push({p, t: tA + i*T.stagger_s, blk:bid}));+ const dwell = (A.captions[bid] && A.captions[bid].d) || T.dwell_s;+ groups.push({ id: bid, t0: tA, t1: tA + dwell });+ tA += dwell;+}+const A_LEN = tA + T.rest_s;+const LEN = { assembly: A_LEN, static: 1 }; // static has NO timeline++let mode = 'assembly', t = 0, playing = true;++const modesEl = document.getElementById('modes');+for (const k of ['assembly','static']) {+ const b = document.createElement('button'); b.textContent = CFG.modes[k].label; b.dataset.k = k;+ b.onclick = () => setMode(k);+ modesEl.appendChild(b);+}+const marksEl = document.getElementById('marks');+function drawMarks() {+ marksEl.innerHTML = '';+ if (mode!=='assembly') return;+ for (const g of groups) marksEl.insertAdjacentHTML('beforeend', `<i style="left:${100*g.t0/A_LEN}%"></i>`);+}+function setMode(k) {+ mode = k; t = 0; lastCapKey = null; camGoal = null; userHold = 0; focusBlk = null;+ [...modesEl.children].forEach(b => b.classList.toggle('on', b.dataset.k===k));+ outlines.forEach(o => o.setEnabled(false));+ COMPONENTS.forEach(p => { setPartVis(p, k==='assembly' ? 0 : 1);+ p.meshes.forEach(m => m.position.copyFrom(p.home)); });+ // static: no timeline, no scripted camera — the camera is entirely the user's+ const isA = k==='assembly';+ document.getElementById('topbar').style.display = isA ? 'block' : 'none';+ document.getElementById('btn-play').style.display = isA ? '' : 'none';+ document.getElementById('btn-replay').style.display = isA ? '' : 'none';+ document.getElementById('btn-power').style.display = (!isA && FN) ? '' : 'none';+ glows.forEach(g => g.setEnabled(!isA && powerOn));+ setCam(home,false);+ drawMarks(); applyLayers(); showCap(null);+ if (!isA && S.blurb) showCap({ eyebrow:'Static', head:'Inspect the board', note:S.blurb });+}++// ---------------------------------------------------------------- render loop+// B exposes only MeshBuilder/StandardMaterial/Vector3/Color3/DynamicTexture — Matrix is NOT+// on it (§5b). Harvest it from a live instance instead of reaching for B.Matrix.+const Matrix = scene.getTransformMatrix().constructor;+const IDM = Matrix.Identity();+const worldToScreen = (v) => {+ try {+ const e = scene.getEngine();+ return B.Vector3.Project(v, IDM, scene.getTransformMatrix(),+ cam.viewport.toGlobal(e.getRenderWidth(), e.getRenderHeight()));+ } catch (_) { return null; }+};+let last = performance.now();+scene.onBeforeRenderObservable.add(() => {+ const now = performance.now(); const dt = Math.min((now-last)/1000, .05); last = now;+ if (playing) t = (t + dt) % LEN[mode];+ if (userHold > 0) userHold -= dt;++ // camera easing — assembly only; static never steers+ if (mode==='assembly' && camGoal && userHold <= 0 &&+ Number.isFinite(camGoal.alpha) && Number.isFinite(camGoal.beta) && Number.isFinite(camGoal.radius)) {+ const k = 1 - Math.pow(0.0016, dt);+ cam.alpha += (camGoal.alpha - cam.alpha) * k;+ cam.beta += (camGoal.beta - cam.beta) * k;+ cam.radius += (camGoal.radius - cam.radius) * k;+ cam.target.x += (camGoal.target.x - cam.target.x) * k;+ cam.target.y += (camGoal.target.y - cam.target.y) * k;+ cam.target.z += (camGoal.target.z - cam.target.z) * k;+ }+ if (mode==='assembly' && userHold <= 0) cam.alpha += dt * 0.005; // gentle drift++ if (mode === 'assembly') {+ // drops+ for (const d of sched) {+ const u = (t - d.t) / T.fall_s;+ if (u < 0) { setPartVis(d.p, 0); continue; }+ const k = Math.min(u, 1), e = 1 - Math.pow(1-k, 3); // easeOutCubic+ setPartVis(d.p, Math.min(1, k*2.2));+ const dz = (1-e) * T.drop_height_mm/1000;+ for (const m of d.p.meshes) { m.position.copyFrom(d.p.home); m.position.z += dz; }+ }+ // caption + focus + camera per GROUP window (intro before, rest after)+ const g = groups.find(g => t>=g.t0 && t<g.t1);+ if (t < groups[0].t0) {+ showCapOnce('a-intro', A.intro); focusBlk = null;+ outlines.forEach((o,id)=>o.setEnabled(id===hoverBlk));+ camGoal = { alpha:home.a, beta:home.b, radius:home.r, target:home.t };+ } else if (g) {+ const c = A.captions[g.id] || {};+ showCapOnce('a'+g.id, { ...c, block:g.id });+ focusBlk = g.id;+ outlines.forEach((o,id)=>o.setEnabled(id===g.id || id===hoverBlk));+ const bb = blockBox(g.id);+ if (bb) camGoal = { alpha: home.a + 0.25*Math.sin(t*0.12), beta: 0.86,+ radius: Math.max(0.105, Math.min(0.24, bb.s.length()*2.6)),+ target: bb.c };+ } else { // rest: whole board seated+ showCapOnce('a-rest', null); focusBlk = null;+ outlines.forEach((o,id)=>o.setEnabled(id===hoverBlk));+ camGoal = { alpha:home.a, beta:home.b, radius:home.r, target:home.t };+ }+ }+ // static mode: nothing per-frame — no timeline, no captions changing, no camera goals.+ // Hover tooltips, legend isolate, labels and the Power toggle all work event-driven.++ // scrub + labels+ if (mode==='assembly') document.getElementById('topbar-fill').style.width = (100*t/A_LEN) + '%';+ for (const [id, L] of labels) {+ const vis = labelsOn && blockOn[id]!==false && (id===focusBlk || id===hoverBlk);+ if (!vis) { L.el.style.opacity = 0; continue; }+ const p = worldToScreen(L.pos);+ if (!p || p.z > 1 || p.z < 0) { L.el.style.opacity = 0; continue; }+ L.el.style.left = p.x+'px'; L.el.style.top = p.y+'px'; L.el.style.opacity = 0.92;+ }+});++// ---------------------------------------------------------------- hover tooltip+const tipEl = document.getElementById('tip');+scene.getEngine().getRenderingCanvas().addEventListener('pointermove', ev => {+ const pick = scene.pick(scene.pointerX, scene.pointerY, m => (m.name||'').indexOf('|')>=0);+ if (!pick || !pick.hit) { tipEl.style.opacity = 0; return; }+ const nm = pick.pickedMesh.name.split('_primitive')[0];+ const [blk, ref] = nm.split('|');+ const f = IDX.fps[ref];+ const p = parts.get(ref);+ const mm = v => (v*1000).toFixed(2);+ if (blk === 'substrate') {+ tipEl.innerHTML = `<div class="t">Bare board</div><div class="m">130.00 × 50.00 mm · 1.6 mm · 4 layers<br>`++ `${{PCB:'FR-4 substrate',COPPER:'copper (all 4 layers)',MASK:'soldermask',SILK:'silkscreen'}[ref]||ref}</div>`;+ } else if (f) {+ tipEl.innerHTML = `<div class="t">${ref} · ${f.val}</div>`++ `<div class="m">${f.fp}<br>${BLAB[blk]||blk} · ${f.layer==='F.Cu'?'top':'bottom'} · ${f.rot?f.rot+'°':'0°'}`++ (p? `<br>extent ${mm(p.size.x)} × ${mm(p.size.y)} × ${mm(p.size.z)} mm` : '')+`</div>`;+ } else return;+ tipEl.style.opacity = 1;+ tipEl.style.left = Math.min(ev.clientX+14, innerWidth-244)+'px';+ tipEl.style.top = Math.min(ev.clientY+14, innerHeight-90)+'px';+});+scene.getEngine().getRenderingCanvas().addEventListener('pointerleave', ()=> tipEl.style.opacity=0);++// ---------------------------------------------------------------- controls+document.getElementById('btn-play').onclick = e => { playing=!playing; e.target.classList.toggle('on',playing); e.target.textContent = playing?'⏸ Pause':'▶ Play'; };+document.getElementById('btn-replay').onclick = () => { t=0; lastCapKey=null; if(mode==='assembly') COMPONENTS.forEach(p=>setPartVis(p,0)); };+document.getElementById('btn-ground').onclick = e => {+ const g = scene.getMeshByName('shadowGround'); if (!g) return;+ const on = !g.isEnabled(); g.setEnabled(on); e.target.classList.toggle('on',on); matchColby();+};+document.getElementById('btn-labels').onclick = e => { labelsOn=!labelsOn; e.target.classList.toggle('on',labelsOn); };+document.getElementById('btn-power').onclick = e => {+ powerOn = !powerOn; e.target.classList.toggle('on', powerOn);+ glows.forEach(g => g.setEnabled(mode!=='assembly' && powerOn));+};+document.getElementById('topbar').onclick = ev => { if (mode==='assembly') { t = A_LEN * (ev.clientX/innerWidth); lastCapKey=null; } };++// debug handle (harmless; also handy from the console)+window.__dbg = { parts, COMPONENTS, byBlock, sched, groups, outlines, labels, cam,+ get t(){return t}, set t(v){t=v; lastCapKey=null;},+ get mode(){return mode}, get focus(){return focusBlk},+ setMode, A_LEN, LEN,+ // jump the camera straight to its goal — the eased chase needs many frames,+ // and headless swiftshader renders this board at well under 1 fps.+ snap(){ if (camGoal) { cam.alpha=camGoal.alpha; cam.beta=camGoal.beta;+ cam.radius=camGoal.radius; cam.target.copyFrom(camGoal.target); } },+ pause(){ playing=false; }, play(){ playing=true; } };++setMode('assembly');+addEventListener('resize', () => scene.getEngine().resize());++} catch (err) {+ statusEl.textContent = 'viewer error: ' + (err && err.message ? err.message : err);+ console.error(err);+}+})();+</script>+"></iframe>+<script>+/* wiki#73 caps: the injected bootstrap patches THIS document, but a sandboxed srcdoc child is a+ separate document with its own unpatched fetch and no __wikiCap. The bootstrap is appended at+ the END of the served document, so wait for it before substituting the cap into the template. */+(function(){+ var tries=0;+ function go(){+ var f=document.getElementById('board-viewer'); if(!f) return;+ var tpl=f.getAttribute('data-viewer-src'); if(!tpl) return;+ var cap=window.__wikiCap||'';+ if(!cap && tries++ < 60) return setTimeout(go,50);+ f.srcdoc=tpl.replace(/__WIKICAP__/g,cap);+ f.removeAttribute('data-viewer-src');+ /* The bootstrap rewrites img/source/video/audio srcs, but NOT iframe srcs — an iframe load is+ a navigation, so a self-contained app embed on a private page needs the cap appended here. */+ Array.prototype.forEach.call(document.querySelectorAll('iframe[data-cap-src]'),function(el){+ var u=el.getAttribute('data-cap-src');+ /* Mirror the wiki bootstrap's own needs()/add() semantics exactly, rather than inventing+ our own: (1) never touch data:/blob:/#/javascript:, (2) SAME-HOST only - compared against+ location.host, not a hardcoded domain, so it holds on any deployment, (3) emit a RELATIVE+ url so the token cannot ride a redirect to another origin. The cap is a bearer credential+ for this page's files; handing it to a third-party origin would be a real leak. */+ var out=u;+ try {+ if (!/^(data:|blob:|#|javascript:)/i.test(u)) {+ var a=new URL(u, location.href);+ if (cap && a.host===location.host && !a.searchParams.has('cap')) {+ a.searchParams.set('cap', cap);+ out = a.pathname + a.search + a.hash;+ }+ }+ } catch(e) { out=u; }+ el.src = out;+ el.removeAttribute('data-cap-src');+ });+ }+ if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',go); else go();+})();+</script>+ <div class="viewer-hint">Assembly — each grouping drops in while its caption explains it · Static — the camera is yours — drag to orbit, scroll to zoom, hover any part</div>+</div>++<div class="wrap">+ <div class="title">+ <h1>Drone Motor Carrier</h1>+ <div class="sub">drone-motor-carrier v1.1 · passive baseplate for the single-motor test cell</div>+ <div class="facts">+ <span><b>104 × 104</b> mm</span><span><b>4</b>-layer, 1.6 mm</span>+ <span><b>96 × 96</b> mm datum</span><span><b>hosts</b> RP2350A/ADS1220 + PiProbe</span>+ </div>+ </div>++ <h2>What it does</h2>+ <p>A passive carrier baseplate. It mounts the BLDC motor, passes the motor's three phases straight+ through from the ESC to the motor leads, routes two load-cell channels, and <b>nests the RP2350A ++ ADS1220 acquisition molecule and a PiProbe</b> over its machine-pin datum. It carries no active+ silicon of its own beyond a single arbitration resistor — its job is mechanical location and clean+ analogue routing.</p>++ <h2>The board, section by section</h2>++ <h3>Molecule nests</h3>+ <p>Two carrier slots (16 × 32 mm): <b>N2</b> takes the <b>RP2350A / ADS1220</b> acquisition molecule,+ <b>N1</b> takes a <b>PiProbe</b> debug molecule. Each seats over the carrier's machine contacts as an+ already-populated sub-assembly. R1's placement (below) lets N1 and N2 sit without conflict.</p>++ <h3>BLDC phases</h3>+ <p>The three motor phases — <b>A, B, C</b> (J1 / J2 / J3) — pass from the ESC through the carrier to the+ motor leads. These are solder-pad terminals; they carry no 3D body in the STEP export, so they appear+ in the section legend but not as dropped parts in the viewer.</p>++ <h3>Load-cell headers</h3>+ <p>Two <b>JST-PH 5-pin</b> connectors (J4 / J5) — Load Cell Conn. 1 and 2. The analogue pairs run at a+ <b>constant 0.45 mm pitch on B.Cu with no vias</b>, referenced to the In2 GND plane. The machine+ contacts sit upstream of the molecule's RC filter, so what the carrier picks up arrives unfiltered at+ the bridge's source — which is why the routing is disciplined.</p>++ <h3>UART arbitration</h3>+ <p>A single resistor keeps the debug bus clean: <b>R1 = 300 R (0603, 1%)</b>. The PiProbe reaches the+ <span class="mono">/UART0_RX</span> net only through R1, while the flight controller and the molecule's+ RX connect directly — so a populated probe (whose debugprobe drives its TX from power-up) never+ contends the bus, and R1 dissipates only ~20 mW of its 100 mW rating under contention.</p>++ <h3>Machine-pin datum</h3>+ <p>The <b>96 × 96 mm machine-pin datum</b> — 25 contacts (MC1–MC25) plus four corner large pins+ (MP1–MP4) — locates the carrier in the cell and passes signals to the nested molecule.</p>++ <h3>Base-plate mounting</h3>+ <p>Four mounting holes (H1–H4) fix the carrier to the base plate.</p>++ <h2>Layout</h2>+ <p>Interactive PCB — click any pad, trace or zone to highlight its whole net; hover for pad and live part data.</p>+ <div class="embed-card">+ <iframe class="app-embed" title="drone-motor-carrier — interactive PCB layout" loading="lazy"+ data-cap-src="https://wiki.adom.inc/blob/component/drone-motor-carrier/viewer/layout.html"+ style="width:100%;height:720px;border:0;display:block;background:#1b232d"></iframe>+ </div>++ <h2>Schematic</h2>+ <p>Interactive schematic — hover a symbol for its KiCad highlight plus live MPN / stock / price / LCSC.</p>+ <div class="embed-card">+ <iframe class="app-embed" title="drone-motor-carrier — interactive schematic" loading="lazy"+ data-cap-src="https://wiki.adom.inc/blob/component/drone-motor-carrier/viewer/sch-root.html"+ style="width:100%;height:720px;border:0;display:block;background:#fff"></iframe>+ </div>++ <h2>How this page was built</h2>+ <ul>+ <li><b>Generated from the design files.</b> The model, sections and part list derive from+ <span class="mono">drone-motor-carrier.kicad_pcb</span> / the schematic / the STEP export. Coverage:+ 404 solids, 34 refs; J1/J2/J3 (the phase pads) are legitimately bodiless in the export.</li>+ <li><b>Author-asserted.</b> The section narrative and routing intent come from the designer's schematic+ notes and the project overview, not a prior page.</li>+ <li><b>Illustrative.</b> Anything shown to demonstrate behaviour is labelled as such.</li>+ </ul>++ <footer>+ Drone Motor Carrier · drone-motor-carrier v1.1 · drawn in KiCad · board page generated from+ <span class="mono">drone-motor-carrier.kicad_pcb</span>.<br>+ Interactive viewer built on the Adom 3D viewer bundle.+ </footer>+</div>++<script>+ // Self-sizing (publish skill §8k-0b): the wiki's app.js listens for `readme-height` and grows+ // #readme-frame to fit, which is what makes this one seamless full-page document.+ (function () {+ var f = document.getElementById('board-viewer');+ // the sandbox cannot read the parent viewport, so size the viewer off the screen+ f.style.height = Math.max(520, (screen.availHeight || 900) - 200) + 'px';+ function post() {+ var h = document.documentElement.scrollHeight;+ parent.postMessage({ type: 'readme-height', height: h }, '*');+ }+ addEventListener('load', post); addEventListener('resize', post);+ setTimeout(post, 400); setTimeout(post, 1600);+ })();+</script>
drone-motor-carrier.glbadded⋯ 1 unchanged line ⋯
package.jsonadded+1@@ -0,0 +1 @@+{"slug":"drone-motor-carrier","version":"1.0.0","type":"component","description":"Passive baseplate for the drone motor test cell: mounts the BLDC motor, passes its 3 phases, routes 2 load-cell channels, nests the RP2350A/ADS1220 molecule + PiProbe. 104 x 104 mm, 4-layer.","tags":["molecule","board","kicad","carrier","bldc","load-cell","adom-machine-pins","drone"],"component":{"mpn":"drone-motor-carrier","manufacturer":"Adom","package":"PCBA 104 x 104 mm, 4-layer","category":"board","model_3d_path":"drone-motor-carrier.glb"},"dependencies":{},"hero":{"type":"image","path":"screenshots/hero.png"}}
screenshots/hero.pngadded⋯ 1 unchanged line ⋯
viewer/board-index.jsonadded+1213@@ -0,0 +1,1213 @@+{+"groups": {+"substrate|PCB": {+"blk": "substrate",+"ref": "PCB",+"lo": [+-0.05200000067055226,+-0.05199999731779098,+0.0+],+"hi": [+0.052000003337860104,+0.051999999240040784,+0.0015162000199779868+],+"n": 1+},+"substrate|MASK": {+"blk": "substrate",+"ref": "MASK",+"lo": [+-0.05200000067055226,+-0.05199999731779098,+-6.499999873689376e-05+],+"hi": [+0.052000003337860104,+0.051999999240040784,+0.0015812000514566899+],+"n": 2+},+"substrate|SILK": {+"blk": "substrate",+"ref": "SILK",+"lo": [+-0.05168994848430157,+-0.0507051311135292,+0.0016062000089883805+],+"hi": [+0.051288890123367306,+0.051999999240040784,+0.0016062000089883805+],+"n": 1+},+"substrate|COPPER": {+"blk": "substrate",+"ref": "COPPER",+"lo": [+-0.051608655199408535,+-0.051307868182659144,+-3.9999998989515007e-05+],+"hi": [+0.051681088685989376,+0.0516995005607605,+0.0015561999753117561+],+"n": 1+},+"pins|MC8": {+"blk": "pins",+"ref": "MC8",+"lo": [+-0.021375001005828384,+0.019566357573028645,+-0.002148799947462976+],+"hi": [+0.0307324827830307,+0.04937480311561376,+0.004236100013107061+],+"n": 2+},+"pins|MC2": {+"blk": "pins",+"ref": "MC2",+"lo": [+0.025565002806950357,+0.03356499815778806,+-4.880002234131098e-05+],+"hi": [+0.040731115203350765,+0.05043363527581096,+0.0042511000545648854+],+"n": 3+},+"pins|MC20": {+"blk": "pins",+"ref": "MC20",+"lo": [+-0.050434998474549506,+0.01756636475399137,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.01843363381410018,+0.0016011999687179923+],+"n": 1+},+"pins|MC11": {+"blk": "pins",+"ref": "MC11",+"lo": [+0.029862482794094813,+0.013566360373049938,+-4.880002234131098e-05+],+"hi": [+0.03843363181874157,+0.05043500076932833,+0.004236100013107061+],+"n": 2+},+"pins|MC3": {+"blk": "pins",+"ref": "MC3",+"lo": [+0.02756499940948562,+0.033565005608368656,+-4.880002234131098e-05+],+"hi": [+0.03873111860081552,+0.05043363527581096,+0.004236100013107061+],+"n": 2+},+"pins|MC7": {+"blk": "pins",+"ref": "MC7",+"lo": [+-0.007375002436339859,+0.02156636162614449,+-0.002148799947462976+],+"hi": [+0.0307324827830307,+0.04937480311561376,+0.004236100013107061+],+"n": 2+},+"pins|MC17": {+"blk": "pins",+"ref": "MC17",+"lo": [+-0.050434998474549506,+0.0275663663931191,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.028433635453227912,+0.0016011999687179923+],+"n": 1+},+"pins|MP2": {+"blk": "pins",+"ref": "MP2",+"lo": [+-0.050382499413564805,+-0.05039999677613376,+-0.008748800144530833+],+"hi": [+0.03109164989506824,+0.0348000055826269,+0.010031199669204653+],+"n": 4+},+"pins|MC19": {+"blk": "pins",+"ref": "MC19",+"lo": [+-0.050434998474549506,+0.023566365679260348,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.02443363473936916,+0.0016011999687179923+],+"n": 1+},+"pins|MC23": {+"blk": "pins",+"ref": "MC23",+"lo": [+-0.050434998474549506,+0.009566367109771823,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.010433636169880633,+0.0016011999687179923+],+"n": 1+},+"pins|MC14": {+"blk": "pins",+"ref": "MC14",+"lo": [+-0.050434998474549506,+0.041566364904399966,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.04243363396450878,+0.0016011999687179923+],+"n": 1+},+"pins|MC25": {+"blk": "pins",+"ref": "MC25",+"lo": [+-0.050434998474549506,+0.0015663657984696377,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.0024336348585784484,+0.0016011999687179923+],+"n": 1+},+"pins|MC24": {+"blk": "pins",+"ref": "MC24",+"lo": [+-0.050434998474549506,+0.01156636371230707,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.012433632772415881,+0.0016011999687179923+],+"n": 1+},+"pins|MC15": {+"blk": "pins",+"ref": "MC15",+"lo": [+-0.050434998474549506,+0.03956636457657442,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.04043363363668323,+0.0016011999687179923+],+"n": 1+},+"pins|MP3": {+"blk": "pins",+"ref": "MP3",+"lo": [+0.029503315682057274,+-0.05039999677613376,+-0.008748800144530833+],+"hi": [+0.050382496492937204,+0.010800001648720345,+0.010031199669204653+],+"n": 4+},+"pins|MP1": {+"blk": "pins",+"ref": "MP1",+"lo": [+-0.050382499413564805,+0.0332000056230463,+-0.008748800144530833+],+"hi": [+0.047091652517672625,+0.050400000561028724,+0.010031199669204653+],+"n": 4+},+"pins|MC16": {+"blk": "pins",+"ref": "MC16",+"lo": [+-0.050434998474549506,+0.03156636699056253,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.032433636050671344,+0.0016011999687179923+],+"n": 1+},+"pins|MC13": {+"blk": "pins",+"ref": "MC13",+"lo": [+0.04156499797897413,+0.04956636621570215,+-4.880002234131098e-05+],+"hi": [+0.04243499796791002,+0.05043363527581096,+0.0016011999687179923+],+"n": 1+},+"pins|MC5": {+"blk": "pins",+"ref": "MC5",+"lo": [+0.0315650000651367,+0.033565005608368656,+-4.880002234131098e-05+],+"hi": [+0.03273112134262918,+0.05043363527581096,+0.004236100013107061+],+"n": 2+},+"pins|MC9": {+"blk": "pins",+"ref": "MC9",+"lo": [+0.029862482794094813,+0.011566371162887654,+-4.880002234131098e-05+],+"hi": [+0.03443499665660782,+0.05043363527581096,+0.004236100013107061+],+"n": 2+},+"pins|MP4": {+"blk": "pins",+"ref": "MP4",+"lo": [+0.04550331457937136,+0.009200001689139745,+-0.008748800144530833+],+"hi": [+0.050382496492937204,+0.050400000561028724,+0.010031199669204653+],+"n": 4+},+"loadcell|J5": {+"blk": "loadcell",+"ref": "J5",+"lo": [+-0.0028802473731339012,+-0.044000000663101654,+-0.0017988000763580203+],+"hi": [+0.009019752435386177,+-0.039450000708922725,+0.007631200020872056+],+"n": 2+},+"pins|MC12": {+"blk": "pins",+"ref": "MC12",+"lo": [+0.039565001376438896,+0.04956636621570215,+-4.880002234131098e-05+],+"hi": [+0.040435001365374784,+0.05043363527581096,+0.0016011999687179923+],+"n": 1+},+"pins|MC18": {+"blk": "pins",+"ref": "MC18",+"lo": [+-0.050434998474549506,+0.025566366065293555,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.026433635125402366,+0.0016011999687179923+],+"n": 1+},+"molecules|N1": {+"blk": "molecules",+"ref": "N1",+"lo": [+0.02829748618602751,+0.0079999976158142,+0.002556200012653135+],+"hi": [+0.04829748573899269,+0.03599999475479125,+0.005486099985167384+],+"n": 121+},+"pins|MC1": {+"blk": "pins",+"ref": "MC1",+"lo": [+0.023564998753834512,+0.03356499815778806,+-4.880002234131098e-05+],+"hi": [+0.04273111925646661,+0.05043363527581096,+0.0042511000545648854+],+"n": 3+},+"pins|MC10": {+"blk": "pins",+"ref": "MC10",+"lo": [+0.029862482794094813,+0.01556636442616581,+-4.880002234131098e-05+],+"hi": [+0.03643363521620631,+0.05043500076932833,+0.004236100013107061+],+"n": 2+},+"pins|MC6": {+"blk": "pins",+"ref": "MC6",+"lo": [+0.006625003583729264,+0.023566365679260334,+-0.002148799947462976+],+"hi": [+0.0307324827830307,+0.04937480311561376,+0.004236100013107061+],+"n": 2+},+"comms|R1": {+"blk": "comms",+"ref": "R1",+"lo": [+0.03301152429079965,+0.0194914191076751,+0.0016311999687179924+],+"hi": [+0.041022485029650835,+0.029699998879339537,+0.0045861000006273385+],+"n": 6+},+"pins|MC21": {+"blk": "pins",+"ref": "MC21",+"lo": [+-0.050434998474549506,+0.015566364426165824,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.016433633486274635,+0.0016011999687179923+],+"n": 1+},+"loadcell|J4": {+"blk": "loadcell",+"ref": "J4",+"lo": [+0.02715000317618252,+-0.044024997361004334,+-0.0017988000763580203+],+"hi": [+0.0390500029847026,+-0.039474997406825404,+0.007631200020872056+],+"n": 2+},+"molecules|N2": {+"blk": "molecules",+"ref": "N2",+"lo": [+0.02480755972862242,+-0.032010052323341365,+-0.0004976000636816025+],+"hi": [+0.051207559585571286,+0.0039899498522281696,+0.008802399742156268+],+"n": 214+},+"pins|MC22": {+"blk": "pins",+"ref": "MC22",+"lo": [+-0.050434998474549506,+0.0075663667819462765,+-4.880002234131098e-05+],+"hi": [+-0.04956499848561362,+0.008433635842055087,+0.0016011999687179923+],+"n": 1+},+"pins|MC4": {+"blk": "pins",+"ref": "MC4",+"lo": [+0.02956500346260145,+0.04956636621570215,+-4.880002234131098e-05+],+"hi": [+0.030435003451537337,+0.05043363527581096,+0.0016011999687179923+],+"n": 1+}+},+"fps": {+"MC10": {+"ref": "MC10",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": -90.0,+"x": 0.03599999999999999,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC4": {+"ref": "MC4",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.03,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC22": {+"ref": "MC22",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.05,+"y": 0.008000000000000007,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC1": {+"ref": "MC1",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.023999999999999994,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"N2": {+"ref": "N2",+"val": "RP2350A / ADS1220",+"fp": "RP2350A _ ADS1220",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.038207552000000006,+"y": -0.014410051999999993,+"blk": "molecules",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"R1": {+"ref": "R1",+"val": "300R",+"fp": "R_0603_1608Metric",+"layer": "F.Cu",+"rot": -45.0,+"x": 0.03386005199999999,+"y": 0.020339948000000004,+"blk": "comms",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"J4": {+"ref": "J4",+"val": "Conn_JST_PH_01x05_TH_Vert",+"fp": "JST_PH_B5B-PH-K_1x05_P2.00mm_Vertical",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.029099999999999987,+"y": -0.041224999999999984,+"blk": "loadcell",+"sheet": "root",+"dnp": false,+"stale_mm": 4.0,+"has3d": true+},+"H2": {+"ref": "H2",+"val": "MountingHole",+"fp": "MountingHole_3.2mm_M3",+"layer": "F.Cu",+"rot": -90.0,+"x": -0.011999999999999997,+"y": 0.012000000000000004,+"blk": "mounting",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": false+},+"J2": {+"ref": "J2",+"val": "2mmHole_8x8mm_RoundRect",+"fp": "2mmHole_8x8mm_RoundRect",+"layer": "F.Cu",+"rot": 180.0,+"x": -0.006000000000000005,+"y": 0.033,+"blk": "motor",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": false+},+"J1": {+"ref": "J1",+"val": "2mmHole_8x8mm_RoundRect",+"fp": "2mmHole_8x8mm_RoundRect",+"layer": "F.Cu",+"rot": 180.0,+"x": 0.007999999999999993,+"y": 0.033,+"blk": "motor",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": false+},+"H1": {+"ref": "H1",+"val": "MountingHole",+"fp": "MountingHole_3.2mm_M3",+"layer": "F.Cu",+"rot": -90.0,+"x": 0.011999999999999997,+"y": 0.012000000000000004,+"blk": "mounting",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": false+},+"MC6": {+"ref": "MC6",+"val": "~",+"fp": "MachineContactLarge",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.007999999999999993,+"y": 0.048,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC21": {+"ref": "MC21",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 180.0,+"x": -0.05,+"y": 0.016000000000000007,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"N1": {+"ref": "N1",+"val": "Molecule_PiProbe",+"fp": "Molecule_PiProbe",+"layer": "F.Cu",+"rot": -90.0,+"x": 0.04029748799999999,+"y": 0.022000000000000006,+"blk": "molecules",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC18": {+"ref": "MC18",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 180.0,+"x": -0.05,+"y": 0.026000000000000002,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"J3": {+"ref": "J3",+"val": "2mmHole_8x8mm_RoundRect",+"fp": "2mmHole_8x8mm_RoundRect",+"layer": "F.Cu",+"rot": 180.0,+"x": -0.020000000000000004,+"y": 0.033,+"blk": "motor",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": false+},+"MC12": {+"ref": "MC12",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.039999999999999994,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"J5": {+"ref": "J5",+"val": "Conn_JST_PH_01x05_TH_Vert",+"fp": "JST_PH_B5B-PH-K_1x05_P2.00mm_Vertical",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.0009302440000000106,+"y": -0.041199999,+"blk": "loadcell",+"sheet": "root",+"dnp": false,+"stale_mm": 4.0,+"has3d": true+},+"MP4": {+"ref": "MP4",+"val": "~",+"fp": "MachinePinLargeShort",+"layer": "F.Cu",+"rot": 90.0,+"x": 0.048,+"y": 0.048,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"H4": {+"ref": "H4",+"val": "MountingHole",+"fp": "MountingHole_3.2mm_M3",+"layer": "F.Cu",+"rot": -90.0,+"x": -0.011999999999999997,+"y": -0.011999999999999997,+"blk": "mounting",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": false+},+"MC9": {+"ref": "MC9",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.034,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC5": {+"ref": "MC5",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.032,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC13": {+"ref": "MC13",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.041999999999999996,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC16": {+"ref": "MC16",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.05,+"y": 0.03200000000000001,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MP1": {+"ref": "MP1",+"val": "~",+"fp": "MachinePinLargeShort",+"layer": "F.Cu",+"rot": 90.0,+"x": -0.048,+"y": 0.048,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MP3": {+"ref": "MP3",+"val": "~",+"fp": "MachinePinLargeShort",+"layer": "F.Cu",+"rot": -90.0,+"x": 0.048,+"y": -0.048,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC15": {+"ref": "MC15",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.05,+"y": 0.04000000000000001,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC24": {+"ref": "MC24",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.05,+"y": 0.012000000000000004,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"H3": {+"ref": "H3",+"val": "MountingHole",+"fp": "MountingHole_3.2mm_M3",+"layer": "F.Cu",+"rot": -90.0,+"x": 0.011999999999999997,+"y": -0.011999999999999997,+"blk": "mounting",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": false+},+"MC25": {+"ref": "MC25",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.05,+"y": 0.0020000000000000018,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC14": {+"ref": "MC14",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.05,+"y": 0.04200000000000001,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC23": {+"ref": "MC23",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.05,+"y": 0.010000000000000002,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC19": {+"ref": "MC19",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": -0.05,+"y": 0.024000000000000007,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MP2": {+"ref": "MP2",+"val": "~",+"fp": "MachinePinLargeShort",+"layer": "F.Cu",+"rot": -90.0,+"x": -0.048,+"y": -0.048,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC17": {+"ref": "MC17",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 180.0,+"x": -0.05,+"y": 0.028000000000000004,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC7": {+"ref": "MC7",+"val": "~",+"fp": "MachineContactLarge",+"layer": "F.Cu",+"rot": 180.0,+"x": -0.006000000000000005,+"y": 0.048,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC3": {+"ref": "MC3",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.027999999999999997,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC11": {+"ref": "MC11",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": -90.0,+"x": 0.03799999999999999,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC20": {+"ref": "MC20",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 180.0,+"x": -0.05,+"y": 0.018000000000000002,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC2": {+"ref": "MC2",+"val": "~",+"fp": "MachineContactMedium",+"layer": "F.Cu",+"rot": 0.0,+"x": 0.025999999999999995,+"y": 0.05,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+},+"MC8": {+"ref": "MC8",+"val": "~",+"fp": "MachineContactLarge",+"layer": "F.Cu",+"rot": 180.0,+"x": -0.020000000000000004,+"y": 0.048,+"blk": "pins",+"sheet": "root",+"dnp": false,+"stale_mm": null,+"has3d": true+}+},+"centre_mm": [+82.0,+68.0+],+"coverage": {+"solids": 404,+"refs_tagged": 34,+"missing_real": [+{+"ref": "J2",+"val": "2mmHole_8x8mm_RoundRect",+"fp": "2mmHole_8x8mm_RoundRect"+},+{+"ref": "J1",+"val": "2mmHole_8x8mm_RoundRect",+"fp": "2mmHole_8x8mm_RoundRect"+},+{+"ref": "J3",+"val": "2mmHole_8x8mm_RoundRect",+"fp": "2mmHole_8x8mm_RoundRect"+}+],+"bodiless_ok": [+{+"ref": "H2",+"val": "MountingHole",+"fp": "MountingHole_3.2mm_M3"+},+{+"ref": "H1",+"val": "MountingHole",+"fp": "MountingHole_3.2mm_M3"+},+{+"ref": "H4",+"val": "MountingHole",+"fp": "MountingHole_3.2mm_M3"+},+{+"ref": "H3",+"val": "MountingHole",+"fp": "MountingHole_3.2mm_M3"+}+],+"stale_geometry": {+"J5": 4.0,+"J4": 4.0+},+"unmatched_solids": [+"drone-motor-carrier_v1.1_silkscreen_1"+],+"dnp": [],+"sections": {+"pins": 29,+"molecules": 2,+"comms": 1,+"loadcell": 2,+"mounting": 4,+"motor": 3+}+}+}
viewer/viewer-config.jsonadded+190@@ -0,0 +1,190 @@+{+ "$shape": "adom-board-viewer-config/0.2",+ "_note": "Path B molecule-carrier viewer. Content from the designer's schematic notes + the Overview; captions author-asserted; behaviours illustrative.",+ "board": {+ "name": "Drone Motor Carrier",+ "subtitle": "drone-motor-carrier v1.1",+ "rev": "as-drawn from drone-motor-carrier_v1.1.kicad_pcb",+ "source": "KiCad project (4-layer PCB + STEP export)",+ "size_mm": [+ 104.0,+ 104.0+ ],+ "thickness_mm": 1.6,+ "copper_layers": 4+ },+ "blocks": [+ {+ "id": "pins",+ "label": "Machine-pin datum",+ "color": "#64748b",+ "short": "MC1-25 + corner MP1-4, 96 x 96 mm datum"+ },+ {+ "id": "mounting",+ "label": "Base-plate mounting",+ "color": "#94a3b8",+ "short": "H1-H4"+ },+ {+ "id": "motor",+ "label": "BLDC phases",+ "color": "#f97316",+ "short": "Phase A/B/C pass-through (J1/J2/J3)"+ },+ {+ "id": "loadcell",+ "label": "Load-cell headers",+ "color": "#22c55e",+ "short": "Two JST-PH load-cell connectors (J4/J5)"+ },+ {+ "id": "comms",+ "label": "UART arbitration",+ "color": "#a855f7",+ "short": "R1 300R on the PiProbe branch"+ },+ {+ "id": "molecules",+ "label": "Molecule nests",+ "color": "#3b82f6",+ "short": "PiProbe (N1) + RP2350A/ADS1220 (N2)"+ }+ ],+ "modes": {+ "assembly": {+ "label": "Assembly",+ "blurb": "Each grouping seats onto the carrier while its caption explains it.",+ "order": [+ "pins",+ "loadcell",+ "comms",+ "molecules"+ ],+ "timing": {+ "fall_s": 0.47,+ "stagger_s": 0.027,+ "dwell_s": 6.3,+ "rest_s": 4.0,+ "drop_height_mm": 18+ },+ "intro": {+ "d": 6,+ "eyebrow": "Drone Motor Carrier",+ "head": "Passive baseplate that carries the motor and hosts the acquisition molecule",+ "rows": [+ [+ "Board",+ "104 x 104 mm, 4-layer"+ ],+ [+ "Datum",+ "machine pins 96 x 96 mm"+ ],+ [+ "Hosts",+ "RP2350A/ADS1220 + PiProbe"+ ]+ ],+ "note": "Mounts the BLDC motor, passes its three phases through from the ESC, routes two load-cell channels, and nests the acquisition molecule and a PiProbe."+ },+ "captions": {+ "pins": {+ "eyebrow": "Datum",+ "head": "Machine-pin nesting interface",+ "rows": [+ [+ "Contacts",+ "MC1-25"+ ],+ [+ "Corners",+ "MP1-4 (large pins)"+ ],+ [+ "Datum",+ "96 x 96 mm"+ ]+ ],+ "note": "Locates the carrier in the cell and passes signals to the nested molecule."+ },+ "mounting": {+ "eyebrow": "Mounting",+ "head": "Base-plate holes",+ "rows": [+ [+ "Holes",+ "H1-H4"+ ]+ ],+ "note": "Fixes the carrier to the base plate."+ },+ "motor": {+ "eyebrow": "BLDC",+ "head": "Three phases, pass-through",+ "rows": [+ [+ "Phases",+ "A / B / C (J1 / J2 / J3)"+ ]+ ],+ "note": "The three motor phases pass from the ESC through the carrier to the motor leads. (Solder-pad terminals; no 3D body in the export.)"+ },+ "loadcell": {+ "eyebrow": "Analogue",+ "head": "Two load-cell channels",+ "rows": [+ [+ "Headers",+ "J4 / J5 (JST-PH 5-pin)"+ ],+ [+ "Routing",+ "pairs on B.Cu, 0.45 mm pitch, no vias"+ ]+ ],+ "note": "Load-cell pairs run at constant 0.45 mm pitch on B.Cu, referenced to the In2 GND plane; the contacts sit upstream of the molecule's RC filter."+ },+ "comms": {+ "eyebrow": "Arbitration",+ "head": "One resistor keeps the debug bus clean",+ "rows": [+ [+ "Part",+ "R1 = 300R 0603 1%"+ ]+ ],+ "note": "The PiProbe reaches /UART0_RX only through R1, so the FC and the molecule's RX connect directly and a populated probe never contends the bus. R1's placement also lets N1 and N2 sit without conflict."+ },+ "molecules": {+ "eyebrow": "Nests",+ "head": "The acquisition molecule and a PiProbe",+ "rows": [+ [+ "N2",+ "RP2350A / ADS1220 molecule"+ ],+ [+ "N1",+ "PiProbe debug molecule"+ ],+ [+ "Slot",+ "16 x 32 mm"+ ]+ ],+ "note": "N2 is the 24-bit acquisition molecule; N1 is a PiProbe for debug. Each seats over the carrier's machine contacts as a populated sub-assembly."+ }+ }+ },+ "static": {+ "label": "Static",+ "blurb": "The seated carrier. Camera is yours; hover a part, click legend rows to isolate a section.",+ "functions": {}+ }+ },+ "tooltips": {+ "enabled": true,+ "_note": "Measured from geometry + schematic-note text; no datasheet citations."+ }+}