Files
ResInsight/ApplicationLibCode/ProjectDataModelCommands/RimcProject.cpp
T
Kristian Bendiksen 77c32a3fa5 #14772 Python: Add snapshot export object methods and forward exportSnapshots to them
- Add RimViewWindow_exportSnapshot on RimViewWindow (3D views and
  plots) with explicit ExportFolder, Prefix, Width, Height and
  FileFormat parameters. Generated as ViewWindow.export_snapshot().
- Add RimProject_exportSnapshots for exporting all views and/or plots
  with a ContentType parameter. Generated as Project.export_snapshots().
- Add RiaDefines::SnapshotFileFormat and SnapshotContentType enums,
  registered as Python StrEnums.
- Extract single-item exportSnapshotOfView()/exportSnapshotOfPlot()
  from the RicSnapshotAll*ToFileFeature loops so the object methods
  and the legacy loops share the implementation.
- RicfExportSnapshots resolves the export folder from the command file
  executor and forwards to the object methods.

Python: the hand-written ViewWindow.export_snapshot() wrapper is
removed (generated method is compatible). PlotWindow.export_snapshot()
and Project.export_snapshots() keep their legacy argument names
(file_prefix, output_format, snapshot_type, plot_format) as deprecated
aliases. Case.export_snapshots_of_all_views() is deprecated in favor
of View.export_snapshot().

Also fix Case.views(), which compared the view id to the case id and
therefore never returned any views.
2026-09-22 14:19:23 +02:00

598 lines
24 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2020- 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimcProject.h"
#include "RimcViewWindow.h"
#include "Ensemble/RiaEnsembleImportTools.h"
#include "KeyValueStore/RiaKeyValueStoreUtil.h"
#include "RiaApplication.h"
#include "RiaGuiApplication.h"
#include "RiaLogging.h"
#include "RiaQStringFormatter.h"
#include "RiaRegressionTestRunner.h"
#include "RiaResultNames.h"
#include "ExportCommands/RicSnapshotAllPlotsToFileFeature.h"
#include "ExportCommands/RicSnapshotAllViewsToFileFeature.h"
#include "RicImportSummaryCasesFeature.h"
#include "ViewLink/RicLinkVisibleViewsFeature.h"
#include "ViewLink/RicUnLinkViewFeature.h"
#include "Rim3dView.h"
#include "RimCornerPointCase.h"
#include "RimEclipseCaseCollection.h"
#include "RimEclipseCellColors.h"
#include "RimEclipseView.h"
#include "RimFileSummaryCase.h"
#include "RimMainPlotCollection.h"
#include "RimOilField.h"
#include "RimProject.h"
#include "RimSummaryCase.h"
#include "RimSummaryCaseMainCollection.h"
#include "RimSurfaceCollection.h"
#include "RimTools.h"
#include "RimValveTemplateCollection.h"
#include "RimWellPathCollection.h"
#include "RiuMainWindow.h"
#include "RiuPlotMainWindow.h"
#include "cafPdmFieldScriptingCapability.h"
#include <QDir>
#include <QFileInfo>
#include <memory>
#include <set>
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_importSummaryCase, "importSummaryCase" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_importSummaryCase::RimProject_importSummaryCase( caf::PdmObjectHandle* self )
: PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Import Summary Case", "", "", "Import Summary Case" );
CAF_PDM_InitScriptableFieldNoDefault( &m_fileName, "FileName", "" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_importSummaryCase::execute()
{
QString absolutePath = m_fileName;
QFileInfo projectPathInfo( absolutePath );
if ( !projectPathInfo.exists() )
{
QDir startDir( RiaApplication::instance()->startDir() );
absolutePath = startDir.absoluteFilePath( m_fileName );
}
QStringList summaryFileNames{ absolutePath };
RiaEnsembleImportTools::CreateConfig createConfig{ .fileType = RiaDefines::FileType::SMSPEC, .ensembleOrGroup = false, .allowDialogs = false };
auto newCases = RiaEnsembleImportTools::createSummaryCasesFromFiles( summaryFileNames, createConfig );
if ( !newCases.empty() )
{
RicImportSummaryCasesFeature::addSummaryCases( newCases );
if ( RiaGuiApplication::isRunning() )
{
RiuPlotMainWindow* mainPlotWindow = RiaGuiApplication::instance()->mainPlotWindow();
if ( mainPlotWindow && !newCases.empty() )
{
mainPlotWindow->updateMultiPlotToolBar();
}
}
if ( newCases.size() == 1 )
{
return newCases[0];
}
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProject_importSummaryCase::classKeywordReturnedType() const
{
return RimFileSummaryCase::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_summaryCase, "summaryCase" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_summaryCase::RimProject_summaryCase( caf::PdmObjectHandle* self )
: PdmObjectMethod( self, PdmObjectMethod::NullPointerType::NULL_IS_VALID, PdmObjectMethod::ResultType::PERSISTENT_TRUE )
{
CAF_PDM_InitObject( "Find Summary Case", "", "", "Find Summary Case" );
CAF_PDM_InitScriptableField( &m_caseId, "CaseId", -1, "" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_summaryCase::execute()
{
auto proj = RimProject::current();
auto sumCases = proj->allSummaryCases();
for ( auto s : sumCases )
{
if ( s->caseId() == m_caseId ) return s;
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProject_summaryCase::classKeywordReturnedType() const
{
return RimFileSummaryCase::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_surfaceFolder, "surfaceFolder" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_surfaceFolder::RimProject_surfaceFolder( caf::PdmObjectHandle* self )
: PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Get Surface Folder", "", "", "Get Surface Folder" );
CAF_PDM_InitScriptableFieldNoDefault( &m_folderName, "FolderName", "" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_surfaceFolder::execute()
{
auto proj = RimProject::current();
RimSurfaceCollection* surfcoll = proj->activeOilField()->surfaceCollection();
// Blank folder name parameter should return the topmost folder
if ( m_folderName().isEmpty() ) return surfcoll;
for ( auto s : surfcoll->subCollections() )
{
if ( s->collectionName() == m_folderName() ) return s;
}
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProject_surfaceFolder::classKeywordReturnedType() const
{
return RimSurfaceCollection::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_createGridFromKeyValues, "createGridFromKeyValues" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_createGridFromKeyValues::RimProject_createGridFromKeyValues( caf::PdmObjectHandle* self )
: PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Create grid from key values", "", "", "Create Grid From Key Values" );
CAF_PDM_InitScriptableFieldNoDefault( &m_name, "Name", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_nx, "Nx", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_ny, "Ny", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_nz, "Nz", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_coordKey, "CoordKey", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_zcornKey, "ZcornKey", "" );
CAF_PDM_InitScriptableFieldNoDefault( &m_actnumKey, "ActnumKey", "" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_createGridFromKeyValues::execute()
{
RiaLogging::info( "Creating grid from key values" );
QString name = m_name();
if ( name.isEmpty() ) return std::unexpected( "Empty name not allowed" );
int nx = m_nx();
int ny = m_ny();
int nz = m_nz();
if ( nx <= 0 || ny <= 0 || nz <= 0 ) return std::unexpected( "Invalid grid size. nx, ny and nz must be positive." );
RiaLogging::info( std::format( "Grid dimensions: [{} {} {}]", nx, ny, nz ) );
RiaLogging::info( std::format( "Coord: {}", m_coordKey() ) );
RiaLogging::info( std::format( "Zcorn: {}", m_zcornKey() ) );
RiaLogging::info( std::format( "Actnum: {}", m_actnumKey() ) );
auto keyValueStore = RiaApplication::instance()->keyValueStore();
std::vector<float> coord = RiaKeyValueStoreUtil::convertToFloatVector( keyValueStore->get( m_coordKey().toStdString() ) );
std::vector<float> zcorn = RiaKeyValueStoreUtil::convertToFloatVector( keyValueStore->get( m_zcornKey().toStdString() ) );
std::vector<float> actnum = RiaKeyValueStoreUtil::convertToFloatVector( keyValueStore->get( m_actnumKey().toStdString() ) );
if ( coord.empty() || zcorn.empty() || actnum.empty() )
{
return std::unexpected( "Found unexcepted empty coord, zcorn or actnum array." );
}
RimProject* project = RimProject::current();
if ( !project ) return std::unexpected( "Invalid project." );
RimEclipseCaseCollection* analysisModels = project->activeOilField() ? project->activeOilField()->analysisModels() : nullptr;
if ( !analysisModels ) return std::unexpected( "Missing analysis models." );
auto result = RimCornerPointCase::createFromCoordinatesArray( nx, ny, nz, coord, zcorn, actnum );
if ( !result.has_value() ) return result;
RimCornerPointCase* grid = result.value();
grid->setCustomCaseName( name );
project->assignCaseIdToCase( grid );
analysisModels->cases.push_back( grid );
RimMainPlotCollection::current()->ensureDefaultFlowPlotsAreCreated();
if ( RiaGuiApplication::isRunning() )
{
if ( RimEclipseView* riv = grid->createAndAddReservoirView() )
{
riv->loadDataAndUpdate();
if ( !riv->cellResult()->hasResult() )
{
riv->cellResult()->setResultVariable( RiaResultNames::undefinedResultName() );
}
analysisModels->updateConnectedEditors();
if ( RiuMainWindow::instance() ) RiuMainWindow::instance()->selectAsCurrentItem( riv->cellResult() );
}
}
keyValueStore->remove( m_coordKey().toStdString() );
keyValueStore->remove( m_zcornKey().toStdString() );
keyValueStore->remove( m_actnumKey().toStdString() );
return grid;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProject_createGridFromKeyValues::classKeywordReturnedType() const
{
return RimCornerPointCase::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_wellPathCollection, "wellPathCollection" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_wellPathCollection::RimProject_wellPathCollection( caf::PdmObjectHandle* self )
: PdmObjectMethod( self, PdmObjectMethod::NullPointerType::NULL_IS_INVALID, PdmObjectMethod::ResultType::PERSISTENT_TRUE )
{
CAF_PDM_InitObject( "Get Well Path Collection", "", "", "Get Well Path Collection" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_wellPathCollection::execute()
{
auto wellPathCollection = RimWellPathCollection::instance();
if ( !wellPathCollection )
{
return std::unexpected( "No well path collection found." );
}
return wellPathCollection;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProject_wellPathCollection::classKeywordReturnedType() const
{
return RimWellPathCollection::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_valveTemplates, "valveTemplates" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_valveTemplates::RimProject_valveTemplates( caf::PdmObjectHandle* self )
: PdmObjectMethod( self, PdmObjectMethod::NullPointerType::NULL_IS_INVALID, PdmObjectMethod::ResultType::PERSISTENT_TRUE )
{
CAF_PDM_InitObject( "Get Valve Template Collection", "", "", "Get Valve Template Collection" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_valveTemplates::execute()
{
auto valveTemplateCollection = RimTools::valveTemplateCollection();
if ( !valveTemplateCollection )
{
return std::unexpected( "No valve template collection found." );
}
return valveTemplateCollection;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimProject_valveTemplates::classKeywordReturnedType() const
{
return RimValveTemplateCollection::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_tileViews, "tileViews" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_tileViews::RimProject_tileViews( caf::PdmObjectHandle* self )
: caf::PdmVoidObjectMethod( self )
{
CAF_PDM_InitObject( "Tile Views", "", "", "Tile all visible 3D view windows" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_tileViews::execute()
{
if ( !RiaGuiApplication::isRunning() )
{
return std::unexpected( "Tiling views requires ResInsight to run with the graphical user interface." );
}
RiuMainWindow* mainWindow = RiuMainWindow::instance();
if ( !mainWindow ) return std::unexpected( "No 3D main window is available." );
mainWindow->tileViewWindows();
return nullptr;
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_linkViews, "linkViews" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_linkViews::RimProject_linkViews( caf::PdmObjectHandle* self )
: caf::PdmVoidObjectMethod( self )
{
CAF_PDM_InitObject( "Link Views", "", "", "Link the specified 3D views" );
CAF_PDM_InitScriptableFieldNoDefault( &m_views, "Views", "Views to link" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_linkViews::execute()
{
auto* project = self<RimProject>();
if ( !project ) return std::unexpected( "No project is available." );
std::set<Rim3dView*> seenViews;
std::vector<Rim3dView*> uniqueViews;
// Use the set only for duplicate detection. The vector preserves input order because the first
// unlinked view becomes the master view when a new view linker is created.
for ( Rim3dView* view : m_views.ptrReferencedObjectsByType() )
{
auto insertResult = seenViews.insert( view );
if ( insertResult.second ) uniqueViews.push_back( view );
}
if ( uniqueViews.size() < 2 ) return std::unexpected( "At least two unique views are required." );
std::vector<Rim3dView*> linkableViews;
for ( Rim3dView* view : uniqueViews )
{
if ( !view->assosiatedViewLinker() ) linkableViews.push_back( view );
}
RicLinkVisibleViewsFeature::linkViews( linkableViews );
return nullptr;
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_unlinkViews, "unlinkViews" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_unlinkViews::RimProject_unlinkViews( caf::PdmObjectHandle* self )
: caf::PdmVoidObjectMethod( self )
{
CAF_PDM_InitObject( "Unlink Views", "", "", "Unlink the specified 3D views" );
CAF_PDM_InitScriptableFieldNoDefault( &m_views, "Views", "Views to unlink" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_unlinkViews::execute()
{
auto* project = self<RimProject>();
if ( !project ) return std::unexpected( "No project is available." );
std::set<Rim3dView*> seenViews;
std::vector<Rim3dView*> uniqueViews;
for ( Rim3dView* view : m_views.ptrReferencedObjectsByType() )
{
auto insertResult = seenViews.insert( view );
if ( insertResult.second ) uniqueViews.push_back( view );
}
if ( uniqueViews.empty() ) return std::unexpected( "At least one view is required." );
RicUnLinkViewFeature::unlinkViews( uniqueViews );
return nullptr;
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimProject, RimProject_exportSnapshots, "exportSnapshots" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimProject_exportSnapshots::RimProject_exportSnapshots( caf::PdmObjectHandle* self )
: caf::PdmVoidObjectMethod( self )
{
CAF_PDM_InitObject( "Export Snapshots", "", "", "Export snapshots of all 3D views and/or plots in the project" );
CAF_PDM_InitScriptableField( &m_contentType,
"ContentType",
RiaDefines::SnapshotContentType::ALL,
"Content Type",
"",
"",
"Export 3D views, plots or both" );
CAF_PDM_InitScriptableField( &m_exportFolder,
"ExportFolder",
QString(),
"Export Folder",
"",
"",
"Folder to export to. Defaults to the 'snapshots' folder next to the project file." );
CAF_PDM_InitScriptableField( &m_prefix, "Prefix", QString(), "Prefix", "", "", "Prefix for the generated file names" );
CAF_PDM_InitScriptableField( &m_width, "Width", -1, "Width", "", "", "Image width in pixels. Use -1 for the current size." );
CAF_PDM_InitScriptableField( &m_height, "Height", -1, "Height", "", "", "Image height in pixels. Use -1 for the current size." );
CAF_PDM_InitScriptableField( &m_plotFileFormat,
"PlotFileFormat",
RiaDefines::SnapshotFileFormat::PNG,
"Plot File Format",
"",
"",
"Output file format for plots. 3D views are always exported as PNG." );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProject_exportSnapshots::setContentType( RiaDefines::SnapshotContentType contentType )
{
m_contentType = contentType;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProject_exportSnapshots::setExportFolder( const QString& exportFolder )
{
m_exportFolder = exportFolder;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProject_exportSnapshots::setPrefix( const QString& prefix )
{
m_prefix = prefix;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProject_exportSnapshots::setWidth( int width )
{
m_width = width;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProject_exportSnapshots::setHeight( int height )
{
m_height = height;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimProject_exportSnapshots::setPlotFileFormat( RiaDefines::SnapshotFileFormat fileFormat )
{
m_plotFileFormat = fileFormat;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimProject_exportSnapshots::execute()
{
if ( !RiaGuiApplication::isRunning() ) return std::unexpected( "Snapshots cannot be exported without a GUI." );
auto* project = self<RimProject>();
if ( !project ) return std::unexpected( "No project is available." );
const QString exportFolder = RimViewWindow_exportSnapshot::resolveExportFolder( m_exportFolder() );
int width = m_width();
int height = m_height();
if ( RiaRegressionTestRunner::instance()->isRunningRegressionTests() )
{
QSize defaultSize = RiaRegressionTestRunner::regressionDefaultImageSize();
width = defaultSize.width();
height = defaultSize.height();
}
const bool exportViews = m_contentType() != RiaDefines::SnapshotContentType::PLOTS;
const bool exportPlots = m_contentType() != RiaDefines::SnapshotContentType::VIEWS;
if ( exportViews )
{
RicSnapshotAllViewsToFileFeature::exportSnapshotOfViewsIntoFolder( exportFolder, width, height, m_prefix() );
}
if ( exportPlots )
{
const bool activateWidget = !RiaRegressionTestRunner::instance()->isRunningRegressionTests();
const QString fileSuffix = m_plotFileFormat() == RiaDefines::SnapshotFileFormat::PDF ? ".pdf" : ".png";
RicSnapshotAllPlotsToFileFeature::exportSnapshotOfPlotsIntoFolder( exportFolder, width, height, activateWidget, m_prefix(), -1, fileSuffix );
}
return nullptr;
}