(sec-dev-guimanualcheck)= # Manual GUI check before a release The tests check what the renderer draws (graphics data per item, low-resolution raytracer images, #2704). **Nothing automatic checks that a person can use the render window and the dialogs**: keys, mouse, tkinter windows, focus, fonts, window placement. That is this list. - **Once per release on each of Windows, Ubuntu and macOS**; one person, about one hour. - Do only what is written; if something looks wrong, note it and go on. - Not checked here: what each setting draws (automatic). Checked here: that the **key or the dialog reaches the renderer** - one or two settings per path. ## 0. Preparation (5 min) 1. Install the release candidate wheel into a clean environment (`pip install exudyn--...whl`). 2. Run `python -m exudyn info` and paste the output into the report. 3. **Back up `~/.exudyn/config.json`** if it exists, then delete it, so that you test the defaults. Some checks below write this file; restore your backup at the end. 4. Copy `python/testing/guiManualCheckModel.py`, `python/Examples/nMassOscillatorEigenmodes.py` and `python/Examples/graphicsCurvedShapes.py` into an empty working directory, and run everything from there in a terminal (not from an IDE): images and files are written relative to it. 5. `python -m exudyn demo 2` - the renderer opens, the demo runs, the window closes (`demo` alone is demo 1, without graphics). If this fails, stop here and report it. **Which model**: sections 1 to 4 and 6 use `guiManualCheckModel.py`, K13 `graphicsCurvedShapes.py`, section 5 `nMassOscillatorEigenmodes.py`, O3 `mouseInteractionExample.py`; each section says it again. **Model used**: `guiManualCheckModel.py` - a mass point with spring-damper and force, and a 3D double pendulum of two rigid bodies with revolute joints; it has at least one node, body, connector, marker, load and sensor, and a sensor trace; the red mass is drawn as its node, the orange sphere on the second link with `general.sphereTiling`. It opens the renderer and **waits**; SPACE starts a real-time simulation of 120 s, Q stops it, then SolutionViewer and PlotSensor follow; the script ends when the plot windows are closed. Report each check as **OK / FAIL / n.a.** with the ID; for a FAIL, one line on what happened. ## 1. Render window: mouse and view keys (15 min) `python guiManualCheckModel.py` - the window opens, the model is not moving yet. | ID | action | expected | |---|---|---| | R1 | look at the window after start | model fully visible (zoom all), help hint shown for ~5 s, window title and status text readable (display scaling!) | | R2 | left mouse: press, drag, release | model moves with the mouse in the screen plane | | R3 | right mouse: press, drag, release | model rotates about the screen axes; release stops it | | R4 | mouse wheel up/down; with CTRL | zoom in/out; CTRL gives small steps | | R5 | left **click** (no drag) on the red mass; then on empty space | status line *Selected item: ...*; item highlighted for ~2 s; empty space: *no item selected* | | R6 | right **click** (no drag) on a pendulum link | read-only dialog *properties of <...>* opens with the item dictionary; ESCAPE closes it; render window still reacts afterwards | | R7 | keys `A`, `.` / `,` (and keypad `+` / `-` if present) | zoom all, zoom in, zoom out | | R8 | cursor keys; CTRL+cursor; SHIFT+cursor; ALT+LEFT/RIGHT | move; small move; rotate about screen x/y; rotate about screen z | | R9 | keypad 2/8, 4/6, 7/9 (if a keypad exists); same with CTRL | rotation about 1, 2, 3 axis; CTRL: small rotation | | R10 | CTRL+1 ... CTRL+7, then SHIFT+CTRL+1 | the standard views (1-2 plane, 1-3, 2-3, ..., 3D view); SHIFT: the same plane from behind; a message names the view | | R11 | pan with left mouse so that the pendulum joint is at the window center, press `O`, then rotate with right mouse | message *Set rotationCenterPoint ...*; the model now rotates about the new center | | R12 | `R`, wait 3 s, `R` again | automatic rotation of the view starts and stops | | R13 | F3, then left-click twice at two points (after CTRL+1) | mouse coordinates in the status line; second click shows `lastPos` and `dist`; F3 again switches off | | R14 | CTRL+F3 (macOS: n.a. if the system shortcut *Move focus to the Dock* is on; then V, **store model view**) | status line shows zoom / rotation / center; the console prints a `SC.renderer.SetModelView(...)` line | ## 2. Render window: item keys, simulation keys, raytracer, views (10 min) | ID | action | expected | |---|---|---| | K1 | `N`, `B`, `C`, `M`, `L`, `S` - each pressed twice; then `B` (bodies off) and `M` twice | nodes / bodies / connectors / markers / loads / sensors disappear and reappear; a message says *show ...: off/on*; the markers sit inside the bodies and are seen with the bodies off | | K2 | CTRL+`N`, CTRL+`B`, CTRL+`C`, CTRL+`M`, CTRL+`L`, CTRL+`S` | the numbers of that item kind appear/disappear; text readable, not hidden behind the bodies | | K3 | `T` pressed repeatedly (6-7 times) | cycles through transparent faces / face edges only / faces with edges / ... and back to the start; message states the mode | | K4 | **SPACE** | simulation starts in real time; the sensor trace of the pendulum tip is drawn | | K5 | SPACE again, wait, SPACE | simulation pauses and continues (message *switch pause on/off*) | | K6 | during simulation: `1`, `5`, `2` | update interval 20 ms (smooth), 100 s (frozen), 100 ms (default) - message each time | | K7 | CTRL+R, rotate a little, CTRL+R | raytraced image (shadows, slower update) with the sensor trace, then back to OpenGL | | K8 | **CTRL+V** | a second window (view 1) opens and shows the running model; mouse and keys work in it independently; CTRL+R there only affects that window | | K9 | in the view 1 window: `Q` | only view 1 closes, the simulation continues in the main window | | K10 | F2, then `N`, then F2, then `N` | message *ignore keys mode switched on*, the first `N` ignored; after F2 again (*... off*) `N` works | | K11 | `Q` | simulation stops; console prints *simulation finished ...*; the window stays and can still be rotated | | K12 | `V`: `openGL.light0.shadow` = `0.4`, `openGL.light0.useCameraFrame` = `True`, `view0.camera.modelCentricView` = `False`; close the dialog, rotate and zoom with the mouse | the shadow of the pendulum stays where the light puts it and follows the view smoothly - it does not flicker or appear and vanish with the view (#2308); set the three back | | K13 | run `graphicsCurvedShapes.py` (its own window): look at the rows; `V`: `openGL.advanced.curvedTriangleTilingAngle` = `90`, then `5`, then back to `15`; `T` until face edges show; CTRL+R and back; SPACE | the curved row (y=0) is smooth where the flat row (y=-3) shows facets, the red rims lie on the cylinder and the vase; at 90 the coarse elements show, at 5 a fine split - at once, without restarting; the face edges of the curved shapes are curved; the raytraced image shows the same shapes; the body at x=12 rotates with its rims and ring (#2709) | ## 3. Dialogs from the render window (15 min) Still in phase 3 of the model (after K11), or start the model again. | ID | action | expected | |---|---|---| | D1 | `H` | help window with mouse and key table; text readable, scrollable, ESCAPE closes it | | D2 | `X`, type `print(mbs)`, CTRL+RETURN | command window opens (may be **behind** the render window - note it); the console shows the system summary | | D3 | in the command window: `x = 42`, CTRL+RETURN; then `print(x)`, CTRL+RETURN; ESCAPE | prints 42 (assignments survive); window closes | | D4 | `X` then `1/0`, CTRL+RETURN | error printed in the console, the dialog and the renderer survive | | V1 | `V` | visualization settings dialog: tree with columns Name / Value / Type / Description, readable font, sensible column widths; render window blocked or updated while it is open (both are acceptable, note which) | | V2 | open `nodes`, click the value of `show`, choose `False` | nodes disappear **in the render window** immediately; the row is marked as changed | | V3 | double-click the same `nodes.show` value | toggles back to `True`; nodes reappear | | V4 | `nodes.defaultSize`: type `0.25`, RETURN | the red mass (a node) grows in the render window | | V5 | `nodes.tiling`: type `2`, RETURN; `general.sphereTiling`: type `2`, RETURN | first the red mass, then the orange sphere and the joint spheres become visibly coarse; set both back with **undo** | | V6 | `connectors.showJointAxes` = `True`; `bodies.showNumbers` = `True` | joint axes drawn; body numbers shown | | V6a | `connectors.drawSimplified`, `markers.drawSimplified`, `loads.drawSimplified`, `sensors.drawSimplified` = `False` (default `True`), then back | the spring windings become a tube; markers, loads and sensors drawn with their full symbols; back to lines and simple symbols | | V7 | enter an invalid value, e.g. `abc` into `nodes.defaultSize`; then `-1` into `general.circleTiling` | value rejected with a message, old value kept, dialog survives | | V8 | enum: `contour.outputVariable` - pick `Displacement` from the list; `contour.outputVariableComponent` = `1` | combo box shows short names (no `OutputVariableType.` prefix); bodies get a contour color and a color bar appears; set back to `_None` | | V9 | hover over a row and over a folder (wait 0.5 s) | tooltip with the description (folder: its class description); tooltip is **in front** of the dialog | | V10 | CTRL+F, type `trace`; RETURN; F3 | hits drop-down enabled; RETURN jumps to the first hit, F3 to the next | | V11 | select a row, press **copy line**, paste into an editor | e.g. `SC.visualizationSettings.nodes.defaultSize = 0.2` | | V12 | **diff to default**, **changes since start** | each opens a window **in front of** the dialog, listing exactly the changes made so far as code lines | | V13 | **undo** (twice), **revert**, **reset** | undo steps back whole states; revert returns to the state when the dialog opened; reset to defaults - render window follows each | | V14 | change `general.backgroundColor` to `[0.9,0.9,1,1]`, press **store settings**; in the window that lists what will be written, press **store** | the list shows the change; console says *stored 1 setting(s) in ...config.json* | | V15 | move/resize the dialog, press **store positions**, then **store** in the list; close with ESCAPE; `V` again | the dialog reopens at the stored position and size | | V16 | CTRL+mouse wheel over the tree | font size changes, row height and columns follow | | V17 | close the dialog with **close**, then `Q` / ESCAPE to leave phase 3 | render window closes; SolutionViewer (next section) starts | (After V14: the next start of any model uses the stored background color - confirmed at S1. Delete the file again at the end.) ## 4. SolutionViewer and PlotSensor (7 min) The model continues with `mbs.SolutionViewer()` automatically. | ID | action | expected | |---|---|---| | S1 | look at the renderer and the dialog | renderer reopens **with the previous view** and the background color stored in V14; dialog with sliders *Solution steps*, *Increment*, *update period*, run modes, record, *Make mp4* | | S2 | let it run; then **Static**; drag the *Solution steps* slider | animation plays; in static mode the model follows the slider | | S3 | *Increment* to 10; *update period* to max | animation faster (skips frames) / slower | | S4 | **One cycle**, **Run** | plays once and stops | | S5 | keys in the render window during the viewer (`B`, `V`) | still work; `V` opens the settings dialog (SPACE pauses a simulation, not the viewer: use **Stop**) | | S6 | *Record frames*, run one cycle, *No recording* | images `frame00000.png`, ... appear in the subfolder `images` of the working directory, whose absolute path the first frame prints, and they show the scene (not black); a failed write is reported in the console and in the render window; (*Make mp4* only if ffmpeg is installed - optional, see the section *Generating animations* of the GUI chapter) | | S7 | close the dialog (window close button, or `q` / ESCAPE in the dialog) | dialog and renderer close, script continues | | P1 | PlotSensor windows | two matplotlib figures: tip position x/y/z with legend and axis labels, and the spring force; windows can be zoomed, panned and closed; the script ends after closing them | ## 5. AnimateModes (5 min) `python nMassOscillatorEigenmodes.py` | ID | action | expected | |---|---|---| | A1 | dialog and renderer open | dialog with *Mode shape*, *Contour plot*, *Amplitude* (+ positive/negative), *update period*, run modes, mesh/faces, recording; renderer shows the masses | | A2 | **Run**; move the *Mode shape* slider; move *Amplitude*; switch *negative* | mode animates; the mode changes; amplitude grows/shrinks; motion inverts | | A3 | *Contour plot* = *Displacement*; *Static continuous* | each mass in one color (it only translates), the colors differ between the masses, with a color bar; the deformed shape stands still | | A4 | close the dialog with `q` in the dialog | dialog and renderer close, script ends without error or hang | ## 6. Quitting (3 min) | ID | action | expected | |---|---|---| | Q1 | `python guiManualCheckModel.py quitdialog`, SPACE, wait > 10 s, **ESCAPE** | dialog *WARNING - long running simulation!* in front; **No** keeps running; ESCAPE again, **Yes** stops the solver and closes the window; the script continues (SolutionViewer) or ends without hanging | | Q2 | in the SolutionViewer of the same run: close the render window with the window close button | viewer stops, no hang, no crash of the Python process | ## 7. Optional, if time is left | ID | action | expected | |---|---|---| | O1 | `python -m exudyn dialogs vis`, `... sim`, `... help` | the dialogs without a model; closing *vis* prints the changed settings as code | | O2 | 3D mouse / 6-axis joystick (3Dconnexion) connected before the renderer starts | message *found 6-axis joystick ...*; the device moves and rotates the view (`interactive.useJoystickInput`, on by default) | | O3 | `python/Examples/mouseInteractionExample.py` | drag the chain with the mouse; F2 / key user function works | | O4 | `python -m exudyn monitor --last` while a model writes sensor files | results monitor opens and updates | | O5 | display scaling 150-200 % (Windows) / HiDPI (Ubuntu, macOS Retina) | texts in the render window and the dialogs readable and not blurred; adjust `dialogs.fontScaling` in V | ## 8. Finish 1. Delete `~/.exudyn/config.json` and restore your backup. 2. Send the report: `python -m exudyn info` output, platform (OS version, X11/Wayland on Ubuntu, Intel/ARM on macOS, display scaling), the list of IDs with OK / FAIL / n.a., and screenshots of anything that looked wrong. ## Platform notes - **macOS** always renders single-threaded: dialogs run inside the renderer's idle loop, so the render window does **not** update while a dialog is open - that is expected. There is no keypad on most keyboards: R9 is *n.a.*, use SHIFT+cursor (R8). CTRL means the **control** key, not CMD. - **Ubuntu**: note whether the session is X11 or Wayland. tkinter dialogs may open behind the render window (D2, V1) - note it, it is known; a dialog that cannot be brought to front at all is a FAIL. - **Windows**: check at least once with display scaling > 100 % (O5), because DPI awareness is set differently with and without the render window (#2634).