Replace hanging STL dialog with local file picker

This commit is contained in:
2026-08-25 22:33:27 +02:00
parent 85ba44281e
commit 411eb6397c
6 changed files with 39 additions and 30 deletions
+4 -1
View File
@@ -25,7 +25,10 @@ addEventListener('blur',()=>{keys.clear();if(!joyDrag)easeJoy();if(!heaveDrag)ea
function openModal(id){document.getElementById(id)?.showModal()}$$('[data-open]').forEach(b=>b.onclick=()=>openModal(b.dataset.open));$$('[data-close]').forEach(b=>b.onclick=()=>b.closest('dialog').close());
$('#window-min').onclick=()=>api('minimize_window');$('#window-max').onclick=()=>api('toggle_maximize_window');$('#window-close').onclick=()=>api('close_window');$('.window-drag').ondblclick=()=>api('toggle_maximize_window');
$('#load-model').onclick=async e=>{const button=e.currentTarget,old=button.textContent;try{button.disabled=true;button.textContent='Loading…';const model=await api('choose_model');if(!model)return;await new Promise(requestAnimationFrame);const result=window.rovModel.load(model);button.textContent='Replace STL';toast(`Loaded ${result.name} • ${result.triangles.toLocaleString()} triangles`)}catch(error){toast(`STL import failed: ${error.message}`,true)}finally{button.disabled=false;if(button.textContent==='Loading…')button.textContent=old}};$('#model-style').onclick=e=>{const edges=window.rovModel.toggleStyle();e.currentTarget.textContent=edges?'Shaded':'Edges'};
const modelInput=$('#model-input'),modelButton=$('#load-model');
modelButton.onclick=()=>{modelInput.value='';modelInput.click()};
modelInput.onchange=async()=>{const file=modelInput.files?.[0],old=modelButton.textContent;if(!file)return;try{if(!file.name.toLowerCase().endsWith('.stl'))throw new Error('Choose an STL mesh');if(file.size>30*1024*1024)throw new Error('The STL is larger than 30 MB; export a lighter mesh');modelButton.disabled=true;modelButton.textContent='Loading…';await new Promise(requestAnimationFrame);const buffer=await file.arrayBuffer(),result=window.rovModel.loadBuffer(buffer,file.name);modelButton.textContent='Replace STL';toast(`Loaded ${result.name} • ${result.triangles.toLocaleString()} triangles`);if(bridgeReady){const dataUrl=await new Promise((resolve,reject)=>{const reader=new FileReader();reader.onload=()=>resolve(reader.result);reader.onerror=()=>reject(reader.error);reader.readAsDataURL(file)});api('save_model',file.name,String(dataUrl).split(',')[1]).catch(error=>toast(`Model loaded, but could not be saved: ${error.message}`,true))}}catch(error){toast(`STL import failed: ${error.message}`,true)}finally{modelButton.disabled=false;if(modelButton.textContent==='Loading…')modelButton.textContent=old}};
$('#model-style').onclick=e=>{const edges=window.rovModel.toggleStyle();e.currentTarget.textContent=edges?'Shaded':'Edges'};
$('#refresh').onclick=async()=>populatePorts(await api('refresh_ports'));$('#connect').onclick=async()=>{try{if(connected)await api('disconnect');else await api('connect',$('#port').value,+$('#baud').value,+$('#rate').value)}catch(e){toast(e.message,true)}};
$('#manual').onclick=()=>api('command','manual').catch(e=>toast(e.message,true));$('#arm').onclick=()=>openModal('arm-dialog');$('#confirm-arm').onclick=()=>{api('command','arm').catch(e=>toast(e.message,true));$('#arm-dialog').close()};$('#disarm').onclick=()=>api('command','disarm').catch(e=>toast(e.message,true));$('#logs').onclick=()=>api('open_logs');
$('#gain').oninput=e=>{$('#gain-value').textContent=`${e.target.value}%`;api('set_gain',+e.target.value)};
+1
View File
@@ -46,6 +46,7 @@
<div class="model-tools">
<button id="load-model">Load STL</button>
<button id="model-style" disabled>Edges</button>
<input id="model-input" type="file" accept=".stl,model/stl,application/sla" hidden>
</div>
</div>
<div class="vehicle-name"><small>VEHICLE</small><strong id="vehicle">No heartbeat</strong><span>SimpleROV-3 • differential drive</span></div>
+8 -5
View File
@@ -65,15 +65,18 @@
addEventListener('resize',requestDraw);
window.rovModel = {
loadBuffer(buffer, name) {
vertices=parseStl(buffer);stage.classList.add('model-loaded');
document.querySelector('#model-style').disabled=false;
document.querySelector('#model-file-name').textContent=name;
yaw=-.55;pitch=-.42;zoom=1;requestDraw();
return {name,triangles:vertices.length/9};
},
load(payload) {
if (!payload?.data) throw new Error('The selected STL returned no mesh data');
const binary=atob(payload.data),bytes=new Uint8Array(binary.length);
for(let i=0;i<binary.length;i++)bytes[i]=binary.charCodeAt(i);
vertices=parseStl(bytes.buffer);stage.classList.add('model-loaded');
document.querySelector('#model-style').disabled=false;
document.querySelector('#model-file-name').textContent=payload.name;
yaw=-.55;pitch=-.42;zoom=1;requestDraw();
return {name:payload.name,triangles:vertices.length/9};
return this.loadBuffer(bytes.buffer,payload.name);
},
toggleStyle() { edgesOnly=!edgesOnly;requestDraw();return edgesOnly; }
};