You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWellEditorController.cpp

869 lines
32 KiB
C++

#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();
}
}