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++

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