Description
Today, formation/zonation data lives in a single project-wide pool: RimOilField::formationNamesCollection holds a RimFormationNamesCollection of RimFormationNames objects, and each RimCase / RimReservoirGridEnsemble carries a PdmPtrField named m_activeFormationNames that points into that pool. Two cases can share the same pool entry; an active pointer selects one of many for a case; RimEclipseCase::effectiveFormationNames() falls back to the parent ensemble when a case has no formation set.
This work makes formations a distributed property of a grid model: each grid case (and each ensemble) directly owns one RimFormationNames as a PdmChildField. The central RimFormationNamesCollection is removed from the data model (kept only as a migration-only artifact for one release). The active formation indirection is dropped — a case has one formation, period. The case → ensemble fallback is preserved so a single FMU layer_zone_table.txt does not have to be duplicated across every realization.
Why
- Formations are intrinsic to a grid model, not a project-wide registry.
- Removing the central pool removes a layer of indirection (PdmPtr lookups, dropdown UI) and makes the data model easier to reason about.
- Each case is self-contained, which simplifies copy/duplicate semantics and per-case import.
Design
New ownership
RimCase gets caf::PdmChildField<RimFormationNames*> m_formationNames (direct ownership, nullable). XML keyword: FormationNames.
RimReservoirGridEnsemble gets the same field independently (it does not inherit RimCase).
RimFormationNames is reparented from the central pool to the case. No internal changes to RimFormationNames / RigFormationNames / RifFormationNamesReader.
Public API
RimCase::formationNames() returns RimFormationNames* (renamed from activeFormationNames()). Returns the case's own formation only — no fallback.
RimCase::setFormationNames(RimFormationNames*) — semantics now case takes ownership. Callers must hand over a heap-allocated object.
RimEclipseCase::effectiveFormationNames() is kept. Logic: case's own formation → parent RimReservoirGridEnsemble's formation → null. Internals updated to call formationNames() on each side. Same applies to any GeoMech equivalent.
Migration of existing project files
Maintain backward compatibility for one release using obsolete-only fields, mirroring the established m_fractureTemplateCollection_OBSOLETE pattern at RimOilField.cpp:104-105.
| Owner |
Old field / keyword |
New treatment |
RimOilField |
formationNamesCollection / FormationNamesCollection |
Renamed m_formationNamesCollection_OBSOLETE, IO write disabled, hidden |
RimCase |
m_activeFormationNames / DefaultFormationNames (PdmPtrField) |
Renamed m_activeFormationNames_OBSOLETE — type and keyword preserved so PDM pointer resolution still wires up during load |
RimReservoirGridEnsemble |
m_activeFormationNames (PdmPtrField) |
Same renaming |
RimFormationNamesCollection class |
— |
Kept as migration-only helper (deleting it would prevent CAF from instantiating the old XML element). Schedule full deletion for the release after this one. |
PDM children's initAfterRead runs before the parent's, so:
RimCase::initAfterRead() and RimReservoirGridEnsemble::initAfterRead(): if the obsolete pointer is non-null, deep-clone via caf::PdmObjectFactory::copyByXmlSerialization into the new m_formationNames child, then null out the obsolete pointer.
RimOilField::initAfterRead(): empty the obsolete collection (children have already been cloned into cases).
Sharing semantics: two cases that previously shared one pool entry now each get an independent clone — accepted change.
Import command
Rewrite RicImportFormationNamesFeature (Commands/RicImportFormationNamesFeature.cpp:59-143):
onActionTriggered: prompt for files; resolve the target case from the active reservoir view (RiaApplication::instance()->activeReservoirView()->ownerCase()); abort with a warning if no active case.
- Construct a fresh
RimFormationNames, set the file path, call readFormationNamesFile, then ownerCase->setFormationNames(formationNames) to reparent into the child field.
- Keep the existing
colorLegendCollection->createColorLegendFromFormationNames(...) and setFormationCellResultAndLegend post-import calls.
- Retire the
importFormationFiles(QStringList) static helper; replace with importFormationFile(const QString&, RimCase*) returning the heap-allocated RimFormationNames*.
Ensemble auto-detection
RimReservoirGridEnsemble.cpp:838-846 currently funnels auto-detected .lyr / layer_zone_table.txt files through the central import. Rewrite:
- Detect a co-located formation file at the ensemble level. Construct one
RimFormationNames, parse it, attach via ensemble->setFormationNames(...).
- Realizations inherit through the kept
effectiveFormationNames() fallback — no per-realization clones unless a realization has a distinct co-located file.
- A realization with its own distinct file gets a fresh
RimFormationNames (case-own takes precedence over ensemble in the fallback chain).
Critical files
ApplicationLibCode/ProjectDataModel/RimOilField.h / .cpp
ApplicationLibCode/ProjectDataModel/RimCase.h / .cpp
ApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h / .cpp
ApplicationLibCode/ProjectDataModel/RimEclipseCase.h / .cpp
ApplicationLibCode/ProjectDataModel/RimGeoMechCase.h / .cpp
ApplicationLibCode/ProjectDataModel/RimProject.cpp
ApplicationLibCode/ProjectDataModel/Formations/RimFormationNamesCollection.h / .cpp (kept as migration-only)
ApplicationLibCode/Commands/RicImportFormationNamesFeature.cpp
ApplicationLibCode/Commands/RicElasticPropertiesImportTools.cpp
ApplicationLibCode/Application/RiaApplication.cpp
- All consumer files listed in the rename step below
Reusable utilities
caf::PdmObjectFactory::copyByXmlSerialization — deep-clone in migration
RiaFieldHandleTools::disableWriteAndSetFieldHidden — mark obsolete fields
- Existing OBSOLETE pattern in
RimOilField.cpp:104-105 and RimOilField::initAfterRead() at :150-158
RifFormationNamesReader::readFormationNamesFile — file parsing unchanged
RimColorLegendCollection::createColorLegendFromFormationNames — legend creation unchanged
Steps
Verification
- Old project round-trip: open a pre-refactor
.rsp with formations assigned. Verify each case shows its formation as a child node; 3D view shades correctly; save → reopen preserves it under the new schema (no <FormationNamesCollection> written, only per-case <FormationNames>).
- Fresh import: in the active eclipse view, run Import Formations, pick a
.lyr, confirm it lands as a child of the case and the formation cell-result legend updates.
- FMU ensemble auto-detect: open an ensemble with a co-located
layer_zone_table.txt. Verify the ensemble owns the RimFormationNames and realizations without their own file inherit via effectiveFormationNames(). Verify a realization with its own distinct file shadows the ensemble.
- Plot consumers: verify well log tracks, RFT plots, well allocation plots, statistics contour maps, color legends, and StimPlan geological export render formation names correctly against
case->formationNames().
- GeoMech path: open a
.odb case with formations; verify formation-based result calculations still work via RigFemPartResultsCollection.
Links
- Existing OBSOLETE pattern reference:
ApplicationLibCode/ProjectDataModel/RimOilField.cpp lines 66, 104-105, 150-158
- Central collection class:
ApplicationLibCode/ProjectDataModel/Formations/RimFormationNamesCollection.h
- Per-file formation entry:
ApplicationLibCode/ProjectDataModel/Formations/RimFormationNames.h
- Runtime data:
ApplicationLibCode/ReservoirDataModel/RigFormationNames.h
- File reader:
ApplicationLibCode/FileInterface/RifFormationNamesReader.h
- Import command:
ApplicationLibCode/Commands/RicImportFormationNamesFeature.cpp
- Existing fallback:
ApplicationLibCode/ProjectDataModel/RimEclipseCase.cpp:551-565
Description
Today, formation/zonation data lives in a single project-wide pool:
RimOilField::formationNamesCollectionholds aRimFormationNamesCollectionofRimFormationNamesobjects, and eachRimCase/RimReservoirGridEnsemblecarries aPdmPtrFieldnamedm_activeFormationNamesthat points into that pool. Two cases can share the same pool entry; an active pointer selects one of many for a case;RimEclipseCase::effectiveFormationNames()falls back to the parent ensemble when a case has no formation set.This work makes formations a distributed property of a grid model: each grid case (and each ensemble) directly owns one
RimFormationNamesas aPdmChildField. The centralRimFormationNamesCollectionis removed from the data model (kept only as a migration-only artifact for one release). The active formation indirection is dropped — a case has one formation, period. The case → ensemble fallback is preserved so a single FMUlayer_zone_table.txtdoes not have to be duplicated across every realization.Why
Design
New ownership
RimCasegetscaf::PdmChildField<RimFormationNames*> m_formationNames(direct ownership, nullable). XML keyword:FormationNames.RimReservoirGridEnsemblegets the same field independently (it does not inheritRimCase).RimFormationNamesis reparented from the central pool to the case. No internal changes toRimFormationNames/RigFormationNames/RifFormationNamesReader.Public API
RimCase::formationNames()returnsRimFormationNames*(renamed fromactiveFormationNames()). Returns the case's own formation only — no fallback.RimCase::setFormationNames(RimFormationNames*)— semantics now case takes ownership. Callers must hand over a heap-allocated object.RimEclipseCase::effectiveFormationNames()is kept. Logic: case's own formation → parentRimReservoirGridEnsemble's formation → null. Internals updated to callformationNames()on each side. Same applies to any GeoMech equivalent.Migration of existing project files
Maintain backward compatibility for one release using obsolete-only fields, mirroring the established
m_fractureTemplateCollection_OBSOLETEpattern atRimOilField.cpp:104-105.RimOilFieldformationNamesCollection/FormationNamesCollectionm_formationNamesCollection_OBSOLETE, IO write disabled, hiddenRimCasem_activeFormationNames/DefaultFormationNames(PdmPtrField)m_activeFormationNames_OBSOLETE— type and keyword preserved so PDM pointer resolution still wires up during loadRimReservoirGridEnsemblem_activeFormationNames(PdmPtrField)RimFormationNamesCollectionclassPDM children's
initAfterReadruns before the parent's, so:RimCase::initAfterRead()andRimReservoirGridEnsemble::initAfterRead(): if the obsolete pointer is non-null, deep-clone viacaf::PdmObjectFactory::copyByXmlSerializationinto the newm_formationNameschild, then null out the obsolete pointer.RimOilField::initAfterRead(): empty the obsolete collection (children have already been cloned into cases).Sharing semantics: two cases that previously shared one pool entry now each get an independent clone — accepted change.
Import command
Rewrite
RicImportFormationNamesFeature(Commands/RicImportFormationNamesFeature.cpp:59-143):onActionTriggered: prompt for files; resolve the target case from the active reservoir view (RiaApplication::instance()->activeReservoirView()->ownerCase()); abort with a warning if no active case.RimFormationNames, set the file path, callreadFormationNamesFile, thenownerCase->setFormationNames(formationNames)to reparent into the child field.colorLegendCollection->createColorLegendFromFormationNames(...)andsetFormationCellResultAndLegendpost-import calls.importFormationFiles(QStringList)static helper; replace withimportFormationFile(const QString&, RimCase*)returning the heap-allocatedRimFormationNames*.Ensemble auto-detection
RimReservoirGridEnsemble.cpp:838-846currently funnels auto-detected.lyr/layer_zone_table.txtfiles through the central import. Rewrite:RimFormationNames, parse it, attach viaensemble->setFormationNames(...).effectiveFormationNames()fallback — no per-realization clones unless a realization has a distinct co-located file.RimFormationNames(case-own takes precedence over ensemble in the fallback chain).Critical files
ApplicationLibCode/ProjectDataModel/RimOilField.h/.cppApplicationLibCode/ProjectDataModel/RimCase.h/.cppApplicationLibCode/ProjectDataModel/RimReservoirGridEnsemble.h/.cppApplicationLibCode/ProjectDataModel/RimEclipseCase.h/.cppApplicationLibCode/ProjectDataModel/RimGeoMechCase.h/.cppApplicationLibCode/ProjectDataModel/RimProject.cppApplicationLibCode/ProjectDataModel/Formations/RimFormationNamesCollection.h/.cpp(kept as migration-only)ApplicationLibCode/Commands/RicImportFormationNamesFeature.cppApplicationLibCode/Commands/RicElasticPropertiesImportTools.cppApplicationLibCode/Application/RiaApplication.cppReusable utilities
caf::PdmObjectFactory::copyByXmlSerialization— deep-clone in migrationRiaFieldHandleTools::disableWriteAndSetFieldHidden— mark obsolete fieldsRimOilField.cpp:104-105andRimOilField::initAfterRead()at:150-158RifFormationNamesReader::readFormationNamesFile— file parsing unchangedRimColorLegendCollection::createColorLegendFromFormationNames— legend creation unchangedSteps
m_formationNamesPdmChildFieldtoRimCaseandRimReservoirGridEnsemble(XML keywordFormationNames). Add publicformationNames()getter and updatesetFormationNames()to reparent into the child field.m_activeFormationNames→m_activeFormationNames_OBSOLETEonRimCaseandRimReservoirGridEnsemble. ApplydisableWriteAndSetFieldHidden. Keep XML keywordDefaultFormationNames.RimOilField::formationNamesCollection→m_formationNamesCollection_OBSOLETE, disable write, hide.initAfterReadmigration inRimCaseandRimReservoirGridEnsemble: clone obsolete pointer target into the new child field. Empty the obsolete collection inRimOilField::initAfterRead().RimEclipseCase::effectiveFormationNames()(and any GeoMech equivalent); update its internal calls to the renamedformationNames()on case and ensemble.RimCase::calculateValueOptionsblock for formations (the central-pool dropdown logic atRimCase.cpp:264-283). The new field is aPdmChildFieldand shows as a tree child, not a dropdown.RicImportFormationNamesFeaturefor per-case import..lyrauto-detection inRimReservoirGridEnsemble.cpp:838-846to assign at the ensemble level; per-realization assignment only when a distinct file is co-located.RiaApplication.cpp:625-640; replace with a per-case loop callingc->formationNames()->readFormationNamesFile(...)when non-null. Same for ensembles.RicElasticPropertiesImportTools::getFormationNamesto take aRimCase*and return that case's owned formation.RimProject.cpp:1544.activeFormationNames()→formationNames()across consumers:ProjectDataModel/WellLog/RimWellLogTrack.cpp:128ProjectDataModel/WellLog/RimWellRftPlot.*ProjectDataModel/Flow/RimWellAllocationPlot.cpp:910ProjectDataModel/RimEnsembleWellLogCurveSet.cpp:1245ProjectDataModel/RimEclipseView.*,RimGeoMechView.*ProjectDataModel/ContourMap/RimStatisticsContourMap.cpp:77ProjectDataModel/RimColorLegendCollection.cpp:223FileInterface/RifStimPlanModelGeologicalFrkExporter.cppGeoMech/GeoMechDataModel/RigFemPartResultCalculatorFormationIndices.*clang-formaton every changed.cpp/.h.build/:cmake --build build. Must compile clean.ResInsightUnitTests—RifFormationNamesReader-Testshould still pass unchanged.Verification
.rspwith formations assigned. Verify each case shows its formation as a child node; 3D view shades correctly; save → reopen preserves it under the new schema (no<FormationNamesCollection>written, only per-case<FormationNames>)..lyr, confirm it lands as a child of the case and the formation cell-result legend updates.layer_zone_table.txt. Verify the ensemble owns theRimFormationNamesand realizations without their own file inherit viaeffectiveFormationNames(). Verify a realization with its own distinct file shadows the ensemble.case->formationNames()..odbcase with formations; verify formation-based result calculations still work viaRigFemPartResultsCollection.Links
ApplicationLibCode/ProjectDataModel/RimOilField.cpplines 66, 104-105, 150-158ApplicationLibCode/ProjectDataModel/Formations/RimFormationNamesCollection.hApplicationLibCode/ProjectDataModel/Formations/RimFormationNames.hApplicationLibCode/ReservoirDataModel/RigFormationNames.hApplicationLibCode/FileInterface/RifFormationNamesReader.hApplicationLibCode/Commands/RicImportFormationNamesFeature.cppApplicationLibCode/ProjectDataModel/RimEclipseCase.cpp:551-565