mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 07:00:17 -05:00
#14426 Export multiple surface properties to IRAP/GRI
Add a multi-select list of values to export in the export dialog, containing the surface depth and the properties available on the selected surfaces. IRAP/GRI files hold a single value per node, so each selected value is written to a separate file named <surface>--<value>. Exporting depth only keeps the plain surface name. Property values are exported without resampling and require a regular surface with matching Nx and Ny. Values that cannot be exported are reported as warnings. The export grid defaults are no longer rounded when they originate from a regular surface, and the grid rotation is added to the dialog. Both are required to export the surface without resampling.
This commit is contained in:
1 parent
172e8f746a
commit
42aa580192
4 files changed
+179
-29
No files matched your search
@@ -40,9 +40,31 @@
|
||||
|
||||
#include <QAction>
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
|
||||
CAF_CMD_SOURCE_INIT( RicExportSurfaceToGriFeature, "RicExportSurfaceToGriFeature" );
|
||||
|
||||
namespace
|
||||
{
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Returns the surface if it is a regular surface with a grid matching gridParams exactly, nullptr otherwise
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimRegularSurface* regularSurfaceWithMatchingGrid( RimSurface* surf, const RigRegularSurfaceData& gridParams )
|
||||
{
|
||||
auto* reg = dynamic_cast<RimRegularSurface*>( surf );
|
||||
if ( !reg ) return nullptr;
|
||||
|
||||
if ( reg->nx() == gridParams.nx && reg->ny() == gridParams.ny && reg->originX() == gridParams.originX &&
|
||||
reg->originY() == gridParams.originY && reg->incrementX() == gridParams.incrementX && reg->incrementY() == gridParams.incrementY &&
|
||||
reg->rotation() == gridParams.rotation )
|
||||
{
|
||||
return reg;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// For a RimRegularSurface whose stored grid matches gridParams exactly, depth values are returned
|
||||
/// directly. Otherwise the surface is resampled onto the grid via RigSurfaceResampler.
|
||||
@@ -50,14 +72,9 @@ CAF_CMD_SOURCE_INIT( RicExportSurfaceToGriFeature, "RicExportSurfaceToGriFeature
|
||||
std::vector<float> RicExportSurfaceToGriFeature::resampleToGrid( RimSurface* surf, const RigRegularSurfaceData& gridParams )
|
||||
{
|
||||
// Regular surface with matching grid: use stored depth values directly (lossless)
|
||||
if ( auto* reg = dynamic_cast<RimRegularSurface*>( surf ) )
|
||||
if ( auto* reg = regularSurfaceWithMatchingGrid( surf, gridParams ) )
|
||||
{
|
||||
if ( reg->nx() == gridParams.nx && reg->ny() == gridParams.ny && reg->originX() == gridParams.originX &&
|
||||
reg->originY() == gridParams.originY && reg->incrementX() == gridParams.incrementX &&
|
||||
reg->incrementY() == gridParams.incrementY && reg->rotation() == gridParams.rotation )
|
||||
{
|
||||
return reg->depthValues();
|
||||
}
|
||||
return reg->depthValues();
|
||||
}
|
||||
|
||||
// Resample the surface onto the requested grid
|
||||
@@ -80,6 +97,23 @@ std::vector<float> RicExportSurfaceToGriFeature::resampleToGrid( RimSurface* sur
|
||||
return depthValues;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<float>
|
||||
RicExportSurfaceToGriFeature::propertyValuesOnGrid( RimSurface* surf, const QString& propertyName, const RigRegularSurfaceData& gridParams )
|
||||
{
|
||||
auto* reg = dynamic_cast<RimRegularSurface*>( surf );
|
||||
if ( !reg ) return {};
|
||||
|
||||
// Property values are stored per node on the surface grid and are exported as they are. The export grid must
|
||||
// therefore have the same number of nodes as the surface.
|
||||
auto values = reg->getProperty( propertyName );
|
||||
if ( values.size() != static_cast<size_t>( gridParams.nx * gridParams.ny ) ) return {};
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -96,6 +130,18 @@ void RicExportSurfaceToGriFeature::exportSurfaces( const std::vector<RimSurface*
|
||||
|
||||
RimRegularSurface* regularSurface = ( surfaces.size() == 1 ) ? dynamic_cast<RimRegularSurface*>( surfaces[0] ) : nullptr;
|
||||
|
||||
// Collect the properties available for export from all selected surfaces
|
||||
std::set<QString> availableProperties;
|
||||
for ( RimSurface* surf : surfaces )
|
||||
{
|
||||
if ( RigSurface* rig = surf->surfaceData() )
|
||||
{
|
||||
for ( const auto& name : rig->propertyNames() )
|
||||
availableProperties.insert( name );
|
||||
}
|
||||
}
|
||||
ui.setAvailableProperties( { availableProperties.begin(), availableProperties.end() } );
|
||||
|
||||
if ( regularSurface )
|
||||
{
|
||||
ui.setGridDefaults( regularSurface->nx(),
|
||||
@@ -103,7 +149,8 @@ void RicExportSurfaceToGriFeature::exportSurfaces( const std::vector<RimSurface*
|
||||
regularSurface->originX(),
|
||||
regularSurface->originY(),
|
||||
regularSurface->incrementX(),
|
||||
regularSurface->incrementY() );
|
||||
regularSurface->incrementY(),
|
||||
regularSurface->rotation() );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -124,11 +171,12 @@ void RicExportSurfaceToGriFeature::exportSurfaces( const std::vector<RimSurface*
|
||||
const double spacing = ( areaApprox > 0.0 ) ? std::sqrt( areaApprox / static_cast<double>( totalVertexCount ) ) : 1.0;
|
||||
const int nx = std::max( 2, static_cast<int>( std::ceil( bb.extent().x() / spacing ) ) + 1 );
|
||||
const int ny = std::max( 2, static_cast<int>( std::ceil( bb.extent().y() / spacing ) ) + 1 );
|
||||
ui.setGridDefaults( nx, ny, bb.min().x(), bb.min().y(), spacing, spacing );
|
||||
// The estimated grid is a starting point for the user, so round the values to something readable
|
||||
ui.setGridDefaults( nx, ny, std::round( bb.min().x() ), std::round( bb.min().y() ), std::round( spacing ), std::round( spacing ), 0.0 );
|
||||
}
|
||||
|
||||
caf::PdmUiPropertyViewDialog dialog( nullptr, &ui, "Export Surface to IRAP/GRI", "" );
|
||||
dialog.resize( QSize( 400, 300 ) );
|
||||
dialog.resize( QSize( 400, 450 ) );
|
||||
if ( dialog.exec() != QDialog::Accepted ) return;
|
||||
|
||||
const QString exportDir = ui.exportFolder();
|
||||
@@ -140,21 +188,54 @@ void RicExportSurfaceToGriFeature::exportSurfaces( const std::vector<RimSurface*
|
||||
const bool binary = ( ui.exportFormat() == RicExportSurfaceToGriUi::ExportFormat::GRI );
|
||||
const QString extension = binary ? ".gri" : ".irap";
|
||||
|
||||
const auto selectedProperties = ui.selectedProperties();
|
||||
if ( selectedProperties.empty() )
|
||||
{
|
||||
RiaLogging::error( "No values selected for export." );
|
||||
return;
|
||||
}
|
||||
|
||||
// IRAP/GRI files contain a single value per node, so each exported value is written to a separate file. When only
|
||||
// depth values are exported, the file name is the surface name without any suffix.
|
||||
const bool useValueNameSuffix = ( selectedProperties.size() > 1 ) ||
|
||||
( selectedProperties[0] != RicExportSurfaceToGriUi::depthEntryName() );
|
||||
|
||||
for ( RimSurface* surf : surfaces )
|
||||
{
|
||||
const QString fileName =
|
||||
caf::Utils::constructFullFileName( exportDir, caf::Utils::makeValidFileBasename( surf->fullName() ), extension );
|
||||
for ( const QString& valueName : selectedProperties )
|
||||
{
|
||||
const bool isDepth = ( valueName == RicExportSurfaceToGriUi::depthEntryName() );
|
||||
const auto values = isDepth ? resampleToGrid( surf, gridParams ) : propertyValuesOnGrid( surf, valueName, gridParams );
|
||||
|
||||
const auto depthValues = resampleToGrid( surf, gridParams );
|
||||
if ( depthValues.empty() ) continue;
|
||||
if ( values.empty() )
|
||||
{
|
||||
if ( isDepth )
|
||||
{
|
||||
RiaLogging::warning( std::format( "No depth values found for surface '{}', skipping export.", surf->fullName() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
RiaLogging::warning( std::format( "Skipping export of '{}' for surface '{}'. Properties can only be exported for a "
|
||||
"regular surface, and Nx and Ny must match the surface.",
|
||||
valueName,
|
||||
surf->fullName() ) );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
bool ok = binary ? RifSurfio::exportToGri( fileName.toStdString(), gridParams, depthValues )
|
||||
: RifSurfio::exportToIrap( fileName.toStdString(), gridParams, depthValues );
|
||||
QString baseName = caf::Utils::makeValidFileBasename( surf->fullName() );
|
||||
if ( useValueNameSuffix ) baseName += "--" + caf::Utils::makeValidFileBasename( valueName );
|
||||
|
||||
if ( ok )
|
||||
RiaLogging::info( std::format( "Exported surface to: {}", fileName ) );
|
||||
else
|
||||
RiaLogging::error( std::format( "Failed to export surface to: {}", fileName ) );
|
||||
const QString fileName = caf::Utils::constructFullFileName( exportDir, baseName, extension );
|
||||
|
||||
bool ok = binary ? RifSurfio::exportToGri( fileName.toStdString(), gridParams, values )
|
||||
: RifSurfio::exportToIrap( fileName.toStdString(), gridParams, values );
|
||||
|
||||
if ( ok )
|
||||
RiaLogging::info( std::format( "Exported surface to: {}", fileName ) );
|
||||
else
|
||||
RiaLogging::error( std::format( "Failed to export surface to: {}", fileName ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ public:
|
||||
// in row-major order (index = j * nx + i). Returns an empty vector on failure.
|
||||
static std::vector<float> resampleToGrid( RimSurface* surf, const RigRegularSurfaceData& gridParams );
|
||||
|
||||
// Returns the values of the named property in row-major order (index = j * nx + i). Property values are exported
|
||||
// without resampling, so an empty vector is returned unless the surface is a regular surface with the same number
|
||||
// of nodes as gridParams.
|
||||
static std::vector<float> propertyValuesOnGrid( RimSurface* surf, const QString& propertyName, const RigRegularSurfaceData& gridParams );
|
||||
|
||||
// Shows the export dialog, resamples and writes each surface. Format is chosen in the dialog.
|
||||
static void exportSurfaces( const std::vector<RimSurface*>& surfaces );
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
#include "RicExportSurfaceToGriUi.h"
|
||||
|
||||
#include "cafPdmUiFilePathEditor.h"
|
||||
|
||||
#include <cmath>
|
||||
#include "cafPdmUiTreeSelectionEditor.h"
|
||||
|
||||
namespace caf
|
||||
{
|
||||
@@ -47,12 +46,25 @@ RicExportSurfaceToGriUi::RicExportSurfaceToGriUi()
|
||||
CAF_PDM_InitFieldNoDefault( &m_exportFolder, "ExportFolder", "Export Folder" );
|
||||
m_exportFolder.uiCapability()->setUiEditorTypeName( caf::PdmUiFilePathEditor::uiEditorTypeName() );
|
||||
|
||||
CAF_PDM_InitFieldNoDefault( &m_selectedProperties, "SelectedProperties", "Export Values" );
|
||||
m_selectedProperties.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() );
|
||||
m_selectedProperties = std::vector<QString>{ depthEntryName() };
|
||||
|
||||
CAF_PDM_InitField( &m_nx, "Nx", 10, "Nx (columns)" );
|
||||
CAF_PDM_InitField( &m_ny, "Ny", 10, "Ny (rows)" );
|
||||
CAF_PDM_InitField( &m_originX, "OriginX", 0.0, "Origin X" );
|
||||
CAF_PDM_InitField( &m_originY, "OriginY", 0.0, "Origin Y" );
|
||||
CAF_PDM_InitField( &m_incrementX, "IncrementX", 1.0, "Increment X" );
|
||||
CAF_PDM_InitField( &m_incrementY, "IncrementY", 1.0, "Increment Y" );
|
||||
CAF_PDM_InitField( &m_rotation, "Rotation", 0.0, "Rotation" );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QString RicExportSurfaceToGriUi::depthEntryName()
|
||||
{
|
||||
return "Depth";
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -66,14 +78,23 @@ void RicExportSurfaceToGriUi::setExportFolder( const QString& exportFolder )
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicExportSurfaceToGriUi::setGridDefaults( int nx, int ny, double originX, double originY, double incrementX, double incrementY )
|
||||
void RicExportSurfaceToGriUi::setGridDefaults( int nx, int ny, double originX, double originY, double incrementX, double incrementY, double rotation )
|
||||
{
|
||||
m_nx = nx;
|
||||
m_ny = ny;
|
||||
m_originX = std::round( originX );
|
||||
m_originY = std::round( originY );
|
||||
m_incrementX = std::round( incrementX );
|
||||
m_incrementY = std::round( incrementY );
|
||||
m_originX = originX;
|
||||
m_originY = originY;
|
||||
m_incrementX = incrementX;
|
||||
m_incrementY = incrementY;
|
||||
m_rotation = rotation;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicExportSurfaceToGriUi::setAvailableProperties( const std::vector<QString>& propertyNames )
|
||||
{
|
||||
m_availableProperties = propertyNames;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -88,10 +109,18 @@ RigRegularSurfaceData RicExportSurfaceToGriUi::gridParams() const
|
||||
p.originY = m_originY;
|
||||
p.incrementX = m_incrementX;
|
||||
p.incrementY = m_incrementY;
|
||||
p.rotation = 0.0;
|
||||
p.rotation = m_rotation;
|
||||
return p;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<QString> RicExportSurfaceToGriUi::selectedProperties() const
|
||||
{
|
||||
return m_selectedProperties();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -121,3 +150,26 @@ void RicExportSurfaceToGriUi::defineEditorAttribute( const caf::PdmFieldHandle*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
QList<caf::PdmOptionItemInfo> RicExportSurfaceToGriUi::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
|
||||
{
|
||||
QList<caf::PdmOptionItemInfo> options;
|
||||
|
||||
if ( fieldNeedingOptions == &m_selectedProperties )
|
||||
{
|
||||
options.push_back( caf::PdmOptionItemInfo( depthEntryName(), depthEntryName() ) );
|
||||
|
||||
for ( const auto& name : m_availableProperties )
|
||||
{
|
||||
// A property with the same name as the depth entry is already represented in the list
|
||||
if ( name == depthEntryName() ) continue;
|
||||
|
||||
options.push_back( caf::PdmOptionItemInfo( name, name ) );
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "cafPdmField.h"
|
||||
#include "cafPdmObject.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
//==================================================================================================
|
||||
/// PdmObject holding the parameters for resampling a surface onto a regular grid before export.
|
||||
//==================================================================================================
|
||||
@@ -40,23 +42,33 @@ public:
|
||||
|
||||
RicExportSurfaceToGriUi();
|
||||
|
||||
// Name of the entry representing the surface depth values in the list of exported values
|
||||
static QString depthEntryName();
|
||||
|
||||
void setExportFolder( const QString& exportFolder );
|
||||
void setGridDefaults( int nx, int ny, double originX, double originY, double incrementX, double incrementY );
|
||||
void setGridDefaults( int nx, int ny, double originX, double originY, double incrementX, double incrementY, double rotation );
|
||||
void setAvailableProperties( const std::vector<QString>& propertyNames );
|
||||
|
||||
RigRegularSurfaceData gridParams() const;
|
||||
QString exportFolder() const;
|
||||
ExportFormat exportFormat() const;
|
||||
std::vector<QString> selectedProperties() const;
|
||||
|
||||
protected:
|
||||
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
|
||||
QList<caf::PdmOptionItemInfo> calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) override;
|
||||
|
||||
private:
|
||||
caf::PdmField<caf::AppEnum<ExportFormat>> m_exportFormat;
|
||||
caf::PdmField<QString> m_exportFolder;
|
||||
caf::PdmField<std::vector<QString>> m_selectedProperties;
|
||||
caf::PdmField<int> m_nx;
|
||||
caf::PdmField<int> m_ny;
|
||||
caf::PdmField<double> m_originX;
|
||||
caf::PdmField<double> m_originY;
|
||||
caf::PdmField<double> m_incrementX;
|
||||
caf::PdmField<double> m_incrementY;
|
||||
caf::PdmField<double> m_rotation;
|
||||
|
||||
std::vector<QString> m_availableProperties;
|
||||
};
|
||||
Reference in new issue
Block a user