Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b740f8d453 | |||
| 5852e11b9f |
@@ -3,6 +3,9 @@ using Prism.Events;
|
|||||||
using System;
|
using System;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XP.Hardware.Detector.Abstractions.Enums;
|
||||||
|
using XP.Hardware.Detector.Abstractions.Events;
|
||||||
|
using XP.Hardware.Detector.Services;
|
||||||
using XP.Hardware.MotionControl.Abstractions;
|
using XP.Hardware.MotionControl.Abstractions;
|
||||||
using XP.Hardware.MotionControl.Abstractions.Enums;
|
using XP.Hardware.MotionControl.Abstractions.Enums;
|
||||||
using XP.Hardware.MotionControl.Abstractions.Events;
|
using XP.Hardware.MotionControl.Abstractions.Events;
|
||||||
@@ -25,6 +28,7 @@ namespace XplorePlane.Tests.Services
|
|||||||
private readonly Mock<IRaySourceService> _mockRaySource;
|
private readonly Mock<IRaySourceService> _mockRaySource;
|
||||||
private readonly Mock<IMotionSystem> _mockMotionSystem;
|
private readonly Mock<IMotionSystem> _mockMotionSystem;
|
||||||
private readonly Mock<IMotionControlService> _mockMotionControlService;
|
private readonly Mock<IMotionControlService> _mockMotionControlService;
|
||||||
|
private readonly Mock<IDetectorService> _mockDetectorService;
|
||||||
private readonly Mock<ILinearAxis> _mockStageX;
|
private readonly Mock<ILinearAxis> _mockStageX;
|
||||||
private readonly Mock<ILinearAxis> _mockStageY;
|
private readonly Mock<ILinearAxis> _mockStageY;
|
||||||
private readonly Mock<ILinearAxis> _mockSourceZ;
|
private readonly Mock<ILinearAxis> _mockSourceZ;
|
||||||
@@ -48,6 +52,7 @@ namespace XplorePlane.Tests.Services
|
|||||||
_mockRaySource = new Mock<IRaySourceService>();
|
_mockRaySource = new Mock<IRaySourceService>();
|
||||||
_mockMotionSystem = new Mock<IMotionSystem>();
|
_mockMotionSystem = new Mock<IMotionSystem>();
|
||||||
_mockMotionControlService = new Mock<IMotionControlService>();
|
_mockMotionControlService = new Mock<IMotionControlService>();
|
||||||
|
_mockDetectorService = new Mock<IDetectorService>();
|
||||||
_mockStageX = CreateLinearAxis(AxisId.StageX, 0);
|
_mockStageX = CreateLinearAxis(AxisId.StageX, 0);
|
||||||
_mockStageY = CreateLinearAxis(AxisId.StageY, 0);
|
_mockStageY = CreateLinearAxis(AxisId.StageY, 0);
|
||||||
_mockSourceZ = CreateLinearAxis(AxisId.SourceZ, 0);
|
_mockSourceZ = CreateLinearAxis(AxisId.SourceZ, 0);
|
||||||
@@ -70,12 +75,24 @@ namespace XplorePlane.Tests.Services
|
|||||||
.Setup(x => x.GetCurrentGeometry())
|
.Setup(x => x.GetCurrentGeometry())
|
||||||
.Returns((0d, 0d, 1d));
|
.Returns((0d, 0d, 1d));
|
||||||
|
|
||||||
|
// DetectorService:GetInfo 在未初始化时抛出,模拟此行为
|
||||||
|
_mockDetectorService
|
||||||
|
.Setup(x => x.GetInfo())
|
||||||
|
.Throws(new InvalidOperationException("探测器未初始化"));
|
||||||
|
_mockDetectorService
|
||||||
|
.SetupGet(x => x.Status)
|
||||||
|
.Returns(DetectorStatus.Uninitialized);
|
||||||
|
_mockDetectorService
|
||||||
|
.SetupGet(x => x.IsConnected)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
_mockLogger.Setup(l => l.ForModule<AppStateService>()).Returns(_mockLogger.Object);
|
_mockLogger.Setup(l => l.ForModule<AppStateService>()).Returns(_mockLogger.Object);
|
||||||
|
|
||||||
_service = new AppStateService(
|
_service = new AppStateService(
|
||||||
_mockRaySource.Object,
|
_mockRaySource.Object,
|
||||||
_mockMotionSystem.Object,
|
_mockMotionSystem.Object,
|
||||||
_mockMotionControlService.Object,
|
_mockMotionControlService.Object,
|
||||||
|
_mockDetectorService.Object,
|
||||||
_eventAggregator,
|
_eventAggregator,
|
||||||
_mockLogger.Object);
|
_mockLogger.Object);
|
||||||
}
|
}
|
||||||
@@ -188,6 +205,57 @@ namespace XplorePlane.Tests.Services
|
|||||||
Assert.Equal(222.2, _service.MotionState.FDD);
|
Assert.Equal(222.2, _service.MotionState.FDD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StatusChangedEvent_Acquiring_SyncsDetectorState()
|
||||||
|
{
|
||||||
|
// 模拟探测器进入采集状态
|
||||||
|
_mockDetectorService.SetupGet(x => x.Status).Returns(DetectorStatus.Acquiring);
|
||||||
|
_mockDetectorService.Setup(x => x.GetInfo()).Throws(new InvalidOperationException());
|
||||||
|
|
||||||
|
_eventAggregator.GetEvent<StatusChangedEvent>()
|
||||||
|
.Publish(DetectorStatus.Acquiring);
|
||||||
|
|
||||||
|
// 等待后台线程处理(BackgroundThread 订阅)
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
|
||||||
|
Assert.True(_service.DetectorState.IsConnected);
|
||||||
|
Assert.True(_service.DetectorState.IsAcquiring);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StatusChangedEvent_Uninitialized_SyncsDetectorStateDisconnected()
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<StatusChangedEvent>()
|
||||||
|
.Publish(DetectorStatus.Uninitialized);
|
||||||
|
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
|
||||||
|
Assert.False(_service.DetectorState.IsConnected);
|
||||||
|
Assert.False(_service.DetectorState.IsAcquiring);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ImageCapturedEvent_UpdatesLatestDetectorFrame()
|
||||||
|
{
|
||||||
|
Assert.Null(_service.LatestDetectorFrame);
|
||||||
|
|
||||||
|
var args = new XP.Hardware.Detector.Abstractions.ImageCapturedEventArgs
|
||||||
|
{
|
||||||
|
ImageData = new ushort[4],
|
||||||
|
Width = 2,
|
||||||
|
Height = 2,
|
||||||
|
FrameNumber = 1,
|
||||||
|
CaptureTime = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
_eventAggregator.GetEvent<ImageCapturedEvent>().Publish(args);
|
||||||
|
|
||||||
|
// 等待后台线程处理
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
|
||||||
|
Assert.Same(args, _service.LatestDetectorFrame);
|
||||||
|
}
|
||||||
|
|
||||||
private static Mock<ILinearAxis> CreateLinearAxis(AxisId axisId, double position)
|
private static Mock<ILinearAxis> CreateLinearAxis(AxisId axisId, double position)
|
||||||
{
|
{
|
||||||
var axis = new Mock<ILinearAxis>();
|
var axis = new Mock<ILinearAxis>();
|
||||||
|
|||||||
@@ -169,3 +169,258 @@ raySourceService?.Dispose();
|
|||||||
```
|
```
|
||||||
|
|
||||||
确保硬件连接在应用退出时正确断开。
|
确保硬件连接在应用退出时正确断开。
|
||||||
|
|
||||||
|
### 7.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 硬件层 → AppState → UI 状态同步机制
|
||||||
|
|
||||||
|
## 整体结论
|
||||||
|
|
||||||
|
| 硬件子系统 | 同步方式 | AppState 订阅 | 状态是否自动同步 |
|
||||||
|
|-----------|---------|--------------|----------------|
|
||||||
|
| 运动控制(Motion) | 事件驱动 + 轮询 | ✅ 已订阅 `GeometryUpdatedEvent` / `AxisStatusChangedEvent` | ✅ 自动同步 |
|
||||||
|
| 射线源(RaySource) | 事件定义存在 | ❌ 未订阅任何事件 | ⚠️ 需手动推送 |
|
||||||
|
| 探测器(Detector) | 事件定义存在 | ❌ 未订阅任何事件 | ⚠️ 需手动推送 |
|
||||||
|
| 相机(Camera) | ViewModel 直连 | ❌ 不经过 AppState | ⚠️ `CameraState` 为空壳 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、运动控制 — 完整链路
|
||||||
|
|
||||||
|
### 1.1 数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
PLC 硬件(B&R)
|
||||||
|
└─ IPlcService.IsConnected(轮询前置检查)
|
||||||
|
└─ MotionControlService.OnPollingTick() [System.Threading.Timer,周期 = PollingInterval ms]
|
||||||
|
├─ _motionSystem.UpdateAllStatus() ← 从 PLC 读取所有轴实际位置
|
||||||
|
├─ GetCurrentGeometry() ← 正算 FOD / FDD / Magnification
|
||||||
|
├─ 发布 GeometryUpdatedEvent ─┐
|
||||||
|
└─ 发布 AxisStatusChangedEvent ─┤
|
||||||
|
↓
|
||||||
|
AppStateService(构造时订阅两个事件)
|
||||||
|
├─ OnGeometryUpdated()
|
||||||
|
└─ OnAxisStatusChanged()
|
||||||
|
└─ TryRefreshMotionStateFromHardware()
|
||||||
|
└─ BuildMotionStateSnapshot()
|
||||||
|
├─ 读取所有轴 ActualPosition / ActualAngle
|
||||||
|
└─ SetMotionState()
|
||||||
|
└─ 触发 MotionStateChanged 事件
|
||||||
|
└─ ViewModel 绑定自动刷新 UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 关键实现细节
|
||||||
|
|
||||||
|
**轮询启动**:`MotionControlService.StartPolling()` 需在应用启动时显式调用,否则轮询不会运行。
|
||||||
|
|
||||||
|
**PLC 未连接时的保护**:
|
||||||
|
```csharp
|
||||||
|
// MotionControlService.OnPollingTick()
|
||||||
|
if (!_plcService.IsConnected) return; // 直接跳过,不报错
|
||||||
|
```
|
||||||
|
|
||||||
|
**连续错误降频**:
|
||||||
|
```csharp
|
||||||
|
if (_pollErrorCount > 3)
|
||||||
|
{
|
||||||
|
if (++_pollErrorCount % 50 != 0) return; // 每 50 次才尝试一次,防止日志刷屏
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**AppStateService 初始化时的首次刷新**:
|
||||||
|
```csharp
|
||||||
|
// AppStateService 构造函数末尾
|
||||||
|
private void SubscribeToExistingServices()
|
||||||
|
{
|
||||||
|
if (TryRefreshMotionStateFromHardware("initialization"))
|
||||||
|
_logger.Info("AppStateService subscribed to motion hardware state");
|
||||||
|
else
|
||||||
|
_logger.Warn("AppStateService could not initialize motion state from hardware");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
PLC 未连接时 warn 但不崩溃,等待后续轮询事件触发再同步。
|
||||||
|
|
||||||
|
**`UpdateMotionState()` 的特殊行为**:
|
||||||
|
```csharp
|
||||||
|
public void UpdateMotionState(MotionState newState)
|
||||||
|
{
|
||||||
|
// 优先从硬件层拉取最新快照,忽略外部传入值
|
||||||
|
if (TryRefreshMotionStateFromHardware("UpdateMotionState"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 硬件不可用时才使用传入值(降级路径)
|
||||||
|
SetMotionState(newState);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
硬件连接后,外部调用 `UpdateMotionState()` 实际上会被硬件快照覆盖,硬件层始终是 `MotionState` 的唯一真实来源。
|
||||||
|
|
||||||
|
### 1.3 事件清单
|
||||||
|
|
||||||
|
| 事件 | 发布方 | 订阅方 | 触发时机 |
|
||||||
|
|------|--------|--------|---------|
|
||||||
|
| `GeometryUpdatedEvent` | `MotionControlService` | `AppStateService`、`MotionControlViewModel`、`AxisControlViewModel` | 每次轮询 tick |
|
||||||
|
| `AxisStatusChangedEvent` | `MotionControlService` | `AppStateService`、`MotionControlViewModel`、`AxisControlViewModel` | 轴状态发生变化时 |
|
||||||
|
| `MotionErrorEvent` | `MotionControlService` | — | 轴状态变为 Error / Alarm |
|
||||||
|
| `DoorStatusChangedEvent` | `MotionControlService` | `MotionControlViewModel` | 安全门状态变化 |
|
||||||
|
| `DoorInterlockChangedEvent` | `MotionControlService` | — | 联锁状态变化 |
|
||||||
|
| `GeometryApplyRequestEvent` | DebugWindow | `MotionControlViewModel` | 调试窗口发起几何反算请求 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、射线源 — 断链(待补全)
|
||||||
|
|
||||||
|
### 2.1 现状
|
||||||
|
|
||||||
|
`AppStateService` 持有 `IRaySourceService` 引用,但**没有订阅任何射线源事件**。`RaySourceState` 只能通过外部显式调用 `UpdateRaySourceState()` 手动推送,硬件状态变化不会自动反映到 AppState。
|
||||||
|
|
||||||
|
```
|
||||||
|
XP.Hardware.RaySource 发布的事件(均无人在 AppState 层订阅):
|
||||||
|
├─ StatusUpdatedEvent ← 实时电压 / 电流 / 功率数据
|
||||||
|
├─ RaySourceStatusChangedEvent ← 三态状态(On / Off / Fault)
|
||||||
|
├─ VariablesConnectedEvent ← PLC 变量连接状态
|
||||||
|
└─ OperationResultEvent ← 操作执行结果
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 当前 `RaySourceState` 的写入路径
|
||||||
|
|
||||||
|
目前只有两处会更新 `RaySourceState`:
|
||||||
|
|
||||||
|
1. `CncProgramService.CreateReferencePointNode()` — 创建参考点时读取一次快照
|
||||||
|
2. 任何直接调用 `appStateService.UpdateRaySourceState()` 的地方(目前代码中无此调用)
|
||||||
|
|
||||||
|
### 2.3 修复方案
|
||||||
|
|
||||||
|
在 `AppStateService` 构造时订阅 `StatusUpdatedEvent`,将实时数据映射到 `RaySourceState`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// AppStateService 构造函数中补充
|
||||||
|
_raySourceStatusToken = _eventAggregator
|
||||||
|
.GetEvent<StatusUpdatedEvent>()
|
||||||
|
.Subscribe(OnRaySourceStatusUpdated);
|
||||||
|
|
||||||
|
// 处理方法
|
||||||
|
private void OnRaySourceStatusUpdated(SystemStatusData data)
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
UpdateRaySourceState(new RaySourceState(
|
||||||
|
IsOn: data.IsOn,
|
||||||
|
Voltage: data.Voltage,
|
||||||
|
Power: data.Power));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
同时在 `Dispose()` 中取消订阅,并在 `AppStateService` 的字段和 `Dispose` 方法中补充对应的 `SubscriptionToken`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、探测器 — 断链(待补全)
|
||||||
|
|
||||||
|
### 3.1 现状
|
||||||
|
|
||||||
|
与射线源情况相同,`AppStateService` 没有订阅任何探测器事件,`DetectorState` 是静态默认值。
|
||||||
|
|
||||||
|
```
|
||||||
|
XP.Hardware.Detector 发布的事件(均无人在 AppState 层订阅):
|
||||||
|
├─ StatusChangedEvent ← 探测器连接 / 采集状态变化
|
||||||
|
├─ ImageCapturedEvent ← 图像采集完成
|
||||||
|
├─ CorrectionCompletedEvent ← 校正完成
|
||||||
|
└─ ErrorOccurredEvent ← 错误发生
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 修复方案
|
||||||
|
|
||||||
|
在 `AppStateService` 构造时订阅 `StatusChangedEvent`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
_detectorStatusToken = _eventAggregator
|
||||||
|
.GetEvent<XP.Hardware.Detector.Abstractions.Events.StatusChangedEvent>()
|
||||||
|
.Subscribe(OnDetectorStatusChanged);
|
||||||
|
|
||||||
|
private void OnDetectorStatusChanged(DetectorStatus status)
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
UpdateDetectorState(new DetectorState(
|
||||||
|
IsConnected: status.IsConnected,
|
||||||
|
IsAcquiring: status.IsAcquiring,
|
||||||
|
FrameRate: status.FrameRate,
|
||||||
|
Resolution: status.Resolution));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、相机 — 独立直连,不经过 AppState
|
||||||
|
|
||||||
|
### 4.1 现状
|
||||||
|
|
||||||
|
相机完全绕过 `AppStateService`,由 `NavigationPropertyPanelViewModel` 直接持有 `ICamera` 引用并订阅硬件事件:
|
||||||
|
|
||||||
|
```
|
||||||
|
NavigationPropertyPanelViewModel
|
||||||
|
└─ 直接持有 ICamera 引用
|
||||||
|
├─ _camera.ImageGrabbed += OnCameraImageGrabbed
|
||||||
|
├─ _camera.GrabError += OnCameraGrabError
|
||||||
|
└─ _camera.ConnectionLost += OnCameraConnectionLost
|
||||||
|
└─ IsCameraConnected 属性直接驱动 UI 命令可用性
|
||||||
|
```
|
||||||
|
|
||||||
|
`AppStateService.CameraState` 字段目前是空壳,没有任何地方向它写入真实数据。
|
||||||
|
|
||||||
|
### 4.2 架构说明
|
||||||
|
|
||||||
|
相机的独立直连是有意为之的设计——相机图像流数据量大、帧率高,不适合经过 AppState 的不可变 record 替换机制。当前图像通过 `ManualImageLoadedEvent` 在模块间传递,与状态管理解耦。
|
||||||
|
|
||||||
|
如需将相机连接状态纳入 AppState 统一管理(例如供其他模块查询),可在 `OnCameraConnectionLost` / `ConnectCamera` 中补充调用 `_appStateService.UpdateCameraState()`,仅同步连接状态,不同步图像帧。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、状态同步全景图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ 硬件层 │
|
||||||
|
│ │
|
||||||
|
│ PLC ──→ MotionControlService ──→ GeometryUpdatedEvent ─────┐ │
|
||||||
|
│ └──→ AxisStatusChangedEvent ────┤ │
|
||||||
|
│ │ │
|
||||||
|
│ RaySource ──→ IRaySourceService ──→ StatusUpdatedEvent ─ ─ ┤ │
|
||||||
|
│ └──→ RaySourceStatusChangedEvent ─┤│
|
||||||
|
│ │ │
|
||||||
|
│ Detector ──→ IDetectorService ──→ StatusChangedEvent ─ ─ ┤ │
|
||||||
|
│ │ │
|
||||||
|
│ Camera ──→ ICamera ──→ ImageGrabbed / ConnectionLost ─ ─ ┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
│ 已接通 ✅ │ 断链 ⚠️
|
||||||
|
↓ ↓
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ AppStateService │
|
||||||
|
│ │
|
||||||
|
│ MotionState ← 自动同步(轮询 + 事件驱动) ✅ │
|
||||||
|
│ RaySourceState ← 需手动推送 / 待补订阅 ⚠️ │
|
||||||
|
│ DetectorState ← 需手动推送 / 待补订阅 ⚠️ │
|
||||||
|
│ CameraState ← 空壳,相机走独立直连路径 ⚠️ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
↓ PropertyChanged / StateChangedEvent
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ ViewModel 层 │
|
||||||
|
│ │
|
||||||
|
│ MotionControlViewModel ← 同时订阅 GeometryUpdatedEvent(双路) │
|
||||||
|
│ CncNodeViewModel ← 通过 IAppStateService 读取快照 │
|
||||||
|
│ CncEditorViewModel ← 通过 IAppStateService 读取快照 │
|
||||||
|
│ NavigationPropertyPanelViewModel ← 直连 ICamera(独立路径) │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、待办事项
|
||||||
|
|
||||||
|
| 优先级 | 项目 | 涉及文件 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| 高 | 补充射线源事件订阅,打通 `RaySourceState` 自动同步 | `AppStateService.cs` |
|
||||||
|
| 高 | 补充探测器事件订阅,打通 `DetectorState` 自动同步 | `AppStateService.cs` |
|
||||||
|
| 中 | 确认 `StartPolling()` 在应用启动流程中被调用 | `App.xaml.cs` / `AppBootstrapper` |
|
||||||
|
| 低 | 评估是否将相机连接状态纳入 `CameraState` 统一管理 | `NavigationPropertyPanelViewModel.cs`、`AppStateService.cs` |
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ using System.Threading;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XP.Hardware.Detector.Abstractions;
|
||||||
|
using XP.Hardware.Detector.Abstractions.Enums;
|
||||||
|
using XP.Hardware.Detector.Abstractions.Events;
|
||||||
|
using XP.Hardware.Detector.Services;
|
||||||
using XP.Hardware.MotionControl.Abstractions;
|
using XP.Hardware.MotionControl.Abstractions;
|
||||||
using XP.Hardware.MotionControl.Abstractions.Enums;
|
using XP.Hardware.MotionControl.Abstractions.Enums;
|
||||||
using XP.Hardware.MotionControl.Abstractions.Events;
|
using XP.Hardware.MotionControl.Abstractions.Events;
|
||||||
@@ -18,6 +22,7 @@ namespace XplorePlane.Services.AppState
|
|||||||
/// Global application state service.
|
/// Global application state service.
|
||||||
/// Motion state is synchronized from the motion hardware service layer and
|
/// Motion state is synchronized from the motion hardware service layer and
|
||||||
/// mapped into the legacy business model for compatibility.
|
/// mapped into the legacy business model for compatibility.
|
||||||
|
/// Detector state and latest frame are synchronized via Prism EventAggregator subscriptions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AppStateService : BindableBase, IAppStateService
|
public class AppStateService : BindableBase, IAppStateService
|
||||||
{
|
{
|
||||||
@@ -25,10 +30,13 @@ namespace XplorePlane.Services.AppState
|
|||||||
private readonly IRaySourceService _raySourceService;
|
private readonly IRaySourceService _raySourceService;
|
||||||
private readonly IMotionSystem _motionSystem;
|
private readonly IMotionSystem _motionSystem;
|
||||||
private readonly IMotionControlService _motionControlService;
|
private readonly IMotionControlService _motionControlService;
|
||||||
|
private readonly IDetectorService _detectorService;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
private readonly SubscriptionToken _axisStatusChangedToken;
|
private readonly SubscriptionToken _axisStatusChangedToken;
|
||||||
private readonly SubscriptionToken _geometryUpdatedToken;
|
private readonly SubscriptionToken _geometryUpdatedToken;
|
||||||
|
private readonly SubscriptionToken _detectorStatusChangedToken;
|
||||||
|
private readonly SubscriptionToken _detectorImageCapturedToken;
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private GeometryData _latestGeometry;
|
private GeometryData _latestGeometry;
|
||||||
@@ -43,6 +51,9 @@ namespace XplorePlane.Services.AppState
|
|||||||
private LinkedViewState _linkedViewState = LinkedViewState.Default;
|
private LinkedViewState _linkedViewState = LinkedViewState.Default;
|
||||||
private RecipeExecutionState _recipeExecutionState = RecipeExecutionState.Default;
|
private RecipeExecutionState _recipeExecutionState = RecipeExecutionState.Default;
|
||||||
|
|
||||||
|
// ── 探测器最新帧(volatile,供任意线程读取)──
|
||||||
|
private volatile ImageCapturedEventArgs _latestDetectorFrame;
|
||||||
|
|
||||||
// ── 类型化状态变更事件 ──
|
// ── 类型化状态变更事件 ──
|
||||||
public event EventHandler<StateChangedEventArgs<MotionState>> MotionStateChanged;
|
public event EventHandler<StateChangedEventArgs<MotionState>> MotionStateChanged;
|
||||||
public event EventHandler<StateChangedEventArgs<RaySourceState>> RaySourceStateChanged;
|
public event EventHandler<StateChangedEventArgs<RaySourceState>> RaySourceStateChanged;
|
||||||
@@ -63,26 +74,36 @@ namespace XplorePlane.Services.AppState
|
|||||||
public LinkedViewState LinkedViewState => _linkedViewState;
|
public LinkedViewState LinkedViewState => _linkedViewState;
|
||||||
public RecipeExecutionState RecipeExecutionState => _recipeExecutionState;
|
public RecipeExecutionState RecipeExecutionState => _recipeExecutionState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器最新采集帧(线程安全,可从任意线程读取)。
|
||||||
|
/// 由 ImageCapturedEvent 驱动更新,无采集时为 null。
|
||||||
|
/// </summary>
|
||||||
|
public ImageCapturedEventArgs LatestDetectorFrame => _latestDetectorFrame;
|
||||||
|
|
||||||
public AppStateService(
|
public AppStateService(
|
||||||
IRaySourceService raySourceService,
|
IRaySourceService raySourceService,
|
||||||
IMotionSystem motionSystem,
|
IMotionSystem motionSystem,
|
||||||
IMotionControlService motionControlService,
|
IMotionControlService motionControlService,
|
||||||
|
IDetectorService detectorService,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
ILoggerService logger)
|
ILoggerService logger)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(raySourceService);
|
ArgumentNullException.ThrowIfNull(raySourceService);
|
||||||
ArgumentNullException.ThrowIfNull(motionSystem);
|
ArgumentNullException.ThrowIfNull(motionSystem);
|
||||||
ArgumentNullException.ThrowIfNull(motionControlService);
|
ArgumentNullException.ThrowIfNull(motionControlService);
|
||||||
|
ArgumentNullException.ThrowIfNull(detectorService);
|
||||||
ArgumentNullException.ThrowIfNull(eventAggregator);
|
ArgumentNullException.ThrowIfNull(eventAggregator);
|
||||||
ArgumentNullException.ThrowIfNull(logger);
|
ArgumentNullException.ThrowIfNull(logger);
|
||||||
|
|
||||||
_raySourceService = raySourceService;
|
_raySourceService = raySourceService;
|
||||||
_motionSystem = motionSystem;
|
_motionSystem = motionSystem;
|
||||||
_motionControlService = motionControlService;
|
_motionControlService = motionControlService;
|
||||||
|
_detectorService = detectorService;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_logger = logger.ForModule<AppStateService>();
|
_logger = logger.ForModule<AppStateService>();
|
||||||
_dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
|
_dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
|
||||||
|
|
||||||
|
// ── 运动控制事件订阅 ──
|
||||||
_geometryUpdatedToken = _eventAggregator
|
_geometryUpdatedToken = _eventAggregator
|
||||||
.GetEvent<GeometryUpdatedEvent>()
|
.GetEvent<GeometryUpdatedEvent>()
|
||||||
.Subscribe(OnGeometryUpdated);
|
.Subscribe(OnGeometryUpdated);
|
||||||
@@ -91,6 +112,16 @@ namespace XplorePlane.Services.AppState
|
|||||||
.GetEvent<AxisStatusChangedEvent>()
|
.GetEvent<AxisStatusChangedEvent>()
|
||||||
.Subscribe(OnAxisStatusChanged);
|
.Subscribe(OnAxisStatusChanged);
|
||||||
|
|
||||||
|
// ── 探测器状态事件订阅(后台线程,避免阻塞采集)──
|
||||||
|
_detectorStatusChangedToken = _eventAggregator
|
||||||
|
.GetEvent<StatusChangedEvent>()
|
||||||
|
.Subscribe(OnDetectorStatusChanged, ThreadOption.BackgroundThread);
|
||||||
|
|
||||||
|
// ── 探测器图像事件订阅(后台线程,仅缓存最新帧)──
|
||||||
|
_detectorImageCapturedToken = _eventAggregator
|
||||||
|
.GetEvent<ImageCapturedEvent>()
|
||||||
|
.Subscribe(OnDetectorImageCaptured, ThreadOption.BackgroundThread);
|
||||||
|
|
||||||
SubscribeToExistingServices();
|
SubscribeToExistingServices();
|
||||||
_logger.Info("AppStateService initialized");
|
_logger.Info("AppStateService initialized");
|
||||||
}
|
}
|
||||||
@@ -260,6 +291,16 @@ namespace XplorePlane.Services.AppState
|
|||||||
_eventAggregator.GetEvent<GeometryUpdatedEvent>().Unsubscribe(_geometryUpdatedToken);
|
_eventAggregator.GetEvent<GeometryUpdatedEvent>().Unsubscribe(_geometryUpdatedToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_detectorStatusChangedToken is not null)
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<StatusChangedEvent>().Unsubscribe(_detectorStatusChangedToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_detectorImageCapturedToken is not null)
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<ImageCapturedEvent>().Unsubscribe(_detectorImageCapturedToken);
|
||||||
|
}
|
||||||
|
|
||||||
MotionStateChanged = null;
|
MotionStateChanged = null;
|
||||||
RaySourceStateChanged = null;
|
RaySourceStateChanged = null;
|
||||||
DetectorStateChanged = null;
|
DetectorStateChanged = null;
|
||||||
@@ -317,6 +358,57 @@ namespace XplorePlane.Services.AppState
|
|||||||
TryRefreshMotionStateFromHardware("geometry-updated");
|
TryRefreshMotionStateFromHardware("geometry-updated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器状态变更回调。
|
||||||
|
/// 将硬件层 DetectorStatus 映射为应用层 DetectorState 并同步到 AppState。
|
||||||
|
/// 运行在后台线程(ThreadOption.BackgroundThread),不阻塞采集。
|
||||||
|
/// </summary>
|
||||||
|
private void OnDetectorStatusChanged(DetectorStatus status)
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
|
||||||
|
// 从 IDetectorService 读取分辨率等补充信息
|
||||||
|
string resolution = string.Empty;
|
||||||
|
double frameRate = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var info = _detectorService.GetInfo();
|
||||||
|
if (info != null)
|
||||||
|
resolution = $"{info.MaxWidth}x{info.MaxHeight}";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 探测器未初始化时 GetInfo 会抛出,忽略即可
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConnected = status != DetectorStatus.Uninitialized && status != DetectorStatus.Error;
|
||||||
|
bool isAcquiring = status == DetectorStatus.Acquiring;
|
||||||
|
|
||||||
|
var newState = new DetectorState(
|
||||||
|
IsConnected: isConnected,
|
||||||
|
IsAcquiring: isAcquiring,
|
||||||
|
FrameRate: frameRate,
|
||||||
|
Resolution: resolution);
|
||||||
|
|
||||||
|
UpdateDetectorState(newState);
|
||||||
|
|
||||||
|
_logger.Info(
|
||||||
|
"探测器状态已同步:{Status} → IsConnected={IsConnected} IsAcquiring={IsAcquiring} | " +
|
||||||
|
"Detector state synced: {Status} → IsConnected={IsConnected} IsAcquiring={IsAcquiring}",
|
||||||
|
status, isConnected, isAcquiring);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器图像采集回调。
|
||||||
|
/// 仅缓存最新帧引用(volatile 写),不做任何图像处理,保持采集链路零阻塞。
|
||||||
|
/// 上层通过 LatestDetectorFrame 属性按需读取。
|
||||||
|
/// </summary>
|
||||||
|
private void OnDetectorImageCaptured(ImageCapturedEventArgs args)
|
||||||
|
{
|
||||||
|
if (_disposed || args?.ImageData == null) return;
|
||||||
|
_latestDetectorFrame = args;
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryRefreshMotionStateFromHardware(string reason)
|
private bool TryRefreshMotionStateFromHardware(string reason)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using XP.Hardware.Detector.Abstractions;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
|
|
||||||
namespace XplorePlane.Services.AppState
|
namespace XplorePlane.Services.AppState
|
||||||
@@ -21,6 +22,13 @@ namespace XplorePlane.Services.AppState
|
|||||||
LinkedViewState LinkedViewState { get; }
|
LinkedViewState LinkedViewState { get; }
|
||||||
RecipeExecutionState RecipeExecutionState { get; }
|
RecipeExecutionState RecipeExecutionState { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器最新采集帧(线程安全,可从任意线程读取)。
|
||||||
|
/// 由 ImageCapturedEvent 驱动更新,探测器未采集时为 null。
|
||||||
|
/// CNC 执行、图像处理等上层服务通过此属性按需取图,无需自行订阅事件。
|
||||||
|
/// </summary>
|
||||||
|
ImageCapturedEventArgs LatestDetectorFrame { get; }
|
||||||
|
|
||||||
// ── 状态更新方法(线程安全,可从任意线程调用)──
|
// ── 状态更新方法(线程安全,可从任意线程调用)──
|
||||||
|
|
||||||
void UpdateMotionState(MotionState newState);
|
void UpdateMotionState(MotionState newState);
|
||||||
|
|||||||
@@ -103,6 +103,63 @@ namespace XplorePlane.Services.Cnc
|
|||||||
{
|
{
|
||||||
switch (node)
|
switch (node)
|
||||||
{
|
{
|
||||||
|
case ReferencePointNode rp:
|
||||||
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
|
"执行参考点节点 [{Index}] {Name} | " +
|
||||||
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
|
"StageRotation={StageRotation} FixtureRotation={FixtureRotation} " +
|
||||||
|
"RayOn={RayOn} Voltage={Voltage}kV Current={Current}uA",
|
||||||
|
rp.Index, rp.Name,
|
||||||
|
rp.StageX, rp.StageY, rp.SourceZ, rp.DetectorZ,
|
||||||
|
rp.DetectorSwing, rp.FDD, rp.FOD, rp.Magnification,
|
||||||
|
rp.StageRotation, rp.FixtureRotation,
|
||||||
|
rp.IsRayOn, rp.Voltage, rp.Current);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SavePositionNode sp:
|
||||||
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
|
"执行保存位置节点 [{Index}] {Name} | " +
|
||||||
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
|
"StageRotation={StageRotation} FixtureRotation={FixtureRotation}",
|
||||||
|
sp.Index, sp.Name,
|
||||||
|
sp.MotionState.StageX, sp.MotionState.StageY,
|
||||||
|
sp.MotionState.SourceZ, sp.MotionState.DetectorZ,
|
||||||
|
sp.MotionState.DetectorSwing, sp.MotionState.FDD,
|
||||||
|
sp.MotionState.FOD, sp.MotionState.Magnification,
|
||||||
|
sp.MotionState.StageRotation, sp.MotionState.FixtureRotation);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SaveNodeNode sn:
|
||||||
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
|
"执行保存节点 [{Index}] {Name} | " +
|
||||||
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
|
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W",
|
||||||
|
sn.Index, sn.Name,
|
||||||
|
sn.MotionState.StageX, sn.MotionState.StageY,
|
||||||
|
sn.MotionState.SourceZ, sn.MotionState.DetectorZ,
|
||||||
|
sn.MotionState.DetectorSwing, sn.MotionState.FDD,
|
||||||
|
sn.MotionState.FOD, sn.MotionState.Magnification,
|
||||||
|
sn.RaySourceState.IsOn, sn.RaySourceState.Voltage, sn.RaySourceState.Power);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SaveNodeWithImageNode sni:
|
||||||
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
|
"执行保存节点(图像) [{Index}] {Name} | " +
|
||||||
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
|
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W ImageFile={ImageFile}",
|
||||||
|
sni.Index, sni.Name,
|
||||||
|
sni.MotionState.StageX, sni.MotionState.StageY,
|
||||||
|
sni.MotionState.SourceZ, sni.MotionState.DetectorZ,
|
||||||
|
sni.MotionState.DetectorSwing, sni.MotionState.FDD,
|
||||||
|
sni.MotionState.FOD, sni.MotionState.Magnification,
|
||||||
|
sni.RaySourceState.IsOn, sni.RaySourceState.Voltage, sni.RaySourceState.Power,
|
||||||
|
sni.ImageFileName);
|
||||||
|
break;
|
||||||
|
|
||||||
case WaitDelayNode waitNode:
|
case WaitDelayNode waitNode:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
private SaveNodeWithImageNode CreateSaveNodeWithImageNode(Guid id, int index)
|
private SaveNodeWithImageNode CreateSaveNodeWithImageNode(Guid id, int index)
|
||||||
{
|
{
|
||||||
return new SaveNodeWithImageNode(
|
return new SaveNodeWithImageNode(
|
||||||
id, index, $"保存节点(图像)_{index}",
|
id, index, $"保存节点_图像_{index}",
|
||||||
MotionState: _appStateService.MotionState,
|
MotionState: _appStateService.MotionState,
|
||||||
RaySourceState: _appStateService.RaySourceState,
|
RaySourceState: _appStateService.RaySourceState,
|
||||||
DetectorState: _appStateService.DetectorState,
|
DetectorState: _appStateService.DetectorState,
|
||||||
|
|||||||
@@ -417,7 +417,7 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("Index,NodeType,Name,StageX,StageY,SourceZ,DetectorZ,DetectorSwing,StageRotation,FixtureRotation,FOD,FDD,Magnification,Voltage_kV,Current_uA,Power_W,RayOn,DetectorConnected,FrameRate,Resolution,ImageFile,MarkerType,MarkerX,MarkerY,DialogTitle,DialogMessage,DelayMs,Pipeline");
|
sb.AppendLine("Index,NodeType,Name,SourceZ,DetectorZ,StageX,StageY,DetectorSwing,StageRotation,FixtureRotation,FOD,FDD,Magnification,Voltage_kV,Current_uA,Power_W,RayOn,DetectorConnected,FrameRate,Resolution,ImageFile,MarkerType,MarkerX,MarkerY,DialogTitle,DialogMessage,DelayMs,Pipeline");
|
||||||
|
|
||||||
var inv = CultureInfo.InvariantCulture;
|
var inv = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
@@ -425,10 +425,10 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
{
|
{
|
||||||
var row = node switch
|
var row = node switch
|
||||||
{
|
{
|
||||||
ReferencePointNode rp => $"{rp.Index},{rp.NodeType},{Esc(rp.Name)},{rp.StageX.ToString(inv)},{rp.StageY.ToString(inv)},{rp.SourceZ.ToString(inv)},{rp.DetectorZ.ToString(inv)},{rp.DetectorSwing.ToString(inv)},{rp.StageRotation.ToString(inv)},{rp.FixtureRotation.ToString(inv)},{rp.FOD.ToString(inv)},{rp.FDD.ToString(inv)},{rp.Magnification.ToString(inv)},{rp.Voltage.ToString(inv)},{rp.Current.ToString(inv)},,{rp.IsRayOn},,,,,,,,,,",
|
ReferencePointNode rp => $"{rp.Index},{rp.NodeType},{Esc(rp.Name)},{rp.SourceZ.ToString(inv)},{rp.DetectorZ.ToString(inv)},{rp.StageX.ToString(inv)},{rp.StageY.ToString(inv)},{rp.DetectorSwing.ToString(inv)},{rp.StageRotation.ToString(inv)},{rp.FixtureRotation.ToString(inv)},{rp.FOD.ToString(inv)},{rp.FDD.ToString(inv)},{rp.Magnification.ToString(inv)},{rp.Voltage.ToString(inv)},{rp.Current.ToString(inv)},,{rp.IsRayOn},,,,,,,,,,",
|
||||||
SaveNodeWithImageNode sni => $"{sni.Index},{sni.NodeType},{Esc(sni.Name)},{sni.MotionState.StageX.ToString(inv)},{sni.MotionState.StageY.ToString(inv)},{sni.MotionState.SourceZ.ToString(inv)},{sni.MotionState.DetectorZ.ToString(inv)},{sni.MotionState.DetectorSwing.ToString(inv)},{sni.MotionState.StageRotation.ToString(inv)},{sni.MotionState.FixtureRotation.ToString(inv)},{sni.MotionState.FOD.ToString(inv)},{sni.MotionState.FDD.ToString(inv)},{sni.MotionState.Magnification.ToString(inv)},{sni.RaySourceState.Voltage.ToString(inv)},,{sni.RaySourceState.Power.ToString(inv)},{sni.RaySourceState.IsOn},{sni.DetectorState.IsConnected},{sni.DetectorState.FrameRate.ToString(inv)},{Esc(sni.DetectorState.Resolution)},{Esc(sni.ImageFileName)},,,,,,",
|
SaveNodeWithImageNode sni => $"{sni.Index},{sni.NodeType},{Esc(sni.Name)},{sni.MotionState.SourceZ.ToString(inv)},{sni.MotionState.DetectorZ.ToString(inv)},{sni.MotionState.StageX.ToString(inv)},{sni.MotionState.StageY.ToString(inv)},{sni.MotionState.DetectorSwing.ToString(inv)},{sni.MotionState.StageRotation.ToString(inv)},{sni.MotionState.FixtureRotation.ToString(inv)},{sni.MotionState.FOD.ToString(inv)},{sni.MotionState.FDD.ToString(inv)},{sni.MotionState.Magnification.ToString(inv)},{sni.RaySourceState.Voltage.ToString(inv)},,{sni.RaySourceState.Power.ToString(inv)},{sni.RaySourceState.IsOn},{sni.DetectorState.IsConnected},{sni.DetectorState.FrameRate.ToString(inv)},{Esc(sni.DetectorState.Resolution)},{Esc(sni.ImageFileName)},,,,,,",
|
||||||
SaveNodeNode sn => $"{sn.Index},{sn.NodeType},{Esc(sn.Name)},{sn.MotionState.StageX.ToString(inv)},{sn.MotionState.StageY.ToString(inv)},{sn.MotionState.SourceZ.ToString(inv)},{sn.MotionState.DetectorZ.ToString(inv)},{sn.MotionState.DetectorSwing.ToString(inv)},{sn.MotionState.StageRotation.ToString(inv)},{sn.MotionState.FixtureRotation.ToString(inv)},{sn.MotionState.FOD.ToString(inv)},{sn.MotionState.FDD.ToString(inv)},{sn.MotionState.Magnification.ToString(inv)},{sn.RaySourceState.Voltage.ToString(inv)},,{sn.RaySourceState.Power.ToString(inv)},{sn.RaySourceState.IsOn},{sn.DetectorState.IsConnected},{sn.DetectorState.FrameRate.ToString(inv)},{Esc(sn.DetectorState.Resolution)},,,,,,,",
|
SaveNodeNode sn => $"{sn.Index},{sn.NodeType},{Esc(sn.Name)},{sn.MotionState.SourceZ.ToString(inv)},{sn.MotionState.DetectorZ.ToString(inv)},{sn.MotionState.StageX.ToString(inv)},{sn.MotionState.StageY.ToString(inv)},{sn.MotionState.DetectorSwing.ToString(inv)},{sn.MotionState.StageRotation.ToString(inv)},{sn.MotionState.FixtureRotation.ToString(inv)},{sn.MotionState.FOD.ToString(inv)},{sn.MotionState.FDD.ToString(inv)},{sn.MotionState.Magnification.ToString(inv)},{sn.RaySourceState.Voltage.ToString(inv)},,{sn.RaySourceState.Power.ToString(inv)},{sn.RaySourceState.IsOn},{sn.DetectorState.IsConnected},{sn.DetectorState.FrameRate.ToString(inv)},{Esc(sn.DetectorState.Resolution)},,,,,,,",
|
||||||
SavePositionNode sp => $"{sp.Index},{sp.NodeType},{Esc(sp.Name)},{sp.MotionState.StageX.ToString(inv)},{sp.MotionState.StageY.ToString(inv)},{sp.MotionState.SourceZ.ToString(inv)},{sp.MotionState.DetectorZ.ToString(inv)},{sp.MotionState.DetectorSwing.ToString(inv)},{sp.MotionState.StageRotation.ToString(inv)},{sp.MotionState.FixtureRotation.ToString(inv)},{sp.MotionState.FOD.ToString(inv)},{sp.MotionState.FDD.ToString(inv)},{sp.MotionState.Magnification.ToString(inv)},,,,,,,,,,,,,,",
|
SavePositionNode sp => $"{sp.Index},{sp.NodeType},{Esc(sp.Name)},{sp.MotionState.SourceZ.ToString(inv)},{sp.MotionState.DetectorZ.ToString(inv)},{sp.MotionState.StageX.ToString(inv)},{sp.MotionState.StageY.ToString(inv)},{sp.MotionState.DetectorSwing.ToString(inv)},{sp.MotionState.StageRotation.ToString(inv)},{sp.MotionState.FixtureRotation.ToString(inv)},{sp.MotionState.FOD.ToString(inv)},{sp.MotionState.FDD.ToString(inv)},{sp.MotionState.Magnification.ToString(inv)},,,,,,,,,,,,,,",
|
||||||
InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}",
|
InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}",
|
||||||
InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,",
|
InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,",
|
||||||
PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
|
PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
|
||||||
|
|||||||
@@ -489,43 +489,43 @@
|
|||||||
Visibility="{Binding SelectedNode.IsMotionSnapshotNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
Visibility="{Binding SelectedNode.IsMotionSnapshotNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
<UniformGrid Margin="8,8,8,6" Columns="2">
|
<UniformGrid Margin="8,8,8,6" Columns="2">
|
||||||
<StackPanel Margin="0,0,6,0">
|
<StackPanel Margin="0,0,6,0">
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="StageX" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="载物台 X (μm)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageX, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageX, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="StageY" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="载物台 Y (μm)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageY, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageY, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="0,0,6,0">
|
<StackPanel Margin="0,0,6,0">
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="SourceZ" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="射线源 Z (μm)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.SourceZ, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.SourceZ, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="DetectorZ" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="探测器 Z (μm)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DetectorZ, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DetectorZ, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="0,0,6,0">
|
<StackPanel Margin="0,0,6,0">
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="DetectorSwing" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="探测器摆动 (°)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DetectorSwing, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DetectorSwing, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="StageRotation" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="载物台旋转 (°)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageRotation, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageRotation, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="0,0,6,0">
|
<StackPanel Margin="0,0,6,0">
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="FixtureRotation" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="夹具旋转 (°)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FixtureRotation, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FixtureRotation, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="FOD" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="FOD (μm)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FOD, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FOD, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="0,0,6,0">
|
<StackPanel Margin="0,0,6,0">
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="FDD" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="FDD (μm)" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FDD, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FDD, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Style="{StaticResource LabelStyle}" Text="Magnification" />
|
<TextBlock Style="{StaticResource LabelStyle}" Text="放大倍率" />
|
||||||
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Magnification, UpdateSourceTrigger=LostFocus}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Magnification, UpdateSourceTrigger=LostFocus}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</UniformGrid>
|
</UniformGrid>
|
||||||
|
|||||||
Reference in New Issue
Block a user