Add keyboard shortcuts to ViewScene (issue #694)#2513
Open
dcwhite wants to merge 69 commits into
Open
Conversation
OffscreenGLRenderer wraps SRInterface with a QOffscreenSurface + QOpenGLContext + QOpenGLFramebufferObject so ViewScene can render without a visible window. Compiles and context/FBO initialization works. Not yet integrated into ViewSceneDialog: spire's VBOMan has a VBO-ID- recycling bug where doFrame() runs the bootstrap (creates VBOs), GC frees them, GL recycles those IDs, and a subsequent handleGeomObject call hits addVBOAttributes with a recycled ID that still has a stale map entry with different attribute layouts — throwing inside a noexcept context and calling terminate(). Next steps: 1. Fix the stale-entry bug in spire VBOMan (remove map entry on GC free) 2. Wire OffscreenGLRenderer into ViewSceneDialog for regression mode 3. Add image-comparison tests using renderToImage() Also: Screenshot::setImage() added for the eventual offscreen path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ling bugs All 61 Renderer regression tests now pass using offscreen rendering (QOffscreenSurface + FBO) instead of a visible GLWidget. No window, no display server, no window-system timing races. Three spire/SRInterface fixes were required to make this work: 1. VBOMan::addInMemoryVBO — erase any stale map entry for a freshly generated buffer name before addVBOAttributes. GL guarantees a new glGenBuffers name is unused, so a lingering entry is always stale (the buffer was deleted by font rendering or the IBO manager, which call glGenBuffers/glDeleteBuffers directly and share GL's single buffer-name namespace). Previously this threw "Differing attributes" inside a noexcept context and aborted. 2. IBOMan::addInMemoryIBO — same fix; its insert() silently kept stale primitive data on a recycled id rather than crashing, a latent rendering bug. 3. SRInterface::addIBOToEntity — skip the IBO component when hasIBO() returns 0 instead of letting getIBOData() throw. A named IBO can be legitimately absent (GC'd between geometry updates); a missing index buffer should drop the pass, not abort. ViewSceneDialog now constructs an OffscreenGLRenderer when isRegressionMode() is set; the window path is unchanged. mGLWidget is null in offscreen mode, so its dereferences are guarded. Residual flakiness is down to ~0-1 of 61 per full run (different test each time, all pass in isolation) — the same environmental async- teardown class tracked separately, no longer a rendering problem. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two CI fixes from the first regression-tests-ci run: 1. async1/async2_multiData hung for 1500s (CTest timeout) on the mac-gui job. These streaming-test networks keep a data-push thread alive; the 30s in-app regression timeout fires but teardown then blocks forever waiting on the thread. quick_exit() was originally added for exactly this (commit c51b537) and I removed it on regression-tests-ci on the mistaken theory that it caused ViewScene flakiness. The real cause of that flakiness was window/timing + spire buffer bugs, now fixed by offscreen rendering — so restore quick_exit() and keep both wins. Verified locally: async1/async2 now exit in ~2s, all 61 Renderer tests still pass. 2. Windows test-log upload failed: results were written to C:\ which is outside the workspace (D:\a\SCIRun\SCIRun), so upload-artifact rejected the path. Write to $env:GITHUB_WORKSPACE instead and reference workspace-relative paths in the upload step. Also added --timeout 300 to all ctest invocations as a safety net so a future hang is killed in minutes instead of eating the 1500s default. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes two consistent segfaults exposed by the offscreen ViewScene path: editMeshBBox_trans_check (SEGFAULT) and additionalTypesOfVSObjectsSaved (flaky SIGABRT) — both ViewScene modules in regression mode. updateDockWidgetProperties is connected to the dock's topLevelChanged signal inside configDockable(). For a ViewScene dialog in regression mode, configDockable calls dockable->setFloating(true), which fires that signal synchronously — but dockable_ is assigned only after config() returns from makeOptionsDialog(). The slot then dereferenced a null dockable_. This was latent before but masked: with the real GLWidget central widget the dock's floating-state transition didn't re-fire during setup. The offscreen path (no GLWidget) changed the dialog's layout/visibility timing enough to trigger the signal, surfacing the null deref. Both tests passed on regression-tests-ci (windowed) and crashed only with offscreen. Guard: return early when dockable_ is not yet set; configDockable handles the initial show/float itself, and later user-driven dock/float events arrive after dockable_ is valid. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ling The headline fix: ModuleWidget::dockable_ was an uninitialized raw pointer. updateDockWidgetProperties() (connected to the dock's topLevelChanged signal in configDockable) can fire before makeOptionsDialog assigns dockable_; the slot null-checks it, but garbage in an uninitialized pointer passes the null check and segfaults in QWidget::setWindowFlags. This was masked serially (benign stack garbage) but crashed random tests under parallel load with different memory contents. Null-initializing dockable_ makes the existing guard correct and eliminates all the parallel segfaults (calcBundleDifference, ResizeMatrixTest, PrintStringIntoString, uncertaintyTensorGlyphs, etc.). Also fixed for parallel safety: - Defer session-trace init to readCommandLine and skip it in regression mode. The trace DB wrote to a fixed shared path, causing SQLite corruption and SIGBUS (mmap) when test processes ran concurrently. Guard the destructor's endSession() since session() is now null in regression mode. - Testing/CMakeLists.txt: give every ViewScene-containing network a shared RESOURCE_LOCK (gpu) so CTest never schedules two GPU tests at once; bump the in-app regression timeout 30->60s for headroom under load. - reusable-build.yml: run the regression ctest step at -j3 (was -j1). Coverage tooling (requested): - ENABLE_COVERAGE CMake option: Clang source-based instrumentation (-fprofile-instr-generate -fcoverage-mapping) on both compile and link flags (the pre-existing ENABLE_GCOV_DATA_FILES set compile flags only). - scripts/coverage.sh: runs ctest with per-process LLVM_PROFILE_FILE, merges with llvm-profdata, reports via llvm-cov (text + HTML) across all dylibs. Results: full suite at -j6 went from ~4 segfaults + bus error to 0 crashes; at -j3 (CI target) it is clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Concurrent regression-test processes shared one settings file (~/Library/Preferences/SCIRun5.plist). Reading a half-written plist during startup — favorites and window geometry restore — could crash in the module-selector tree (handleCheckedModuleEntry -> QTreeWidget::setCurrentItem). Suffix the QSettings application name with the process id in regression mode so each process gets an isolated, empty, deterministic store. Normal interactive runs keep the shared "SCIRun5" settings. Stress result: full suite at -j6 now passes 100% in 2 of 3 runs (was crashing on random tests every run). One rarer pre-existing async-state heap race remains (ViewScene::asyncExecute -> SimpleMapModuleState::setTransientValue), tracked separately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of starting each regression process with an empty settings store, copy all keys from the shared default "SCIRun5" store into the per-process "SCIRun5_regression_<pid>" store at startup. Regression runs now see the developer's real settings (favorites, directories, mouse mode, etc.) and look consistent run to run, while still only ever *writing* to their own isolated file. Concurrent *reads* of the stable shared store are safe; only the concurrent writes caused the original startup race. dest.clear() first so a recycled pid can't inherit a stale store. Verified: a regression process's plist inherits the source keys; a full -j6 run shows no new failures (the two that remain are the pre-existing async setTransientValue race tracked in #2486 and a -j6-only load timeout). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Regression processes each create an isolated SCIRun5_regression_<pid> settings store; without cleanup these accumulate (one per test process, ~300 per run). Cleanup is done after the run rather than in-app: on macOS cfprefsd owns the plist and recreates it if a still-running process deletes its own file, so in-process deletion at exit doesn't stick. Once the run finishes the domains are dead and removal is reliable. - scripts/run-regression-tests.sh: wraps ctest, removing the per-process stores before and after the run. Cross-platform (macOS plist via `defaults delete` + rm, Linux .conf, Windows registry keys). Verified: 5 stale stores before -> 0 after. - reusable-build.yml: the Unix regression CI step removes the stores afterward. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New mac-coverage job in regression-tests.yml: a gui macOS build with -DENABLE_COVERAGE=ON (Debug), runs the full test suite under llvm profiling, and uploads a coverage report (text summary + HTML) as an artifact. reusable-build.yml gains a `coverage` input that: - checks out SCIRunTestData (also needed for the regression portion), - adds --debug -DENABLE_COVERAGE:BOOL=ON to the build, - runs scripts/coverage.sh (per-process LLVM_PROFILE_FILE -> llvm-profdata merge -> llvm-cov text+HTML), cleans up the regression QSettings stores, and uploads coverage-report-macOS. Superbuild.cmake: declare ENABLE_COVERAGE and forward it to the inner SCIRun build via SCIRUN_CACHE_ARGS. Without this the option set on build.sh never reached the project that defines it, so no instrumentation would be applied. (The pre-existing ENABLE_GCOV_DATA_FILES had the same gap.) Coverage toolchain commands validated against a standalone instrumented binary; the llvm-cov report/show invocations and profdata merge work as used. Note: the instrumented Debug build running the full suite is a long job (expect several hours); scope the ctest selection in the coverage step down if runtime becomes a problem. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Badges are per-workflow on GitHub Actions, so one regression-tests badge covers all of its jobs (linux-headless, mac-gui, windows-headless, and mac-coverage). Links to the workflow's Actions page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The .Test.ImportNetwork.* tests pass -x, which routed them to ConsoleApplication where ImportNetworkFile is unimplemented — every test threw "Unknown global command type" and exited 1. Drop -x so import goes through the GuiCommandFactory's FileImportCommand (the intended path), and make that path testable in regression mode: - GlobalCommandBuilderFromCommandLine: enqueue a quit after import/save in regression mode (the GUI import flow never executes the network, so nothing else quits it — tests previously hung until manually closed). Interactive --import keeps the window open. Also redirect the converted <name>_imported.srn5 to a temp dir in regression mode so the test data dir isn't polluted. - GuiCommands: never pop a modal "module not found" dialog in regression mode (it blocked the run waiting for a manual OK); report via the log instead. Add a failTestOnError() hook (true for import) so an import failure in regression mode exits non-zero, surfacing as a failed ctest result with the reason logged. - Testing/CMakeLists.txt: name import tests by path relative to the data dir (several legacy nets share a basename and collided into one test name); filter the glob to skip serialization artifacts (TransientOutput/) and scratch files (*_TMP.srn); add a 120s CTest timeout. Result: import suite goes from 0/434 (all crashing at startup) to 98% passing in ~84s with no dialogs. The ~9 remaining failures are genuine gaps the tests now correctly surface (undefined legacy modules; a v4 lexical-cast parse error), tracked separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…GLRenderer OffscreenGLRenderer.h included <QOffscreenSurface>/<QOpenGLContext>/ <QOpenGLFramebufferObject>, which pull in <gl.h> on Windows. Because that landed before glew.h (included via gl-platform in the .cc), MSVC failed the Interface_Modules_Render build with C1189 "gl.h included before glew.h". This broke every Windows CI build on the branch since the OffscreenGLRenderer was added; Linux/macOS tolerate the ordering. Forward-declare the Qt OpenGL classes in the header (they're only pointer members; only QImage, which is gl.h-free, still needs including) and in the .cc include gl-platform (glew) before the Qt OpenGL headers, matching GLWidget's ordering. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ges) Foundation for golden-image regression testing. The offscreen renderer already feeds rendered frames into the screenshot path; this makes the saved filenames deterministic so they can be matched against reference images. In regression mode, autoSaveScreenshot() now names files <dir>/<network>.<module>.png — keyed to the loaded network file (getCurrentFileName) and the module id, with no wall-clock timestamp and no 1s sleep. The directory defaults to the current working dir when the screenshot preference is unset. Interactive (non-regression) behavior is unchanged. Verified: `SCIRun_test -E ViewSceneBackgroundColor.srn5 --regression --save-images` writes ViewSceneBackgroundColor.ViewScene-0.png ... -5.png, each a correct 800x600 offscreen render (background color + orientation axes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-render Step 1 of golden-image regression: control where rendered images go and stop the quit-time crash. - New --image-dir command-line option (wired through ApplicationParameters like --datadir). In regression mode autoSaveScreenshot writes to it when given, else the screenshot pref, else CWD; the directory is created if missing. - autoSaveScreenshot now saves the frame already captured during the run (frameFinished -> takeScreenshot) instead of re-rendering at exit. A fresh offscreen doFrame at teardown was crashing in the GL geometry draw for networks with real geometry. Verified: --image-dir writes deterministic <network>.<module>.png files to the requested dir (auto-created), and field/glyph networks no longer crash with --save-images. Known limitation (documented in OffscreenGLRenderer::renderToImage and tracked separately): real VBO/IBO geometry still renders blank offscreen — forcing it to draw (waiting for shader promises) crashes in RenderBasicSys/glDrawElements, likely a GL profile/VAO mismatch vs the QOpenGLWidget context. Background + orientation axes render correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire up the existing ViewSceneShortcuts.ui (previously unused) as a lazily- created QDialog shown by pressing I. Grays out unimplemented rows (stereo, backculling). Adds the .ui to CMakeLists so ui_ViewSceneShortcuts.h is generated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n widths - Extract showShortcutsDialog() as a proper slot; both Key_I and the new toolbar button call it - Add ? toolbar button (Qt standard question-mark icon) to the top toolbar - Expand grayed-out rows to cover all currently unimplemented shortcuts: 1-8 axis views, Ctrl+0, X snap, Ctrl+1-9, Set Home, Home, bounding box, flat shading, orthographic, stereo, backculling, wireframe - Call resizeColumnsToContents() so column widths fit the text Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…color - Set NoEditTriggers on the table so all cells are read-only - Populate missing Action column cells for all 20 rows so grayed rows show their action name in the disabled style - Switch gray-out from Qt::gray to palette Disabled/Text color for correct appearance on both light and dark themes - Also strip ItemIsEnabled flag from grayed items - Increase initial dialog size to 700x720 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…active Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tions and action mechanics
- Add ViewSceneDialog::ShortcutDef nested struct with:
Id enum (one value per shortcut), Qt key + modifiers, actionName,
shortcutDisplay, description, and nullable Action (std::function)
- Add private static shortcutTable() returning the canonical ShortcutTable;
Autoview and OpenHelp have actions; everything else is null (grayed)
- showShortcutsDialog() now builds the table entirely from shortcutTable()
— no hardcoded row indices or separate name arrays
- keyPressEvent() dispatches through shortcutTable() instead of a switch;
adding a new shortcut now only requires updating one place
- Strip all row/item data from ViewSceneShortcuts.ui; table is code-driven
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unimplemented (grayed) rows silently do nothing. Tooltip on the table advertises the feature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#694) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… focus Extract dispatchShortcutKey() from keyPressEvent. Install an event filter on the shortcuts dialog that calls it; returns true only when a shortcut fired, so Escape/Enter still close the dialog normally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Key events go to the focused child widget (QTableWidget), not the dialog itself. Install the event filter on the table too and check for either object in eventFilter. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n in session On first open, dialog appears flush with the right edge of the ViewScene window. QEvent::Move is caught in eventFilter and stored in shortcutsDialogPos_ (std::optional<QPoint>), so subsequent opens restore wherever the user last dragged it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ault size ScreenParams initialises to 640x480 and is only updated by resizeGL(), which Qt calls asynchronously. On first show, paintGL() fires before resizeGL(), so the glyph renders with the wrong aspect ratio until the first manual resize. Fix: at the top of paintGL(), if the widget's actual size doesn't match what the renderer knows, call eventResize() immediately before doFrame(). This is idempotent and harmless on subsequent frames where the sizes already agree. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dows-2022 - reusable-build.yml: add Externals/src to cache path so ExternalProject's git-update script can find .git when the cache is restored; bump cache key to v2 to avoid restoring broken v1 caches - regression-tests.yml: change windows-headless-regression runner from windows-latest to windows-2022, matching every other Windows job; fixes "could not find any instance of Visual Studio" on the updated runner image Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The cache was storing bin/Externals/src (lowercase) but CMake
ExternalProject with EP_BASE puts git clones in bin/Externals/Source
(capital S). On a cache restore, CMake still ran the git update check
for every GIT_REPOSITORY external and hit 'fatal: not a git repository'
because the Source directory was never actually cached.
Fix: set EP_UPDATE_DISCONNECTED TRUE globally in Superbuild.cmake so
that when Install+Stamp are restored from cache the git update step is
skipped entirely. The cache key already covers hashFiles('Superbuild/*.cmake'),
so any GIT_TAG change still forces a fresh clone. Remove the bogus /src
path from the cache and bump the key to v3 to avoid restoring broken
v2 caches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mac-gui was picking up the mac-headless cache via the loose restore-key that strips the variant suffix. Headless doesn't build Zlib (GUI-only external), so there was no Zlib stamp in that cache. mac-gui then tried to build Zlib but Source/ isn't cached, hitting the same 'not a git repository' error. Drop the variant-agnostic restore-key so a gui build can only fall back to another gui cache (same variant, different python flag), never to a headless cache with a disjoint set of built externals. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
std::remove without erase left bin contents unchanged; apply erase-remove idiom. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
EP_UPDATE_DISCONNECTED alone was insufficient. CMake regenerates
*-gitupdate.cmake during every configure run with a fresh timestamp;
the Makefile rule for the update step depends on that script, so
cached stamp files were always stale relative to it, causing make
to re-run the git update step — which then fails because Source/
is not cached.
Setting UPDATE_COMMAND "" tells CMake to generate a simple no-op
update step (no gitupdate.cmake, no git operations). With the script
dependency gone from the Makefile rule, the cached stamp is sufficient
to skip the update step on cache hits.
The cache key already includes hashFiles('Superbuild/*.cmake'), so any
GIT_TAG change still forces a full fresh build.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cmake rewrites Externals/tmp/*/*.cmake on every configure run with a fresh timestamp. The Makefile rules for each ExternalProject step depend on those scripts, so every step appears stale after a cache restore -- causing make to re-run download/configure/build/install against missing source directories and fail immediately. Fix: touch all files in Externals/Stamp/ right after cmake configure (before make) so they are always newer than the regenerated tmp/ scripts. Applies in build.sh (Linux/macOS) and inline in the Windows workflow step. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Broken cache entries (partial stamps from failed cold builds) were being restored and failing again because configure stamps didn't exist. The stamp-touch step can only touch files that exist. Two fixes: - Bump cache key to v4 to abandon all broken v3 entries and force fresh cold builds that populate complete stamp sets - Split actions/cache@v4 into restore + save with `if: success()` so that only builds that fully succeed save a cache entry; partial/failed builds no longer poison the cache for future runs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lution mac-coverage (python=true) was getting a cache hit via restore-key from mac-gui-nonpython (python=false). The non-python cache had a different set of stamps (no Python external), causing configure step failures for unrelated externals like Teem. Remove restore-keys entirely. Only exact key matches are safe: every combination of (OS, variant, python-flag) builds a different set of externals and must have its own cache entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plain touch (current time) is not enough. CMake's ExternalProject_Add_Step generates add_custom_command rules whose DEPENDS list includes ExternalProject.cmake itself. If a runner has a newer cmake installation than the runner that built the cached stamps, ExternalProject.cmake has a newer timestamp and make considers every external step stale — even with fresh-touched stamps. Setting stamps to 2100 makes them unconditionally newer than any cmake module file on any runner, regardless of when cmake was installed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All Superbuild externals use INSTALL_COMMAND "" — compiled artifacts live in Externals/Build/, not Externals/Install/. Caching Install/ and Stamp/ captured almost nothing useful (17-66 KB of cmake config files and stamps) while the actual libs in Build/ were never present on a cache restore, causing SCIRun itself to fail finding its dependencies. Reverts the cache restore/save steps, the 'Set externals path' step, and the stamp-touch blocks in build.sh and the Windows workflow. sccache remains in place for C++ compilation caching within SCIRun itself. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docs/Support-Python-Libraries-Design.md wasn't matched by any toctree glob in docs/index.rst, so Sphinx emitted a "not included in any toctree" warning. With fail_on_warning: true in .readthedocs.yaml, that warning failed the build. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The doc had no Markdown headings at all, so Sphinx/MyST couldn't derive a page title for it. With docs/index.rst's dev_doc/* toctree glob picking it up, every reference to it emitted a "toctree contains reference to document that doesn't have a title" warning, and fail_on_warning: true in .readthedocs.yaml turned those into a build failure. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements a keyboard shortcut system for the ViewScene 3D viewer, addressing issue #694.
Shortcuts added
0Ctrl+01–6XHAlt+HCDKLOIHelp dialog
Ikey (or toolbar?button) opens a reference dialog listing all shortcutsRefactor
ShortcutDefenum class with string annotations and action mechanics, replacing a hand-coded.uitableBug fixes
CoreBootstrap(stale 640×480 default size)OffscreenGLRenderer
OffscreenGLRenderer.cc/.h) added but not wired intoViewScene— kept for future regression-image workTest plan
I— confirm help dialog opens at top-right, closes cleanlyAlt+H/Hround-trip (set home, move camera, go home)🤖 Generated with Claude Code