#include "nmPebiResultWellAnnotationLayer.h" #include "nmPebiResultSnapshot.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { bool isWellAnnotationGuiThread() { QCoreApplication* pApplication = QCoreApplication::instance(); return pApplication == nullptr || pApplication->thread() == QThread::currentThread(); } // Saphir 风格标注使用固定像素偏移,缩放相机时文字和引线长度保持不变。 const int WELL_NAME_FONT_SIZE = 16; const int WELL_NAME_OFFSET_X = 18; const int WELL_NAME_OFFSET_Y = 40; const int WELL_LEADER_OFFSET_X = 14; const int WELL_LEADER_OFFSET_Y = 36; const double WELL_LEADER_RED = 140.0 / 255.0; const double WELL_LEADER_GREEN = 87.0 / 255.0; const double WELL_LEADER_BLUE = 59.0 / 255.0; } nmPebiResultWellAnnotationLayer::nmPebiResultWellAnnotationLayer( const QString& sLayerId) : nmVTKRenderLayer(sLayerId), m_pRootAssembly(vtkSmartPointer::New()), m_pActiveSnapshotAssembly(nullptr), m_pCandidateSnapshotAssembly(nullptr) { Q_ASSERT(isWellAnnotationGuiThread()); // setRenderProp 只在未挂载时可调用,因此根 Assembly 在构造时一次固定。 setRenderProp(m_pRootAssembly); } nmPebiResultWellAnnotationLayer::~nmPebiResultWellAnnotationLayer() { Q_ASSERT(isWellAnnotationGuiThread()); detach(); clear(); m_pRootAssembly = nullptr; } bool nmPebiResultWellAnnotationLayer::prepareCandidate( const QSharedPointer& pSnapshot) { Q_ASSERT(isWellAnnotationGuiThread()); discardCandidate(); if(!isWellAnnotationGuiThread() || pSnapshot.isNull() || !pSnapshot->isComplete()) { return false; } QSet setWellIds; for(int nIndex = 0; nIndex < pSnapshot->getWellCount(); ++nIndex) { const nmPebiResultWellSnapshot* pWell = pSnapshot->getWellAt(nIndex); if(pWell == nullptr || pWell->m_sWellInstanceId.isEmpty() || pWell->m_sWellName.isEmpty() || !vtkMath::IsFinite(pWell->m_oLocation.x()) || !vtkMath::IsFinite(pWell->m_oLocation.y()) || setWellIds.contains(pWell->m_sWellInstanceId)) { discardCandidate(); return false; } setWellIds.insert(pWell->m_sWellInstanceId); } vtkSmartPointer pSnapshotAssembly = vtkSmartPointer::New(); vtkSmartPointer pAnnotationGroup = vtkSmartPointer::New(); if(pSnapshot->getWellCount() > 0) { for(int nIndex = 0; nIndex < pSnapshot->getWellCount(); ++nIndex) { const nmPebiResultWellSnapshot* pWell = pSnapshot->getWellAt(nIndex); vtkSmartPointer pWorldAnchor = vtkSmartPointer::New(); // Z=0 仅表示冻结 XY 所在平面,不表示井深、井斜或井轨迹。 pWorldAnchor->SetCoordinateSystemToWorld(); pWorldAnchor->SetValue( pWell->m_oLocation.x(), pWell->m_oLocation.y(), 0.0); vtkSmartPointer pWellAnnotation = vtkSmartPointer::New(); vtkSmartPointer pLeader = vtkSmartPointer::New(); // 引线两个端点都引用同一 World 锚点,只叠加固定 Display 像素偏移。 vtkCoordinate* pStartCoordinate = pLeader->GetPositionCoordinate(); pStartCoordinate->SetCoordinateSystemToDisplay(); pStartCoordinate->SetReferenceCoordinate(pWorldAnchor); pStartCoordinate->SetValue(0.0, 0.0, 0.0); vtkCoordinate* pEndCoordinate = pLeader->GetPosition2Coordinate(); pEndCoordinate->SetCoordinateSystemToDisplay(); pEndCoordinate->SetReferenceCoordinate(pWorldAnchor); pEndCoordinate->SetValue( WELL_LEADER_OFFSET_X, WELL_LEADER_OFFSET_Y, 0.0); pLeader->AutoLabelOff(); pLeader->SetLabel(""); pLeader->SetArrowPlacementToNone(); pLeader->SetRadius(0.0); pLeader->GetProperty()->SetColor( WELL_LEADER_RED, WELL_LEADER_GREEN, WELL_LEADER_BLUE); pLeader->GetProperty()->SetLineWidth(1.0); // 二维引线始终绘制在三维网格前方,避免被致密单元边线遮挡。 pLeader->GetProperty()->SetDisplayLocationToForeground(); pWellAnnotation->AddPart(pLeader); vtkSmartPointer pNameMapper = vtkSmartPointer::New(); const QByteArray aWellName = pWell->m_sWellName.toUtf8(); pNameMapper->SetInput(aWellName.constData()); vtkTextProperty* pTextProperty = pNameMapper->GetTextProperty(); pTextProperty->SetFontFamilyToArial(); pTextProperty->SetFontSize(WELL_NAME_FONT_SIZE); pTextProperty->SetColor(1.0, 1.0, 0.0); pTextProperty->BoldOff(); pTextProperty->ItalicOff(); pTextProperty->ShadowOn(); pTextProperty->SetShadowOffset(1, -1); pTextProperty->SetJustificationToLeft(); pTextProperty->SetVerticalJustificationToBottom(); vtkSmartPointer pNameActor = vtkSmartPointer::New(); pNameActor->SetMapper(pNameMapper); // 井名与引线使用相同的前景层级,保证顶视图下仍清晰可读。 pNameActor->GetProperty()->SetDisplayLocationToForeground(); vtkCoordinate* pNameCoordinate = pNameActor->GetPositionCoordinate(); pNameCoordinate->SetCoordinateSystemToDisplay(); pNameCoordinate->SetReferenceCoordinate(pWorldAnchor); pNameCoordinate->SetValue( WELL_NAME_OFFSET_X, WELL_NAME_OFFSET_Y, 0.0); pWellAnnotation->AddPart(pNameActor); // 每口井的文字与引线形成一个不可拆分的候选注记单元。 pAnnotationGroup->AddPart(pWellAnnotation); } } pSnapshotAssembly->AddPart(pAnnotationGroup); // 所有校验和 VTK 对象构造成功后才记录候选;活动子树仍未触碰。 m_pCandidateSnapshotAssembly = pSnapshotAssembly; return true; } void nmPebiResultWellAnnotationLayer::discardCandidate() { Q_ASSERT(isWellAnnotationGuiThread()); m_pCandidateSnapshotAssembly = nullptr; } void nmPebiResultWellAnnotationLayer::commitCandidate() { Q_ASSERT(isWellAnnotationGuiThread()); Q_ASSERT(m_pCandidateSnapshotAssembly != nullptr); if(m_pActiveSnapshotAssembly != nullptr) { m_pRootAssembly->RemovePart(m_pActiveSnapshotAssembly); } m_pActiveSnapshotAssembly = m_pCandidateSnapshotAssembly; m_pRootAssembly->AddPart(m_pActiveSnapshotAssembly); discardCandidate(); } void nmPebiResultWellAnnotationLayer::clear() { Q_ASSERT(isWellAnnotationGuiThread()); discardCandidate(); if(m_pRootAssembly != nullptr && m_pActiveSnapshotAssembly != nullptr) { m_pRootAssembly->RemovePart(m_pActiveSnapshotAssembly); } m_pActiveSnapshotAssembly = nullptr; } bool nmPebiResultWellAnnotationLayer::hasActiveAnnotations() const { return m_pActiveSnapshotAssembly != nullptr; }