Streamlines: New generator and UI usability improvements (#7424)

Co-authored-by: jonjenssen <jonjenssen@users.noreply.github.com>
Co-authored-by: Magne Sjaastad <magne.sjaastad@ceetronsolutions.com>
This commit is contained in:
jonjenssen
2021-02-25 16:38:56 +01:00
committed by GitHub
parent f7a5f18e7f
commit 9bd0a9ebac
34 changed files with 3338 additions and 34 deletions

View File

@@ -0,0 +1,26 @@
set (SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RimStreamline.h
${CMAKE_CURRENT_LIST_DIR}/RimStreamlineInViewCollection.h
${CMAKE_CURRENT_LIST_DIR}/RimStreamlineGeneratorBase.h
${CMAKE_CURRENT_LIST_DIR}/RimStreamlineDataAccess.h
${CMAKE_CURRENT_LIST_DIR}/RimStreamlineGenerator2.h
)
set (SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimStreamline.cpp
${CMAKE_CURRENT_LIST_DIR}/RimStreamlineInViewCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimStreamlineGeneratorBase.cpp
${CMAKE_CURRENT_LIST_DIR}/RimStreamlineDataAccess.cpp
${CMAKE_CURRENT_LIST_DIR}/RimStreamlineGenerator2.cpp
)
list(APPEND CODE_HEADER_FILES
${SOURCE_GROUP_HEADER_FILES}
)
list(APPEND CODE_SOURCE_FILES
${SOURCE_GROUP_SOURCE_FILES}
)
source_group( "ProjectDataModel\\Streamlines" FILES ${SOURCE_GROUP_HEADER_FILES} ${SOURCE_GROUP_SOURCE_FILES} ${CMAKE_CURRENT_LIST_DIR}/CMakeLists_files.cmake )

View File

@@ -0,0 +1,123 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "RimStreamline.h"
#include "cafPdmFieldScriptingCapability.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiLineEditor.h"
#include "cafPdmUiTextEditor.h"
#include <algorithm>
#include <cmath>
CAF_PDM_ABSTRACT_SOURCE_INIT( RimStreamline, "Streamline" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamline::RimStreamline( QString simWellName )
{
CAF_PDM_InitScriptableObject( "Streamline", ":/Erase.png", "", "" );
CAF_PDM_InitScriptableField( &m_simWellName, "Name", simWellName, "Name", "", "", "" );
m_simWellName.uiCapability()->setUiReadOnly( true );
m_simWellName.uiCapability()->setUiHidden( true );
CAF_PDM_InitScriptableFieldNoDefault( &m_propertiesTable, "PropertiesTable", "Properties Table", "", "", "" );
m_propertiesTable.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() );
m_propertiesTable.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN );
m_propertiesTable.uiCapability()->setUiReadOnly( true );
m_propertiesTable.xmlCapability()->disableIO();
setDeletable( false );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamline::~RimStreamline()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmFieldHandle* RimStreamline::userDescriptionField()
{
return &m_simWellName;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const RigTracer& RimStreamline::tracer() const
{
return m_tracer;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const QString RimStreamline::simWellName() const
{
return m_simWellName;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamline::addTracerPoint( cvf::Vec3d position, cvf::Vec3d direction, RiaDefines::PhaseType dominantPhase )
{
m_tracer.appendPoint( position, direction, dominantPhase );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamline::reverse()
{
m_tracer.reverse();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamline::generateStatistics()
{
QString stats;
stats += "Total distance: ";
stats += QString::number( m_tracer.totalDistance(), 'f', 2 );
stats += " meters\n";
stats += "\n";
stats += "Number of points: ";
stats += QString::number( m_tracer.size() );
stats += "\n";
stats += "\n";
m_propertiesTable = stats;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimStreamline::size() const
{
return m_tracer.size();
}

View File

@@ -0,0 +1,59 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RigTracer.h"
#include "RiaDefines.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
#include "cafPdmFieldCvfColor.h"
#include "cafPdmProxyValueField.h"
#include "cvfObject.h"
#include "cvfVector3.h"
#include <vector>
class RimStreamline : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
RimStreamline( QString simwellname );
~RimStreamline() override;
const RigTracer& tracer() const;
const QString simWellName() const;
size_t size() const;
void addTracerPoint( cvf::Vec3d position, cvf::Vec3d direction, RiaDefines::PhaseType dominantPhase );
void reverse();
void generateStatistics();
protected:
caf::PdmFieldHandle* userDescriptionField() override;
private:
RigTracer m_tracer;
caf::PdmField<QString> m_simWellName;
caf::PdmField<QString> m_propertiesTable;
};

View File

@@ -0,0 +1,235 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 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 "RimStreamlineDataAccess.h"
#include "RigCaseCellResultsData.h"
#include "RigCell.h"
#include "RigEclipseCaseData.h"
#include "RigEclipseResultAddress.h"
#include "RigMainGrid.h"
#include "RigResultAccessor.h"
#include "RigResultAccessorFactory.h"
#include "cafAssert.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineDataAccess::RimStreamlineDataAccess()
: m_data( nullptr )
, m_grid( nullptr )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineDataAccess::~RimStreamlineDataAccess()
{
}
//--------------------------------------------------------------------------------------------------
/// Set up data accessors to access the selected flroil/gas/wat data for all faces
//--------------------------------------------------------------------------------------------------
bool RimStreamlineDataAccess::setupDataAccess( RigMainGrid* grid,
RigEclipseCaseData* data,
std::list<RiaDefines::PhaseType> phases,
int timeIdx )
{
m_grid = grid;
m_data = data;
m_dataAccess.clear();
for ( auto phase : phases )
{
m_dataAccess[phase] = std::vector<cvf::ref<RigResultAccessor>>();
// Note: NEG_? accessors are set to POS_? accessors, but will be referring the neighbor cell when used
m_dataAccess[phase].push_back( getDataAccessor( cvf::StructGridInterface::FaceType::POS_I, phase, timeIdx ) );
m_dataAccess[phase].push_back( getDataAccessor( cvf::StructGridInterface::FaceType::POS_I, phase, timeIdx ) );
m_dataAccess[phase].push_back( getDataAccessor( cvf::StructGridInterface::FaceType::POS_J, phase, timeIdx ) );
m_dataAccess[phase].push_back( getDataAccessor( cvf::StructGridInterface::FaceType::POS_J, phase, timeIdx ) );
m_dataAccess[phase].push_back( getDataAccessor( cvf::StructGridInterface::FaceType::POS_K, phase, timeIdx ) );
m_dataAccess[phase].push_back( getDataAccessor( cvf::StructGridInterface::FaceType::POS_K, phase, timeIdx ) );
}
for ( auto& pair : m_dataAccess )
{
for ( auto& access : pair.second )
{
if ( access.isNull() ) return false;
}
}
return true;
}
//--------------------------------------------------------------------------------------------------
/// Return a data accessor for the given phase and face and time step
//--------------------------------------------------------------------------------------------------
cvf::ref<RigResultAccessor> RimStreamlineDataAccess::getDataAccessor( cvf::StructGridInterface::FaceType faceIdx,
RiaDefines::PhaseType phase,
int timeIdx )
{
RiaDefines::PorosityModelType porModel = RiaDefines::PorosityModelType::MATRIX_MODEL;
RigCaseCellResultsData* data = m_data->results( porModel );
QString resultname = gridResultNameFromPhase( phase, faceIdx );
int gridIdx = 0;
RigEclipseResultAddress address( RiaDefines::ResultCatType::DYNAMIC_NATIVE, resultname );
// Make sure we have the data we need loaded for the given phase and time step index
data->ensureKnownResultLoadedForTimeStep( address, timeIdx );
return RigResultAccessorFactory::createFromResultAddress( m_data, gridIdx, porModel, timeIdx, address );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimStreamlineDataAccess::gridResultNameFromPhase( RiaDefines::PhaseType phase,
cvf::StructGridInterface::FaceType faceIdx ) const
{
QString retval = "";
switch ( phase )
{
case RiaDefines::PhaseType::GAS_PHASE:
retval += "FLRGAS";
break;
case RiaDefines::PhaseType::OIL_PHASE:
retval += "FLROIL";
break;
case RiaDefines::PhaseType::WATER_PHASE:
retval += "FLRWAT";
break;
default:
CAF_ASSERT( false );
break;
}
switch ( faceIdx )
{
case cvf::StructGridInterface::FaceType::POS_I:
retval += "I+";
break;
case cvf::StructGridInterface::FaceType::POS_J:
retval += "J+";
break;
case cvf::StructGridInterface::FaceType::POS_K:
retval += "K+";
break;
default:
CAF_ASSERT( false );
break;
}
return retval;
}
//--------------------------------------------------------------------------------------------------
/// Return the face scalar value for the given cell and NEG_? face, by using the neighbor cell
//--------------------------------------------------------------------------------------------------
double RimStreamlineDataAccess::negFaceValueDividedByArea( RigCell cell,
cvf::StructGridInterface::FaceType faceIdx,
RiaDefines::PhaseType phase ) const
{
double retval = 0.0;
RigCell neighborCell = cell.neighborCell( faceIdx );
if ( neighborCell.isInvalid() ) return retval;
std::vector<cvf::ref<RigResultAccessor>> access = m_dataAccess.at( phase );
retval = access[faceIdx]->cellScalar( neighborCell.mainGridCellIndex() );
double area = cell.faceNormalWithAreaLength( faceIdx ).length();
if ( area != 0.0 )
retval /= area;
else
retval = 0.0;
if ( std::isinf( retval ) ) retval = 0.0;
return retval;
}
//--------------------------------------------------------------------------------------------------
/// Return the face scalar value for the given cell and POS_? face
//--------------------------------------------------------------------------------------------------
double RimStreamlineDataAccess::posFaceValueDividedByArea( RigCell cell,
cvf::StructGridInterface::FaceType faceIdx,
RiaDefines::PhaseType phase ) const
{
std::vector<cvf::ref<RigResultAccessor>> access = m_dataAccess.at( phase );
double retval = access[faceIdx]->cellScalar( cell.mainGridCellIndex() );
double length = cell.faceNormalWithAreaLength( faceIdx ).length();
if ( length != 0.0 )
retval /= length;
else
retval = 0.0;
if ( std::isinf( retval ) ) retval = 0.0;
return retval;
}
//--------------------------------------------------------------------------------------------------
/// Return the face scalar value for the given cell and face
//--------------------------------------------------------------------------------------------------
double RimStreamlineDataAccess::faceValueDividedByArea( RigCell cell,
cvf::StructGridInterface::FaceType faceIdx,
RiaDefines::PhaseType phase ) const
{
if ( faceIdx % 2 == 0 ) return posFaceValueDividedByArea( cell, faceIdx, phase );
// NEG_? face values must be read from the neighbor cells
return negFaceValueDividedByArea( cell, faceIdx, phase );
}
//--------------------------------------------------------------------------------------------------
/// Return the face scalar value for the given cell and face, by combining flow for all specified phases
//--------------------------------------------------------------------------------------------------
double RimStreamlineDataAccess::combinedFaceValueByArea( RigCell cell,
cvf::StructGridInterface::FaceType faceIdx,
std::list<RiaDefines::PhaseType> phases,
RiaDefines::PhaseType& outDominantPhase ) const
{
double retValue = 0.0;
outDominantPhase = phases.front();
double max = 0.0;
for ( auto phase : phases )
{
double tmp = 0.0;
if ( faceIdx % 2 == 0 )
tmp = posFaceValueDividedByArea( cell, faceIdx, phase );
else
tmp = negFaceValueDividedByArea( cell, faceIdx, phase );
if ( abs( tmp ) > max )
{
outDominantPhase = phase;
max = abs( tmp );
}
retValue += tmp;
}
return retValue;
}

View File

@@ -0,0 +1,70 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaDefines.h"
#include "cvfStructGrid.h"
#include <QString>
#include <list>
#include <set>
class RigCell;
class RimStreamline;
class RigMainGrid;
class RigGridBase;
class RigResultAccessor;
class RigEclipseCaseData;
class RimStreamlineDataAccess
{
public:
RimStreamlineDataAccess();
~RimStreamlineDataAccess();
bool setupDataAccess( RigMainGrid* grid, RigEclipseCaseData* data, std::list<RiaDefines::PhaseType> phases, int timeIdx );
double faceValueDividedByArea( RigCell cell, cvf::StructGridInterface::FaceType faceIdx, RiaDefines::PhaseType phase ) const;
double combinedFaceValueByArea( RigCell cell,
cvf::StructGridInterface::FaceType faceIdx,
std::list<RiaDefines::PhaseType> phases,
RiaDefines::PhaseType& dominantPhaseOut ) const;
const RigMainGrid* grid() const { return m_grid; }
protected:
cvf::ref<RigResultAccessor>
getDataAccessor( cvf::StructGridInterface::FaceType faceIdx, RiaDefines::PhaseType phase, int timeIdx );
QString gridResultNameFromPhase( RiaDefines::PhaseType phase, cvf::StructGridInterface::FaceType faceIdx ) const;
double posFaceValueDividedByArea( RigCell cell,
cvf::StructGridInterface::FaceType faceIdx,
RiaDefines::PhaseType phase ) const;
double negFaceValueDividedByArea( RigCell cell,
cvf::StructGridInterface::FaceType faceIdx,
RiaDefines::PhaseType phase ) const;
private:
RigMainGrid* m_grid;
RigEclipseCaseData* m_data;
std::map<RiaDefines::PhaseType, std::vector<cvf::ref<RigResultAccessor>>> m_dataAccess;
};

View File

@@ -0,0 +1,199 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 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 "RimStreamlineGenerator2.h"
#include "RigCell.h"
#include "RigMainGrid.h"
#include "RimStreamline.h"
#include "RimStreamlineDataAccess.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineGenerator2::RimStreamlineGenerator2( std::set<size_t>& wellCells )
: RimStreamlineGeneratorBase( wellCells )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineGenerator2::~RimStreamlineGenerator2()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineGenerator2::generateTracer( RigCell cell,
double direction,
QString simWellName,
std::list<RimStreamline*>& outStreamlines )
{
RiaDefines::PhaseType dominantPhase = m_phases.front();
const size_t cellIdx = cell.gridLocalCellIndex();
// try to generate a tracer for all faces in the selected cell with positive flow
for ( auto faceIdx : m_allFaces )
{
double flowVelocity = m_dataAccess->combinedFaceValueByArea( cell, faceIdx, m_phases, dominantPhase ) * direction;
if ( flowVelocity > m_flowThreshold )
{
m_seeds.push_back( std::make_pair( cellIdx, faceIdx ) );
}
}
while ( m_seeds.size() > 0 )
{
const size_t cellIdx = m_seeds.front().first;
const cvf::StructGridInterface::FaceType faceIdx = m_seeds.front().second;
RimStreamline* streamline = new RimStreamline( simWellName );
growStreamline( streamline, cellIdx, faceIdx, direction );
if ( direction < 0.0 ) streamline->reverse();
outStreamlines.push_back( streamline );
m_seeds.pop_front();
}
return;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineGenerator2::growStreamline( RimStreamline* streamline,
size_t cellIdx,
cvf::StructGridInterface::FaceType faceIdx,
double direction )
{
// get the cell
RigCell cell = m_dataAccess->grid()->cell( cellIdx );
// get rate
RiaDefines::PhaseType dominantPhaseOut;
double flowVelocity = std::abs( m_dataAccess->combinedFaceValueByArea( cell, faceIdx, m_phases, dominantPhaseOut ) );
while ( flowVelocity >= m_flowThreshold )
{
// grow from given face center to cell center, exiting if we reach the max length
if ( !growStreamlineFromTo( streamline, cell.faceCenter( faceIdx ), cell.center(), flowVelocity, dominantPhaseOut ) )
break;
// move to next cell
RigCell neighbor = cell.neighborCell( faceIdx );
if ( neighbor.isInvalid() ) break;
cvf::StructGridInterface::FaceType neighborFaceIdx = cvf::StructGridInterface::oppositeFace( faceIdx );
// get rate
flowVelocity =
std::abs( m_dataAccess->combinedFaceValueByArea( neighbor, neighborFaceIdx, m_phases, dominantPhaseOut ) );
// grow from face center to cell center, exiting if we reach the max point limit
if ( !growStreamlineFromTo( streamline,
neighbor.faceCenter( neighborFaceIdx ),
neighbor.center(),
flowVelocity,
dominantPhaseOut ) )
break;
// have we been here?
if ( m_visitedCells.count( neighbor.gridLocalCellIndex() ) > 0 ) break;
// is this a well?
if ( m_wellCells.count( neighbor.gridLocalCellIndex() ) > 0 ) break;
m_visitedCells.insert( neighbor.gridLocalCellIndex() );
// find the face with max flow where we should exit the cell
cvf::StructGridInterface::FaceType exitFace = cvf::StructGridInterface::FaceType::NO_FACE;
double maxRate = 0.0;
std::map<cvf::StructGridInterface::FaceType, double> rateMap;
for ( auto face : m_allFaces )
{
RiaDefines::PhaseType dummy;
if ( face == neighborFaceIdx ) continue;
double faceRate = m_dataAccess->combinedFaceValueByArea( neighbor, face, m_phases, dummy ) * direction;
if ( ( ( direction < 0.0 ) && ( faceRate < maxRate ) ) || ( ( direction > 0.0 ) && ( faceRate > maxRate ) ) )
{
exitFace = face;
maxRate = faceRate;
}
if ( face % 2 != 0 )
rateMap[face] = -1.0 * faceRate * direction;
else
rateMap[face] = faceRate * direction;
}
flowVelocity = std::abs( maxRate );
faceIdx = exitFace;
cell = neighbor;
// add seeds for other faces with flow > threshold
for ( auto& kvp : rateMap )
{
if ( kvp.first == exitFace ) continue;
if ( std::abs( kvp.second ) < m_flowThreshold ) continue;
m_seeds.push_back( std::make_pair( neighbor.gridLocalCellIndex(), kvp.first ) );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimStreamlineGenerator2::growStreamlineFromTo( RimStreamline* streamline,
cvf::Vec3d startPos,
cvf::Vec3d endPos,
double flowVelocity,
RiaDefines::PhaseType dominantPhase )
{
double totDistance = endPos.pointDistance( startPos );
if ( totDistance < 0.1 ) return true;
if ( flowVelocity < m_flowThreshold ) return false;
cvf::Vec3d direction = endPos - startPos;
direction.normalize();
direction *= flowVelocity;
int nSteps = (int)std::round( ( totDistance / flowVelocity ) / m_resolution );
cvf::Vec3d curpos = startPos;
for ( int i = 0; i < nSteps; i++ )
{
streamline->addTracerPoint( curpos, direction, dominantPhase );
curpos = startPos + direction;
if ( streamline->size() > m_maxPoints ) return false;
}
return true;
}

View File

@@ -0,0 +1,55 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaDefines.h"
#include "RimStreamlineGeneratorBase.h"
#include <QString>
#include <list>
#include <set>
#include <utility>
class RigCell;
class RimStreamline;
class RimStreamlineGenerator2 : public RimStreamlineGeneratorBase
{
public:
RimStreamlineGenerator2( std::set<size_t>& wellCells );
~RimStreamlineGenerator2();
void generateTracer( RigCell cell, double direction, QString simWellName, std::list<RimStreamline*>& outStreamlines ) override;
protected:
void growStreamline( RimStreamline* streamline,
size_t cellIdx,
cvf::StructGridInterface::FaceType faceIdx,
double direction );
bool growStreamlineFromTo( RimStreamline* streamline,
cvf::Vec3d startPos,
cvf::Vec3d endpos,
double flowVelocity,
RiaDefines::PhaseType dominantPhase );
std::list<std::pair<size_t, cvf::StructGridInterface::FaceType>> m_seeds;
};

View File

@@ -0,0 +1,68 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 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 "RimStreamlineGeneratorBase.h"
#include "RigCell.h"
#include "RigMainGrid.h"
#include "RimStreamline.h"
#include "RimStreamlineDataAccess.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineGeneratorBase::RimStreamlineGeneratorBase( std::set<size_t>& wellCells )
: m_maxDays( 10000 )
, m_flowThreshold( 0.0 )
, m_resolution( 10.0 )
, m_wellCells( wellCells )
, m_dataAccess( nullptr )
, m_maxPoints( 1 )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineGeneratorBase::~RimStreamlineGeneratorBase()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineGeneratorBase::setLimits( double flowThreshold, int maxDays, double resolutionInDays )
{
m_flowThreshold = flowThreshold;
m_maxDays = maxDays;
m_resolution = resolutionInDays;
m_maxPoints = maxDays / resolutionInDays;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineGeneratorBase::initGenerator( RimStreamlineDataAccess* dataAccess, std::list<RiaDefines::PhaseType> phases )
{
m_dataAccess = dataAccess;
m_phases.clear();
for ( auto phase : phases )
m_phases.push_back( phase );
}

View File

@@ -0,0 +1,71 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaDefines.h"
#include "cvfStructGrid.h"
#include <list>
#include <set>
#include <QString>
class RigCell;
class RimStreamline;
class RigMainGrid;
class RigGridBase;
class RigResultAccessor;
class RigEclipseCaseData;
class RimStreamlineDataAccess;
class RimStreamlineGeneratorBase
{
public:
RimStreamlineGeneratorBase( std::set<size_t>& wellCells );
~RimStreamlineGeneratorBase();
void setLimits( double flowThreshold, int maxDays, double resolutionInDays );
void initGenerator( RimStreamlineDataAccess* dataAccess, std::list<RiaDefines::PhaseType> phases );
virtual void
generateTracer( RigCell cell, double direction, QString simWellName, std::list<RimStreamline*>& outStreamlines ) = 0;
protected:
double m_flowThreshold;
int m_maxDays;
double m_resolution;
size_t m_maxPoints;
std::set<size_t> m_visitedCells;
std::set<size_t>& m_wellCells;
RimStreamlineDataAccess* m_dataAccess;
std::list<RiaDefines::PhaseType> m_phases;
const std::list<cvf::StructGridInterface::FaceType> m_allFaces = { cvf::StructGridInterface::FaceType::POS_I,
cvf::StructGridInterface::FaceType::NEG_I,
cvf::StructGridInterface::FaceType::POS_J,
cvf::StructGridInterface::FaceType::NEG_J,
cvf::StructGridInterface::FaceType::POS_K,
cvf::StructGridInterface::FaceType::NEG_K };
};

View File

@@ -0,0 +1,688 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 versio<n.
//
// 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 "RimStreamlineInViewCollection.h"
#include "RigCaseCellResultsData.h"
#include "RigCell.h"
#include "RigEclipseCaseData.h"
#include "RigEclipseResultAddress.h"
#include "RigGridBase.h"
#include "RigMainGrid.h"
#include "RigResultAccessor.h"
#include "RigResultAccessorFactory.h"
#include "RigSimWellData.h"
#include "RigTracerPoint.h"
#include "RimEclipseCase.h"
#include "RimEclipseView.h"
#include "RimRegularLegendConfig.h"
#include "RimStreamline.h"
#include "RimStreamlineDataAccess.h"
#include "RimStreamlineGenerator2.h"
#include "RiaLogging.h"
#include "RiuViewer.h"
#include "cafPdmFieldScriptingCapability.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiDoubleSliderEditor.h"
#include "cafPdmUiSliderEditor.h"
#include "cafPdmUiTreeOrdering.h"
#include "cafProgressInfo.h"
#include "cvfCollection.h"
#include <cmath>
#include <QDebug>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
namespace caf
{
template <>
void AppEnum<RimStreamlineInViewCollection::VisualizationMode>::setUp()
{
addItem( RimStreamlineInViewCollection::VisualizationMode::ANIMATION, "ANIMATION", "Animation" );
addItem( RimStreamlineInViewCollection::VisualizationMode::MANUAL, "MANUAL", "Manual control" );
addItem( RimStreamlineInViewCollection::VisualizationMode::VECTORS, "VECTORS", "Vectors" );
setDefault( RimStreamlineInViewCollection::VisualizationMode::ANIMATION );
}
template <>
void AppEnum<RimStreamlineInViewCollection::StreamlinePhaseType>::setUp()
{
addItem( RimStreamlineInViewCollection::StreamlinePhaseType::OIL, "OIL", "Oil" );
addItem( RimStreamlineInViewCollection::StreamlinePhaseType::GAS, "GAS", "Gas" );
addItem( RimStreamlineInViewCollection::StreamlinePhaseType::WATER, "WATER", "Water" );
addItem( RimStreamlineInViewCollection::StreamlinePhaseType::COMBINED, "COMBINED", "Combined" );
setDefault( RimStreamlineInViewCollection::StreamlinePhaseType::OIL );
}
} // namespace caf
CAF_PDM_SOURCE_INIT( RimStreamlineInViewCollection, "StreamlineInViewCollection" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineInViewCollection::RimStreamlineInViewCollection()
: m_shouldGenerateTracers( false )
, m_currentTimestep( -1 )
{
CAF_PDM_InitScriptableObject( "Streamlines", ":/Erase.png", "", "" );
CAF_PDM_InitFieldNoDefault( &m_legendConfig, "LegendDefinition", "Color Legend", "", "", "" );
m_legendConfig = new RimRegularLegendConfig();
m_legendConfig.uiCapability()->setUiHidden( true );
CAF_PDM_InitScriptableFieldNoDefault( &m_collectionName, "Name", "Name", "", "", "" );
m_collectionName = "Streamlines";
m_collectionName.uiCapability()->setUiReadOnly( true );
CAF_PDM_InitScriptableFieldNoDefault( &m_flowThreshold, "FlowThreshold", "Flow Threshold [m/day]", "", "", "" );
m_flowThreshold = 0.001;
CAF_PDM_InitScriptableFieldNoDefault( &m_lengthThreshold, "LengthThreshold", "Minimum Length [m]", "", "", "" );
m_lengthThreshold = 100.0;
CAF_PDM_InitScriptableFieldNoDefault( &m_resolution, "Resolution", "Resolution [days]", "", "", "" );
m_resolution = 20.0;
CAF_PDM_InitScriptableFieldNoDefault( &m_maxDays, "MaxDays", "Max Days", "", "", "" );
m_maxDays = 50000;
CAF_PDM_InitScriptableFieldNoDefault( &m_useProducers, "UseProducers", "Producer Wells", "", "", "" );
m_useProducers = true;
CAF_PDM_InitScriptableFieldNoDefault( &m_useInjectors, "UseInjectors", "Injector Wells", "", "", "" );
m_useInjectors = true;
CAF_PDM_InitScriptableField( &m_phases, "Phase", StreamlinePhaseTypeEnum( StreamlinePhaseType::OIL ), "Phase", "", "", "" );
CAF_PDM_InitField( &m_isActive, "isActive", false, "Active", "", "", "" );
m_isActive.uiCapability()->setUiHidden( true );
CAF_PDM_InitFieldNoDefault( &m_visualizationMode, "VisualizationMode", "Visualization Mode", "", "", "" );
CAF_PDM_InitFieldNoDefault( &m_animationSpeed, "AnimationSpeed", "Animation Speed", "", "", "" );
m_animationSpeed.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
m_animationSpeed = 10;
CAF_PDM_InitFieldNoDefault( &m_animationIndex, "AnimationIndex", "Animation Index", "", "", "" );
m_animationIndex.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
m_animationIndex = 0;
m_maxAnimationIndex = 0;
CAF_PDM_InitFieldNoDefault( &m_scaleFactor, "ScaleFactor", "Scale Factor", "", "", "" );
m_scaleFactor.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() );
m_scaleFactor = 100.0;
CAF_PDM_InitFieldNoDefault( &m_tracerLength, "TracerLength", "Tracer Length", "", "", "" );
m_tracerLength.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
m_tracerLength = 100;
CAF_PDM_InitFieldNoDefault( &m_injectionDeltaTime, "InjectionDeltaTime", "Pause between injections", "", "", "" );
m_injectionDeltaTime.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
m_injectionDeltaTime = 500;
CAF_PDM_InitScriptableFieldNoDefault( &m_streamlines, "Streamlines", "Streamlines", "", "", "" );
m_streamlines.uiCapability()->setUiTreeHidden( true );
m_streamlines.xmlCapability()->disableIO();
m_eclipseCase = nullptr;
// we are a topmost folder, do not delete us
setDeletable( false );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineInViewCollection::~RimStreamlineInViewCollection()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmFieldHandle* RimStreamlineInViewCollection::objectToggleField()
{
return &m_isActive;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::setEclipseCase( RimEclipseCase* reservoir )
{
m_shouldGenerateTracers = true;
m_eclipseCase = reservoir;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimEclipseCase* RimStreamlineInViewCollection::eclipseCase() const
{
return m_eclipseCase;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimStreamlineInViewCollection::isActive() const
{
return m_isActive();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::list<RiaDefines::PhaseType> RimStreamlineInViewCollection::phases() const
{
std::list<RiaDefines::PhaseType> retval;
switch ( m_phases() )
{
case StreamlinePhaseType::OIL:
retval.push_back( RiaDefines::PhaseType::OIL_PHASE );
break;
case StreamlinePhaseType::GAS:
retval.push_back( RiaDefines::PhaseType::GAS_PHASE );
break;
case StreamlinePhaseType::WATER:
retval.push_back( RiaDefines::PhaseType::WATER_PHASE );
break;
case StreamlinePhaseType::COMBINED:
retval.push_back( RiaDefines::PhaseType::OIL_PHASE );
retval.push_back( RiaDefines::PhaseType::GAS_PHASE );
retval.push_back( RiaDefines::PhaseType::WATER_PHASE );
break;
default:
break;
}
return retval;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimStreamlineInViewCollection::VisualizationMode RimStreamlineInViewCollection::visualizationMode() const
{
return m_visualizationMode();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::list<RigTracer>& RimStreamlineInViewCollection::tracers()
{
m_activeTracers.clear();
for ( auto& streamline : m_streamlines() )
{
if ( streamline->tracer().size() > 1 )
{
m_activeTracers.push_back( streamline->tracer() );
}
}
return m_activeTracers;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const RimRegularLegendConfig* RimStreamlineInViewCollection::legendConfig() const
{
return m_legendConfig();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::mappingRange( double& min, double& max ) const
{
min = HUGE_VAL;
max = -HUGE_VAL;
for ( auto& streamline : m_streamlines() )
{
const RigTracer& tracer = streamline->tracer();
for ( size_t i = 0; i < tracer.tracerPoints().size() - 1; i++ )
{
min = std::min( min, tracer.tracerPoints()[i].absValue() );
max = std::max( max, tracer.tracerPoints()[i].absValue() );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::updateLegendRangesTextAndVisibility( RiuViewer* nativeOrOverrideViewer,
bool isUsingOverrideViewer )
{
if ( m_isActive() && ( m_streamlines.size() > 0 ) && m_legendConfig->showLegend() )
{
QString title = "Streamlines: \n";
title += m_phases().uiText() + " flow\n";
m_legendConfig->setTitle( title );
double minResultValue;
double maxResultValue;
mappingRange( minResultValue, maxResultValue );
m_legendConfig->setAutomaticRanges( minResultValue, maxResultValue, minResultValue, maxResultValue );
double posClosestToZero = HUGE_VAL;
double negClosestToZero = -HUGE_VAL;
m_legendConfig->setClosestToZeroValues( posClosestToZero, negClosestToZero, posClosestToZero, negClosestToZero );
nativeOrOverrideViewer->addColorLegendToBottomLeftCorner( m_legendConfig->titledOverlayFrame(),
isUsingOverrideViewer );
}
}
//--------------------------------------------------------------------------------------------------
/// Main entry point for triggering a streamline update
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::updateFromCurrentTimeStep( int timeStep )
{
if ( timeStep != m_currentTimestep )
{
m_shouldGenerateTracers = true;
m_currentTimestep = timeStep;
}
updateStreamlines();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimStreamlineInViewCollection::animationSpeed() const
{
return m_animationSpeed();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimStreamlineInViewCollection::animationIndex() const
{
return m_animationIndex();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimStreamlineInViewCollection::scaleFactor() const
{
return m_scaleFactor();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimStreamlineInViewCollection::tracerLength() const
{
return m_tracerLength();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimStreamlineInViewCollection::injectionDeltaTime() const
{
return m_injectionDeltaTime();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::refresh()
{
m_shouldGenerateTracers = true;
updateStreamlines();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::findStartCells( int timeIdx,
std::vector<std::pair<QString, RigCell>>& outInjectorCells,
std::vector<std::pair<QString, RigCell>>& outProducerCells )
{
// get the simulation wells
const cvf::Collection<RigSimWellData>& simWellData = eclipseCase()->eclipseCaseData()->wellResults();
std::vector<const RigGridBase*> grids;
eclipseCase()->eclipseCaseData()->allGrids( &grids );
// go through all sim wells and find all open producer and injector cells for the given timestep
for ( auto& swdata : simWellData )
{
if ( !swdata->hasWellResult( timeIdx ) || !swdata->hasAnyValidCells( timeIdx ) ) continue;
RigWellResultFrame frame = swdata->wellResultFrame( timeIdx );
for ( auto& branch : frame.m_wellResultBranches )
{
for ( const auto& point : branch.m_branchResultPoints )
{
if ( point.isValid() && point.m_isOpen )
{
RigCell cell = grids[point.m_gridIndex]->cell( point.m_gridCellIndex );
if ( frame.m_productionType == RigWellResultFrame::WellProductionType::PRODUCER )
{
outProducerCells.push_back( std::pair<QString, RigCell>( swdata->m_wellName, cell ) );
}
else if ( frame.m_productionType != RigWellResultFrame::WellProductionType::UNDEFINED_PRODUCTION_TYPE )
{
outInjectorCells.push_back( std::pair<QString, RigCell>( swdata->m_wellName, cell ) );
}
m_wellCellIds.insert( cell.mainGridCellIndex() );
}
}
}
}
}
//--------------------------------------------------------------------------------------------------
/// Main entry point for generating streamlines/tracers
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::updateStreamlines()
{
bool bNeedRedraw = ( m_streamlines.size() > 0 );
// get the view
RimEclipseView* eclView = nullptr;
this->firstAncestorOrThisOfType( eclView );
if ( !eclView ) return;
if ( m_shouldGenerateTracers && isActive() )
{
// reset generated streamlines
m_streamlines().clear();
m_wellCellIds.clear();
// get current simulation timestep
int timeIdx = eclView->currentTimeStep();
// get the well cells we should start growing from
std::vector<std::pair<QString, RigCell>> seedCellsInjector;
std::vector<std::pair<QString, RigCell>> seedCellsProducer;
findStartCells( timeIdx, seedCellsInjector, seedCellsProducer );
// set up the data access helper
RimStreamlineDataAccess dataAccess;
bool accessOk = dataAccess.setupDataAccess( eclipseCase()->eclipseCaseData()->mainGrid(),
eclipseCase()->eclipseCaseData(),
phases(),
timeIdx );
// did we find the data we needed?
if ( accessOk )
{
// setup the streamline generator to use
RimStreamlineGenerator2 generator( m_wellCellIds );
generator.setLimits( m_flowThreshold, m_maxDays, m_resolution );
generator.initGenerator( &dataAccess, phases() );
const int reverseDirection = -1.0;
const int normalDirection = 1.0;
std::list<RimStreamline*> streamlines;
size_t seedsCount = 0;
if ( m_useInjectors() ) seedsCount += seedCellsInjector.size();
if ( m_useProducers() ) seedsCount += seedCellsProducer.size();
caf::ProgressInfo streamlineProgress( seedsCount, "Generating Streamlines" );
// generate tracers for all injectors
if ( m_useInjectors() )
{
for ( auto& cellinfo : seedCellsInjector )
{
generator.generateTracer( cellinfo.second, normalDirection, cellinfo.first, streamlines );
streamlineProgress.incrementProgress();
}
}
// generate tracers for all producers, make sure to invert the direction to backtrack the traces
if ( m_useProducers() )
{
for ( auto& cellinfo : seedCellsProducer )
{
generator.generateTracer( cellinfo.second, reverseDirection, cellinfo.first, streamlines );
streamlineProgress.incrementProgress();
}
}
// get rid of empty and too short streamlines
m_maxAnimationIndex = 0;
for ( auto& sline : streamlines )
{
if ( sline && sline->size() > 1 )
{
double distance = sline->tracer().totalDistance();
if ( distance >= m_lengthThreshold )
{
m_maxAnimationIndex = std::max( sline->size(), m_maxAnimationIndex );
sline->generateStatistics();
m_streamlines.push_back( sline );
sline = nullptr;
}
if ( sline ) delete sline;
}
}
bNeedRedraw = true;
}
m_shouldGenerateTracers = false;
}
if ( bNeedRedraw )
{
eclView->scheduleCreateDisplayModelAndRedraw();
}
}
//--------------------------------------------------------------------------------------------------
// debug output
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::outputSummary() const
{
qDebug() << "Generated" << m_streamlines.size() << " tracers";
for ( auto s : m_streamlines )
{
QString debStr( "Tracer for well " );
debStr += s->simWellName();
debStr += " of length ";
debStr += QString::number( s->tracer().totalDistance(), 'f', 2 );
debStr += " meters and ";
debStr += QString::number( s->tracer().size() );
debStr += " points.";
qDebug() << debStr;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::initAfterRead()
{
RimEclipseView* eclView = nullptr;
this->firstAncestorOrThisOfType( eclView );
if ( eclView && m_isActive() ) eclView->requestAnimationTimer();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_collectionName );
caf::PdmUiGroup* dataGroup = uiOrdering.addNewGroup( "Data Selection" );
dataGroup->add( &m_phases );
dataGroup->add( &m_flowThreshold );
dataGroup->add( &m_lengthThreshold );
dataGroup->add( &m_resolution );
dataGroup->add( &m_maxDays );
caf::PdmUiGroup* wellGroup = uiOrdering.addNewGroup( "Well Selection" );
wellGroup->add( &m_useInjectors );
wellGroup->add( &m_useProducers );
caf::PdmUiGroup* visualizationGroup = uiOrdering.addNewGroup( "Visualization Settings" );
visualizationGroup->add( &m_visualizationMode );
if ( m_visualizationMode() == VisualizationMode::ANIMATION )
{
visualizationGroup->add( &m_animationSpeed );
visualizationGroup->add( &m_tracerLength );
visualizationGroup->add( &m_injectionDeltaTime );
}
if ( m_visualizationMode() == VisualizationMode::MANUAL )
{
visualizationGroup->add( &m_animationIndex );
}
if ( m_visualizationMode() == VisualizationMode::VECTORS )
{
visualizationGroup->add( &m_scaleFactor );
}
uiOrdering.skipRemainingFields();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering,
QString uiConfigName /*= "" */ )
{
uiTreeOrdering.add( &m_legendConfig );
uiTreeOrdering.skipRemainingChildren();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::defineEditorAttribute( const caf::PdmFieldHandle* field,
QString uiConfigName,
caf::PdmUiEditorAttribute* attribute )
{
if ( field == &m_animationSpeed )
{
caf::PdmUiSliderEditorAttribute* myAttr = dynamic_cast<caf::PdmUiSliderEditorAttribute*>( attribute );
if ( myAttr )
{
myAttr->m_minimum = 1;
myAttr->m_maximum = 100;
}
}
else if ( field == &m_animationIndex )
{
caf::PdmUiSliderEditorAttribute* myAttr = dynamic_cast<caf::PdmUiSliderEditorAttribute*>( attribute );
if ( myAttr )
{
myAttr->m_minimum = 0;
myAttr->m_maximum = static_cast<int>( m_maxAnimationIndex );
}
}
else if ( field == &m_tracerLength )
{
caf::PdmUiSliderEditorAttribute* myAttr = dynamic_cast<caf::PdmUiSliderEditorAttribute*>( attribute );
if ( myAttr )
{
myAttr->m_minimum = 1;
myAttr->m_maximum = static_cast<int>( 1000 );
}
}
else if ( field == &m_injectionDeltaTime )
{
caf::PdmUiSliderEditorAttribute* myAttr = dynamic_cast<caf::PdmUiSliderEditorAttribute*>( attribute );
if ( myAttr )
{
myAttr->m_minimum = 1;
myAttr->m_maximum = static_cast<int>( 1000 );
}
}
else if ( field == &m_scaleFactor )
{
caf::PdmUiDoubleSliderEditorAttribute* myAttr = dynamic_cast<caf::PdmUiDoubleSliderEditorAttribute*>( attribute );
if ( myAttr )
{
myAttr->m_minimum = 0.1;
myAttr->m_maximum = 10000.0;
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimStreamlineInViewCollection::fieldChangedByUi( const caf::PdmFieldHandle* changedField,
const QVariant& oldValue,
const QVariant& newValue )
{
if ( changedField == &m_animationSpeed || changedField == &m_animationIndex ||
changedField == &m_injectionDeltaTime || changedField == &m_tracerLength )
{
return;
}
if ( changedField == &m_visualizationMode &&
qvariant_cast<int>( newValue ) != static_cast<int>( VisualizationMode::VECTORS ) &&
qvariant_cast<int>( oldValue ) != static_cast<int>( VisualizationMode::VECTORS ) )
{
return;
}
if ( changedField == &m_lengthThreshold || changedField == &m_flowThreshold || changedField == &m_resolution ||
changedField == &m_maxDays || changedField == &m_useProducers || changedField == &m_useInjectors ||
changedField == &m_phases )
{
m_shouldGenerateTracers = true;
}
RimEclipseView* eclView = nullptr;
this->firstAncestorOrThisOfType( eclView );
if ( changedField == &m_isActive )
{
if ( eclView )
{
if ( m_isActive() )
eclView->requestAnimationTimer();
else
eclView->releaseAnimationTimer();
}
}
if ( eclView ) eclView->scheduleCreateDisplayModelAndRedraw();
}

View File

@@ -0,0 +1,138 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaDefines.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmChildField.h"
#include "cafPdmField.h"
#include "cafPdmObject.h"
#include <list>
#include <set>
#include <vector>
class RimStreamline;
class RimEclipseCase;
class RimRegularLegendConfig;
class RigTracer;
class RigCell;
class RiuViewer;
class RimStreamlineInViewCollection : public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
enum class VisualizationMode
{
ANIMATION = 0,
MANUAL,
VECTORS
};
using VisualizationModeEnum = caf::AppEnum<VisualizationMode>;
enum class StreamlinePhaseType
{
OIL,
GAS,
WATER,
COMBINED
};
using StreamlinePhaseTypeEnum = caf::AppEnum<StreamlinePhaseType>;
public:
RimStreamlineInViewCollection();
~RimStreamlineInViewCollection() override;
void setEclipseCase( RimEclipseCase* reservoir );
RimEclipseCase* eclipseCase() const;
std::list<RiaDefines::PhaseType> phases() const;
VisualizationMode visualizationMode() const;
size_t animationSpeed() const;
size_t animationIndex() const;
double scaleFactor() const;
size_t tracerLength() const;
size_t injectionDeltaTime() const;
bool isActive() const;
void refresh();
const std::list<RigTracer>& tracers();
const RimRegularLegendConfig* legendConfig() const;
void mappingRange( double& min, double& max ) const;
void updateLegendRangesTextAndVisibility( RiuViewer* nativeOrOverrideViewer, bool isUsingOverrideViewer );
void updateFromCurrentTimeStep( int timeStep );
protected:
caf::PdmFieldHandle* objectToggleField() override;
void updateStreamlines();
void initAfterRead() override;
private:
void findStartCells( int timeIdx,
std::vector<std::pair<QString, RigCell>>& outInjectorCells,
std::vector<std::pair<QString, RigCell>>& outProducerCells );
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override;
void defineEditorAttribute( const caf::PdmFieldHandle* field,
QString uiConfigName,
caf::PdmUiEditorAttribute* attribute ) override;
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void outputSummary() const;
private:
caf::PdmField<bool> m_isActive;
caf::PdmField<QString> m_collectionName;
caf::PdmField<double> m_lengthThreshold;
caf::PdmField<double> m_flowThreshold;
caf::PdmField<double> m_resolution;
caf::PdmField<double> m_maxDays;
caf::PdmField<bool> m_useProducers;
caf::PdmField<bool> m_useInjectors;
caf::PdmPointer<RimEclipseCase> m_eclipseCase;
caf::PdmChildArrayField<RimStreamline*> m_streamlines;
caf::PdmField<StreamlinePhaseTypeEnum> m_phases;
caf::PdmField<VisualizationModeEnum> m_visualizationMode;
caf::PdmField<size_t> m_animationSpeed;
caf::PdmField<size_t> m_animationIndex;
caf::PdmField<double> m_scaleFactor;
caf::PdmField<size_t> m_tracerLength;
caf::PdmField<size_t> m_injectionDeltaTime;
size_t m_maxAnimationIndex;
bool m_shouldGenerateTracers;
int m_currentTimestep;
std::list<RigTracer> m_activeTracers;
std::set<size_t> m_wellCellIds;
caf::PdmChildField<RimRegularLegendConfig*> m_legendConfig;
};