mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
e3722cf243
Adds an "Open .ifcview…" button that opens the browser's native file chooser. The picked file's bytes are written into MEMFS and a new exported entry point, load_uploaded_model_c, reads them back via the existing loadSidecarFromPath: it resetScene()s the current model (replace, not append), loads the sidecar, and viewAll()s it. Geometry becomes resident over subsequent frames through render()'s inline driveStreamingLoads, same as the embedded sample. Deliberately a file picker, not drag-drop: browser file drag-drop needs an X11 drag source (a file manager) to drag *from*, which a minimal WM (ratpoison) doesn't provide. The native chooser is WM-independent. Exports _load_uploaded_model_c and the FS runtime method; URL/byte-range fetch of remote models stays for #88. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
116 lines
5.0 KiB
HTML
116 lines
5.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>IfcViewer (web)</title>
|
|
<style>
|
|
html, body { margin: 0; height: 100%; background: #0f1117; color: #c8ccd6;
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
|
#viewer-canvas { display: block; width: 100vw; height: 100vh; outline: none;
|
|
background: #1a1d24; }
|
|
#status { position: fixed; top: 8px; left: 12px; right: 12px;
|
|
max-height: 80vh; overflow-y: auto; font-size: 11px;
|
|
font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
|
|
background: rgba(20,22,28,.78); padding: 6px 10px; border-radius: 4px;
|
|
white-space: pre-wrap; pointer-events: auto; }
|
|
#status.error { background: rgba(120,30,30,.85); color: #fff; }
|
|
#open-btn { position: fixed; top: 8px; right: 12px; z-index: 10;
|
|
background: #2b6cb0; color: #fff; border: none; padding: 6px 12px;
|
|
border-radius: 4px; font-size: 12px; cursor: pointer; }
|
|
#open-btn:hover { background: #3182ce; }
|
|
#file-input { display: none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<canvas id="viewer-canvas" width="1280" height="800"></canvas>
|
|
<button id="open-btn">Open .ifcview…</button>
|
|
<input id="file-input" type="file" accept=".ifcview">
|
|
<div id="status">Starting…</div>
|
|
<script>
|
|
// Emscripten Module hook: route stderr to the status overlay so any
|
|
// wgpu init failure (no WebGPU, canvas missing, etc.) is visible
|
|
// without opening devtools.
|
|
var statusEl = document.getElementById('status');
|
|
var Module = {
|
|
canvas: document.getElementById('viewer-canvas'),
|
|
// Keep the wasm runtime alive after main() returns so the
|
|
// Dawn-web RequestAdapter/RequestDevice promise callbacks
|
|
// (queued from main) actually land. Without this flag the
|
|
// runtime tears down at end-of-main and the callbacks never
|
|
// fire — symptom: adapter cb fires (synchronous-ish on the
|
|
// first JS tick) but device cb does not. EXIT_RUNTIME=0 in
|
|
// CMakeLists is the build-time half; this is the runtime half.
|
|
noExitRuntime: true,
|
|
print: function(t) { console.log(t); },
|
|
printErr: function(t) {
|
|
console.warn(t);
|
|
// Accumulate every stderr line so the init sequence is visible
|
|
// even when the wasm hangs partway. The first time something is
|
|
// printed we drop the "Starting…" placeholder.
|
|
if (statusEl.textContent === 'Starting…' ||
|
|
statusEl.textContent === 'wasm loaded — waiting for WebGPU') {
|
|
statusEl.textContent = '';
|
|
}
|
|
statusEl.textContent += t + '\n';
|
|
statusEl.scrollTop = statusEl.scrollHeight;
|
|
if (/fail|error|null/i.test(t)) statusEl.classList.add('error');
|
|
},
|
|
onRuntimeInitialized: function() {
|
|
statusEl.textContent = 'wasm loaded — waiting for WebGPU';
|
|
// RAF loop. Polls for Module._app_ptr (set by C once the wgpu
|
|
// device callback completes init) and only then drives the C
|
|
// tick. Living in shell.html means the loop is set up from a
|
|
// clean JS top-level, NOT nested inside Dawn-web's Promise.then
|
|
// chain — which is the configuration that stalls device-callback
|
|
// delivery (verified during web bring-up).
|
|
function shellTick() {
|
|
if (Module._app_ptr && Module._raf_tick_c) {
|
|
Module._raf_tick_c(Module._app_ptr);
|
|
}
|
|
requestAnimationFrame(shellTick);
|
|
}
|
|
requestAnimationFrame(shellTick);
|
|
}
|
|
};
|
|
if (!navigator.gpu) {
|
|
statusEl.textContent = 'navigator.gpu is missing — open in a browser with WebGPU enabled';
|
|
statusEl.classList.add('error');
|
|
}
|
|
|
|
// File-browse loading. The picked file's bytes are written into MEMFS
|
|
// (the same virtual FS the wasm fopen()s) and then load_uploaded_model_c
|
|
// is invoked to read + render it. No drag-drop: that needs an X11 drag
|
|
// source (a file manager), which a minimal WM may not provide; the
|
|
// native file chooser this button opens is WM-independent.
|
|
var openBtn = document.getElementById('open-btn');
|
|
var fileInput = document.getElementById('file-input');
|
|
openBtn.addEventListener('click', function() { fileInput.click(); });
|
|
fileInput.addEventListener('change', function(ev) {
|
|
var f = ev.target.files && ev.target.files[0];
|
|
if (!f) return;
|
|
if (!Module.FS || !Module._load_uploaded_model_c) {
|
|
statusEl.textContent += 'viewer not ready yet — wait for WebGPU init\n';
|
|
return;
|
|
}
|
|
var reader = new FileReader();
|
|
reader.onload = function() {
|
|
try {
|
|
var bytes = new Uint8Array(reader.result);
|
|
try { Module.FS.mkdir('/uploads'); } catch (e) { /* already exists */ }
|
|
Module.FS.writeFile('/uploads/model.ifcview', bytes);
|
|
Module._load_uploaded_model_c();
|
|
} catch (e) {
|
|
statusEl.textContent += 'load failed: ' + e + '\n';
|
|
statusEl.classList.add('error');
|
|
}
|
|
fileInput.value = ''; // let the same file be re-picked
|
|
};
|
|
reader.readAsArrayBuffer(f);
|
|
});
|
|
</script>
|
|
<!-- IfcViewerWeb.js is emitted alongside this shell by the emcc build;
|
|
`--shell-file` injects this HTML around it. -->
|
|
{{{ SCRIPT }}}
|
|
</body>
|
|
</html>
|