#include "nmVTKScalarBarLayer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // ScalarBarActor 和 LookupTable 均只能在 Qt GUI 线程中修改。 bool isScalarBarGuiThread() { QCoreApplication* pApplication = QCoreApplication::instance(); return pApplication == nullptr || pApplication->thread() == QThread::currentThread(); } // 独立色块使用归一化视口坐标,避免依赖仅支持 IndexedLookup 的 ScalarBar Annotation。 vtkSmartPointer createRangeSwatchActor(vtkPoints* pPoints) { vtkSmartPointer pPolygons = vtkSmartPointer::New(); pPolygons->InsertNextCell(4); pPolygons->InsertCellPoint(0); pPolygons->InsertCellPoint(1); pPolygons->InsertCellPoint(2); pPolygons->InsertCellPoint(3); vtkSmartPointer pData = vtkSmartPointer::New(); pData->SetPoints(pPoints); pData->SetPolys(pPolygons); vtkSmartPointer pViewportCoordinate = vtkSmartPointer::New(); pViewportCoordinate->SetCoordinateSystemToNormalizedViewport(); vtkSmartPointer pMapper = vtkSmartPointer::New(); pMapper->SetInputData(pData); pMapper->SetTransformCoordinate(pViewportCoordinate); pMapper->ScalarVisibilityOff(); vtkSmartPointer pActor = vtkSmartPointer::New(); pActor->SetMapper(pMapper); pActor->GetProperty()->SetDisplayLocationToForeground(); pActor->PickableOff(); pActor->SetVisibility(0); return pActor; } // 统一独立文本样式,使常量值和越界说明与普通色标刻度保持一致。 void configureOverlayTextActor(vtkTextActor* pActor) { pActor->SetInput(""); pActor->SetTextScaleModeToNone(); pActor->GetTextProperty()->SetFontSize(11); pActor->GetTextProperty()->BoldOff(); pActor->GetTextProperty()->ItalicOff(); pActor->GetTextProperty()->ShadowOff(); pActor->GetTextProperty()->SetJustificationToLeft(); pActor->GetTextProperty()->SetVerticalJustificationToCentered(); pActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport(); pActor->PickableOff(); pActor->SetVisibility(0); } // 更新矩形四角并显式标记 Points 已修改,Mapper 会在下一次 Render 使用新位置。 void updateRectangle(vtkPoints* pPoints, double dX, double dY, double dWidth, double dHeight) { pPoints->SetPoint(0, dX, dY, 0.0); pPoints->SetPoint(1, dX + dWidth, dY, 0.0); pPoints->SetPoint(2, dX + dWidth, dY + dHeight, 0.0); pPoints->SetPoint(3, dX, dY + dHeight, 0.0); pPoints->Modified(); } } nmVTKScalarBarLayer::nmVTKScalarBarLayer(const QString& sLayerId) : nmVTKRenderLayer(sLayerId), m_pLookupTable(nullptr), m_pRootAssembly(vtkSmartPointer::New()), m_pActor(vtkSmartPointer::New()), m_pConstantValueActor(vtkSmartPointer::New()), m_pBelowRangeSwatchPoints(vtkSmartPointer::New()), m_pBelowRangeSwatchActor(nullptr), m_pBelowRangeTextActor(vtkSmartPointer::New()), m_pAboveRangeSwatchPoints(vtkSmartPointer::New()), m_pAboveRangeSwatchActor(nullptr), m_pAboveRangeTextActor(vtkSmartPointer::New()), m_sLabelFormat(QString::fromLatin1("%.6g")), m_dPositionX(0.0), m_dPositionY(0.0), m_dWidth(0.1), m_dHeight(0.8) { Q_ASSERT(isScalarBarGuiThread()); // vtkScalarBarActor 是二维 Prop,仍通过图层基类统一完成挂载和显隐。 m_pActor->SetNumberOfLabels(5); m_pActor->SetLabelFormat("%.6g"); m_pActor->UnconstrainedFontSizeOn(); m_pActor->GetLabelTextProperty()->SetFontSize(11); m_pActor->GetLabelTextProperty()->BoldOff(); m_pActor->GetLabelTextProperty()->ItalicOff(); m_pActor->GetLabelTextProperty()->ShadowOff(); m_pActor->GetTitleTextProperty()->SetFontSize(12); m_pActor->GetTitleTextProperty()->BoldOff(); m_pActor->GetTitleTextProperty()->ItalicOff(); m_pActor->GetTitleTextProperty()->ShadowOff(); m_pActor->DrawAnnotationsOff(); m_pActor->DrawBelowRangeSwatchOff(); m_pActor->DrawAboveRangeSwatchOff(); // 这些二维装饰必须独立显示,不能为网格共享 LUT 开启 IndexedLookup。 m_pBelowRangeSwatchPoints->SetNumberOfPoints(4); m_pAboveRangeSwatchPoints->SetNumberOfPoints(4); m_pBelowRangeSwatchActor = createRangeSwatchActor(m_pBelowRangeSwatchPoints); m_pAboveRangeSwatchActor = createRangeSwatchActor(m_pAboveRangeSwatchPoints); configureOverlayTextActor(m_pConstantValueActor); configureOverlayTextActor(m_pBelowRangeTextActor); configureOverlayTextActor(m_pAboveRangeTextActor); updateOverlayLayout(); // 根 Assembly 只在构造时注册,运行期仅更新子 Actor 状态。 m_pRootAssembly->AddPart(m_pActor); m_pRootAssembly->AddPart(m_pConstantValueActor); m_pRootAssembly->AddPart(m_pBelowRangeSwatchActor); m_pRootAssembly->AddPart(m_pBelowRangeTextActor); m_pRootAssembly->AddPart(m_pAboveRangeSwatchActor); m_pRootAssembly->AddPart(m_pAboveRangeTextActor); setRenderProp(m_pRootAssembly); } nmVTKScalarBarLayer::~nmVTKScalarBarLayer() { Q_ASSERT(isScalarBarGuiThread()); // 先移除二维 Prop,再释放对外部 LookupTable 的共享引用。 detach(); m_pLookupTable = nullptr; m_pAboveRangeTextActor = nullptr; m_pAboveRangeSwatchActor = nullptr; m_pAboveRangeSwatchPoints = nullptr; m_pBelowRangeTextActor = nullptr; m_pBelowRangeSwatchActor = nullptr; m_pBelowRangeSwatchPoints = nullptr; m_pConstantValueActor = nullptr; m_pActor = nullptr; m_pRootAssembly = nullptr; } void nmVTKScalarBarLayer::setLookupTable(vtkLookupTable* pLookupTable) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread()) { return; } // 智能指针共享 LookupTable,保证外部释放原指针后色标仍可安全显示。 m_pLookupTable = pLookupTable; m_pActor->SetLookupTable(pLookupTable); updateRangeSwatchColors(); } vtkLookupTable* nmVTKScalarBarLayer::getLookupTable() const { return m_pLookupTable; } void nmVTKScalarBarLayer::setTitle(const QString& sTitle) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread()) { return; } m_sTitle = sTitle; // VTK 7.1 接口接收窄字符串,统一以 UTF-8 临时缓冲传入并由 Actor 复制。 const QByteArray aTitle = sTitle.toUtf8(); m_pActor->SetTitle(aTitle.constData()); } QString nmVTKScalarBarLayer::getTitle() const { return m_sTitle; } bool nmVTKScalarBarLayer::setLabelFormat(const QString& sFormat) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread() || sFormat.isEmpty()) { return false; } m_sLabelFormat = sFormat; const QByteArray aFormat = sFormat.toLatin1(); m_pActor->SetLabelFormat(aFormat.constData()); return true; } QString nmVTKScalarBarLayer::getLabelFormat() const { return m_sLabelFormat; } bool nmVTKScalarBarLayer::setNumberOfLabels(int nLabelCount) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread() || nLabelCount < 0 || nLabelCount > 64) { return false; } m_pActor->SetNumberOfLabels(nLabelCount); return true; } int nmVTKScalarBarLayer::getNumberOfLabels() const { return m_pActor != nullptr ? m_pActor->GetNumberOfLabels() : 0; } void nmVTKScalarBarLayer::setBelowRangeSwatch( bool bVisible, const QString& sText) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread()) { return; } m_sBelowRangeText = bVisible ? sText : QString(); const QByteArray aText = m_sBelowRangeText.toUtf8(); m_pBelowRangeTextActor->SetInput(aText.constData()); m_pBelowRangeSwatchActor->SetVisibility(bVisible ? 1 : 0); m_pBelowRangeTextActor->SetVisibility( bVisible && !m_sBelowRangeText.isEmpty() ? 1 : 0); } void nmVTKScalarBarLayer::setAboveRangeSwatch( bool bVisible, const QString& sText) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread()) { return; } m_sAboveRangeText = bVisible ? sText : QString(); const QByteArray aText = m_sAboveRangeText.toUtf8(); m_pAboveRangeTextActor->SetInput(aText.constData()); m_pAboveRangeSwatchActor->SetVisibility(bVisible ? 1 : 0); m_pAboveRangeTextActor->SetVisibility( bVisible && !m_sAboveRangeText.isEmpty() ? 1 : 0); } void nmVTKScalarBarLayer::setConstantValueLabel( bool bVisible, const QString& sText) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread()) { return; } m_sConstantValueText = bVisible ? sText : QString(); const QByteArray aText = m_sConstantValueText.toUtf8(); m_pConstantValueActor->SetInput(aText.constData()); m_pConstantValueActor->SetVisibility( bVisible && !m_sConstantValueText.isEmpty() ? 1 : 0); } bool nmVTKScalarBarLayer::setPosition(double dX, double dY) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread() || !vtkMath::IsFinite(dX) || !vtkMath::IsFinite(dY)) { return false; } // vtkScalarBarActor 默认使用归一化视口坐标,位置值由上层布局策略决定。 m_dPositionX = dX; m_dPositionY = dY; m_pActor->SetPosition(dX, dY); updateOverlayLayout(); return true; } bool nmVTKScalarBarLayer::setSize(double dWidth, double dHeight) { Q_ASSERT(isScalarBarGuiThread()); if(!isScalarBarGuiThread() || !vtkMath::IsFinite(dWidth) || !vtkMath::IsFinite(dHeight) || dWidth <= 0.0 || dHeight <= 0.0) { return false; } // 这里只拒绝非正尺寸,不替业务层限制色标是否超出视口。 m_dWidth = dWidth; m_dHeight = dHeight; m_pActor->SetWidth(dWidth); m_pActor->SetHeight(dHeight); updateOverlayLayout(); return true; } void nmVTKScalarBarLayer::updateOverlayLayout() { // 常量值放在色带右侧的垂直中心,视觉位置与普通刻度标签一致。 const double dLabelX = m_dPositionX + m_dWidth + 0.01; const double dLabelY = m_dPositionY + m_dHeight * 0.5; m_pConstantValueActor->GetPositionCoordinate()->SetValue( dLabelX, dLabelY, 0.0); // 越界色块位于色带上下两端外侧,避免覆盖连续色带和普通刻度。 double dSwatchWidth = m_dWidth * 0.25; if(dSwatchWidth < 0.012) { dSwatchWidth = 0.012; } else if(dSwatchWidth > 0.025) { dSwatchWidth = 0.025; } const double dSwatchHeight = 0.018; const double dSwatchGap = 0.012; const double dTextGap = 0.008; const double dBelowY = m_dPositionY - dSwatchGap - dSwatchHeight; const double dAboveY = m_dPositionY + m_dHeight + dSwatchGap; updateRectangle(m_pBelowRangeSwatchPoints, m_dPositionX, dBelowY, dSwatchWidth, dSwatchHeight); updateRectangle(m_pAboveRangeSwatchPoints, m_dPositionX, dAboveY, dSwatchWidth, dSwatchHeight); m_pBelowRangeTextActor->GetPositionCoordinate()->SetValue( m_dPositionX + dSwatchWidth + dTextGap, dBelowY + dSwatchHeight * 0.5, 0.0); m_pAboveRangeTextActor->GetPositionCoordinate()->SetValue( m_dPositionX + dSwatchWidth + dTextGap, dAboveY + dSwatchHeight * 0.5, 0.0); } void nmVTKScalarBarLayer::updateRangeSwatchColors() { if(m_pLookupTable == nullptr) { return; } // 读取 LookupTable 的业务颜色属性,不直接访问内部特殊表项索引。 const double* pBelowColor = m_pLookupTable->GetBelowRangeColor(); const double* pAboveColor = m_pLookupTable->GetAboveRangeColor(); m_pBelowRangeSwatchActor->GetProperty()->SetColor( pBelowColor[0], pBelowColor[1], pBelowColor[2]); m_pBelowRangeSwatchActor->GetProperty()->SetOpacity(pBelowColor[3]); m_pAboveRangeSwatchActor->GetProperty()->SetColor( pAboveColor[0], pAboveColor[1], pAboveColor[2]); m_pAboveRangeSwatchActor->GetProperty()->SetOpacity(pAboveColor[3]); } vtkScalarBarActor* nmVTKScalarBarLayer::getActor() const { return m_pActor; }