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; } } 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)