mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
560 lines
21 KiB
C++
560 lines
21 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2026 Equinor ASA
|
|
//
|
|
// ResInsight is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE.
|
|
//
|
|
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
|
// for more details.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "RimGeneric3dView.h"
|
|
|
|
#include "Rim3dOverlayInfoConfig.h"
|
|
#include "Rim3dPropertiesInterface.h"
|
|
#include "RimAnnotationInViewCollection.h"
|
|
#include "RimLegendConfig.h"
|
|
#include "RimNameConfig.h"
|
|
#include "RimReachCircleAnnotation.h"
|
|
#include "RimReachCircleAnnotationInView.h"
|
|
#include "RimRegularLegendConfig.h"
|
|
#include "RimSurface.h"
|
|
#include "RimSurfaceCollection.h"
|
|
#include "RimSurfaceInView.h"
|
|
#include "RimSurfaceInViewCollection.h"
|
|
#include "RimTextAnnotation.h"
|
|
#include "RimTextAnnotationInView.h"
|
|
#include "RimTools.h"
|
|
#include "RimViewNameConfig.h"
|
|
#include "RimWellPath.h"
|
|
#include "RimWellPathCollection.h"
|
|
|
|
#include "Well/RigWellPath.h"
|
|
|
|
#include "Polygons/RimPolygon.h"
|
|
#include "Polygons/RimPolygonInView.h"
|
|
#include "Polygons/RimPolygonInViewCollection.h"
|
|
|
|
#include "Riu3DMainWindowTools.h"
|
|
#include "RiuViewer.h"
|
|
|
|
#include "cafPdmObjectScriptingCapability.h"
|
|
#include "cafPdmUiTreeOrdering.h"
|
|
|
|
#include "cafCmdFeatureMenuBuilder.h"
|
|
#include "cafDisplayCoordTransform.h"
|
|
#include "cvfBoundingBox.h"
|
|
#include "cvfModelBasicList.h"
|
|
#include "cvfPart.h"
|
|
#include "cvfScene.h"
|
|
#include "cvfString.h"
|
|
#include "cvfTransform.h"
|
|
|
|
#include <algorithm>
|
|
|
|
CAF_PDM_SOURCE_INIT( RimGeneric3dView, "RimGeneric3dView" );
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RimGeneric3dView::RimGeneric3dView()
|
|
: m_isDomainBoundingBoxCached( false )
|
|
{
|
|
CAF_PDM_InitScriptableObjectWithNameAndComment( "Generic View", ":/3DWindow.svg", "", "", "GenericView", "A 3D view not tied to any grid case" );
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_surfaceCollection, "SurfaceInViewCollection", "Surface Collection Field" );
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_polygonInViewCollection, "PolygonInViewCollection", "Polygon Collection Field" );
|
|
m_polygonInViewCollection = new RimPolygonInViewCollection();
|
|
m_polygonInViewCollection->uiCapability()->setUiIcon( caf::IconProvider( ":/PolylinesFromFile16x16.png" ) );
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_annotationCollection, "AnnotationCollection", "Annotations" );
|
|
m_annotationCollection = new RimAnnotationInViewCollection;
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_overlayInfoConfig, "OverlayInfoConfig", "Info Box" );
|
|
m_overlayInfoConfig = new Rim3dOverlayInfoConfig();
|
|
m_overlayInfoConfig->setReservoirView( this );
|
|
|
|
m_scaleTransform = new cvf::Transform();
|
|
|
|
nameConfig()->hideCaseNameField( true );
|
|
nameConfig()->hideAggregationTypeField( true );
|
|
nameConfig()->hidePropertyField( true );
|
|
nameConfig()->hideSampleSpacingField( true );
|
|
|
|
meshMode.uiCapability()->setUiHidden( true );
|
|
surfaceMode.uiCapability()->setUiHidden( true );
|
|
hideComparisonViewField();
|
|
|
|
setDeletable( true );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RimGeneric3dView::~RimGeneric3dView()
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RimSurfaceInViewCollection* RimGeneric3dView::surfaceInViewCollection() const
|
|
{
|
|
return m_surfaceCollection;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RimPolygonInViewCollection* RimGeneric3dView::polygonInViewCollection() const
|
|
{
|
|
return m_polygonInViewCollection;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RiaDefines::View3dContent RimGeneric3dView::viewContent() const
|
|
{
|
|
return RiaDefines::View3dContent::DATA_OBJECTS;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RimGeneric3dView::isGridVisualizationMode() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<RimLegendConfig*> RimGeneric3dView::legendConfigs() const
|
|
{
|
|
std::vector<RimLegendConfig*> legends;
|
|
|
|
if ( m_surfaceCollection )
|
|
{
|
|
for ( auto legendConfig : m_surfaceCollection->legendConfigs() )
|
|
{
|
|
legends.push_back( legendConfig );
|
|
}
|
|
}
|
|
|
|
legends.erase( std::remove( legends.begin(), legends.end(), nullptr ), legends.end() );
|
|
|
|
return legends;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::scheduleGeometryRegen( RivCellSetEnum geometryType )
|
|
{
|
|
// no need to do anything here, there are no cell based geometry sets in a case-less view
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
cvf::BoundingBox RimGeneric3dView::computeDomainBoundingBox() const
|
|
{
|
|
cvf::BoundingBox bb;
|
|
|
|
if ( auto* wellPathColl = RimWellPathCollection::instance() )
|
|
{
|
|
for ( auto* wellPath : wellPathColl->allWellPaths() )
|
|
{
|
|
if ( !wellPath || !wellPath->showWellPath() || !wellPath->wellPathGeometry() ) continue;
|
|
|
|
for ( const auto& point : wellPath->wellPathGeometry()->wellPathPoints() )
|
|
bb.add( point );
|
|
}
|
|
}
|
|
|
|
if ( m_surfaceCollection() )
|
|
{
|
|
for ( auto* surfaceInView : m_surfaceCollection->visibleSurfacesInView() )
|
|
{
|
|
if ( auto* surface = surfaceInView->surface() )
|
|
{
|
|
if ( auto* propsInterface = dynamic_cast<Rim3dPropertiesInterface*>( surface ) )
|
|
{
|
|
bb.add( propsInterface->boundingBoxInDomainCoords() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_polygonInViewCollection() )
|
|
{
|
|
// visiblePolygonsInView() only checks the collection's own checked state, not each individual polygon, so
|
|
// the per-item checked state must be filtered here to match what is actually rendered.
|
|
for ( auto* polygonInView : m_polygonInViewCollection->visiblePolygonsInView() )
|
|
{
|
|
if ( !polygonInView || !polygonInView->isChecked() ) continue;
|
|
|
|
if ( auto* polygon = polygonInView->polygon() )
|
|
{
|
|
for ( const auto& point : polygon->pointsInDomainCoords() )
|
|
bb.add( point );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_annotationCollection() )
|
|
{
|
|
// Annotation part managers reject an invalid box, so annotations must widen it themselves
|
|
for ( auto* inView : m_annotationCollection->globalTextAnnotations() )
|
|
{
|
|
if ( auto* annotation = inView->sourceAnnotation() )
|
|
{
|
|
bb.add( annotation->anchorPoint() );
|
|
bb.add( annotation->labelPoint() );
|
|
}
|
|
}
|
|
|
|
for ( auto* inView : m_annotationCollection->globalReachCircleAnnotations() )
|
|
{
|
|
if ( auto* annotation = inView->sourceAnnotation() ) bb.add( annotation->centerPoint() );
|
|
}
|
|
}
|
|
|
|
if ( bb.isValid() )
|
|
{
|
|
// Avoid a degenerate bounding box where one axis is disproportionately small compared to the others
|
|
// (e.g. surfaces or polygons located on a single horizontal plane give a near-zero Z extent, or a
|
|
// single vertical well path gives a near-zero XY extent). A very thin axis gives the scene a
|
|
// degenerate grid box and can confuse the camera framing, so each axis is padded to a minimum
|
|
// fraction of the largest extent.
|
|
const double maxExtent = std::max( { bb.extent().x(), bb.extent().y(), bb.extent().z() } );
|
|
const double minExtent = maxExtent * 0.01;
|
|
|
|
cvf::Vec3d min = bb.min();
|
|
cvf::Vec3d max = bb.max();
|
|
|
|
for ( int axis = 0; axis < 3; ++axis )
|
|
{
|
|
const double extent = max[axis] - min[axis];
|
|
if ( extent < minExtent )
|
|
{
|
|
const double pad = 0.5 * ( minExtent - extent );
|
|
min[axis] -= pad;
|
|
max[axis] += pad;
|
|
}
|
|
}
|
|
|
|
bb = cvf::BoundingBox( min, max );
|
|
}
|
|
|
|
return bb;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
cvf::BoundingBox RimGeneric3dView::cachedDomainBoundingBox() const
|
|
{
|
|
if ( !m_isDomainBoundingBoxCached )
|
|
{
|
|
m_domainBoundingBox = computeDomainBoundingBox();
|
|
m_isDomainBoundingBoxCached = true;
|
|
}
|
|
|
|
return m_domainBoundingBox;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::invalidateDomainBoundingBox()
|
|
{
|
|
m_isDomainBoundingBoxCached = false;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
cvf::BoundingBox RimGeneric3dView::domainBoundingBox()
|
|
{
|
|
return cachedDomainBoundingBox();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
double RimGeneric3dView::characteristicCellSize() const
|
|
{
|
|
// No case to ask, so derive a length scale from the extent of the visualized data. The well pipe radius is
|
|
// 0.1 (RimWellPathCollection::wellPathRadiusScaleFactor) times this value, so 1/100 of the horizontal extent
|
|
// gives a pipe radius of ~1/1000 of the scene, which reads well on screen. Clamped to the same interval
|
|
// RimEclipseCase::characteristicCellSize() uses.
|
|
const auto bb = cachedDomainBoundingBox();
|
|
if ( !bb.isValid() ) return 10.0;
|
|
|
|
return std::clamp( 0.01 * std::max( bb.extent().x(), bb.extent().y() ), 10.0, 200.0 );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::updateGridBoxData()
|
|
{
|
|
if ( viewer() )
|
|
{
|
|
viewer()->updateGridBoxData( m_scaleZ(), cvf::Vec3d::ZERO, backgroundColor(), domainBoundingBox(), fontSize() );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::recomputeDomainBoundingBoxAndUpdateGridBox()
|
|
{
|
|
invalidateDomainBoundingBox();
|
|
updateGridBoxData();
|
|
|
|
createDisplayModelAndRedraw();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
|
{
|
|
Rim3dView::defineUiOrdering( uiConfigName, uiOrdering );
|
|
|
|
caf::PdmUiGroup* nameGroup = uiOrdering.addNewGroup( "View Name" );
|
|
nameConfig()->uiOrdering( uiConfigName, *nameGroup );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName /*= ""*/ )
|
|
{
|
|
uiTreeOrdering.add( m_overlayInfoConfig() );
|
|
if ( surfaceInViewCollection() ) uiTreeOrdering.add( surfaceInViewCollection() );
|
|
uiTreeOrdering.add( polygonInViewCollection() );
|
|
uiTreeOrdering.add( annotationCollection() );
|
|
|
|
uiTreeOrdering.skipRemainingChildren( true );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::onCreateDisplayModel()
|
|
{
|
|
if ( nativeOrOverrideViewer() == nullptr ) return;
|
|
|
|
// The bounding box is intentionally not invalidated here: it stays fixed across visibility toggles and is only
|
|
// recomputed on initial load or when the user explicitly triggers RicUpdateGenericViewBoundingBoxFeature.
|
|
const auto bb = domainBoundingBox();
|
|
|
|
// Remove all existing frames from the viewer.
|
|
nativeOrOverrideViewer()->removeAllFrames( isUsingOverrideViewer() );
|
|
|
|
// Set the main scene in the viewer before adding the static models, since addStaticModelOnce() appends to the
|
|
// frames of the existing scene.
|
|
cvf::ref<cvf::Scene> mainScene = new cvf::Scene;
|
|
nativeOrOverrideViewer()->setMainScene( mainScene.p(), isUsingOverrideViewer() );
|
|
|
|
// Well path model
|
|
|
|
auto* wellPathPipeVizModel = m_vizModels.findOrCreate( RivNamedVisualizationModels::wellPathPipeModelName() );
|
|
wellPathPipeVizModel->removeAllParts();
|
|
addWellPathsToModel( wellPathPipeVizModel, bb, characteristicCellSize() );
|
|
nativeOrOverrideViewer()->addStaticModelOnce( wellPathPipeVizModel, isUsingOverrideViewer() );
|
|
|
|
// Surfaces
|
|
|
|
auto* surfaceVizModel = m_vizModels.findOrCreate( "SurfaceModel" );
|
|
surfaceVizModel->removeAllParts();
|
|
if ( m_surfaceCollection )
|
|
{
|
|
bool nativeOnly = true;
|
|
m_surfaceCollection->appendPartsToModel( surfaceVizModel, scaleTransform(), nativeOnly );
|
|
nativeOrOverrideViewer()->addStaticModelOnce( surfaceVizModel, isUsingOverrideViewer() );
|
|
}
|
|
|
|
// Polygons
|
|
|
|
auto* polygonVizModel = m_vizModels.findOrCreate( "PolygonModel" );
|
|
polygonVizModel->removeAllParts();
|
|
if ( m_polygonInViewCollection )
|
|
{
|
|
cvf::ref<caf::DisplayCoordTransform> transform = displayCoordTransform();
|
|
for ( auto* polygonInView : m_polygonInViewCollection->visiblePolygonsInView() )
|
|
{
|
|
if ( polygonInView ) polygonInView->appendPartsToModel( polygonVizModel, transform.p(), bb );
|
|
}
|
|
nativeOrOverrideViewer()->addStaticModelOnce( polygonVizModel, isUsingOverrideViewer() );
|
|
polygonVizModel->updateBoundingBoxesRecursive();
|
|
}
|
|
|
|
// Annotations
|
|
|
|
cvf::ref<cvf::ModelBasicList> annotationModel = new cvf::ModelBasicList;
|
|
annotationModel->setName( "Annotations" );
|
|
addAnnotationsToModel( annotationModel.p() );
|
|
mainScene->addModel( annotationModel.p() );
|
|
|
|
onUpdateLegends();
|
|
if ( m_surfaceCollection )
|
|
{
|
|
m_surfaceCollection->applySingleColorEffect();
|
|
}
|
|
|
|
if ( bb.isValid() ) nativeOrOverrideViewer()->setPointOfInterest( bb.center() );
|
|
|
|
m_overlayInfoConfig()->update3DInfo();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::defineAxisLabels( cvf::String* xLabel, cvf::String* yLabel, cvf::String* zLabel )
|
|
{
|
|
*xLabel = "E(x)";
|
|
*yLabel = "N(y)";
|
|
*zLabel = "Z";
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::onUpdateLegends()
|
|
{
|
|
if ( !nativeOrOverrideViewer() ) return;
|
|
|
|
if ( !isUsingOverrideViewer() )
|
|
{
|
|
nativeOrOverrideViewer()->removeAllColorLegends();
|
|
}
|
|
else
|
|
{
|
|
for ( auto legendConf : legendConfigs() )
|
|
{
|
|
nativeOrOverrideViewer()->removeColorLegend( legendConf->titledOverlayFrame() );
|
|
}
|
|
}
|
|
|
|
if ( m_surfaceCollection && m_surfaceCollection->isChecked() )
|
|
{
|
|
m_surfaceCollection->updateLegendRangesTextAndVisibility( nativeOrOverrideViewer(), isUsingOverrideViewer() );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::onLoadDataAndUpdate()
|
|
{
|
|
updateViewTreeItems( RiaDefines::ItemIn3dView::ALL );
|
|
synchronizeLocalAnnotationsFromGlobal();
|
|
onUpdateScaleTransform();
|
|
|
|
updateDockWindowVisibility();
|
|
|
|
// Surface data must load before the bounding box is computed, or boundingBoxInDomainCoords() returns an
|
|
// empty box because the surface data has not been fetched yet.
|
|
if ( m_surfaceCollection ) m_surfaceCollection->loadData( m_currentTimeStep );
|
|
|
|
invalidateDomainBoundingBox();
|
|
|
|
scheduleCreateDisplayModelAndRedraw();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::selectOverlayInfoConfig()
|
|
{
|
|
Riu3DMainWindowTools::selectAsCurrentItem( m_overlayInfoConfig );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
cvf::Transform* RimGeneric3dView::scaleTransform()
|
|
{
|
|
return m_scaleTransform.p();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RimGeneric3dView::createAutoName() const
|
|
{
|
|
if ( !nameConfig()->customName().isEmpty() ) return nameConfig()->customName();
|
|
|
|
return "Generic View";
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::updateViewTreeItems( RiaDefines::ItemIn3dView itemType )
|
|
{
|
|
auto bitmaskEnum = BitmaskEnum( itemType );
|
|
|
|
if ( bitmaskEnum.AnyOf( RiaDefines::ItemIn3dView::SURFACE ) )
|
|
{
|
|
RimSurfaceCollection* surfColl = RimTools::surfaceCollection();
|
|
|
|
if ( surfColl && surfColl->containsSurface() )
|
|
{
|
|
if ( !m_surfaceCollection() )
|
|
{
|
|
m_surfaceCollection = new RimSurfaceInViewCollection();
|
|
}
|
|
|
|
m_surfaceCollection->setSurfaceCollection( surfColl );
|
|
m_surfaceCollection->updateFromSurfaceCollection();
|
|
}
|
|
else
|
|
{
|
|
delete m_surfaceCollection;
|
|
}
|
|
}
|
|
|
|
if ( bitmaskEnum.AnyOf( RiaDefines::ItemIn3dView::POLYGON ) )
|
|
{
|
|
m_polygonInViewCollection->updateFromPolygonCollection();
|
|
}
|
|
|
|
updateConnectedEditors();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
|
|
{
|
|
Rim3dView::appendMenuItems( menuBuilder );
|
|
|
|
menuBuilder << "Separator";
|
|
menuBuilder << "RicUpdateGenericViewBoundingBoxFeature";
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGeneric3dView::appendViewerContextMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
|
|
{
|
|
menuBuilder << "RicLinkViewFeature";
|
|
menuBuilder << "RicUpdateGenericViewBoundingBoxFeature";
|
|
menuBuilder << "RicViewZoomAllFeature";
|
|
}
|