app
AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
← Commit history
Merge PR #151: Honor Follow placement during recording and support full-board population videos
3 files changed
+8−2
docs/silkscreen.md+3@@ -151,3 +151,6 @@ Pin-label alignment is target-facing, not universally centered: right-align ink ## Final documentation and priority review Prefer readable documentation blocks in open board interior, not crowded rim space. Include a verified description of what the board does, based on its project wiki/spec. Physical connector, test-point and contact labels remain local on both faces. Check interior alternatives across readable sizes before using an edge fallback; reflow coherent sections around holes, never scatter their sentences. Preserve left-aligned visible text, heading hierarchy and ordered rows. Record reasons and selected font heights. The complete priority/pass checklist is in AI Flow `docs/silkscreen-priority-pass.md`; its ordered requirements apply before native DRC and both-view visual acceptance.++## Full-board process recording+For a short silkscreen population video, prefer a fixed full-board camera with Follow placement off. Preserve the selected follow setting when recording; do not silently enable it. Record every accepted placement group at a readable event cadence, top and bottom separately, with only the active face visible and the bottom shown from below. Keep the whole board inside the frame. Accelerate the recorded footage with ffmpeg to the requested duration rather than skipping placement events. Distinguish event replay from live solver timing. Finish with refreshed native EDA 3D evidence; for a one-minute film, reserve about ten seconds for that proof. Close-up follow mode remains optional for detailed review.
tools/silkscreen-dashboard/SKILL.md+3@@ -80,3 +80,6 @@ Pin-label alignment is target-facing, not universally centered: right-align ink ## Final documentation and priority review Prefer readable documentation blocks in open board interior, not crowded rim space. Include a verified description of what the board does, based on its project wiki/spec. Physical connector, test-point and contact labels remain local on both faces. Check interior alternatives across readable sizes before using an edge fallback; reflow coherent sections around holes, never scatter their sentences. Preserve left-aligned visible text, heading hierarchy and ordered rows. Record reasons and selected font heights. The complete priority/pass checklist is in AI Flow `docs/silkscreen-priority-pass.md`; its ordered requirements apply before native DRC and both-view visual acceptance.++## Full-board process recording+For a short silkscreen population video, prefer a fixed full-board camera with Follow placement off. Preserve the selected follow setting when recording; do not silently enable it. Record every accepted placement group at a readable event cadence, top and bottom separately, with only the active face visible and the bottom shown from below. Keep the whole board inside the frame. Accelerate the recorded footage with ffmpeg to the requested duration rather than skipping placement events. Distinguish event replay from live solver timing. Finish with refreshed native EDA 3D evidence; for a one-minute film, reserve about ten seconds for that proof. Close-up follow mode remains optional for detailed review.
tools/silkscreen-dashboard/index.html+2−2@@ -49,8 +49,8 @@ function manualCamera(){cancelAnimationFrame(cameraAnimation);$('#follow').check canvas.addEventListener('wheel',e=>{e.preventDefault();if(!view||!e.deltaY)return;manualCamera();const r=canvas.getBoundingClientRect(),delta=e.deltaY*(e.deltaMode===1?16:e.deltaMode===2?r.height:1),requested=Math.exp(Math.max(-1,Math.min(1,delta*.0015))),span=Math.max(view[2]-view[0],view[3]-view[1]),f=Math.max(.5,Math.min(1000,span*requested))/span,p=boardPoint(e.clientX,e.clientY);view=view.map((v,i)=>p[i%2]+(v-p[i%2])*f);draw()},{passive:false}); canvas.onpointerdown=e=>{if(!view)return;manualCamera();drag={x:e.clientX,y:e.clientY,v:[...view]};canvas.setPointerCapture(e.pointerId)};canvas.onpointermove=e=>{if(!drag)return;const a=boardPoint(drag.x,drag.y,drag.v),b=boardPoint(e.clientX,e.clientY,drag.v),dx=b[0]-a[0],dy=b[1]-a[1];view=[drag.v[0]-dx,drag.v[1]-dy,drag.v[2]-dx,drag.v[3]-dy];draw()};canvas.onpointerup=canvas.onpointercancel=()=>drag=null; -const captureCanvas=document.createElement('canvas');captureCanvas.width=1920;captureCanvas.height=1080;const captureCtx=captureCanvas.getContext('2d');function captureExtent(){if(state.summary||!state.playing)return bounds;const e=history[state.cursor],children=e?.candidate?.children||[];const boxes=children.map(c=>c.box).filter(Boolean);for(const child of children){if(child.reference)for(const o of data.obstacles)if(o.reference===child.reference&&o.layer===state.layer&&o.box)boxes.push(o.box)}if(!boxes.length)return bounds;const b=[Math.min(...boxes.map(b=>b[0])),Math.min(...boxes.map(b=>b[1])),Math.max(...boxes.map(b=>b[2])),Math.max(...boxes.map(b=>b[3]))],cx=(b[0]+b[2])/2,cy=(b[1]+b[3])/2,w=Math.max(18,b[2]-b[0]+6),h=Math.max(10,b[3]-b[1]+6);return [cx-w/2,cy-h/2,cx+w/2,cy+h/2]}function paintCapture(){paint(captureCtx,1920,1080,captureExtent());captureCtx.fillStyle='#0d1117e8';captureCtx.fillRect(0,0,1920,50);captureCtx.fillStyle='#e6edf3';captureCtx.font='22px sans-serif';captureCtx.fillStyle=state.layer==='B.SilkS'?'#79b8ff':'#ffd479';captureCtx.fillText('Adom AI Flow · '+(state.layer==='F.SilkS'?'Top, viewed from above':'Bottom, viewed from below')+' silkscreen · '+$('#textsize').textContent,20,32)}-recordReplay=async function(summary=false){if(recording)return;const latest=history.map((e,i)=>e.kind==='start'?i:-1).filter(i=>i>=0).pop()||0;await api('control',{mode:'replay',playing:false,summary,cursor:latest-1,view:'2d'});$('#follow').checked=!summary;frame(bounds);recording=true;const chunks=[],rec=new MediaRecorder(captureCanvas.captureStream(30),{mimeType:'video/webm'});rec.ondataavailable=e=>chunks.push(e.data);rec.onstop=()=>{recording=false;const blob=new Blob(chunks,{type:'video/webm'}),a=document.createElement('a');window.silkscreen.recordingBlob=blob;const reader=new FileReader();reader.onload=()=>api('recording',{kind:summary?'overview':'walkthrough',video:reader.result.split(',')[1]}).catch(e=>$('#error').textContent=e.message);reader.readAsDataURL(blob);a.href=URL.createObjectURL(blob);a.download='silkscreen-replay.webm';a.click();$('#record').textContent='Record replay';draw()};paintCapture();rec.start();$('#record').textContent='Recording replay…';await api('control',{playing:true});const timer=setInterval(()=>{draw();paintCapture();if(!state.playing&&state.cursor>=history.length-1){clearInterval(timer);frame(bounds);const hold=setInterval(paintCapture,33);setTimeout(()=>{clearInterval(hold);paintCapture();rec.stop()},1200)}},100)};$('#record').onclick=()=>recordReplay(false);$('#short').onclick=()=>recordReplay(true);+const captureCanvas=document.createElement('canvas');captureCanvas.width=1920;captureCanvas.height=1080;const captureCtx=captureCanvas.getContext('2d');function captureExtent(){if(state.summary||!state.playing||!$('#follow').checked)return bounds;const e=history[state.cursor],children=e?.candidate?.children||[];const boxes=children.map(c=>c.box).filter(Boolean);for(const child of children){if(child.reference)for(const o of data.obstacles)if(o.reference===child.reference&&o.layer===state.layer&&o.box)boxes.push(o.box)}if(!boxes.length)return bounds;const b=[Math.min(...boxes.map(b=>b[0])),Math.min(...boxes.map(b=>b[1])),Math.max(...boxes.map(b=>b[2])),Math.max(...boxes.map(b=>b[3]))],cx=(b[0]+b[2])/2,cy=(b[1]+b[3])/2,w=Math.max(18,b[2]-b[0]+6),h=Math.max(10,b[3]-b[1]+6);return [cx-w/2,cy-h/2,cx+w/2,cy+h/2]}function paintCapture(){paint(captureCtx,1920,1080,captureExtent());captureCtx.fillStyle='#0d1117e8';captureCtx.fillRect(0,0,1920,50);captureCtx.fillStyle='#e6edf3';captureCtx.font='22px sans-serif';captureCtx.fillStyle=state.layer==='B.SilkS'?'#79b8ff':'#ffd479';captureCtx.fillText('Adom AI Flow · '+(state.layer==='F.SilkS'?'Top, viewed from above':'Bottom, viewed from below')+' silkscreen · '+$('#textsize').textContent,20,32)}+recordReplay=async function(summary=false){if(recording)return;const latest=history.map((e,i)=>e.kind==='start'?i:-1).filter(i=>i>=0).pop()||0;await api('control',{mode:'replay',playing:false,summary,cursor:latest-1,view:'2d'});frame(bounds);recording=true;const chunks=[],rec=new MediaRecorder(captureCanvas.captureStream(30),{mimeType:'video/webm'});rec.ondataavailable=e=>chunks.push(e.data);rec.onstop=()=>{recording=false;const blob=new Blob(chunks,{type:'video/webm'}),a=document.createElement('a');window.silkscreen.recordingBlob=blob;const reader=new FileReader();reader.onload=()=>api('recording',{kind:summary?'overview':'walkthrough',video:reader.result.split(',')[1]}).catch(e=>$('#error').textContent=e.message);reader.readAsDataURL(blob);a.href=URL.createObjectURL(blob);a.download='silkscreen-replay.webm';a.click();$('#record').textContent='Record replay';draw()};paintCapture();rec.start();$('#record').textContent='Recording replay…';await api('control',{playing:true});const timer=setInterval(()=>{draw();paintCapture();if(!state.playing&&state.cursor>=history.length-1){clearInterval(timer);frame(bounds);const hold=setInterval(paintCapture,33);setTimeout(()=>{clearInterval(hold);paintCapture();rec.stop()},1200)}},100)};$('#record').onclick=()=>recordReplay(false);$('#short').onclick=()=>recordReplay(true); let infoTimer;const brand=$('.brand'),info=$('#appinfo');brand.onmouseenter=()=>{clearTimeout(infoTimer);infoTimer=setTimeout(()=>info.hidden=false,600)};brand.onmouseleave=()=>{clearTimeout(infoTimer);infoTimer=setTimeout(()=>info.hidden=true,220)};brand.onfocus=()=>info.hidden=false;document.addEventListener('keydown',e=>{if(e.key==='Escape'){if($('#settings').open)$('#settings').close();info.hidden=true}}); new ResizeObserver(draw).observe($('#stage'));draw();}catch(e){$('#error').textContent=e.stack} </script></body></html>