From 40a70f1518816415730af876b9f3d868ce1425e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20H=C3=A6gland?= Date: Thu, 21 May 2026 06:04:31 +0200 Subject: [PATCH 1/2] Add setSnapshotSize gRPC command for scripted snapshot sizing Snapshots exported via gRPC (rips Instance.export_snapshots, PlotWindow.export_snapshot) use whatever geometry Qt happens to give the plot widget at the time of export. In offscreen mode the widget is typically much smaller than the user expects, because set_plot_window_size resizes the outer RiuPlotMainWindow but leaves the MDI sub-windows maximized at the original layout geometry. The CLI --snapshotsize flag avoids this by calling RiuMainWindowTools::setWindowSizeOnWidgetsInMdiWindows, which un-maximizes each MDI sub-window and resizes the inner viewWidget to the requested dimensions. This commit wraps the same helper as a scripting command (setSnapshotSize) so Python clients can request a specific snapshot resolution before calling export_snapshot. Also handles 3D views via setFixedWindowSizeFor3dViews, mirroring what --snapshotsize does in RiaGuiApplication. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../CMakeLists_files.cmake | 2 + .../RicfSetSnapshotSize.cpp | 80 +++++++++++++++++++ .../RicfSetSnapshotSize.h | 42 ++++++++++ GrpcInterface/GrpcProtos/Commands.proto | 1 + 4 files changed, 125 insertions(+) create mode 100644 ApplicationLibCode/CommandFileInterface/RicfSetSnapshotSize.cpp create mode 100644 ApplicationLibCode/CommandFileInterface/RicfSetSnapshotSize.h diff --git a/ApplicationLibCode/CommandFileInterface/CMakeLists_files.cmake b/ApplicationLibCode/CommandFileInterface/CMakeLists_files.cmake index a64e24d426f..f67dff2650f 100644 --- a/ApplicationLibCode/CommandFileInterface/CMakeLists_files.cmake +++ b/ApplicationLibCode/CommandFileInterface/CMakeLists_files.cmake @@ -15,6 +15,7 @@ set(SOURCE_GROUP_HEADER_FILES ${CMAKE_CURRENT_LIST_DIR}/RicfSetExportFolder.h ${CMAKE_CURRENT_LIST_DIR}/RicfSetMainWindowSize.h ${CMAKE_CURRENT_LIST_DIR}/RicfSetPlotWindowSize.h + ${CMAKE_CURRENT_LIST_DIR}/RicfSetSnapshotSize.h ${CMAKE_CURRENT_LIST_DIR}/RicfSetStartDir.h ${CMAKE_CURRENT_LIST_DIR}/RicfSetTimeStep.h ${CMAKE_CURRENT_LIST_DIR}/RicfScaleFractureTemplate.h @@ -55,6 +56,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicfSetExportFolder.cpp ${CMAKE_CURRENT_LIST_DIR}/RicfSetMainWindowSize.cpp ${CMAKE_CURRENT_LIST_DIR}/RicfSetPlotWindowSize.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicfSetSnapshotSize.cpp ${CMAKE_CURRENT_LIST_DIR}/RicfSetStartDir.cpp ${CMAKE_CURRENT_LIST_DIR}/RicfSetTimeStep.cpp ${CMAKE_CURRENT_LIST_DIR}/RicfScaleFractureTemplate.cpp diff --git a/ApplicationLibCode/CommandFileInterface/RicfSetSnapshotSize.cpp b/ApplicationLibCode/CommandFileInterface/RicfSetSnapshotSize.cpp new file mode 100644 index 00000000000..364f4e85b81 --- /dev/null +++ b/ApplicationLibCode/CommandFileInterface/RicfSetSnapshotSize.cpp @@ -0,0 +1,80 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RicfSetSnapshotSize.h" + +#include "RiaGuiApplication.h" +#include "RiuMainWindow.h" +#include "RiuMainWindowTools.h" +#include "RiuPlotMainWindow.h" + +#include "cafPdmFieldScriptingCapability.h" + +#include + +CAF_PDM_SOURCE_INIT( RicfSetSnapshotSize, "setSnapshotSize" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RicfSetSnapshotSize::RicfSetSnapshotSize() +{ + CAF_PDM_InitScriptableField( &m_height, "height", -1, "Height" ); + CAF_PDM_InitScriptableField( &m_width, "width", -1, "Width" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +caf::PdmScriptResponse RicfSetSnapshotSize::execute() +{ + if ( m_width() <= 0 || m_height() <= 0 ) + { + return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, "setSnapshotSize: width and height must both be > 0" ); + } + + RiaGuiApplication* guiApp = RiaGuiApplication::instance(); + if ( !guiApp ) + { + return caf::PdmScriptResponse( caf::PdmScriptResponse::COMMAND_ERROR, "Need GUI ResInsight to set snapshot size" ); + } + + // Resize plot sub-windows so plot snapshots render at the requested size. + // This is the same helper --snapshotsize uses in the CLI dispatch path. + // Note: for the inner plot widget to also be resized (and not just the + // outer multi-plot wrapper), the caller must have invoked + // set_plot_window_size first so the plots are realized inside MDI + // sub-windows. Without that, only the wrapper resizes and the qwt + // canvas stays anchored at its initial geometry. + if ( auto* plotMainWindow = guiApp->mainPlotWindow() ) + { + RiuMainWindowTools::setWindowSizeOnWidgetsInMdiWindows( plotMainWindow, m_width(), m_height() ); + } + + // Mirror --snapshotsize behavior for 3D views as well. + if ( auto* mainWindow = guiApp->mainWindow() ) + { + RiuMainWindowTools::setFixedWindowSizeFor3dViews( mainWindow, m_width(), m_height() ); + } + + // Let the resize events propagate before the caller's next gRPC + // request (typically export_snapshot) renders the widget. + QApplication::processEvents(); + + return caf::PdmScriptResponse(); +} diff --git a/ApplicationLibCode/CommandFileInterface/RicfSetSnapshotSize.h b/ApplicationLibCode/CommandFileInterface/RicfSetSnapshotSize.h new file mode 100644 index 00000000000..ebb18214a8b --- /dev/null +++ b/ApplicationLibCode/CommandFileInterface/RicfSetSnapshotSize.h @@ -0,0 +1,42 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RicfCommandObject.h" + +#include "cafPdmField.h" + +//================================================================================================== +// +// +// +//================================================================================================== +class RicfSetSnapshotSize : public RicfCommandObject +{ + CAF_PDM_HEADER_INIT; + +public: + RicfSetSnapshotSize(); + + caf::PdmScriptResponse execute() override; + +private: + caf::PdmField m_height; + caf::PdmField m_width; +}; diff --git a/GrpcInterface/GrpcProtos/Commands.proto b/GrpcInterface/GrpcProtos/Commands.proto index 5dbdf8fc6ec..f6d787a3ed6 100644 --- a/GrpcInterface/GrpcProtos/Commands.proto +++ b/GrpcInterface/GrpcProtos/Commands.proto @@ -338,6 +338,7 @@ message CommandParams { SetWindowSizeParams setPlotWindowSize = 37; ExportContourMapToTextRequest exportContourMapToText = 38; SaveProjectRequest saveProject = 39; + SetWindowSizeParams setSnapshotSize = 40; } } From 898a8470f8a01577636aa2a9ce6b703916d79e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5kon=20H=C3=A6gland?= Date: Thu, 21 May 2026 06:04:45 +0200 Subject: [PATCH 2/2] Add Instance.set_snapshot_size to rips Python client Wraps the new setSnapshotSize gRPC command so callers can do ri.set_snapshot_size(1600, 1000) for plot in ri.project.plots(): plot.export_snapshot(export_folder=...) and get 1600x1000 PNGs back. Mirrors the existing Instance.set_plot_window_size wrapper shape. The headless_plot_export example is updated to demonstrate the call between plot creation and the export_snapshot loop. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../headless_plot_export.py | 6 ++++ GrpcInterface/Python/rips/instance.py | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/GrpcInterface/Python/rips/PythonExamples/export_and_plotting/headless_plot_export.py b/GrpcInterface/Python/rips/PythonExamples/export_and_plotting/headless_plot_export.py index 4b3f366e4ce..344f2d257f2 100644 --- a/GrpcInterface/Python/rips/PythonExamples/export_and_plotting/headless_plot_export.py +++ b/GrpcInterface/Python/rips/PythonExamples/export_and_plotting/headless_plot_export.py @@ -26,6 +26,12 @@ summary_cases=[summary_case], address="WOPR:A*;WOPR:B*" ) +# Resize the plot sub-windows so the exported PNGs come out at the +# requested resolution. Without this, the offscreen layout engine picks +# its own (typically small) geometry and snapshots ignore the --size +# launch parameter. +resinsight.set_snapshot_size(1200, 1000) + plots = resinsight.project.plots() for plot in plots: plot.export_snapshot() diff --git a/GrpcInterface/Python/rips/instance.py b/GrpcInterface/Python/rips/instance.py index e406c01f0ea..18dcb891da8 100644 --- a/GrpcInterface/Python/rips/instance.py +++ b/GrpcInterface/Python/rips/instance.py @@ -561,6 +561,42 @@ def set_plot_window_size(self, width: int, height: int): ) ) + def set_snapshot_size(self, width: int, height: int): + """ + Set the size used for snapshot export of plots and 3D views. + + Affects subsequent calls to ``export_snapshot`` / + ``export_snapshots``. Equivalent to the ``--snapshotsize`` CLI + flag. Must be called after at least one plot window has been + created (otherwise there is nothing to resize). + + Unlike :meth:`set_plot_window_size`, which only resizes the + outer plot main window, this method un-maximizes and resizes + each MDI sub-window so the actual plot widget is rendered at + the requested dimensions. + + Recommended call order for a filled snapshot: + + 1. :meth:`set_plot_window_size` *before* creating plots, so the + plots are realized inside MDI sub-windows. + 2. Create the plot(s). + 3. ``project.plots()`` to fetch the plot to export. This can + re-tile the MDI sub-windows back to their defaults, so call + ``set_snapshot_size`` *after* it, not before. + 4. ``set_snapshot_size(width, height)``. + 5. ``plot.export_snapshot(...)``. + + **Parameters**:: + + Parameter | Description | Type + --------- | ---------------- | ----- + width | Width in pixels | Integer + height | Height in pixels | Integer + """ + return self.__execute_command( + setSnapshotSize=Commands_pb2.SetWindowSizeParams(width=width, height=height) + ) + def major_version(self) -> int: """Get an integer with the major version number""" return int(self.__version_message().major_version)