← Commit history

Publish 1.9.251

John Lauer ·1f2dd802a6 ·1mo ago ·parent 1da3a1c
3 files changed +28−14
.release-page-head+1−1
@@ -1 +1 @@-7cdf298b8b3049bfe3da0a4c9a7fc374aa79fc7c\ No newline at end of file+1da3a1c1028206e4b2fe2d47243fc7e58478dc2b\ No newline at end of file
dashboard/ui/quick-actions.html+20−3
@@ -63,6 +63,11 @@ input[type=text]{font:inherit;color:var(--text);background:var(--surface);border .stage b{color:var(--text);font-weight:500} .plan{list-style:none;margin-top:8px;font:12px var(--mono)}.plan li{display:flex;justify-content:space-between;gap:12px;padding:4px 0;border-bottom:1px solid var(--border)} .plan li:last-child{border-bottom:0}.plan .c{color:var(--text-2)}.plan .c.measured{color:var(--green)}+.live{margin-top:10px;background:#000;border:1px solid var(--border);border-radius:10px;padding:8px;max-width:720px}+.live-head{display:flex;align-items:center;gap:8px;font:11px var(--mono);color:var(--text-2);margin-bottom:6px;letter-spacing:.04em;text-transform:uppercase}+.live-led{width:8px;height:8px;border-radius:50%;background:var(--text-2)}.live-led.on{background:var(--red);box-shadow:0 0 6px var(--red);animation:blink 1s steps(2) infinite}+@keyframes blink{50%{opacity:.35}}+.live img{display:block;width:100%;height:auto;border-radius:6px;min-height:120px;background:#111} .gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;margin-top:10px} .shot{background:var(--surface);border:1px solid var(--border);border-radius:8px;overflow:hidden} .shot img{display:block;width:100%;height:auto;background:#000}@@ -148,6 +153,11 @@ input[type=text]{font:inherit;color:var(--text);background:var(--surface);border       <button class="btn" id="btnAttach" data-action="attach_run" title="Attach to a run already in flight">Attach to running import</button>       <span class="mini" id="importNote">resolve a part first</span>     </div>+    <div class="live" id="live" hidden>+      <div class="live-head"><span class="live-led" id="liveLed"></span><span id="liveLabel">live · Fusion on this machine</span><span class="mini" id="liveTick"></span></div>+      <img id="liveImg" alt="latest live frame of the Fusion window">+      <div class="mini" id="liveNote"></div>+    </div>     <div class="prog" id="prog"><div></div></div>     <div class="stage" id="stage"></div>     <div class="kv" id="receipt"></div>@@ -218,10 +228,17 @@ function stage(path,buf,note){var u=new Uint8Array(buf),CH=1024*1024,n=Math.max( // ---- import: one call, then poll -------------------------------------------------------- function setProg(p,cls){var el=$('prog');el.className='prog '+(cls||'');el.firstElementChild.style.width=Math.max(0,Math.min(100,p||0))+'%'} function setStage(html){$('stage').innerHTML=html}-function renderLint(pm){if(!pm){$('lint').innerHTML='';return}var ok=pm.ok!==false;var h='<div class="lint '+(ok?'ok':'warn')+'"><b>Part numbers</b> '+esc(pm.summary||(ok?'every component carries a manufacturer part number and a distributor number.':''))+'';if(pm.warnings&&pm.warnings.length){h+='<ul>'+pm.warnings.slice(0,6).map(function(w){return '<li>'+esc(typeof w==='string'?w:(w.detail||w.message||((w.deviceset||'')+': missing '+(Array.isArray(w.missing)?w.missing.join(', '):''))))+'</li>'}).join('')+'</ul>'}if(pm.userAction)h+='<div class="mini" style="margin-top:6px">'+esc(pm.userAction)+'</div>';h+='</div>';$('lint').innerHTML=h}+function renderLint(pm){if(!pm){$('lint').innerHTML='';return}var ok=pm.ok!==false;var mc=pm.missingCounts||{};var tot=pm.componentsTotal||pm.variantsTotal||0;var parts=[];['MPN','MANUFACTURER'].forEach(function(k){if(mc[k])parts.push(k.toLowerCase()==='mpn'?'manufacturer part number':'manufacturer')});if(pm.componentsWithoutAnyDistributor)parts.push('a distributor number (Mouser, DigiKey or LCSC)');var line=ok?'every component carries a manufacturer part number and a distributor number.':(tot?(pm.componentsWithoutAnyDistributor||mc.MPN||0)+' of '+tot+' component'+(tot===1?'':'s')+' missing ':'missing ')+(parts.join(', ')||'part numbers')+'. The import is fine; the BOM will be blank for it until the numbers are added upstream.';$('lint').innerHTML='<div class="lint '+(ok?'ok':'warn')+'"><b>Part numbers</b> '+esc(line)+'</div>'} function renderReceipt(d){var unb=(d.unbound||[]).map(function(u){return u.package+' ('+u.why+')'}).join('; ');kv($('receipt'),{installed:d.installed?'yes, '+d.libraryPath:'no',library:d.libraryName||'',parts:(d.partsToBind||0)+' of '+(d.partsTotal||0)+' paired with a STEP','named skips':unb||'none','fusion':(d.fusion&&d.fusion.message)||'',runId:d.runId||'','estimated':d.estimatedSec?Math.round(d.estimatedSec)+'s':''});renderLint(d.partMetadata)}-function renderFrames(ev){var g=$('gallery');g.innerHTML='';(ev||[]).forEach(function(f){var src=f.thumbnailDataUrl||f.dataUrl;var h='<div class="shot">'+(src?'<img src="'+src+'" alt="'+esc(f.step||f.view||'frame')+'">':'<div class="cap" style="padding:18px 8px">frame on the box: '+esc((f.screenshot||'').split('/').pop())+'</div>')+'<div class="cap"><span><span class="tag '+(f.kind==='proof'?'proof':'feed')+'">'+esc(f.kind||'frame')+'</span> '+esc(f.step||f.view||'')+'</span><span>'+esc(f.part||'')+'</span></div></div>';g.insertAdjacentHTML('beforeend',h)})}-function poll(id){runId=id;clearTimeout(pollTimer);var tick=0;(function loop(){tick++;cmd('fusion_prove_result',{runId:id}).then(function(d){var r=d.result||d;var pct=typeof r.percent==='number'?r.percent:null;if(r.stage==='failed'){setProg(100,'fail');setStage('<b>failed</b> '+esc(r.error||(r.bind&&r.bind.error)||r.errorCode||''));status($('importStatus'),'failed','bad');renderFrames(r.evidence);toast('Import failed: '+(r.error||(r.bind&&r.bind.error)||'see the log'),'err');return}if(r.stage==='done'){setProg(100,'done');var pv=r.prove||{};setStage('<b>done</b> verdict: '+esc(r.verdict||(pv.proven?'proven':'not proven'))+' · '+(pv.partsProven||0)+'/'+(pv.partsTotal||r.partsTotal||0)+' parts proven · '+(r.evidenceCount||0)+' frames');status($('importStatus'),r.verdict==='proven'||pv.proven?'proven':'finished, not proven',r.verdict==='proven'||pv.proven?'ok':'warn');if(r.partMetadata)renderLint(r.partMetadata);toast('Library created in Fusion: '+(pv.partsProven||0)+'/'+(pv.partsTotal||0)+' parts proven','ok');return cmd('fusion_prove_result',{runId:id,inlineFrames:true},{timeoutMs:120000}).then(function(d2){var r2=d2.result||d2;renderFrames(r2.evidence||r.evidence)}).catch(function(){renderFrames(r.evidence)})}if(pct!==null)setProg(pct);setStage('<b>'+esc(r.stage||'working')+'</b>'+(r.stageLabel?' · '+esc(r.stageLabel):'')+(pct!==null?' · '+Math.round(pct)+'%':'')+(typeof r.etaSec==='number'?' · ~'+Math.round(r.etaSec)+'s left':''));if(r.evidence&&r.evidence.length)renderFrames(r.evidence);if(r.errorCode==='addin_restart_required')showAsk(r);pollTimer=setTimeout(loop,tick<3?5000:10000)}).catch(function(e){if(e.code==='run_not_found'&&tick<6){setStage('starting...');pollTimer=setTimeout(loop,6000);return}setStage('<b>poll failed</b> '+esc(e.message));status($('importStatus'),'poll failed','bad')})})()}+var frameCache={};+function frameSrc(f){var src=f.thumbnailDataUrl||f.dataUrl;if(src)return Promise.resolve(src);var p=f.screenshot||f.path;if(!p)return Promise.resolve(null);if(frameCache[p])return Promise.resolve(frameCache[p]);return cmd('read_file',{path:p,encoding:'base64'},{timeoutMs:60000}).then(function(d){var c=d.content||'';if(!c)return null;var u='data:image/'+(/\.png$/i.test(p)?'png':'webp')+';base64,'+c;frameCache[p]=u;return u}).catch(function(){return null})}+function renderFrames(ev){var g=$('gallery');g.innerHTML='';(ev||[]).forEach(function(f,i){var id='shot'+i;var h='<div class="shot" id="'+id+'"><div class="cap" style="padding:18px 8px">'+(f.ok===false?'REFUSED: '+esc(f.error||'not evidence'):'loading frame...')+'</div><div class="cap"><span><span class="tag '+(f.kind==='proof'?'proof':'feed')+'">'+esc(f.kind||'frame')+'</span> '+esc(f.step||f.view||'')+(f.width?' '+f.width+'x'+f.height:'')+'</span><span>'+esc(f.part||'')+'</span></div></div>';g.insertAdjacentHTML('beforeend',h);if(f.ok===false)return;frameSrc(f).then(function(src){var el=document.getElementById(id);if(!el)return;if(src){el.firstElementChild.outerHTML='<img src="'+src+'" alt="'+esc(f.step||f.view||'frame')+'">'}else{el.firstElementChild.textContent='frame could not be fetched from the box: '+((f.screenshot||'').split('/').pop())}})})}+// ---- the 1 fps live view: a slow movie of the Fusion window while the run works ------+var live={timer:null,since:0,on:false};+function liveStart(){if(live.on)return;live.on=true;live.since=0;$('live').hidden=false;$('liveLed').className='live-led';$('liveTick').textContent='';$('liveNote').textContent='starting the 1 fps capture on this machine...';cmd('fusion_live_feed',{action:'start',intervalSec:1,maxSec:1500}).then(function(){liveTick()}).catch(function(e){$('liveNote').textContent='feed could not start: '+e.message})}+function liveTick(){if(!live.on)return;cmd('fusion_live_feed',{action:'frames',since:live.since},{timeoutMs:20000}).then(function(d){var fr=(d.frames||[]).filter(function(f){return f.kind==='main'});if(d.healthy===false&&!fr.length){$('liveLed').className='live-led';$('liveNote').textContent=d._honesty||'feed is not capturing';}if(fr.length){var f=fr[fr.length-1];live.since=Math.max(live.since,f.tick);return frameSrc(f).then(function(src){if(src&&live.on){$('liveImg').src=src;$('liveLed').className='live-led on';$('liveTick').textContent='frame '+f.tick+' · '+(f.popups?f.popups+' dialog(s) over it':'no dialogs');$('liveNote').textContent=f.title||''}})}}).catch(function(e){$('liveNote').textContent='feed: '+e.message}).then(function(){if(live.on)live.timer=setTimeout(liveTick,1500)})}+function liveStop(why){if(!live.on)return;live.on=false;clearTimeout(live.timer);$('liveLed').className='live-led';$('liveLabel').textContent='live view ended · '+(why||'');cmd('fusion_live_feed',{action:'stop'}).catch(function(){})}+function poll(id){runId=id;clearTimeout(pollTimer);var tick=0;liveStart();(function loop(){tick++;cmd('fusion_prove_result',{runId:id}).then(function(d){var r=d.result||d;var pct=typeof r.percent==='number'?r.percent:null;if(r.stage==='failed'){liveStop('failed');setProg(100,'fail');setStage('<b>failed</b> '+esc(r.error||(r.bind&&r.bind.error)||r.errorCode||''));status($('importStatus'),'failed','bad');renderFrames(r.evidence);toast('Import failed: '+(r.error||(r.bind&&r.bind.error)||'see the log'),'err');return}if(r.stage==='done'){liveStop('done');setProg(100,'done');var pv=r.prove||{};setStage('<b>done</b> verdict: '+esc(r.verdict||(pv.proven?'proven':'not proven'))+' · '+(pv.partsProven||0)+'/'+(pv.partsTotal||r.partsTotal||0)+' parts proven · '+(r.evidenceCount||0)+' frames');status($('importStatus'),r.verdict==='proven'||pv.proven?'proven':'finished, not proven',r.verdict==='proven'||pv.proven?'ok':'warn');if(r.partMetadata)renderLint(r.partMetadata);toast('Library created in Fusion: '+(pv.partsProven||0)+'/'+(pv.partsTotal||0)+' parts proven','ok');return cmd('fusion_prove_result',{runId:id,inlineFrames:true},{timeoutMs:120000}).then(function(d2){var r2=d2.result||d2;renderFrames(r2.evidence||r.evidence)}).catch(function(){renderFrames(r.evidence)})}if(pct!==null)setProg(pct);setStage('<b>'+esc(r.stage||'working')+'</b>'+(r.stageLabel?' · '+esc(r.stageLabel):'')+(pct!==null?' · '+Math.round(pct)+'%':'')+(typeof r.etaSec==='number'?' · ~'+Math.round(r.etaSec)+'s left':''));if(r.evidence&&r.evidence.length)renderFrames(r.evidence);if(r.errorCode==='addin_restart_required')showAsk(r);pollTimer=setTimeout(loop,tick<3?5000:10000)}).catch(function(e){if(e.code==='run_not_found'&&tick<6){setStage('starting...');pollTimer=setTimeout(loop,6000);return}liveStop('poll failed');setStage('<b>poll failed</b> '+esc(e.message));status($('importStatus'),'poll failed','bad')})})()} function importBundle(){if(!part)return;var b=$('btnImport');b.disabled=true;status($('importStatus'),'working');$('gallery').innerHTML='';$('receipt').innerHTML='';$('lint').innerHTML='';setProg(0);setStage('fetching '+esc(part.lbr.name)+' from the wiki...');var fetches=[fetch(part.lbr.url).then(function(r){return r.arrayBuffer()})].concat(part.steps.map(function(s){return fetch(s.url).then(function(r){return r.arrayBuffer()})}));Promise.all(fetches).then(function(bufs){setStage('zipping in the browser...');var entries=[{name:part.lbr.name,data:bufs[0]}].concat(part.steps.map(function(s,i){return{name:s.name,data:bufs[i+1]}}));var zip=makeZip(entries);var name=part.name.replace(/[^A-Za-z0-9._-]+/g,'_');return stage('~/adom-wiki-libs/'+name+'.zip',zip,'staging the bundle on this machine (approve in Adom Bridge)').then(function(p){staged=p;setStage('installing the bundle (one call)...');return cmd('fusion_install_library_bundle',{zipPath:p,libraryName:name})})}).then(function(d){renderReceipt(d);if(d.errorCode==='nothing_to_bind'){setStage('<b>installed</b>, nothing to bind: '+esc(d._hint||''));status($('importStatus'),'installed, unbound','warn');b.disabled=false;return}if(d.attached)toast('An import is already running; attached to '+d.runId);else toast('Installed and started run '+d.runId,'ok');poll(d.runId)}).catch(function(e){setProg(100,'fail');setStage('<b>'+esc(e.code||'failed')+'</b> '+esc(e.message));status($('importStatus'),'failed','bad');if(e.payload&&e.payload.errorCode==='addin_restart_required')showAsk(e.payload);toast((e.code==='web_denied'?'Denied in Adom Bridge: ':'')+e.message,'err')}).finally(function(){b.disabled=false})} function attachRun(){cmd('fusion_prove_result',{}).then(function(d){var r=d.result||d;if(!r.runId){toast('No run to attach to','err');return}toast('Attached to run '+r.runId);poll(r.runId)}).catch(function(e){toast(e.message,'err')})} 
page.json+7−10
@@ -11,10 +11,6 @@     "uninstall": "./uninstall.sh"   },   "tags": [-    "ab",-    "bridge",-    "fusion",-    "desktop-app",     "fusion360",     "autodesk",     "cad",@@ -79,10 +75,7 @@     "build a basic parts library",     "add a 3d model to a component",     "make a fusion 360 library with 3d",-    "set up the fusion bridge",-    "fusion dashboard",-    "fusion status page",-    "open the fusion launch page"+    "set up the fusion bridge"   ],   "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).",   "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -111,5 +104,9 @@       "label": "Part volume",       "prompt": "How much material does each printed part in this assembly use?"     }-  ]-}+  ],+  "author": {+    "name": "John Lauer",+    "email": "[email protected]"+  }+}\ No newline at end of file