|
|
#include "nmWellEditorController.h"
|
|
|
|
|
|
#include "nmDataWellBase.h"
|
|
|
#include "nmDataVerticalWell.h"
|
|
|
#include "nmDataVerticalFracturedWell.h"
|
|
|
#include "nmDataHorizontalFracturedWell.h"
|
|
|
#include "nmWxWellboreTrajectoryDisplay.h"
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
|
|
|
nmWellEditorController::nmWellEditorController(nmDataWellBase* pModel, nmWxWellboreTrajectoryDisplay* pView, QObject *parent)
|
|
|
: QObject(parent),
|
|
|
m_pModel(pModel),
|
|
|
m_pView(pView),
|
|
|
m_eCurrentMode(WellEditMode::None)
|
|
|
{
|
|
|
}
|
|
|
|
|
|
nmWellEditorController::~nmWellEditorController()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::clearAllEditModes()
|
|
|
{
|
|
|
// 关闭所有可能激活的视图模式
|
|
|
if(m_pView) {
|
|
|
m_pView->setEditWellMode(false);
|
|
|
m_pView->setPerforationEditMode(false);
|
|
|
m_pView->setAddPerforationMode(false);
|
|
|
m_pView->setDeletePerforationMode(false);
|
|
|
m_pView->setSnapPerforationMode(false);
|
|
|
}
|
|
|
|
|
|
m_eCurrentMode = WellEditMode::None; // 重置控制器内部模式状态
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::setMode(WellEditMode eNewMode)
|
|
|
{
|
|
|
if(!m_pView) return;
|
|
|
|
|
|
// 如果请求激活的模式与当前模式相同,则视为取消该模式
|
|
|
if(m_eCurrentMode == eNewMode && eNewMode != WellEditMode::None) {
|
|
|
clearAllEditModes();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 首先,清理所有当前的编辑模式
|
|
|
clearAllEditModes();
|
|
|
|
|
|
// 然后,激活新请求的模式
|
|
|
m_eCurrentMode = eNewMode;
|
|
|
|
|
|
switch(eNewMode) {
|
|
|
case WellEditMode::EditWellboreTrajectory:
|
|
|
m_pView->setEditWellMode(true);
|
|
|
break;
|
|
|
|
|
|
case WellEditMode::EditPerforation:
|
|
|
m_pView->setPerforationEditMode(true);
|
|
|
break;
|
|
|
|
|
|
case WellEditMode::AddPerforation:
|
|
|
m_pView->setAddPerforationMode(true);
|
|
|
break;
|
|
|
|
|
|
case WellEditMode::DeletePerforation:
|
|
|
m_pView->setDeletePerforationMode(true);
|
|
|
break;
|
|
|
|
|
|
case WellEditMode::SnapPerforation:
|
|
|
m_pView->setSnapPerforationMode(true);
|
|
|
// 处理吸附逻辑
|
|
|
snapPerforationToWellbore();
|
|
|
break;
|
|
|
|
|
|
case WellEditMode::None:
|
|
|
default:
|
|
|
// 已经在 clearAllEditModes 中处理
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::onRequestEditWellMode(int nCurrentViewId)
|
|
|
{
|
|
|
// 控制器决定是否允许在当前视图下编辑井筒/裂缝
|
|
|
setMode(WellEditMode::EditWellboreTrajectory);
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::onRequestEditPerforationsMode(int nCurrentViewId)
|
|
|
{
|
|
|
setMode(WellEditMode::EditPerforation);
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::onRequestNewPerforationMode(int nCurrentViewId)
|
|
|
{
|
|
|
setMode(WellEditMode::AddPerforation);
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::onRequestDeletePerforationMode(int nCurrentViewId)
|
|
|
{
|
|
|
setMode(WellEditMode::DeletePerforation);
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::onRequestSnapPerforationMode(int nCurrentViewId)
|
|
|
{
|
|
|
setMode(WellEditMode::SnapPerforation);
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::onViewChanged(int nNewViewId)
|
|
|
{
|
|
|
// 视图切换时,控制器应清空所有当前模式,确保状态同步
|
|
|
clearAllEditModes();
|
|
|
// 视图本身已经重置了其工具栏按钮的选中状态
|
|
|
// 如果需要,控制器可以在这里根据 nNewViewId 激活某个视图的默认模式
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::slotClearViewStates()
|
|
|
{
|
|
|
clearAllEditModes();
|
|
|
}
|
|
|
|
|
|
// 响应井筒点移动信号
|
|
|
void nmWellEditorController::slotWellboreTrajectoryPointMoved(const QPointF& newScenePos)
|
|
|
{
|
|
|
if(!m_pModel) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 1. 只有直井类型可以修改井点位置
|
|
|
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(m_pModel);
|
|
|
|
|
|
if(!pVerticalWell) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
double dNewX = newScenePos.x();
|
|
|
double dNewY = newScenePos.y();
|
|
|
|
|
|
// 临时阻塞 m_pModel 的信号
|
|
|
bool bOldSignalsBlocked = m_pModel->blockSignals(true);
|
|
|
|
|
|
// 2. 修改数据模型
|
|
|
// 直接更新 nmDataVerticalWell 中的 X 和 Y 值
|
|
|
pVerticalWell->getX().setValue(dNewX);
|
|
|
pVerticalWell->getY().setValue(dNewY);
|
|
|
|
|
|
// 恢复原始阻塞状态
|
|
|
m_pModel->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 刷新纵截面视图
|
|
|
m_pView->updataCrossSectionView();
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
if (!bOldSignalsBlocked) {
|
|
|
m_pModel->notifyParameterChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::slotPerforationDragFinished(nmDataPerforation* pPerforationData, double proposedMdStart, double proposedMdEnd)
|
|
|
{
|
|
|
if(!pPerforationData) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 临时阻塞 pPerforationData 的信号
|
|
|
bool bOldSignalsBlocked = pPerforationData->blockSignals(true);
|
|
|
|
|
|
// 2. 修改数据模型
|
|
|
// 直接更新 nmDataVerticalWell 中的 X 和 Y 值
|
|
|
pPerforationData->getMdStart().setValue(proposedMdStart);
|
|
|
pPerforationData->getMdEnd().setValue(proposedMdEnd);
|
|
|
|
|
|
// 恢复原始阻塞状态
|
|
|
pPerforationData->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
if (!bOldSignalsBlocked) {
|
|
|
m_pModel->notifyParameterChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
void nmWellEditorController::slotTryAddNewPerforation(const QPointF& scenePos,
|
|
|
double fractureTopMd,
|
|
|
double fractureBottomMd,
|
|
|
const QLineF& wellboreVisualLine)
|
|
|
{
|
|
|
// 1. 确保当前模式是添加射孔模式和由模型数据
|
|
|
if(m_eCurrentMode != WellEditMode::AddPerforation || !m_pModel) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 是否添加成功
|
|
|
bool bIsAddSuccess = false;
|
|
|
|
|
|
// 2. 根据井筒类型进行分发
|
|
|
switch(m_pModel->getWellType()) {
|
|
|
case NM_WELL_MODEL::Vertical_Well: {
|
|
|
// 垂直井,只需处理场景坐标
|
|
|
bIsAddSuccess = tryAddNewPerforationInVerticalWell(scenePos);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
case NM_WELL_MODEL::Vertical_Fractured_Well: {
|
|
|
// 垂直裂缝井,需要裂缝MD范围
|
|
|
bIsAddSuccess = tryAddNewPerforationInVerticalFracturedWell(scenePos, fractureTopMd, fractureBottomMd);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
case NM_WELL_MODEL::Horizontal_Fractured_Well: {
|
|
|
// 水平裂缝井,需要井筒线段来计算MD
|
|
|
bIsAddSuccess = tryAddNewPerforationInHorizontalFracturedWell(scenePos, wellboreVisualLine);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
// 未知或不支持的井类型,可以打印警告
|
|
|
qWarning() << "Unsupported well type for adding perforation.";
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
// 添加成功后,退出添加模式
|
|
|
if(bIsAddSuccess) {
|
|
|
setMode(WellEditMode::None);
|
|
|
m_pView->setAddPerforationMode(false);
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
m_pModel->notifyParameterChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool nmWellEditorController::tryAddNewPerforationInVerticalWell(const QPointF& scenePos)
|
|
|
{
|
|
|
nmDataVerticalWell* pVerticalWell = dynamic_cast<nmDataVerticalWell*>(m_pModel);
|
|
|
|
|
|
if(!pVerticalWell) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// --- 横坐标距离检查 ---
|
|
|
double dWellboreCenterX = pVerticalWell->getX().getValue().toDouble();
|
|
|
const double dClickToleranceX = 5.0; // 允许点击位置距离井筒中心线的最大水平距离
|
|
|
|
|
|
if(qAbs(scenePos.x() - dWellboreCenterX) > dClickToleranceX) {
|
|
|
// 如果点击位置距离井筒中心太远,则不处理
|
|
|
// 控制器可以选择在这里发出一个信号,通知视图“添加失败”或显示提示
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 1. 获取点击位置对应的测量深度 (MD)
|
|
|
// 对于垂直井,场景的Y坐标直接对应MD
|
|
|
double dClickedMd = scenePos.y();
|
|
|
|
|
|
// 2. 检查点击位置是否在井筒的有效MD范围内
|
|
|
double dWellboreTopMd = pVerticalWell->getBottomholeMD().getValue().toDouble();
|
|
|
double dWellboreBottomMd = dWellboreTopMd + pVerticalWell->getWellLength().getValue().toDouble();
|
|
|
|
|
|
if(dClickedMd < dWellboreTopMd || dClickedMd > dWellboreBottomMd) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 3. 计算空白区域长度并检查重叠
|
|
|
double dNearestPerforationAboveMd = dWellboreTopMd;
|
|
|
double dNearestPerforationBelowMd = dWellboreBottomMd;
|
|
|
|
|
|
foreach(nmDataPerforation* pExistingPerforation, pVerticalWell->getPerforations()) {
|
|
|
double dExistingStartMd = pExistingPerforation->getMdStart().getValue().toDouble();
|
|
|
double dExistingEndMd = pExistingPerforation->getMdEnd().getValue().toDouble();
|
|
|
|
|
|
if(dClickedMd >= dExistingStartMd && dClickedMd <= dExistingEndMd) {
|
|
|
return false; // 点击在现有射孔段内部,不允许添加
|
|
|
}
|
|
|
|
|
|
if(dExistingEndMd < dClickedMd) {
|
|
|
dNearestPerforationAboveMd = std::max(dNearestPerforationAboveMd, dExistingEndMd);
|
|
|
}
|
|
|
|
|
|
if(dExistingStartMd > dClickedMd) {
|
|
|
dNearestPerforationBelowMd = std::min(dNearestPerforationBelowMd, dExistingStartMd);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
double dAvailableBlankAreaStartMd = dNearestPerforationAboveMd;
|
|
|
double dAvailableBlankAreaEndMd = dNearestPerforationBelowMd;
|
|
|
|
|
|
if(dAvailableBlankAreaStartMd >= dAvailableBlankAreaEndMd) {
|
|
|
return false; // 没有足够的空白空间
|
|
|
}
|
|
|
|
|
|
// 4. 计算新射孔段的长度和位置
|
|
|
double dMaxAvailableLength = dAvailableBlankAreaEndMd - dAvailableBlankAreaStartMd;
|
|
|
double dActualPerforationLength = dMaxAvailableLength / 3.0; // 长度为可用空白区域的三分之一
|
|
|
|
|
|
double dNewPerforationStartMd = dClickedMd - dActualPerforationLength / 2.0;
|
|
|
double dNewPerforationEndMd = dClickedMd + dActualPerforationLength / 2.0;
|
|
|
|
|
|
// 调整确保新射孔段完全在计算出的空白区域内
|
|
|
if(dNewPerforationStartMd < dAvailableBlankAreaStartMd) {
|
|
|
dNewPerforationStartMd = dAvailableBlankAreaStartMd;
|
|
|
dNewPerforationEndMd = dNewPerforationStartMd + dActualPerforationLength;
|
|
|
}
|
|
|
|
|
|
if(dNewPerforationEndMd > dAvailableBlankAreaEndMd) {
|
|
|
dNewPerforationEndMd = dAvailableBlankAreaEndMd;
|
|
|
dNewPerforationStartMd = dNewPerforationEndMd - dActualPerforationLength;
|
|
|
}
|
|
|
|
|
|
// 再次检查最终计算出的射孔段是否仍然有效且不重叠
|
|
|
// 主要是防止由于浮点数计算误差导致边界问题
|
|
|
if(dNewPerforationStartMd >= dNewPerforationEndMd || dNewPerforationStartMd < dAvailableBlankAreaStartMd || dNewPerforationEndMd > dAvailableBlankAreaEndMd) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 5. 创建并添加新的射孔数据模型 (nmDataPerforation)
|
|
|
// 临时阻塞 Model 的信号,避免在添加过程中重复触发视图刷新
|
|
|
bool bOldSignalsBlocked = pVerticalWell->blockSignals(true);
|
|
|
|
|
|
nmDataPerforation* dNewPerforationData = new nmDataPerforation();
|
|
|
dNewPerforationData->getName().setValue("Perforation#" + QString::number(pVerticalWell->getPerforations().size() + 1));
|
|
|
dNewPerforationData->getMdStart().setValue(dNewPerforationStartMd);
|
|
|
dNewPerforationData->getMdEnd().setValue(dNewPerforationEndMd);
|
|
|
dNewPerforationData->getSkin().setValue(0.0);
|
|
|
|
|
|
// 恢复 Model 的信号
|
|
|
pVerticalWell->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 添加射孔,会触发井数据改变信号,刷新视图
|
|
|
pVerticalWell->addPerforation(dNewPerforationData);
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmWellEditorController::tryAddNewPerforationInVerticalFracturedWell(const QPointF& scenePos, const double& fractureTopMd, const double& fractureBottomMd)
|
|
|
{
|
|
|
nmDataVerticalFracturedWell* pVFWell = dynamic_cast<nmDataVerticalFracturedWell*>(m_pModel);
|
|
|
|
|
|
if(!pVFWell) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// === 检查裂缝MD范围 ===
|
|
|
// 如果没有有效的裂缝MD范围,则无法添加射孔段。
|
|
|
if(fractureTopMd == -1.0 || fractureBottomMd == -1.0 || fractureTopMd >= fractureBottomMd) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// --- 横坐标距离检查 ---
|
|
|
double dWellboreCenterX = pVFWell->getX().getValue().toDouble();
|
|
|
const double dClickToleranceX = 5.0; // 允许点击位置距离井筒中心线的最大水平距离
|
|
|
|
|
|
if(qAbs(scenePos.x() - dWellboreCenterX) > dClickToleranceX) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 1. 获取点击位置对应的测量深度 (MD)
|
|
|
double dClickedMd = scenePos.y();
|
|
|
|
|
|
// 2. 获取井筒的起点和终点MD
|
|
|
double dWellboreTopMd = pVFWell->getBottomholeMD().getValue().toDouble();
|
|
|
double dWellboreBottomMd = dWellboreTopMd + pVFWell->getWellLength().getValue().toDouble();
|
|
|
|
|
|
// 合并约束:初始可用空白区域必须同时在井筒和裂缝的MD范围内
|
|
|
double dAvailableBlankAreaStartMd = std::max(dWellboreTopMd, fractureTopMd);
|
|
|
double dAvailableBlankAreaEndMd = std::min(dWellboreBottomMd, fractureBottomMd);
|
|
|
|
|
|
// 如果计算出的初始可用区域无效,说明没有有效空间来添加射孔
|
|
|
if(dAvailableBlankAreaStartMd >= dAvailableBlankAreaEndMd) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 3. 检查点击位置是否在最终确定的有效MD范围内
|
|
|
if(dClickedMd < dAvailableBlankAreaStartMd || dClickedMd > dAvailableBlankAreaEndMd) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 4. 计算空白区域长度并检查与现有射孔段的重叠
|
|
|
double dNearestPerforationAboveMd = dAvailableBlankAreaStartMd;
|
|
|
double dNearestPerforationBelowMd = dAvailableBlankAreaEndMd;
|
|
|
|
|
|
foreach(nmDataPerforation* pExistingPerforation, pVFWell->getPerforations()) {
|
|
|
double dExistingStartMd = pExistingPerforation->getMdStart().getValue().toDouble();
|
|
|
double dExistingEndMd = pExistingPerforation->getMdEnd().getValue().toDouble();
|
|
|
|
|
|
if(dClickedMd >= dExistingStartMd && dClickedMd <= dExistingEndMd) {
|
|
|
return false; // 点击在现有射孔段内部,不允许添加
|
|
|
}
|
|
|
|
|
|
if(dExistingEndMd < dClickedMd) {
|
|
|
dNearestPerforationAboveMd = std::max(dNearestPerforationAboveMd, dExistingEndMd);
|
|
|
}
|
|
|
|
|
|
if(dExistingStartMd > dClickedMd) {
|
|
|
dNearestPerforationBelowMd = std::min(dNearestPerforationBelowMd, dExistingStartMd);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 再次合并所有约束(井筒、裂缝、现有射孔段)以确定最终可用的空白区域
|
|
|
dAvailableBlankAreaStartMd = std::max(dAvailableBlankAreaStartMd, dNearestPerforationAboveMd);
|
|
|
dAvailableBlankAreaEndMd = std::min(dAvailableBlankAreaEndMd, dNearestPerforationBelowMd);
|
|
|
|
|
|
// 再次检查最终计算出的空白区域是否有效
|
|
|
if(dAvailableBlankAreaStartMd >= dAvailableBlankAreaEndMd) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 5. 计算新射孔段的长度和位置
|
|
|
double dMaxAvailableLength = dAvailableBlankAreaEndMd - dAvailableBlankAreaStartMd;
|
|
|
double dActualPerforationLength = dMaxAvailableLength / 3.0; // 长度为可用空白区域的三分之一
|
|
|
|
|
|
double dNewPerforationStartMd = dClickedMd - dActualPerforationLength / 2.0;
|
|
|
double dNewPerforationEndMd = dClickedMd + dActualPerforationLength / 2.0;
|
|
|
|
|
|
// 调整确保新射孔段完全在计算出的空白区域内
|
|
|
if(dNewPerforationStartMd < dAvailableBlankAreaStartMd) {
|
|
|
dNewPerforationStartMd = dAvailableBlankAreaStartMd;
|
|
|
dNewPerforationEndMd = dNewPerforationStartMd + dActualPerforationLength;
|
|
|
}
|
|
|
|
|
|
if(dNewPerforationEndMd > dAvailableBlankAreaEndMd) {
|
|
|
dNewPerforationEndMd = dAvailableBlankAreaEndMd;
|
|
|
dNewPerforationStartMd = dNewPerforationEndMd - dActualPerforationLength;
|
|
|
}
|
|
|
|
|
|
// 再次检查最终计算出的射孔段是否仍然有效且不重叠
|
|
|
if(dNewPerforationStartMd >= dNewPerforationEndMd ||
|
|
|
dNewPerforationStartMd < dAvailableBlankAreaStartMd || dNewPerforationEndMd > dAvailableBlankAreaEndMd) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 6. 创建并添加新的射孔数据模型 (nmDataPerforation)
|
|
|
nmDataPerforation* dNewPerforationData = new nmDataPerforation();
|
|
|
dNewPerforationData->getName().setValue("Perforation#" + QString::number(pVFWell->getPerforations().size() + 1));
|
|
|
dNewPerforationData->getMdStart().setValue(dNewPerforationStartMd);
|
|
|
dNewPerforationData->getMdEnd().setValue(dNewPerforationEndMd);
|
|
|
dNewPerforationData->getSkin().setValue(0.0);
|
|
|
|
|
|
pVFWell->addPerforation(dNewPerforationData); // 添加到模型
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmWellEditorController::tryAddNewPerforationInHorizontalFracturedWell(const QPointF& scenePos, const QLineF& wellboreVisualLine)
|
|
|
{
|
|
|
nmDataHorizontalFracturedWell *pHFWell = dynamic_cast<nmDataHorizontalFracturedWell*>(m_pModel);
|
|
|
|
|
|
if(!pHFWell || qFuzzyIsNull(wellboreVisualLine.length())) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// --- 1. 检查点击位置是否在井筒周围 ---
|
|
|
const double dClickToleranceVisual = 5.0; // 允许点击位置距离井筒视觉线段的最大垂直距离
|
|
|
|
|
|
QPointF wellboreStartScene = wellboreVisualLine.p1();
|
|
|
QPointF wellboreEndScene = wellboreVisualLine.p2();
|
|
|
|
|
|
// 计算点击点到井筒线段的最近点 (投影点)
|
|
|
QVector2D vecAP = QVector2D(scenePos - wellboreStartScene);
|
|
|
QVector2D vecAB = QVector2D(wellboreEndScene - wellboreStartScene);
|
|
|
|
|
|
double lenSq_AB = vecAB.lengthSquared();
|
|
|
double t = 0.0;
|
|
|
|
|
|
if(!qFuzzyIsNull(lenSq_AB)) {
|
|
|
t = QVector2D::dotProduct(vecAP, vecAB) / lenSq_AB;
|
|
|
}
|
|
|
|
|
|
// 限制 t 在 [0, 1] 之间,确保投影点在线段上
|
|
|
t = qBound(0.0, t, 1.0);
|
|
|
|
|
|
// 计算投影点在井筒上的场景坐标
|
|
|
QPointF projectedPoint = wellboreStartScene + vecAB.toPointF() * t;
|
|
|
// 计算点击点到投影点的距离
|
|
|
double distanceToWellbore = QLineF(scenePos, projectedPoint).length();
|
|
|
|
|
|
if(distanceToWellbore > dClickToleranceVisual) {
|
|
|
return false; // 点击位置离井筒太远
|
|
|
}
|
|
|
|
|
|
// --- 2. 获取点击位置对应的测量深度 (MD) ---
|
|
|
// 从模型数据中获取井筒的MD起点和总长度
|
|
|
double dWellboreStartAbsoluteMd = pHFWell->getPerforations()[0]->getMdStart().getValue().toDouble();
|
|
|
double dTotalWellboreMdLength = pHFWell->getWellLength().getValue().toDouble();
|
|
|
|
|
|
if(qFuzzyIsNull(dTotalWellboreMdLength) || dTotalWellboreMdLength < 0) {
|
|
|
return false; // 井筒MD长度无效,无法映射MD
|
|
|
}
|
|
|
|
|
|
// 计算投影点对应的MD
|
|
|
double dClickedMd = dWellboreStartAbsoluteMd + (t * dTotalWellboreMdLength);
|
|
|
|
|
|
|
|
|
// 3. 检查点击MD是否在井筒的有效MD范围内
|
|
|
double dWellboreTopMd = dWellboreStartAbsoluteMd;
|
|
|
double dWellboreBottomMd = dWellboreStartAbsoluteMd + dTotalWellboreMdLength;
|
|
|
|
|
|
// 确保点击MD在井筒的有效范围内 (使用DBL_EPSILON进行浮点比较容差)
|
|
|
if(dClickedMd < (dWellboreTopMd - DBL_EPSILON) || dClickedMd > (dWellboreBottomMd + DBL_EPSILON)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 4. 计算空白区域长度并检查重叠
|
|
|
double dNearestPerforationAboveMd = dWellboreTopMd; // 上方最近射孔段的结束MD (初始为井筒顶部MD)
|
|
|
double dNearestPerforationBelowMd = dWellboreBottomMd; // 下方最近射孔段的开始MD (初始为井筒底部MD)
|
|
|
|
|
|
foreach(nmDataPerforation* pExistingPerforation, pHFWell->getPerforations()) {
|
|
|
double dExistingStartMd = pExistingPerforation->getMdStart().getValue().toDouble();
|
|
|
double dExistingEndMd = pExistingPerforation->getMdEnd().getValue().toDouble();
|
|
|
|
|
|
// 如果点击MD在现有射孔段内部,则不允许添加(点击在射孔段上无效)
|
|
|
if(dClickedMd >= (dExistingStartMd - DBL_EPSILON) && dClickedMd <= (dExistingEndMd + DBL_EPSILON)) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 更新上方最近的射孔段的结束MD
|
|
|
if(dExistingEndMd < dClickedMd) {
|
|
|
dNearestPerforationAboveMd = std::max(dNearestPerforationAboveMd, dExistingEndMd);
|
|
|
}
|
|
|
|
|
|
// 更新下方最近的射孔段的开始MD
|
|
|
if(dExistingStartMd > dClickedMd) {
|
|
|
dNearestPerforationBelowMd = std::min(dNearestPerforationBelowMd, dExistingStartMd);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 确认最终可用的空白区域范围
|
|
|
double dAvailableBlankAreaStartMd = dNearestPerforationAboveMd;
|
|
|
double dAvailableBlankAreaEndMd = dNearestPerforationBelowMd;
|
|
|
|
|
|
// 如果计算出的空白区域无效(起点大于等于终点),说明没有足够空间
|
|
|
if(dAvailableBlankAreaStartMd >= dAvailableBlankAreaEndMd - DBL_EPSILON) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 5. 计算新射孔段的长度和位置
|
|
|
double dMaxAvailableLength = dAvailableBlankAreaEndMd - dAvailableBlankAreaStartMd;
|
|
|
double dActualPerforationLength = dMaxAvailableLength / 3.0;
|
|
|
|
|
|
// 确保新射孔段至少有一个最小长度,避免创建零长度或负长度的射孔
|
|
|
// const double dMinPerforationLength = 0.5;
|
|
|
// if(dActualPerforationLength < dMinPerforationLength) {
|
|
|
// return false;
|
|
|
// }
|
|
|
// 如果计算出的长度大于最大可用长度,则限制
|
|
|
dActualPerforationLength = std::min(dActualPerforationLength, dMaxAvailableLength);
|
|
|
|
|
|
// 计算新射孔段的起点和终点MD,以点击位置为中心
|
|
|
double dNewPerforationStartMd = dClickedMd - dActualPerforationLength / 2.0;
|
|
|
double dNewPerforationEndMd = dClickedMd + dActualPerforationLength / 2.0;
|
|
|
|
|
|
// 调整确保新射孔段完全在计算出的空白区域内
|
|
|
// 情况1: 如果新射孔段的起点超出了空白区域的起始点(向上越界)
|
|
|
if(dNewPerforationStartMd < dAvailableBlankAreaStartMd - DBL_EPSILON) {
|
|
|
dNewPerforationStartMd = dAvailableBlankAreaStartMd;
|
|
|
dNewPerforationEndMd = dNewPerforationStartMd + dActualPerforationLength;
|
|
|
}
|
|
|
|
|
|
// 情况2: 如果新射孔段的终点超出了空白区域的结束点(向下越界)
|
|
|
if(dNewPerforationEndMd > dAvailableBlankAreaEndMd + DBL_EPSILON) {
|
|
|
dNewPerforationEndMd = dAvailableBlankAreaEndMd;
|
|
|
dNewPerforationStartMd = dNewPerforationEndMd - dActualPerforationLength;
|
|
|
}
|
|
|
|
|
|
// 再次检查最终计算出的射孔段是否仍然有效且不重叠
|
|
|
// 主要是防止由于浮点数计算误差导致边界问题
|
|
|
// 确保长度为正,且完全在空白区域内
|
|
|
if(dNewPerforationStartMd >= dNewPerforationEndMd - DBL_EPSILON ||
|
|
|
dNewPerforationStartMd < dAvailableBlankAreaStartMd - DBL_EPSILON ||
|
|
|
dNewPerforationEndMd > dAvailableBlankAreaEndMd + DBL_EPSILON) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 6. 创建并添加新的射孔数据模型
|
|
|
nmDataPerforation* dNewPerforationData = new nmDataPerforation();
|
|
|
dNewPerforationData->getName().setValue("Perforation#" + QString::number(pHFWell->getPerforations().size() + 1));
|
|
|
dNewPerforationData->getMdStart().setValue(dNewPerforationStartMd);
|
|
|
dNewPerforationData->getMdEnd().setValue(dNewPerforationEndMd);
|
|
|
dNewPerforationData->getSkin().setValue(0.0);
|
|
|
|
|
|
// 将新的射孔数据添加到井筒的数据模型中
|
|
|
pHFWell->addPerforation(dNewPerforationData);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::slotDeletePerforation(nmDataPerforation* pPerforationData)
|
|
|
{
|
|
|
// 验证数据有效性
|
|
|
if(!m_pModel || !pPerforationData || m_eCurrentMode != WellEditMode::DeletePerforation) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// <-- 将所有删除的业务逻辑集中在这里
|
|
|
// 井数据类型判断和规则检查
|
|
|
nmDataHorizontalFracturedWell* pHFWell = dynamic_cast<nmDataHorizontalFracturedWell*>(m_pModel);
|
|
|
|
|
|
// 如果是水平压裂井类型
|
|
|
if(pHFWell) {
|
|
|
// 获取所有射孔段,并检查待删除的射孔段是否为第一条
|
|
|
QVector<nmDataPerforation*>& vecPerforations = pHFWell->getPerforations();
|
|
|
|
|
|
// 检查射孔列表是否非空,并且待删除的射孔是第一条
|
|
|
if(!vecPerforations.isEmpty() && vecPerforations.first() == pPerforationData) {
|
|
|
// 是第一条射孔,不允许删除
|
|
|
// 退出删除模式
|
|
|
setMode(WellEditMode::None);
|
|
|
m_pView->setDeletePerforationMode(false);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果通过了所有检查,执行实际的删除操作
|
|
|
QVector<nmDataPerforation*>& vecPerforations = m_pModel->getPerforations();
|
|
|
|
|
|
// 查找并删除
|
|
|
for(int i = 0; i < vecPerforations.size(); ++i) {
|
|
|
if(vecPerforations.at(i) == pPerforationData) {
|
|
|
m_pModel->removePerforation(i);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 退出删除模式
|
|
|
setMode(WellEditMode::None);
|
|
|
m_pView->setDeletePerforationMode(false);
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
m_pModel->notifyParameterChanged();
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::snapPerforationToWellbore()
|
|
|
{
|
|
|
// 检查井数据有效性
|
|
|
if(!m_pModel) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 已经是吸附的射孔段了,则直接返回
|
|
|
if(m_pModel->isBasePerforation()) {
|
|
|
// 退出吸附模式
|
|
|
setMode(WellEditMode::None);
|
|
|
m_pView->setSnapPerforationMode(false);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 临时阻塞 m_pModel 的信号
|
|
|
bool bOldSignalsBlocked = m_pModel->blockSignals(true);
|
|
|
|
|
|
// 删除所有的射孔段数据
|
|
|
m_pModel->clearPerforations();
|
|
|
|
|
|
// 创建并添加新的射孔数据模型
|
|
|
nmDataPerforation* dNewPerforationData = new nmDataPerforation();
|
|
|
dNewPerforationData->getMdStart().setValue(m_pModel->getBottomholeMD().getValue().toDouble());
|
|
|
dNewPerforationData->getMdEnd().setValue(m_pModel->getBottomholeMD().getValue().toDouble() + m_pModel->getWellLength().getValue().toDouble());
|
|
|
dNewPerforationData->getSkin().setValue(0.0);
|
|
|
|
|
|
// 恢复原始阻塞状态
|
|
|
m_pModel->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 将新的射孔数据添加到井筒的数据模型中
|
|
|
m_pModel->addPerforation(dNewPerforationData);
|
|
|
|
|
|
// 退出吸附模式
|
|
|
setMode(WellEditMode::None);
|
|
|
m_pView->setSnapPerforationMode(false);
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
m_pModel->notifyParameterChanged();
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::slotFractureGeometryChanged(double newHalfLength, double newAngle)
|
|
|
{
|
|
|
// 在这里,控制器接收到来自视图的信号
|
|
|
if(!m_pModel) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 临时阻塞 m_pModel 的信号
|
|
|
bool bOldSignalsBlocked = m_pModel->blockSignals(true);
|
|
|
|
|
|
// 检查井数据类型
|
|
|
nmDataVerticalFracturedWell* pVFWell = dynamic_cast<nmDataVerticalFracturedWell*>(m_pModel);
|
|
|
|
|
|
if(pVFWell) {
|
|
|
// 制器负责调用模型的方法来修改数据
|
|
|
pVFWell->getFractureHalfLength().setValue(newHalfLength);
|
|
|
pVFWell->getFractureAngle().setValue(newAngle);
|
|
|
}
|
|
|
|
|
|
// 恢复原始阻塞状态
|
|
|
m_pModel->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 刷新截面图
|
|
|
m_pView->updataCrossSectionView();
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
if (!bOldSignalsBlocked) {
|
|
|
m_pModel->notifyParameterChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 原有的槽函数可以简化,只负责更新裂缝自身的数据
|
|
|
void nmWellEditorController::slotHandleFractureRectUpdate(qreal newTopMD, qreal newBottomMD)
|
|
|
{
|
|
|
if(!m_pModel || m_pModel->getWellType() != NM_WELL_MODEL::Vertical_Fractured_Well) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 1. 验证和修正裂缝数据
|
|
|
qreal dFinalTopMD = newTopMD;
|
|
|
qreal dFinalBottomMD = newBottomMD;
|
|
|
|
|
|
// 获取储层位置
|
|
|
qreal dMinReservoirTop = nmDataAnalyzeManager::getCurrentInstance()->getMinLayerTop();
|
|
|
qreal dMaxReservoirBottom = nmDataAnalyzeManager::getCurrentInstance()->getMaxLayerBottom();
|
|
|
|
|
|
// 应用储层边界限制
|
|
|
if(dFinalTopMD < dMinReservoirTop) {
|
|
|
dFinalTopMD = dMinReservoirTop;
|
|
|
}
|
|
|
|
|
|
if(dFinalBottomMD > dMaxReservoirBottom) {
|
|
|
dFinalBottomMD = dMaxReservoirBottom;
|
|
|
}
|
|
|
|
|
|
// 2. 更新数据模型中的裂缝数据
|
|
|
nmDataVerticalFracturedWell *pVFWell = dynamic_cast<nmDataVerticalFracturedWell*>(m_pModel);
|
|
|
|
|
|
if(pVFWell) {
|
|
|
// 临时阻塞 pVFWell 的信号
|
|
|
bool bOldSignalsBlocked = pVFWell->blockSignals(true);
|
|
|
pVFWell->getFractureHeight().setValue(dFinalBottomMD - dFinalTopMD);
|
|
|
qreal newMidPointMD = (dFinalTopMD + dFinalBottomMD) / 2.0;
|
|
|
pVFWell->getFractureMidPointHeight().setValue(dMaxReservoirBottom - newMidPointMD);
|
|
|
// 恢复原始阻塞状态
|
|
|
pVFWell->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
if (!bOldSignalsBlocked) {
|
|
|
pVFWell->notifyParameterChanged();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::slotUpdatePerforations(const QMap<nmDataPerforation*, QPair<double, double>>& updatedPerforations)
|
|
|
{
|
|
|
if(!m_pModel || m_pModel->getWellType() != NM_WELL_MODEL::Vertical_Fractured_Well) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmDataVerticalFracturedWell *pVFWell = dynamic_cast<nmDataVerticalFracturedWell*>(m_pModel);
|
|
|
|
|
|
if(!pVFWell) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 临时阻塞 pVFWell 的信号,防止多次触发更新
|
|
|
bool bOldSignalsBlocked = pVFWell->blockSignals(true);
|
|
|
|
|
|
// 从你的数据模型中获取射孔列表
|
|
|
QVector<nmDataPerforation*>& perforations = pVFWell->getPerforations();
|
|
|
|
|
|
// 遍历所有射孔,更新或删除
|
|
|
for(int i = perforations.size() - 1; i >= 0; --i) {
|
|
|
nmDataPerforation* pPerf = perforations[i];
|
|
|
|
|
|
if(updatedPerforations.contains(pPerf)) {
|
|
|
// 如果映射中包含此射孔,则更新其MD值
|
|
|
QPair<double, double> newMdRange = updatedPerforations.value(pPerf);
|
|
|
pPerf->getMdStart().setValue(newMdRange.first);
|
|
|
pPerf->getMdEnd().setValue(newMdRange.second);
|
|
|
} else {
|
|
|
// 如果映射中不包含此射孔,说明它在视图中被删除了,
|
|
|
// 在数据模型中也应该被删除
|
|
|
pVFWell->removePerforation(i);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 恢复信号
|
|
|
pVFWell->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
if (!bOldSignalsBlocked) {
|
|
|
pVFWell->notifyParameterChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::slotUpdateFractureHalfLength(double newHalfLength)
|
|
|
{
|
|
|
nmDataHorizontalFracturedWell *pHFWell = dynamic_cast<nmDataHorizontalFracturedWell*>(m_pModel);
|
|
|
|
|
|
if(!pHFWell) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 临时阻塞 pVFWell 的信号,防止多次触发更新
|
|
|
bool bOldSignalsBlocked = pHFWell->blockSignals(true);
|
|
|
// 更新裂缝半长
|
|
|
pHFWell->getFractureHalfLength().setValue(newHalfLength);
|
|
|
// 恢复信号
|
|
|
pHFWell->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
if (!bOldSignalsBlocked) {
|
|
|
pHFWell->notifyParameterChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::slotUpdateWellborePos(QPointF finalWellboreEndPos)
|
|
|
{
|
|
|
nmDataHorizontalFracturedWell *pHFWell = dynamic_cast<nmDataHorizontalFracturedWell*>(m_pModel);
|
|
|
|
|
|
if(!pHFWell) return;
|
|
|
|
|
|
// 临时阻塞 pVFWell 的信号,防止多次触发更新
|
|
|
bool bOldSignalsBlocked = pHFWell->blockSignals(true);
|
|
|
|
|
|
// 1. 获取井筒起点
|
|
|
QPointF wellboreStartScene(pHFWell->getX().getValue().toDouble(), pHFWell->getY().getValue().toDouble());
|
|
|
|
|
|
// 2. 重新计算井身长度和角度
|
|
|
double newWellLength = QLineF(wellboreStartScene, finalWellboreEndPos).length();
|
|
|
double deltaX = finalWellboreEndPos.x() - wellboreStartScene.x();
|
|
|
double deltaY = finalWellboreEndPos.y() - wellboreStartScene.y();
|
|
|
double newDarinAngleRad = atan2(deltaY, deltaX);
|
|
|
double newDarinAngleDeg = newDarinAngleRad * 180.0 / M_PI;
|
|
|
|
|
|
// 3. 更新井筒长度和倾角的数据模型
|
|
|
pHFWell->getWellLength().setValue(newWellLength);
|
|
|
pHFWell->getDrainAngle().setValue(newDarinAngleDeg);
|
|
|
|
|
|
// 4. 更新射孔位置
|
|
|
pHFWell->slotWellLengthChanged();
|
|
|
|
|
|
// 恢复信号
|
|
|
pHFWell->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 刷新截面图
|
|
|
m_pView->updataCrossSectionView();
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
if (!bOldSignalsBlocked) {
|
|
|
pHFWell->notifyParameterChanged();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void nmWellEditorController::slotHFWellFractureGeometryChanged(double newHeight, double newMidPointY)
|
|
|
{
|
|
|
nmDataHorizontalFracturedWell *pHFWell = dynamic_cast<nmDataHorizontalFracturedWell*>(m_pModel);
|
|
|
|
|
|
if(!pHFWell) return;
|
|
|
|
|
|
// 临时阻塞 pVFWell 的信号,防止多次触发更新
|
|
|
bool bOldSignalsBlocked = pHFWell->blockSignals(true);
|
|
|
|
|
|
// 在控制器层执行业务逻辑,更新数据模型
|
|
|
pHFWell->getFractureHeight().setValue(newHeight);
|
|
|
|
|
|
qreal maxReservoirDepth = nmDataAnalyzeManager::getCurrentInstance()->getMaxLayerBottom();
|
|
|
double newMidPointHeightFromBottom = maxReservoirDepth - newMidPointY;
|
|
|
pHFWell->getFractureMidPointHeight().setValue(newMidPointHeightFromBottom);
|
|
|
|
|
|
// 恢复信号
|
|
|
pHFWell->blockSignals(bOldSignalsBlocked);
|
|
|
|
|
|
// 通知发送信号,更新左侧参数视图
|
|
|
if (!bOldSignalsBlocked) {
|
|
|
pHFWell->notifyParameterChanged();
|
|
|
}
|
|
|
}
|