diff --git a/ApplicationLibCode/Application/RiaApplication.cpp b/ApplicationLibCode/Application/RiaApplication.cpp index 7fb1f21dbc..f3f9390e28 100644 --- a/ApplicationLibCode/Application/RiaApplication.cpp +++ b/ApplicationLibCode/Application/RiaApplication.cpp @@ -75,6 +75,8 @@ #include "RimFileWellPath.h" #include "RimFileWellPathDataLoader.h" #include "RimFractureTemplateCollection.h" +#include "RimGeneric3dView.h" +#include "RimGenericViewCollection.h" #include "RimGeoMechCase.h" #include "RimGeoMechModels.h" #include "RimGeoMechView.h" @@ -906,9 +908,20 @@ bool RiaApplication::loadProject( const QString& projectFileName, ProjectLoadAct for ( RimOilField* oilField : m_project->oilFields ) { - for ( auto seisView : oilField->seismicViewCollection()->views() ) + if ( oilField->seismicViewCollection() ) { - seisView->loadDataAndUpdate(); + for ( auto seisView : oilField->seismicViewCollection()->views() ) + { + seisView->loadDataAndUpdate(); + } + } + + if ( oilField->genericViewCollection() ) + { + for ( auto dataView : oilField->genericViewCollection()->genericViews() ) + { + dataView->loadDataAndUpdate(); + } } } diff --git a/ApplicationLibCode/Application/RiaDefines.h b/ApplicationLibCode/Application/RiaDefines.h index eb7316ca79..09ae4105dc 100644 --- a/ApplicationLibCode/Application/RiaDefines.h +++ b/ApplicationLibCode/Application/RiaDefines.h @@ -263,7 +263,8 @@ enum class View3dContent FLAT_INTERSECTION = 0b00000100, CONTOUR = 0b00001000, SEISMIC = 0b00010000, - ALL = 0b00011111 + DATA_OBJECTS = 0b00100000, + ALL = 0b00111111 }; enum class ItemIn3dView diff --git a/ApplicationLibCode/Commands/CMakeLists_files.cmake b/ApplicationLibCode/Commands/CMakeLists_files.cmake index 88aa11abf2..7e255b4a60 100644 --- a/ApplicationLibCode/Commands/CMakeLists_files.cmake +++ b/ApplicationLibCode/Commands/CMakeLists_files.cmake @@ -5,6 +5,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RicCloseSummaryCaseInCollectionFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicCloseObservedDataFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewViewFeature.cpp + ${CMAKE_CURRENT_LIST_DIR}/RicNewGenericDataViewFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicNewContourMapViewFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicImportFaciesFeature.cpp ${CMAKE_CURRENT_LIST_DIR}/RicImportFormationNamesFeature.cpp diff --git a/ApplicationLibCode/Commands/ExportCommands/RicAdvancedSnapshotExportFeature.cpp b/ApplicationLibCode/Commands/ExportCommands/RicAdvancedSnapshotExportFeature.cpp index f25bd263c7..571480bf0d 100644 --- a/ApplicationLibCode/Commands/ExportCommands/RicAdvancedSnapshotExportFeature.cpp +++ b/ApplicationLibCode/Commands/ExportCommands/RicAdvancedSnapshotExportFeature.cpp @@ -33,7 +33,7 @@ #include "RimEclipseCase.h" #include "RimEclipseCellColors.h" #include "RimEclipseView.h" -#include "RimEclipseViewCollection.h" +#include "RimGenericViewCollection.h" #include "RimGeoMechCase.h" #include "RimGeoMechCellColors.h" #include "RimGeoMechResultDefinition.h" @@ -350,7 +350,7 @@ void RicAdvancedSnapshotExportFeature::removeViewFromViewCollection( RimEclipseV RimOilField* oilField = project->activeOilField(); if ( !oilField ) return; - RimEclipseViewCollection* viewColl = oilField->eclipseViewCollection(); + RimGenericViewCollection* viewColl = oilField->genericViewCollection(); if ( !viewColl ) return; viewColl->removeView( view ); } diff --git a/ApplicationLibCode/Commands/RicNewGenericDataViewFeature.cpp b/ApplicationLibCode/Commands/RicNewGenericDataViewFeature.cpp new file mode 100644 index 0000000000..af05e4db41 --- /dev/null +++ b/ApplicationLibCode/Commands/RicNewGenericDataViewFeature.cpp @@ -0,0 +1,92 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RicNewGenericDataViewFeature.h" + +#include "RimGeneric3dView.h" +#include "RimGenericViewCollection.h" +#include "RimOilField.h" +#include "RimProject.h" + +#include "Riu3DMainWindowTools.h" + +#include + +CAF_CMD_SOURCE_INIT( RicNewGenericDataViewFeature, "RicNewGenericDataViewFeature" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicNewGenericDataViewFeature::isCommandEnabled() const +{ + // A data view is useful even in an empty project; content can be added afterwards. + return true; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicNewGenericDataViewFeature::onActionTriggered( bool isChecked ) +{ + createDataView(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicNewGenericDataViewFeature::setupActionLook( QAction* actionToSetup ) +{ + actionToSetup->setIcon( QIcon( ":/3DWindow.svg" ) ); + actionToSetup->setText( "New Generic View" ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimGeneric3dView* RicNewGenericDataViewFeature::createInitialViewIfNeeded() +{ + auto proj = RimProject::current(); + if ( !proj ) return nullptr; + + // Only auto-create when the project has no 3D view at all, mirroring RicNewSeismicViewFeature + if ( !proj->allViews().empty() ) return nullptr; + + return createDataView(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimGeneric3dView* RicNewGenericDataViewFeature::createDataView() +{ + auto proj = RimProject::current(); + if ( !proj ) return nullptr; + + auto oilField = proj->activeOilField(); + if ( !oilField || !oilField->genericViewCollection() ) return nullptr; + + auto view = oilField->genericViewCollection()->addGenericView(); + if ( !view ) return nullptr; + + oilField->genericViewCollection()->updateAllRequiredEditors(); + view->scheduleCreateDisplayModelAndRedraw(); + + Riu3DMainWindowTools::selectAsCurrentItem( view ); + + return view; +} diff --git a/ApplicationLibCode/Commands/RicNewGenericDataViewFeature.h b/ApplicationLibCode/Commands/RicNewGenericDataViewFeature.h new file mode 100644 index 0000000000..0fee7676dd --- /dev/null +++ b/ApplicationLibCode/Commands/RicNewGenericDataViewFeature.h @@ -0,0 +1,40 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cafCmdFeature.h" + +class RimGeneric3dView; + +//================================================================================================== +/// +//================================================================================================== +class RicNewGenericDataViewFeature : public caf::CmdFeature +{ + CAF_CMD_HEADER_INIT; + +public: + static RimGeneric3dView* createInitialViewIfNeeded(); + static RimGeneric3dView* createDataView(); + +protected: + bool isCommandEnabled() const override; + void onActionTriggered( bool isChecked ) override; + void setupActionLook( QAction* actionToSetup ) override; +}; diff --git a/ApplicationLibCode/Commands/RicNewViewFeature.cpp b/ApplicationLibCode/Commands/RicNewViewFeature.cpp index 4668bbe153..7b73248916 100644 --- a/ApplicationLibCode/Commands/RicNewViewFeature.cpp +++ b/ApplicationLibCode/Commands/RicNewViewFeature.cpp @@ -28,6 +28,7 @@ #include "RimEclipseCaseTools.h" #include "RimEclipseView.h" #include "RimEclipseViewCollection.h" +#include "RimGenericViewCollection.h" #include "RimGeoMechCase.h" #include "RimGeoMechView.h" @@ -57,13 +58,31 @@ Rim3dView* RicNewViewFeature::addReservoirView( RimEclipseCase* eclipseCase, Rim return newView; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +Rim3dView* RicNewViewFeature::addReservoirView( RimEclipseCase* eclipseCase, RimGeoMechCase* geomCase, RimGenericViewCollection* viewColl ) +{ + Rim3dView* newView = createReservoirView( eclipseCase, geomCase, viewColl ); + + if ( newView ) + { + Riu3DMainWindowTools::setExpanded( newView ); + + // Select the new view to make sure RiaApplication::setActiveReservoirView() is called + Riu3DMainWindowTools::selectAsCurrentItem( newView ); + } + + return newView; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RicNewViewFeature::isCommandEnabled() const { return selectedEclipseCase() != nullptr || selectedEclipseView() != nullptr || selectedGeoMechCase() != nullptr || - selectedGeoMechView() != nullptr || selectedEclipseViewCollection() != nullptr; + selectedGeoMechView() != nullptr || selectedEclipseViewCollection() != nullptr || selectedGenericViewCollection() != nullptr; } //-------------------------------------------------------------------------------------------------- @@ -77,6 +96,7 @@ void RicNewViewFeature::onActionTriggered( bool isChecked ) RimGeoMechView* geoMechView = selectedGeoMechView(); RimEclipseView* reservoirView = selectedEclipseView(); RimEclipseViewCollection* viewCollection = selectedEclipseViewCollection(); + RimGenericViewCollection* genericViewColl = selectedGenericViewCollection(); RimEclipseCaseEnsemble* eclipseEnsemble = selectedEclipseCaseEnsemble(); // Find case to insert into @@ -109,6 +129,17 @@ void RicNewViewFeature::onActionTriggered( bool isChecked ) } } } + else if ( genericViewColl ) + { + auto eclipseCases = RimEclipseCaseTools::nativeEclipseGridCases(); + if ( !eclipseCases.empty() ) + { + eclipseCase = eclipseCases[0]; + } + + addReservoirView( eclipseCase, geomCase, genericViewColl ); + return; + } addReservoirView( eclipseCase, geomCase, viewCollection ); } @@ -154,6 +185,38 @@ Rim3dView* RicNewViewFeature::createReservoirView( RimEclipseCase* eclipseCase, return insertedView; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +Rim3dView* RicNewViewFeature::createReservoirView( RimEclipseCase* eclipseCase, RimGeoMechCase* geomCase, RimGenericViewCollection* viewColl ) +{ + RimGridView* insertedView = nullptr; + + if ( eclipseCase ) + { + insertedView = eclipseCase->createAndAddReservoirView( viewColl ); + } + else if ( geomCase ) + { + insertedView = geomCase->createAndAddReservoirView(); + } + + // Must be run before buildViewItems, as wells are created in this function + if ( insertedView ) insertedView->loadDataAndUpdate(); + + if ( eclipseCase ) + { + eclipseCase->updateConnectedEditors(); + } + + if ( geomCase ) + { + geomCase->updateConnectedEditors(); + } + + return insertedView; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -195,6 +258,14 @@ RimEclipseViewCollection* RicNewViewFeature::selectedEclipseViewCollection() return caf::SelectionManager::instance()->selectedItemOfType(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimGenericViewCollection* RicNewViewFeature::selectedGenericViewCollection() +{ + return caf::SelectionManager::instance()->selectedItemOfType(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/Commands/RicNewViewFeature.h b/ApplicationLibCode/Commands/RicNewViewFeature.h index b3beba0322..b926829e61 100644 --- a/ApplicationLibCode/Commands/RicNewViewFeature.h +++ b/ApplicationLibCode/Commands/RicNewViewFeature.h @@ -27,6 +27,7 @@ class RimGeoMechCase; class RimGeoMechView; class Rim3dView; class RimEclipseViewCollection; +class RimGenericViewCollection; class RimEclipseCaseEnsemble; //================================================================================================== @@ -38,6 +39,7 @@ class RicNewViewFeature : public caf::CmdFeature public: static Rim3dView* addReservoirView( RimEclipseCase* eclipseCase, RimGeoMechCase* geomCase, RimEclipseViewCollection* viewColl ); + static Rim3dView* addReservoirView( RimEclipseCase* eclipseCase, RimGeoMechCase* geomCase, RimGenericViewCollection* viewColl ); protected: bool isCommandEnabled() const override; @@ -46,11 +48,13 @@ protected: private: static Rim3dView* createReservoirView( RimEclipseCase* eclipseCase, RimGeoMechCase* geomCase, RimEclipseViewCollection* viewColl ); + static Rim3dView* createReservoirView( RimEclipseCase* eclipseCase, RimGeoMechCase* geomCase, RimGenericViewCollection* viewColl ); static RimEclipseCase* selectedEclipseCase(); static RimGeoMechCase* selectedGeoMechCase(); static RimEclipseView* selectedEclipseView(); static RimGeoMechView* selectedGeoMechView(); static RimEclipseViewCollection* selectedEclipseViewCollection(); + static RimGenericViewCollection* selectedGenericViewCollection(); static RimEclipseCaseEnsemble* selectedEclipseCaseEnsemble(); }; diff --git a/ApplicationLibCode/Commands/SeismicCommands/RicNewSeismicViewFeature.cpp b/ApplicationLibCode/Commands/SeismicCommands/RicNewSeismicViewFeature.cpp index 1f4c780007..6842f15b05 100644 --- a/ApplicationLibCode/Commands/SeismicCommands/RicNewSeismicViewFeature.cpp +++ b/ApplicationLibCode/Commands/SeismicCommands/RicNewSeismicViewFeature.cpp @@ -21,6 +21,7 @@ #include "RiaApplication.h" #include "RiaSeismicDefines.h" +#include "RimGeneric3dView.h" #include "RimOilField.h" #include "RimProject.h" #include "RimSeismicData.h" @@ -75,7 +76,12 @@ RimSeismicView* RicNewSeismicViewFeature::createInitialViewIfNeeded( RimSeismicD { auto proj = RimProject::current(); - if ( !proj->allViews().empty() ) return nullptr; + // Only auto-create when the project has no 3D view at all, but ignore any auto-created data view - + // that view has no seismic content, and importing seismic should still get its own view. + for ( auto view : proj->allViews() ) + { + if ( !dynamic_cast( view ) ) return nullptr; + } return createSeismicView( seisData ); } diff --git a/ApplicationLibCode/Commands/SurfaceCommands/RicImportSurfacesFeature.cpp b/ApplicationLibCode/Commands/SurfaceCommands/RicImportSurfacesFeature.cpp index eb30410807..fae8651875 100644 --- a/ApplicationLibCode/Commands/SurfaceCommands/RicImportSurfacesFeature.cpp +++ b/ApplicationLibCode/Commands/SurfaceCommands/RicImportSurfacesFeature.cpp @@ -18,6 +18,8 @@ #include "RicImportSurfacesFeature.h" +#include "RicNewGenericDataViewFeature.h" + #include "RiaApplication.h" #include "RimSurface.h" @@ -82,6 +84,8 @@ void RicImportSurfacesFeature::onActionTriggered( bool isChecked ) if ( lastCreatedOrUpdated ) { + RicNewGenericDataViewFeature::createInitialViewIfNeeded(); + Riu3DMainWindowTools::selectAsCurrentItem( lastCreatedOrUpdated ); } } diff --git a/ApplicationLibCode/Commands/WellPathCommands/RicImportWellPaths.cpp b/ApplicationLibCode/Commands/WellPathCommands/RicImportWellPaths.cpp index 3040855c53..78d28e8da7 100644 --- a/ApplicationLibCode/Commands/WellPathCommands/RicImportWellPaths.cpp +++ b/ApplicationLibCode/Commands/WellPathCommands/RicImportWellPaths.cpp @@ -19,6 +19,8 @@ #include "RicImportWellPaths.h" +#include "RicNewGenericDataViewFeature.h" + #include "RiaApplication.h" #include "RiaGuiApplication.h" #include "RiaLogging.h" @@ -141,6 +143,8 @@ std::vector RicImportWellPaths::importWellPaths( const QStringList if ( oilField && !oilField->wellPathCollection->allWellPaths().empty() ) { + RicNewGenericDataViewFeature::createInitialViewIfNeeded(); + RimWellPath* wellPath = oilField->wellPathCollection->mostRecentlyUpdatedWellPath(); if ( wellPath ) { diff --git a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake index bdd8fbe37e..da2640494e 100644 --- a/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake +++ b/ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake @@ -39,6 +39,8 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RimViewWindow.cpp ${CMAKE_CURRENT_LIST_DIR}/Rim3dView.cpp ${CMAKE_CURRENT_LIST_DIR}/RimGridView.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimGeneric3dView.cpp + ${CMAKE_CURRENT_LIST_DIR}/RimGenericViewCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/Rim2dIntersectionView.cpp ${CMAKE_CURRENT_LIST_DIR}/Rim2dIntersectionViewCollection.cpp ${CMAKE_CURRENT_LIST_DIR}/RimViewManipulator.cpp diff --git a/ApplicationLibCode/ProjectDataModel/Rim3dView.cpp b/ApplicationLibCode/ProjectDataModel/Rim3dView.cpp index 70e2b77f00..e092edc666 100644 --- a/ApplicationLibCode/ProjectDataModel/Rim3dView.cpp +++ b/ApplicationLibCode/ProjectDataModel/Rim3dView.cpp @@ -34,6 +34,7 @@ #include "RimAnnotationInViewCollection.h" #include "RimCase.h" #include "RimDockWindowController.h" +#include "RimGeneric3dView.h" #include "RimGridView.h" #include "RimLegendConfig.h" #include "RimMainPlotCollection.h" @@ -600,6 +601,7 @@ std::vector Rim3dView::validComparisonViews() const for ( auto view : project->allViews() ) { if ( dynamic_cast( view ) ) continue; + if ( dynamic_cast( view ) ) continue; bool isSameViewType = isIntersectionView( this ) == isIntersectionView( view ); diff --git a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp index 326a0a0e93..284366bba3 100644 --- a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp @@ -338,6 +338,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() } else if ( dynamic_cast( firstUiItem ) ) { + menuBuilder << "RicNewGenericDataViewFeature"; menuBuilder << "RicNewEditableWellPathFeature"; menuBuilder << "RicPasteModeledWellPathFeature"; menuBuilder << "RicCreateEnsembleWellLogFeature"; diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCase.cpp b/ApplicationLibCode/ProjectDataModel/RimEclipseCase.cpp index d9b03e6aec..f0b7c89cf4 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCase.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCase.cpp @@ -65,6 +65,7 @@ #include "RimFaultDistance.h" #include "RimFaultDistanceCollection.h" #include "RimFaultInViewCollection.h" +#include "RimGenericViewCollection.h" #include "RimGridCollection.h" #include "RimIntersectionCollection.h" #include "RimMainPlotCollection.h" @@ -378,8 +379,12 @@ void RimEclipseCase::clearResultAliases() //-------------------------------------------------------------------------------------------------- RimEclipseView* RimEclipseCase::createAndAddReservoirView( bool useGlobalViewCollection ) { - RimEclipseViewCollection* viewColl = useGlobalViewCollection ? globalViewCollection() : viewCollection(); - return createAndAddReservoirView( viewColl ); + if ( useGlobalViewCollection ) + { + return createAndAddReservoirView( globalViewCollection() ); + } + + return createAndAddReservoirView( viewCollection() ); } //-------------------------------------------------------------------------------------------------- @@ -392,6 +397,16 @@ RimEclipseView* RimEclipseCase::createAndAddReservoirView( RimEclipseViewCollect return viewColl->addView( this ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseView* RimEclipseCase::createAndAddReservoirView( RimGenericViewCollection* viewColl ) +{ + if ( !viewColl ) return nullptr; + + return viewColl->addView( this ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -1395,7 +1410,7 @@ RimEclipseViewCollection* RimEclipseCase::viewCollection() const //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -RimEclipseViewCollection* RimEclipseCase::globalViewCollection() const +RimGenericViewCollection* RimEclipseCase::globalViewCollection() const { RimProject* project = RimProject::current(); if ( !project ) return nullptr; @@ -1403,7 +1418,7 @@ RimEclipseViewCollection* RimEclipseCase::globalViewCollection() const RimOilField* oilField = project->activeOilField(); if ( !oilField ) return nullptr; - return oilField->eclipseViewCollection(); + return oilField->genericViewCollection(); } //-------------------------------------------------------------------------------------------------- @@ -1442,6 +1457,23 @@ void RimEclipseCase::addViewsFromViewCollection( std::vector& v } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseCase::addViewsFromViewCollection( std::vector& views, const RimGenericViewCollection* viewColl ) const +{ + if ( viewColl ) + { + for ( auto view : viewColl->views() ) + { + if ( view && view->eclipseCase() && view->eclipseCase() == this ) + { + views.push_back( view ); + } + } + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/RimEclipseCase.h b/ApplicationLibCode/ProjectDataModel/RimEclipseCase.h index 30010ca278..d1629af451 100644 --- a/ApplicationLibCode/ProjectDataModel/RimEclipseCase.h +++ b/ApplicationLibCode/ProjectDataModel/RimEclipseCase.h @@ -59,6 +59,7 @@ class RimIdenticalGridCaseGroup; class RimReservoirCellResultsStorage; class RimEclipseResultAddressCollection; class RimEclipseViewCollection; +class RimGenericViewCollection; class RimResultNameAlias; class RimReservoirGridEnsemble; @@ -111,6 +112,7 @@ public: RimEclipseView* createAndAddReservoirView( bool useGlobalViewCollection = false ); RimEclipseView* createAndAddReservoirView( RimEclipseViewCollection* viewColl ); + RimEclipseView* createAndAddReservoirView( RimGenericViewCollection* viewColl ); RimEclipseView* createCopyAndAddView( const RimEclipseView* sourceView ); const RigVirtualPerforationTransmissibilities* computeAndGetVirtualPerforationTransmissibilities(); @@ -175,8 +177,9 @@ protected: protected: void computeCachedData(); std::vector additionalFiles() const; - RimEclipseViewCollection* globalViewCollection() const; + RimGenericViewCollection* globalViewCollection() const; void addViewsFromViewCollection( std::vector& views, const RimEclipseViewCollection* viewColl ) const; + void addViewsFromViewCollection( std::vector& views, const RimGenericViewCollection* viewColl ) const; const RigFormationNames* effectiveFormationNames() const; diff --git a/ApplicationLibCode/ProjectDataModel/RimGeneric3dView.cpp b/ApplicationLibCode/ProjectDataModel/RimGeneric3dView.cpp new file mode 100644 index 0000000000..0ab1eb28b2 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimGeneric3dView.cpp @@ -0,0 +1,521 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// 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 "RimWellPathCollection.h" + +#include "Polygons/RimPolygon.h" +#include "Polygons/RimPolygonInView.h" +#include "Polygons/RimPolygonInViewCollection.h" + +#include "Riu3DMainWindowTools.h" +#include "RiuViewer.h" + +#include "cafPdmUiTreeOrdering.h" + +#include "cafDisplayCoordTransform.h" +#include "cvfBoundingBox.h" +#include "cvfModelBasicList.h" +#include "cvfPart.h" +#include "cvfScene.h" +#include "cvfString.h" +#include "cvfTransform.h" + +#include + +CAF_PDM_SOURCE_INIT( RimGeneric3dView, "RimGeneric3dView" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimGeneric3dView::RimGeneric3dView() + : m_isDomainBoundingBoxCached( false ) +{ + CAF_PDM_InitObject( "Generic View", ":/3DWindow.svg" ); + + 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 RimGeneric3dView::legendConfigs() const +{ + std::vector 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() ) bb.add( wellPathColl->wellPathsBoundingBox() ); + + if ( m_surfaceCollection() ) + { + for ( auto* surfaceInView : m_surfaceCollection->visibleSurfacesInView() ) + { + if ( auto* surface = surfaceInView->surface() ) + { + if ( auto* propsInterface = dynamic_cast( surface ) ) + { + bb.add( propsInterface->boundingBoxInDomainCoords() ); + } + } + } + } + + if ( m_polygonInViewCollection() ) + { + for ( auto* polygonInView : m_polygonInViewCollection->visiblePolygonsInView() ) + { + 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::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; + + invalidateDomainBoundingBox(); + 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 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 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 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::setDefaultView() +{ + if ( viewer() ) + { + viewer()->setDefaultView( cvf::Vec3d::Y_AXIS, cvf::Vec3d::Z_AXIS ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +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(); + } + + invalidateDomainBoundingBox(); + + updateConnectedEditors(); +} diff --git a/ApplicationLibCode/ProjectDataModel/RimGeneric3dView.h b/ApplicationLibCode/ProjectDataModel/RimGeneric3dView.h new file mode 100644 index 0000000000..425599d6a7 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimGeneric3dView.h @@ -0,0 +1,85 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "Rim3dView.h" + +#include "cafPdmField.h" + +#include "cafPdmObject.h" + +#include "cvfBoundingBox.h" + +class Rim3dOverlayInfoConfig; +class RimSurfaceInViewCollection; +class RimPolygonInViewCollection; + +class RimGeneric3dView : public Rim3dView +{ + CAF_PDM_HEADER_INIT; + +public: + RimGeneric3dView(); + ~RimGeneric3dView() override; + + RimSurfaceInViewCollection* surfaceInViewCollection() const; + RimPolygonInViewCollection* polygonInViewCollection() const; + + RiaDefines::View3dContent viewContent() const override; + bool isGridVisualizationMode() const override; + std::vector legendConfigs() const override; + void scheduleGeometryRegen( RivCellSetEnum geometryType ) override; + + cvf::BoundingBox domainBoundingBox() override; + void updateGridBoxData() override; + double characteristicCellSize() const override; + +protected: + void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; + + void onCreateDisplayModel() override; + void defineAxisLabels( cvf::String* xLabel, cvf::String* yLabel, cvf::String* zLabel ) override; + void onUpdateLegends() override; + + void onLoadDataAndUpdate() override; + void selectOverlayInfoConfig() override; + + cvf::Transform* scaleTransform() override; + + QString createAutoName() const override; + + void setDefaultView() override; + + void updateViewTreeItems( RiaDefines::ItemIn3dView itemType ) override; + +private: + cvf::BoundingBox computeDomainBoundingBox() const; + cvf::BoundingBox cachedDomainBoundingBox() const; + void invalidateDomainBoundingBox(); + +private: + caf::PdmChildField m_surfaceCollection; + caf::PdmChildField m_polygonInViewCollection; + caf::PdmChildField m_overlayInfoConfig; + + cvf::ref m_scaleTransform; + + mutable cvf::BoundingBox m_domainBoundingBox; + mutable bool m_isDomainBoundingBoxCached; +}; diff --git a/ApplicationLibCode/ProjectDataModel/RimGenericViewCollection.cpp b/ApplicationLibCode/ProjectDataModel/RimGenericViewCollection.cpp new file mode 100644 index 0000000000..b1f716a8f0 --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimGenericViewCollection.cpp @@ -0,0 +1,193 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RimGenericViewCollection.h" + +#include "RiaPreferences.h" + +#include "QuickAccess/RimQuickAccessCollection.h" +#include "RimCase.h" +#include "RimCellEdgeColors.h" +#include "RimEclipseCase.h" +#include "RimEclipseCellColors.h" +#include "RimEclipseView.h" +#include "RimFaultInViewCollection.h" +#include "RimGeneric3dView.h" +#include "RimStimPlanColors.h" + +#include "cafPdmDocument.h" +#include "cafPdmUiTreeOrdering.h" + +#include "cafCmdFeatureMenuBuilder.h" + +CAF_PDM_SOURCE_INIT( RimGenericViewCollection, "GenericViewCollection", "GenericViewCollection" ); + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimGenericViewCollection::RimGenericViewCollection() +{ + CAF_PDM_InitObject( "Views", ":/3DView16x16.png" ); + + CAF_PDM_InitFieldNoDefault( &m_eclipseViews, "Views", "Views" ); + CAF_PDM_InitFieldNoDefault( &m_genericViews, "GenericViews", "Views" ); + + setDeletable( false ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimGenericViewCollection::~RimGenericViewCollection() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimGenericViewCollection::isEmpty() const +{ + return !m_eclipseViews.hasChildren() && !m_genericViews.hasChildren(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimGenericViewCollection::views() const +{ + return m_eclipseViews.childrenByType(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimEclipseView* RimGenericViewCollection::addView( RimEclipseCase* eclipseCase ) +{ + RimEclipseView* view = new RimEclipseView(); + + view->setEclipseCase( eclipseCase ); + + auto prefs = RiaPreferences::current(); + view->faultCollection()->setActive( prefs->enableFaultsByDefault() ); + + view->cellEdgeResult()->setResultVariable( "MULT" ); + view->cellEdgeResult()->setActive( false ); + view->fractureColors()->setDefaultResultName(); + + caf::PdmDocument::updateUiIconStateRecursively( view ); + + m_eclipseViews.push_back( view ); + + RimQuickAccessCollection::instance()->addQuickAccessFields( view ); + + view->loadDataAndUpdate(); + + // Set default values + if ( view->currentGridCellResults() ) + { + auto defaultResult = view->currentGridCellResults()->defaultResult(); + view->cellResult()->setFromEclipseResultAddress( defaultResult ); + } + + updateConnectedEditors(); + + return view; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimGenericViewCollection::addView( RimEclipseView* view ) +{ + m_eclipseViews.push_back( view ); + updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimGenericViewCollection::removeView( RimEclipseView* view ) +{ + m_eclipseViews.removeChild( view ); + updateConnectedEditors(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimGeneric3dView* RimGenericViewCollection::addGenericView() +{ + auto* view = new RimGeneric3dView(); + + // Add before loadDataAndUpdate() so RimProject::assignViewIdToView() sees the view in the project tree + m_genericViews.push_back( view ); + caf::PdmDocument::updateUiIconStateRecursively( view ); + + view->loadDataAndUpdate(); + + updateConnectedEditors(); + + return view; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimGenericViewCollection::genericViews() const +{ + return m_genericViews.childrenByType(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimGenericViewCollection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) +{ + // Display Eclipse views and generic 3D views together as a single flat list. + for ( auto view : m_eclipseViews.childrenByType() ) + { + uiTreeOrdering.add( view ); + } + + for ( auto view : m_genericViews.childrenByType() ) + { + uiTreeOrdering.add( view ); + } + + uiTreeOrdering.skipRemainingChildren( true ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimGenericViewCollection::onChildDeleted( caf::PdmChildArrayFieldHandle* childArray, std::vector& referringObjects ) +{ + if ( auto parentCase = firstAncestorOrThisOfType() ) + { + parentCase->updateConnectedEditors(); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimGenericViewCollection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + menuBuilder << "RicNewViewFeature"; + menuBuilder << "RicNewGenericDataViewFeature"; +} diff --git a/ApplicationLibCode/ProjectDataModel/RimGenericViewCollection.h b/ApplicationLibCode/ProjectDataModel/RimGenericViewCollection.h new file mode 100644 index 0000000000..d04c9fcd4c --- /dev/null +++ b/ApplicationLibCode/ProjectDataModel/RimGenericViewCollection.h @@ -0,0 +1,64 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "cafPdmField.h" + +#include "cafPdmChildArrayField.h" +#include "cafPdmObject.h" + +#include + +class RimEclipseView; +class RimEclipseCase; +class RimGeneric3dView; + +//================================================================================================== +/// +/// Top level collection holding both Eclipse views and generic (case-less) 3D views. The two +/// types are displayed as a single flat list, see defineUiTreeOrdering(). +/// +//================================================================================================== +class RimGenericViewCollection : public caf::PdmObject +{ + CAF_PDM_HEADER_INIT; + +public: + RimGenericViewCollection(); + ~RimGenericViewCollection() override; + + bool isEmpty() const; + + RimEclipseView* addView( RimEclipseCase* eclipseCase ); + void addView( RimEclipseView* view ); + void removeView( RimEclipseView* view ); + + std::vector views() const; + + RimGeneric3dView* addGenericView(); + std::vector genericViews() const; + +private: + void onChildDeleted( caf::PdmChildArrayFieldHandle* childArray, std::vector& referringObjects ) override; + void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; + +private: + caf::PdmChildArrayField m_eclipseViews; + caf::PdmChildArrayField m_genericViews; +}; diff --git a/ApplicationLibCode/ProjectDataModel/RimOilField.cpp b/ApplicationLibCode/ProjectDataModel/RimOilField.cpp index 8a6d2db67a..008e23d6de 100644 --- a/ApplicationLibCode/ProjectDataModel/RimOilField.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimOilField.cpp @@ -28,9 +28,11 @@ #include "RimEclipseViewCollection.h" #include "RimEnsembleWellLogsCollection.h" #include "RimFractureTemplateCollection.h" +#include "RimGenericViewCollection.h" #include "RimGeoMechModels.h" #include "RimMeasurement.h" #include "RimObservedDataCollection.h" +#include "RimProject.h" #include "RimSeismicDataCollection.h" #include "RimSeismicViewCollection.h" #include "RimSummaryCaseMainCollection.h" @@ -78,8 +80,12 @@ RimOilField::RimOilField() CAF_PDM_InitFieldNoDefault( &seismicViewCollection, "SeismicViewCollection", "Seismic Views" ); seismicViewCollection = new RimSeismicViewCollection(); - CAF_PDM_InitFieldNoDefault( &eclipseViewCollection, "EclipseViewCollection", "Eclipse Views", ":/3DView16x16.png" ); - eclipseViewCollection = new RimEclipseViewCollection(); + CAF_PDM_InitFieldNoDefault( &genericViewCollection, "GenericViewCollection", "Views", ":/3DView16x16.png" ); + genericViewCollection = new RimGenericViewCollection(); + + // Obsolete field kept for reading old project files, migrated in initAfterRead() + CAF_PDM_InitFieldNoDefault( &m_eclipseViewCollection_OBSOLETE, "EclipseViewCollection", "Views" ); + m_eclipseViewCollection_OBSOLETE.xmlCapability()->setIOWritable( false ); CAF_PDM_InitFieldNoDefault( &eclipseContourMapCollection, "ContourMaps", "2d Contour Maps" ); eclipseContourMapCollection = new RimEclipseContourMapViewCollection; @@ -154,4 +160,19 @@ void RimOilField::initAfterRead() m_fractureTemplateCollection_OBSOLETE.removeChild( fractureTemplateCollection ); completionTemplateCollection->setFractureTemplateCollection( fractureTemplateCollection ); } + + // genericViewCollection replaced this field in project files newer than version 2026.09.0 + auto proj = RimProject::current(); + if ( proj && proj->isProjectFileVersionEqualOrOlderThan( "2026.09.0" ) && m_eclipseViewCollection_OBSOLETE.value() ) + { + RimEclipseViewCollection* obsoleteViewCollection = m_eclipseViewCollection_OBSOLETE.value(); + for ( RimEclipseView* view : obsoleteViewCollection->views() ) + { + obsoleteViewCollection->removeView( view ); + genericViewCollection->addView( view ); + } + + delete m_eclipseViewCollection_OBSOLETE; + m_eclipseViewCollection_OBSOLETE = nullptr; + } } diff --git a/ApplicationLibCode/ProjectDataModel/RimOilField.h b/ApplicationLibCode/ProjectDataModel/RimOilField.h index 42cac85898..ae93710d66 100644 --- a/ApplicationLibCode/ProjectDataModel/RimOilField.h +++ b/ApplicationLibCode/ProjectDataModel/RimOilField.h @@ -42,6 +42,7 @@ class RimSeismicViewCollection; class RimSurfaceCollection; class RimEnsembleWellLogsCollection; class RimPolygonCollection; +class RimGenericViewCollection; class RimEclipseViewCollection; class RimEclipseContourMapViewCollection; class RimVfpDataCollection; @@ -77,7 +78,7 @@ public: caf::PdmChildField surfaceCollection; caf::PdmChildField seismicDataCollection; caf::PdmChildField seismicViewCollection; - caf::PdmChildField eclipseViewCollection; + caf::PdmChildField genericViewCollection; caf::PdmChildField ensembleWellLogsCollection; caf::PdmChildField polygonCollection; caf::PdmChildField eclipseContourMapCollection; @@ -89,4 +90,5 @@ protected: private: caf::PdmChildField m_fractureTemplateCollection_OBSOLETE; + caf::PdmChildField m_eclipseViewCollection_OBSOLETE; }; diff --git a/ApplicationLibCode/ProjectDataModel/RimProject.cpp b/ApplicationLibCode/ProjectDataModel/RimProject.cpp index aade7e03f3..e10756d8be 100644 --- a/ApplicationLibCode/ProjectDataModel/RimProject.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimProject.cpp @@ -54,11 +54,12 @@ #include "RimEclipseCaseCollection.h" #include "RimEclipseCaseEnsemble.h" #include "RimEclipseView.h" -#include "RimEclipseViewCollection.h" #include "RimEnsembleWellLogsCollection.h" #include "RimFlowPlotCollection.h" #include "RimFractureTemplate.h" #include "RimFractureTemplateCollection.h" +#include "RimGeneric3dView.h" +#include "RimGenericViewCollection.h" #include "RimGeoMechCase.h" #include "RimGeoMechModels.h" #include "RimGridCalculationCollection.h" @@ -899,6 +900,14 @@ std::vector RimProject::allViews() const } } + if ( oilField->genericViewCollection() ) + { + for ( auto dataView : oilField->genericViewCollection()->genericViews() ) + { + views.push_back( dataView ); + } + } + if ( oilField->analysisModels() ) { for ( auto ensemble : oilField->analysisModels()->caseEnsembles.childrenByType() ) @@ -1516,7 +1525,7 @@ void RimProject::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, Q if ( oilField ) { if ( oilField->analysisModels() ) uiTreeOrdering.add( oilField->analysisModels() ); - if ( oilField->eclipseViewCollection() ) uiTreeOrdering.add( oilField->eclipseViewCollection() ); + if ( oilField->genericViewCollection() ) uiTreeOrdering.add( oilField->genericViewCollection() ); if ( oilField->geoMechModels() ) uiTreeOrdering.add( oilField->geoMechModels() ); if ( oilField->wellPathCollection() ) uiTreeOrdering.add( oilField->wellPathCollection() ); if ( oilField->polygonCollection() ) uiTreeOrdering.add( oilField->polygonCollection() ); diff --git a/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceCollection.cpp b/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceCollection.cpp index 1cab31ddff..b6bbb3ac6f 100644 --- a/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceCollection.cpp @@ -318,6 +318,8 @@ void RimSurfaceCollection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuil menuBuilder << "RicCreateEnsembleSurfaceFeature"; menuBuilder.addSeparator(); menuBuilder << "RicNewNestedCollectionFeature"; + menuBuilder.addSeparator(); + menuBuilder << "RicNewGenericDataViewFeature"; } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceInView.cpp b/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceInView.cpp index 3435d9a71f..e6339a5911 100644 --- a/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceInView.cpp +++ b/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceInView.cpp @@ -34,6 +34,8 @@ #include "RivSurfacePartMgr.h" +#include "cafPdmUiTreeOrdering.h" + CAF_PDM_SOURCE_INIT( RimSurfaceInView, "SurfaceInView" ); namespace caf @@ -326,6 +328,14 @@ void RimSurfaceInView::defineUiOrdering( QString uiConfigName, caf::PdmUiOrderin defineSeparateDataSourceUi( uiConfigName, uiOrdering ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimSurfaceInView::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) +{ + uiTreeOrdering.skipRemainingChildren( true ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceInView.h b/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceInView.h index 5414f14bdf..ceb722322c 100644 --- a/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceInView.h +++ b/ApplicationLibCode/ProjectDataModel/Surfaces/RimSurfaceInView.h @@ -85,6 +85,7 @@ private: caf::PdmFieldHandle* userDescriptionField() override; void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override; void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override; + void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; caf::PdmProxyValueField m_name; caf::PdmPtrField m_surface; diff --git a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp index 0e39c5e051..766f2b2c3f 100644 --- a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.cpp @@ -78,6 +78,8 @@ #include "cafPdmUiTreeOrdering.h" #include "cafProgressInfo.h" +#include "cvfBoundingBox.h" + #include #include #include @@ -401,6 +403,24 @@ std::vector RimWellPathCollection::allWellPaths() const return m_wellPaths.childrenByType(); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::BoundingBox RimWellPathCollection::wellPathsBoundingBox() const +{ + cvf::BoundingBox bb; + + for ( const auto wellPath : allWellPaths() ) + { + if ( !wellPath || !wellPath->wellPathGeometry() ) continue; + + for ( const auto& point : wellPath->wellPathGeometry()->wellPathPoints() ) + bb.add( point ); + } + + return bb; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.h b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.h index bd72e8ce27..1b360cd3f8 100644 --- a/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.h +++ b/ApplicationLibCode/ProjectDataModel/WellPath/RimWellPathCollection.h @@ -111,6 +111,7 @@ public: bool loadDataAndUpdate(); std::vector addWellPaths( QStringList filePaths, QStringList* errorMessages ); std::vector allWellPaths() const; + cvf::BoundingBox wellPathsBoundingBox() const; void removeWellPath( RimWellPath* wellPath ); void deleteAllWellPaths();