diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index faa48a8d..433022ee 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -31,6 +31,7 @@ env: jobs: builds-and-tests: strategy: + fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] target: [dbg, opt] @@ -230,7 +231,7 @@ jobs: if: matrix.build-system == 'cmake' run: | python -m pip install --upgrade pip - pip install scikit-image + python -m pip install -r glvis/tests/requirements.txt - name: setup Linux testing dependencies if: matrix.build-system == 'cmake' && matrix.os == 'ubuntu-latest' diff --git a/CHANGELOG b/CHANGELOG index e0b0e9a5..a328b3f8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,12 @@ Version 4.2.1 (development) - Added a compilation parameter for the default font size. +- Added option to specify the floating point number formatting in GLVis axes and + colorbar. Use 'Alt+a' for the axes and 'Alt+c' for the colorbar. Formatting + can also be specified in socket stream or glvis script with axis_numberformat + or colorbar_numberformat, followed by a C-like formatting string, for example + "colorbar_numberformat '%+06.1f'". + - Added visualization of quadrature data (QuadratureFunction in MFEM). Both loading from file, with the new command line argument '-q', or from a socket stream, with the keyword 'quadrature', are supported. Three visualization diff --git a/README.md b/README.md index ca73cc36..530fbe73 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,8 @@ Key commands - . – Start/stop `z`-spinning (speed/direction can be controlled with 0 / Enter) - , , , – Manual rotation - 1, 2, 3, 4, 5, 6, 7, 8, 9 – Manual rotation along coordinate axes +- Alt + a – Set axes number format +- Alt + c – Set colorbar number format - Ctrl + , , , – Translate the viewpoint - Ctrl + o – Toggle an element ordering curve - n / N – Cycle through numberings. The options are: diff --git a/glvis.cpp b/glvis.cpp index 82700368..7d570f1f 100644 --- a/glvis.cpp +++ b/glvis.cpp @@ -769,6 +769,28 @@ void ExecuteScriptCommand() cout << min << ' ' << max << ' ' << num << endl; MyExpose(); } + else if (word == "axis_numberformat") + { + char delim; + string axis_formatting; + scr >> ws >> delim; + getline(scr, axis_formatting, delim); + cout << "Script: axis_numberformat: " << flush; + vs->SetAxisNumberFormat(axis_formatting); + cout << axis_formatting << endl; + MyExpose(); + } + else if (word == "colorbar_numberformat") + { + char delim; + string colorbar_formatting; + scr >> ws >> delim; + getline(scr, colorbar_formatting, delim); + cout << "Script: colorbar_numberformat: " << flush; + vs->SetColorbarNumberFormat(colorbar_formatting); + cout << colorbar_formatting << endl; + MyExpose(); + } else if (word == "window") { scr >> window_x >> window_y >> window_w >> window_h; @@ -1489,6 +1511,8 @@ int main (int argc, char *argv[]) cout << "Can not open script: " << script_file << endl; return 1; } + cout << "Running script from file: " << script_file << endl; + cout << "You may need to press to execute the script steps." << endl; PlayScript(scr); return 0; } diff --git a/lib/aux_vis.cpp b/lib/aux_vis.cpp index 3660bafd..7c92d0f0 100644 --- a/lib/aux_vis.cpp +++ b/lib/aux_vis.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "mfem.hpp" #include "sdl.hpp" @@ -1783,3 +1784,57 @@ void SetFont(const std::string& fn) << "', height = " << font_size << endl; #endif } + +function NumberFormatter(int precision, char format, bool showsign) +{ + return [precision, format, showsign](double x) -> string + { + ostringstream oss; + switch (format) { + case 'f': + oss << fixed; + break; + case 's': + oss << scientific; + break; + case 'd': + oss << defaultfloat; + break; + default: + MFEM_WARNING("Unknown formatting type. Using default. " + << "Valid options include: ['f', 's', 'd']" << endl); + oss << defaultfloat; + break; + }; + if (showsign) + { + oss << showpos; + } + oss << setprecision(precision) << x; + return oss.str(); + }; +} + +function NumberFormatter(string formatting) +{ + if (!isValidNumberFormatting(formatting)) + { + MFEM_WARNING("Invalid formatting string. Using default. " << endl); + return NumberFormatter(); + } + else + { + return [formatting](double x) -> string + { + char buf[64]; + snprintf(buf, sizeof(buf), formatting.c_str(), x); + return string(buf); + }; + } +} + +bool isValidNumberFormatting(const string& formatting) +{ + regex rgx = regex(R"(%[\-+#0\s]?[0-9]{0,3}\.?[0-9]{0,3}[FfEeGg])"); + return regex_match(formatting, rgx); +} diff --git a/lib/aux_vis.hpp b/lib/aux_vis.hpp index 97d03ccf..3361a153 100644 --- a/lib/aux_vis.hpp +++ b/lib/aux_vis.hpp @@ -136,5 +136,52 @@ bool SetFont(const vector& patterns, int height); void SetFont(const std::string& fn); void SetUseHiDPI(bool status); +function NumberFormatter(int precision=4, char format='d', bool showsign=false); +function NumberFormatter(string formatting); +bool isValidNumberFormatting(const string& formatting); + +// This is a helper function for prompting the user for inputs. The benefit +// over using just `cin >> input` is that you can specify a type and optionally +// a validator lambda. The a validator if not specified, it defaults to the +// True function. If the input cannot be type casted to the expected type, or +// if it fails the validation, the user is asked again for a new input. +template +T prompt(const string question, + const T* default_value = nullptr, + function validator = [](T) { return true; }) +{ + T input; + string strInput; + + while (true) + { + cout << question << " "; + getline(cin, strInput); + stringstream buf(strInput); + + if (strInput.empty() && default_value != nullptr) + { + cout << "Input empty. Using default value: " << *default_value << endl; + return *default_value; + } + + if (buf >> input) + { + if (validator(input)) + { + return input; + } + else + { + cout << "Input is not valid. Please try again." << endl; + } + } + else + { + cout << "Input can not be casted to expected type. Please try again." << endl; + } + } + return input; +} #endif diff --git a/lib/sdl.cpp b/lib/sdl.cpp index a4262be8..9b932a6c 100644 --- a/lib/sdl.cpp +++ b/lib/sdl.cpp @@ -267,51 +267,34 @@ void SdlWindow::mouseEventUp(SDL_MouseButtonEvent& eb) } } -void SdlWindow::keyEvent(SDL_Keysym& ks) +void SdlWindow::keyDownEvent(SDL_Keysym& ks) { - bool handled = false; - if (ks.sym >= 128 || ks.sym < 32) - { - if (onKeyDown[ks.sym]) - { - onKeyDown[ks.sym](ks.mod); - handled = true; - } - } - else if (ks.sym < 256 && std::isdigit(ks.sym)) - { - if (!(SDL_GetModState() & KMOD_SHIFT)) - { - // handle number key event here - onKeyDown[ks.sym](ks.mod); - handled = true; - } - } - else if (ctrlDown) - { - if (onKeyDown[ks.sym]) - { - onKeyDown[ks.sym](ks.mod); - handled = true; - } - } - if (ks.sym == SDLK_RCTRL || ks.sym == SDLK_LCTRL) - { - ctrlDown = true; + // Some keyDown events will be followed by a textInput event which will + // handle key translation due to Shift or CapsLock, so we leave such events + // to be processed there. + // Note: the same condition has to be used in signalKeyDown(). + if ((ks.sym >= 32 && ks.sym < 127) && + (ks.mod & ~(KMOD_SHIFT | KMOD_CAPS)) == 0) + { + lastKeyDownProcessed = false; + return; } - if (handled) + // If any 'mod' key other than KMOD_SHIFT or KMOD_CAPS is pressed, or the key + // is not in the range [32,127) then we processed the event here. + lastKeyDownProcessed = true; + if (onKeyDown[ks.sym]) { + onKeyDown[ks.sym](ks.mod); + + // Record the key in 'saved_keys': bool isAlt = ks.mod & (KMOD_ALT); bool isCtrl = ks.mod & (KMOD_CTRL); saved_keys += "["; if (isCtrl) { saved_keys += "C-"; } if (isAlt) { saved_keys += "Alt-"; } - if (ks.sym < 256 && std::isalpha(ks.sym)) + if (ks.sym >= 32 && ks.sym < 127) { - // key with corresponding text output - char c = ks.sym; - if (!(ks.mod & KMOD_SHIFT)) { c = std::tolower(c); } - saved_keys += c; + saved_keys += (char)(ks.sym); } else { @@ -321,25 +304,21 @@ void SdlWindow::keyEvent(SDL_Keysym& ks) } } -void SdlWindow::keyEvent(char c) +void SdlWindow::textInputEvent(const SDL_TextInputEvent &tie) { - if (!std::isdigit(c) && onKeyDown[c]) - { - SDL_Keymod mods = SDL_GetModState(); - bool isAlt = mods & (KMOD_ALT); - bool isCtrl = mods & (KMOD_CTRL); + // This event follows a keyDown event where we've recorded if the event was + // processed in keyDownEvent(). If it was not processed, we do it here. + if (lastKeyDownProcessed) { return; } + const char c = tie.text[0]; + if (onKeyDown[c]) + { + // Keys with 'mods' (other than Shift and CapsLock) are processed in + // keyDownEvent(). + const int mods = 0; onKeyDown[c](mods); - if (isAlt || isCtrl) - { - saved_keys += "["; - if (isCtrl) { saved_keys += "C-"; } - if (isAlt) { saved_keys += "Alt-"; } - } + + // Record the key in 'saved_keys': saved_keys += c; - if (isAlt || isCtrl) - { - saved_keys += "]"; - } } } @@ -403,17 +382,30 @@ void SdlWindow::mainIter() keep_going = true; break; case SDL_KEYDOWN: - keyEvent(e.key.keysym); +// For debugging: uncomment the next line to track key events. +// #define TRACK_KEY_EVENTS +#ifdef TRACK_KEY_EVENTS + cout << "Event: SDL_KEYDOWN sym=" << e.key.keysym.sym + << " mod=" << e.key.keysym.mod << endl; +#endif + keyDownEvent(e.key.keysym); break; case SDL_KEYUP: - if (e.key.keysym.sym == SDLK_LCTRL - || e.key.keysym.sym == SDLK_RCTRL) - { - ctrlDown = false; - } +#ifdef TRACK_KEY_EVENTS + cout << "Event: SDL_KEYUP sym=" << e.key.keysym.sym + << " mod=" << e.key.keysym.mod << endl; +#endif break; case SDL_TEXTINPUT: - keyEvent(e.text.text[0]); +#ifdef TRACK_KEY_EVENTS + cout << "Event: SDL_TEXTINPUT text[0..3]=" + << (int)(unsigned char)(e.text.text[0]) + << ' ' << (int)(unsigned char)(e.text.text[1]) + << ' ' << (int)(unsigned char)(e.text.text[2]) + << ' ' << (int)(unsigned char)(e.text.text[3]) + << " (as codes 0-255)" << endl; +#endif + textInputEvent(e.text); break; case SDL_MOUSEMOTION: motionEvent(e.motion); @@ -630,20 +622,21 @@ void SdlWindow::setWindowPos(int x, int y) void SdlWindow::signalKeyDown(SDL_Keycode k, SDL_Keymod m) { SDL_Event event; - if (k >= 32 && k < 128) + + event.type = SDL_KEYDOWN; + event.key.windowID = window_id; + event.key.keysym.sym = k; + event.key.keysym.mod = m; + queueEvents({ event }); + + // The same condition as in keyDownEvent(). + if ((k >= 32 && k < 127) && (m & ~(KMOD_SHIFT | KMOD_CAPS)) == 0) { event.type = SDL_TEXTINPUT; event.text.windowID = window_id; event.text.text[0] = k; + queueEvents({ event }); } - else - { - event.type = SDL_KEYDOWN; - event.key.windowID = window_id; - event.key.keysym.sym = k; - event.key.keysym.mod = m; - } - queueEvents({ event }); } void SdlWindow::swapBuffer() diff --git a/lib/sdl.hpp b/lib/sdl.hpp index 6d157328..df5fb987 100644 --- a/lib/sdl.hpp +++ b/lib/sdl.hpp @@ -103,8 +103,6 @@ class SdlWindow TouchDelegate onTouchPinch{nullptr}; TouchDelegate onTouchRotate{nullptr}; - bool ctrlDown{false}; - #ifdef __EMSCRIPTEN__ std::string canvas_id_; #endif @@ -127,14 +125,15 @@ class SdlWindow bool takeScreenshot{false}; std::string screenshot_file; bool screenshot_convert; + bool lastKeyDownProcessed; // internal event handlers void windowEvent(SDL_WindowEvent& ew); void motionEvent(SDL_MouseMotionEvent& em); void mouseEventDown(SDL_MouseButtonEvent& eb); void mouseEventUp(SDL_MouseButtonEvent& eb); - void keyEvent(SDL_Keysym& ks); - void keyEvent(char c); + void keyDownEvent(SDL_Keysym& ks); + void textInputEvent(const SDL_TextInputEvent &e); void multiGestureEvent(SDL_MultiGestureEvent & e); bool is_multithreaded{true}; diff --git a/lib/threads.cpp b/lib/threads.cpp index 9044fc19..2a410849 100644 --- a/lib/threads.cpp +++ b/lib/threads.cpp @@ -205,6 +205,36 @@ int GLVisCommand::AxisLabels(const char *a_x, const char *a_y, const char *a_z) return 0; } +int GLVisCommand::AxisNumberFormat(string formatting) +{ + if (lock() < 0) + { + return -1; + } + command = AXIS_NUMBERFORMAT; + axis_formatting = formatting; + if (signal() < 0) + { + return -2; + } + return 0; +} + +int GLVisCommand::ColorbarNumberFormat(string formatting) +{ + if (lock() < 0) + { + return -1; + } + command = COLORBAR_NUMBERFORMAT; + colorbar_formatting = formatting; + if (signal() < 0) + { + return -2; + } + return 0; +} + int GLVisCommand::Pause() { if (lock() < 0) @@ -525,6 +555,24 @@ int GLVisCommand::Execute() break; } + case AXIS_NUMBERFORMAT: + { + cout << "Command: axis_numberformat: '" + << axis_formatting << "'" << endl; + (*vs)->SetAxisNumberFormat(axis_formatting); + MyExpose(); + break; + } + + case COLORBAR_NUMBERFORMAT: + { + cout << "Command: colorbar_numberformat: '" + << colorbar_formatting << "'" << endl; + (*vs)->SetColorbarNumberFormat(colorbar_formatting); + MyExpose(); + break; + } + case PAUSE: { cout << "Command: pause: "; @@ -1202,6 +1250,48 @@ void communication_thread::execute() goto comm_terminate; } } + else if (ident == "axis_numberformat") + { + char c; + string formatting; + + *is[0] >> ws >> c; // read the opening char + getline(*is[0], formatting, c); // read formatting string & use c for termination + + // all processors sent the command + for (size_t i = 1; i < is.size(); i++) + { + *is[i] >> ws >> ident; // 'axis_numberformat' + *is[i] >> ws >> c; + getline(*is[i], ident, c); + } + + if (glvis_command->AxisNumberFormat(formatting)) + { + goto comm_terminate; + } + } + else if (ident == "colorbar_numberformat") + { + char c; + string formatting; + + *is[0] >> ws >> c; // read the opening char + getline(*is[0], formatting, c); // read formatting string & use c for termination + + // all processors sent the command + for (size_t i = 1; i < is.size(); i++) + { + *is[i] >> ws >> ident; // 'colorbar_numberformat' + *is[i] >> ws >> c; + getline(*is[i], ident, c); + } + + if (glvis_command->ColorbarNumberFormat(formatting)) + { + goto comm_terminate; + } + } else if (ident == "shading") { string shd; diff --git a/lib/threads.hpp b/lib/threads.hpp index 8b5bce2e..77579f3e 100644 --- a/lib/threads.hpp +++ b/lib/threads.hpp @@ -57,7 +57,9 @@ class GLVisCommand PLOT_CAPTION = 18, AXIS_LABELS = 19, PALETTE_REPEAT = 20, - LEVELLINES = 21 + LEVELLINES = 21, + AXIS_NUMBERFORMAT = 22, + COLORBAR_NUMBERFORMAT = 23 }; std::atomic command_ready{false}; @@ -88,6 +90,8 @@ class GLVisCommand int lvl_num; double camera[9]; std::string autopause_mode; + std::string axis_formatting; + std::string colorbar_formatting; // internal variables int autopause; @@ -125,6 +129,8 @@ class GLVisCommand int Palette(int pal); int PaletteRepeat(int n); int Levellines(double minv, double maxv, int number); + int AxisNumberFormat(string formatting); + int ColorbarNumberFormat(string formatting); int Camera(const double cam[]); int Autopause(const char *mode); diff --git a/lib/vsdata.cpp b/lib/vsdata.cpp index 3c4add44..5b94e113 100644 --- a/lib/vsdata.cpp +++ b/lib/vsdata.cpp @@ -414,6 +414,22 @@ void VisualizationSceneScalarData::Arrow(gl3::GlDrawable& buf, } } +void VisualizationSceneScalarData::SetColorbarNumberFormat(int precision, + char format, + bool showsign) +{ + colorbar_formatter = NumberFormatter(precision, format, showsign); + // The first two arguments are required but I don't think they are used? + PrepareColorBar(0,0); +} + +void VisualizationSceneScalarData::SetColorbarNumberFormat(string formatting) +{ + colorbar_formatter = NumberFormatter(formatting); + // The first two arguments are required but I don't think they are used? + PrepareColorBar(0,0); +} + void VisualizationSceneScalarData::PrepareColorBar (double minval, double maxval, Array *mesh_level, @@ -496,9 +512,7 @@ void VisualizationSceneScalarData::PrepareColorBar (double minval, val = ULogVal(i / 4.0); - ostringstream buf; - buf << setprecision(4) << val; - color_bar.addText(text_x,Y,posz, buf.str()); + color_bar.addText(text_x,Y,posz,colorbar_formatter(val)); } } else @@ -508,9 +522,7 @@ void VisualizationSceneScalarData::PrepareColorBar (double minval, val = (*mesh_level)[i]; Y = miny + (maxy - miny) * LogUVal(val); - ostringstream buf; - buf << setprecision(4) << val; - color_bar.addText(text_x,Y,posz, buf.str()); + color_bar.addText(text_x,Y,posz,colorbar_formatter(val)); } } @@ -521,9 +533,7 @@ void VisualizationSceneScalarData::PrepareColorBar (double minval, val = (*lsurf_levels)[i]; Y = miny + (maxy - miny) * LogUVal(val); - ostringstream buf; - buf << setprecision(4) << val; - color_bar.addText(text_x,Y,posz, buf.str()); + color_bar.addText(text_x,Y,posz,colorbar_formatter(val)); } } updated_bufs.emplace_back(&color_bar); @@ -550,10 +560,31 @@ void VisualizationSceneScalarData::PrepareCaption() thread_local VisualizationSceneScalarData * vsdata; extern thread_local VisualizationScene * locscene; -void KeycPressed() +void KeycPressed(GLenum state) { - vsdata->ToggleDrawColorbar(); - SendExposeEvent(); + if (state & KMOD_ALT) + { + cout << "Setting colorbar number formatting..." << endl; + int default_precision = 4; + char default_format = 'd'; + bool default_showsign = false; + + int precision = prompt("Enter precision (4): ", + &default_precision, + [](int p){ return p>=0; }); + char format = prompt("Enter format [(d)efault, (f)ixed, (s)cientific] (d): ", + &default_format, + [](char c){ return c=='d' || c=='f' || c=='s'; }); + bool showsign = prompt("Show sign? [(1)true, (0)false] (0): ", + &default_showsign); + vsdata->SetColorbarNumberFormat(precision, format, showsign); + SendExposeEvent(); + } + else + { + vsdata->ToggleDrawColorbar(); + SendExposeEvent(); + } } void KeyCPressed() @@ -588,6 +619,24 @@ void Key_Mod_a_Pressed(GLenum state) cout << autoscale_modes[autoscale] << endl; SendExposeEvent(); } + else if (state & KMOD_ALT) + { + cout << "Setting axes number formatting..." << endl; + int default_precision = 4; + char default_format = 'd'; + bool default_showsign = false; + + int precision = prompt("Enter precision (4): ", + &default_precision, + [](int p){ return p>=0; }); + char format = prompt("Enter format [(d)efault, (f)ixed, (s)cientific] (d): ", + &default_format, + [](char c){ return c=='d' || c=='f' || c=='s'; }); + bool showsign = prompt("Show sign? [(1)true, (0)false] (0): ", + &default_showsign); + vsdata->SetAxisNumberFormat(precision, format, showsign); + SendExposeEvent(); + } else { vsdata->ToggleDrawAxes(); @@ -1426,6 +1475,20 @@ void VisualizationSceneScalarData::SetAxisLabels(const char * a_x, PrepareAxes(); } +void VisualizationSceneScalarData::SetAxisNumberFormat(int precision, + char format, + bool showsign) +{ + axis_formatter = NumberFormatter(precision, format, showsign); + PrepareAxes(); +} + +void VisualizationSceneScalarData::SetAxisNumberFormat(string formatting) +{ + axis_formatter = NumberFormatter(formatting); + PrepareAxes(); +} + void VisualizationSceneScalarData::PrepareAxes() { // std::array blk = GetLineColor(); @@ -1514,13 +1577,17 @@ void VisualizationSceneScalarData::PrepareAxes() int ox = -desc/2; int oy = -3*desc/2; ostringstream buf; - buf << setprecision(4) - << "(" << bb.x[0] << "," << bb.y[0] << "," << bb.z[0] << ")" ; + buf << "(" + << axis_formatter(bb.x[0]) << "," + << axis_formatter(bb.y[0]) << "," + << axis_formatter(bb.z[0]) << ")"; axes_buf.addText(bb.x[0], bb.y[0], bb.z[0], ox, oy, buf.str()); ostringstream buf1; - buf1 << setprecision(4) - << "(" << bb.x[1] << "," << bb.y[1] << "," << bb.z[1] << ")" ; + buf1 << "(" + << axis_formatter(bb.x[1]) << "," + << axis_formatter(bb.y[1]) << "," + << axis_formatter(bb.z[1]) << ")"; axes_buf.addText(bb.x[1], bb.y[1], bb.z[1], ox, oy, buf1.str()); } updated_bufs.emplace_back(&axes_buf); diff --git a/lib/vsdata.hpp b/lib/vsdata.hpp index 92683110..126101a4 100644 --- a/lib/vsdata.hpp +++ b/lib/vsdata.hpp @@ -16,6 +16,7 @@ #include "mfem.hpp" #include "openglvis.hpp" +#include "aux_vis.hpp" using namespace mfem; @@ -79,6 +80,10 @@ class VisualizationSceneScalarData : public VisualizationScene Shading shading; int auto_ref_max, auto_ref_max_surf_elem; + // Formatter for axes & colorbar numbers. Set defaults. + function axis_formatter = NumberFormatter(4, 'd', false); + function colorbar_formatter = NumberFormatter(4, 'd', false); + vector updated_bufs; gl3::GlDrawable axes_buf; gl3::GlDrawable coord_cross_buf; @@ -269,12 +274,18 @@ class VisualizationSceneScalarData : public VisualizationScene // Turn on or off the caption void PrepareCaption(); + void SetColorbarNumberFormat(int precision, char format, bool showsign); + void SetColorbarNumberFormat(string formatting); + void PrepareColorBar(double minval, double maxval, Array * level = NULL, Array * levels = NULL); void SetAxisLabels(const char * a_x, const char * a_y, const char * a_z); + void SetAxisNumberFormat(int precision, char format, bool showsign); + void SetAxisNumberFormat(string formatting); + void PrepareAxes(); void ToggleDrawAxes() { diff --git a/lib/vssolution.cpp b/lib/vssolution.cpp index bab6d0d5..2270fc68 100644 --- a/lib/vssolution.cpp +++ b/lib/vssolution.cpp @@ -85,6 +85,8 @@ std::string VisualizationSceneSolution::GetHelpString() const << "| y/Y Rotate the cutting plane |" << endl << "| z/Z Move the cutting plane |" << endl << "| \\ - Set light source position |" << endl + << "| Alt+a - Axes number format |" << endl + << "| Alt+c - Colorbar number format |" << endl << "| Ctrl+o - Element ordering curve |" << endl << "| Ctrl+p - Print to a PDF file |" << endl << "+------------------------------------+" << endl diff --git a/lib/vssolution3d.cpp b/lib/vssolution3d.cpp index bc135ab2..4d253b2b 100644 --- a/lib/vssolution3d.cpp +++ b/lib/vssolution3d.cpp @@ -77,6 +77,8 @@ std::string VisualizationSceneSolution3d::GetHelpString() const << "| y/Y Rotate cutting plane (theta) |" << endl << "| z/Z Translate cutting plane |" << endl << "| \\ - Set light source position |" << endl + << "| Alt+a - Axes number format |" << endl + << "| Alt+c - Colorbar number format |" << endl << "| Ctrl+o - Element ordering curve |" << endl << "| Ctrl+p - Print to a PDF file |" << endl << "+------------------------------------+" << endl diff --git a/lib/vsvector.cpp b/lib/vsvector.cpp index 96e3b317..479f70e8 100644 --- a/lib/vsvector.cpp +++ b/lib/vsvector.cpp @@ -62,6 +62,8 @@ std::string VisualizationSceneVector::GetHelpString() const << "| v - Cycle through vector fields |" << endl << "| V - Change the arrows scaling |" << endl << "| \\ - Set light source position |" << endl + << "| Alt+a - Axes number format |" << endl + << "| Alt+c - Colorbar number format |" << endl << "| Ctrl+p - Print to a PDF file |" << endl << "+------------------------------------+" << endl << "| Function keys |" << endl diff --git a/lib/vsvector3d.cpp b/lib/vsvector3d.cpp index 244ed611..ac239344 100644 --- a/lib/vsvector3d.cpp +++ b/lib/vsvector3d.cpp @@ -74,6 +74,8 @@ std::string VisualizationSceneVector3d::GetHelpString() const << "| y/Y Rotate cutting plane (theta) |" << endl << "| z/Z Translate cutting plane |" << endl << "| \\ - Set light source position |" << endl + << "| Alt+a - Axes number format |" << endl + << "| Alt+c - Colorbar number format |" << endl << "| Ctrl+p - Print to a PDF file |" << endl << "+------------------------------------+" << endl << "| Function keys |" << endl diff --git a/tests/data b/tests/data index 81de802a..7457c82e 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 81de802ad63ba41cd1fcd3825fbb3d2471538ace +Subproject commit 7457c82e272a521c7877ad54b1ac4e0533945377 diff --git a/tests/glvis_driver.py b/tests/glvis_driver.py index 34556929..af3fb2d2 100644 --- a/tests/glvis_driver.py +++ b/tests/glvis_driver.py @@ -72,31 +72,31 @@ def compare_images(baseline_file, output_file, expect_fail=False): print("[FAIL] Differences were not detected in the control case.") else: print("[PASS] Images match.") - print(" actual ssim = {}, cutoff = {}".format(ssim, cutoff_ssim)) + print(f" actual ssim = {ssim}, cutoff = {cutoff_ssim}") return ssim >= cutoff_ssim if not expect_fail else ssim < cutoff_ssim # Function to test a given glvis command with a variety of key-based commands. # Not currently in use. def test_case(exec_path, exec_args, baseline, t_group, t_name, cmd): - print("Testing {0}:{1}...".format(t_group, t_name)) + print(f"Testing {t_group0}:{t_name1}...")) full_screenshot_cmd = cmd + screenshot_keys - cmd = "{0} {1} -k \"{2}\"".format(exec_path, exec_args, full_screenshot_cmd) - print("Exec: {}".format(cmd)) + cmd = f"{exec_path0} {exec_args} -k \"{full_screenshot_cmd2}\"" + print(f"Exec: {cmd}") ret = os.system(cmd + " > /dev/null 2>&1") if ret != 0: - print("[FAIL] GLVis exited with error code {}.".format(ret)) + print(f"[FAIL] GLVis exited with error code {ret}.") return False if not os.path.exists(t_group): os.mkdir(t_group) - output_name = "{0}/{1}.png".format(t_group, t_name) + output_name = f"{t_group0}/{t_name1}.png" - ret = os.system("mv {0} {1}".format(screenshot_file, output_name)) + ret = os.system(f"mv {screenshot_file0} {output_name}") if ret != 0: - print("[FAIL] Could not move output image: exit code {}.".format(ret)) + print(f"[FAIL] Could not move output image: exit code {ret}.") return False if baseline: - baseline_name = "{0}/test.{1}.png".format(baseline, test_name) + baseline_name = f"{baseline0}/test.{test_name1}.png" return compare_images(baseline_name, output_name) else: print("[IGNORE] No baseline exists to compare against.") @@ -106,35 +106,35 @@ def test_stream(exec_path, exec_args, save_file, baseline): if exec_args is None: exec_args = "" test_name = os.path.basename(save_file) - print("Testing {}...".format(save_file)) + print(f"Testing {save_file}...") # Create new stream file with command to screenshot and close stream_data = None with open(save_file) as in_f: stream_data = in_f.read() - output_name = "test.{}.png".format(test_name) - output_name_fail = "test.fail.{}.png".format(test_name) + output_name = f"test.{test_name}.png" + output_name_fail = f"test.fail.{test_name}.png" tmp_file = "test.saved" with open(tmp_file, 'w') as out_f: out_f.write(stream_data) out_f.write("\nwindow_size 800 600") - out_f.write("\nscreenshot {}".format(output_name)) + out_f.write(f"\nscreenshot {output_name}") # Zooming in should create some difference in the images out_f.write("\nkeys *") - out_f.write("\nscreenshot {}".format(output_name_fail)) + out_f.write(f"\nscreenshot {output_name_fail}") out_f.write("\nkeys q") # Run GLVis with modified stream file - cmd = "{0} {1} -saved {2}".format(exec_path, exec_args, tmp_file) - print("Exec: {}".format(cmd)) + cmd = f"{exec_path} {exec_args} -saved {tmp_file2}" + print(f"Exec: {cmd}") ret = os.system(cmd) if ret != 0: - print("[FAIL] GLVis exited with error code {}.".format(ret)) + print(f"[FAIL] GLVis exited with error code {ret}.") return False if baseline: - baseline_name = "{0}/test.{1}.png".format(baseline, test_name) + baseline_name = f"{baseline0}/test.{test_name1}.png") test_baseline = compare_images(baseline_name, output_name) test_control = compare_images(baseline_name, output_name_fail, expect_fail=True) diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 00000000..48389795 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,3 @@ +scikit-image +plotly +numpy >= 1.20.0, < 2.0.0