#include "nmWxWellboreTrajectoryDisplay.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nmDataAnalyzeManager.h" #include "nmDataPerforation.h" nmWellborePointGraphicsItem::nmWellborePointGraphicsItem(QGraphicsItem *parent) : QGraphicsEllipseItem(parent), m_bIsDragging(false) { setPen(QPen(Qt::black, 0.5f)); setBrush(QBrush(Qt::darkGreen)); } void nmWellborePointGraphicsItem::setStyle(const QPen& pen, const QBrush& brush) { setPen(pen); setBrush(brush); } void nmWellborePointGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { // 如果是左键按下,并且允许移动 if(event->button() == Qt::LeftButton && (flags() & QGraphicsItem::ItemIsMovable)) { m_bIsDragging = true; // 记录鼠标按下时的场景坐标 m_dragStartPos = event->scenePos(); // 记录图元拖动前的原始位置(左上角) m_originalPos = pos(); event->accept(); // 接受事件,表示已处理 } QGraphicsEllipseItem::mousePressEvent(event); } void nmWellborePointGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if(m_bIsDragging && (event->buttons() & Qt::LeftButton)) { // 计算偏移量 QPointF delta = event->scenePos() - m_dragStartPos; setPos(m_originalPos + delta); emit sigPositionChanging(sceneBoundingRect().center()); event->accept(); } QGraphicsEllipseItem::mouseMoveEvent(event); } void nmWellborePointGraphicsItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if(m_bIsDragging) { m_bIsDragging = false; emit sigDragFinished(sceneBoundingRect().center()); event->accept(); } QGraphicsEllipseItem::mouseReleaseEvent(event); } nmWellboreLineGraphicsItem::nmWellboreLineGraphicsItem(QGraphicsItem *parent) : QGraphicsLineItem(parent) { QPen wellborePen(Qt::blue, 2); wellborePen.setCosmetic(true); setPen(wellborePen); } void nmWellboreLineGraphicsItem::setStyle(const QPen& pen) { QPen cosmetic_pen = pen; cosmetic_pen.setCosmetic(true); setPen(cosmetic_pen); } nmPerforationHandleItem::nmPerforationHandleItem(PerforationHandleType type, QGraphicsItem *parent) : m_handleType(type), QGraphicsRectItem(parent) { setZValue(5); } #include #include #include #include #include void nmPerforationHandleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // 获取视图缩放比例 if (!scene() || scene()->views().isEmpty()) { return; } QGraphicsView* view = scene()->views().first(); if (!view) { return; } const QTransform viewTransform = view->transform(); const qreal scaleY = qAbs(viewTransform.m22()); if (scaleY < 1e-6) { return; } // 定义手柄和边框的固定像素大小 const qreal fixedHandleSizeInPixels = 13.0; const qreal fixedBorderWidthInPixels = 2.0; // 定义边框的像素宽度 // 根据缩放比例计算动态大小 const qreal dynamicHandleSize = fixedHandleSizeInPixels / scaleY; const qreal dynamicBorderWidth = fixedBorderWidthInPixels / scaleY; // 计算动态边框宽度 // 定义手柄的矩形区域 QRectF handleRect(-dynamicHandleSize / 2, -dynamicHandleSize / 2, dynamicHandleSize, dynamicHandleSize); // 创建一个新的 QPen 用于绘制边框 QPen pen(Qt::black); // 颜色为黑色 pen.setWidthF(dynamicBorderWidth); // 设置动态计算出的宽度 painter->setPen(pen); painter->setBrush(Qt::red); // 填充颜色保持为红色 painter->drawRect(handleRect); // 绘制带边框的矩形 } QRectF nmPerforationHandleItem::boundingRect() const { // 获取视图缩放比例 if (!scene() || scene()->views().isEmpty()) { return QRectF(); } QGraphicsView* view = scene()->views().first(); if (!view) { return QRectF(); } const QTransform viewTransform = view->transform(); const qreal scaleY = qAbs(viewTransform.m22()); if (scaleY < 1e-6) { return QRectF(); } // 使用与paint方法相同的动态大小计算 const qreal fixedHandleSizeInPixels = 13.0; const qreal dynamicHandleSize = fixedHandleSizeInPixels / scaleY; // 返回动态计算的矩形,与paint方法绘制的区域一致 return QRectF(-dynamicHandleSize / 2, -dynamicHandleSize / 2, dynamicHandleSize, dynamicHandleSize); } nmPerforationGraphicsItem::nmPerforationGraphicsItem(nmDataPerforation *pPerData, NM_WELL_MODEL wellType, double wellboreStartAbsoluteMd, double totalWellboreMdLength, QGraphicsItem *parent) : m_pPerforationData(pPerData), // 初始化关联的射孔数据 m_eWellType(wellType), // 初始化井筒类型 m_wellboreStartAbsoluteMd(wellboreStartAbsoluteMd), // 初始化井筒起始MD m_totalWellboreMdLength(totalWellboreMdLength), // 初始化井筒总长度 QGraphicsRectItem(parent), // 调用基类 QGraphicsRectItem 的构造函数 m_bEditable(false), // 默认不可编辑 m_eCurrentInteractionMode(PerforationInteractionMode::Normal), // 默认正常交互模式 m_dPerWidth(10.0), // 射孔默认宽度 m_handlePen(Qt::black, 1), // 手柄边框笔触 m_handleBrush(Qt::red), // 手柄填充颜色 m_handleSize(0.0), // 手柄尺寸(稍后根据井筒类型设置) m_pStartHandle(nullptr), // 起点手柄指针初始为空 m_pEndHandle(nullptr), // 终点手柄指针初始为空 m_isDragging(false), // 初始状态,未在拖动 m_tempDragMdStart(0.0), // 拖动过程中使用的临时起点MD m_tempDragMdEnd(0.0) // 拖动过程中使用的临时终点MD { m_initialMdStart = 0.0; m_initialMdEnd = 0.0; m_dCroppedMdStart = 0.0; m_dCroppedMdEnd = 0.0; // 根据井筒类型设置射孔宽度和手柄大小 if(m_eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well) { m_dPerWidth = 2.0; m_handleSize = 10.0; } else { m_handleSize = m_dPerWidth + 2; // 垂直井筒手柄略大于射孔宽度 } QPen perforationPen(Qt::red, 1); perforationPen.setCosmetic(true); setPen(perforationPen);// 射孔本体边框 setBrush(QBrush(Qt::red));// 射孔本体填充颜色 setFlags(ItemIsSelectable);// 允许射孔本体被选中 setCacheMode(DeviceCoordinateCache);// 缓存绘图,提高性能 m_handlePen.setCosmetic(true); // 创建起点和终点手柄,并设置父项为当前射孔图形项的父类 m_pStartHandle = new nmPerforationHandleItem(PerforationHandleType::StartHandle, parent); m_pEndHandle = new nmPerforationHandleItem(PerforationHandleType::EndHandle, parent); // 设置手柄的样式 m_pStartHandle->setPen(m_handlePen); m_pStartHandle->setBrush(m_handleBrush); m_pEndHandle->setPen(m_handlePen); m_pEndHandle->setBrush(m_handleBrush); // 默认隐藏手柄,只有在编辑模式下才显示 m_pStartHandle->setVisible(false); m_pEndHandle->setVisible(false); // 在创建时,将裁剪范围初始化为原始 MD 范围 m_dCroppedMdStart = m_pPerforationData->getMdStart().getValue().toDouble(); m_dCroppedMdEnd = m_pPerforationData->getMdEnd().getValue().toDouble(); } nmPerforationGraphicsItem::~nmPerforationGraphicsItem() { // 手动删除手柄 if(m_pStartHandle) { delete m_pStartHandle; m_pStartHandle = nullptr; } if(m_pEndHandle) { delete m_pEndHandle; m_pEndHandle = nullptr; } } void nmPerforationGraphicsItem::setEditMode(bool enable) { m_bEditable = enable; if(m_pStartHandle && m_pEndHandle) { m_pStartHandle->setVisible(enable); // 根据编辑模式显示或隐藏手柄 m_pEndHandle->setVisible(enable); } update(); // 请求重绘以反映手柄的显示/隐藏状态 } void nmPerforationGraphicsItem::updateGraphics() { prepareGeometryChange(); // 通知场景图形项的几何形状即将改变,以便场景更新包围盒和重绘 // 当 Model 变化时,清空拖动标志,确保视图不再使用临时值,而是使用 Model 的真实数据进行渲染 m_isDragging = false; // 从模型中读取最新的原始 MD 值 double mdStart = m_pPerforationData->getMdStart().getValue().toDouble(); double mdEnd = m_pPerforationData->getMdEnd().getValue().toDouble(); // 更新裁剪范围为原始 MD,因为这是最终状态 m_dCroppedMdStart = mdStart; m_dCroppedMdEnd = mdEnd; // 调用渲染函数进行渲染 updatePerforationAndHandleGeometry(m_dCroppedMdStart, m_dCroppedMdEnd); } nmDataPerforation* nmPerforationGraphicsItem::getPerforationData() { return m_pPerforationData; } void nmPerforationGraphicsItem::updateWellboreVisuals(const QPointF& wellboreStartScene, const QPointF& wellboreEndScene, double wellboreStartAbsoluteMd, double totalWellboreMdLength) { m_wellboreStartScenePoint = wellboreStartScene; m_wellboreEndScenePoint = wellboreEndScene; m_wellboreStartAbsoluteMd = wellboreStartAbsoluteMd; m_totalWellboreMdLength = totalWellboreMdLength; m_wellboreVisualLine.setPoints(wellboreStartScene, wellboreEndScene); // 如果井筒长度为0,避免后续计算中出现除零 if(qFuzzyIsNull(m_totalWellboreMdLength)) { m_totalWellboreMdLength = 1.0; // 赋一个非零小值,避免除零,但在实际应用中需要更严谨的错误处理 } updateGraphics(); // 更新图形项的几何形状 } void nmPerforationGraphicsItem::updateGraphicsForPreview(qreal fractureTopMD, qreal fractureBottomMD) { prepareGeometryChange(); // 射孔的原始 MD 值。 // mdStart 是较小的值(更靠近井筒顶部) // mdEnd 是较大的值(更靠近井筒底部) double mdStart = m_pPerforationData->getMdStart().getValue().toDouble(); //double mdEnd = m_pPerforationData->getMdEnd().getValue().toDouble(); // 之前裁剪后的 MD 值。 double currentCroppedStart = m_dCroppedMdStart; double currentCroppedEnd = m_dCroppedMdEnd; // 裂缝当前位置的新 MD 范围。 double newFractureTop = fractureTopMD; double newFractureBottom = fractureBottomMD; // --- 核心逻辑:寻找范围的交集 --- // 新的裁剪起始 MD 是射孔当前起始 MD 和裂缝新顶部 MD 中的较大者。 // 这正确处理了从顶部进行的裁剪。 double newCroppedStart = qMax(currentCroppedStart, newFractureTop); // 新的裁剪结束 MD 是射孔当前结束 MD 和裂缝新底部 MD 中的较小者。 // 这正确处理了从底部进行的裁剪。 double newCroppedEnd = qMin(currentCroppedEnd, newFractureBottom); // 如果新的裁剪范围是有效的(起始 < 结束),我们才进行更新。 if(newCroppedStart < newCroppedEnd) { m_dCroppedMdStart = newCroppedStart; m_dCroppedMdEnd = newCroppedEnd; } else { // 如果新范围无效(例如,完全超出裂缝范围), // 我们可以将裁剪范围设置为一个无效状态,以便图元被隐藏。 m_dCroppedMdStart = mdStart; m_dCroppedMdEnd = mdStart; } // 调用渲染函数,并传入更新后的裁剪 MD 值。 updatePerforationAndHandleGeometry(m_dCroppedMdStart, m_dCroppedMdEnd); } void nmPerforationGraphicsItem::updatePerforationAndHandleGeometry(double mdStartToRender, double mdEndToRender) { // 如果没有关联数据,则不进行更新 if(!m_pPerforationData) { return; } // 计算射孔本体在场景中的起始和结束点 QPointF sceneStartPoint = getScenePointFromMd(mdStartToRender); // 计算起点MD对应的场景坐标 QPointF sceneEndPoint = getScenePointFromMd(mdEndToRender); // 计算终点MD对应的场景坐标 // 计算射孔的视觉长度(像素) double perforationVisualLength = QLineF(sceneStartPoint, sceneEndPoint).length(); // 计算井筒的方向向量和角度 (用于射孔本体和手柄的旋转) QVector2D wellboreVector = QVector2D(m_wellboreVisualLine.p2() - m_wellboreVisualLine.p1()); qreal wellboreAngleRad = qAtan2(wellboreVector.y(), wellboreVector.x()); qreal wellboreAngleDegrees = wellboreAngleRad * 180.0 / M_PI; // 计算射孔中心点对应的场景坐标 QPointF scenePerforationCenter = getScenePointFromMd((mdStartToRender + mdEndToRender) / 2.0); // *** 更新射孔本体的几何形状 *** // 设置射孔图形项的位置和旋转 // 注意:这里的 setPos 和 setRotation 是针对射孔本体 (this) 的 setPos(scenePerforationCenter); // 设置图形项的中心点到射孔中心 setTransformOriginPoint(0, 0); // 旋转的原点设为图形项的中心 setRotation(wellboreAngleDegrees); // 设置旋转角度,使射孔与井筒对齐 // 设置射孔本体的矩形区域(局部坐标系) // 这个矩形是相对于 setPos 设置的中心点的 QRectF localPerforationRect(-perforationVisualLength / 2.0, // X 轴起点 -m_dPerWidth / 2.0, // Y 轴起点 perforationVisualLength, // 宽度 m_dPerWidth); // 高度 setRect(localPerforationRect); // *** 更新手柄的几何形状 *** // 由于手柄和射孔本体是兄弟项(拥有相同的父项), // 它们的位置需要直接在共同父项的坐标系中(通常是场景坐标系)设置。 if(m_pStartHandle && m_pEndHandle) { qreal halfHandleSize = m_handleSize / 2.0; // 手柄的矩形是相对于其自身的 pos() 的。 // 所以,先设置手柄自身的局部矩形(通常以其自身中心为 (0,0)), // 然后通过 setPos 将手柄移动到场景中的正确位置。 QRectF handleLocalRect(-halfHandleSize, -halfHandleSize, m_handleSize, m_handleSize); m_pStartHandle->setRect(handleLocalRect); m_pEndHandle->setRect(handleLocalRect); // 关键:直接将手柄的 pos() 设置为场景坐标 // getScenePointFromMd 返回的就是场景坐标,直接用于 setPos() m_pStartHandle->setPos(sceneStartPoint); m_pEndHandle->setPos(sceneEndPoint); // 如果手柄也需要与井筒对齐旋转,则它们也需要设置旋转角度 m_pStartHandle->setTransformOriginPoint(0, 0); // 相对自身中心旋转 m_pEndHandle->setTransformOriginPoint(0, 0); m_pStartHandle->setRotation(wellboreAngleDegrees); m_pEndHandle->setRotation(wellboreAngleDegrees); } } QPointF nmPerforationGraphicsItem::getScenePointFromMd(double md) const { // 防止除零或负长度 if(qFuzzyIsNull(m_totalWellboreMdLength) || m_totalWellboreMdLength < 0) { return m_wellboreStartScenePoint; } // 计算相对MD(从井筒起始MD开始算) double relativeMd = md - m_wellboreStartAbsoluteMd; // 计算井筒视觉长度 double wellboreVisualLength = QLineF(m_wellboreStartScenePoint, m_wellboreEndScenePoint).length(); if(qFuzzyIsNull(wellboreVisualLength)) { return m_wellboreStartScenePoint; } // 计算MD到像素的缩放比例 double mdToVisualScale = wellboreVisualLength / m_totalWellboreMdLength; // 计算沿井筒方向的视觉距离 double distanceAlongWellbore = relativeMd * mdToVisualScale; // 计算井筒方向的单位向量 QVector2D wellboreDirection = QVector2D(m_wellboreEndScenePoint - m_wellboreStartScenePoint); if(qFuzzyIsNull(wellboreDirection.length())) { return m_wellboreStartScenePoint; } wellboreDirection.normalize(); // 归一化为单位向量 // 返回根据距离和方向计算出的场景点 return m_wellboreStartScenePoint + (wellboreDirection * distanceAlongWellbore).toPointF(); } double nmPerforationGraphicsItem::getMdFromScenePointOnWellbore(const QPointF& scenePoint) const { // 直井筒的简化计算 if(m_eWellType == NM_WELL_MODEL::Vertical_Well || m_eWellType == NM_WELL_MODEL::Vertical_Fractured_Well) { double yOffset = scenePoint.y() - m_wellboreStartScenePoint.y(); double wellboreVisualLength = QLineF(m_wellboreStartScenePoint, m_wellboreEndScenePoint).length(); if(qFuzzyIsNull(wellboreVisualLength)) { return m_wellboreStartAbsoluteMd; } double mdScale = m_totalWellboreMdLength / wellboreVisualLength; return m_wellboreStartAbsoluteMd + yOffset * mdScale; } // 针对其他井筒类型,使用投影计算 QPointF p1 = m_wellboreStartScenePoint; QPointF p2 = m_wellboreEndScenePoint; QLineF wellboreLine(p1, p2); if(qFuzzyIsNull(wellboreLine.length()) || qFuzzyIsNull(m_totalWellboreMdLength) || m_totalWellboreMdLength <= 0) { return m_wellboreStartAbsoluteMd; } // 计算点在井筒线上的投影比例 QVector2D vecAP = QVector2D(scenePoint - p1); QVector2D vecAB = QVector2D(p2 - p1); double dotProduct_AP_AB = QVector2D::dotProduct(vecAP, vecAB); double lenSq_AB = vecAB.lengthSquared(); double t = 0.0; if(!qFuzzyIsNull(lenSq_AB)) { t = dotProduct_AP_AB / lenSq_AB; } t = qBound(0.0, t, 1.0); // 将比例限制在 0.0 到 1.0 之间 // 计算投影点沿井筒的距离 double distanceAlongVisualWellbore = wellboreLine.length() * t; double wellboreVisualLength = wellboreLine.length(); double mdToVisualScale = 0.0; if(!qFuzzyIsNull(m_totalWellboreMdLength)) { mdToVisualScale = wellboreVisualLength / m_totalWellboreMdLength; } if(qFuzzyIsNull(mdToVisualScale)) { return m_wellboreStartAbsoluteMd; } // 将视觉距离转换为MD double relativeMd = distanceAlongVisualWellbore / mdToVisualScale; return m_wellboreStartAbsoluteMd + relativeMd; } double nmPerforationGraphicsItem::getCroppedMdStart() { return m_dCroppedMdStart; } double nmPerforationGraphicsItem::getCroppedMdEnd() { return m_dCroppedMdEnd; } void nmPerforationGraphicsItem::updateVisualsForWellboreDrag(const QPointF& wellboreStartScene, const QPointF& wellboreEndScene, double wellboreStartAbsoluteMd, double originalWellboreLength, double newWellboreLength) { // 如果旧井筒MD长度无效,则直接返回 if(qFuzzyIsNull(originalWellboreLength) || originalWellboreLength <= 0) { return; } // 临时更新井筒的视觉信息,确保 getScenePointFromMd 函数能够使用最新的几何信息 m_wellboreStartScenePoint = wellboreStartScene; m_wellboreEndScenePoint = wellboreEndScene; m_wellboreVisualLine.setPoints(wellboreStartScene, wellboreEndScene); // 从数据模型中获取原始MD值(这些是拖动开始时的旧值) double originalMdStart = m_pPerforationData->getMdStart().getValue().toDouble(); double originalMdEnd = m_pPerforationData->getMdEnd().getValue().toDouble(); // 关键修正1: 在预览模式下,我们将井筒的总MD长度临时设置为新的井筒像素长度。 // 这样,MD值和像素长度就有了1:1的比例关系,确保了 MD 到像素转换的正确性。 m_totalWellboreMdLength = newWellboreLength; // 关键修正2: 基于原始MD和旧MD长度,计算射孔的相对比例 double relativeStart = (originalMdStart - wellboreStartAbsoluteMd) / originalWellboreLength; double relativeEnd = (originalMdEnd - wellboreStartAbsoluteMd) / originalWellboreLength; // 关键修正3: 根据新的虚拟MD长度(即newWellboreLength),按比例计算出新的MD值 // 这些MD值是临时的,只用于渲染,并不修改数据模型 double newMdStart = wellboreStartAbsoluteMd + relativeStart * m_totalWellboreMdLength; double newMdEnd = wellboreStartAbsoluteMd + relativeEnd * m_totalWellboreMdLength; // 调用渲染函数,传入计算出的新MD值进行渲染 updatePerforationAndHandleGeometry(newMdStart, newMdEnd); } void nmPerforationGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); // --- 1. 获取视图和缩放比例 --- // 前提条件检查:必须能获取到场景和视图 if (!scene() || scene()->views().isEmpty()) { return; } // 获取场景关联的第一个视图 QGraphicsView* view = scene()->views().first(); if (!view) { return; } // 从视图获取变换矩阵,这比从painter获取更稳定 const QTransform viewTransform = view->transform(); // 获取垂直缩放因子。使用 qAbs() 来处理坐标系翻转(scaleY为负)的情况 const qreal scaleY = qAbs(viewTransform.m22()); // --- 2. 核心计算(与之前相同,但基于更可靠的scaleY) --- if (scaleY < 1e-6) { // 安全检查,避免除以一个极小或为零的数 return; } // 定义在屏幕上看到的固定厚度 const qreal fixedVisualHeightInPixels = 15.0; // 动态计算在当前缩放级别下,为达到目标像素厚度,所需的几何高度 const qreal dynamicGeomHeight = fixedVisualHeightInPixels / scaleY; // --- 3. 创建并绘制矩形 --- const QRectF originalGeomRect = this->rect(); // 创建一个用于本次绘制的新矩形 QRectF rectToDraw(0, 0, originalGeomRect.width(), dynamicGeomHeight); rectToDraw.moveCenter(originalGeomRect.center()); // 使用纯色填充来绘制 painter->setPen(Qt::NoPen); painter->setBrush(Qt::red); painter->drawRect(rectToDraw); } QRectF nmPerforationGraphicsItem::boundingRect() const { return this->rect(); } void nmPerforationGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { // 如果不可编辑,则调用基类事件并返回 if(!m_bEditable) { QGraphicsRectItem::mousePressEvent(event); return; } m_lastMouseScenePos = event->scenePos(); // 记录鼠标按下的场景坐标 // 获取鼠标点击位置最顶层的图形项 QGraphicsItem* clickedItem = scene()->itemAt(event->scenePos(), QTransform()); // 根据点击的图形项类型设置交互模式 if(clickedItem == m_pStartHandle) { // 点击了起点手柄 double dStartMd = m_pPerforationData->getMdStart().getValue().toDouble(); bool isStartAtWellboreOrigin = qFuzzyCompare(dStartMd, m_wellboreStartAbsoluteMd); // 特殊处理水平裂缝井,如果起点在井筒原点,则不允许拖动起点 if(m_eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well && isStartAtWellboreOrigin) { m_eCurrentInteractionMode = PerforationInteractionMode::Normal; event->ignore(); // 忽略事件,不处理 } else { m_eCurrentInteractionMode = PerforationInteractionMode::ResizeStart; // 进入调整起点模式 m_initialMdStart = dStartMd; // 记录拖动开始时 Model 的原始起点MD m_initialMdEnd = m_pPerforationData->getMdEnd().getValue().toDouble(); // 记录拖动开始时 Model 的原始终点MD // 拖动开始时,设置临时MD值与 Model 真实值一致,并设置拖动标志 m_tempDragMdStart = m_initialMdStart; m_tempDragMdEnd = m_initialMdEnd; m_isDragging = true; // 标志开始拖动 event->accept(); // 接受事件,表示已处理 } } else if(clickedItem == m_pEndHandle) { // 点击了终点手柄 m_eCurrentInteractionMode = PerforationInteractionMode::ResizeEnd; // 进入调整终点模式 m_initialMdStart = m_pPerforationData->getMdStart().getValue().toDouble(); m_initialMdEnd = m_pPerforationData->getMdEnd().getValue().toDouble(); // 拖动开始时,设置临时MD值与 Model 真实值一致,并设置拖动标志 m_tempDragMdStart = m_initialMdStart; m_tempDragMdEnd = m_initialMdEnd; m_isDragging = true; event->accept(); } else { // 如果点击的是射孔本体(而不是手柄) // 注意:这里的 rect() 和 mapFromScene() 是针对 nmPerforationGraphicsItem 自身的局部坐标系 if(rect().contains(mapFromScene(event->scenePos()))) { // 检查点击点是否在射孔本体的局部矩形内 double dStartMd = m_pPerforationData->getMdStart().getValue().toDouble(); bool isStartAtWellboreOrigin = qFuzzyCompare(dStartMd, m_wellboreStartAbsoluteMd); // 特殊处理水平裂缝井,如果起点在井筒原点,则不允许移动整个射孔 if(m_eWellType == NM_WELL_MODEL::Horizontal_Fractured_Well && isStartAtWellboreOrigin) { m_eCurrentInteractionMode = PerforationInteractionMode::Normal; event->ignore(); } else { m_eCurrentInteractionMode = PerforationInteractionMode::MoveItem; // 进入移动整个项模式 if(flags() & ItemIsSelectable) { setSelected(true); // 如果可选择,则选中该项 } // 拖动开始时,设置临时MD值与 Model 真实值一致,并设置拖动标志 m_tempDragMdStart = m_initialMdStart; m_tempDragMdEnd = m_initialMdEnd; m_isDragging = true; event->accept(); } } else { m_eCurrentInteractionMode = PerforationInteractionMode::Normal; // 否则回到正常模式 QGraphicsRectItem::mousePressEvent(event); // 调用基类事件 } } // 再次检查,如果进入了某种交互模式,则接受事件 if(m_eCurrentInteractionMode != PerforationInteractionMode::Normal) { event->accept(); } else { QGraphicsRectItem::mousePressEvent(event); // 否则调用基类事件 } } void nmPerforationGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // 如果不可编辑,或者不是在拖动,或者不是正在进行交互模式,则调用基类事件并返回 if(!m_bEditable || m_eCurrentInteractionMode == PerforationInteractionMode::Normal || !m_isDragging) { QGraphicsRectItem::mouseMoveEvent(event); return; } QPointF currentScenePos = event->scenePos(); // 获取当前鼠标的场景坐标 double newMd = getMdFromScenePointOnWellbore(currentScenePos); // 计算当前鼠标位置对应的MD值 // 应该取自 m_tempDragMdEnd 和 m_tempDragMdStart,以保持与当前拖动状态一致,而不是模型当前值。 // 因为模型尚未更新,如果取模型当前值,可能会导致在拖动过程中再次被旧数据限制。 double dCurrentDragEndMd = m_tempDragMdEnd; //double dCurrentDragStartMd = m_tempDragMdStart; double dProposedMdStart = m_initialMdStart; // 默认使用拖动开始时的原始起点MD double dProposedMdEnd = m_initialMdEnd; // 默认使用拖动开始时的原始终点MD // 根据当前交互模式计算建议的MD值 if(m_eCurrentInteractionMode == PerforationInteractionMode::ResizeStart) { dProposedMdStart = newMd; // 如果是调整起点,新起点就是当前鼠标位置的MD } else if(m_eCurrentInteractionMode == PerforationInteractionMode::ResizeEnd) { dProposedMdEnd = newMd; // 如果是调整终点,新终点就是当前鼠标位置的MD } else if(m_eCurrentInteractionMode == PerforationInteractionMode::MoveItem) { // 计算MD位移量。这里采用了增量计算:当前鼠标位置与上次位置的MD差值 QPointF deltaScene = currentScenePos - m_lastMouseScenePos; // 当前帧的像素位移 double mdDelta; if(m_eWellType == NM_WELL_MODEL::Vertical_Well) { mdDelta = deltaScene.y(); // 垂直井筒通常只关注Y轴位移 } else { QLineF wellboreDirLine(m_wellboreStartScenePoint, m_wellboreEndScenePoint); if(qFuzzyIsNull(wellboreDirLine.length()) || qFuzzyIsNull(m_totalWellboreMdLength) || m_totalWellboreMdLength <= 0) { mdDelta = 0.0; // 防止除零 } else { // 计算鼠标位移在井筒方向上的投影,并转换为MD值 QVector2D wellboreUnitVec = QVector2D(wellboreDirLine.p2() - wellboreDirLine.p1()).normalized(); double visualMoveAlongWellbore = QVector2D::dotProduct(QVector2D(deltaScene), wellboreUnitVec); mdDelta = visualMoveAlongWellbore / wellboreDirLine.length() * m_totalWellboreMdLength; } } // 对于移动操作,将计算出的MD位移量累加到当前的临时MD值上 dProposedMdStart = m_tempDragMdStart + mdDelta; dProposedMdEnd = m_tempDragMdEnd + mdDelta; } // 获取父级视图,用于获取MD范围限制 nmAbstractWellboreVisual* parentView = static_cast(parentItem()); if(parentView) { double dUpperAllowedMd = -DBL_MAX; // 允许的最小MD值(上限) double dLowerAllowedMd = DBL_MAX; // 允许的最大MD值(下限) // 获取射孔允许的MD范围,这个函数应该基于 Model 的当前数据来判断限制 parentView->getWellData()->getPerforationAllowedMdRange(m_pPerforationData, dUpperAllowedMd, dLowerAllowedMd); // 应用MD范围限制 if(m_eCurrentInteractionMode == PerforationInteractionMode::ResizeStart) { dProposedMdStart = qMax(dProposedMdStart, dUpperAllowedMd); // 起点不能小于允许的上限 dProposedMdStart = qMin(dProposedMdStart, dCurrentDragEndMd); // 起点不能大于 Model 的当前终点MD } else if(m_eCurrentInteractionMode == PerforationInteractionMode::ResizeEnd) { dProposedMdEnd = qMin(dProposedMdEnd, dLowerAllowedMd); // 终点不能大于允许的下限 dProposedMdEnd = qMax(dProposedMdEnd, m_pPerforationData->getMdStart().getValue().toDouble()); // 终点不能小于 Model 的当前起点MD } else if(m_eCurrentInteractionMode == PerforationInteractionMode::MoveItem) { // 移动模式下的边界限制 // 使用拖动开始时的原始长度来保持长度不变 double dPerforationLength = m_initialMdEnd - m_initialMdStart; if(dProposedMdEnd > dLowerAllowedMd) { // 如果终点超出下限 dProposedMdEnd = dLowerAllowedMd; // 将终点设为下限 dProposedMdStart = dProposedMdEnd - dPerforationLength; // 起点也相应调整 } if(dProposedMdStart < dUpperAllowedMd) { // 如果起点超出上限 dProposedMdStart = dUpperAllowedMd; // 将起点设为上限 dProposedMdEnd = dProposedMdStart + dPerforationLength; // 终点也相应调整 } if(dProposedMdStart > dProposedMdEnd) { // 确保射孔长度不为负 (起点MD不能大于终点MD) dProposedMdStart = dProposedMdEnd; // 如果出现这种情况,将起点MD设为终点MD,变为零长度 } } } // 关键:更新临时变量,并使用它们进行局部视图更新 m_tempDragMdStart = dProposedMdStart; // 保存计算出的建议起点MD到临时变量 m_tempDragMdEnd = dProposedMdEnd; // 保存计算出的建议终点MD到临时变量 // 局部更新视图:直接调用 updatePerforationAndHandleGeometry,传入临时MD值进行渲染 // 此时不修改数据模型,也不发射信号,只更新屏幕显示。 // 手柄和射孔本体都会根据这些临时 MD 值重新计算并设置其在场景中的位置。 updatePerforationAndHandleGeometry(m_tempDragMdStart, m_tempDragMdEnd); m_lastMouseScenePos = currentScenePos; // 更新鼠标位置,为下一次移动做准备 event->accept(); // 接受事件,表示已处理 } void nmPerforationGraphicsItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { // 如果不可编辑,则调用基类事件并返回 if(!m_bEditable) { QGraphicsRectItem::mouseReleaseEvent(event); return; } m_eCurrentInteractionMode = PerforationInteractionMode::Normal; // 鼠标释放后,回到正常模式 QGraphicsRectItem::mouseReleaseEvent(event); // 调用基类的鼠标释放事件 // 关键:鼠标释放(拖动完成)时,发射最终的请求信号 // 只有在确实进行了拖动操作并且关联了数据模型时才发射信号 if(m_isDragging && m_pPerforationData) { // 发射信号,请求控制器更新数据。直接传递 m_pPerforationData 指针和最终的临时MD值 emit sigRequestPerforationMdUpdate(m_pPerforationData, m_tempDragMdStart, m_tempDragMdEnd); // 同步 m_initialMdStart 和 m_initialMdEnd // 确保下一次鼠标按下时的“原始位置”是本次拖动结束后的新位置 m_initialMdStart = m_tempDragMdStart; m_initialMdEnd = m_tempDragMdEnd; } // 重置拖动标志 m_isDragging = false; // 拖动操作结束 event->accept(); // 接受事件,表示已处理 } nmFractureLineHandleItem::nmFractureLineHandleItem(QGraphicsItem *parent) : QObject(nullptr), // 初始化 QObject 部分 QGraphicsRectItem(parent), // 初始化 QGraphicsRectItem 部分 m_bIsDragging(false) { // 默认样式,可以在外部设置 setBrush(QBrush(Qt::red)); setPen(QPen(Qt::black, 1)); setRect(-5, -5, 10, 10); // 默认大小,居中 setZValue(2.0); // 确保在裂缝之上 } void nmFractureLineHandleItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if(event->button() == Qt::LeftButton) { m_bIsDragging = true; m_lastMousePos = event->pos(); // 记录鼠标在手柄局部坐标系中的位置 event->accept(); // 接受这个事件,防止事件传递给父项或视图 } else { event->ignore(); // 忽略其他鼠标按钮 } //QGraphicsRectItem::mousePressEvent(event); // 调用基类实现(重要) } void nmFractureLineHandleItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if(m_bIsDragging) { // 获取当前鼠标位置(场景坐标) QPointF currentScenePos = event->scenePos(); // 获取上一次鼠标位置(场景坐标) QPointF lastScenePos = mapToScene(m_lastMousePos); // 计算增量(场景坐标) QPointF deltaScene = currentScenePos - lastScenePos; // 转换为父项坐标的增量 QPointF deltaParent = parentItem()->mapFromScene(currentScenePos) - parentItem()->mapFromScene(lastScenePos); // 更新位置(父项坐标) setPos(pos() + deltaParent); // 发出信号(传递父项坐标) emit handlePositionChanged(this, pos(), m_bIsDragging); // 更新记录(局部坐标) m_lastMousePos = mapFromScene(currentScenePos); event->accept(); // 接受事件 } else { event->ignore(); } QGraphicsRectItem::mouseMoveEvent(event); // 调用基类实现(重要) } void nmFractureLineHandleItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if(m_bIsDragging) { m_bIsDragging = false; // 发出信号通知父级裂缝项,手柄位置已改变 emit handlePositionChanged(this, pos(), m_bIsDragging); event->accept(); } else { event->ignore(); } QGraphicsRectItem::mouseReleaseEvent(event); // 调用基类实现(重要) } void nmFractureLineHandleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // 1. 获取视图缩放比例 QGraphicsScene* pScene = scene(); if (!pScene || pScene->views().isEmpty()) { return; } QGraphicsView* view = pScene->views().first(); if (!view) { return; } const QTransform viewTransform = view->transform(); // 截面图,依然使用 Y 轴缩放 const qreal scaleY = qAbs(viewTransform.m22()); if (scaleY < 1e-6) { return; } // 2. 定义手柄和边框的固定像素大小 const qreal fixedHandleSizeInPixels = 13.0; const qreal fixedBorderWidthInPixels = 2.0; // 定义边框的像素宽度 // 3. 根据缩放比例计算动态大小 const qreal dynamicHandleSize = fixedHandleSizeInPixels / scaleY; const qreal dynamicBorderWidth = fixedBorderWidthInPixels / scaleY; // 计算动态边框宽度 // 4. 定义手柄的矩形区域 QRectF handleRect(-dynamicHandleSize / 2, -dynamicHandleSize / 2, dynamicHandleSize, dynamicHandleSize); // 5. 创建一个新的 QPen 用于绘制边框 QPen pen(Qt::black); // 颜色为黑色 pen.setWidthF(dynamicBorderWidth); // 设置动态计算出的宽度 painter->setPen(pen); painter->setBrush(Qt::red); // 填充颜色保持为红色 painter->drawRect(handleRect); // 绘制带边框的矩形 } QRectF nmFractureLineHandleItem::boundingRect() const { // 1. 获取视图缩放比例 if (!scene() || scene()->views().isEmpty()) { return QRectF(); } QGraphicsView* view = scene()->views().first(); if (!view) { return QRectF(); } const QTransform viewTransform = view->transform(); const qreal scaleY = qAbs(viewTransform.m22()); if (scaleY < 1e-6) { return QRectF(); } // 2. 使用与paint方法相同的动态大小计算 const qreal fixedHandleSizeInPixels = 13.0; const qreal dynamicHandleSize = fixedHandleSizeInPixels / scaleY; // 3. 返回动态计算的矩形 return QRectF(-dynamicHandleSize / 2, -dynamicHandleSize / 2, dynamicHandleSize, dynamicHandleSize); } #include "nmDataVerticalFracturedWell.h" nmFractureLineGraphicsItem::nmFractureLineGraphicsItem(QGraphicsItem *parent) : QGraphicsLineItem(parent), m_bEditMode(false) { // 设置裂缝线的样式 //setPen(QPen(QColor("#1e90ff"), 5.0)); //createHandles(); QPen fracturePen(QColor("#1e90ff"), 5.0); fracturePen.setCosmetic(true); setPen(fracturePen); createHandles(); } void nmFractureLineGraphicsItem::setStyle(const QPen& pen) { QPen cosmetic_pen = pen; cosmetic_pen.setWidth(0); cosmetic_pen.setCosmetic(true); setPen(cosmetic_pen); //setPen(pen); } void nmFractureLineGraphicsItem::setEditMode(bool enable) { if(m_bEditMode == enable) { return; // 状态未改变,无需操作 } m_bEditMode = enable; if(m_pHandle1 && m_pHandle2) { m_pHandle1->setVisible(enable); // 根据模式显示或隐藏手柄 m_pHandle2->setVisible(enable); // 如果进入编辑模式,确保手柄位置是最新的 if(enable) { updateHandlesPosition(); } } // 强制更新视图,确保手柄的显示/隐藏立即生效 //scene()->update(); } void nmFractureLineGraphicsItem::createHandles() { // 使用裂缝手柄类 m_pHandle1 = new nmFractureLineHandleItem(this); // 以当前裂缝项为父项 m_pHandle2 = new nmFractureLineHandleItem(this); // 连接手柄的信号到本类的槽函数 connect(m_pHandle1, SIGNAL(handlePositionChanged(nmFractureLineHandleItem*, QPointF, bool)), this, SLOT(onHandlePositionChanged(nmFractureLineHandleItem*, QPointF, bool))); connect(m_pHandle2, SIGNAL(handlePositionChanged(nmFractureLineHandleItem*, QPointF, bool)), this, SLOT(onHandlePositionChanged(nmFractureLineHandleItem*, QPointF, bool))); // 创建完隐藏手柄 m_pHandle1->setVisible(false); m_pHandle2->setVisible(false); } void nmFractureLineGraphicsItem::updateHandlesPosition() { if(m_pHandle1 && m_pHandle2) { // 因为手柄是子项,它们的pos是相对于父项(裂缝项)的 m_pHandle1->setPos(line().p1()); m_pHandle2->setPos(line().p2()); } } // 槽函数的实现 void nmFractureLineGraphicsItem::onHandlePositionChanged(nmFractureLineHandleItem* handle, QPointF newPos, bool isDragging) { // 在这里直接调用更新裂缝的函数 if(m_bEditMode) { updateFractureFromHandle(handle, newPos, isDragging); } } // 修改 updateFractureFromHandle 的参数类型 void nmFractureLineGraphicsItem::updateFractureFromHandle(nmFractureLineHandleItem* draggedHandle, const QPointF& newHandlePosInParent, bool isDragging) { // 因为 nmFractureLineGraphicsItem 的 pos() 已经被设置到井筒位置,所以这个 (0,0) 就是井筒。 QPointF fractureCenterInLocal = QPointF(0, 0); QPointF p1_new_local, p2_new_local; if(draggedHandle == m_pHandle1) { p1_new_local = newHandlePosInParent; QPointF vecToDragged = p1_new_local - fractureCenterInLocal; p2_new_local = fractureCenterInLocal - vecToDragged; } else { // draggedHandle == m_pHandle2 p2_new_local = newHandlePosInParent; QPointF vecToDragged = p2_new_local - fractureCenterInLocal; p1_new_local = fractureCenterInLocal - vecToDragged; } // 设置裂缝线和手柄位置(此部分应保持在 mouseMoveEvent 链中) setLine(QLineF(p1_new_local, p2_new_local)); updateHandlesPosition(); // 拖动结束后发送信号 if(!isDragging) { // 将局部坐标转换为场景坐标 QPointF fractureCenterInScene = mapToScene(fractureCenterInLocal); QPointF p1_scene = mapToScene(p1_new_local); // 计算半长 double newHalfLength = QLineF(fractureCenterInScene, p1_scene).length(); // 计算并归一化裂缝角度 double dx = p1_scene.x() - fractureCenterInScene.x(); double dy = p1_scene.y() - fractureCenterInScene.y(); // 使用 qAtan2 获得角度(从正X轴逆时针旋转) double angleRadians = qAtan2(dy, dx); double angleDegrees = angleRadians * 180.0 / M_PI; // 将角度归一化到 [-90, 90] 的范围 // 这一步简化了你原有的复杂if/else结构,但实现了同样的效果 if(angleDegrees > 90) { angleDegrees -= 180; } else if(angleDegrees < -90) { angleDegrees += 180; } double finalAngle = angleDegrees; // 核心修改: 不再直接修改模型,而是发出信号 emit sigFractureGeometryChanged(newHalfLength, finalAngle); } } nmHorizontalFractureGraphicsItem::nmHorizontalFractureGraphicsItem(int index, QGraphicsItem *parent) : QObject(nullptr), // QObject初始化 QGraphicsLineItem(parent), // QGraphicsLineItem初始化 m_bEditMode(false), m_fractureIndex(index) { // 设置裂缝线的样式 //QPen fracturePen(QColor("#1e90ff"), 3.0); // 裂缝线条颜色为 #1e90ff //setPen(fracturePen); QPen fracturePen(QColor("#1e90ff"), 3.0); fracturePen.setCosmetic(true); setPen(fracturePen); // 创建手柄,以当前裂缝项为父项 m_pHandle1 = new nmFractureLineHandleItem(this); m_pHandle2 = new nmFractureLineHandleItem(this); // 连接手柄的信号到本类的槽函数 connect(m_pHandle1, SIGNAL(handlePositionChanged(nmFractureLineHandleItem*, QPointF, bool)), this, SLOT(onHandlePositionChanged(nmFractureLineHandleItem*, QPointF, bool))); connect(m_pHandle2, SIGNAL(handlePositionChanged(nmFractureLineHandleItem*, QPointF, bool)), this, SLOT(onHandlePositionChanged(nmFractureLineHandleItem*, QPointF, bool))); // 初始时手柄隐藏 m_pHandle1->setVisible(false); m_pHandle2->setVisible(false); setZValue(3); // 确保在井筒之上,手柄在裂缝之上 } void nmHorizontalFractureGraphicsItem::setStyle(const QPen& pen) { QPen cosmetic_pen = pen; cosmetic_pen.setWidth(0); cosmetic_pen.setCosmetic(true); setPen(cosmetic_pen); //setPen(pen); } void nmHorizontalFractureGraphicsItem::updateFractureLineAndHandles(const QPointF& p1, const QPointF& p2) { setLine(QLineF(p1, p2)); if(m_pHandle1 && m_pHandle2) { // 手柄的pos是相对于父项(裂缝线)的 // 所以直接设置到线的端点即可 m_pHandle1->setPos(p1); m_pHandle2->setPos(p2); } } void nmHorizontalFractureGraphicsItem::setWellboreInfo(const QPointF& wellboreStartScene, const QPointF& wellboreEndScene) { m_wellboreStartScene = wellboreStartScene; m_wellboreEndScene = wellboreEndScene; // 将 QPointF 转换为 QVector2D 进行归一化 QVector2D tempWellboreDir = QVector2D(wellboreEndScene - wellboreStartScene); if(!tempWellboreDir.isNull()) { // 检查是否是零向量 m_wellboreDirection = tempWellboreDir.normalized().toPointF(); // 归一化后转换回 QPointF } else { m_wellboreDirection = QPointF(1.0, 0.0); // 默认一个方向 } } void nmHorizontalFractureGraphicsItem::setEditMode(bool enable) { m_bEditMode = enable; if(m_pHandle1 && m_pHandle2) { m_pHandle1->setVisible(enable); m_pHandle2->setVisible(enable); } } void nmHorizontalFractureGraphicsItem::onHandlePositionChanged(nmFractureLineHandleItem* handle, QPointF newHandlePosInParent, bool isDragging) { if(!m_bEditMode) { return; } // 1. 获取裂缝中心点 (交点) 在父项(裂缝线)局部坐标系中的位置 QPointF currentFractureCenterInParent = (line().p1() + line().p2()) / 2.0; // 将新的手柄位置从父项局部坐标转换为场景坐标 QPointF newHandleScenePos = mapToScene(newHandlePosInParent); // 将裂缝中心点从父项局部坐标转换为场景坐标 QPointF fractureCenterScene = mapToScene(currentFractureCenterInParent); // 2. 计算手柄新位置在垂直于井筒方向上的投影点 // 垂直于井筒的方向向量 (使用QPointF直接计算,然后转换为QVector2D进行归一化) QVector2D rawPerpendicularWellboreDirection = QVector2D(-m_wellboreDirection.y(), m_wellboreDirection.x()); QVector2D perpendicularWellboreDirection = rawPerpendicularWellboreDirection; if(perpendicularWellboreDirection.isNull()) { perpendicularWellboreDirection = QVector2D(0.0, 1.0); // 避免零向量 } perpendicularWellboreDirection = perpendicularWellboreDirection.normalized(); // 向量从裂缝中心到手柄新位置 (转换为QVector2D进行点积运算) QVector2D vecCenterToHandle = QVector2D(newHandleScenePos - fractureCenterScene); // 计算vecCenterToHandle在垂直于井筒方向上的投影长度 // 这个 projectionLength 应该带有方向性,表示手柄相对于裂缝中心沿着垂直方向的“位移” double signedProjectionLength = QVector2D::dotProduct(vecCenterToHandle, perpendicularWellboreDirection); // 新手柄的有效场景位置 (强制在垂直于井筒的方向上移动) // 这里的 effectiveNewHandleScenePos 不再直接决定新的半长,而是作为计算端点的中间量 QPointF effectiveNewHandleScenePos = fractureCenterScene + perpendicularWellboreDirection.toPointF() * signedProjectionLength; // 3. 计算新的裂缝半长 // newHalfLength 现在直接是带符号的投影长度 double newHalfLength = signedProjectionLength; // 4. 更新图形项自身 (视觉更新) QPointF p1_new_scene, p2_new_scene; // 裂缝的两个端点应该分别位于中心点的两侧,距离中心点为 newHalfLength (有符号) // 根据拖动的是哪个手柄来确定哪个方向是 "正" 半长方向 if(handle == m_pHandle1) { // 如果拖动的是手柄1 (裂缝的p1) // 手柄1沿着 perpendicularWellboreDirection 的负方向移动 // 所以 newHalfLength 应该取其相反数 newHalfLength = -signedProjectionLength; // 使得 halfLength 总是指向 p2 的方向 } else { // handle == m_pHandle2 (拖动的是手柄2,裂缝的p2) // 手柄2沿着 perpendicularWellboreDirection 的正方向移动 newHalfLength = signedProjectionLength; // halfLength 保持正值 } // 计算新的裂缝端点 (场景坐标) // 注意:裂缝的实际绘制长度是 newHalfLength 的绝对值 * 2 // 但在数据模型中,halfLength 通常是正值。 // 这里我们用 newHalfLength 表示从中心点到被拖动手柄的方向上的“半长” // 另一个手柄则在反方向 // 裂缝的实际绘制长度(绝对值) double actualDisplayHalfLength = qAbs(newHalfLength); // 裂缝的两个端点基于裂缝中心和垂直方向向量 // p1 永远在 perpendicularWellboreDirection 的负方向 // p2 永远在 perpendicularWellboreDirection 的正方向 p1_new_scene = fractureCenterScene - perpendicularWellboreDirection.toPointF() * actualDisplayHalfLength; p2_new_scene = fractureCenterScene + perpendicularWellboreDirection.toPointF() * actualDisplayHalfLength; // 如果拖动的是 p1,那么 newHalfLength 应该以 p1 到中心点的距离为准,但数据模型存储的是正的半长。 // 如果是拖动 p1 并且 newHalfLength < 0 (即 p1 移动到原来 p2 的位置),需要反转 // 否则如果拖动 p2 且 newHalfLength < 0,也需要反转 // 最终向数据模型报告的半长必须是正值 double finalHalfLengthForDataModel = qAbs(newHalfLength); // 将场景坐标转换回局部坐标并设置线段 QPointF p1_local = mapFromScene(p1_new_scene); QPointF p2_local = mapFromScene(p2_new_scene); setLine(QLineF(p1_local, p2_local)); // 5. 更新手柄位置以匹配新的线段端点 updateFractureLineAndHandles(p1_local, p2_local); // 更新手柄视觉位置 // 6. 发出信号通知父项(nmHorizontalFracturedWellTopViewItem) // !!!这里发出的半长应该是正值,表示实际的几何长度 emit fractureHalfLengthChanged(finalHalfLengthForDataModel, isDragging, m_fractureIndex); } nmFractureRectHandleItem::nmFractureRectHandleItem(HandleType type, QGraphicsItem *parent) : QGraphicsRectItem(parent), m_eHandleType(type) { // 设置手柄的颜色和样式 setBrush(QColor("#808080")); // 灰色填充 setPen(Qt::NoPen); // 无边框 setRect(-5, -5, 10, 10); // 设置手柄的Z值,确保它在最前面。 setZValue(5); } nmFractureRectHandleItem::~nmFractureRectHandleItem() { // 析构函数,目前无需特殊清理 } // 获取手柄类型 HandleType nmFractureRectHandleItem::getHandleType() { return m_eHandleType; } void nmFractureRectHandleItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { // 当鼠标左键按下时 if(event->button() == Qt::LeftButton) { // 只有上下方向的手柄(包括角上的)才触发拖动 if(m_eHandleType == TopLeftHandle || m_eHandleType == TopMiddleHandle || m_eHandleType == TopRightHandle || m_eHandleType == BottomLeftHandle || m_eHandleType == BottomMiddleHandle || m_eHandleType == BottomRightHandle) { m_lastMouseScenePos = event->scenePos(); // 记录鼠标在场景坐标系下的按下位置 // 发送信号通知父项(nmFractureRectGraphicsItem)手柄被按下 emit handlePressed(m_eHandleType, m_lastMouseScenePos); // 仅接受左键事件,防止其他鼠标按钮产生副作用 setAcceptedMouseButtons(Qt::LeftButton); } // 如果是 MiddleLeftHandle 或 MiddleRightHandle,这里不发送信号,也不设置 acceptedMouseButtons // 这样它们就不会进入“拖动”状态,也不会影响父项的 m_eCurrentHandle } // 调用基类的鼠标按下事件处理函数 //QGraphicsRectItem::mousePressEvent(event); // 仍然调用基类,以处理可能的默认行为(如选择) } void nmFractureRectHandleItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // 当鼠标左键被按住并移动时 if(event->buttons() & Qt::LeftButton) { // 只有上下方向的手柄才发送移动信号 if(m_eHandleType == TopLeftHandle || m_eHandleType == TopMiddleHandle || m_eHandleType == TopRightHandle || m_eHandleType == BottomLeftHandle || m_eHandleType == BottomMiddleHandle || m_eHandleType == BottomRightHandle) { // 发送信号通知父项手柄正在移动,并传递当前鼠标的场景坐标 emit handleMoved(m_eHandleType, event->scenePos()); m_lastMouseScenePos = event->scenePos(); // 更新上一次鼠标位置 } // 如果是 MiddleLeftHandle 或 MiddleRightHandle,则不发送 handleMoved 信号 } // 调用基类的鼠标移动事件处理函数 //QGraphicsRectItem::mouseMoveEvent(event); } void nmFractureRectHandleItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { // 只有上下方向的手柄才发送释放信号 if(m_eHandleType == TopLeftHandle || m_eHandleType == TopMiddleHandle || m_eHandleType == TopRightHandle || m_eHandleType == BottomLeftHandle || m_eHandleType == BottomMiddleHandle || m_eHandleType == BottomRightHandle) { // 当鼠标左键释放时,所有手柄都应发送释放信号,以便父项重置 m_eCurrentHandle emit handleReleased(m_eHandleType, event->scenePos()); // 恢复接受左键事件 setAcceptedMouseButtons(Qt::LeftButton); } // 调用基类的鼠标释放事件处理函数 //QGraphicsRectItem::mouseReleaseEvent(event); } void nmFractureRectHandleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); // 1. 获取视图缩放比例 if (!scene() || scene()->views().isEmpty()) { return; } QGraphicsView* view = scene()->views().first(); if (!view) { return; } const QTransform viewTransform = view->transform(); const qreal scaleY = qAbs(viewTransform.m22()); if (scaleY < 1e-6) { // 避免除零 return; } // 2. 定义固定的像素大小 const qreal fixedHandleSizeInPixels = 15.0; // 调整像素大小 // 3. 根据缩放比例计算动态大小 const qreal dynamicHandleSize = fixedHandleSizeInPixels / scaleY; // 4. 定义手柄的矩形区域 (以 (0,0) 为中心) QRectF handleRect(-dynamicHandleSize / 2, -dynamicHandleSize / 2, dynamicHandleSize, dynamicHandleSize); // 5. 绘制 painter->setPen(Qt::NoPen); // 保持无边框 painter->setBrush(QColor("#808080")); // 保持灰色填充 painter->drawRect(handleRect); // 绘制动态计算的矩形 } QRectF nmFractureRectHandleItem::boundingRect() const { // 1. 获取视图缩放比例 if (!scene() || scene()->views().isEmpty()) { return QRectF(); } QGraphicsView* view = scene()->views().first(); if (!view) { return QRectF(); } const QTransform viewTransform = view->transform(); const qreal scaleY = qAbs(viewTransform.m22()); if (scaleY < 1e-6) { return QRectF(); } // 2. 使用与 paint 方法相同的动态大小计算 const qreal fixedHandleSizeInPixels = 15.0; // 必须与 paint 中的值一致 const qreal dynamicHandleSize = fixedHandleSizeInPixels / scaleY; // 3. 返回动态计算的矩形 return QRectF(-dynamicHandleSize / 2, -dynamicHandleSize / 2, dynamicHandleSize, dynamicHandleSize); } nmFractureRectGraphicsItem::nmFractureRectGraphicsItem(nmDataVerticalFracturedWell* pDataWell, QGraphicsItem *parent) : m_pDataWell(pDataWell), QGraphicsItem(parent), m_bEditMode(false), m_handleSize(18.0), m_eCurrentHandle(NoHandle) { // 设置Z值,确保裂缝矩形在储层之上,射孔之下 setZValue(-2); // 获取储层的边界信息 m_minReservoirTop = nmDataAnalyzeManager::getCurrentInstance()->getMinLayerTop(); m_maxReservoirBottom = nmDataAnalyzeManager::getCurrentInstance()->getMaxLayerBottom(); // 初始更新裂缝几何信息。手柄的创建和销毁与编辑模式关联。 updateFractureGeometry(); } nmFractureRectGraphicsItem::~nmFractureRectGraphicsItem() { // 析构函数中销毁所有创建的手柄子项,防止内存泄漏 destroyHandles(); } QRectF nmFractureRectGraphicsItem::boundingRect() const { // 边界矩形只包含裂缝矩形本身。 // 手柄是独立的子项,它们有自己的boundingRect,QGraphicsItem 不会自动包含子项的边界。 return m_fractureRect.normalized(); } void nmFractureRectGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); // 避免编译器警告,表示此参数未使用 Q_UNUSED(widget); // 避免编译器警告 painter->setRenderHint(QPainter::Antialiasing); // 开启抗锯齿,使绘制更平滑 // 绘制裂缝矩形 //painter->setPen(QPen(QColor("#0c2e14"), 1.0)); // 绘制边框 //painter->setBrush(QColor("#0000ff")); // 设置填充颜色为蓝色 //painter->drawRect(m_fractureRect); // 绘制裂缝矩形 QPen fracturePen(QColor("#0c2e14"), 1.0); fracturePen.setCosmetic(true); painter->setPen(fracturePen); painter->setBrush(QColor("#0000ff")); painter->drawRect(m_fractureRect); } void nmFractureRectGraphicsItem::setEditMode(bool bEdit) { // 如果编辑模式状态发生改变 if(m_bEditMode != bEdit) { m_bEditMode = bEdit; // 更新编辑模式状态 if(m_bEditMode) { createHandles(); // 如果进入编辑模式,则创建并显示手柄 } else { destroyHandles(); // 如果退出编辑模式,则销毁手柄 } update(); // 请求重新绘制图元,以反映手柄的显示/隐藏状态 } } // 槽函数实现:当手柄被按下时 void nmFractureRectGraphicsItem::onHandlePressed(HandleType type, QPointF scenePos) { m_eCurrentHandle = type; // 记录当前正在拖动的手柄类型 m_lastMouseScenePos = scenePos; // 记录鼠标在场景坐标系中的位置 } // 槽函数实现:当手柄被拖动时 void nmFractureRectGraphicsItem::onHandleMoved(HandleType type, QPointF scenePos) { if(m_eCurrentHandle == NoHandle) return; // 如果没有手柄被激活拖动,则直接返回 prepareGeometryChange(); // 通知 QGraphicsItem 框架几何形状即将改变,以便更新场景 qreal dy = scenePos.y() - m_lastMouseScenePos.y(); // 计算鼠标在垂直方向上的位移 qreal newY = m_fractureRect.y(); // 裂缝新的顶部Y坐标 qreal newHeight = m_fractureRect.height(); // 裂缝新的高度 // 根据当前拖动的手柄类型,调整裂缝的高度和/或位置 switch(m_eCurrentHandle) { case TopLeftHandle: case TopMiddleHandle: case TopRightHandle: // 拖动顶部手柄,顶部Y坐标改变,高度随之变化 newY += dy; newHeight -= dy; break; case BottomLeftHandle: case BottomMiddleHandle: case BottomRightHandle: // 拖动底部手柄,高度改变,顶部Y坐标不变 newHeight += dy; break; case MiddleLeftHandle: case MiddleRightHandle: // 左右两个手柄在垂直方向上不起作用。 // 在本例中,裂缝的半长(宽度)是由 nmDataVerticalFracturedWell 固定的, // 因此这里也不处理水平方向的缩放。 break; default: break; } // --- 应用储层高度限制 --- qreal potentialTop = newY; // 潜在的裂缝顶部Y坐标 qreal potentialBottom = newY + newHeight; // 潜在的裂缝底部Y坐标 // 1. 如果潜在的新顶部超出了储层顶部边界 if(potentialTop < m_minReservoirTop) { newY = m_minReservoirTop; // 裂缝顶部固定在储层顶部 newHeight = potentialBottom - newY; // 重新计算高度 } // 2. 如果潜在的新底部超出了储层底部边界 if(newY + newHeight > m_maxReservoirBottom) { newHeight = m_maxReservoirBottom - newY; // 重新计算高度 } // 3. 确保裂缝高度不小于最小值 if(newHeight < 1.0) { // 例如,最小高度设置为1.0像素 newHeight = 1.0; // 如果是从顶部缩小,且达到了最小高度,则调整 Y 坐标以保持底部不变 if(m_eCurrentHandle == TopLeftHandle || m_eCurrentHandle == TopMiddleHandle || m_eCurrentHandle == TopRightHandle) { newY = m_fractureRect.bottom() - newHeight; } } // 在拖动过程中,只更新矩形数据,不应用任何业务逻辑(如边界限制) --> // 只进行实时视觉反馈,不修改模型,也不发送信号。 m_fractureRect.setRect(m_fractureRect.x(), newY, m_fractureRect.width(), newHeight); updateHandlePositions(); // 裂缝矩形改变后,重新定位所有手柄 m_lastMouseScenePos = scenePos; // 更新上一次鼠标的场景坐标 update(); // 请求重绘整个裂缝矩形图元及其子项(手柄) // 拖动过程中实时发送裂缝位置 emit sigFractureGeometryChanged(m_fractureRect.top(), m_fractureRect.bottom()); } // 槽函数实现:当手柄被释放时 void nmFractureRectGraphicsItem::onHandleReleased(HandleType type, QPointF scenePos) { // 1. 重置当前拖动的手柄,表示拖动结束 m_eCurrentHandle = NoHandle; // 2. 发送最终的、经过视图修正后的 MD 值给控制器 // 注意,这里传递的是 m_fractureRect.top() 和 m_fractureRect.bottom(), // 这些值已经经过了上面的实时修正,所以不会超出视图范围。 // 但是控制器仍然需要再次检查,以确保数据的最终一致性。 emit sigRequestFractureRectUpdate(m_fractureRect.top(), m_fractureRect.bottom()); } void nmFractureRectGraphicsItem::updateFractureGeometry() { if(!m_pDataWell) return; // 从数据模型中获取裂缝的相关几何数据 double dWellboreX = m_pDataWell->getX().getValue().toDouble(); // 井的X坐标 double dFHalfLength = m_pDataWell->getFractureHalfLength().getValue().toDouble(); // 裂缝半长 double dFHeight = m_pDataWell->getFractureHeight().getValue().toDouble(); // 裂缝高度 double dMidPointHeight = m_pDataWell->getFractureMidPointHeight().getValue().toDouble(); // 裂缝中点高度 // 裂缝的纵向定位 qreal fractureTopY = m_maxReservoirBottom - dMidPointHeight - dFHeight / 2; // 设置裂缝矩形的位置和尺寸 // X坐标:井中心X - 裂缝半长 // Y坐标:m_minReservoirTop (矩形顶部Y坐标) // 宽度:裂缝半长 * 2 // 高度:裂缝高度 m_fractureRect.setRect(dWellboreX - dFHalfLength, fractureTopY, dFHalfLength * 2.0, // 裂缝宽度 = 裂缝半长 * 2 dFHeight); // 裂缝高度 } void nmFractureRectGraphicsItem::updateHandlePositions() { qreal x = m_fractureRect.x(); // 裂缝矩形的X坐标 qreal y = m_fractureRect.y(); // 裂缝矩形的Y坐标 qreal w = m_fractureRect.width(); // 裂缝矩形的宽度 qreal h = m_fractureRect.height(); // 裂缝矩形的高度 // 我们的手柄在它们的局部坐标系中是 (0,0) 居中的。 // 我们只需要把它们的 `pos()` 设置到父坐标系中的正确位置。 foreach(nmFractureRectHandleItem *handle, m_handles) { QPointF handlePos; // 用于存储手柄在父项坐标系中的 *中心点* 位置 switch(handle->getHandleType()) { case TopLeftHandle: handlePos.setX(x); handlePos.setY(y); break; case TopMiddleHandle: handlePos.setX(x + w / 2.0); handlePos.setY(y); break; case TopRightHandle: handlePos.setX(x + w); handlePos.setY(y); break; case MiddleLeftHandle: handlePos.setX(x); handlePos.setY(y + h / 2.0); break; case MiddleRightHandle: handlePos.setX(x + w); handlePos.setY(y + h / 2.0); break; case BottomLeftHandle: handlePos.setX(x); handlePos.setY(y + h); break; case BottomMiddleHandle: handlePos.setX(x + w / 2.0); handlePos.setY(y + h); break; case BottomRightHandle: handlePos.setX(x + w); handlePos.setY(y + h); break; default: break; } handle->setPos(handlePos); } } //void nmFractureRectGraphicsItem::updateHandlePositions() //{ // qreal x = m_fractureRect.x(); // 裂缝矩形的X坐标 // qreal y = m_fractureRect.y(); // 裂缝矩形的Y坐标 // qreal w = m_fractureRect.width(); // 裂缝矩形的宽度 // qreal h = m_fractureRect.height(); // 裂缝矩形的高度 // // qreal hs = m_handleSize / 2.0; // 手柄半宽/高,用于在中心定位手柄 // // // 遍历所有手柄,并根据其类型设置其在裂缝矩形本地坐标系中的位置和尺寸 // foreach(nmFractureRectHandleItem *handle, m_handles) { // QRectF handleRect; // // switch(handle->getHandleType()) { // case TopLeftHandle: // handleRect.setRect(x - hs, y - hs, m_handleSize, m_handleSize); // break; // // case TopMiddleHandle: // handleRect.setRect(x + w / 2.0 - hs, y - hs, m_handleSize, m_handleSize); // break; // // case TopRightHandle: // handleRect.setRect(x + w - hs, y - hs, m_handleSize, m_handleSize); // break; // // case MiddleLeftHandle: // handleRect.setRect(x - hs, y + h / 2.0 - hs, m_handleSize, m_handleSize); // break; // // case MiddleRightHandle: // handleRect.setRect(x + w - hs, y + h / 2.0 - hs, m_handleSize, m_handleSize); // break; // // case BottomLeftHandle: // handleRect.setRect(x - hs, y + h - hs, m_handleSize, m_handleSize); // break; // // case BottomMiddleHandle: // handleRect.setRect(x + w / 2.0 - hs, y + h - hs, m_handleSize, m_handleSize); // break; // // case BottomRightHandle: // handleRect.setRect(x + w - hs, y + h - hs, m_handleSize, m_handleSize); // break; // // default: // break; // } // // handle->setRect(handleRect); // 设置手柄的矩形,QGraphicsRectItem 会自动根据其 rect 绘制 // } //} void nmFractureRectGraphicsItem::createHandles() { destroyHandles(); // 在创建新手柄之前,先确保销毁所有旧手柄 // 获取 nmFractureRectGraphicsItem 的父项,也就是 nmVerticalFracturedWellCrossSectionItem // 确保这个父项存在且是一个 QGraphicsItem QGraphicsItem* pRootParent = this->parentItem(); if(!pRootParent) { qWarning() << "nmFractureRectGraphicsItem has no parent item. Handles might not be visible."; return; } // 按照 HandleType 枚举的顺序创建八个手柄 // 将 nmFractureRectGraphicsItem 的父项作为手柄的父项 m_handles.append(new nmFractureRectHandleItem(TopLeftHandle, pRootParent)); m_handles.append(new nmFractureRectHandleItem(TopMiddleHandle, pRootParent)); m_handles.append(new nmFractureRectHandleItem(TopRightHandle, pRootParent)); m_handles.append(new nmFractureRectHandleItem(MiddleLeftHandle, pRootParent)); m_handles.append(new nmFractureRectHandleItem(MiddleRightHandle, pRootParent)); m_handles.append(new nmFractureRectHandleItem(BottomLeftHandle, pRootParent)); m_handles.append(new nmFractureRectHandleItem(BottomMiddleHandle, pRootParent)); m_handles.append(new nmFractureRectHandleItem(BottomRightHandle, pRootParent)); // 连接每个手柄的信号到当前类的槽函数,以便响应手柄的拖动事件 foreach(nmFractureRectHandleItem *handle, m_handles) { connect(handle, SIGNAL(handlePressed(HandleType, QPointF)), this, SLOT(onHandlePressed(HandleType, QPointF))); connect(handle, SIGNAL(handleMoved(HandleType, QPointF)), this, SLOT(onHandleMoved(HandleType, QPointF))); connect(handle, SIGNAL(handleReleased(HandleType, QPointF)), this, SLOT(onHandleReleased(HandleType, QPointF))); } updateHandlePositions(); // 更新手柄的位置,使其与裂缝矩形对齐 } void nmFractureRectGraphicsItem::destroyHandles() { qDeleteAll(m_handles); // 删除 QList 中所有 nmFractureRectHandleItem 对象 m_handles.clear(); // 清空 QList,使其不包含任何指针 } nmReservoirGraphicsItems::nmReservoirGraphicsItems(QGraphicsItem *parent) : QGraphicsItem(parent) { // 设置Z值,确保它总是在所有其他井筒图元的下方绘制 setZValue(-1000); // 确保它是最底层的背景图元 // 获取储层数据 QVector vecLayers = nmDataAnalyzeManager::getCurrentInstance()->getLayers(); this->setReservoirLayers(vecLayers); } nmReservoirGraphicsItems::~nmReservoirGraphicsItems() { qDeleteAll(m_internalLayers); m_internalLayers.clear(); } QRectF nmReservoirGraphicsItems::boundingRect() const { double dMinTop = nmDataAnalyzeManager::getCurrentInstance()->getMinLayerTop(); double dMaxBottom = nmDataAnalyzeManager::getCurrentInstance()->getMaxLayerBottom(); return QRectF(-100000, dMinTop, 200000, dMaxBottom - dMinTop); //return QRectF(-50, dMinTop, 100, dMaxBottom - dMinTop); } void nmReservoirGraphicsItems::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { if(!option) { return; } painter->setRenderHint(QPainter::Antialiasing); // visibleRect 表示当前需要绘制的区域(在 Item 的本地坐标系中) QRectF visibleRect = option->exposedRect; // 如果没有层数据,则什么都不画 if(m_internalLayers.isEmpty()) { return; } // 定义绘制层的水平范围,使其覆盖当前可见区域的整个宽度 // 这样层在水平方向上就会看起来“无限大” qreal layerXStart = visibleRect.left(); qreal layerXEnd = visibleRect.right(); qreal currentDrawWidth = layerXEnd - layerXStart; // 根据当前可见区域计算宽度 double dMinTop = nmDataAnalyzeManager::getCurrentInstance()->getMinLayerTop(); double dMaxBottom = nmDataAnalyzeManager::getCurrentInstance()->getMaxLayerBottom(); // 1. 绘制内部储层 for(int i = 0; i < m_internalLayers.size(); ++i) { const nmDataLayer* layer = m_internalLayers.at(i); if(!layer) { continue; } qreal topDepth = layer->getTop(); qreal bottomDepth = layer->getBottom(); QColor fillColor = layer->getColor(); if(topDepth >= bottomDepth) { continue; // 无效的层 } // 绘制填充矩形,其宽度为当前可见区域的宽度 painter->setPen(Qt::NoPen); painter->setBrush(fillColor); painter->drawRect(layerXStart, topDepth, currentDrawWidth, bottomDepth - topDepth); //// 绘制层与层之间的分隔线 //if(i < m_internalLayers.size() - 1) { // painter->setPen(QPen(Qt::black, 0.5)); // 细线 // painter->drawLine(layerXStart, bottomDepth, layerXEnd, bottomDepth); //} } // 2. 绘制最外层的上下边框 (现在它们就是 m_minLayerTop 和 m_maxLayerBottom) //QColor outerBorderColor = QColor("#8b4513"); //qreal outerBorderPenWidth = 5.0; //// 绘制最顶部的边框 (对应 m_minLayerTop) //painter->setPen(QPen(outerBorderColor, outerBorderPenWidth)); //// 边框也应覆盖当前可见区域的整个宽度 //painter->drawLine(layerXStart, dMinTop, layerXEnd, dMinTop); //// 绘制最底部的边框 (对应 m_maxLayerBottom) //painter->setPen(QPen(outerBorderColor, outerBorderPenWidth)); //// 边框也应覆盖当前可见区域的整个宽度 //painter->drawLine(layerXStart, dMaxBottom, layerXEnd, dMaxBottom); // 修改边框绘制 QColor outerBorderColor = QColor("#8b4513"); QPen outerBorderPen(outerBorderColor, 5); // 线宽0表示1像素 outerBorderPen.setCosmetic(true); // 不随变换缩放 painter->setPen(outerBorderPen); painter->drawLine(layerXStart, dMinTop, layerXEnd, dMinTop); painter->drawLine(layerXStart, dMaxBottom, layerXEnd, dMaxBottom); } qreal nmReservoirGraphicsItems::getMinLayerTop() { return nmDataAnalyzeManager::getCurrentInstance()->getMinLayerTop(); } qreal nmReservoirGraphicsItems::getMaxLayerBottom() { return nmDataAnalyzeManager::getCurrentInstance()->getMaxLayerBottom(); } //void nmReservoirGraphicsItems::setReservoirLayers(const QVector& layers) //{ // qDeleteAll(m_internalLayers); // m_internalLayers.clear(); // // for(int i = 0; i < layers.size(); ++i) { // nmDataLayer* layer = layers.at(i); // // if(layer) { // m_internalLayers.append(static_cast(layer->clone())); // } // } // // prepareGeometryChange(); // update(); //} void nmReservoirGraphicsItems::setReservoirLayers(const QVector& layers) { qDeleteAll(m_internalLayers); m_internalLayers.clear(); for (int i = 0; i < layers.size(); ++i) { nmDataLayer* layer = layers.at(i); if (layer) { // 直接用拷贝构造做深拷贝 m_internalLayers.append(new nmDataLayer(*layer)); } } prepareGeometryChange(); update(); } nmAbstractWellboreVisual::nmAbstractWellboreVisual(QGraphicsItem *parent) : QGraphicsItemGroup(parent), m_pDataWell(nullptr) { // 可以在这里设置一些通用的标志,例如:可选择、可移动等 // setFlags(ItemIsSelectable); setHandlesChildEvents(false); // 关键:组不处理子项事件 //setFlag(QGraphicsItem::ItemIsMovable, false); // 组本身不可移动 } // 析构函数实现 nmAbstractWellboreVisual::~nmAbstractWellboreVisual() { } nmDataWellBase* nmAbstractWellboreVisual::getWellData() { return m_pDataWell; } void nmAbstractWellboreVisual::removePerforationVisual(nmPerforationGraphicsItem* itemToRemove) { if(!itemToRemove) { return; } m_listPerforations.removeOne(itemToRemove); // 查找并移除指针 // 删除图形项对象,这会自动将其从场景和其父QGraphicsItemGroup中移除 delete itemToRemove; itemToRemove = nullptr; // 避免悬空指针 // 通知视图几何形状可能已改变,触发重绘 prepareGeometryChange(); update(); } void nmAbstractWellboreVisual::clearAllPerforationItems() { // 移除并删除射孔段图元 foreach(nmPerforationGraphicsItem* item, m_listPerforations) { delete item; } m_listPerforations.clear(); } // 清除所有子项的通用方法 void nmAbstractWellboreVisual::clearAllChildItems() { // 遍历当前组的所有子项 foreach(QGraphicsItem *child, childItems()) { removeFromGroup(child); // 从当前组中移除该子项 delete child; // 删除该子项(因为它们是new出来的) child = nullptr; } // 清空内部的跟踪列表 m_listPoints.clear(); m_listLines.clear(); m_listPerforations.clear(); m_listLineFractures.clear(); m_listRectFractures.clear(); } // 辅助函数:将 nmWellborePointGraphicsItem 添加到组中并记录 void nmAbstractWellboreVisual::addPoint(nmWellborePointGraphicsItem* item) { addToGroup(item); // 将项添加到QGraphicsItemGroup中 m_listPoints.append(item); // 添加到内部跟踪列表 } // 辅助函数:将 nmWellboreLineGraphicsItem 添加到组中并记录 void nmAbstractWellboreVisual::addLine(nmWellboreLineGraphicsItem* item) { addToGroup(item); m_listLines.append(item); } // 辅助函数:将 nmPerforationGraphicsItem 添加到组中并记录 void nmAbstractWellboreVisual::addPerforation(nmPerforationGraphicsItem* item) { addToGroup(item); m_listPerforations.append(item); } // 辅助函数:将 nmFractureLineGraphicsItem 添加到组中并记录 void nmAbstractWellboreVisual::addFractureLine(nmFractureLineGraphicsItem* item) { addToGroup(item); m_listLineFractures.append(item); } // 辅助函数:将 nmFractureRectGraphicsItem 添加到组中并记录 void nmAbstractWellboreVisual::addFractureRect(nmFractureRectGraphicsItem* item) { addToGroup(item); m_listRectFractures.append(item); } #include "nmDataWellBase.h" #include "nmDataVerticalWell.h" // --- VerticalWellTopViewItem 实现 --- nmVerticalWellTopViewItem::nmVerticalWellTopViewItem(QGraphicsItem *parent) : nmAbstractWellboreVisual(parent) { m_pWellPoint = nullptr; m_pOriginalPoint = nullptr; m_pDashLine = nullptr; } void nmVerticalWellTopViewItem::updateGeometryVisuals(nmDataWellBase *pDataWell) { prepareGeometryChange(); // 通知QGraphicsView,此项的几何形状可能发生变化 clearAllChildItems(); // 清除所有已有的子项 m_pDataWell = pDataWell; // 存储几何数据指针 // 检查几何数据是否有效,且类型是否匹配 (垂直井或垂直压裂井在俯视图中显示类似) if(!m_pDataWell || m_pDataWell->getWellType() != NM_WELL_MODEL::Vertical_Well) { return; // 不符合类型则不绘制 } nmDataVerticalWell *pVerticalWell = dynamic_cast(m_pDataWell); if(pVerticalWell == nullptr) { return; } // 1. 绘制井筒点 (绿色圆点) m_pWellPoint = new nmWellborePointGraphicsItem(this); // 创建井点图元,并将其父项设为当前组合图元 //double dRadius = pVerticalWell->getRadius().getValue().toDouble(); // 井点可视化的大小(半径) double dRadius = 7; double dX = pVerticalWell->getX().getValue().toDouble(); // 井口横坐标 double dY = pVerticalWell->getY().getValue().toDouble(); // 井口纵坐标 // 设置图元位置到场景坐标系的(dX,dY) m_pWellPoint->setRect(-dRadius, -dRadius, dRadius * 2, dRadius * 2); // 矩形中心在 (0,0) m_pWellPoint->setPos(dX, dY); // 将图元移动到场景中的 (dX, dY) m_pWellPoint->setFlag(QGraphicsItem::ItemIsMovable, false); // 初始不可移动 // 设置Z值,确保在上面 m_pWellPoint->setZValue(1); // 连接井点拖动相关信号 connect(m_pWellPoint, SIGNAL(sigPositionChanging(QPointF)), this, SLOT(slotUpdateDashLine(QPointF))); connect(m_pWellPoint, SIGNAL(sigDragFinished(QPointF)), this, SLOT(slotWellborePointDragFinished(QPointF))); addPoint(m_pWellPoint); // 添加到组合图元中并记录 // 绘制原始点 (红色圆点) 和虚线 if(!m_pOriginalPoint) { m_pOriginalPoint = new nmWellborePointGraphicsItem(this); } m_pOriginalPoint->setRect(m_pWellPoint->rect()); m_pOriginalPoint->setPos(m_pWellPoint->pos()); m_pOriginalPoint->setStyle(QPen(Qt::black, 0.5f), QBrush(Qt::red)); m_pOriginalPoint->setFlag(QGraphicsItem::ItemIsMovable, false); // 设置Z值,确保在绿色图元下面 m_pOriginalPoint->setZValue(0); m_pOriginalPoint->setVisible(false); // 默认隐藏 if(!m_pDashLine) { //m_pDashLine = new QGraphicsLineItem(this); //m_pDashLine->setPen(QPen(Qt::darkGreen, 1, Qt::DashLine)); //m_pDashLine->setZValue(-1); //m_pDashLine->setVisible(false); // 默认隐藏 m_pDashLine = new QGraphicsLineItem(this); QPen dashPen(Qt::darkGreen, 0, Qt::DashLine); dashPen.setCosmetic(true); m_pDashLine->setPen(dashPen); m_pDashLine->setZValue(-1); m_pDashLine->setVisible(false); } // 初始虚线连接位置 m_pDashLine->setLine(QLineF(m_pWellPoint->pos(), m_pOriginalPoint->pos())); update(); // 请求重绘 } void nmVerticalWellTopViewItem::setEditMode(bool enabled) { if(m_pWellPoint) { m_pWellPoint->setFlag(QGraphicsItem::ItemIsMovable, enabled); // 启用/禁用绿点移动 // 刷新虚线和红点的位置和可见性 if(m_pOriginalPoint) { m_pOriginalPoint->setRect(m_pWellPoint->rect()); m_pOriginalPoint->setPos(m_pWellPoint->pos()); m_pOriginalPoint->setVisible(enabled); } if(m_pDashLine) { m_pDashLine->setLine(QLineF(m_pWellPoint->pos(), m_pOriginalPoint->pos())); m_pDashLine->setVisible(enabled); } } } void nmVerticalWellTopViewItem::slotUpdateDashLine(const QPointF& newPos) { if(m_pDashLine && m_pOriginalPoint) { // 更新虚线连接位置 m_pDashLine->setLine(QLineF(newPos, m_pOriginalPoint->pos())); } } void nmVerticalWellTopViewItem::slotWellborePointDragFinished(const QPointF& finalCenterScenePos) { // 此时拖动已经结束,发出信号给 nmWxWellboreTopView,它会再将此信号转发给控制器 emit sigWellborePointDragFinished(finalCenterScenePos); } nmVerticalWellCrossSectionItem::nmVerticalWellCrossSectionItem(QGraphicsItem *parent) : nmAbstractWellboreVisual(parent) { // 可以在这里设置一些默认的样式或初始化成员变量 } void nmVerticalWellCrossSectionItem::updateGeometryVisuals(nmDataWellBase *pDataWell) { prepareGeometryChange(); clearAllChildItems(); // 清除所有已有的子项 m_pDataWell = pDataWell; // 存储几何数据指针 // 检查几何数据是否有效,且类型是否匹配 if(!m_pDataWell || m_pDataWell->getWellType() != NM_WELL_MODEL::Vertical_Well) { return; } nmDataVerticalWell *pVerticalWell = dynamic_cast(m_pDataWell); if(!pVerticalWell) { return; } // 在纵截面视图中,X轴通常代表左右延伸,Y轴代表深度(向下为正) double dWellboreX = pVerticalWell->getX().getValue().toDouble(); // 获取井底深 double dBottomholeMD = pVerticalWell->getBottomholeMD().getValue().toDouble(); // 获取井身长度 double dWellLength = pVerticalWell->getWellLength().getValue().toDouble(); // 1. 绘制储层矩形 (nmReservoirGraphicsItems) - 最底层 (Z值: -1000) nmReservoirGraphicsItems* pReservoirs = new nmReservoirGraphicsItems(this); // 创建实例,Z值在构造函数中已设置 addToGroup(pReservoirs); // 直接添加到组 // 2. 绘制井筒线 (nmWellboreLineGraphicsItem) - 中间层 (Z值: 0) nmWellboreLineGraphicsItem* pWellboreLine = new nmWellboreLineGraphicsItem(this); pWellboreLine->setLine(dWellboreX, dBottomholeMD, dWellboreX, dBottomholeMD + dWellLength); // 垂直线 //pWellboreLine->setStyle(QPen(QColor("#d3d3d3"), 2)); QPen wellborePen(QColor("#d3d3d3"), 2); wellborePen.setCosmetic(true); pWellboreLine->setStyle(wellborePen); pWellboreLine->setZValue(2); // 设置Z值为2,确保在射孔段之上 addLine(pWellboreLine); // 添加到组中 // 获取整个组合图元的包围矩形 // 井筒的视觉起点和终点 QPointF wellboreStartScene = QPointF(dWellboreX, dBottomholeMD); QPointF wellboreEndScene = QPointF(dWellboreX, dBottomholeMD + dWellLength); // 垂直线 // 井筒的MD起始值和总MD长度 double wellboreStartAbsoluteMd = dBottomholeMD; double totalWellboreMdLength = dWellLength; // 3. 绘制射孔段 (nmPerforationGraphicsItem) - 最上层 (Z值: 1) QVector& perforations = pVerticalWell->getPerforations(); // 获取射孔段集合 for(int i = 0; i < perforations.size(); ++i) { nmDataPerforation* pPerfData = perforations.at(i); nmPerforationGraphicsItem* pPerforation = new nmPerforationGraphicsItem( pPerfData, NM_WELL_MODEL::Vertical_Well, // 传递井类型 wellboreStartAbsoluteMd, // 传递井筒的MD起始值 totalWellboreMdLength, // 传递井筒的总MD长度 this ); pPerforation->setZValue(1); // 设置Z值为1,确保在井筒(Z=2)之下 addPerforation(pPerforation); // 添加到组中 // 初始化射孔段的视觉信息 pPerforation->updateWellboreVisuals(wellboreStartScene, wellboreEndScene, wellboreStartAbsoluteMd, totalWellboreMdLength); // 连接射孔拖动信号 connect(pPerforation, SIGNAL(sigRequestPerforationMdUpdate(nmDataPerforation*, double, double)), this, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double))); } update(); // 请求重绘 } void nmVerticalWellCrossSectionItem::setPerforationEditMode(bool enabled) { // TODO,处于编辑状态要做什么 // 遍历射孔段数组,设置为编辑状态 foreach(nmPerforationGraphicsItem* item, m_listPerforations) { item->setEditMode(enabled); // 如果需要,可以在这里根据编辑模式切换选中状态,确保一致性 //item->setSelected(enabled); //item->setFlag(QGraphicsItem::ItemIsMovable, true); } } QRectF nmVerticalFracturedWellCrossSectionItem::getFractureMD() { return m_pRectFracture->boundingRect(); } // --- nmVerticalFracturedWellTopViewItem 实现 --- nmVerticalFracturedWellTopViewItem::nmVerticalFracturedWellTopViewItem(QGraphicsItem *parent) : nmAbstractWellboreVisual(parent), m_pFracture(nullptr) { } void nmVerticalFracturedWellTopViewItem::updateGeometryVisuals(nmDataWellBase *pDataWell) { prepareGeometryChange(); // 通知QGraphicsView,此项的几何形状可能发生变化 clearAllChildItems(); // 清除所有已有的子项 m_pDataWell = pDataWell; // 存储几何数据指针 // 检查几何数据是否有效,且类型是否匹配 if(!m_pDataWell || m_pDataWell->getWellType() != NM_WELL_MODEL::Vertical_Fractured_Well) { return; // 不符合类型则不绘制 } nmDataVerticalFracturedWell *pVerticalFracturedWell = dynamic_cast(m_pDataWell); if(pVerticalFracturedWell == nullptr) { return; } // 1. 绘制井筒点 (绿色圆点) nmWellborePointGraphicsItem* pWellPoint = new nmWellborePointGraphicsItem(this); // 创建井点图元,并将其父项设为当前组合图元 //double dRadius = pVerticalWell->getRadius().getValue().toDouble(); // 井点可视化的大小(半径) double dRadius = 7; double dX = pVerticalFracturedWell->getX().getValue().toDouble(); // 井口横坐标 double dY = pVerticalFracturedWell->getY().getValue().toDouble(); // 井口纵坐标 // 设置图元位置到场景坐标系的(dX,dY) pWellPoint->setRect(-dRadius, -dRadius, dRadius * 2, dRadius * 2); // 矩形中心在 (0,0) pWellPoint->setPos(dX, dY); // 将图元移动到场景中的 (dX, dY) pWellPoint->setFlag(QGraphicsItem::ItemIsMovable, false); // 初始不可移动 // 设置Z值,确保在上面 pWellPoint->setZValue(1); addPoint(pWellPoint); // 添加到组合图元中并记录 // 2. 绘制裂缝 m_pFracture = new nmFractureLineGraphicsItem(this); // 将裂缝图元的局部原点 (0,0) 设置到井筒在父项 (nmVerticalFracturedWellTopViewItem) 中的位置 m_pFracture->setPos(dX, dY); // 获取裂缝位置信息 QVector vecFractures = pVerticalFracturedWell->getFracs(); // 确保有且仅有两个点来构成一条线 if(vecFractures.size() == 2) { QPointF startPointScene = vecFractures.at(0); // 裂缝起点 (场景坐标) QPointF endPointScene = vecFractures.at(1); // 裂缝终点 (场景坐标) // 裂缝图元的局部原点现在是 (dX, dY),所以需要减去这个偏移量来获得其在 pFracture 内部的相对坐标 QPointF startPointLocal = startPointScene - QPointF(dX, dY); QPointF endPointLocal = endPointScene - QPointF(dX, dY); m_pFracture->setLine(QLineF(startPointLocal, endPointLocal)); // 设置裂缝线,它接受局部坐标 } else { // 如果数据不符合预期,则不绘制裂缝并清理创建的图元 delete m_pFracture; return; } // 设置 Z 值,确保在井点之上 m_pFracture->setZValue(pWellPoint->zValue() + 0.5); // 确保在井点之上,可以稍微高一点 addFractureLine(m_pFracture); // 添加裂缝图元 // 转发裂缝几何位置变化后的位置 connect(m_pFracture, SIGNAL(sigFractureGeometryChanged(double, double)), this, SIGNAL(sigFracturePosChanged(double, double))); update(); // 请求重绘 } void nmVerticalFracturedWellTopViewItem::setEditMode(bool enabled) { m_pFracture->setEditMode(enabled); } // --- nmVerticalFracturedWellCrossSectionItem 实现 --- nmVerticalFracturedWellCrossSectionItem::nmVerticalFracturedWellCrossSectionItem(QGraphicsItem *parent) : nmAbstractWellboreVisual(parent), m_pRectFracture(nullptr) { } void nmVerticalFracturedWellCrossSectionItem::updateGeometryVisuals(nmDataWellBase *pDataWell) { prepareGeometryChange(); clearAllChildItems(); // 清除所有已有的子项 m_pDataWell = pDataWell; // 存储几何数据指针 // 检查几何数据是否有效,且类型是否匹配 if(!m_pDataWell || m_pDataWell->getWellType() != NM_WELL_MODEL::Vertical_Fractured_Well) { // 如果类型不匹配或数据无效,清空所有子项和映射 for(auto it = m_mapPerforationItem.begin(); it != m_mapPerforationItem.end(); ++it) { scene()->removeItem(it.value()); // 从场景中移除 (如果 item 尚未被 clearAllChildItems 清除) delete it.value(); // 释放内存 } m_mapPerforationItem.clear(); // 清空映射 m_listPerforations.clear(); return; } nmDataVerticalFracturedWell *pVFWell = dynamic_cast(m_pDataWell); if(!pVFWell) { return; } // 在纵截面视图中,X轴通常代表左右延伸,Y轴代表深度(向下为正) double dWellboreX = pVFWell->getX().getValue().toDouble(); // 获取井底深 double dBottomholeMD = pVFWell->getBottomholeMD().getValue().toDouble(); // 获取井身长度 double dWellLength = pVFWell->getWellLength().getValue().toDouble(); // 1. 绘制储层矩形 (nmReservoirGraphicsItems) - 最底层 (Z值: -1000) nmReservoirGraphicsItems* pReservoirs = new nmReservoirGraphicsItems(this); // 创建实例,Z值在构造函数中已设置 addToGroup(pReservoirs); // 直接添加到组 // 2. 绘制裂缝矩形 m_pRectFracture = new nmFractureRectGraphicsItem(pVFWell, this); addFractureRect(m_pRectFracture); // 连接裂缝编辑时,裂缝拖动完成后的信号 connect(m_pRectFracture, SIGNAL(sigRequestFractureRectUpdate(qreal, qreal)), this, SLOT(slotFractureDragFinished(qreal, qreal))); // 连接拖动过程中的实时预览信号 connect(m_pRectFracture, SIGNAL(sigFractureGeometryChanged(qreal, qreal)), this, SLOT(slotFractureConstraintChanged(qreal, qreal))); // 3. 绘制井筒线 (nmWellboreLineGraphicsItem) - 中间层 (Z值: 0) // 井筒的总深度 nmWellboreLineGraphicsItem* pWellboreLine = new nmWellboreLineGraphicsItem(this); pWellboreLine->setLine(dWellboreX, dBottomholeMD, dWellboreX, dBottomholeMD + dWellLength); // 垂直线 //pWellboreLine->setStyle(QPen(QColor("#d3d3d3"), 2)); QPen wellborePen(QColor("#d3d3d3"), 2); wellborePen.setCosmetic(true); pWellboreLine->setStyle(wellborePen); pWellboreLine->setZValue(2); // 设置Z值为2,确保在射孔段之上 addLine(pWellboreLine); // 添加到组中 updatePerforationChildGraphics(); update(); // 请求重绘 } void nmVerticalFracturedWellCrossSectionItem::setFractureEditMode(bool enabled) { m_pRectFracture->setEditMode(enabled); } void nmVerticalFracturedWellCrossSectionItem::setPerforationEditMode(bool enabled) { // 遍历射孔段数组,设置为编辑状态 foreach(nmPerforationGraphicsItem* item, m_listPerforations) { item->setEditMode(enabled); } } void nmVerticalFracturedWellCrossSectionItem::slotFractureConstraintChanged(qreal fractureTopMD, qreal fractureBottomMD) { // 这个槽函数只用于实时预览 // 遍历所有射孔图元,从后向前遍历以便安全删除 for(int i = m_listPerforations.size() - 1; i >= 0; --i) { nmPerforationGraphicsItem* pPerforationItem = m_listPerforations.at(i); // 获取射孔图元关联的原始数据 // 注意:这里读取的是原始数据,不是裁剪后的数据 nmDataPerforation* pPerfData = pPerforationItem->getPerforationData(); double dPerfStartMD = pPerfData->getMdStart().getValue().toDouble(); double dPerfEndMD = pPerfData->getMdEnd().getValue().toDouble(); // 检查是否需要删除图元(如果射孔的原始范围完全在裂缝之外) if(dPerfEndMD <= fractureTopMD || dPerfStartMD >= fractureBottomMD) { // 从场景和父组中移除图元 removeFromGroup(pPerforationItem); scene()->removeItem(pPerforationItem); // 从列表中移除并删除 m_listPerforations.removeAt(i); delete pPerforationItem; } else { // 如果图元还在裂缝范围内,调用预览函数进行渲染 pPerforationItem->updateGraphicsForPreview(fractureTopMD, fractureBottomMD); } } // 请求场景重绘,以显示最新的预览效果 scene()->update(); } void nmVerticalFracturedWellCrossSectionItem::slotFractureDragFinished(qreal fractureTopMD, qreal fractureBottomMD) { QMap> updatedPerforations; // 遍历所有射孔图元,将它们被裁剪后的MD值收集起来 foreach(nmPerforationGraphicsItem* pPerforationItem, m_listPerforations) { double croppedStart = pPerforationItem->getCroppedMdStart(); double croppedEnd = pPerforationItem->getCroppedMdEnd(); // 存入映射,用于传递给控制器 updatedPerforations.insert(pPerforationItem->getPerforationData(), QPair(croppedStart, croppedEnd)); } // 1. 向控制器发送信号,传递所有被修改的射孔数据 emit sigFractureDragFinished(updatedPerforations); // 2. 发出更新裂缝的信号 emit sigRequestFractureRectUpdate(fractureTopMD, fractureBottomMD); } void nmVerticalFracturedWellCrossSectionItem::updatePerforationChildGraphics() { if(!m_pDataWell || m_pDataWell->getWellType() != NM_WELL_MODEL::Vertical_Fractured_Well) { // 如果数据无效,清空所有射孔图形项 for(auto it = m_mapPerforationItem.begin(); it != m_mapPerforationItem.end(); ++it) { // 从场景和父组中移除 removeFromGroup(it.value()); scene()->removeItem(it.value()); delete it.value(); } m_mapPerforationItem.clear(); m_listPerforations.clear(); return; } nmDataVerticalFracturedWell *pVFWell = dynamic_cast(m_pDataWell); if(!pVFWell) return; QVector& vecCurrentPerforations = pVFWell->getPerforations(); // 1: 找出需要删除的图形项 (数据模型中已不存在的) QList listPerforationsToRemoveFromMap; for(auto it = m_mapPerforationItem.begin(); it != m_mapPerforationItem.end(); ++it) { if(!vecCurrentPerforations.contains(it.key())) { listPerforationsToRemoveFromMap.append(it.key()); } } foreach(nmDataPerforation* pPerfData, listPerforationsToRemoveFromMap) { nmPerforationGraphicsItem* item = m_mapPerforationItem.take(pPerfData); if(item) { removeFromGroup(item); // 从父组中移除 scene()->removeItem(item); // 从场景中移除 m_listPerforations.removeOne(item); delete item; // 释放内存 } } double dWellboreX = pVFWell->getX().getValue().toDouble(); // 井口X坐标 // 获取井筒的MD信息 double dWellheadMD = pVFWell->getBottomholeMD().getValue().toDouble(); // 假设有此属性 double dWellLength = pVFWell->getWellLength().getValue().toDouble(); double dWellboreEndMD = dWellheadMD + dWellLength; // 井筒的MD起始值和总MD长度 (用于nmPerforationGraphicsItem) double wellboreStartAbsoluteMd = dWellheadMD; double totalWellboreMdLength = dWellLength; // 井筒的视觉起点和终点 QPointF wellboreStartScene = QPointF(dWellboreX, dWellheadMD); QPointF wellboreEndScene = QPointF(dWellboreX, dWellboreEndMD); // 2: 添加新图形项 foreach(nmDataPerforation* pPerfData, vecCurrentPerforations) { nmPerforationGraphicsItem* pPerforationItem = nullptr; // 如果图形项不存在,则创建新的 if(!m_mapPerforationItem.contains(pPerfData)) { pPerforationItem = new nmPerforationGraphicsItem( pPerfData, NM_WELL_MODEL::Vertical_Well, // 传递井类型 (此处仍然是Vertical_Well,因为是纵截面) wellboreStartAbsoluteMd, // 传递井筒的MD起始值 totalWellboreMdLength, // 传递井筒的总MD长度 this ); pPerforationItem->setZValue(1); // 设置Z值为1,确保在井筒(Z=2)之下 addPerforation(pPerforationItem); // 添加到父组中 m_mapPerforationItem.insert(pPerfData, pPerforationItem); // 添加到映射中 // 连接射孔拖动信号 connect(pPerforationItem, SIGNAL(sigRequestPerforationMdUpdate(nmDataPerforation*, double, double)), this, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double))); } // 无论新建还是现有,都更新其视觉信息,确保实时刷新 if(pPerforationItem) { pPerforationItem->updateWellboreVisuals(wellboreStartScene, wellboreEndScene, wellboreStartAbsoluteMd, totalWellboreMdLength); } } // 请求场景重绘,只重绘受影响的区域 scene()->update(); } // nmWellboreEndHandleGraphicsItem 类的实现 nmWellboreEndHandleGraphicsItem::nmWellboreEndHandleGraphicsItem(QGraphicsItem *parent) : QGraphicsRectItem(parent) { //// 设置手柄的默认样式 //QPen pen(Qt::black); // 边框为黑色 //QBrush brush(Qt::red); // 填充为红色 //setPen(pen); //setBrush(brush); QPen pen(Qt::black, 0); pen.setCosmetic(true); QBrush brush(Qt::red); setPen(pen); setBrush(brush); } void nmWellboreEndHandleGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if(event->button() == Qt::LeftButton) { m_lastMouseScenePos = event->scenePos(); // 记录鼠标按下时的场景坐标 } } void nmWellboreEndHandleGraphicsItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if(event->buttons() & Qt::LeftButton) { // 如果左键被按下并拖动 QPointF delta = event->scenePos() - m_lastMouseScenePos; // 计算鼠标移动的增量 moveBy(delta.x(), delta.y()); // 移动手柄图元本身 m_lastMouseScenePos = event->scenePos(); // 更新上次鼠标位置 emit handlePositionChanged(event->scenePos(), true); // 发出信号,通知新的场景位置 } QGraphicsRectItem::mouseMoveEvent(event); // 调用基类的处理 } void nmWellboreEndHandleGraphicsItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QGraphicsRectItem::mouseReleaseEvent(event); // 调用基类的处理 emit handlePositionChanged(scenePos(), false); // 鼠标释放时,再次发送最终的场景位置,确保精确 } #include "nmDataHorizontalFracturedWell.h" // --- nmHorizontalFracturedWellTopViewItem 实现 --- nmHorizontalFracturedWellTopViewItem::nmHorizontalFracturedWellTopViewItem(QGraphicsItem *parent) : nmAbstractWellboreVisual(parent), m_pWellboreLine(nullptr), m_pEndHandle(nullptr), m_bEditMode(false) { } void nmHorizontalFracturedWellTopViewItem::updateGeometryVisuals(nmDataWellBase *pDataWell) { prepareGeometryChange(); // 通知QGraphicsView,此项的几何形状可能发生变化 clearAllChildItems(); // 清除所有已有的子项 // 清空 m_listFractures qDeleteAll(m_listFractures); m_listFractures.clear(); m_pWellboreLine = nullptr; m_pEndHandle = nullptr; m_pDataWell = pDataWell; // 存储几何数据指针 // 检查几何数据是否有效,且类型是否匹配 (垂直井或垂直压裂井在俯视图中显示类似) if(!m_pDataWell || m_pDataWell->getWellType() != NM_WELL_MODEL::Horizontal_Fractured_Well) { return; // 不符合类型则不绘制 } nmDataHorizontalFracturedWell *pHFWell = dynamic_cast(m_pDataWell); if(pHFWell == nullptr) { return; } // 1. 绘制井筒点 (绿色圆点) nmWellborePointGraphicsItem* pWellPoint = new nmWellborePointGraphicsItem(this); // 创建井点图元,并将其父项设为当前组合图元 double dRadius = 7; double dX = pHFWell->getX().getValue().toDouble(); // 井口横坐标 double dY = pHFWell->getY().getValue().toDouble(); // 井口纵坐标 // 设置图元位置到场景坐标系的(dX,dY) pWellPoint->setRect(-dRadius, -dRadius, dRadius * 2, dRadius * 2); // 矩形中心在 (0,0) pWellPoint->setPos(dX, dY); // 将图元移动到场景中的 (dX, dY) pWellPoint->setFlag(QGraphicsItem::ItemIsMovable, false); // 初始不可移动 // 设置Z值,确保在上面 pWellPoint->setZValue(1); addPoint(pWellPoint); // 添加到组合图元中并记录 // 2. 绘制井筒(俯视图下) // 获取几何信息 // 井身长度 double dWellLength = pHFWell->getWellLength().getValue().toDouble(); // 与水平方向的夹角(直角坐标系) double dDarinAngle = pHFWell->getDrainAngle().getValue().toDouble(); // 将角度从度转换为弧度,因为 Qt 的三角函数通常接受弧度 double dDarinAngleRad = dDarinAngle * M_PI / 180.0; // 计算井筒的终点坐标 // endX = 起始X + 长度 * cos(角度) // endY = 起始Y + 长度 * sin(角度) double endX = dX + dWellLength * qCos(dDarinAngleRad); double endY = dY + dWellLength * qSin(dDarinAngleRad); // 创建井筒线段 m_pWellboreLine = new nmWellboreLineGraphicsItem(this); QPen linePen; linePen.setColor(QColor("#32cd32")); // 设置线段颜色 linePen.setWidth(5); // 设置线宽 linePen.setCosmetic(true); m_pWellboreLine->setStyle(linePen); // 应用样式 m_pWellboreLine->setLine(dX, dY, endX, endY); // 设置线段的起始点和终点 m_pWellboreLine->setZValue(2); addLine(m_pWellboreLine); // 添加到组合图元中并记录 // 创建井筒末端矩形手柄 m_pEndHandle = new nmWellboreEndHandleGraphicsItem(this); // 设置手柄的矩形区域 m_pEndHandle->setRect(-5, -5, 10, 10); // 将手柄的中心设置在井筒线的终点 m_pEndHandle->setPos(endX, endY); m_pEndHandle->setVisible(m_bEditMode); m_pEndHandle->setZValue(5); // 连接手柄的 handlePositionChanged 信号到当前类的 onEndHandleMoved 槽函数 connect(m_pEndHandle, SIGNAL(handlePositionChanged(QPointF, bool)), this, SLOT(onEndHandleMoved(QPointF, bool))); // 3. 绘制射孔段 QVector& vecCurrentPerforations = pHFWell->getPerforations(); // 井筒的MD起始值(多段压裂水平井默认是第一条射孔起点)和总MD长度 double wellboreStartAbsoluteMd = vecCurrentPerforations[0]->getMdStart().getValue().toDouble(); double totalWellboreMdLength = dWellLength; // 井筒的视觉起点和终点 QPointF wellboreStartScene = QPointF(dX, dY); QPointF wellboreEndScene = QPointF(endX, endY); foreach(nmDataPerforation* pPerfData, vecCurrentPerforations) { nmPerforationGraphicsItem* pPerforationItem = nullptr; // 构造函数传入最新的MD信息 pPerforationItem = new nmPerforationGraphicsItem( pPerfData, NM_WELL_MODEL::Horizontal_Fractured_Well, // 传递井类型 wellboreStartAbsoluteMd, // 传递井筒的MD起始值 totalWellboreMdLength, // 传递井筒的总MD长度 this ); pPerforationItem->setZValue(3); addPerforation(pPerforationItem); // 添加到父组中 // 初始化射孔段的视觉信息 pPerforationItem->updateWellboreVisuals(wellboreStartScene, wellboreEndScene, wellboreStartAbsoluteMd, totalWellboreMdLength); // 连接射孔拖动信号 connect(pPerforationItem, SIGNAL(sigRequestPerforationMdUpdate(nmDataPerforation*, double, double)), this, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double))); } // 4. 绘制裂缝 (使用新的 nmHorizontalFractureGraphicsItem) // 强制数据模型更新裂缝点,确保其是基于当前井筒几何的最新状态 QVector> vecFracturePoint = pHFWell->getFracs(); int fractureIndex = 0; // 用于给裂缝分配索引 for(QVector>::const_iterator it = vecFracturePoint.constBegin(); it != vecFracturePoint.constEnd(); ++it) { const QPair& fracPair = *it; // 获取当前元素 nmHorizontalFractureGraphicsItem* pFractureItem = new nmHorizontalFractureGraphicsItem(fractureIndex, this); pFractureItem->updateFractureLineAndHandles(fracPair.first, fracPair.second); // 传递井筒场景信息和方向,用于裂缝内部的拖动约束 pFractureItem->setWellboreInfo(wellboreStartScene, wellboreEndScene); pFractureItem->setEditMode(m_bEditMode); connect(pFractureItem, SIGNAL(fractureHalfLengthChanged(double, bool, int)), this, SLOT(onSingleFractureHalfLengthChanged(double, bool, int))); // !!!将裂缝图元添加到本类自己的列表中 m_listFractures.append(pFractureItem); pFractureItem->setZValue(1.5); // 井点之上、井筒之下 fractureIndex++; } // 清空旧缓存 m_vecFractureMds.clear(); // 从数据模型获取MD值 m_vecFractureMds = pHFWell->getFractureMds(); update(); // 请求重绘 } void nmHorizontalFracturedWellTopViewItem::setEditMode(bool enabled) { m_bEditMode = enabled; // 更新内部状态 if(m_pEndHandle) { m_pEndHandle->setVisible(enabled); // 设置手柄的可见性 } // 设置裂缝的编辑状态 foreach(nmHorizontalFractureGraphicsItem* pFractureItem, m_listFractures) { if(pFractureItem) { pFractureItem->setEditMode(enabled); } } } void nmHorizontalFracturedWellTopViewItem::setPerforationEditMode(bool enabled) { // 遍历射孔段数组,设置为编辑状态 foreach(nmPerforationGraphicsItem* item, m_listPerforations) { item->setEditMode(enabled); } } void nmHorizontalFracturedWellTopViewItem::onEndHandleMoved(QPointF newScenePos, bool isDragging) { if(!m_bEditMode) { return; } if(!m_pDataWell) return; nmDataHorizontalFracturedWell *pHFWell = dynamic_cast(m_pDataWell); if(!pHFWell) return; prepareGeometryChange(); // 井筒起点是固定的井口坐标 QPointF wellboreStartScene(pHFWell->getX().getValue().toDouble(), pHFWell->getY().getValue().toDouble()); QPointF wellboreEndScene = newScenePos; // 实时终点 // 实时计算井身长度和方向 double currentWellLength = QLineF(wellboreStartScene, wellboreEndScene).length(); QVector2D wellboreDirection = QVector2D(wellboreEndScene - wellboreStartScene).normalized(); QPointF perpendicularDirection = QPointF(-wellboreDirection.y(), wellboreDirection.x()); // 1. 更新井筒线段的终点(实时) if(m_pWellboreLine) { m_pWellboreLine->setLine(wellboreStartScene.x(), wellboreStartScene.y(), wellboreEndScene.x(), wellboreEndScene.y()); } // 2. 实时更新所有射孔图元的视觉状态 // 从数据模型中获取旧的井筒MD数据作为计算比例的基准 double originalWellheadMd = pHFWell->getPerforations()[0]->getMdStart().getValue().toDouble(); double originalWellLength = pHFWell->getWellLength().getValue().toDouble(); foreach(nmPerforationGraphicsItem* pPerforationItem, m_listPerforations) { if(pPerforationItem) { // 调用专门用于实时预览的方法,它会根据旧MD比例和新长度计算位置 pPerforationItem->updateVisualsForWellboreDrag(wellboreStartScene, wellboreEndScene, originalWellheadMd, originalWellLength, currentWellLength); } } // 3. 实时更新所有裂缝图元 if(!m_vecFractureMds.empty()) { double halfLength = pHFWell->getFractureHalfLength().getValue().toDouble(); // 裂缝半长在拖动井身时是固定的 double fractureRelativeAngleRad = pHFWell->getFractureAngle().getValue().toDouble() * M_PI / 180.0; // 计算裂缝的绝对角度 qreal wellboreAngleRad = qAtan2(wellboreDirection.y(), wellboreDirection.x()); double fractureAbsoluteAngleRad = wellboreAngleRad + fractureRelativeAngleRad; // 计算裂缝端点的偏移量 double dx_frac = halfLength * cos(fractureAbsoluteAngleRad); double dy_frac = halfLength * sin(fractureAbsoluteAngleRad); // 这里的逻辑是保持裂缝在井筒上的“均匀分布” int fractureCount = m_vecFractureMds.size(); double intervalRatio = (fractureCount > 1) ? 1.0 / (fractureCount - 1) : 0.0; for(int i = 0; i < fractureCount; ++i) { nmHorizontalFractureGraphicsItem* pFractureItem = m_listFractures.at(i); if(pFractureItem) { // 根据新的总长度和均匀间隔比例,计算每个裂缝中心点在井筒上的位置 double currentPositionAlongWellbore = i * intervalRatio * currentWellLength; QPointF fractureCenterScene = wellboreStartScene + wellboreDirection.toPointF() * currentPositionAlongWellbore; QPointF p1_new_scene = QPointF(fractureCenterScene.x() - dx_frac, fractureCenterScene.y() - dy_frac); QPointF p2_new_scene = QPointF(fractureCenterScene.x() + dx_frac, fractureCenterScene.y() + dy_frac); pFractureItem->updateFractureLineAndHandles(p1_new_scene, p2_new_scene); pFractureItem->setWellboreInfo(wellboreStartScene, wellboreEndScene); // 更新井筒信息,供裂缝内部拖动使用 } } } // 4. 只有当拖动结束时,才发出信号给控制器 if(!isDragging) { emit sigWellboreDragFinished(newScenePos); // 更新缓存的裂缝MD,临时,可能后面会有时序问题 m_vecFractureMds.clear(); m_vecFractureMds = pHFWell->getFractureMds(); } } void nmHorizontalFracturedWellTopViewItem::onSingleFractureHalfLengthChanged(double newHalfLength, bool isDragging, int fractureIndex) { // 获取井筒的场景坐标信息 QPointF wellboreStartScene = m_pWellboreLine->line().p1(); QPointF wellboreEndScene = m_pWellboreLine->line().p2(); double wellboreVisualLength = QLineF(wellboreStartScene, wellboreEndScene).length(); QVector2D wellboreDirection = QVector2D(wellboreEndScene - wellboreStartScene).normalized(); // 获取井筒的总MD长度(这个值在拖动裂缝半长时是固定的) nmDataHorizontalFracturedWell *pHFWell = dynamic_cast(m_pDataWell); if(!pHFWell) { return; } double totalWellboreMdLength = pHFWell->getWellLength().getValue().toDouble(); // 计算垂直于井筒的方向向量 QPointF perpendicularDirection = QPointF(-wellboreDirection.y(), wellboreDirection.x()); // 拖动过程中,实时计算所有裂缝的视觉位置 if(isDragging) { // 遍历所有裂缝图元,使用缓存的MD值 for(int i = 0; i < m_listFractures.size() && i < m_vecFractureMds.size(); ++i) { nmHorizontalFractureGraphicsItem* pFractureItem = m_listFractures.at(i); if(pFractureItem) { // 根据缓存的MD值计算中心点 double fractureMd = m_vecFractureMds.at(i); QPointF fractureCenterScene = wellboreStartScene + wellboreDirection.toPointF() * (fractureMd / totalWellboreMdLength * wellboreVisualLength); // 计算新的裂缝端点 QPointF p1_new_scene = fractureCenterScene - perpendicularDirection * newHalfLength; QPointF p2_new_scene = fractureCenterScene + perpendicularDirection * newHalfLength; // 更新图元的视觉状态 pFractureItem->updateFractureLineAndHandles(p1_new_scene, p2_new_scene); } } } else { // 拖动结束时,发出信号给控制器 emit sigFractureHalfLengthChanged(newHalfLength); } } QLineF nmHorizontalFracturedWellTopViewItem::getWellboreLine() { return m_pWellboreLine->line(); } nmCrossSectionFractureItem::nmCrossSectionFractureItem(int fractureIndex, QGraphicsItem *parent) : QObject(nullptr), QGraphicsLineItem(parent), m_pTopHandle(nullptr), m_pBottomHandle(nullptr), m_bEditMode(false), m_fractureIndex(fractureIndex), m_currentFractureXPosition(0.0), m_currentFractureMidPointY(0.0), m_currentFractureHeight(0.0), m_minReservoirDepth(0.0), // 初始化 m_maxReservoirDepth(0.0), // 初始化 m_isDraggingBody(false) { //QPen pen(QColor("#0000ff"), 3); //setPen(pen); //setZValue(3); // 确保在井筒和储层之上 QPen pen(QColor("#0000ff"), 3); pen.setCosmetic(true); setPen(pen); setZValue(3); m_pTopHandle = new nmFractureLineHandleItem(this); m_pBottomHandle = new nmFractureLineHandleItem(this); connect(m_pTopHandle, SIGNAL(handlePositionChanged(nmFractureLineHandleItem*, QPointF, bool)), this, SLOT(onHandlePositionChanged(nmFractureLineHandleItem*, QPointF, bool))); connect(m_pBottomHandle, SIGNAL(handlePositionChanged(nmFractureLineHandleItem*, QPointF, bool)), this, SLOT(onHandlePositionChanged(nmFractureLineHandleItem*, QPointF, bool))); // 默认禁用所有不必要的视觉效果和交互 setEditMode(false); } nmCrossSectionFractureItem::~nmCrossSectionFractureItem() { m_pTopHandle = nullptr; m_pBottomHandle = nullptr; } // 更新裂缝的视觉表示,并传入储层深度限制 void nmCrossSectionFractureItem::updateFractureVisuals(double fractureXPosition, double midPointY, double fractureHeight, double minReservoirDepth, double maxReservoirDepth) { prepareGeometryChange(); // 准备几何形状变化通知 m_currentFractureXPosition = fractureXPosition; m_currentFractureMidPointY = midPointY; m_currentFractureHeight = fractureHeight; m_minReservoirDepth = minReservoirDepth; // 存储储层深度限制 m_maxReservoirDepth = maxReservoirDepth; // 存储储层深度限制 // 裂缝作为垂直线,X坐标固定,Y坐标从 (midY - halfHeight) 到 (midY + halfHeight) double halfHeight = fractureHeight / 2.0; QPointF p1_local(fractureXPosition, midPointY - halfHeight); // 顶部点 QPointF p2_local(fractureXPosition, midPointY + halfHeight); // 底部点 (假设Y轴向下为正) setLine(QLineF(p1_local, p2_local)); // 更新把手位置 if(m_pTopHandle) { m_pTopHandle->setPos(p1_local); } if(m_pBottomHandle) { m_pBottomHandle->setPos(p2_local); } } // 设置编辑模式,并禁用不必要的视觉效果 void nmCrossSectionFractureItem::setEditMode(bool enable) { m_bEditMode = enable; if(m_pTopHandle) { m_pTopHandle->setVisible(enable); m_pTopHandle->setEnabled(enable); // 启用/禁用把手交互 // 确保 nmFractureLineHandleItem 也不修改鼠标样式 // 并在其 paint 方法中不绘制选中框 } if(m_pBottomHandle) { m_pBottomHandle->setVisible(enable); m_pBottomHandle->setEnabled(enable); // 确保 nmFractureLineHandleItem 也不修改鼠标样式 } // 禁用 ItemIsSelectable 以避免选中虚线框,因为我们不需要编辑线样式 setFlag(ItemIsSelectable, false); setFlag(ItemIsMovable, enable); // 启用/禁用图元整体拖动 // setFlag(ItemSendsGeometryChanges, false); // 不需要发送几何变化,因为我们手动处理 } // 槽函数:响应把手位置变化(主要影响高度) void nmCrossSectionFractureItem::onHandlePositionChanged(nmFractureLineHandleItem* handle, QPointF newHandlePosInParent, bool isDragging) { if(!m_bEditMode) { return; } double currentTopY = line().p1().y(); double currentBottomY = line().p2().y(); double desiredNewHandleY = newHandlePosInParent.y(); double newHeight; double newMidPointY = m_currentFractureMidPointY; if(handle == m_pTopHandle) { // 拖动顶部把手 // 1. 限制不能穿过底部把手:新的顶部Y不能大于当前底部Y double maxAllowedTopY = currentBottomY; // 2. 限制不能超过储层最小深度:新的顶部Y不能小于 m_minReservoirDepth double minAllowedTopY = m_minReservoirDepth; // 综合限制 double actualNewTopY = qBound(minAllowedTopY, desiredNewHandleY, maxAllowedTopY); newHeight = currentBottomY - actualNewTopY; newMidPointY = currentBottomY - (newHeight / 2.0); // 重新计算中点Y } else { // handle == m_pBottomHandle // 拖动底部把手 // 1. 限制不能穿过顶部把手:新的底部Y不能小于当前顶部Y double minAllowedBottomY = currentTopY; // 2. 限制不能超过储层最大深度:新的底部Y不能大于 m_maxReservoirDepth double maxAllowedBottomY = m_maxReservoirDepth; // 综合限制 double actualNewBottomY = qBound(minAllowedBottomY, desiredNewHandleY, maxAllowedBottomY); newHeight = actualNewBottomY - currentTopY; newMidPointY = currentTopY + (newHeight / 2.0); // 重新计算中点Y } // 确保高度非负,尽管前面的 qBound 应该已经处理了 newHeight = qMax(0.0, newHeight); // 更新视觉,使用新的高度和计算出的新中点Y,并传入储层限制 updateFractureVisuals(m_currentFractureXPosition, newMidPointY, newHeight, m_minReservoirDepth, m_maxReservoirDepth); // 发出信号通知父图元 emit fractureGeometryChanged(newHeight, newMidPointY, isDragging, m_fractureIndex); } // 鼠标按下事件:处理整体拖动 void nmCrossSectionFractureItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { if(m_bEditMode && event->button() == Qt::LeftButton) { // 检查鼠标是否在把手上。如果把手接受了事件,则不处理整体拖动。 // 这里需要更精确的判断,因为 QGraphicsItem 默认会把事件传递给子项。 // 如果 event->lastAcceptedItem() 是把手,则不处理。 // 为了简化,我们只检查 event->pos() 是否在把手范围内。 if(!m_pTopHandle->contains(event->pos()) && !m_pBottomHandle->contains(event->pos())) { m_lastMousePos = event->pos(); // 存储在图元局部坐标系中的位置 m_isDraggingBody = true; // 不修改鼠标样式 // 立即发出信号,表示开始拖动 emit fractureGeometryChanged(m_currentFractureHeight, m_currentFractureMidPointY, true, m_fractureIndex); event->accept(); // 接受事件,阻止传递给父级或下层 return; // 提前返回,不调用基类 } } QGraphicsLineItem::mousePressEvent(event); // 传递给基类,处理把手或其他默认行为 } // 鼠标移动事件:处理整体拖动 void nmCrossSectionFractureItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if(m_bEditMode && m_isDraggingBody) { qreal dy = event->pos().y() - m_lastMousePos.y(); // 局部坐标系中的垂直位移 // 计算期望的新中点Y double desiredNewMidPointY = m_currentFractureMidPointY + dy; // 计算期望的裂缝顶部和底部Y坐标 double desiredNewTopY = desiredNewMidPointY - m_currentFractureHeight / 2.0; double desiredNewBottomY = desiredNewMidPointY + m_currentFractureHeight / 2.0; double actualNewMidPointY = desiredNewMidPointY; // 最终实际的中点Y // 限制顶部不能超过储层最小深度 if(desiredNewTopY < m_minReservoirDepth) { actualNewMidPointY = m_minReservoirDepth + m_currentFractureHeight / 2.0; } // 限制底部不能超过储层最大深度 if(desiredNewBottomY > m_maxReservoirDepth) { actualNewMidPointY = m_maxReservoirDepth - m_currentFractureHeight / 2.0; } // 如果同时超出上下限(这种情况通常不会发生,除非高度大于储层高度) // 优先根据顶部限制调整,或者根据哪个超出更多来调整 // 更新视觉,使用受限后的中点Y,高度保持不变 updateFractureVisuals(m_currentFractureXPosition, actualNewMidPointY, m_currentFractureHeight, m_minReservoirDepth, m_maxReservoirDepth); m_lastMousePos = event->pos(); // 更新上次鼠标位置 // 发出信号通知父图元中点Y已改变,并处于拖动中 emit fractureGeometryChanged(m_currentFractureHeight, actualNewMidPointY, true, m_fractureIndex); event->accept(); } else { QGraphicsLineItem::mouseMoveEvent(event); // 传递给基类 } } // 鼠标释放事件:结束整体拖动 void nmCrossSectionFractureItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if(m_bEditMode && m_isDraggingBody && event->button() == Qt::LeftButton) { m_isDraggingBody = false; // 发出信号通知父图元中点Y改变结束 emit fractureGeometryChanged(m_currentFractureHeight, m_currentFractureMidPointY, false, m_fractureIndex); event->accept(); } else { QGraphicsLineItem::mouseReleaseEvent(event); // 传递给基类 } } nmHorizontalFracturedWellCrossSectionItem::nmHorizontalFracturedWellCrossSectionItem(QGraphicsItem *parent) : nmAbstractWellboreVisual(parent), m_pHFWellData(nullptr), m_listCrossSectionFractures(), m_pWellboreLine(nullptr), m_pReservoirsItem(nullptr), // 初始化新增的储层图元指针 m_bEditMode(false) { // 可以在这里设置一些默认的样式或初始化成员变量 } void nmHorizontalFracturedWellCrossSectionItem::updateGeometryVisuals(nmDataWellBase *pDataWell) { prepareGeometryChange(); clearAllChildItems(); // 清除所有已有的子项 // 清除内部管理的裂缝列表和井筒/储层指针 qDeleteAll(m_listCrossSectionFractures); m_listCrossSectionFractures.clear(); m_pWellboreLine = nullptr; m_pReservoirsItem = nullptr; // 清空指针 m_pDataWell = pDataWell; // 存储几何数据指针 // 检查几何数据是否有效,且类型是否匹配 if(!m_pDataWell || m_pDataWell->getWellType() != NM_WELL_MODEL::Horizontal_Fractured_Well) { qWarning() << "Invalid data well type or null data for Horizontal Fractured Well."; return; } nmDataHorizontalFracturedWell *pHFWell = dynamic_cast(m_pDataWell); if(!pHFWell) { qWarning() << "Failed to dynamic_cast to nmDataHorizontalFracturedWell."; return; } m_pHFWellData = pHFWell; // 存储强类型指针 // --- 坐标系统:X轴为井筒水平延伸,Y轴为测深 (MD),向下为正 --- // 获取井筒数据 double dWellboreX = m_pHFWellData->getX().getValue().toDouble(); // 井筒在视图X轴的起点 double dWellLength = m_pHFWellData->getWellLength().getValue().toDouble(); // 井身长度 // 获取井筒的固定Y坐标(深度),即第一个射孔段的测深起点 // 确保 pHFWell->getPerforations() 不为空,且索引0有效 double dWellboreFixedDepthY = 0.0; if(!m_pHFWellData->getPerforations().isEmpty() && m_pHFWellData->getPerforations()[0]) { double dWellboreFixedDepth = m_pHFWellData->getFractureHeight().getValue().toDouble(); dWellboreFixedDepthY = dWellboreFixedDepth/2; } else { // 设定一个默认值以避免崩溃 dWellboreFixedDepthY = 6150.0; // 或者一个合理的默认深度 } // 1. 绘制储层矩形 (nmReservoirGraphicsItems) - 最底层 m_pReservoirsItem = new nmReservoirGraphicsItems(this); // 创建实例 addToGroup(m_pReservoirsItem); // 直接添加到组 // 获取储层的最小和最大深度,传递给裂缝图元 qreal minReservoirDepth = m_pReservoirsItem->getMinLayerTop(); qreal maxReservoirDepth = m_pReservoirsItem->getMaxLayerBottom(); // 2. 绘制水平井筒线 (nmWellboreLineGraphicsItem) m_pWellboreLine = new nmWellboreLineGraphicsItem(this); m_pWellboreLine->setLine(dWellboreX, minReservoirDepth + dWellboreFixedDepthY, dWellboreX + dWellLength, minReservoirDepth + dWellboreFixedDepthY); QPen wellborePen(QColor("#d3d3d3"), 0); wellborePen.setCosmetic(true); m_pWellboreLine->setStyle(wellborePen); m_pWellboreLine->setZValue(2); // 设置Z值为2,确保在射孔段之上 addLine(m_pWellboreLine); // 添加到组中 // 3. 绘制裂缝 //m_pHFWellData->setFracs(); // 确保数据模型中裂缝信息是最新的 int dFNum = m_pHFWellData->getNumberOfFractures().getValue().toInt(); double dFHeight = m_pHFWellData->getFractureHeight().getValue().toDouble(); double dFMidPointHeightFromBottom = m_pHFWellData->getFractureMidPointHeight().getValue().toDouble(); // 计算裂缝中心点的Y坐标(绝对深度) double fractureCenterY = maxReservoirDepth - dFMidPointHeightFromBottom; QVector fractureMDs = m_pHFWellData->getFractureMds(); int numToDraw = qMin(dFNum, fractureMDs.size()); for(int i = 0; i < numToDraw; ++i) { double fractureMd = fractureMDs.at(i); nmCrossSectionFractureItem* pFractureItem = new nmCrossSectionFractureItem(i, this); // 更新裂缝视觉,并传递储层深度限制 pFractureItem->updateFractureVisuals(fractureMd, fractureCenterY, dFHeight, minReservoirDepth, maxReservoirDepth); pFractureItem->setEditMode(m_bEditMode); connect(pFractureItem, SIGNAL(fractureGeometryChanged(double, double, bool, int)), this, SLOT(onSingleFractureGeometryChanged(double, double, bool, int))); m_listCrossSectionFractures.append(pFractureItem); } update(); // 请求重绘 } // 设置编辑模式 void nmHorizontalFracturedWellCrossSectionItem::setEditMode(bool enabled) { m_bEditMode = enabled; // 控制所有裂缝图元的编辑模式 foreach(nmCrossSectionFractureItem* pFractureItem, m_listCrossSectionFractures) { if(pFractureItem) { pFractureItem->setEditMode(enabled); } } // TODO: 如果井筒线或其他图元也需要编辑模式,在这里进行控制 } void nmHorizontalFracturedWellCrossSectionItem::onSingleFractureGeometryChanged(double newHeight, double newMidPointY, bool isDragging, int fractureIndex) { if(!m_bEditMode || !m_pHFWellData || !m_pReservoirsItem) { return; } // 实时更新视觉,遍历所有裂缝并应用新的高度和中点位置 // 注意:这里的逻辑需要考虑所有裂缝是否同步更新 // 如果所有裂缝的高度和中点都应该相同,那么这里就应该遍历所有裂缝并调用 updateFractureVisuals qreal minReservoirDepth = m_pReservoirsItem->getMinLayerTop(); qreal maxReservoirDepth = m_pReservoirsItem->getMaxLayerBottom(); QVector fractureMDs = m_pHFWellData->getFractureMds(); for(int i = 0; i < m_listCrossSectionFractures.size() && i < fractureMDs.size(); ++i) { nmCrossSectionFractureItem* pFractureItem = m_listCrossSectionFractures.at(i); if(pFractureItem) { pFractureItem->updateFractureVisuals(fractureMDs.at(i), newMidPointY, newHeight, minReservoirDepth, maxReservoirDepth); } } // 拖动结束时,发出信号给控制器,传递所有更新后的数据 if(!isDragging) { emit sigFractureGeometryChanged(newHeight, newMidPointY); } } // nmAbstractWellboreView 构造函数 nmAbstractWellboreView::nmAbstractWellboreView(QWidget *parent) : QGraphicsView(parent), m_scene(nullptr), m_currentWellboreVisual(nullptr) { setupScene(); // 初始化场景 setRenderHint(QPainter::Antialiasing); // 启用抗锯齿渲染,使图形更平滑 //setTransformationAnchor(AnchorUnderMouse); // 设置缩放和平移的锚点为鼠标位置 //setResizeAnchor(AnchorUnderMouse); // 设置调整大小时的锚点 //setDragMode(QGraphicsView::ScrollHandDrag); // 允许平移场景 //setDragMode(QGraphicsView::NoDrag); //setInteractive(true); // 确保启用交互 //setDragMode(QGraphicsView::ScrollHandDrag); // 允许平移场景 // 禁止拖拽场景 setDragMode(QGraphicsView::NoDrag); setInteractive(true); // 确保启用交互 setMouseTracking(true); // 启用鼠标跟踪,以便实时获取鼠标位置 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 关闭水平滚动条 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // 关闭垂直滚动条 // 设置鼠标滚轮缩放和锚点 setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // 缩放和平移的锚点设为鼠标位置 setResizeAnchor(QGraphicsView::AnchorUnderMouse); // 调整大小时的锚点 } // AbstractWellboreView 析构函数 nmAbstractWellboreView::~nmAbstractWellboreView() { // 场景的删除会处理其内部所有图形项的删除 delete m_scene; } void nmAbstractWellboreView::mouseMoveEvent(QMouseEvent *event) { // 将视图坐标(像素)转换为场景坐标 // 场景坐标已经包含了视图自身的变换 (如 Y 轴翻转) QPointF scenePos = mapToScene(event->pos()); emit mouseCoordinatesChanged(scenePos.x(), scenePos.y()); QGraphicsView::mouseMoveEvent(event); // 调用基类实现,确保拖拽等功能正常 } void nmAbstractWellboreView::enterEvent(QEvent *event) { emit mouseEnteredView(); // 鼠标进入视图区域时通知主窗口 QGraphicsView::enterEvent(event); } void nmAbstractWellboreView::leaveEvent(QEvent *event) { emit mouseLeftView(); // 鼠标离开视图区域时通知主窗口 QGraphicsView::leaveEvent(event); } void nmAbstractWellboreView::wheelEvent(QWheelEvent *event) { // 获取滚轮旋转的delta值 int nDelta = event->delta(); // 根据delta值判断缩放方向 if(nDelta > 0) { // 向上/向前滚动,放大 zoomIn(); } else { // 向下/向后滚动,缩小 zoomOut(); } //// 计算缩放因子 //qreal factor = (event->delta() > 0) ? 1.15 : 1.0 / 1.15; //// 执行缩放 //scale(factor, factor); //// 1. 获取鼠标在场景中的位置(这将是我们的缩放中心点) //// Qt4 中使用 event->pos() 获取鼠标在视图中的 QPoint (整数坐标) //const QPointF scenePos = mapToScene(event->pos()); //// 2. 计算缩放因子 //// Qt4 中使用 event->delta() 获取滚轮滚动的量。 //// 正值通常表示向上滚动(放大),负值表示向下滚动(缩小)。 //qreal scaleFactor = event->delta() > 0 ? 1.15 : 1.0 / 1.15; // 每次缩放的比例 //// 3. 构建新的变换矩阵 //QTransform newTransform; //// a. 将场景的原点平移到鼠标位置 (scenePos) //newTransform.translate(scenePos.x(), scenePos.y()); //// b. 在鼠标位置(临时原点)处执行缩放 //newTransform.scale(scaleFactor, scaleFactor); //// c. 将场景的原点再平移回来 //newTransform.translate(-scenePos.x(), -scenePos.y()); //// 4. 应用新的变换到视图的当前变换之上 //// 顺序很重要:newTransform * this->transform() 意味着先应用当前变换,再应用新的缩放平移 //setTransform(newTransform * this->transform()); // 5. 接受事件,防止事件继续传播 //event->accept(); //qreal s=0; //bool in = true; //bool out = true; //qreal scaleValue = 1.5; //if(event->delta() >= 0) //{ // scaleValue += 1; //} //if(event->delta() < 0) //{ // scaleValue -= 1; //} //if( scaleValue < -5 ) //{ // scaleValue = -5; // in = false; // return; //}else if( scaleValue > 5 ) //{ // scaleValue = 5; // out = false; // return; //} //if(in) //{ // s = qPow(1.01,event->delta()/10); //} //if(out) //{ // s = qPow(1/1.01,-event->delta()/10); //} //scale(s,s); } void nmAbstractWellboreView::zoomIn() { qreal qScaleStepFactor = 1.15; // 每次缩放的步长 scale(qScaleStepFactor, qScaleStepFactor); } void nmAbstractWellboreView::zoomOut() { qreal qScaleStepFactor = 1.15; // 每次缩放的步长 scale(1.0 / qScaleStepFactor, 1.0 / qScaleStepFactor); } // 初始化 QGraphicsScene void nmAbstractWellboreView::setupScene() { m_scene = new QGraphicsScene(this); // 创建新的场景 // 不需要设置场景大小范围,默认无限大 //m_scene->setSceneRect(-1000, -1000, 2000, 2000); // 设定场景范围 setScene(m_scene); // 将场景设置到视图上 } // 清理旧的井筒图元并准备场景以显示新的图元 void nmAbstractWellboreView::clearAndPrepareScene() { // 如果存在当前的井筒组合图元,则从场景中移除并删除它 if(m_currentWellboreVisual) { m_scene->removeItem(m_currentWellboreVisual); delete m_currentWellboreVisual; m_currentWellboreVisual = nullptr; } } // === nmWxWellboreTopView 的实现 === nmWxWellboreTopView::nmWxWellboreTopView(QWidget *parent) : nmAbstractWellboreView(parent) { m_bIsInAddPerforationMode = false; m_bIsInDeletePerforationMode = false; } // 更新俯视图 void nmWxWellboreTopView::updateView(nmDataWellBase* geometry) { // 1. 清理旧的图元,并根据新的geometry更新显示 clearAndPrepareScene(); // 清理旧的井筒图元 // 清空场景 m_scene->clear(); if(!geometry) { return; // 没有数据则返回 } m_currentWellboreVisual = createWellboreVisual(geometry->getWellType()); // 创建或获取可视化图元 if(m_currentWellboreVisual) { m_scene->addItem(m_currentWellboreVisual); // 添加到场景 m_currentWellboreVisual->updateGeometryVisuals(geometry); // 更新图元几何形状 // 仅当是 nmVerticalWellTopViewItem 类型时才连接其井筒点拖拽信号 if(nmVerticalWellTopViewItem * pVerticalWellTopView = dynamic_cast(m_currentWellboreVisual)) { // 连接 nmVerticalWellTopViewItem 的井筒点拖拽完成信号,转发给主视图 connect(pVerticalWellTopView, SIGNAL(sigWellborePointDragFinished(QPointF)), this, SIGNAL(sigWellborePointMovedInTopView(QPointF))); } // 仅当是 nmVerticalFracturedWellTopViewItem 类型时才连接其裂缝拖拽信号 if(nmVerticalFracturedWellTopViewItem * pVFWellTopView = dynamic_cast(m_currentWellboreVisual)) { // 连接 nmVerticalFracturedWellTopViewItem 的射孔拖拽完成信号,转发给主视图 connect(pVFWellTopView, SIGNAL(sigFracturePosChanged(double, double)), this, SIGNAL(sigFracturePosChangedInTopView(double, double))); } // 仅当是 nmHorizontalFracturedWellTopViewItem 类型时才连接其射孔拖拽信号 if(nmHorizontalFracturedWellTopViewItem * pHFWellTopView = dynamic_cast(m_currentWellboreVisual)) { // 连接 nmHorizontalFracturedWellTopViewItem 的射孔拖拽完成信号,转发给主视图 connect(pHFWellTopView, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double)), this, SIGNAL(sigPerforationDragInTopView(nmDataPerforation*, double, double))); // 连接 nmHorizontalFracturedWellTopViewItem 的裂缝半长拖拽完成信号,转发给主视图 connect(pHFWellTopView, SIGNAL(sigFractureHalfLengthChanged(double)), this, SIGNAL(sigFractureHalfLengthChanged(double))); // 连接 nmHorizontalFracturedWellTopViewItem 的井身拖拽完成信号,转发给主视图 connect(pHFWellTopView, SIGNAL(sigWellboreDragFinished(QPointF)), this, SIGNAL(sigWellboreDragFinished(QPointF))); } // 获取井口坐标 double dX = geometry->getX().getValue().toDouble(); double dY = geometry->getY().getValue().toDouble(); // 定义场景的可见区域大小(根据视图尺寸动态调整) qreal viewWidth = this->viewport()->width(); qreal viewHeight = this->viewport()->height(); qreal sceneMargin = 1000.0; // 边距 // 计算场景矩形(以井口为中心,范围略大于视图尺寸) QRectF sceneRect3( dX - viewWidth / 2.0 - sceneMargin, // 左边界 dY - viewHeight / 2.0 - sceneMargin, // 上边界 viewWidth + 2 * sceneMargin, // 宽度 viewHeight + 2 * sceneMargin // 高度 ); // TODO:后续再修改,先临时设置 m_scene->setSceneRect(sceneRect3); //// 2. 重置视图变换,让当前井处于视图中心 //resetViewTransform(); } } void nmWxWellboreTopView::setPerForationEditMode(bool enabled) { if(auto topViewItem = dynamic_cast(m_currentWellboreVisual)) { topViewItem->setPerforationEditMode(enabled); } } void nmWxWellboreTopView::setAddPerforationMode(bool enable) { m_bIsInAddPerforationMode = enable; } void nmWxWellboreTopView::setPerforationDeleteMode(bool enabled) { m_bIsInDeletePerforationMode = enabled; } #include void nmWxWellboreTopView::resetViewTransform() { resetTransform(); // 重置为单位矩阵,Y轴向上为正 // 应用 Y 轴翻转(向上为正) scale(1, -1); // 在所有视图变换设置完毕后,再进行中心化操作 if(m_currentWellboreVisual && m_currentWellboreVisual->getWellData()) { nmDataWellBase* pGeometry = m_currentWellboreVisual->getWellData(); // 如果是多段压裂水平井,则视图中心为井筒中心 if(pGeometry->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { nmDataHorizontalFracturedWell* pHFWell = dynamic_cast(pGeometry); if(pHFWell) { // 如果是多段压裂水平井 // 获取井的起点 double dStartX = pHFWell->getX().getValue().toDouble(); double dStartY = pHFWell->getY().getValue().toDouble(); // 获取井长和角度 double dWellLength = pHFWell->getWellLength().getValue().toDouble(); double dDrainAngleRad = pHFWell->getDrainAngle().getValue().toDouble() * M_PI / 180.0; // 计算井的终点 double dEndX = dStartX + dWellLength * qCos(dDrainAngleRad); double dEndY = dStartY + dWellLength * qSin(dDrainAngleRad); // 计算井筒的中点 double dCenterX = (dStartX + dEndX) / 2.0; double dCenterY = (dStartY + dEndY) / 2.0; centerOn(dCenterX, dCenterY); } } else { double dX = pGeometry->getX().getValue().toDouble(); double dY = pGeometry->getY().getValue().toDouble(); centerOn(dX, dY); } } else if(m_scene && !m_scene->items().isEmpty()) { centerOn(m_scene->itemsBoundingRect().center()); } else { centerOn(0, 0); } update(); } void nmWxWellboreTopView::setEditMode(bool enabled) { if(auto vTopViewItem = dynamic_cast(m_currentWellboreVisual)) { vTopViewItem->setEditMode(enabled); } else if(auto VFtopViewItem = dynamic_cast(m_currentWellboreVisual)) { VFtopViewItem->setEditMode(enabled); } else if(auto HFtopViewItem = dynamic_cast(m_currentWellboreVisual)) { HFtopViewItem->setEditMode(enabled); } } // 工厂方法:根据井类型创建适合俯视图的组合图元实例 nmAbstractWellboreVisual* nmWxWellboreTopView::createWellboreVisual(NM_WELL_MODEL type) { switch(type) { case Vertical_Well: return new nmVerticalWellTopViewItem(); case Vertical_Fractured_Well: return new nmVerticalFracturedWellTopViewItem(); case Horizontal_Fractured_Well: return new nmHorizontalFracturedWellTopViewItem(); default: return nullptr; } } void nmWxWellboreTopView::mousePressEvent(QMouseEvent *event) { // 添加射孔段模式处理 if(m_bIsInAddPerforationMode && event->button() == Qt::LeftButton) { nmHorizontalFracturedWellTopViewItem* pHFWellItem = dynamic_cast(m_currentWellboreVisual); if(pHFWellItem) { QPointF scenePos = mapToScene(event->pos()); // 将鼠标位置转换为场景坐标 double fractureTopMd = -1.0; double fractureBottomMd = -1.0; QLineF wellboreVisualLine = pHFWellItem->getWellboreLine(); // 发射信号,传递所有参数 emit sigRequestAddPerforation(scenePos, fractureTopMd, fractureBottomMd, wellboreVisualLine); } event->accept(); // 阻止事件进一步传播,确保只处理一次点击 return; } // 删除射孔模式的处理 if(m_bIsInDeletePerforationMode && event->button() == Qt::LeftButton) { QPointF scenePos = mapToScene(event->pos()); // 将鼠标位置转换为场景坐标 // 获取点击位置的所有图形项,默认按Z值从高到低排序 QList clickedItems = scene()->items(scenePos); nmPerforationGraphicsItem *perforationItem = nullptr; // 遍历所有点击到的项,找到第一个射孔段图元 foreach(QGraphicsItem *item, clickedItems) { perforationItem = dynamic_cast(item); if(perforationItem) { // 找到了射孔段图元,停止查找 break; } } if(perforationItem) { // 如果点击的是一个射孔段图形项 nmDataPerforation* pPerforationData = perforationItem->getPerforationData(); // 获取关联的射孔数据 if(pPerforationData) { // 直接发射信号,将删除请求发送给控制器 emit sigRequestDeletePerforation(pPerforationData); event->accept(); return; } } } nmAbstractWellboreView::mousePressEvent(event); // 调用基类的鼠标事件处理 } // === nmWxWellboreCrossSectionView 的实现 === nmWxWellboreCrossSectionView::nmWxWellboreCrossSectionView(QWidget *parent) : nmAbstractWellboreView(parent) { m_bIsInAddPerforationMode = false; m_bIsInDeletePerforationMode = false; m_isInitialFitDone = false; } void nmWxWellboreCrossSectionView::updateView(nmDataWellBase* geometry) { clearAndPrepareScene(); m_scene->clear(); m_currentWellboreVisual = createWellboreVisual(geometry->getWellType()); // 创建或获取可视化图元 if(m_currentWellboreVisual) { m_scene->addItem(m_currentWellboreVisual); // 添加到场景 m_currentWellboreVisual->updateGeometryVisuals(geometry); // 更新图元几何形状 // 仅当是 nmVerticalWellCrossSectionItem和nmVerticalFracturedWellCrossSectionItem 类型时才连接其井筒点拖拽信号 if(nmVerticalWellCrossSectionItem * pVWellCrossView = dynamic_cast(m_currentWellboreVisual)) { // 连接 nmVerticalWellCrossSectionItem 的射孔拖拽完成信号,转发给主视图 connect(pVWellCrossView, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double)), this, SIGNAL(sigPerforationDragInCrossView(nmDataPerforation*, double, double))); } else if(nmVerticalFracturedWellCrossSectionItem * pVFWellCrossView = dynamic_cast(m_currentWellboreVisual)) { // 连接 nmVerticalFracturedWellCrossSectionItem 的射孔拖拽完成信号,转发给主视图 connect(pVFWellCrossView, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double)), this, SIGNAL(sigPerforationDragInCrossView(nmDataPerforation*, double, double))); // 转发裂缝几何位置更新的信号 connect(pVFWellCrossView, SIGNAL(sigRequestFractureRectUpdate(qreal, qreal)), this, SIGNAL(sigRequestFractureRectUpdate(qreal, qreal))); // 射孔更新 connect(pVFWellCrossView, SIGNAL(sigFractureDragFinished(const QMap>&)), this, SIGNAL(sigFractureDragFinished(const QMap>&))); } else if(nmHorizontalFracturedWellCrossSectionItem * pHFWellCrossView = dynamic_cast(m_currentWellboreVisual)) { // 连接 nmHorizontalFracturedWellCrossSectionItem 的裂缝拖拽完成信号,转发给主视图 connect(pHFWellCrossView, SIGNAL(sigFractureGeometryChanged(double, double)), this, SIGNAL(sigFractureGeometryChanged(double, double))); } double dCenterX = geometry->getX().getValue().toDouble(); double dWellLength = geometry->getWellLength().getValue().toDouble(); double dBottomholeMD = geometry->getBottomholeMD().getValue().toDouble(); double dCenterY = dBottomholeMD + dWellLength / 2.0; // 多段压裂水平井的截面图是正常居中的,不需要额外设置 if (geometry->getWellType() ==Horizontal_Fractured_Well) { // 获取井的起点 double dStartX = geometry->getX().getValue().toDouble(); double dStartY = geometry->getPerforations()[0]->getMdStart().getValue().toDouble(); // 获取井长 double dWellLength = geometry->getWellLength().getValue().toDouble(); // 计算井的终点 double dEndX = dStartX + dWellLength; double dEndY = dStartY; // 计算井筒的中点 dCenterX = (dStartX + dEndX) / 2.0; dCenterY = (dStartY + dEndY) / 2.0; } // 定义场景的可见区域大小(根据视图尺寸动态调整) qreal viewWidth = this->viewport()->width(); qreal viewHeight = this->viewport()->height(); qreal sceneMargin = 1000.0; // 边距 // 计算场景矩形(以井口为中心,范围略大于视图尺寸) QRectF sceneRect3( dCenterX - viewWidth / 2.0 - sceneMargin, // 左边界 dCenterY - viewHeight / 2.0 - sceneMargin, // 上边界 viewWidth + 2 * sceneMargin, // 宽度 viewHeight + 2 * sceneMargin // 高度 ); // TODO:后续再修改,先临时设置 m_scene->setSceneRect(sceneRect3); m_isInitialFitDone = false; // 重新设置视图变换以适应新的数据 //resetViewTransform(); } } void nmWxWellboreCrossSectionView::resizeEvent(QResizeEvent *event) { // 调用基类的实现 QGraphicsView::resizeEvent(event); // 如果场景内容为空,或者初始缩放已经完成,则不做任何事 if (!m_scene || m_scene->items().isEmpty() || m_isInitialFitDone) { return; } // 调用缩放函数 resetViewTransform(); // 将标志位置为 true,防止后续的 resize 事件(如用户拖动窗口)再次触发 m_isInitialFitDone = true; } void nmWxWellboreCrossSectionView::setPerForationEditMode(bool enabled) { if(auto verticalCrossViewItem = dynamic_cast(m_currentWellboreVisual)) { verticalCrossViewItem->setPerforationEditMode(enabled); } else if(auto VerticalFCrossViewItem = dynamic_cast(m_currentWellboreVisual)) { VerticalFCrossViewItem->setPerforationEditMode(enabled); } } void nmWxWellboreCrossSectionView::setAddPerforationMode(bool enable) { m_bIsInAddPerforationMode = enable; } void nmWxWellboreCrossSectionView::setPerforationDeleteMode(bool enabled) { m_bIsInDeletePerforationMode = enabled; } void nmWxWellboreCrossSectionView::resetViewTransform() { // 1. 检查核心显示对象和场景是否有效 if (!m_currentWellboreVisual || !m_scene || m_scene->items().isEmpty()) { resetTransform(); centerOn(0, 0); return; } // 2. 从数据管理器获取全局的顶部和底部边界 const qreal minTop = nmDataAnalyzeManager::getCurrentInstance()->getMinLayerTop(); const qreal maxBottom = nmDataAnalyzeManager::getCurrentInstance()->getMaxLayerBottom(); qreal targetHeight = 0; QPointF viewCenter; // 3. 检查获取的全局边界是否有效 if (minTop < maxBottom) { // 3a. 计算目标高度为所有地层的总跨度 targetHeight = maxBottom - minTop; // 3b. 计算视图的中心点 // 水平中心 (CenterX): 保持不变,依然是井筒的中心X坐标 //const qreal centerX = m_currentWellboreVisual->boundingRect().center().x(); // 垂直中心 (CenterY): 现在是全局地层范围的中心Y坐标 //const qreal centerY = (minTop + maxBottom) / 2.0; nmDataWellBase* pGeometry = m_currentWellboreVisual->getWellData(); const qreal centerX = pGeometry->getX().getValue().toDouble(); // 井筒X坐标 const qreal centerY = (minTop + maxBottom) / 2.0; viewCenter.setX(centerX); viewCenter.setY(centerY); } else { // [后备方案] 如果无法获取有效的全局边界 (例如数据未加载), // 则退回至基于井筒自身边界的显示逻辑。 viewCenter = m_currentWellboreVisual->boundingRect().center(); targetHeight = m_currentWellboreVisual->boundingRect().height(); // 如果井筒本身高度很小或为0,提供一个最小默认高度 if (targetHeight < 1.0) { targetHeight = 100.0; } } // 4. 为计算出的目标高度增加垂直边距 (padding),例如总共20% // 这使得视图的上下边缘与最顶/最底的地层之间有一些空间。 targetHeight *= 1.20; // 5. 获取视图窗口的宽高比 const QSize viewSize = this->viewport()->size(); if (viewSize.isEmpty() || viewSize.height() == 0) { centerOn(viewCenter); // 至少执行居中 return; } const qreal viewAspectRatio = static_cast(viewSize.width()) / viewSize.height(); // 6. 根据目标高度和宽高比,计算目标宽度 const qreal targetWidth = targetHeight * viewAspectRatio; // 7. 创建目标视野矩形,并将其中心移动到我们计算出的viewCenter QRectF targetRect(0, 0, targetWidth, targetHeight); targetRect.moveCenter(viewCenter); // 8. 应用视图变换,使目标矩形正好填满视图 (逻辑保持不变) this->fitInView(targetRect, Qt::KeepAspectRatio); } //void nmWxWellboreCrossSectionView::resetViewTransform() //{ // resetTransform(); // // // 从当前的可视化项中获取数据指针来居中 // if(m_currentWellboreVisual && m_currentWellboreVisual->getWellData()) { // nmDataWellBase* pGeometry = m_currentWellboreVisual->getWellData(); // // // 如果是多段压裂水平井,则视图中心为井筒中心 // if(pGeometry->getWellType() == NM_WELL_MODEL::Horizontal_Fractured_Well) { // nmDataHorizontalFracturedWell* pHFWell = dynamic_cast(pGeometry); // // if(pHFWell) { // 如果是多段压裂水平井 // // 获取井的起点 // double dStartX = pHFWell->getX().getValue().toDouble(); // double dStartY = pHFWell->getPerforations()[0]->getMdStart().getValue().toDouble(); // // // 获取井长 // double dWellLength = pHFWell->getWellLength().getValue().toDouble(); // // // 计算井的终点 // double dEndX = dStartX + dWellLength; // double dEndY = dStartY; // // // 计算井筒的中点 // double dCenterX = (dStartX + dEndX) / 2.0; // double dCenterY = (dStartY + dEndY) / 2.0; // // centerOn(dCenterX, dCenterY); // } // } else { // double dCenterX = pGeometry->getX().getValue().toDouble(); // double dWellLength = pGeometry->getWellLength().getValue().toDouble(); // double dBottomholeMD = pGeometry->getBottomholeMD().getValue().toDouble(); // double dCenterY = dBottomholeMD + dWellLength / 2.0; // // centerOn(dCenterX, dCenterY); // } // // } else if(m_scene && !m_scene->items().isEmpty()) { // // 如果没有特定的 wellData,但场景中有其他 item,居中到场景的 bounding rect // centerOn(m_scene->itemsBoundingRect().center()); // } else { // // 场景为空,居中到 (0,0) // centerOn(0, 0); // } // // update(); //} nmAbstractWellboreVisual* nmWxWellboreCrossSectionView::createWellboreVisual(NM_WELL_MODEL type) { switch(type) { case Vertical_Well: return new nmVerticalWellCrossSectionItem(); // 返回新的纵截面图元实例 case Vertical_Fractured_Well: return new nmVerticalFracturedWellCrossSectionItem(); // 返回新的纵截面图元实例 case Horizontal_Fractured_Well: return new nmHorizontalFracturedWellCrossSectionItem(); // 返回新的纵截面图元实例 default: qWarning() << "Unsupported well type for Cross Section View:" << type; return nullptr; } } void nmWxWellboreCrossSectionView::mousePressEvent(QMouseEvent *event) { // 添加射孔段模式处理 if(m_bIsInAddPerforationMode && event->button() == Qt::LeftButton) { QPointF scenePos = mapToScene(event->pos()); // 将鼠标位置转换为场景坐标 // 裂缝MD范围和井筒线段 double fractureTopMd = -1.0; double fractureBottomMd = -1.0; QLineF wellboreVisualLine; // 默认是空行 // 根据当前显示的井筒类型,获取特定的参数 if(m_currentWellboreVisual) { // 尝试将当前图元转换为垂直裂缝井的视图项 nmVerticalFracturedWellCrossSectionItem* pVFWellItem = dynamic_cast(m_currentWellboreVisual); // 如果转换成功,说明是垂直裂缝井,获取裂缝的MD范围 if(pVFWellItem) { QRectF fractureRect = pVFWellItem->getFractureMD(); // boundingRect().top() 和 .bottom() 对应于 MD 范围,假设Y轴向上是深度增加 fractureTopMd = fractureRect.top(); fractureBottomMd = fractureRect.bottom(); } // 如果转换失败,说明是普通的垂直井,fractureTopMd和fractureBottomMd会保持默认值-1.0 } // 准备好所有参数后,只发射一次信号 emit sigRequestAddPerforation(scenePos, fractureTopMd, fractureBottomMd, wellboreVisualLine); event->accept(); // 阻止事件进一步传播,确保只处理一次点击 return; } // 删除射孔模式的处理 if(m_bIsInDeletePerforationMode && event->button() == Qt::LeftButton) { QPointF scenePos = mapToScene(event->pos()); // 将鼠标位置转换为场景坐标 // 获取点击位置的所有图形项,默认按Z值从高到低排序 QList clickedItems = scene()->items(scenePos); nmPerforationGraphicsItem *perforationItem = nullptr; // 遍历所有点击到的项,找到第一个射孔段图元 foreach(QGraphicsItem *item, clickedItems) { perforationItem = dynamic_cast(item); if(perforationItem) { // 找到了射孔段图元,停止查找 break; } } if(perforationItem) { // 如果点击的是一个射孔段图形项 nmDataPerforation* pPerforationData = perforationItem->getPerforationData(); // 获取关联的射孔数据 if(pPerforationData) { // 直接发射删除信号 emit sigRequestDeletePerforation(pPerforationData); event->accept(); return; } } } nmAbstractWellboreView::mousePressEvent(event); // 调用基类的鼠标事件处理 } void nmWxWellboreCrossSectionView::setFractureEditMode(bool enabled) { if(auto VFCrossViewItem = dynamic_cast(m_currentWellboreVisual)) { VFCrossViewItem->setFractureEditMode(enabled); } else if(auto HFCrossViewItem = dynamic_cast(m_currentWellboreVisual)) { HFCrossViewItem->setEditMode(enabled); } } // === nmWxWellbore3DView 的实现 === nmWxWellbore3DView::nmWxWellbore3DView(QWidget *parent) : QWidget(parent) { QVBoxLayout *pLayout = new QVBoxLayout(this); QLabel *pLabel = new QLabel("3D View Content Here (3D View)", this); pLabel->setAlignment(Qt::AlignCenter); pLayout->addWidget(pLabel); setLayout(pLayout); setStyleSheet("background-color: lightcoral;"); // 方便区分 } nmWxWellboreTrajectoryDisplay::nmWxWellboreTrajectoryDisplay(QWidget *parent) : QWidget(parent), m_pDataWell(nullptr) { this->initUI(); } nmWxWellboreTrajectoryDisplay::~nmWxWellboreTrajectoryDisplay() { } void nmWxWellboreTrajectoryDisplay::setCurrentWellboreData(nmDataWellBase* pWellData) { m_pDataWell = pWellData; connect(m_pDataWell, SIGNAL(sigWellDataChanged()), this, SLOT(onWellboreDataChanged())); // 设置井类型后更新工具栏 this->updateToolBarForCurrentView(m_pStackedWidget->currentIndex()); // 立即触发更新视图 onWellboreDataChanged(); // 视图调整到以井为中心 m_pTopViewWidget->resetViewTransform(); m_pCrossSectionViewWidget->resetViewTransform(); } void nmWxWellboreTrajectoryDisplay::setEditWellMode(bool enabled) { // 将状态传递给当前激活的子视图 int nCurrentViewIndex = m_pStackedWidget->currentIndex(); // 获取当前视图索引 if(nCurrentViewIndex == 0 && m_pTopViewWidget) { m_pTopViewWidget->setEditMode(enabled); } else if(nCurrentViewIndex == 1 && m_pCrossSectionViewWidget) { // 截面图是编辑垂直裂缝井下的裂缝 m_pCrossSectionViewWidget->setFractureEditMode(enabled); } // 更新父视图的工具栏按钮选中状态 m_pEditWellAction->setChecked(enabled); } void nmWxWellboreTrajectoryDisplay::setPerforationEditMode(bool enabled) { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); // 获取当前视图索引 if(nCurrentViewIndex == 0 && m_pTopViewWidget) { m_pTopViewWidget->setPerForationEditMode(enabled); } else if(nCurrentViewIndex == 1 && m_pCrossSectionViewWidget) { m_pCrossSectionViewWidget->setPerForationEditMode(enabled); } m_pEditPerforationsAction->setChecked(enabled); } void nmWxWellboreTrajectoryDisplay::setAddPerforationMode(bool enabled) { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); // 获取当前视图索引 if(nCurrentViewIndex == 0 && m_pTopViewWidget) { m_pTopViewWidget->setAddPerforationMode(enabled); } else if(nCurrentViewIndex == 1 && m_pCrossSectionViewWidget) { m_pCrossSectionViewWidget->setAddPerforationMode(enabled); } m_pNewPerforationAction->setChecked(enabled); } void nmWxWellboreTrajectoryDisplay::setDeletePerforationMode(bool enabled) { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); // 获取当前视图索引 if(nCurrentViewIndex == 0 && m_pTopViewWidget) { m_pTopViewWidget->setPerforationDeleteMode(enabled); } else if(nCurrentViewIndex == 1 && m_pCrossSectionViewWidget) { m_pCrossSectionViewWidget->setPerforationDeleteMode(enabled); } m_pDeletePerforationAction->setChecked(enabled); } void nmWxWellboreTrajectoryDisplay::setSnapPerforationMode(bool enabled) { m_pSnapPerforationAction->setChecked(enabled); } void nmWxWellboreTrajectoryDisplay::initUI() { // 整体垂直布局 QVBoxLayout *pMainLayout = new QVBoxLayout(this); pMainLayout->setContentsMargins(5, 5, 5, 5); pMainLayout->setSpacing(5); // 1. 创建视图选项区 this->createViewOptions(); pMainLayout->addWidget(m_pViewGroupBox); // 2. 创建工具栏 this->createToolBar(); pMainLayout->addWidget(m_pToolBar); // 3. 创建堆栈式视图区域 this->createViewWidgets(); pMainLayout->addWidget(m_pStackedWidget); // 4. 添加显示鼠标坐标位置的 QLabel // 为鼠标坐标创建浮动标签 m_pMouseCoordLabel = new QLabel(this); // 父对象设置为当前 widget (nmWxWellboreTrajectoryDisplay) m_pMouseCoordLabel->setStyleSheet("background-color: lightyellow; border: 1px solid gray; padding: 2px;"); m_pMouseCoordLabel->setVisible(false); // 初始时隐藏 m_pMouseCoordLabel->setWordWrap(false); // 防止文本换行导致宽度变化 m_pMouseCoordLabel->setFixedSize(100, 40); // 设置固定大小 m_bMouseLabelEnabled = false; this->setLayout(pMainLayout); // 设置主布局 // 5. 连接信号和槽 this->connectSignalsSlots(); // 6. 初始状态:选中顶部视图 m_pTopViewRadio->setChecked(true); } void nmWxWellboreTrajectoryDisplay::createViewOptions() { m_pViewGroupBox = new QGroupBox(tr("View"), this); QHBoxLayout *pHLayout = new QHBoxLayout(m_pViewGroupBox); m_pTopViewRadio = new QRadioButton(tr("Top view"), m_pViewGroupBox); m_pCrossSectionRadio = new QRadioButton(tr("Cross section"), m_pViewGroupBox); m_p3DViewRadio = new QRadioButton(tr("3D view"), m_pViewGroupBox); // 添加到布局器中,并设置伸缩因子 pHLayout->addWidget(m_pTopViewRadio, 1); pHLayout->addWidget(m_pCrossSectionRadio, 1); pHLayout->addWidget(m_p3DViewRadio, 1); m_pViewGroupBox->setLayout(pHLayout); m_pViewButtonGroup = new QButtonGroup(this); m_pViewButtonGroup->addButton(m_pTopViewRadio, 0); // ID 0 m_pViewButtonGroup->addButton(m_pCrossSectionRadio, 1); // ID 1 m_pViewButtonGroup->addButton(m_p3DViewRadio, 2); // ID 2 } void nmWxWellboreTrajectoryDisplay::createToolBar() { QString sAppDir = QCoreApplication::applicationDirPath(); sAppDir = sAppDir.section('/', 0, -2); // 获取上一级目录 // 图标目录 QString sIconDir = sAppDir + "/Res/Icon/"; m_pToolBar = new QToolBar("Tools", this); m_pToolBar->setIconSize(QSize(35, 35)); m_pToolBar->setFixedHeight(47); // 创建QAction并添加到工具栏 m_pZoomOutAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore1.png"), "Zoom Out"); // 缩小 m_pHomeViewAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore2.png"), "Home View"); // 复位/恢复 m_pZoomInAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore3.png"), "Zoom In"); // 放大 m_pToolBar->addSeparator(); m_pXYAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore4.png"), "XY"); // 坐标显示 m_pEditWellAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore5.png"), "Edit Well"); // 编辑井筒轨迹 m_pLoadAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore6.png"), "Load"); // 加载 m_pToolBar->addSeparator(); m_pEditPerforationsAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore7.png"), "Edit Perforations"); // 编辑射孔 m_pNewPerforationAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore8.png"), "New Perforation"); // 添加射孔段 m_pDeletePerforationAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore9.png"), "Delete Perforation"); // 删除射孔段 m_pSnapPerforationAction = m_pToolBar->addAction(QIcon(sIconDir + "NmWellbore10.png"), "Snap Perforation"); // 对齐/吸附 } void nmWxWellboreTrajectoryDisplay::createViewWidgets() { m_pStackedWidget = new QStackedWidget(this); m_pTopViewWidget = new nmWxWellboreTopView(this); m_pCrossSectionViewWidget = new nmWxWellboreCrossSectionView(this); m_p3DViewWidget = new nmWxWellbore3DView(this); m_pStackedWidget->addWidget(m_pTopViewWidget); // 索引 0 m_pStackedWidget->addWidget(m_pCrossSectionViewWidget); // 索引 1 m_pStackedWidget->addWidget(m_p3DViewWidget); // 索引 2 } void nmWxWellboreTrajectoryDisplay::connectSignalsSlots() { // 连接QButtonGroup的buttonClicked信号,现在视图切换会发出信号给控制器 connect(m_pViewButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(onViewRadioButtonToggled(int))); // 连接工具栏QAction的triggered信号,现在这些槽函数将发出请求信号给控制器 connect(m_pZoomOutAction, SIGNAL(triggered()), this, SLOT(onZoomOutClicked())); connect(m_pHomeViewAction, SIGNAL(triggered()), this, SLOT(onHomeViewClicked())); connect(m_pZoomInAction, SIGNAL(triggered()), this, SLOT(onZoomInClicked())); connect(m_pXYAction, SIGNAL(triggered()), this, SLOT(onXYClicked())); // XY 坐标显示仍然在视图内管理 // ============== 模式请求信号连接 ============== // 这些槽函数现在将发出请求给控制器 connect(m_pEditWellAction, SIGNAL(triggered()), this, SLOT(onEditWellClicked())); connect(m_pEditPerforationsAction, SIGNAL(triggered()), this, SLOT(onEditPerforationsClicked())); connect(m_pNewPerforationAction, SIGNAL(triggered()), this, SLOT(onNewPerforationClicked())); connect(m_pDeletePerforationAction, SIGNAL(triggered()), this, SLOT(onDeletePerforationClicked())); connect(m_pSnapPerforationAction, SIGNAL(triggered()), this, SLOT(onSnapPerforationClicked())); // ============================================ // 连接来自俯视图的井筒点移动信号 (只在拖动结束时触发) connect(m_pTopViewWidget, SIGNAL(sigWellborePointMovedInTopView(QPointF)), this, SIGNAL(sigWellboreTrajectoryPointMoved(QPointF))); // 连接来自俯视图的射孔段移动信号 (只在拖动结束时触发) connect(m_pTopViewWidget, SIGNAL(sigPerforationDragInTopView(nmDataPerforation*, double, double)), this, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double))); // 连接来自纵截面图的射孔段移动信号 (只在拖动结束时触发) connect(m_pCrossSectionViewWidget, SIGNAL(sigPerforationDragInCrossView(nmDataPerforation*, double, double)), this, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double))); // 连接添加射孔模式下获取到的相关位置 connect(m_pTopViewWidget, SIGNAL(sigRequestAddPerforation(QPointF, double, double, QLineF)), this, SIGNAL(sigTryAddNewPerforation(QPointF, double, double, QLineF))); connect(m_pCrossSectionViewWidget, SIGNAL(sigRequestAddPerforation(QPointF, double, double, QLineF)), this, SIGNAL(sigTryAddNewPerforation(QPointF, double, double, QLineF))); // 连接请求删除射孔信号 connect(m_pTopViewWidget, SIGNAL(sigRequestDeletePerforation(nmDataPerforation*)), this, SIGNAL(sigTryDeletePerforation(nmDataPerforation*))); connect(m_pCrossSectionViewWidget, SIGNAL(sigRequestDeletePerforation(nmDataPerforation*)), this, SIGNAL(sigTryDeletePerforation(nmDataPerforation*))); // 连接俯视图下更新裂缝位置的信号 connect(m_pTopViewWidget, SIGNAL(sigFracturePosChangedInTopView(double, double)), this, SIGNAL(sigFractureGeometryDragFinished(double, double))); // 连接截面图下更新裂缝位置的信号 connect(m_pCrossSectionViewWidget, SIGNAL(sigRequestFractureRectUpdate(qreal, qreal)), this, SIGNAL(sigRequestFractureRectUpdate(qreal, qreal))); // 射孔更新 connect(m_pCrossSectionViewWidget, SIGNAL(sigFractureDragFinished(const QMap>&)), this, SIGNAL(sigFractureDragFinished(const QMap>&))); // 多段压裂水平井俯视图裂缝半长更新 connect(m_pTopViewWidget, SIGNAL(sigFractureHalfLengthChanged(double)), this, SIGNAL(sigFractureHalfLengthChanged(double))); // 多段压裂水平井俯视图井身位置 connect(m_pTopViewWidget, SIGNAL(sigWellboreDragFinished(QPointF)), this, SIGNAL(sigWellboreDragFinished(QPointF))); // 多段压裂水平井截面图裂缝位置更新 connect(m_pCrossSectionViewWidget, SIGNAL(sigFractureGeometryChanged(double, double)), this, SIGNAL(sigFractureGeometryChanged(double, double))); // 连接各个 QGraphicsView 的鼠标坐标和进入/离开信号到主窗口的槽 connect(m_pTopViewWidget, SIGNAL(mouseCoordinatesChanged(double, double)), this, SLOT(onMouseMovedOnPlot(double, double))); connect(m_pCrossSectionViewWidget, SIGNAL(mouseCoordinatesChanged(double, double)), this, SLOT(onMouseMovedOnPlot(double, double))); connect(m_pTopViewWidget, SIGNAL(mouseEnteredView()), this, SLOT(onMouseEnteredAnyView())); connect(m_pTopViewWidget, SIGNAL(mouseLeftView()), this, SLOT(onMouseLeftAnyView())); connect(m_pCrossSectionViewWidget, SIGNAL(mouseEnteredView()), this, SLOT(onMouseEnteredAnyView())); connect(m_pCrossSectionViewWidget, SIGNAL(mouseLeftView()), this, SLOT(onMouseLeftAnyView())); } void nmWxWellboreTrajectoryDisplay::onViewRadioButtonToggled(int id) { QRadioButton *pSelectedButton = qobject_cast(m_pViewButtonGroup->button(id)); if(pSelectedButton && pSelectedButton->isChecked()) { m_pStackedWidget->setCurrentIndex(id); // === 关通知控制器视图已切换,控制器会负责清空模式和重新设置 === emit currentViewChanged(id); // 更新工具栏按钮的启用/禁用状态 (这只与井类型和当前视图有关,与编辑模式状态无关) updateToolBarForCurrentView(id); } } void nmWxWellboreTrajectoryDisplay::onZoomOutClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); qDebug() << "Zoom Out clicked for current view (ID:" << nCurrentViewIndex << ")"; // 根据当前视图类型调用对应的视图Widget的缩放方法 // 你需要在 nmWxWellboreTopView, nmWxWellboreCrossSectionView, nmWxWellbore3DView 中实现这些方法 switch(nCurrentViewIndex) { case 0: // Top View if(m_pTopViewWidget) { m_pTopViewWidget->zoomOut(); } break; case 1: // Cross Section View if(m_pCrossSectionViewWidget) { m_pCrossSectionViewWidget->zoomOut(); } break; case 2: // 3D View // if (m_p3DViewWidget) m_p3DViewWidget->zoomOut(); qDebug() << "3D View: Performing Zoom Out"; break; default: break; } } void nmWxWellboreTrajectoryDisplay::onHomeViewClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); qDebug() << "Home View clicked for current view (ID:" << nCurrentViewIndex << ")"; // 类似,调用对应视图的 homeView() 或 resetView() 方法 switch(nCurrentViewIndex) { case 0: // Top View if(m_pTopViewWidget) { // 重置视图变换 m_pTopViewWidget->resetViewTransform(); } break; case 1: // Cross Section View if(m_pCrossSectionViewWidget) { // 重置视图变换 m_pCrossSectionViewWidget->resetViewTransform(); } break; case 2: // 3D View // if (m_p3DViewWidget) m_p3DViewWidget->homeView(); qDebug() << "3D View: Resetting View"; break; default: break; } } void nmWxWellboreTrajectoryDisplay::onZoomInClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); qDebug() << "Zoom In clicked for current view (ID:" << nCurrentViewIndex << ")"; // 类似 onZoomOutClicked,调用对应视图的 zoomIn() 方法 switch(nCurrentViewIndex) { case 0: // Top View if(m_pTopViewWidget) { m_pTopViewWidget->zoomIn(); } break; case 1: // Cross Section View if(m_pCrossSectionViewWidget) { m_pCrossSectionViewWidget->zoomIn(); } break; case 2: // 3D View // if (m_p3DViewWidget) m_p3DViewWidget->zoomIn(); qDebug() << "3D View: Performing Zoom In"; break; default: break; } } void nmWxWellboreTrajectoryDisplay::onXYClicked() { // 切换全局开关状态 m_bMouseLabelEnabled = !m_bMouseLabelEnabled; // 根据新的开关状态直接设置标签的可见性 if(m_bMouseLabelEnabled) { // 如果启用,当鼠标下次进入视图时会显示 // 这里不需要立即设置 m_pMouseCoordLabel->setVisible(true), // 因为 mouseMoveEvent 会触发 } else { // 如果禁用,立即隐藏标签并清空文本 m_pMouseCoordLabel->setVisible(false); m_pMouseCoordLabel->setText(""); } } void nmWxWellboreTrajectoryDisplay::onEditWellClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); // 发出请求信号,控制器会根据 nCurrentViewIndex 决定激活何种模式 emit requestedEditWellMode(nCurrentViewIndex); } void nmWxWellboreTrajectoryDisplay::onLoadClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); qDebug() << "Load Data clicked for current view (ID:" << nCurrentViewIndex << ")"; // 实现加载数据的逻辑,可能弹出文件对话框或发出信号给控制器 } void nmWxWellboreTrajectoryDisplay::onEditPerforationsClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); emit requestedEditPerforationsMode(nCurrentViewIndex); } void nmWxWellboreTrajectoryDisplay::onNewPerforationClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); emit requestedNewPerforationMode(nCurrentViewIndex); } void nmWxWellboreTrajectoryDisplay::onDeletePerforationClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); emit requestedDeletePerforationMode(nCurrentViewIndex); } void nmWxWellboreTrajectoryDisplay::onSnapPerforationClicked() { int nCurrentViewIndex = m_pStackedWidget->currentIndex(); emit requestedSnapPerforationMode(nCurrentViewIndex); } void nmWxWellboreTrajectoryDisplay::onWellboreDataChanged() { // 1. 通知控制器,清理当前状态 emit sigClearViewStates(); // 2. 更新视图 m_pTopViewWidget->updateView(m_pDataWell); m_pCrossSectionViewWidget->updateView(m_pDataWell); } void nmWxWellboreTrajectoryDisplay::onMouseMovedOnPlot(double x, double y) { // 获取当前鼠标在 nmWxWellboreTrajectoryDisplay 窗口中的全局位置 QPoint globalMousePos = QCursor::pos(); // 将全局位置转换为 nmWxWellboreTrajectoryDisplay 窗口的局部位置 QPoint localMousePos = this->mapFromGlobal(globalMousePos); QString axisYLabel; int nCurrentViewIndex = m_pStackedWidget->currentIndex(); switch(nCurrentViewIndex) { case 0: // Top View axisYLabel = "Y"; break; case 1: // Cross Section View axisYLabel = "TVD"; break; case 2: // 3D View (如果它也基于 QGraphicsView) axisYLabel = "Z"; break; default: axisYLabel = "Y"; break; } m_pMouseCoordLabel->setText(QString("x = %1 m\n%2 = %3 m").arg(x, 0, 'f', 2).arg(axisYLabel).arg(y, 0, 'f', 2)); // 计算 QLabel 的新位置 int labelX = localMousePos.x(); int labelY = localMousePos.y() - m_pMouseCoordLabel->height(); m_pMouseCoordLabel->move(labelX, labelY); } void nmWxWellboreTrajectoryDisplay::onMouseEnteredAnyView() { // 只有当全局开关m_bMouseLabelEnabled为true时,才显示标签 if(m_bMouseLabelEnabled) { m_pMouseCoordLabel->setVisible(true); } } void nmWxWellboreTrajectoryDisplay::onMouseLeftAnyView() { // 鼠标离开任何一个视图时,隐藏标签并清空文本 m_pMouseCoordLabel->setVisible(false); m_pMouseCoordLabel->setText(""); } void nmWxWellboreTrajectoryDisplay::updateToolBarForCurrentView(int viewIndex) { // 默认所有工具都可用,然后根据视图类型进行调整 m_pZoomOutAction->setEnabled(true); m_pHomeViewAction->setEnabled(true); m_pZoomInAction->setEnabled(true); m_pXYAction->setEnabled(true); m_pEditWellAction->setEnabled(true); m_pLoadAction->setEnabled(true); m_pEditPerforationsAction->setEnabled(true); m_pNewPerforationAction->setEnabled(true); m_pDeletePerforationAction->setEnabled(true); m_pSnapPerforationAction->setEnabled(true); // 默认所有射孔相关工具都禁用 m_pEditPerforationsAction->setEnabled(false); m_pNewPerforationAction->setEnabled(false); m_pDeletePerforationAction->setEnabled(false); m_pSnapPerforationAction->setEnabled(false); // 获取当前井的类型,如果 m_pDataWell 为空,则默认为未知类型 NM_WELL_MODEL currentWellType = NM_WELL_MODEL::Unknow_Well; if(m_pDataWell) { currentWellType = m_pDataWell->getWellType(); } switch(viewIndex) { case 0: // nmWxWellboreTopView (俯视图) qDebug() << "Updating toolbar for Top View"; // 如果是多段压裂水平井,则在俯视图中启用射孔操作 if(currentWellType == NM_WELL_MODEL::Horizontal_Fractured_Well) { m_pEditPerforationsAction->setEnabled(true); m_pNewPerforationAction->setEnabled(true); m_pDeletePerforationAction->setEnabled(true); } break; case 1: // nmWxWellboreCrossSectionView (纵截面视图) // 如果是直井,则在纵截面视图中启用射孔操作 if(currentWellType != NM_WELL_MODEL::Horizontal_Fractured_Well) { m_pEditPerforationsAction->setEnabled(true); m_pNewPerforationAction->setEnabled(true); m_pDeletePerforationAction->setEnabled(true); m_pSnapPerforationAction->setEnabled(true); } break; case 2: // nmWxWellbore3DView (3D视图) qDebug() << "Updating toolbar for 3D View"; // 3D视图中,所有射孔操作保持禁用状态 break; default: qDebug() << "Updating toolbar for unknown view index:" << viewIndex; // 对于未知视图类型,射孔操作保持禁用状态 break; } } void nmWxWellboreTrajectoryDisplay::updataCrossSectionView() { m_pCrossSectionViewWidget->updateView(m_pDataWell); m_pCrossSectionViewWidget->resetViewTransform(); }