diff --git a/ApplicationCode/Commands/WellPathCommands/CMakeLists_files.cmake b/ApplicationCode/Commands/WellPathCommands/CMakeLists_files.cmake index 063a266700..587e6a7b83 100644 --- a/ApplicationCode/Commands/WellPathCommands/CMakeLists_files.cmake +++ b/ApplicationCode/Commands/WellPathCommands/CMakeLists_files.cmake @@ -6,6 +6,7 @@ endif() set (SOURCE_GROUP_HEADER_FILES ${CEE_CURRENT_LIST_DIR}RicWellPathDeleteFeature.h +${CEE_CURRENT_LIST_DIR}RicWellPathExportCompletionDataFeature.h ${CEE_CURRENT_LIST_DIR}RicWellPathImportCompletionsFileFeature.h ${CEE_CURRENT_LIST_DIR}RicWellPathsImportFileFeature.h ${CEE_CURRENT_LIST_DIR}RicWellPathsImportSsihubFeature.h @@ -14,6 +15,7 @@ ${CEE_CURRENT_LIST_DIR}RicWellPathViewerEventHandler.h set (SOURCE_GROUP_SOURCE_FILES ${CEE_CURRENT_LIST_DIR}RicWellPathDeleteFeature.cpp +${CEE_CURRENT_LIST_DIR}RicWellPathExportCompletionDataFeature.cpp ${CEE_CURRENT_LIST_DIR}RicWellPathImportCompletionsFileFeature.cpp ${CEE_CURRENT_LIST_DIR}RicWellPathsImportFileFeature.cpp ${CEE_CURRENT_LIST_DIR}RicWellPathsImportSsihubFeature.cpp diff --git a/ApplicationCode/Commands/WellPathCommands/RicWellPathExportCompletionDataFeature.cpp b/ApplicationCode/Commands/WellPathCommands/RicWellPathExportCompletionDataFeature.cpp new file mode 100644 index 0000000000..4e6ed3d9ba --- /dev/null +++ b/ApplicationCode/Commands/WellPathCommands/RicWellPathExportCompletionDataFeature.cpp @@ -0,0 +1,354 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2015- Statoil ASA +// Copyright (C) 2015- Ceetron Solutions AS +// +// 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 "RicWellPathExportCompletionDataFeature.h" + +#include "RiaApplication.h" +#include "RiaLogging.h" + +#include "RimProject.h" +#include "RimWellPath.h" +#include "RimFishbonesMultipleSubs.h" +#include "RimCaseAndFileExportSettings.h" + +#include "RiuMainWindow.h" + +#include "RifEclipseOutputTableFormatter.h" + +#include "RigWellLogExtractionTools.h" +#include "RigEclipseCaseData.h" +#include "RigMainGrid.h" +#include "RigWellPath.h" + +#include "cafSelectionManager.h" +#include "cafPdmUiPropertyViewDialog.h" + +#include +#include +#include + +namespace caf +{ + CAF_CMD_SOURCE_INIT(RicWellPathExportCompletionDataFeature, "RicWellPathExportCompletionDataFeature"); + + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicWellPathExportCompletionDataFeature::isCommandEnabled() +{ + std::vector objects; + caf::SelectionManager::instance()->objectsByType(&objects); + + if (objects.size() == 1) { + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicWellPathExportCompletionDataFeature::onActionTriggered(bool isChecked) +{ + std::vector objects; + caf::SelectionManager::instance()->objectsByType(&objects); + + CVF_ASSERT(objects.size() == 1); + + RiaApplication* app = RiaApplication::instance(); + + QString projectFolder = app->currentProjectPath(); + QString defaultDir = RiaApplication::instance()->lastUsedDialogDirectoryWithFallback("COMPLETIONS", projectFolder); + + RimCaseAndFileExportSettings exportSettings; + + exportSettings.fileName = QDir(defaultDir).filePath("Completions"); + + RimEclipseCase* caseToApply; + objects[0]->firstAncestorOrThisOfType(caseToApply); + exportSettings.caseToApply = caseToApply; + + caf::PdmUiPropertyViewDialog propertyDialog(RiuMainWindow::instance(), &exportSettings, "Export Completion Data", ""); + if (propertyDialog.exec() == QDialog::Accepted) + { + RiaApplication::instance()->setLastUsedDialogDirectory("COMPLETIONS", QFileInfo(exportSettings.fileName).absolutePath()); + + exportToFolder(objects[0], exportSettings.fileName, exportSettings.caseToApply); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicWellPathExportCompletionDataFeature::setupActionLook(QAction* actionToSetup) +{ + actionToSetup->setText("Export Completion Data"); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicWellPathExportCompletionDataFeature::exportToFolder(RimWellPath* wellPath, const QString& fileName, const RimEclipseCase* caseToApply) +{ + QFile exportFile(fileName); + + if (!exportFile.open(QIODevice::WriteOnly)) + { + RiaLogging::error(QString("Export Completions Data: Could not open the file: %1").arg(fileName)); + return; + } + + QTextStream stream(&exportFile); + + const RigEclipseCaseData* caseData = caseToApply->eclipseCaseData(); + std::vector wellPathCells = findIntersectingCells(caseData, wellPath->wellPathGeometry()->m_wellPathPoints); + + RifEclipseOutputTableFormatter formatter(stream); + std::vector header = { + RifEclipseOutputTableColumn{"Well", LEFT}, + RifEclipseOutputTableColumn{"I", LEFT}, + RifEclipseOutputTableColumn{"J", LEFT}, + RifEclipseOutputTableColumn{"K1", LEFT}, + RifEclipseOutputTableColumn{"K2", LEFT}, + RifEclipseOutputTableColumn{"Status", LEFT}, + RifEclipseOutputTableColumn{"SAT", LEFT}, + RifEclipseOutputTableColumn{"TR", LEFT}, + RifEclipseOutputTableColumn{"DIAM", LEFT}, + RifEclipseOutputTableColumn{"KH", LEFT}, + RifEclipseOutputTableColumn{"S", LEFT}, + RifEclipseOutputTableColumn{"Df", LEFT}, + RifEclipseOutputTableColumn{"DIR", LEFT}, + RifEclipseOutputTableColumn{"r0", LEFT} + }; + + formatter.keyword("COMPDAT"); + formatter.header(header); + + for (RimFishbonesMultipleSubs* subs : wellPath->fishbonesSubs) + { + for (size_t subIndex = 0; subIndex < subs->locationOfSubs().size(); ++subIndex) + { + for (size_t lateralIndex = 0; lateralIndex < subs->lateralLengths().size(); ++lateralIndex) + { + std::vector lateralCoords = subs->coordsForLateral(subIndex, lateralIndex); + + std::vector lateralCells = findIntersectingCells(caseData, lateralCoords); + + std::vector cellsUniqueToLateral = filterWellPathCells(lateralCells, wellPathCells); + + std::vector cellRanges = getCellIndexRange(caseData->mainGrid(), cellsUniqueToLateral); + + formatter.comment(QString("Fishbone %1 - Sub: %2 - Lateral: %3").arg(subs->name()).arg(subIndex).arg(lateralIndex)); + for (auto cellRange : cellRanges) + { + // Add cell indices + formatter.add(wellPath->name()).addZeroBasedCellIndex(cellRange.i).addZeroBasedCellIndex(cellRange.j).addZeroBasedCellIndex(cellRange.k1).addZeroBasedCellIndex(cellRange.k2); + // Remaining data, to be computed + formatter.add("'OPEN'").add("1*").add("1*").add(0.0).add("1*").add("1*").add("1*").add("'Z'").add("1*"); + formatter.rowCompleted(); + } + } + } + } + formatter.flush(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RicWellPathExportCompletionDataFeature::findCloseCells(const RigEclipseCaseData* caseData, const cvf::BoundingBox& bb) +{ + std::vector closeCells; + caseData->mainGrid()->findIntersectingCells(bb, &closeCells); + return closeCells; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RicWellPathExportCompletionDataFeature::getCellIndexRange(const RigMainGrid* grid, const std::vector& cellIndices) +{ + // Retrieve I, J, K indices + std::vector eclipseCellIndices; + for (auto cellIndex : cellIndices) + { + size_t i, j, k; + if (!grid->ijkFromCellIndex(cellIndex, &i, &j, &k)) continue; + eclipseCellIndices.push_back(std::make_tuple(i, j, k)); + } + + // Remove any duplicate cells + { + std::set uniqueCellIndices(eclipseCellIndices.begin(), eclipseCellIndices.end()); + eclipseCellIndices.assign(uniqueCellIndices.begin(), uniqueCellIndices.end()); + } + + // Group cell indices in K-ranges + std::sort(eclipseCellIndices.begin(), eclipseCellIndices.end(), RicWellPathExportCompletionDataFeature::cellOrdering); + std::vector eclipseCellRanges; + size_t lastI = std::numeric_limits::max(); + size_t lastJ = std::numeric_limits::max(); + size_t lastK = std::numeric_limits::max(); + size_t startK = std::numeric_limits::max(); + for (EclipseCellIndex cell : eclipseCellIndices) + { + size_t i, j, k; + std::tie(i, j, k) = cell; + if (i != lastI || j != lastJ || k != lastK + 1) + { + if (startK != std::numeric_limits::max()) + { + EclipseCellIndexRange cellRange = {lastI, lastJ, startK, lastK}; + eclipseCellRanges.push_back(cellRange); + } + lastI = i; + lastJ = j; + lastK = k; + startK = k; + } + else + { + lastK = k; + } + } + // Append last cell range + if (startK != std::numeric_limits::max()) + { + EclipseCellIndexRange cellRange = {lastI, lastJ, startK, lastK}; + eclipseCellRanges.push_back(cellRange); + } + return eclipseCellRanges; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicWellPathExportCompletionDataFeature::cellOrdering(const EclipseCellIndex& cell1, const EclipseCellIndex& cell2) +{ + size_t i1, i2, j1, j2, k1, k2; + std::tie(i1, j1, k1) = cell1; + std::tie(i2, j2, k2) = cell2; + if (i1 == i2) + { + if (j1 == j2) + { + return k1 < k2; + } + else + { + return j1 < j2; + } + } + else + { + return i1 < i2; + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RicWellPathExportCompletionDataFeature::findIntersectingCells(const RigEclipseCaseData* caseData, const std::vector& coords) +{ + const std::vector& nodeCoords = caseData->mainGrid()->nodes(); + std::vector cells; + + // Find starting cell + if (coords.size() > 0) + { + cvf::BoundingBox bb; + bb.add(coords[0]); + std::vector closeCells = findCloseCells(caseData, bb); + cvf::Vec3d hexCorners[8]; + + for (size_t closeCell : closeCells) + { + const RigCell& cell = caseData->mainGrid()->globalCellArray()[closeCell]; + if (cell.isInvalid()) continue; + + setHexCorners(cell, nodeCoords, hexCorners); + + if (RigHexIntersector::isPointInCell(coords[0], hexCorners, closeCell)) + { + cells.push_back(closeCell); + break; + } + } + } + + for (size_t i = 0; i < coords.size() - 1; ++i) + { + cvf::BoundingBox bb; + bb.add(coords[i]); + bb.add(coords[i + 1]); + + std::vector closeCells = findCloseCells(caseData, bb); + + cvf::Vec3d hexCorners[8]; + std::vector intersections; + + for (size_t closeCell : closeCells) + { + const RigCell& cell = caseData->mainGrid()->globalCellArray()[closeCell]; + if (cell.isInvalid()) continue; + + setHexCorners(cell, nodeCoords, hexCorners); + + RigHexIntersector::lineHexCellIntersection(coords[i], coords[i + 1], hexCorners, closeCell, &intersections); + } + + for (auto intersection : intersections) + { + cells.push_back(intersection.m_hexIndex); + } + } + std::sort(cells.begin(), cells.end()); + return cells; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RicWellPathExportCompletionDataFeature::setHexCorners(const RigCell& cell, const std::vector& nodeCoords, cvf::Vec3d* hexCorners) +{ + const caf::SizeTArray8& cornerIndices = cell.cornerIndices(); + + hexCorners[0] = nodeCoords[cornerIndices[0]]; + hexCorners[1] = nodeCoords[cornerIndices[1]]; + hexCorners[2] = nodeCoords[cornerIndices[2]]; + hexCorners[3] = nodeCoords[cornerIndices[3]]; + hexCorners[4] = nodeCoords[cornerIndices[4]]; + hexCorners[5] = nodeCoords[cornerIndices[5]]; + hexCorners[6] = nodeCoords[cornerIndices[6]]; + hexCorners[7] = nodeCoords[cornerIndices[7]]; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RicWellPathExportCompletionDataFeature::filterWellPathCells(const std::vector& completionCells, const std::vector& wellPathCells) +{ + std::vector filteredCells; + std::set_difference(completionCells.begin(), completionCells.end(), wellPathCells.begin(), wellPathCells.end(), std::back_inserter(filteredCells)); + return filteredCells; +} + +} // end namespace caf diff --git a/ApplicationCode/Commands/WellPathCommands/RicWellPathExportCompletionDataFeature.h b/ApplicationCode/Commands/WellPathCommands/RicWellPathExportCompletionDataFeature.h new file mode 100644 index 0000000000..cba7d2507e --- /dev/null +++ b/ApplicationCode/Commands/WellPathCommands/RicWellPathExportCompletionDataFeature.h @@ -0,0 +1,71 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2015- Statoil ASA +// Copyright (C) 2015- Ceetron Solutions AS +// +// 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 "RigWellLogExtractionTools.h" + +#include "cafCmdFeature.h" + +#include "cvfBoundingBox.h" + +class RimWellPath; +class RimEclipseCase; +class RigEclipseCaseData; +class RigMainGrid; +class RigCell; + +namespace caf +{ + +struct EclipseCellIndexRange { + size_t i; + size_t j; + size_t k1; + size_t k2; +}; + +typedef std::tuple EclipseCellIndex; + +//================================================================================================== +/// +//================================================================================================== +class RicWellPathExportCompletionDataFeature : public CmdFeature +{ + CAF_CMD_HEADER_INIT; +protected: + + // Overrides + virtual bool isCommandEnabled() override; + virtual void onActionTriggered(bool isChecked) override; + virtual void setupActionLook(QAction* actionToSetup) override; + +private: + static void exportToFolder(RimWellPath* wellPath, const QString& fileName, const RimEclipseCase* caseToApply); + static std::vector findCloseCells(const RigEclipseCaseData* caseData, const cvf::BoundingBox& bb); + static std::vector getCellIndexRange(const RigMainGrid* grid, const std::vector& cellIndices); + static bool cellOrdering(const EclipseCellIndex& cell1, const EclipseCellIndex& cell2); + static std::vector findIntersectingCells(const RigEclipseCaseData* grid, const std::vector& coords); + static void setHexCorners(const RigCell& cell, const std::vector& nodeCoords, cvf::Vec3d* hexCorners); + static std::vector filterWellPathCells(const std::vector& completionCells, const std::vector& wellPathCells); +}; + + + +} // end namespace caf diff --git a/ApplicationCode/FileInterface/RifEclipseOutputTableFormatter.cpp b/ApplicationCode/FileInterface/RifEclipseOutputTableFormatter.cpp index 27d8347a5b..c1fc5e076b 100644 --- a/ApplicationCode/FileInterface/RifEclipseOutputTableFormatter.cpp +++ b/ApplicationCode/FileInterface/RifEclipseOutputTableFormatter.cpp @@ -48,12 +48,15 @@ void RifEclipseOutputTableFormatter::flush() //-------------------------------------------------------------------------------------------------- void RifEclipseOutputTableFormatter::outputBuffer() { - m_out << "-- "; - for (RifEclipseOutputTableColumn& column : m_columns) + if (m_columns.size() > 0) { - m_out << formatColumn(column.title, column); + m_out << "-- "; + for (RifEclipseOutputTableColumn& column : m_columns) + { + m_out << formatColumn(column.title, column); + } + m_out << "\n"; } - m_out << "\n"; for (auto line : m_buffer) { @@ -253,6 +256,6 @@ QString RifEclipseOutputTableFormatter::formatColumn(const QString str, RifEclip } else { - return str.rightJustified(column.width + m_colSpacing, ' '); + return str.rightJustified(column.width, ' ').leftJustified(m_colSpacing, ' '); } } diff --git a/ApplicationCode/ProjectDataModel/RimContextCommandBuilder.cpp b/ApplicationCode/ProjectDataModel/RimContextCommandBuilder.cpp index 0e8d012ed1..d2aeb22561 100644 --- a/ApplicationCode/ProjectDataModel/RimContextCommandBuilder.cpp +++ b/ApplicationCode/ProjectDataModel/RimContextCommandBuilder.cpp @@ -399,6 +399,7 @@ QStringList RimContextCommandBuilder::commandsFromSelection() commandIds << "RicNewPerforationIntervalFeature"; commandIds << "RicEditPerforationCollectionFeature"; commandIds << "RicExportFishbonesLateralsFeature"; + commandIds << "RicWellPathExportCompletionDataFeature"; commandIds << "RicWellPathImportCompletionsFileFeature"; // Work in progress -- End