ifcviewer-web: let host pages choose the mouse nav preset

ViewportCore has had a preset table (blender / rhino / revit / web) since the
nav bindings were shared with the desktop, and the web input handlers already
classify presses against it — but main() hard-coded "web" and nothing could
reach setNavPreset from JS, so every page was stuck on LMB-orbit.

Export ifcv_set_nav_preset_c and wrap it as IfcViewer.create's `navPreset`
option plus a setNavPreset() method. The preset is only the button/modifier
table, so it needs no GPU state: it applies as soon as the module resolves,
which means the first drag already uses the host's scheme rather than
flipping after a frame or two.

Names are validated in JS against the four the core knows. The core silently
falls back to blender for anything unrecognised, which would turn a typo into
a mystery change of scheme rather than an error.

The default stays "web", so existing pages and the smoke tests are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dion Moult
2026-07-28 15:44:37 +10:00
parent 6bcfb175a3
commit 8cc96127ce
3 changed files with 50 additions and 5 deletions
+31 -1
View File
@@ -5,7 +5,8 @@
// <script src="IfcViewerWeb.js"></script>
// <script src="ifcviewer.js"></script>
// <script>
// const viewer = await IfcViewer.create({ canvas: myCanvas });
// const viewer = await IfcViewer.create({ canvas: myCanvas,
// navPreset: 'blender' });
// await viewer.ready; // GPU app is live
// await viewer.addFile(file, { replace: true });
// await viewer.addUrl('/model.ifcview'); // appends (federation)
@@ -14,6 +15,7 @@
// viewer.setSelection(['3vB2YO$MX4xv5uCqZZG05x']);
// viewer.setColor(objects.filter(o => o.type === 'IfcWall'), '#ff8800');
// viewer.setCamera({ yaw: 45, pitch: 30 });
// viewer.setNavPreset('rhino'); // or switch schemes later
// </script>
//
// The canvas element MUST have id="viewer-canvas" — the wasm side hard-codes
@@ -41,6 +43,10 @@
return cr ? parseInt(cr.split('/')[1] || '0', 10) : 0;
}
// Mouse navigation schemes the wasm's classifyPress understands. Named so a
// typo is an error here rather than a silent fall-back to blender in the core.
const NAV_PRESETS = ['blender', 'rhino', 'revit', 'web'];
// Pack a colour into the u32 the shader unpacks: 0xAABBGGRR. Accepts
// '#rgb' / '#rrggbb' / '#rrggbbaa', or {r, g, b, a} with 0-255 channels
// (alpha defaulting to opaque). An alpha of 0 is the "no override" sentinel
@@ -314,6 +320,23 @@
if (c.ortho !== undefined) Module._ifcv_set_ortho_c(c.ortho ? 1 : 0);
},
// ---- Navigation ------------------------------------------------------
// Which mouse buttons orbit / pan / select, as one of NAV_PRESETS:
// blender MMB orbit · Shift+MMB pan · LMB select
// rhino RMB orbit · Shift+RMB pan · LMB select
// revit Shift+MMB orbit · MMB pan · LMB select
// web LMB orbit · MMB pan · RMB select (the default)
// The wheel zooms under all four. Also settable up-front as the
// `navPreset` option to create().
setNavPreset: function (name) {
if (NAV_PRESETS.indexOf(name) < 0) {
throw new Error('unknown nav preset: ' + name +
' (expected one of ' + NAV_PRESETS.join(', ') + ')');
}
Module.ccall('ifcv_set_nav_preset_c', null, ['string'], [name]);
},
// id: 0 Front, 1 Back, 2 Left, 3 Right, 4 Top, 5 Bottom.
setStandardView: function (id) { Module._standard_view_c(id | 0); },
viewAll: function () { Module._view_all_c(); },
@@ -447,6 +470,13 @@
Module._load_sidecar_from_source_c(await registerUrl(url));
},
};
// The nav preset is pure button-table state on the wasm side, so it can be
// set the moment main() has run (which is by the time `factory` resolved) —
// no need to wait on `ready`. Doing it here means the very first drag uses
// the host's scheme rather than the "web" default for a frame or two.
if (opts.navPreset) api.setNavPreset(opts.navPreset);
return api;
}