ResInsight/ApplicationLibCode/UserInterface/RiuMohrsCirclePlot.cpp
2024-09-19 12:47:29 +02:00

756 lines
26 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2018- 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 "RiuMohrsCirclePlot.h"
#include "RiaColorTables.h"
#include "RiaLogging.h"
#include "RigFemPart.h"
#include "RigFemPartCollection.h"
#include "RigFemPartGrid.h"
#include "RigFemPartResultCalculatorDSM.h"
#include "RigFemPartResultsCollection.h"
#include "RigFemResultPosEnum.h"
#include "RigGeoMechCaseData.h"
#include "Rim2dIntersectionView.h"
#include "RimGeoMechCase.h"
#include "RimGeoMechCellColors.h"
#include "RimGeoMechResultDefinition.h"
#include "RimGeoMechView.h"
#include "Riu3dSelectionManager.h"
#include "RiuQwtPlotTools.h"
#include <QPainterPath>
#include <QPen>
#include <QTimer>
#include <QWidget>
#include "qwt_legend.h"
#include "qwt_plot_curve.h"
#include "qwt_plot_layout.h"
#include "qwt_plot_rescaler.h"
#include "qwt_plot_shapeitem.h"
#include <cmath>
//==================================================================================================
///
/// \class RiuMohrsCirclePlot
///
///
///
//==================================================================================================
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuMohrsCirclePlot::RiuMohrsCirclePlot( QWidget* parent )
: RiuDockedQwtPlot( parent )
, m_viewToFollowAnimationFrom( nullptr )
, m_scheduleUpdateAxisScaleTimer( nullptr )
{
RiuQwtPlotTools::setCommonPlotBehaviour( this );
setAxesCount( QwtAxis::XBottom, 1 );
setAxesCount( QwtAxis::YLeft, 1 );
setAxisTitle( QwtAxis::XBottom, "Effective Normal Stress" );
setAxisTitle( QwtAxis::YLeft, "Shear Stress" );
applyFontSizes( false );
// The legend will be deleted in the destructor of the plot or when
// another legend is inserted.
QwtLegend* legend = new QwtLegend( this );
insertLegend( legend, BottomLegend );
// setTitle(QString("SE"));
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RiuMohrsCirclePlot::~RiuMohrsCirclePlot()
{
deletePlotItems();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::appendSelection( const RiuSelectionItem* selectionItem )
{
m_viewToFollowAnimationFrom = nullptr;
if ( isVisible() )
{
Rim3dView* newFollowAnimView = nullptr;
RiuGeoMechSelectionItem* geoMechSelectionItem = nullptr;
geoMechSelectionItem = extractGeoMechSelectionItem( selectionItem, newFollowAnimView );
if ( geoMechSelectionItem )
{
const size_t gridIndex = geoMechSelectionItem->m_gridIndex;
RigFemPart* femPart = geoMechSelectionItem->m_resultDefinition->ownerCaseData()->femParts()->part( gridIndex );
const size_t cellIndex = geoMechSelectionItem->m_cellIndex;
const int elmId = femPart->elmId( cellIndex );
const cvf::Color3f color = geoMechSelectionItem->m_color;
addOrUpdateCurves( geoMechSelectionItem->m_resultDefinition,
geoMechSelectionItem->m_timestepIdx,
geoMechSelectionItem->m_frameIdx,
gridIndex,
cellIndex,
elmId,
cvf::Color3ub( color ) );
updatePlot();
m_viewToFollowAnimationFrom = geoMechSelectionItem->m_view;
}
}
else
{
clearPlot();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::clearPlot()
{
deletePlotItems();
m_viewToFollowAnimationFrom = nullptr;
replot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::updateOnTimeStepChanged( Rim3dView* changedView )
{
if ( !isVisible() )
{
return;
}
// Don't update the plot if the view that changed time step is different
// from the view that was the source of the current plot
if ( changedView != m_viewToFollowAnimationFrom )
{
return;
}
std::vector<MohrsCirclesInfo> mohrsCiclesInfosCopy = m_mohrsCiclesInfos;
deletePlotItems();
RimGeoMechView* geomechView = dynamic_cast<RimGeoMechView*>( changedView );
if ( geomechView == nullptr ) return;
auto [stepIdx, frameIdx] = geomechView->currentStepAndDataFrame();
for ( const MohrsCirclesInfo& mohrInfo : mohrsCiclesInfosCopy )
{
addOrUpdateCurves( mohrInfo.geomResDef, stepIdx, frameIdx, mohrInfo.gridIndex, mohrInfo.elmIndex, mohrInfo.elmId, mohrInfo.color );
}
updatePlot();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QSize RiuMohrsCirclePlot::sizeHint() const
{
return QSize( 100, 100 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QSize RiuMohrsCirclePlot::minimumSizeHint() const
{
return QSize( 0, 0 );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::addOrUpdateMohrCircleCurves( const MohrsCirclesInfo& mohrsCirclesInfo )
{
const cvf::Vec3f& principals = mohrsCirclesInfo.principals;
std::array<std::pair<double /*radius*/, double /*centerX*/>, 3> mohrsCircles;
mohrsCircles[0].first = ( principals[0] - principals[2] ) / 2.0;
mohrsCircles[0].second = ( principals[0] + principals[2] ) / 2.0;
mohrsCircles[1].first = ( principals[1] - principals[2] ) / 2.0;
mohrsCircles[1].second = ( principals[1] + principals[2] ) / 2.0;
mohrsCircles[2].first = ( principals[0] - principals[1] ) / 2.0;
mohrsCircles[2].second = ( principals[0] + principals[1] ) / 2.0;
for ( size_t i = 0; i < 3; i++ )
{
QwtPlotShapeItem* plotItem = new QwtPlotShapeItem( "Circle" );
QPainterPath* circleDrawing = new QPainterPath();
QPointF center( mohrsCircles[i].second, 0 );
circleDrawing->addEllipse( center, mohrsCircles[i].first, mohrsCircles[i].first );
plotItem->setPen( QColor( mohrsCirclesInfo.color.r(), mohrsCirclesInfo.color.g(), mohrsCirclesInfo.color.b() ) );
plotItem->setShape( *circleDrawing );
plotItem->setRenderHint( QwtPlotItem::RenderAntialiased, true );
if ( i == 0 )
{
QString textBuilder;
textBuilder.append( QString( "<b>FOS</b>: %1, " ).arg( QString::number( mohrsCirclesInfo.factorOfSafety, 'f', 2 ) ) );
textBuilder.append( QString( "<b>Element Id</b>: %1, <b>ijk</b>[%2, %3, %4]," )
.arg( mohrsCirclesInfo.elmId )
.arg( mohrsCirclesInfo.i )
.arg( mohrsCirclesInfo.j )
.arg( mohrsCirclesInfo.k ) );
textBuilder.append( QString( "<b>&sigma;<sub>1</sub></b>: %1, " ).arg( principals[0] ) );
textBuilder.append( QString( "<b>&sigma;<sub>2</sub></b>: %1, " ).arg( principals[1] ) );
textBuilder.append( QString( "<b>&sigma;<sub>3</sub></b>: %1" ).arg( principals[2] ) );
plotItem->setTitle( textBuilder );
plotItem->setItemAttribute( QwtPlotItem::Legend );
}
plotItem->attach( this );
m_circlePlotItems.push_back( plotItem );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::deleteCircles()
{
for ( size_t i = 0; i < m_circlePlotItems.size(); i++ )
{
m_circlePlotItems[i]->detach();
delete m_circlePlotItems[i];
}
m_circlePlotItems.clear();
for ( size_t i = 0; i < m_transparentCurves.size(); i++ )
{
m_transparentCurves[i]->detach();
delete m_transparentCurves[i];
}
m_transparentCurves.clear();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::addorUpdateEnvelopeCurve( const cvf::Vec3f& principals, const RimGeoMechCase* geomCase )
{
if ( !geomCase ) return;
double cohesion = geomCase->cohesion();
double frictionAngle = geomCase->frictionAngleDeg();
if ( cohesion == HUGE_VAL || frictionAngle == HUGE_VAL || frictionAngle >= 90 )
{
return;
}
double xVals[2];
double yVals[2];
double tanFrictionAngle = cvf::Math::abs( cvf::Math::tan( cvf::Math::toRadians( frictionAngle ) ) );
if ( tanFrictionAngle == 0 || tanFrictionAngle == HUGE_VAL )
{
return;
}
double x = cohesion / tanFrictionAngle;
xVals[0] = -x;
xVals[1] = principals[0];
yVals[0] = 0;
yVals[1] = ( cohesion / x ) * ( x + principals[0] );
// If envelope for the view already exists, check if a "larger" envelope should be created
if ( m_envolopePlotItems.find( geomCase ) != m_envolopePlotItems.end() )
{
if ( yVals[1] <= m_envolopePlotItems[geomCase]->maxYValue() )
{
return;
}
else
{
m_envolopePlotItems[geomCase]->detach();
delete m_envolopePlotItems[geomCase];
m_envolopePlotItems.erase( geomCase );
}
}
QwtPlotCurve* qwtCurve = new QwtPlotCurve();
qwtCurve->setSamples( xVals, yVals, 2 );
qwtCurve->setStyle( QwtPlotCurve::Lines );
qwtCurve->setRenderHint( QwtPlotItem::RenderAntialiased, true );
const QPen curvePen( envelopeColor( geomCase ) );
qwtCurve->setPen( curvePen );
qwtCurve->setTitle( QString( "<b>Envelope for %1</b>, (<b>S<sub>0</sub></b>: %2, <b>&Phi;</b>: %3)" )
.arg( geomCase->caseUserDescription() )
.arg( cohesion )
.arg( frictionAngle ) );
qwtCurve->attach( this );
m_envolopePlotItems[geomCase] = qwtCurve;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::deleteEnvelopes()
{
for ( const std::pair<const RimGeoMechCase* const, QwtPlotCurve*>& envelope : m_envolopePlotItems )
{
envelope.second->detach();
delete envelope.second;
}
m_envolopePlotItems.clear();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuMohrsCirclePlot::addOrUpdateCurves( const RimGeoMechResultDefinition* geomResDef,
int timeStepIndex,
int frameIndex,
size_t gridIndex,
size_t elmIndex,
int elmId,
const cvf::Color3ub& color )
{
RigFemPart* femPart = geomResDef->ownerCaseData()->femParts()->part( gridIndex );
if ( femPart->elementType( elmIndex ) != RigElementType::HEX8P ) return false;
RigFemPartResultsCollection* resultCollection = geomResDef->geoMechCase()->geoMechData()->femPartResults();
RigFemResultAddress address( RigFemResultPosEnum::RIG_ELEMENT_NODAL, "SE", "" );
// TODO: All tensors are calculated every time this function is called. FIX
std::vector<caf::Ten3f> vertexTensors = resultCollection->tensors( address, 0, timeStepIndex, frameIndex );
if ( vertexTensors.empty() )
{
return false;
}
// Calculate average tensor in element
caf::Ten3f tensorSumOfElmNodes = vertexTensors[femPart->elementNodeResultIdx( (int)elmIndex, 0 )];
for ( int i = 1; i < 8; i++ )
{
tensorSumOfElmNodes = tensorSumOfElmNodes + vertexTensors[femPart->elementNodeResultIdx( (int)elmIndex, i )];
}
caf::Ten3f elmTensor = tensorSumOfElmNodes * ( 1.0 / 8.0 );
cvf::Vec3f principals = elmTensor.calculatePrincipals( nullptr );
if ( !isValidPrincipals( principals ) )
{
return false;
}
double cohesion = geomResDef->geoMechCase()->cohesion();
double frictionAngleDeg = geomResDef->geoMechCase()->frictionAngleDeg();
size_t i, j, k;
bool validIndex = femPart->getOrCreateStructGrid()->ijkFromCellIndex( elmIndex, &i, &j, &k );
if ( validIndex )
{
int elmId = femPart->elmId( elmIndex );
MohrsCirclesInfo
mohrsCircle( principals, gridIndex, elmIndex, elmId, i, j, k, geomResDef, calculateFOS( principals, frictionAngleDeg, cohesion ), color );
m_mohrsCiclesInfos.push_back( mohrsCircle );
addorUpdateEnvelopeCurve( mohrsCircle.principals, mohrsCircle.geomResDef->geoMechCase() );
addOrUpdateMohrCircleCurves( mohrsCircle );
updateTransparentCurvesOnPrincipals();
return true;
}
auto txt = QString( "Not able to create Mohr plot for element index:%1, element ID:%2" ).arg( elmIndex ).arg( elmId );
RiaLogging::warning( txt );
return false;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::updatePlot()
{
setAxesScaleAndReplot();
// Update axis scale is called one more time because the legend which is added on a later stage may disrupt the canvas
scheduleUpdateAxisScale();
}
//--------------------------------------------------------------------------------------------------
/// Add a transparent curve to make tooltip available on principals crossing the x-axis
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::updateTransparentCurvesOnPrincipals()
{
for ( size_t i = 0; i < m_transparentCurves.size(); i++ )
{
m_transparentCurves[i]->detach();
delete m_transparentCurves[i];
}
m_transparentCurves.clear();
for ( const MohrsCirclesInfo& mohrCircleInfo : m_mohrsCiclesInfos )
{
QwtPlotCurve* transparentCurve = new QwtPlotCurve();
QVector<QPointF> qVectorPoints;
qVectorPoints.push_back( QPointF( mohrCircleInfo.principals[0], 0 ) );
qVectorPoints.push_back( QPointF( mohrCircleInfo.principals[1], 0 ) );
qVectorPoints.push_back( QPointF( mohrCircleInfo.principals[2], 0 ) );
transparentCurve->setSamples( qVectorPoints );
transparentCurve->setYAxis( QwtAxis::YLeft );
transparentCurve->setStyle( QwtPlotCurve::NoCurve );
transparentCurve->setLegendAttribute( QwtPlotCurve::LegendNoAttribute );
transparentCurve->attach( this );
m_transparentCurves.push_back( transparentCurve );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RiuMohrsCirclePlot::largestCircleRadiusInPlot() const
{
double currentLargestDiameter = -HUGE_VAL;
for ( const MohrsCirclesInfo& mohrCircleInfo : m_mohrsCiclesInfos )
{
if ( mohrCircleInfo.principals[0] > currentLargestDiameter )
{
currentLargestDiameter = mohrCircleInfo.principals[0] - mohrCircleInfo.principals[2];
}
}
return currentLargestDiameter / 2;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RiuMohrsCirclePlot::smallestPrincipal() const
{
double currentSmallestPrincipal = HUGE_VAL;
for ( const MohrsCirclesInfo& mohrCircleInfo : m_mohrsCiclesInfos )
{
if ( mohrCircleInfo.principals[2] < currentSmallestPrincipal )
{
currentSmallestPrincipal = mohrCircleInfo.principals[2];
}
}
return currentSmallestPrincipal;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RiuMohrsCirclePlot::largestPrincipal() const
{
double currentLargestPrincipal = -HUGE_VAL;
for ( const MohrsCirclesInfo& mohrCircleInfo : m_mohrsCiclesInfos )
{
if ( mohrCircleInfo.principals[0] > currentLargestPrincipal )
{
currentLargestPrincipal = mohrCircleInfo.principals[0];
}
}
return currentLargestPrincipal;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiuMohrsCirclePlot::isValidPrincipals( const cvf::Vec3f& principals )
{
float p1 = principals[0];
float p2 = principals[1];
float p3 = principals[2];
// Inf
if ( p1 == HUGE_VAL || p2 == HUGE_VAL || p3 == HUGE_VAL )
{
return false;
}
// Nan
if ( ( p1 != p1 ) || ( p2 != p2 ) || p3 != p3 )
{
return false;
}
// Principal rules:
if ( ( p1 < p2 ) || ( p2 < p3 ) )
{
return false;
}
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
float RiuMohrsCirclePlot::calculateFOS( const cvf::Vec3f& principals, double frictionAngle, double cohesion )
{
if ( cvf::Math::cos( frictionAngle ) == 0 )
{
return std::nan( "" );
}
float se1 = principals[0];
float se3 = principals[2];
float tanFricAng = cvf::Math::tan( cvf::Math::toRadians( frictionAngle ) );
float cohPrTanFricAngle = 1.0f * cohesion / tanFricAng;
float dsm = RigFemPartResultCalculatorDSM::dsm( se1, se3, tanFricAng, cohPrTanFricAngle );
return 1.0f / dsm;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QColor RiuMohrsCirclePlot::envelopeColor( const RimGeoMechCase* geomCase )
{
if ( m_envolopeColors.find( geomCase ) == m_envolopeColors.end() )
{
cvf::Color3ub cvfColor = RiaColorTables::summaryCurveDefaultPaletteColors().cycledColor3ub( m_envolopeColors.size() );
QColor color( cvfColor.r(), cvfColor.g(), cvfColor.b() );
m_envolopeColors[geomCase] = color;
}
return m_envolopeColors[geomCase];
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::deletePlotItems()
{
m_mohrsCiclesInfos.clear();
deleteCircles();
deleteEnvelopes();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::scheduleUpdateAxisScale()
{
if ( !m_scheduleUpdateAxisScaleTimer )
{
m_scheduleUpdateAxisScaleTimer = new QTimer( this );
connect( m_scheduleUpdateAxisScaleTimer, SIGNAL( timeout() ), this, SLOT( setAxesScaleAndReplot() ) );
}
if ( !m_scheduleUpdateAxisScaleTimer->isActive() )
{
m_scheduleUpdateAxisScaleTimer->setSingleShot( true );
m_scheduleUpdateAxisScaleTimer->start( 100 );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::resizeEvent( QResizeEvent* e )
{
setAxesScaleAndReplot();
// Update axis scale is called one more time because setAxesScaleAndReplot does not work the first
// time if the user does a very quick resizing of the window
scheduleUpdateAxisScale();
QwtPlot::resizeEvent( e );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::idealAxesEndPoints( double* xMin, double* xMax, double* yMax ) const
{
*xMin = HUGE_VAL;
*xMax = -HUGE_VAL;
*yMax = -HUGE_VAL;
double maxYEnvelope = -HUGE_VAL;
for ( const std::pair<const RimGeoMechCase* const, QwtPlotCurve*>& envelope : m_envolopePlotItems )
{
double tempMax = envelope.second->maxYValue();
if ( tempMax > maxYEnvelope )
{
maxYEnvelope = tempMax;
}
}
*yMax = std::max( maxYEnvelope, 1.2 * largestCircleRadiusInPlot() );
double minXEvelope = HUGE_VAL;
for ( const std::pair<const RimGeoMechCase* const, QwtPlotCurve*>& envelope : m_envolopePlotItems )
{
double tempMin = envelope.second->minXValue();
if ( tempMin < minXEvelope )
{
minXEvelope = tempMin;
}
}
if ( minXEvelope < 0 )
{
*xMin = minXEvelope;
}
else
{
*xMin = 1.1 * smallestPrincipal();
}
*xMax = 1.1 * largestPrincipal();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RiuMohrsCirclePlot::setAxesScaleAndReplot()
{
// yMin is always 0
double xMin, xMax, yMax;
idealAxesEndPoints( &xMin, &xMax, &yMax );
if ( xMax == -HUGE_VAL || xMin == HUGE_VAL || yMax == -HUGE_VAL )
{
return;
}
int canvasHeight = canvas()->height();
int canvasWidth = canvas()->width();
const double minPlotWidth = xMax - xMin;
const double minPlotHeight = yMax;
double xMaxDisplayed = xMax;
double yMaxDisplayed = yMax;
double canvasWidthOverHeightRatio = ( 1.0 * canvasWidth ) / ( 1.0 * canvasHeight );
// widthToKeepAspectRatio increases when canvas height is increased
double widthToKeepAspectRatio = minPlotHeight * canvasWidthOverHeightRatio;
// heightToKeepAspectRatio increases when canvas width is increased
double heightToKeepAspectRatio = minPlotWidth / canvasWidthOverHeightRatio;
if ( widthToKeepAspectRatio > minPlotWidth )
{
xMaxDisplayed = widthToKeepAspectRatio + xMin;
}
else if ( heightToKeepAspectRatio > minPlotHeight )
{
yMaxDisplayed = heightToKeepAspectRatio;
}
setAxisScale( QwtAxis::YLeft, 0, yMaxDisplayed );
setAxisScale( QwtAxis::XBottom, xMin, xMaxDisplayed );
replot();
}
RiuGeoMechSelectionItem* RiuMohrsCirclePlot::extractGeoMechSelectionItem( const RiuSelectionItem* selectionItem, Rim3dView*& newFollowAnimView )
{
newFollowAnimView = nullptr;
RiuGeoMechSelectionItem* geoMechSelectionItem = nullptr;
geoMechSelectionItem = dynamic_cast<RiuGeoMechSelectionItem*>( const_cast<RiuSelectionItem*>( selectionItem ) );
if ( geoMechSelectionItem )
{
// If we clicked in an geoMech view, and hit something using the standard result definition there,
// set this up to follow the animation there.
RimGeoMechView* clickedInEclView = dynamic_cast<RimGeoMechView*>( geoMechSelectionItem->m_view.p() );
if ( clickedInEclView && clickedInEclView->cellResult() == geoMechSelectionItem->m_resultDefinition.p() )
{
newFollowAnimView = geoMechSelectionItem->m_view;
}
}
else
{
auto intersectionSelItem = dynamic_cast<const Riu2dIntersectionSelectionItem*>( selectionItem );
if ( intersectionSelItem && intersectionSelItem->geoMechSelectionItem() )
{
geoMechSelectionItem = intersectionSelItem->geoMechSelectionItem();
newFollowAnimView = intersectionSelItem->view();
}
}
return geoMechSelectionItem;
}