diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index 41456ca4..f040dfd3 100644
Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ
diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index fabdf9fc..8e3205f4 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -2809,7 +2809,7 @@ Reason: %1
Property:属性:
Show Grid Lines显示网格线
Show Scalar Bar显示色标
- Coordinate Grid坐标比例网格
+ Scale Grid比例网格
The grid preview data is invalid.网格预览数据无效。
No property无属性
Material材质
diff --git a/Include/nmNum/nmRender/nmVTKPlanarScaleGridLayer.h b/Include/nmNum/nmRender/nmVTKPlanarScaleGridLayer.h
new file mode 100644
index 00000000..386c4cad
--- /dev/null
+++ b/Include/nmNum/nmRender/nmVTKPlanarScaleGridLayer.h
@@ -0,0 +1,41 @@
+#ifndef NMVTKPLANARSCALEGRIDLAYER_H
+#define NMVTKPLANARSCALEGRIDLAYER_H
+
+#include "nmVTKRenderLayer.h"
+
+class nmVTKPlanarScaleGridLayerPrivate;
+
+/**
+ * @brief 在世界坐标 XY 平面中显示比例网格、坐标轴和数值标签。
+ *
+ * 图层只复制 X/Y 边界和所在平面的 Z 值,不引用业务网格。浅色底板、
+ * 背景比例线、坐标轴及刻度均使用世界坐标几何;数值标签由世界坐标
+ * 刻度点逐帧投影为屏幕 Overlay。绘制前根据相机所在平面侧别调整深度
+ * 顺序,但不管理相机,也不注册 Interactor 或 Camera Observer。
+ */
+class NM_RENDER_EXPORT nmVTKPlanarScaleGridLayer : public nmVTKRenderLayer
+{
+public:
+ explicit nmVTKPlanarScaleGridLayer(const QString& sLayerId);
+ virtual ~nmVTKPlanarScaleGridLayer();
+
+ /**
+ * @brief 设置真实 X/Y 边界和网格所在平面的 Z 值,不主动渲染。
+ * @return 参数均有限且 X/Y 范围严格递增时返回 true。
+ */
+ bool setBounds(double dXMinimum,
+ double dXMaximum,
+ double dYMinimum,
+ double dYMaximum,
+ double dPlaneZ);
+
+ /** @brief 返回未经显示偏移处理的真实 X/Y 边界。 */
+ bool getBounds(double dBounds[4]) const;
+
+private:
+ Q_DISABLE_COPY(nmVTKPlanarScaleGridLayer)
+
+ nmVTKPlanarScaleGridLayerPrivate* m_pPrivate;
+};
+
+#endif // NMVTKPLANARSCALEGRIDLAYER_H
diff --git a/Include/nmNum/nmSubWxs/nmPebiGridSceneController.h b/Include/nmNum/nmSubWxs/nmPebiGridSceneController.h
index 3f255bcd..d769e7d8 100644
--- a/Include/nmNum/nmSubWxs/nmPebiGridSceneController.h
+++ b/Include/nmNum/nmSubWxs/nmPebiGridSceneController.h
@@ -12,7 +12,7 @@ class nmWxVTKRenderContainerWidget;
/**
* @brief 把二维 PEBI 网格组合为 nmRender 通用图层。
*
- * Scene 拥有 Mesh、按需创建的 ScalarBar 和 BoundsGrid 图层;本控制器只
+ * Scene 拥有 Mesh、按需创建的 ScalarBar 和平面比例网格图层;本控制器只
* 保存非所有权指针。所有批量修改完成后由控制器统一请求一次渲染。
*/
class NM_SUB_WXS_EXPORT nmPebiGridSceneController
diff --git a/Src/nmNum/nmRender/nmVTKOrientationMarkerController.cpp b/Src/nmNum/nmRender/nmVTKOrientationMarkerController.cpp
index 690e8995..165153cc 100644
--- a/Src/nmNum/nmRender/nmVTKOrientationMarkerController.cpp
+++ b/Src/nmNum/nmRender/nmVTKOrientationMarkerController.cpp
@@ -23,13 +23,12 @@ bool isOrientationMarkerGuiThread()
pApplication->thread() == QThread::currentThread();
}
-// 同时设置轴杆、箭头和标签颜色,避免依赖不同 VTK 版本的默认外观。
+// 方向轴杆和箭头使用各自的方向色,标签颜色由独立函数统一设置。
void setAxisColor(vtkProperty* pShaftProperty,
- vtkProperty* pTipProperty,
- vtkCaptionActor2D* pCaptionActor,
- double dRed,
- double dGreen,
- double dBlue)
+ vtkProperty* pTipProperty,
+ double dRed,
+ double dGreen,
+ double dBlue)
{
if(pShaftProperty != nullptr) {
pShaftProperty->SetColor(dRed, dGreen, dBlue);
@@ -37,6 +36,13 @@ void setAxisColor(vtkProperty* pShaftProperty,
if(pTipProperty != nullptr) {
pTipProperty->SetColor(dRed, dGreen, dBlue);
}
+}
+
+void setCaptionColor(vtkCaptionActor2D* pCaptionActor,
+ double dRed,
+ double dGreen,
+ double dBlue)
+{
if(pCaptionActor != nullptr &&
pCaptionActor->GetCaptionTextProperty() != nullptr) {
pCaptionActor->GetCaptionTextProperty()->SetColor(
@@ -68,16 +74,19 @@ nmVTKOrientationMarkerController::nmVTKOrientationMarkerController()
m_pAxesActor->SetZAxisLabelText("Z");
setAxisColor(m_pAxesActor->GetXAxisShaftProperty(),
m_pAxesActor->GetXAxisTipProperty(),
- m_pAxesActor->GetXAxisCaptionActor2D(),
1.0, 0.0, 0.0);
setAxisColor(m_pAxesActor->GetYAxisShaftProperty(),
m_pAxesActor->GetYAxisTipProperty(),
- m_pAxesActor->GetYAxisCaptionActor2D(),
0.0, 1.0, 0.0);
setAxisColor(m_pAxesActor->GetZAxisShaftProperty(),
m_pAxesActor->GetZAxisTipProperty(),
- m_pAxesActor->GetZAxisCaptionActor2D(),
0.0, 0.0, 1.0);
+ setCaptionColor(m_pAxesActor->GetXAxisCaptionActor2D(),
+ 1.0, 1.0, 1.0);
+ setCaptionColor(m_pAxesActor->GetYAxisCaptionActor2D(),
+ 1.0, 1.0, 1.0);
+ setCaptionColor(m_pAxesActor->GetZAxisCaptionActor2D(),
+ 1.0, 1.0, 1.0);
}
if(m_pMarkerWidget != nullptr) {
diff --git a/Src/nmNum/nmRender/nmVTKPlanarScaleGridLayer.cpp b/Src/nmNum/nmRender/nmVTKPlanarScaleGridLayer.cpp
new file mode 100644
index 00000000..c16152d3
--- /dev/null
+++ b/Src/nmNum/nmRender/nmVTKPlanarScaleGridLayer.cpp
@@ -0,0 +1,987 @@
+#include "nmVTKPlanarScaleGridLayer.h"
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+namespace {
+
+const double PLANAR_AXIS_DEPTH_FACTOR = 0.00020;
+const double PLANAR_GRID_DEPTH_FACTOR = 0.00050;
+const double PLANAR_BACKGROUND_DEPTH_FACTOR = 0.00100;
+const double PLANAR_CAMERA_SIDE_TOLERANCE_FACTOR = 0.000001;
+const double PLANAR_EDGE_SWITCH_PIXEL_TOLERANCE = 2.0;
+const double PLANAR_TICK_LENGTH_FACTOR = 0.012;
+const int PLANAR_LABEL_PIXEL_GAP = 8;
+const int PLANAR_CORNER_LABEL_PIXEL_SHIFT = 12;
+const int PLANAR_TARGET_INTERVAL_COUNT = 6;
+
+bool isPlanarScaleGridGuiThread()
+{
+ QCoreApplication* pApplication = QCoreApplication::instance();
+ return pApplication == nullptr ||
+ pApplication->thread() == QThread::currentThread();
+}
+
+bool isValidPlanarBounds(double dXMinimum,
+ double dXMaximum,
+ double dYMinimum,
+ double dYMaximum,
+ double dPlaneZ)
+{
+ return vtkMath::IsFinite(dXMinimum) &&
+ vtkMath::IsFinite(dXMaximum) &&
+ vtkMath::IsFinite(dYMinimum) &&
+ vtkMath::IsFinite(dYMaximum) &&
+ vtkMath::IsFinite(dPlaneZ) &&
+ dXMinimum < dXMaximum && dYMinimum < dYMaximum;
+}
+
+void appendLine(vtkPoints* pPoints,
+ vtkCellArray* pLines,
+ double dX1,
+ double dY1,
+ double dZ1,
+ double dX2,
+ double dY2,
+ double dZ2)
+{
+ if(pPoints == nullptr || pLines == nullptr) {
+ return;
+ }
+ vtkIdType nPointIds[2];
+ nPointIds[0] = pPoints->InsertNextPoint(dX1, dY1, dZ1);
+ nPointIds[1] = pPoints->InsertNextPoint(dX2, dY2, dZ2);
+ pLines->InsertNextCell(2, nPointIds);
+}
+
+void applyLineData(vtkPolyData* pPolyData,
+ vtkPoints* pPoints,
+ vtkCellArray* pLines)
+{
+ if(pPolyData == nullptr || pPoints == nullptr || pLines == nullptr) {
+ return;
+ }
+ pPolyData->SetPoints(pPoints);
+ pPolyData->SetLines(pLines);
+ pPolyData->Modified();
+}
+
+void applySurfaceData(vtkPolyData* pPolyData,
+ vtkPoints* pPoints,
+ vtkCellArray* pPolygons)
+{
+ if(pPolyData == nullptr || pPoints == nullptr || pPolygons == nullptr) {
+ return;
+ }
+ pPolyData->SetPoints(pPoints);
+ pPolyData->SetPolys(pPolygons);
+ pPolyData->Modified();
+}
+
+void updatePolyDataZ(vtkPolyData* pPolyData, double dZ)
+{
+ vtkPoints* pPoints = pPolyData != nullptr
+ ? pPolyData->GetPoints() : nullptr;
+ if(pPoints == nullptr) {
+ return;
+ }
+
+ double dPoint[3];
+ const vtkIdType nPointCount = pPoints->GetNumberOfPoints();
+ for(vtkIdType nPoint = 0; nPoint < nPointCount; ++nPoint) {
+ pPoints->GetPoint(nPoint, dPoint);
+ dPoint[2] = dZ;
+ pPoints->SetPoint(nPoint, dPoint);
+ }
+ pPoints->Modified();
+ pPolyData->Modified();
+}
+
+void configureLineActor(vtkPolyData* pPolyData,
+ vtkPolyDataMapper* pMapper,
+ vtkActor* pActor,
+ double dRed,
+ double dGreen,
+ double dBlue,
+ float fLineWidth)
+{
+ if(pPolyData == nullptr || pMapper == nullptr || pActor == nullptr) {
+ return;
+ }
+ pMapper->SetInputData(pPolyData);
+ pMapper->ScalarVisibilityOff();
+ pActor->SetMapper(pMapper);
+ pActor->GetProperty()->SetColor(dRed, dGreen, dBlue);
+ pActor->GetProperty()->SetLineWidth(fLineWidth);
+ pActor->GetProperty()->SetOpacity(1.0);
+ pActor->GetProperty()->LightingOff();
+ pActor->PickableOff();
+}
+
+void configureBackgroundActor(vtkPolyData* pPolyData,
+ vtkPolyDataMapper* pMapper,
+ vtkActor* pActor)
+{
+ if(pPolyData == nullptr || pMapper == nullptr || pActor == nullptr) {
+ return;
+ }
+ pMapper->SetInputData(pPolyData);
+ pMapper->ScalarVisibilityOff();
+ pActor->SetMapper(pMapper);
+ pActor->GetProperty()->SetColor(
+ 171.0 / 255.0, 232.0 / 255.0, 241.0 / 255.0);
+ pActor->GetProperty()->SetOpacity(1.0);
+ pActor->GetProperty()->SetRepresentationToSurface();
+ pActor->GetProperty()->EdgeVisibilityOff();
+ pActor->GetProperty()->LightingOff();
+ // 底板两面都需要可见,相机翻到 XY 平面另一侧时仍由深度关系遮挡。
+ pActor->GetProperty()->FrontfaceCullingOff();
+ pActor->GetProperty()->BackfaceCullingOff();
+ pActor->PickableOff();
+}
+
+bool calculateNiceTicks(double dMinimum,
+ double dMaximum,
+ std::vector& vTicks,
+ double& dStep)
+{
+ vTicks.clear();
+ dStep = 0.0;
+ const double dRange = dMaximum - dMinimum;
+ if(!vtkMath::IsFinite(dRange) || dRange <= 0.0) {
+ return false;
+ }
+
+ const double dRawStep =
+ dRange / static_cast(PLANAR_TARGET_INTERVAL_COUNT);
+ const double dMagnitude = std::pow(
+ 10.0, std::floor(std::log10(dRawStep)));
+ if(!vtkMath::IsFinite(dMagnitude) || dMagnitude <= 0.0) {
+ return false;
+ }
+
+ const double dNormalizedStep = dRawStep / dMagnitude;
+ double dNiceFactor = 10.0;
+ if(dNormalizedStep <= 1.0) {
+ dNiceFactor = 1.0;
+ } else if(dNormalizedStep <= 2.0) {
+ dNiceFactor = 2.0;
+ } else if(dNormalizedStep <= 2.5) {
+ dNiceFactor = 2.5;
+ } else if(dNormalizedStep <= 5.0) {
+ dNiceFactor = 5.0;
+ }
+ dStep = dNiceFactor * dMagnitude;
+ if(!vtkMath::IsFinite(dStep) || dStep <= 0.0) {
+ return false;
+ }
+
+ const double dTolerance = dStep * 0.00000001;
+ const double dFirstTick =
+ std::ceil((dMinimum - dTolerance) / dStep) * dStep;
+ for(int nIndex = 0; nIndex < 128; ++nIndex) {
+ double dValue = dFirstTick + static_cast(nIndex) * dStep;
+ if(dValue > dMaximum + dTolerance) {
+ break;
+ }
+ if(dValue >= dMinimum - dTolerance) {
+ if(std::fabs(dValue) < dTolerance) {
+ dValue = 0.0;
+ }
+ vTicks.push_back(dValue);
+ }
+ }
+ return !vTicks.empty();
+}
+
+QString formatTickValue(double dValue, double dStep)
+{
+ const double dAbsoluteStep = std::fabs(dStep);
+ if(std::fabs(dValue) < dAbsoluteStep * 0.00000001) {
+ dValue = 0.0;
+ }
+
+ if(std::fabs(dValue) >= 1000000000.0) {
+ return QString::number(dValue, 'g', 8);
+ }
+
+ int nDecimalCount = 0;
+ double dScaledStep = dAbsoluteStep;
+ while(nDecimalCount < 8 &&
+ std::fabs(dScaledStep - std::floor(dScaledStep + 0.5)) >
+ 0.00000001) {
+ dScaledStep *= 10.0;
+ ++nDecimalCount;
+ }
+ return QString::number(dValue, 'f', nDecimalCount);
+}
+
+int roundedPixelOffset(double dValue)
+{
+ return static_cast(dValue >= 0.0
+ ? std::floor(dValue + 0.5)
+ : std::ceil(dValue - 0.5));
+}
+
+}
+
+class nmVTKPlanarScaleGridLayerPrivate;
+
+/**
+ * @brief 在 VTK 每次绘制组合 Prop 前更新相机相关轴边,不注册额外 Observer。
+ */
+class vtkNmPlanarScaleGridProp : public vtkPropAssembly
+{
+public:
+ static vtkNmPlanarScaleGridProp* New();
+ vtkTypeMacro(vtkNmPlanarScaleGridProp, vtkPropAssembly);
+
+ void setOwner(nmVTKPlanarScaleGridLayerPrivate* pOwner)
+ {
+ m_pOwner = pOwner;
+ }
+
+ virtual int RenderOpaqueGeometry(vtkViewport* pViewport);
+ virtual int RenderOverlay(vtkViewport* pViewport);
+
+protected:
+ vtkNmPlanarScaleGridProp()
+ : m_pOwner(nullptr)
+ {
+ }
+
+ virtual ~vtkNmPlanarScaleGridProp()
+ {
+ m_pOwner = nullptr;
+ }
+
+private:
+ vtkNmPlanarScaleGridProp(const vtkNmPlanarScaleGridProp&);
+ void operator=(const vtkNmPlanarScaleGridProp&);
+
+ nmVTKPlanarScaleGridLayerPrivate* m_pOwner; ///< 非所有权指针。
+};
+
+vtkStandardNewMacro(vtkNmPlanarScaleGridProp);
+
+class nmVTKPlanarScaleGridLayerPrivate
+{
+public:
+ nmVTKPlanarScaleGridLayerPrivate()
+ : m_pRootProp(vtkSmartPointer::New()),
+ m_pBackgroundData(vtkSmartPointer::New()),
+ m_pGridData(vtkSmartPointer::New()),
+ m_pFrameData(vtkSmartPointer::New()),
+ m_pXAxisData(vtkSmartPointer::New()),
+ m_pYAxisData(vtkSmartPointer::New()),
+ m_pBackgroundMapper(vtkSmartPointer::New()),
+ m_pGridMapper(vtkSmartPointer::New()),
+ m_pFrameMapper(vtkSmartPointer::New()),
+ m_pXAxisMapper(vtkSmartPointer::New()),
+ m_pYAxisMapper(vtkSmartPointer::New()),
+ m_pBackgroundActor(vtkSmartPointer::New()),
+ m_pGridActor(vtkSmartPointer::New()),
+ m_pFrameActor(vtkSmartPointer::New()),
+ m_pXAxisActor(vtkSmartPointer::New()),
+ m_pYAxisActor(vtkSmartPointer::New()),
+ m_pLabelTextProperty(vtkSmartPointer::New()),
+ m_dPlaneZ(0.0),
+ m_dScale(0.0),
+ m_dBackgroundZ(0.0),
+ m_dGridZ(0.0),
+ m_dAxisZ(0.0),
+ m_dTickLength(0.0),
+ m_dXTickStep(0.0),
+ m_dYTickStep(0.0),
+ m_bHasBounds(false),
+ m_bAxesInitialized(false),
+ m_bXAxisAtMaximum(false),
+ m_bYAxisAtMaximum(false),
+ m_nCameraSide(1)
+ {
+ for(int nIndex = 0; nIndex < 4; ++nIndex) {
+ m_dBounds[nIndex] = 0.0;
+ }
+
+ configureBackgroundActor(m_pBackgroundData,
+ m_pBackgroundMapper,
+ m_pBackgroundActor);
+ configureLineActor(m_pGridData, m_pGridMapper, m_pGridActor,
+ 25.0 / 255.0, 25.0 / 255.0,
+ 112.0 / 255.0, 1.0f);
+ // 先使用 VTK 7.1 原生线型;不支持线型的后端会自然回退为实线。
+ m_pGridActor->GetProperty()->SetLineStipplePattern(0xF0F0);
+ m_pGridActor->GetProperty()->SetLineStippleRepeatFactor(1);
+ configureLineActor(m_pFrameData, m_pFrameMapper, m_pFrameActor,
+ 25.0 / 255.0, 25.0 / 255.0,
+ 112.0 / 255.0, 1.5f);
+ m_pFrameActor->GetProperty()->SetLineStipplePattern(0xFFFF);
+ m_pFrameActor->GetProperty()->SetLineStippleRepeatFactor(1);
+ configureLineActor(m_pXAxisData, m_pXAxisMapper, m_pXAxisActor,
+ 1.0, 0.0, 0.0, 1.5f);
+ configureLineActor(m_pYAxisData, m_pYAxisMapper, m_pYAxisActor,
+ 0.0, 160.0 / 255.0, 0.0, 1.5f);
+
+ m_pLabelTextProperty->SetFontFamilyToArial();
+ m_pLabelTextProperty->SetFontSize(14);
+ m_pLabelTextProperty->SetColor(
+ 235.0 / 255.0, 235.0 / 255.0, 190.0 / 255.0);
+ m_pLabelTextProperty->SetBold(0);
+ m_pLabelTextProperty->SetItalic(0);
+ // VTK 7.1 根据浅色文字自动计算黑色对比阴影,没有 SetShadowColor 接口。
+ m_pLabelTextProperty->SetShadow(1);
+ m_pLabelTextProperty->SetShadowOffset(1, -1);
+ m_pLabelTextProperty->SetBackgroundOpacity(0.0);
+ m_pLabelTextProperty->SetJustificationToCentered();
+ m_pLabelTextProperty->SetVerticalJustificationToCentered();
+
+ // RootProp 是 Scene 唯一接管的主 Prop,其余 Actor 生命周期均归本图层。
+ m_pRootProp->setOwner(this);
+ m_pRootProp->AddPart(m_pBackgroundActor);
+ m_pRootProp->AddPart(m_pGridActor);
+ m_pRootProp->AddPart(m_pFrameActor);
+ m_pRootProp->AddPart(m_pXAxisActor);
+ m_pRootProp->AddPart(m_pYAxisActor);
+ m_pRootProp->PickableOff();
+ }
+
+ ~nmVTKPlanarScaleGridLayerPrivate()
+ {
+ // 先断开逐帧回调目标,再从组合 Prop 移除其持有的所有子 Prop 引用。
+ m_pRootProp->setOwner(nullptr);
+ clearLabels();
+ m_pRootProp->RemovePart(m_pBackgroundActor);
+ m_pRootProp->RemovePart(m_pGridActor);
+ m_pRootProp->RemovePart(m_pFrameActor);
+ m_pRootProp->RemovePart(m_pXAxisActor);
+ m_pRootProp->RemovePart(m_pYAxisActor);
+ }
+
+ vtkProp* getRootProp() const
+ {
+ return m_pRootProp;
+ }
+
+ bool setBounds(double dXMinimum,
+ double dXMaximum,
+ double dYMinimum,
+ double dYMaximum,
+ double dPlaneZ)
+ {
+ std::vector vXTicks;
+ std::vector vYTicks;
+ double dXTickStep = 0.0;
+ double dYTickStep = 0.0;
+ if(!isValidPlanarBounds(dXMinimum, dXMaximum,
+ dYMinimum, dYMaximum, dPlaneZ) ||
+ !calculateNiceTicks(dXMinimum, dXMaximum,
+ vXTicks, dXTickStep) ||
+ !calculateNiceTicks(dYMinimum, dYMaximum,
+ vYTicks, dYTickStep)) {
+ return false;
+ }
+
+ m_dBounds[0] = dXMinimum;
+ m_dBounds[1] = dXMaximum;
+ m_dBounds[2] = dYMinimum;
+ m_dBounds[3] = dYMaximum;
+ m_vXTicks = vXTicks;
+ m_vYTicks = vYTicks;
+ m_dXTickStep = dXTickStep;
+ m_dYTickStep = dYTickStep;
+ m_dPlaneZ = dPlaneZ;
+
+ m_dScale = dXMaximum - dXMinimum;
+ if(dYMaximum - dYMinimum > m_dScale) {
+ m_dScale = dYMaximum - dYMinimum;
+ }
+ m_dTickLength = m_dScale * PLANAR_TICK_LENGTH_FACTOR;
+ updateDepthCoordinates();
+
+ rebuildBackgroundGeometry();
+ rebuildGridGeometry();
+ rebuildLabels();
+ rebuildAxisGeometry(false, false);
+ m_bHasBounds = true;
+ return true;
+ }
+
+ bool getBounds(double dBounds[4]) const
+ {
+ if(dBounds == nullptr || !m_bHasBounds) {
+ return false;
+ }
+ for(int nIndex = 0; nIndex < 4; ++nIndex) {
+ dBounds[nIndex] = m_dBounds[nIndex];
+ }
+ return true;
+ }
+
+ void updateForRenderer(vtkRenderer* pRenderer)
+ {
+ if(pRenderer == nullptr || !m_bHasBounds) {
+ return;
+ }
+
+ // 仅当相机明确越过带容差的平面分界时翻转深度顺序,侧视附近沿用上次侧别。
+ const bool bDepthChanged = updateCameraSide(pRenderer);
+ if(bDepthChanged) {
+ updateGeometryDepth();
+ }
+
+ double dCornerDisplay[4][3];
+ const double dCorners[4][3] = {
+ { m_dBounds[0], m_dBounds[2], m_dPlaneZ },
+ { m_dBounds[1], m_dBounds[2], m_dPlaneZ },
+ { m_dBounds[1], m_dBounds[3], m_dPlaneZ },
+ { m_dBounds[0], m_dBounds[3], m_dPlaneZ }
+ };
+ for(int nCorner = 0; nCorner < 4; ++nCorner) {
+ if(!projectPoint(pRenderer, dCorners[nCorner],
+ dCornerDisplay[nCorner])) {
+ return;
+ }
+ }
+
+ const double dMinimumYAverage =
+ (dCornerDisplay[0][1] + dCornerDisplay[1][1]) * 0.5;
+ const double dMaximumYAverage =
+ (dCornerDisplay[2][1] + dCornerDisplay[3][1]) * 0.5;
+ const double dMinimumXAverage =
+ (dCornerDisplay[0][0] + dCornerDisplay[3][0]) * 0.5;
+ const double dMaximumXAverage =
+ (dCornerDisplay[1][0] + dCornerDisplay[2][0]) * 0.5;
+
+ // VTK 显示坐标原点位于左下角,平均值较小的边即当前底边或左边。
+ const bool bXAxisAtMaximum = selectMaximumEdge(
+ dMinimumYAverage, dMaximumYAverage,
+ m_bXAxisAtMaximum);
+ const bool bYAxisAtMaximum = selectMaximumEdge(
+ dMinimumXAverage, dMaximumXAverage,
+ m_bYAxisAtMaximum);
+ if(!m_bAxesInitialized ||
+ bXAxisAtMaximum != m_bXAxisAtMaximum ||
+ bYAxisAtMaximum != m_bYAxisAtMaximum) {
+ rebuildAxisGeometry(bXAxisAtMaximum, bYAxisAtMaximum);
+ }
+ updateLabelPositions(pRenderer,
+ bXAxisAtMaximum,
+ bYAxisAtMaximum);
+ }
+
+private:
+ void updateDepthCoordinates()
+ {
+ const double dCameraSide = static_cast(m_nCameraSide);
+ m_dAxisZ = m_dPlaneZ + dCameraSide *
+ m_dScale * PLANAR_AXIS_DEPTH_FACTOR;
+ m_dGridZ = m_dPlaneZ - dCameraSide *
+ m_dScale * PLANAR_GRID_DEPTH_FACTOR;
+ m_dBackgroundZ = m_dPlaneZ - dCameraSide *
+ m_dScale * PLANAR_BACKGROUND_DEPTH_FACTOR;
+ }
+
+ bool updateCameraSide(vtkRenderer* pRenderer)
+ {
+ vtkCamera* pCamera = pRenderer != nullptr
+ ? pRenderer->GetActiveCamera() : nullptr;
+ const double* pCameraPosition = pCamera != nullptr
+ ? pCamera->GetPosition() : nullptr;
+ if(pCameraPosition == nullptr ||
+ !vtkMath::IsFinite(pCameraPosition[2])) {
+ return false;
+ }
+
+ const double dDistanceToPlane = pCameraPosition[2] - m_dPlaneZ;
+ const double dTolerance =
+ m_dScale * PLANAR_CAMERA_SIDE_TOLERANCE_FACTOR;
+ int nCameraSide = m_nCameraSide;
+ if(dDistanceToPlane > dTolerance) {
+ nCameraSide = 1;
+ } else if(dDistanceToPlane < -dTolerance) {
+ nCameraSide = -1;
+ }
+ if(nCameraSide == m_nCameraSide) {
+ return false;
+ }
+
+ m_nCameraSide = nCameraSide;
+ updateDepthCoordinates();
+ return true;
+ }
+
+ bool selectMaximumEdge(double dMinimumEdgeAverage,
+ double dMaximumEdgeAverage,
+ bool bCurrentMaximum) const
+ {
+ const double dDifference =
+ dMaximumEdgeAverage - dMinimumEdgeAverage;
+ if(dDifference < -PLANAR_EDGE_SWITCH_PIXEL_TOLERANCE) {
+ return true;
+ }
+ if(dDifference > PLANAR_EDGE_SWITCH_PIXEL_TOLERANCE) {
+ return false;
+ }
+ return m_bAxesInitialized ? bCurrentMaximum : false;
+ }
+
+ void updateGeometryDepth()
+ {
+ // 相机翻面只改已有点的 Z,不重建底板、比例线拓扑或任何 VTK Prop。
+ updatePolyDataZ(m_pBackgroundData, m_dBackgroundZ);
+ updatePolyDataZ(m_pGridData, m_dGridZ);
+ updatePolyDataZ(m_pFrameData, m_dAxisZ);
+ updatePolyDataZ(m_pXAxisData, m_dAxisZ);
+ updatePolyDataZ(m_pYAxisData, m_dAxisZ);
+ }
+
+ void rebuildBackgroundGeometry()
+ {
+ vtkSmartPointer pPoints =
+ vtkSmartPointer::New();
+ vtkSmartPointer pPolygons =
+ vtkSmartPointer::New();
+ pPoints->SetDataTypeToDouble();
+
+ vtkIdType nPointIds[4];
+ nPointIds[0] = pPoints->InsertNextPoint(
+ m_dBounds[0], m_dBounds[2], m_dBackgroundZ);
+ nPointIds[1] = pPoints->InsertNextPoint(
+ m_dBounds[1], m_dBounds[2], m_dBackgroundZ);
+ nPointIds[2] = pPoints->InsertNextPoint(
+ m_dBounds[1], m_dBounds[3], m_dBackgroundZ);
+ nPointIds[3] = pPoints->InsertNextPoint(
+ m_dBounds[0], m_dBounds[3], m_dBackgroundZ);
+ pPolygons->InsertNextCell(4, nPointIds);
+ applySurfaceData(m_pBackgroundData, pPoints, pPolygons);
+ }
+
+ void rebuildGridGeometry()
+ {
+ vtkSmartPointer pPoints =
+ vtkSmartPointer::New();
+ vtkSmartPointer pLines =
+ vtkSmartPointer::New();
+ pPoints->SetDataTypeToDouble();
+
+ // 内部主刻度使用虚线;矩形外框由独立实线 Actor 绘制。
+ const double dXTolerance = m_dXTickStep * 0.00000001;
+ for(std::size_t nIndex = 0; nIndex < m_vXTicks.size(); ++nIndex) {
+ const double dX = m_vXTicks[nIndex];
+ if(dX > m_dBounds[0] + dXTolerance &&
+ dX < m_dBounds[1] - dXTolerance) {
+ appendLine(pPoints, pLines, dX, m_dBounds[2], m_dGridZ,
+ dX, m_dBounds[3], m_dGridZ);
+ }
+ }
+ const double dYTolerance = m_dYTickStep * 0.00000001;
+ for(std::size_t nIndex = 0; nIndex < m_vYTicks.size(); ++nIndex) {
+ const double dY = m_vYTicks[nIndex];
+ if(dY > m_dBounds[2] + dYTolerance &&
+ dY < m_dBounds[3] - dYTolerance) {
+ appendLine(pPoints, pLines, m_dBounds[0], dY, m_dGridZ,
+ m_dBounds[1], dY, m_dGridZ);
+ }
+ }
+ applyLineData(m_pGridData, pPoints, pLines);
+ }
+
+ void rebuildAxisGeometry(bool bXAxisAtMaximum,
+ bool bYAxisAtMaximum)
+ {
+ const double dXAxisY = bXAxisAtMaximum
+ ? m_dBounds[3] : m_dBounds[2];
+ const double dXAxisDirection = bXAxisAtMaximum ? 1.0 : -1.0;
+ vtkSmartPointer pXPoints =
+ vtkSmartPointer::New();
+ vtkSmartPointer pXLines =
+ vtkSmartPointer::New();
+ pXPoints->SetDataTypeToDouble();
+ appendLine(pXPoints, pXLines,
+ m_dBounds[0], dXAxisY, m_dAxisZ,
+ m_dBounds[1], dXAxisY, m_dAxisZ);
+ for(std::size_t nIndex = 0; nIndex < m_vXTicks.size(); ++nIndex) {
+ appendLine(pXPoints, pXLines,
+ m_vXTicks[nIndex], dXAxisY, m_dAxisZ,
+ m_vXTicks[nIndex],
+ dXAxisY + dXAxisDirection * m_dTickLength,
+ m_dAxisZ);
+ }
+ applyLineData(m_pXAxisData, pXPoints, pXLines);
+
+ const double dYAxisX = bYAxisAtMaximum
+ ? m_dBounds[1] : m_dBounds[0];
+ const double dYAxisDirection = bYAxisAtMaximum ? 1.0 : -1.0;
+ vtkSmartPointer pYPoints =
+ vtkSmartPointer::New();
+ vtkSmartPointer pYLines =
+ vtkSmartPointer::New();
+ pYPoints->SetDataTypeToDouble();
+ appendLine(pYPoints, pYLines,
+ dYAxisX, m_dBounds[2], m_dAxisZ,
+ dYAxisX, m_dBounds[3], m_dAxisZ);
+ for(std::size_t nIndex = 0; nIndex < m_vYTicks.size(); ++nIndex) {
+ appendLine(pYPoints, pYLines,
+ dYAxisX, m_vYTicks[nIndex], m_dAxisZ,
+ dYAxisX + dYAxisDirection * m_dTickLength,
+ m_vYTicks[nIndex], m_dAxisZ);
+ }
+ applyLineData(m_pYAxisData, pYPoints, pYLines);
+
+ // 外框只绘制当前坐标轴的两条对边,底边和左边分别由红、绿轴覆盖。
+ vtkSmartPointer pFramePoints =
+ vtkSmartPointer::New();
+ vtkSmartPointer pFrameLines =
+ vtkSmartPointer::New();
+ pFramePoints->SetDataTypeToDouble();
+ const double dOppositeXAxisY = bXAxisAtMaximum
+ ? m_dBounds[2] : m_dBounds[3];
+ const double dOppositeYAxisX = bYAxisAtMaximum
+ ? m_dBounds[0] : m_dBounds[1];
+ appendLine(pFramePoints, pFrameLines,
+ m_dBounds[0], dOppositeXAxisY, m_dAxisZ,
+ m_dBounds[1], dOppositeXAxisY, m_dAxisZ);
+ appendLine(pFramePoints, pFrameLines,
+ dOppositeYAxisX, m_dBounds[2], m_dAxisZ,
+ dOppositeYAxisX, m_dBounds[3], m_dAxisZ);
+ applyLineData(m_pFrameData, pFramePoints, pFrameLines);
+
+ m_bXAxisAtMaximum = bXAxisAtMaximum;
+ m_bYAxisAtMaximum = bYAxisAtMaximum;
+ m_bAxesInitialized = true;
+ }
+
+ vtkSmartPointer createLabel(
+ const QString& sText)
+ {
+ vtkSmartPointer pLabel =
+ vtkSmartPointer::New();
+ const QByteArray aText = sText.toUtf8();
+ pLabel->SetInput(aText.constData());
+ pLabel->SetTextProperty(m_pLabelTextProperty);
+ pLabel->SetTextScaleModeToNone();
+ pLabel->GetProperty()->SetDisplayLocationToForeground();
+ pLabel->PickableOff();
+ m_pRootProp->AddPart(pLabel);
+ return pLabel;
+ }
+
+ void rebuildLabels()
+ {
+ clearLabels();
+ for(std::size_t nIndex = 0; nIndex < m_vXTicks.size(); ++nIndex) {
+ m_vXLabels.push_back(createLabel(
+ formatTickValue(m_vXTicks[nIndex],
+ m_dXTickStep)));
+ }
+ for(std::size_t nIndex = 0; nIndex < m_vYTicks.size(); ++nIndex) {
+ m_vYLabels.push_back(createLabel(
+ formatTickValue(m_vYTicks[nIndex],
+ m_dYTickStep)));
+ }
+ }
+
+ void clearLabels()
+ {
+ for(std::size_t nIndex = 0; nIndex < m_vXLabels.size(); ++nIndex) {
+ m_pRootProp->RemovePart(m_vXLabels[nIndex]);
+ }
+ for(std::size_t nIndex = 0; nIndex < m_vYLabels.size(); ++nIndex) {
+ m_pRootProp->RemovePart(m_vYLabels[nIndex]);
+ }
+ m_vXLabels.clear();
+ m_vYLabels.clear();
+ }
+
+ bool projectPoint(vtkRenderer* pRenderer,
+ const double dWorld[3],
+ double dDisplay[3]) const
+ {
+ if(pRenderer == nullptr || dWorld == nullptr || dDisplay == nullptr) {
+ return false;
+ }
+ pRenderer->SetWorldPoint(
+ dWorld[0], dWorld[1], dWorld[2], 1.0);
+ pRenderer->WorldToDisplay();
+ const double* pDisplay = pRenderer->GetDisplayPoint();
+ if(pDisplay == nullptr ||
+ !vtkMath::IsFinite(pDisplay[0]) ||
+ !vtkMath::IsFinite(pDisplay[1]) ||
+ !vtkMath::IsFinite(pDisplay[2])) {
+ return false;
+ }
+ dDisplay[0] = pDisplay[0];
+ dDisplay[1] = pDisplay[1];
+ dDisplay[2] = pDisplay[2];
+ return true;
+ }
+
+ bool calculateDisplayDirection(vtkRenderer* pRenderer,
+ const double dAnchor[3],
+ const double dOuterPoint[3],
+ double dDirection[2]) const
+ {
+ double dAnchorDisplay[3];
+ double dOuterDisplay[3];
+ if(!projectPoint(pRenderer, dAnchor, dAnchorDisplay) ||
+ !projectPoint(pRenderer, dOuterPoint, dOuterDisplay)) {
+ return false;
+ }
+ const double dX = dOuterDisplay[0] - dAnchorDisplay[0];
+ const double dY = dOuterDisplay[1] - dAnchorDisplay[1];
+ const double dLength = std::sqrt(dX * dX + dY * dY);
+ if(!vtkMath::IsFinite(dLength) || dLength < 0.000001) {
+ return false;
+ }
+ dDirection[0] = dX / dLength;
+ dDirection[1] = dY / dLength;
+ return true;
+ }
+
+ void updateLabelPositions(vtkRenderer* pRenderer,
+ bool bXAxisAtMaximum,
+ bool bYAxisAtMaximum)
+ {
+ const double dXAxisY = bXAxisAtMaximum
+ ? m_dBounds[3] : m_dBounds[2];
+ const double dXAxisDirection = bXAxisAtMaximum ? 1.0 : -1.0;
+ const double dXAxisAnchor[3] = {
+ (m_dBounds[0] + m_dBounds[1]) * 0.5,
+ dXAxisY,
+ m_dAxisZ
+ };
+ const double dXAxisOuterPoint[3] = {
+ dXAxisAnchor[0],
+ dXAxisY + dXAxisDirection * m_dTickLength,
+ m_dAxisZ
+ };
+ double dXAxisDisplayDirection[2] = { 0.0, -1.0 };
+ calculateDisplayDirection(pRenderer,
+ dXAxisAnchor,
+ dXAxisOuterPoint,
+ dXAxisDisplayDirection);
+ const int nXAxisOffsetX = roundedPixelOffset(
+ dXAxisDisplayDirection[0] * PLANAR_LABEL_PIXEL_GAP);
+ const int nXAxisOffsetY = roundedPixelOffset(
+ dXAxisDisplayDirection[1] * PLANAR_LABEL_PIXEL_GAP);
+
+ const double dXAxisMinimumPoint[3] = {
+ m_dBounds[0], dXAxisY, m_dAxisZ
+ };
+ const double dXAxisMaximumPoint[3] = {
+ m_dBounds[1], dXAxisY, m_dAxisZ
+ };
+ double dXAxisPositiveDirection[2] = { 1.0, 0.0 };
+ calculateDisplayDirection(pRenderer,
+ dXAxisMinimumPoint,
+ dXAxisMaximumPoint,
+ dXAxisPositiveDirection);
+ const double dXAxisCornerValue = bYAxisAtMaximum
+ ? m_dBounds[1] : m_dBounds[0];
+ const double dXAxisCornerDirection = bYAxisAtMaximum ? -1.0 : 1.0;
+ const double dXAxisTolerance = m_dXTickStep * 0.00000001;
+ for(std::size_t nIndex = 0; nIndex < m_vXLabels.size(); ++nIndex) {
+ const double dLabelWorld[3] = {
+ m_vXTicks[nIndex], dXAxisOuterPoint[1], m_dAxisZ
+ };
+ double dLabelDisplay[3];
+ if(!projectPoint(pRenderer, dLabelWorld, dLabelDisplay)) {
+ m_vXLabels[nIndex]->SetVisibility(0);
+ continue;
+ }
+ int nOffsetX = nXAxisOffsetX;
+ int nOffsetY = nXAxisOffsetY;
+ if(std::fabs(m_vXTicks[nIndex] - dXAxisCornerValue) <=
+ dXAxisTolerance) {
+ nOffsetX += roundedPixelOffset(
+ dXAxisPositiveDirection[0] *
+ dXAxisCornerDirection *
+ PLANAR_CORNER_LABEL_PIXEL_SHIFT);
+ nOffsetY += roundedPixelOffset(
+ dXAxisPositiveDirection[1] *
+ dXAxisCornerDirection *
+ PLANAR_CORNER_LABEL_PIXEL_SHIFT);
+ }
+ m_vXLabels[nIndex]->SetDisplayPosition(
+ roundedPixelOffset(dLabelDisplay[0]) + nOffsetX,
+ roundedPixelOffset(dLabelDisplay[1]) + nOffsetY);
+ m_vXLabels[nIndex]->SetVisibility(1);
+ }
+
+ const double dYAxisX = bYAxisAtMaximum
+ ? m_dBounds[1] : m_dBounds[0];
+ const double dYAxisDirection = bYAxisAtMaximum ? 1.0 : -1.0;
+ const double dYAxisAnchor[3] = {
+ dYAxisX,
+ (m_dBounds[2] + m_dBounds[3]) * 0.5,
+ m_dAxisZ
+ };
+ const double dYAxisOuterPoint[3] = {
+ dYAxisX + dYAxisDirection * m_dTickLength,
+ dYAxisAnchor[1],
+ m_dAxisZ
+ };
+ double dYAxisDisplayDirection[2] = { -1.0, 0.0 };
+ calculateDisplayDirection(pRenderer,
+ dYAxisAnchor,
+ dYAxisOuterPoint,
+ dYAxisDisplayDirection);
+ const int nYAxisOffsetX = roundedPixelOffset(
+ dYAxisDisplayDirection[0] * PLANAR_LABEL_PIXEL_GAP);
+ const int nYAxisOffsetY = roundedPixelOffset(
+ dYAxisDisplayDirection[1] * PLANAR_LABEL_PIXEL_GAP);
+
+ const double dYAxisMinimumPoint[3] = {
+ dYAxisX, m_dBounds[2], m_dAxisZ
+ };
+ const double dYAxisMaximumPoint[3] = {
+ dYAxisX, m_dBounds[3], m_dAxisZ
+ };
+ double dYAxisPositiveDirection[2] = { 0.0, 1.0 };
+ calculateDisplayDirection(pRenderer,
+ dYAxisMinimumPoint,
+ dYAxisMaximumPoint,
+ dYAxisPositiveDirection);
+ const double dYAxisCornerValue = bXAxisAtMaximum
+ ? m_dBounds[3] : m_dBounds[2];
+ const double dYAxisCornerDirection = bXAxisAtMaximum ? -1.0 : 1.0;
+ const double dYAxisTolerance = m_dYTickStep * 0.00000001;
+ for(std::size_t nIndex = 0; nIndex < m_vYLabels.size(); ++nIndex) {
+ const double dLabelWorld[3] = {
+ dYAxisOuterPoint[0], m_vYTicks[nIndex], m_dAxisZ
+ };
+ double dLabelDisplay[3];
+ if(!projectPoint(pRenderer, dLabelWorld, dLabelDisplay)) {
+ m_vYLabels[nIndex]->SetVisibility(0);
+ continue;
+ }
+ int nOffsetX = nYAxisOffsetX;
+ int nOffsetY = nYAxisOffsetY;
+ if(std::fabs(m_vYTicks[nIndex] - dYAxisCornerValue) <=
+ dYAxisTolerance) {
+ nOffsetX += roundedPixelOffset(
+ dYAxisPositiveDirection[0] *
+ dYAxisCornerDirection *
+ PLANAR_CORNER_LABEL_PIXEL_SHIFT);
+ nOffsetY += roundedPixelOffset(
+ dYAxisPositiveDirection[1] *
+ dYAxisCornerDirection *
+ PLANAR_CORNER_LABEL_PIXEL_SHIFT);
+ }
+ m_vYLabels[nIndex]->SetDisplayPosition(
+ roundedPixelOffset(dLabelDisplay[0]) + nOffsetX,
+ roundedPixelOffset(dLabelDisplay[1]) + nOffsetY);
+ m_vYLabels[nIndex]->SetVisibility(1);
+ }
+ }
+
+ vtkSmartPointer m_pRootProp;
+ vtkSmartPointer m_pBackgroundData;
+ vtkSmartPointer m_pGridData;
+ vtkSmartPointer m_pFrameData;
+ vtkSmartPointer m_pXAxisData;
+ vtkSmartPointer m_pYAxisData;
+ vtkSmartPointer m_pBackgroundMapper;
+ vtkSmartPointer m_pGridMapper;
+ vtkSmartPointer m_pFrameMapper;
+ vtkSmartPointer m_pXAxisMapper;
+ vtkSmartPointer m_pYAxisMapper;
+ vtkSmartPointer m_pBackgroundActor;
+ vtkSmartPointer m_pGridActor;
+ vtkSmartPointer m_pFrameActor;
+ vtkSmartPointer m_pXAxisActor;
+ vtkSmartPointer m_pYAxisActor;
+ vtkSmartPointer m_pLabelTextProperty;
+ std::vector > m_vXLabels;
+ std::vector > m_vYLabels;
+ std::vector m_vXTicks;
+ std::vector m_vYTicks;
+ double m_dBounds[4];
+ double m_dPlaneZ;
+ double m_dScale;
+ double m_dBackgroundZ;
+ double m_dGridZ;
+ double m_dAxisZ;
+ double m_dTickLength;
+ double m_dXTickStep;
+ double m_dYTickStep;
+ bool m_bHasBounds;
+ bool m_bAxesInitialized;
+ bool m_bXAxisAtMaximum;
+ bool m_bYAxisAtMaximum;
+ int m_nCameraSide;
+};
+
+int vtkNmPlanarScaleGridProp::RenderOpaqueGeometry(vtkViewport* pViewport)
+{
+ if(m_pOwner != nullptr) {
+ m_pOwner->updateForRenderer(vtkRenderer::SafeDownCast(pViewport));
+ }
+ return this->Superclass::RenderOpaqueGeometry(pViewport);
+}
+
+int vtkNmPlanarScaleGridProp::RenderOverlay(vtkViewport* pViewport)
+{
+ if(m_pOwner != nullptr) {
+ // Overlay 阶段再次同步屏幕坐标,确保文字使用本帧最终相机矩阵。
+ m_pOwner->updateForRenderer(vtkRenderer::SafeDownCast(pViewport));
+ }
+ return this->Superclass::RenderOverlay(pViewport);
+}
+
+nmVTKPlanarScaleGridLayer::nmVTKPlanarScaleGridLayer(
+ const QString& sLayerId)
+ : nmVTKRenderLayer(sLayerId),
+ m_pPrivate(new nmVTKPlanarScaleGridLayerPrivate)
+{
+ Q_ASSERT(isPlanarScaleGridGuiThread());
+ setRenderProp(m_pPrivate->getRootProp());
+}
+
+nmVTKPlanarScaleGridLayer::~nmVTKPlanarScaleGridLayer()
+{
+ Q_ASSERT(isPlanarScaleGridGuiThread());
+ // 先从 Renderer 移除组合 Prop,再清除其逐帧更新目标和子 Prop 引用。
+ detach();
+ delete m_pPrivate;
+ m_pPrivate = nullptr;
+}
+
+bool nmVTKPlanarScaleGridLayer::setBounds(
+ double dXMinimum,
+ double dXMaximum,
+ double dYMinimum,
+ double dYMaximum,
+ double dPlaneZ)
+{
+ Q_ASSERT(isPlanarScaleGridGuiThread());
+ return isPlanarScaleGridGuiThread() && m_pPrivate != nullptr &&
+ m_pPrivate->setBounds(dXMinimum, dXMaximum,
+ dYMinimum, dYMaximum, dPlaneZ);
+}
+
+bool nmVTKPlanarScaleGridLayer::getBounds(double dBounds[4]) const
+{
+ return m_pPrivate != nullptr && m_pPrivate->getBounds(dBounds);
+}
diff --git a/Src/nmNum/nmRender/nmWxVTKRenderContainerWidget.cpp b/Src/nmNum/nmRender/nmWxVTKRenderContainerWidget.cpp
index fc583fb4..b7b97502 100644
--- a/Src/nmNum/nmRender/nmWxVTKRenderContainerWidget.cpp
+++ b/Src/nmNum/nmRender/nmWxVTKRenderContainerWidget.cpp
@@ -21,6 +21,7 @@
#include
VTK_MODULE_INIT(vtkRenderingOpenGL)
+VTK_MODULE_INIT(vtkRenderingFreeType)
VTK_MODULE_INIT(vtkInteractionStyle)
#include
diff --git a/Src/nmNum/nmSubWxs/nmPebiGridSceneController.cpp b/Src/nmNum/nmSubWxs/nmPebiGridSceneController.cpp
index e83ed20b..daf4cf60 100644
--- a/Src/nmNum/nmSubWxs/nmPebiGridSceneController.cpp
+++ b/Src/nmNum/nmSubWxs/nmPebiGridSceneController.cpp
@@ -2,8 +2,8 @@
#include "nmPebiGridViewData.h"
-#include "nmVTKBoundsGridLayer.h"
#include "nmVTKMeshLayer.h"
+#include "nmVTKPlanarScaleGridLayer.h"
#include "nmVTKScalarBarLayer.h"
#include "nmVTKScene.h"
#include "nmWxVTKRenderContainerWidget.h"
@@ -11,7 +11,6 @@
#include
#include
-#include
#include
#include
#include
@@ -54,7 +53,7 @@ public:
: m_pContainer(pRenderContainer),
m_pMeshLayer(nullptr),
m_pScalarBarLayer(nullptr),
- m_pBoundsGridLayer(nullptr),
+ m_pPlanarScaleGridLayer(nullptr),
m_bGridLinesVisible(true),
m_bScalarBarVisible(true),
m_bBoundsGridVisible(false),
@@ -82,24 +81,25 @@ public:
return false;
}
m_pMeshLayer = pLayer;
- // 固定面色、边线颜色和完全不透明外观沿用旧 PEBI 预览。
+ // 无属性时使用稳定灰色;Material 有效时仍由 MeshLayer 的 LUT 着色。
m_pMeshLayer->getActor()->GetProperty()->SetColor(
- 0.0, 120.0 / 255.0, 215.0 / 255.0);
+ 211.0 / 255.0, 211.0 / 255.0,
+ 211.0 / 255.0);
m_pMeshLayer->getActor()->GetProperty()->SetEdgeColor(
0.0, 0.0, 0.0);
+ // 仅关闭当前 PEBI 预览 Actor 的光照,避免固定面色随视角变化。
+ m_pMeshLayer->getActor()->GetProperty()->LightingOff();
}
- if(m_pBoundsGridLayer == nullptr) {
- nmVTKBoundsGridLayer* pLayer =
- new nmVTKBoundsGridLayer("pebi.grid.bounds_grid");
- pLayer->setMode(nmVTKBoundsGridLayer::Mode_PlanarXY);
- pLayer->setCamera(pRenderer->GetActiveCamera());
+ if(m_pPlanarScaleGridLayer == nullptr) {
+ nmVTKPlanarScaleGridLayer* pLayer =
+ new nmVTKPlanarScaleGridLayer("pebi.grid.scale_grid");
pLayer->setVisible(false);
if(!pScene->addLayer(pLayer)) {
delete pLayer;
return false;
}
- m_pBoundsGridLayer = pLayer;
+ m_pPlanarScaleGridLayer = pLayer;
}
return true;
}
@@ -175,7 +175,7 @@ public:
const QString& sRequestedProperty,
const double dBounds[6])
{
- if(m_pMeshLayer == nullptr || m_pBoundsGridLayer == nullptr ||
+ if(m_pMeshLayer == nullptr || m_pPlanarScaleGridLayer == nullptr ||
dBounds == nullptr) {
return false;
}
@@ -189,15 +189,15 @@ public:
m_pMeshLayer->setEdgeVisibility(m_bGridLinesVisible);
m_pMeshLayer->setVisible(true);
- if(!m_pBoundsGridLayer->setBounds(
+ if(!m_pPlanarScaleGridLayer->setBounds(
dBounds[0], dBounds[1], dBounds[2],
- dBounds[3], dBounds[4], dBounds[5])) {
+ dBounds[3], dBounds[4])) {
m_pMeshLayer->setDataSet(nullptr);
m_pMeshLayer->setVisible(false);
m_oViewData.clear();
return false;
}
- m_pBoundsGridLayer->setVisible(m_bBoundsGridVisible);
+ m_pPlanarScaleGridLayer->setVisible(m_bBoundsGridVisible);
m_bHasData = true;
// 属性不可用只回退固定面色,不影响已经验证成功的网格几何提交。
@@ -216,8 +216,8 @@ public:
if(m_pScalarBarLayer != nullptr) {
m_pScalarBarLayer->setVisible(false);
}
- if(m_pBoundsGridLayer != nullptr) {
- m_pBoundsGridLayer->setVisible(false);
+ if(m_pPlanarScaleGridLayer != nullptr) {
+ m_pPlanarScaleGridLayer->setVisible(false);
}
m_oViewData.clear();
m_sPropertyName.clear();
@@ -227,7 +227,7 @@ public:
nmWxVTKRenderContainerWidget* m_pContainer; ///< 非所有权指针。
nmVTKMeshLayer* m_pMeshLayer; ///< Scene 所有,控制器只观察。
nmVTKScalarBarLayer* m_pScalarBarLayer; ///< Scene 所有,按需创建。
- nmVTKBoundsGridLayer* m_pBoundsGridLayer; ///< Scene 所有。
+ nmVTKPlanarScaleGridLayer* m_pPlanarScaleGridLayer; ///< Scene 所有。
nmPebiGridViewData m_oViewData;
bool m_bGridLinesVisible;
bool m_bScalarBarVisible; ///< 用户偏好,无属性时仍保留。
@@ -252,7 +252,7 @@ nmPebiGridSceneController::~nmPebiGridSceneController()
m_pPrivate->m_pContainer = nullptr;
m_pPrivate->m_pMeshLayer = nullptr;
m_pPrivate->m_pScalarBarLayer = nullptr;
- m_pPrivate->m_pBoundsGridLayer = nullptr;
+ m_pPrivate->m_pPlanarScaleGridLayer = nullptr;
delete m_pPrivate;
m_pPrivate = nullptr;
}
@@ -394,8 +394,8 @@ void nmPebiGridSceneController::setBoundsGridVisible(bool bVisible)
return;
}
m_pPrivate->m_bBoundsGridVisible = bVisible;
- if(m_pPrivate->m_pBoundsGridLayer != nullptr) {
- m_pPrivate->m_pBoundsGridLayer->setVisible(
+ if(m_pPrivate->m_pPlanarScaleGridLayer != nullptr) {
+ m_pPrivate->m_pPlanarScaleGridLayer->setVisible(
bVisible && m_pPrivate->m_bHasData);
}
if(m_pPrivate->m_bHasData && m_pPrivate->m_pContainer != nullptr) {
diff --git a/Src/nmNum/nmSubWxs/nmWxPebiGridViewWidget.cpp b/Src/nmNum/nmSubWxs/nmWxPebiGridViewWidget.cpp
index d70516c7..b1815e59 100644
--- a/Src/nmNum/nmSubWxs/nmWxPebiGridViewWidget.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxPebiGridViewWidget.cpp
@@ -107,8 +107,8 @@ void nmWxPebiGridViewWidget::createUi()
m_pScalarBarAction->setCheckable(true);
m_pBoundsGridAction = new QAction(
QIcon(gridViewIconPath("CalGrid.png")),
- tr("Coordinate Grid"), this);
- m_pBoundsGridAction->setToolTip(tr("Coordinate Grid"));
+ tr("Scale Grid"), this);
+ m_pBoundsGridAction->setToolTip(tr("Scale Grid"));
m_pBoundsGridAction->setCheckable(true);
if(pCommandController != nullptr) {
diff --git a/Src4/nmNum/nmRender/nmRender.pro b/Src4/nmNum/nmRender/nmRender.pro
index ca70498e..f2f7ebfd 100644
--- a/Src4/nmNum/nmRender/nmRender.pro
+++ b/Src4/nmNum/nmRender/nmRender.pro
@@ -21,6 +21,7 @@ SOURCES += $${wtSrc}/nmNum/nmRender/nmVTKCameraController.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKInteractionManager.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKOrientationMarkerController.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKMeshLayer.cpp \
+ $${wtSrc}/nmNum/nmRender/nmVTKPlanarScaleGridLayer.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKRenderLayer.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKScalarBarLayer.cpp \
$${wtSrc}/nmNum/nmRender/nmVTKScene.cpp \
@@ -34,6 +35,7 @@ HEADERS += $${wtInclude}/nmNum/nmRender/nmRender_global.h \
$${wtInclude}/nmNum/nmRender/nmVTKInteractionManager.h \
$${wtInclude}/nmNum/nmRender/nmVTKOrientationMarkerController.h \
$${wtInclude}/nmNum/nmRender/nmVTKMeshLayer.h \
+ $${wtInclude}/nmNum/nmRender/nmVTKPlanarScaleGridLayer.h \
$${wtInclude}/nmNum/nmRender/nmVTKRenderLayer.h \
$${wtInclude}/nmNum/nmRender/nmVTKScalarBarLayer.h \
$${wtInclude}/nmNum/nmRender/nmVTKScene.h \