diff --git a/src/ifcviewer-web/CMakeLists.txt b/src/ifcviewer-web/CMakeLists.txt index 1a5b2ce29f..13678a7ac0 100644 --- a/src/ifcviewer-web/CMakeLists.txt +++ b/src/ifcviewer-web/CMakeLists.txt @@ -108,13 +108,15 @@ target_link_options(IfcViewerWeb PRIVATE # sidecar via HTTP Range; _ifcv_on_range_done / _ifcv_source_ready are the # JS→C completion callbacks for a landed range / a resolved URL size. # The _ifcv_{get,set,apply}_* family is the scripting API (camera, selection, - # visibility, colour override, object enumeration) that web/ifcviewer.js - # wraps; _malloc/_free let it marshal id arrays into the wasm heap. + # visibility, colour override, object enumeration, mouse nav preset) that + # web/ifcviewer.js wraps; _malloc/_free let it marshal id arrays into the + # wasm heap. # EMSCRIPTEN_KEEPALIVE alone keeps the symbols in the binary but doesn't # add them to Module. ccall lets the host page (web/ifcviewer.js) pass a JS string (the ?model # URL) to load_sidecar_from_url_c without manual heap marshalling. - "-sEXPORTED_FUNCTIONS=['_main','_malloc','_free','_raf_tick_c','_load_sidecar_from_source_c','_clear_scene_c','_ifcv_on_range_done','_ifcv_chunks_resident_c','_ifcv_chunks_total_c','_ifcv_model_count_c','_ifcv_model_resident_c','_ifcv_model_total_c','_ifcv_bytes_total_c','_ifcv_bytes_needed_c','_ifcv_bytes_loaded_c','_view_all_c','_frame_selection_c','_toggle_projection_c','_projection_is_ortho_c','_standard_view_c','_toggle_fly_c','_fly_is_active_c','_hide_selected_c','_isolate_selected_c','_show_all_c','_hide_all_c','_toggle_xray_c','_xray_is_active_c','_toggle_section_c','_clear_section_c','_section_is_active_c','_ifcv_get_camera_c','_ifcv_set_camera_c','_ifcv_set_ortho_c','_ifcv_get_selection_c','_ifcv_get_active_object_c','_ifcv_apply_selection_c','_ifcv_set_visible_c','_ifcv_get_hidden_c','_ifcv_set_color_c','_ifcv_clear_colors_c','_ifcv_request_objects_c']" - # ccall: the host page (web/ifcviewer.js) passes the ?model URL string to load_sidecar_from_url_c. + "-sEXPORTED_FUNCTIONS=['_main','_malloc','_free','_raf_tick_c','_load_sidecar_from_source_c','_clear_scene_c','_ifcv_on_range_done','_ifcv_chunks_resident_c','_ifcv_chunks_total_c','_ifcv_model_count_c','_ifcv_model_resident_c','_ifcv_model_total_c','_ifcv_bytes_total_c','_ifcv_bytes_needed_c','_ifcv_bytes_loaded_c','_view_all_c','_frame_selection_c','_toggle_projection_c','_projection_is_ortho_c','_standard_view_c','_toggle_fly_c','_fly_is_active_c','_hide_selected_c','_isolate_selected_c','_show_all_c','_hide_all_c','_toggle_xray_c','_xray_is_active_c','_toggle_section_c','_clear_section_c','_section_is_active_c','_ifcv_get_camera_c','_ifcv_set_camera_c','_ifcv_set_ortho_c','_ifcv_set_nav_preset_c','_ifcv_get_selection_c','_ifcv_get_active_object_c','_ifcv_apply_selection_c','_ifcv_set_visible_c','_ifcv_get_hidden_c','_ifcv_set_color_c','_ifcv_clear_colors_c','_ifcv_request_objects_c']" + # ccall: the host page (web/ifcviewer.js) passes the ?model URL string to load_sidecar_from_url_c, + # and the nav-preset name to ifcv_set_nav_preset_c. # HEAPU8: lets tooling/tests read the wasm heap size (e.g. to verify a large # sidecar streams by range instead of loading whole). HEAPU32/HEAPF32: the # scripting API marshals object-id arrays and the camera state through them. diff --git a/src/ifcviewer-web/main_web.cpp b/src/ifcviewer-web/main_web.cpp index b2aac97ab8..99cdd9c0af 100644 --- a/src/ifcviewer-web/main_web.cpp +++ b/src/ifcviewer-web/main_web.cpp @@ -627,6 +627,17 @@ extern "C" EMSCRIPTEN_KEEPALIVE void ifcv_set_ortho_c(int on) { if (bool(on) != g_app->core.projectionOrtho()) g_app->core.toggleProjection(); } +// Mouse navigation scheme: "blender" | "rhino" | "revit" | "web". The preset +// only rewrites the button/modifier table classifyPress reads, so unlike the +// rest of the scripting API it does not need the GPU app to be live — a host +// page can pick its scheme the moment the module resolves. Unknown names fall +// back to blender inside the core; web/ifcviewer.js rejects them before they +// get here so a typo is an error rather than a silent scheme change. +extern "C" EMSCRIPTEN_KEEPALIVE void ifcv_set_nav_preset_c(const char* name) { + if (!g_app || !name) return; + g_app->core.setNavPreset(name); +} + // Selection. extern "C" EMSCRIPTEN_KEEPALIVE int ifcv_get_selection_c(std::uint32_t* out, int max) { if (!g_app || !g_app->ready) return 0; @@ -778,6 +789,8 @@ int main(int /*argc*/, char** /*argv*/) { Log::info() << "ifcviewer-web: starting"; g_app = new AppState(); // Default to the web mouse scheme: LMB orbit, MMB pan, RMB select/marquee. + // Host pages override it with ifcv_set_nav_preset_c (IfcViewer.create's + // `navPreset` option) — e.g. "blender" for MMB-orbit. g_app->core.setNavPreset("web"); g_app->core.initWgpuAsyncWeb([](bool ok) { if (!ok) { diff --git a/src/ifcviewer-web/web/ifcviewer.js b/src/ifcviewer-web/web/ifcviewer.js index 62931aab46..fb1d6c8cdc 100644 --- a/src/ifcviewer-web/web/ifcviewer.js +++ b/src/ifcviewer-web/web/ifcviewer.js @@ -5,7 +5,8 @@ // // // // // 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; }