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

4548 lines
178 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmWxWellboreTrajectoryDisplay.h"
#include <QIcon>
#include <QRadioButton>
#include <QButtonGroup>
#include <QToolBar>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGroupBox>
#include <QAction>
#include <QLabel>
#include <QDebug>
#include <QCoreApplication>
#include <QMouseEvent>
#include <QCursor>
#include <QWheelEvent>
#include <QMessageBox>
#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 <QPainter>
#include <QStyleOptionGraphicsItem>
#include <QWidget>
#include <QGraphicsScene>
#include <QGraphicsView>
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<nmAbstractWellboreVisual*>(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
{
// 边界矩形只包含裂缝矩形本身。
// 手柄是独立的子项它们有自己的boundingRectQGraphicsItem 不会自动包含子项的边界。
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<nmDataLayer*> 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<nmDataLayer*>& 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<nmDataLayer*>(layer->clone()));
// }
// }
//
// prepareGeometryChange();
// update();
//}
void nmReservoirGraphicsItems::setReservoirLayers(const QVector<nmDataLayer*>& 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<nmDataVerticalWell*>(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<nmDataVerticalWell*>(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<nmDataPerforation*>& 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<nmDataVerticalFracturedWell*>(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<QPointF> 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<nmDataVerticalFracturedWell*>(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<nmDataPerforation*, QPair<double, double>> updatedPerforations;
// 遍历所有射孔图元将它们被裁剪后的MD值收集起来
foreach(nmPerforationGraphicsItem* pPerforationItem, m_listPerforations) {
double croppedStart = pPerforationItem->getCroppedMdStart();
double croppedEnd = pPerforationItem->getCroppedMdEnd();
// 存入映射,用于传递给控制器
updatedPerforations.insert(pPerforationItem->getPerforationData(), QPair<double, double>(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<nmDataVerticalFracturedWell*>(m_pDataWell);
if(!pVFWell) return;
QVector<nmDataPerforation*>& vecCurrentPerforations = pVFWell->getPerforations();
// 1: 找出需要删除的图形项 (数据模型中已不存在的)
QList<nmDataPerforation*> 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<nmDataHorizontalFracturedWell*>(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<nmDataPerforation*>& 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<QPair<QPointF, QPointF>> vecFracturePoint = pHFWell->getFracs();
int fractureIndex = 0; // 用于给裂缝分配索引
for(QVector<QPair<QPointF, QPointF>>::const_iterator it = vecFracturePoint.constBegin();
it != vecFracturePoint.constEnd(); ++it) {
const QPair<QPointF, QPointF>& 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<nmDataHorizontalFracturedWell*>(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<nmDataHorizontalFracturedWell*>(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<nmDataHorizontalFracturedWell*>(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<double> 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<double> 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<nmVerticalWellTopViewItem * >(m_currentWellboreVisual)) {
// 连接 nmVerticalWellTopViewItem 的井筒点拖拽完成信号,转发给主视图
connect(pVerticalWellTopView, SIGNAL(sigWellborePointDragFinished(QPointF)),
this, SIGNAL(sigWellborePointMovedInTopView(QPointF)));
}
// 仅当是 nmVerticalFracturedWellTopViewItem 类型时才连接其裂缝拖拽信号
if(nmVerticalFracturedWellTopViewItem * pVFWellTopView = dynamic_cast<nmVerticalFracturedWellTopViewItem * >(m_currentWellboreVisual)) {
// 连接 nmVerticalFracturedWellTopViewItem 的射孔拖拽完成信号,转发给主视图
connect(pVFWellTopView, SIGNAL(sigFracturePosChanged(double, double)),
this, SIGNAL(sigFracturePosChangedInTopView(double, double)));
}
// 仅当是 nmHorizontalFracturedWellTopViewItem 类型时才连接其射孔拖拽信号
if(nmHorizontalFracturedWellTopViewItem * pHFWellTopView = dynamic_cast<nmHorizontalFracturedWellTopViewItem * >(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<nmHorizontalFracturedWellTopViewItem * >(m_currentWellboreVisual)) {
topViewItem->setPerforationEditMode(enabled);
}
}
void nmWxWellboreTopView::setAddPerforationMode(bool enable)
{
m_bIsInAddPerforationMode = enable;
}
void nmWxWellboreTopView::setPerforationDeleteMode(bool enabled)
{
m_bIsInDeletePerforationMode = enabled;
}
#include <QTransform>
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<nmDataHorizontalFracturedWell*>(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<nmVerticalWellTopViewItem * >(m_currentWellboreVisual)) {
vTopViewItem->setEditMode(enabled);
} else if(auto VFtopViewItem = dynamic_cast<nmVerticalFracturedWellTopViewItem * >(m_currentWellboreVisual)) {
VFtopViewItem->setEditMode(enabled);
} else if(auto HFtopViewItem = dynamic_cast<nmHorizontalFracturedWellTopViewItem * >(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<nmHorizontalFracturedWellTopViewItem*>(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<QGraphicsItem*> clickedItems = scene()->items(scenePos);
nmPerforationGraphicsItem *perforationItem = nullptr;
// 遍历所有点击到的项,找到第一个射孔段图元
foreach(QGraphicsItem *item, clickedItems) {
perforationItem = dynamic_cast<nmPerforationGraphicsItem*>(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<nmVerticalWellCrossSectionItem * >(m_currentWellboreVisual)) {
// 连接 nmVerticalWellCrossSectionItem 的射孔拖拽完成信号,转发给主视图
connect(pVWellCrossView, SIGNAL(sigPerforationDragFinished(nmDataPerforation*, double, double)),
this, SIGNAL(sigPerforationDragInCrossView(nmDataPerforation*, double, double)));
} else if(nmVerticalFracturedWellCrossSectionItem * pVFWellCrossView = dynamic_cast<nmVerticalFracturedWellCrossSectionItem * >(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<nmDataPerforation*, QPair<double, double>>&)),
this, SIGNAL(sigFractureDragFinished(const QMap<nmDataPerforation*, QPair<double, double>>&)));
} else if(nmHorizontalFracturedWellCrossSectionItem * pHFWellCrossView = dynamic_cast<nmHorizontalFracturedWellCrossSectionItem * >(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<nmVerticalWellCrossSectionItem * >(m_currentWellboreVisual)) {
verticalCrossViewItem->setPerforationEditMode(enabled);
} else if(auto VerticalFCrossViewItem = dynamic_cast<nmVerticalFracturedWellCrossSectionItem * >(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<qreal>(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<nmDataHorizontalFracturedWell*>(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<nmVerticalFracturedWellCrossSectionItem*>(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<QGraphicsItem*> clickedItems = scene()->items(scenePos);
nmPerforationGraphicsItem *perforationItem = nullptr;
// 遍历所有点击到的项,找到第一个射孔段图元
foreach(QGraphicsItem *item, clickedItems) {
perforationItem = dynamic_cast<nmPerforationGraphicsItem*>(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<nmVerticalFracturedWellCrossSectionItem * >(m_currentWellboreVisual)) {
VFCrossViewItem->setFractureEditMode(enabled);
} else if(auto HFCrossViewItem = dynamic_cast<nmHorizontalFracturedWellCrossSectionItem * >(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<nmDataPerforation*, QPair<double, double>>&)),
this, SIGNAL(sigFractureDragFinished(const QMap<nmDataPerforation*, QPair<double, double>>&)));
// 多段压裂水平井俯视图裂缝半长更新
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<QRadioButton*>(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();
}