diff --git a/XplorePlane.Tests/Models/StateModelsTests.cs b/XplorePlane.Tests/Models/StateModelsTests.cs
index 6a96afa..f436a04 100644
--- a/XplorePlane.Tests/Models/StateModelsTests.cs
+++ b/XplorePlane.Tests/Models/StateModelsTests.cs
@@ -20,21 +20,25 @@ namespace XplorePlane.Tests.Models
public void MotionState_Default_AllZeros()
{
var state = MotionState.Default;
- _output.WriteLine($"MotionState.Default: XM={state.XM}, YM={state.YM}, ZT={state.ZT}, ZD={state.ZD}, TiltD={state.TiltD}, Dist={state.Dist}");
- _output.WriteLine($" Speeds: XM={state.XMSpeed}, YM={state.YMSpeed}, ZT={state.ZTSpeed}, ZD={state.ZDSpeed}, TiltD={state.TiltDSpeed}, Dist={state.DistSpeed}");
+ _output.WriteLine($"MotionState.Default: StageX={state.StageX}, StageY={state.StageY}, SourceZ={state.SourceZ}, DetectorZ={state.DetectorZ}, DetectorSwing={state.DetectorSwing}, FDD={state.FDD}");
+ _output.WriteLine($" Speeds: StageX={state.StageXSpeed}, StageY={state.StageYSpeed}, SourceZ={state.SourceZSpeed}, DetectorZ={state.DetectorZSpeed}, DetectorSwing={state.DetectorSwingSpeed}, FDD={state.FDDSpeed}");
- Assert.Equal(0, state.XM);
- Assert.Equal(0, state.YM);
- Assert.Equal(0, state.ZT);
- Assert.Equal(0, state.ZD);
- Assert.Equal(0, state.TiltD);
- Assert.Equal(0, state.Dist);
- Assert.Equal(0, state.XMSpeed);
- Assert.Equal(0, state.YMSpeed);
- Assert.Equal(0, state.ZTSpeed);
- Assert.Equal(0, state.ZDSpeed);
- Assert.Equal(0, state.TiltDSpeed);
- Assert.Equal(0, state.DistSpeed);
+ Assert.Equal(0, state.StageX);
+ Assert.Equal(0, state.StageY);
+ Assert.Equal(0, state.SourceZ);
+ Assert.Equal(0, state.DetectorZ);
+ Assert.Equal(0, state.DetectorSwing);
+ Assert.Equal(0, state.FDD);
+ Assert.Equal(0, state.StageXSpeed);
+ Assert.Equal(0, state.StageYSpeed);
+ Assert.Equal(0, state.SourceZSpeed);
+ Assert.Equal(0, state.DetectorZSpeed);
+ Assert.Equal(0, state.DetectorSwingSpeed);
+ Assert.Equal(0, state.FDDSpeed);
+ Assert.Equal(0, state.StageRotation);
+ Assert.Equal(0, state.FixtureRotation);
+ Assert.Equal(0, state.FOD);
+ Assert.Equal(0, state.Magnification);
}
[Fact]
@@ -116,15 +120,15 @@ namespace XplorePlane.Tests.Models
public void MotionState_WithExpression_ProducesNewInstance()
{
var original = MotionState.Default;
- var modified = original with { XM = 100 };
- _output.WriteLine($"Original.XM={original.XM}, Modified.XM={modified.XM}, SameRef={ReferenceEquals(original, modified)}");
+ var modified = original with { StageX = 100 };
+ _output.WriteLine($"Original.StageX={original.StageX}, Modified.StageX={modified.StageX}, SameRef={ReferenceEquals(original, modified)}");
// New instance is different from original
Assert.NotSame(original, modified);
- Assert.Equal(100, modified.XM);
+ Assert.Equal(100, modified.StageX);
// Original is unchanged
- Assert.Equal(0, original.XM);
+ Assert.Equal(0, original.StageX);
}
// ── CalibrationMatrix Transform Tests ─────────────────────────
@@ -168,4 +172,4 @@ namespace XplorePlane.Tests.Models
Assert.Equal(0, z, precision: 10);
}
}
-}
\ No newline at end of file
+}
diff --git a/XplorePlane.Tests/Services/AppStateServiceTests.cs b/XplorePlane.Tests/Services/AppStateServiceTests.cs
index 9e94c1d..8513d13 100644
--- a/XplorePlane.Tests/Services/AppStateServiceTests.cs
+++ b/XplorePlane.Tests/Services/AppStateServiceTests.cs
@@ -1,7 +1,15 @@
using Moq;
+using Prism.Events;
using System;
using System.Windows;
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.Enums;
+using XP.Hardware.MotionControl.Abstractions.Events;
+using XP.Hardware.MotionControl.Services;
using XP.Hardware.RaySource.Services;
using XplorePlane.Models;
using XplorePlane.Services.AppState;
@@ -11,30 +19,82 @@ using Xunit.Abstractions;
namespace XplorePlane.Tests.Services
{
///
- /// AppStateService 单元测试。
- /// 验证默认状态值、Dispose 后行为、null 参数校验、CalibrationMatrix 缺失时的错误处理。
+ /// AppStateService unit tests.
+ /// Verifies default values, null guards, dispose behavior, and hardware-driven motion-state sync.
///
public class AppStateServiceTests : IDisposable
{
private readonly AppStateService _service;
private readonly Mock _mockRaySource;
+ private readonly Mock _mockMotionSystem;
+ private readonly Mock _mockMotionControlService;
+ private readonly Mock _mockDetectorService;
+ private readonly Mock _mockStageX;
+ private readonly Mock _mockStageY;
+ private readonly Mock _mockSourceZ;
+ private readonly Mock _mockDetectorZ;
+ private readonly Mock _mockDetectorSwing;
+ private readonly Mock _mockStageRotation;
+ private readonly Mock _mockFixtureRotation;
private readonly Mock _mockLogger;
+ private readonly EventAggregator _eventAggregator;
private readonly ITestOutputHelper _output;
public AppStateServiceTests(ITestOutputHelper output)
{
_output = output;
- // Ensure WPF Application exists for Dispatcher
if (Application.Current == null)
{
new Application();
}
_mockRaySource = new Mock();
+ _mockMotionSystem = new Mock();
+ _mockMotionControlService = new Mock();
+ _mockDetectorService = new Mock();
+ _mockStageX = CreateLinearAxis(AxisId.StageX, 0);
+ _mockStageY = CreateLinearAxis(AxisId.StageY, 0);
+ _mockSourceZ = CreateLinearAxis(AxisId.SourceZ, 0);
+ _mockDetectorZ = CreateLinearAxis(AxisId.DetectorZ, 0);
+ _mockDetectorSwing = CreateRotaryAxis(RotaryAxisId.DetectorSwing, 0);
+ _mockStageRotation = CreateRotaryAxis(RotaryAxisId.StageRotation, 0);
+ _mockFixtureRotation = CreateRotaryAxis(RotaryAxisId.FixtureRotation, 0);
_mockLogger = new Mock();
+ _eventAggregator = new EventAggregator();
+
+ _mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.StageX)).Returns(_mockStageX.Object);
+ _mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.StageY)).Returns(_mockStageY.Object);
+ _mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.SourceZ)).Returns(_mockSourceZ.Object);
+ _mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.DetectorZ)).Returns(_mockDetectorZ.Object);
+ _mockMotionSystem.Setup(x => x.GetRotaryAxis(RotaryAxisId.DetectorSwing)).Returns(_mockDetectorSwing.Object);
+ _mockMotionSystem.Setup(x => x.GetRotaryAxis(RotaryAxisId.StageRotation)).Returns(_mockStageRotation.Object);
+ _mockMotionSystem.Setup(x => x.GetRotaryAxis(RotaryAxisId.FixtureRotation)).Returns(_mockFixtureRotation.Object);
+
+ _mockMotionControlService
+ .Setup(x => x.GetCurrentGeometry())
+ .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()).Returns(_mockLogger.Object);
- _service = new AppStateService(_mockRaySource.Object, _mockLogger.Object);
+
+ _service = new AppStateService(
+ _mockRaySource.Object,
+ _mockMotionSystem.Object,
+ _mockMotionControlService.Object,
+ _mockDetectorService.Object,
+ _eventAggregator,
+ _mockLogger.Object);
}
public void Dispose()
@@ -42,13 +102,15 @@ namespace XplorePlane.Tests.Services
_service.Dispose();
}
- // ── 默认状态值验证 ──
-
[Fact]
- public void DefaultState_MotionState_IsDefault()
+ public void DefaultState_MotionState_IsHardwareSnapshot()
{
- _output.WriteLine($"MotionState == MotionState.Default: {ReferenceEquals(MotionState.Default, _service.MotionState)}");
- Assert.Same(MotionState.Default, _service.MotionState);
+ Assert.Equal(0, _service.MotionState.StageX);
+ Assert.Equal(0, _service.MotionState.StageY);
+ Assert.Equal(0, _service.MotionState.SourceZ);
+ Assert.Equal(0, _service.MotionState.DetectorZ);
+ Assert.Equal(0, _service.MotionState.DetectorSwing);
+ Assert.Equal(0, _service.MotionState.FDD);
}
[Fact]
@@ -72,8 +134,6 @@ namespace XplorePlane.Tests.Services
Assert.Null(_service.CalibrationMatrix);
}
- // ── null 参数抛出 ArgumentNullException ──
-
[Fact]
public void UpdateMotionState_NullArgument_ThrowsArgumentNullException()
{
@@ -102,36 +162,117 @@ namespace XplorePlane.Tests.Services
_output.WriteLine($"UpdateSystemState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
- // ── Dispose 后 Update 被忽略 ──
-
[Fact]
public void Dispose_ThenUpdate_IsIgnored()
{
var originalState = _service.MotionState;
_service.Dispose();
- _output.WriteLine("Service disposed, attempting UpdateMotionState...");
- // Should not throw, and state should remain unchanged
var newState = new MotionState(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
_service.UpdateMotionState(newState);
- _output.WriteLine($"State unchanged after dispose: {ReferenceEquals(originalState, _service.MotionState)}");
Assert.Same(originalState, _service.MotionState);
}
- // ── CalibrationMatrix 为 null 时 RequestLinkedView 设置错误状态 ──
-
[Fact]
public void RequestLinkedView_NoCalibrationMatrix_SetsErrorState()
{
- // CalibrationMatrix is null by default
Assert.Null(_service.CalibrationMatrix);
_service.RequestLinkedView(100.0, 200.0);
- _output.WriteLine($"RequestLinkedView(100, 200) without CalibrationMatrix: HasError={_service.SystemState.HasError}, ErrorMessage='{_service.SystemState.ErrorMessage}'");
Assert.True(_service.SystemState.HasError);
Assert.NotEmpty(_service.SystemState.ErrorMessage);
}
+
+ [Fact]
+ public void GeometryUpdatedEvent_RefreshesMotionStateFromHardware()
+ {
+ _mockStageX.SetupGet(x => x.ActualPosition).Returns(12.5);
+ _mockStageY.SetupGet(x => x.ActualPosition).Returns(34.5);
+ _mockSourceZ.SetupGet(x => x.ActualPosition).Returns(56.5);
+ _mockDetectorZ.SetupGet(x => x.ActualPosition).Returns(78.5);
+ _mockDetectorSwing.SetupGet(x => x.ActualAngle).Returns(9.5);
+
+ _eventAggregator.GetEvent()
+ .Publish(new GeometryData(100, 222.2, 2.22));
+
+ Assert.Equal(12.5, _service.MotionState.StageX);
+ Assert.Equal(34.5, _service.MotionState.StageY);
+ Assert.Equal(56.5, _service.MotionState.SourceZ);
+ Assert.Equal(78.5, _service.MotionState.DetectorZ);
+ Assert.Equal(9.5, _service.MotionState.DetectorSwing);
+ 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()
+ .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()
+ .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().Publish(args);
+
+ // 等待后台线程处理
+ System.Threading.Thread.Sleep(100);
+
+ Assert.Same(args, _service.LatestDetectorFrame);
+ }
+
+ private static Mock CreateLinearAxis(AxisId axisId, double position)
+ {
+ var axis = new Mock();
+ axis.SetupGet(x => x.Id).Returns(axisId);
+ axis.SetupGet(x => x.ActualPosition).Returns(position);
+ axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
+ return axis;
+ }
+
+ private static Mock CreateRotaryAxis(RotaryAxisId axisId, double angle)
+ {
+ var axis = new Mock();
+ axis.SetupGet(x => x.Id).Returns(axisId);
+ axis.SetupGet(x => x.ActualAngle).Returns(angle);
+ axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
+ axis.SetupGet(x => x.Enabled).Returns(true);
+ return axis;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/XplorePlane.Tests/Services/RecipeServiceTests.cs b/XplorePlane.Tests/Services/RecipeServiceTests.cs
index a7e24f5..031dcae 100644
--- a/XplorePlane.Tests/Services/RecipeServiceTests.cs
+++ b/XplorePlane.Tests/Services/RecipeServiceTests.cs
@@ -96,7 +96,7 @@ namespace XplorePlane.Tests.Services
var pipeline = new PipelineModel { Name = "TestPipeline" };
var step = _service.RecordCurrentStep(recipe, pipeline);
- _output.WriteLine($"RecordCurrentStep: StepIndex={step.StepIndex}, MotionState.XM={step.MotionState.XM}, RaySource.Voltage={step.RaySourceState.Voltage}, Detector.Resolution={step.DetectorState.Resolution}, Pipeline={step.Pipeline.Name}");
+ _output.WriteLine($"RecordCurrentStep: StepIndex={step.StepIndex}, MotionState.StageX={step.MotionState.StageX}, RaySource.Voltage={step.RaySourceState.Voltage}, Detector.Resolution={step.DetectorState.Resolution}, Pipeline={step.Pipeline.Name}");
Assert.Equal(0, step.StepIndex);
Assert.Same(motionState, step.MotionState);
@@ -138,4 +138,4 @@ namespace XplorePlane.Tests.Services
}
}
}
-}
\ No newline at end of file
+}
diff --git a/XplorePlane.Tests/ViewModels/CncEditorViewModelTests.cs b/XplorePlane.Tests/ViewModels/CncEditorViewModelTests.cs
index 3282f6b..5dba882 100644
--- a/XplorePlane.Tests/ViewModels/CncEditorViewModelTests.cs
+++ b/XplorePlane.Tests/ViewModels/CncEditorViewModelTests.cs
@@ -25,7 +25,7 @@ namespace XplorePlane.Tests.ViewModels
{
public class CncEditorViewModelTests
{
- // 鈹€鈹€ Helpers 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
+ // ── Helpers ──────────────────────────────────────────────────────────────────
private static CncEditorViewModel CreateVm(
Mock mockExecSvc = null,
@@ -50,7 +50,7 @@ namespace XplorePlane.Tests.ViewModels
DateTime.UtcNow, DateTime.UtcNow,
new List
{
- new ReferencePointNode(Guid.NewGuid(), 0, "鍙傝€冪偣_0", 0, 0, 0, 0, 0, 0, false, 0, 0)
+ new ReferencePointNode(Guid.NewGuid(), 0, "参考点_0", 0, 0, 0, 0, 0, 0, false, 0, 0)
}.AsReadOnly()));
mockCncProgramSvc
@@ -121,9 +121,9 @@ namespace XplorePlane.Tests.ViewModels
return new CncProgram(Guid.NewGuid(), "TestProgram", DateTime.UtcNow, DateTime.UtcNow, nodes);
}
- // 鈹€鈹€ Property 1: 杩愯/鍋滄鎸夐挳鐘舵€佷簰鏂?鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
+ // ── Property 1: 运行/停止按钮状态互斥 ──────────────────────────────────────────
- // Feature: cnc-run-execution, Property 1: 杩愯/鍋滄鎸夐挳鐘舵€佷簰鏂?
+ // Feature: cnc-run-execution, Property 1: 运行/停止按钮状态互斥
// Validates: Requirements 1.1, 1.3, 1.4
[Property(MaxTest = 100)]
public Property RunStop_Commands_AreMutuallyExclusive()
@@ -168,9 +168,9 @@ namespace XplorePlane.Tests.ViewModels
});
}
- // 鈹€鈹€ Property 2: 鎵ц瀹屾垚鍚庣姸鎬侀噸缃?鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
+ // ── Property 2: 执行完成后状态重置 ──────────────────────────────────────────────
- // Feature: cnc-run-execution, Property 2: 鎵ц瀹屾垚鍚庣姸鎬侀噸缃?
+ // Feature: cnc-run-execution, Property 2: 执行完成后状态重置
// Validates: Requirements 1.7, 6.5
[Property(MaxTest = 100)]
public Property AfterExecution_IsRunningFalse_AllNodesIdle()
@@ -219,9 +219,9 @@ namespace XplorePlane.Tests.ViewModels
});
}
- // 鈹€鈹€ Property 12: 鎵ц涓紪杈戝懡浠ゅ叏閮ㄧ鐢?鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
+ // ── Property 12: 执行中编辑命令全部禁用 ──────────────────────────────────────────
- // Feature: cnc-run-execution, Property 12: 鎵ц涓紪杈戝懡浠ゅ叏閮ㄧ鐢?
+ // Feature: cnc-run-execution, Property 12: 执行中编辑命令全部禁用
// Validates: Requirements 6.7
[Property(MaxTest = 100)]
public Property WhileRunning_AllEditCommands_AreDisabled()
diff --git a/XplorePlane/Doc/硬件层及UI集成技术路线.md b/XplorePlane/Doc/硬件层及UI集成技术路线.md
index 791baeb..ede56ae 100644
Binary files a/XplorePlane/Doc/硬件层及UI集成技术路线.md and b/XplorePlane/Doc/硬件层及UI集成技术路线.md differ
diff --git a/XplorePlane/Models/CncModels.cs b/XplorePlane/Models/CncModels.cs
index 3ab9979..7418b1d 100644
--- a/XplorePlane/Models/CncModels.cs
+++ b/XplorePlane/Models/CncModels.cs
@@ -40,69 +40,89 @@ namespace XplorePlane.Models
Guid Id,
int Index,
CncNodeType NodeType,
- string Name
- );
+ string Name);
/// 参考点节点 | Reference point node
public record ReferencePointNode(
- Guid Id, int Index, string Name,
- double XM, double YM, double ZT, double ZD, double TiltD, double Dist,
- bool IsRayOn, double Voltage, double Current
- ) : CncNode(Id, Index, CncNodeType.ReferencePoint, Name);
+ Guid Id,
+ int Index,
+ string Name,
+ double StageX,
+ double StageY,
+ double SourceZ,
+ double DetectorZ,
+ double DetectorSwing,
+ double FDD,
+ bool IsRayOn,
+ double Voltage,
+ double Current,
+ double StageRotation = 0,
+ double FixtureRotation = 0,
+ double FOD = 0,
+ double Magnification = 0) : CncNode(Id, Index, CncNodeType.ReferencePoint, Name);
/// 保存节点(含图像)| Save node with image
public record SaveNodeWithImageNode(
- Guid Id, int Index, string Name,
+ Guid Id,
+ int Index,
+ string Name,
MotionState MotionState,
RaySourceState RaySourceState,
DetectorState DetectorState,
- string ImageFileName
- ) : CncNode(Id, Index, CncNodeType.SaveNodeWithImage, Name);
+ string ImageFileName) : CncNode(Id, Index, CncNodeType.SaveNodeWithImage, Name);
/// 保存节点(不含图像)| Save node without image
public record SaveNodeNode(
- Guid Id, int Index, string Name,
+ Guid Id,
+ int Index,
+ string Name,
MotionState MotionState,
RaySourceState RaySourceState,
- DetectorState DetectorState
- ) : CncNode(Id, Index, CncNodeType.SaveNode, Name);
+ DetectorState DetectorState) : CncNode(Id, Index, CncNodeType.SaveNode, Name);
/// 保存位置节点 | Save position node
public record SavePositionNode(
- Guid Id, int Index, string Name,
- MotionState MotionState
- ) : CncNode(Id, Index, CncNodeType.SavePosition, Name);
+ Guid Id,
+ int Index,
+ string Name,
+ MotionState MotionState) : CncNode(Id, Index, CncNodeType.SavePosition, Name);
/// 检测模块节点 | Inspection module node
public record InspectionModuleNode(
- Guid Id, int Index, string Name,
- PipelineModel Pipeline
- ) : CncNode(Id, Index, CncNodeType.InspectionModule, Name);
+ Guid Id,
+ int Index,
+ string Name,
+ PipelineModel Pipeline) : CncNode(Id, Index, CncNodeType.InspectionModule, Name);
/// 检测标记节点 | Inspection marker node
public record InspectionMarkerNode(
- Guid Id, int Index, string Name,
+ Guid Id,
+ int Index,
+ string Name,
string MarkerType,
- double MarkerX, double MarkerY
- ) : CncNode(Id, Index, CncNodeType.InspectionMarker, Name);
+ double MarkerX,
+ double MarkerY) : CncNode(Id, Index, CncNodeType.InspectionMarker, Name);
/// 停顿对话框节点 | Pause dialog node
public record PauseDialogNode(
- Guid Id, int Index, string Name,
+ Guid Id,
+ int Index,
+ string Name,
string DialogTitle,
- string DialogMessage
- ) : CncNode(Id, Index, CncNodeType.PauseDialog, Name);
+ string DialogMessage) : CncNode(Id, Index, CncNodeType.PauseDialog, Name);
/// 等待延时节点 | Wait delay node
public record WaitDelayNode(
- Guid Id, int Index, string Name,
- int DelayMilliseconds
- ) : CncNode(Id, Index, CncNodeType.WaitDelay, Name);
+ Guid Id,
+ int Index,
+ string Name,
+ int DelayMilliseconds) : CncNode(Id, Index, CncNodeType.WaitDelay, Name);
/// 完成程序节点 | Complete program node
public record CompleteProgramNode(
- Guid Id, int Index, string Name
- ) : CncNode(Id, Index, CncNodeType.CompleteProgram, Name);
+ Guid Id,
+ int Index,
+ string Name) : CncNode(Id, Index, CncNodeType.CompleteProgram, Name);
// ── CNC 程序 | CNC Program ────────────────────────────────────────
diff --git a/XplorePlane/Models/StateModels.cs b/XplorePlane/Models/StateModels.cs
index c9c6dca..59d39f4 100644
--- a/XplorePlane/Models/StateModels.cs
+++ b/XplorePlane/Models/StateModels.cs
@@ -2,8 +2,6 @@ using System;
namespace XplorePlane.Models
{
- // ── Enumerations ──────────────────────────────────────────────────
-
/// 系统操作模式
public enum OperationMode
{
@@ -23,82 +21,85 @@ namespace XplorePlane.Models
Error // 出错
}
- // ── State Records ─────────────────────────────────────────────────
+ // — State Records —
- /// 运动控制状态(不可变)
+ ///
+ /// 运动控制状态(不可变)。
+ /// 统一的运动与几何快照,与运动硬件模型对齐。
+ ///
public record MotionState(
- double XM, // X 轴位置 (μm)
- double YM, // Y 轴位置 (μm)
- double ZT, // Z 上轴位置 (μm)
- double ZD, // Z 下轴位置 (μm)
- double TiltD, // 倾斜角度 (m°)
- double Dist, // 距离 (μm)
- double XMSpeed, // X 轴速度 (μm/s)
- double YMSpeed, // Y 轴速度 (μm/s)
- double ZTSpeed, // Z 上轴速度 (μm/s)
- double ZDSpeed, // Z 下轴速度 (μm/s)
- double TiltDSpeed, // 倾斜速度 (m°/s)
- double DistSpeed // 距离速度 (μm/s)
- )
+ double StageX, // X 轴位置(μm)
+ double StageY, // Y 轴位置(μm)
+ double SourceZ, // Z 上轴位置(μm)
+ double DetectorZ, // Z 下轴位置(μm)
+ double DetectorSwing, // 探测器摆角(°)
+ double FDD, // 焦点-探测器距离(μm)
+ double StageXSpeed, // X 轴速度(μm/s)
+ double StageYSpeed, // Y 轴速度(μm/s)
+ double SourceZSpeed, // Z 上轴速度(μm/s)
+ double DetectorZSpeed, // Z 下轴速度(μm/s)
+ double DetectorSwingSpeed, // 探测器摆角速度(°/s)
+ double FDDSpeed, // 焦点-探测器距离速度(μm/s)
+ double StageRotation = 0, // 载台旋转角度(°)
+ double FixtureRotation = 0, // 夹具旋转角度(°)
+ double FOD = 0, // 焦点-物体距离(μm)
+ double Magnification = 0, // 放大倍率
+ double StageRotationSpeed = 0, // 载台旋转速度(°/s)
+ double FixtureRotationSpeed = 0) // 夹具旋转速度(°/s)
{
public static readonly MotionState Default = new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
/// 射线源状态(不可变)
public record RaySourceState(
- bool IsOn, // 开关状态
- double Voltage, // 电压 (kV)
- double Power // 功率 (W)
- )
+ bool IsOn, // 是否开启
+ double Voltage, // 电压(kV)
+ double Power) // 功率(W)
{
public static readonly RaySourceState Default = new(false, 0, 0);
}
/// 探测器状态(不可变)
public record DetectorState(
- bool IsConnected, // 连接状态
- bool IsAcquiring, // 是否正在采集
- double FrameRate, // 当前帧率 (fps)
- string Resolution // 分辨率描述,如 "2048x2048"
- )
+ bool IsConnected, // 是否已连接
+ bool IsAcquiring, // 是否正在采集
+ double FrameRate, // 帧率(fps)
+ string Resolution) // 分辨率描述
{
public static readonly DetectorState Default = new(false, false, 0, string.Empty);
}
- /// 系统级状态(不可变)
+ /// 系统整体状态(不可变)
public record SystemState(
- OperationMode OperationMode, // 当前操作模式
- bool HasError, // 是否存在系统错误
- string ErrorMessage // 错误描述
- )
+ OperationMode OperationMode, // 当前操作模式
+ bool HasError, // 是否存在错误
+ string ErrorMessage) // 错误信息
{
public static readonly SystemState Default = new(OperationMode.Idle, false, string.Empty);
}
- /// 摄像头视频流状态(不可变)
+ /// 相机状态(不可变)
public record CameraState(
- bool IsConnected, // 连接状态
- bool IsStreaming, // 是否正在推流
- object CurrentFrame, // 当前帧数据引用(BitmapSource 或 byte[],Frozen)
- int Width, // 分辨率宽
- int Height, // 分辨率高
- double FrameRate // 帧率 (fps)
- )
+ bool IsConnected, // 是否已连接
+ bool IsStreaming, // 是否正在推流
+ object CurrentFrame, // 当前帧数据
+ int Width, // 图像宽度(px)
+ int Height, // 图像高度(px)
+ double FrameRate) // 帧率(fps)
{
public static readonly CameraState Default = new(false, false, null, 0, 0, 0);
}
- /// 物理坐标
+ /// 物理坐标位置
public record PhysicalPosition(double X, double Y, double Z);
- /// 图像标定矩阵,像素坐标 → 物理坐标映射
+ /// 标定矩阵(3×3 仿射变换)
public record CalibrationMatrix(
- double M11, double M12, double M13, // 3x3 仿射变换矩阵
+ double M11, double M12, double M13,
double M21, double M22, double M23,
- double M31, double M32, double M33
- )
+ double M31, double M32, double M33)
{
- /// 将像素坐标转换为物理坐标
+ /// 将像素坐标变换为物理坐标
public (double X, double Y, double Z) Transform(double pixelX, double pixelY)
{
double x = M11 * pixelX + M12 * pixelY + M13;
@@ -108,13 +109,12 @@ namespace XplorePlane.Models
}
}
- /// 画面联动状态(不可变)
+ /// 联动视图状态(不可变)
public record LinkedViewState(
- PhysicalPosition TargetPosition, // 目标物理坐标
- bool IsExecuting, // 联动是否正在执行
- DateTime LastRequestTime // 最近一次联动请求时间
- )
+ PhysicalPosition TargetPosition, // 目标物理位置
+ bool IsExecuting, // 是否正在执行移动
+ DateTime LastRequestTime) // 最近一次请求时间
{
public static readonly LinkedViewState Default = new(new PhysicalPosition(0, 0, 0), false, DateTime.MinValue);
}
-}
\ No newline at end of file
+}
diff --git a/XplorePlane/Services/AppState/AppStateService.cs b/XplorePlane/Services/AppState/AppStateService.cs
index 9e6abcc..d324144 100644
--- a/XplorePlane/Services/AppState/AppStateService.cs
+++ b/XplorePlane/Services/AppState/AppStateService.cs
@@ -1,29 +1,48 @@
+using Prism.Events;
using Prism.Mvvm;
using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
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.Enums;
+using XP.Hardware.MotionControl.Abstractions.Events;
+using XP.Hardware.MotionControl.Services;
using XP.Hardware.RaySource.Services;
using XplorePlane.Models;
namespace XplorePlane.Services.AppState
{
///
- /// 全局应用状态管理服务实现。
- /// 继承 BindableBase 以支持 WPF 数据绑定,使用 Interlocked.Exchange 保证线程安全写入,
- /// 通过 Dispatcher.BeginInvoke 将事件调度到 UI 线程。
+ /// Global application state service.
+ /// Motion state is synchronized from the motion hardware service layer and
+ /// mapped into the legacy business model for compatibility.
+ /// Detector state and latest frame are synchronized via Prism EventAggregator subscriptions.
///
public class AppStateService : BindableBase, IAppStateService
{
private readonly Dispatcher _dispatcher;
private readonly IRaySourceService _raySourceService;
+ private readonly IMotionSystem _motionSystem;
+ private readonly IMotionControlService _motionControlService;
+ private readonly IDetectorService _detectorService;
+ private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger;
+ private readonly SubscriptionToken _axisStatusChangedToken;
+ private readonly SubscriptionToken _geometryUpdatedToken;
+ private readonly SubscriptionToken _detectorStatusChangedToken;
+ private readonly SubscriptionToken _detectorImageCapturedToken;
+
private bool _disposed;
+ private GeometryData _latestGeometry;
// ── 状态字段(通过 Interlocked.Exchange 原子替换)──
private MotionState _motionState = MotionState.Default;
-
private RaySourceState _raySourceState = RaySourceState.Default;
private DetectorState _detectorState = DetectorState.Default;
private SystemState _systemState = SystemState.Default;
@@ -32,26 +51,21 @@ namespace XplorePlane.Services.AppState
private LinkedViewState _linkedViewState = LinkedViewState.Default;
private RecipeExecutionState _recipeExecutionState = RecipeExecutionState.Default;
+ // ── 探测器最新帧(volatile,供任意线程读取)──
+ private volatile ImageCapturedEventArgs _latestDetectorFrame;
+
// ── 类型化状态变更事件 ──
public event EventHandler> MotionStateChanged;
-
public event EventHandler> RaySourceStateChanged;
-
public event EventHandler> DetectorStateChanged;
-
public event EventHandler> SystemStateChanged;
-
public event EventHandler> CameraStateChanged;
-
public event EventHandler> LinkedViewStateChanged;
-
public event EventHandler> RecipeExecutionStateChanged;
-
public event EventHandler LinkedViewRequested;
// ── 状态属性(只读)──
public MotionState MotionState => _motionState;
-
public RaySourceState RaySourceState => _raySourceState;
public DetectorState DetectorState => _detectorState;
public SystemState SystemState => _systemState;
@@ -60,19 +74,56 @@ namespace XplorePlane.Services.AppState
public LinkedViewState LinkedViewState => _linkedViewState;
public RecipeExecutionState RecipeExecutionState => _recipeExecutionState;
+ ///
+ /// 探测器最新采集帧(线程安全,可从任意线程读取)。
+ /// 由 ImageCapturedEvent 驱动更新,无采集时为 null。
+ ///
+ public ImageCapturedEventArgs LatestDetectorFrame => _latestDetectorFrame;
+
public AppStateService(
IRaySourceService raySourceService,
+ IMotionSystem motionSystem,
+ IMotionControlService motionControlService,
+ IDetectorService detectorService,
+ IEventAggregator eventAggregator,
ILoggerService logger)
{
ArgumentNullException.ThrowIfNull(raySourceService);
+ ArgumentNullException.ThrowIfNull(motionSystem);
+ ArgumentNullException.ThrowIfNull(motionControlService);
+ ArgumentNullException.ThrowIfNull(detectorService);
+ ArgumentNullException.ThrowIfNull(eventAggregator);
ArgumentNullException.ThrowIfNull(logger);
_raySourceService = raySourceService;
+ _motionSystem = motionSystem;
+ _motionControlService = motionControlService;
+ _detectorService = detectorService;
+ _eventAggregator = eventAggregator;
_logger = logger.ForModule();
- _dispatcher = Application.Current.Dispatcher;
+ _dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
+
+ // ── 运动控制事件订阅 ──
+ _geometryUpdatedToken = _eventAggregator
+ .GetEvent()
+ .Subscribe(OnGeometryUpdated);
+
+ _axisStatusChangedToken = _eventAggregator
+ .GetEvent()
+ .Subscribe(OnAxisStatusChanged);
+
+ // ── 探测器状态事件订阅(后台线程,避免阻塞采集)──
+ _detectorStatusChangedToken = _eventAggregator
+ .GetEvent()
+ .Subscribe(OnDetectorStatusChanged, ThreadOption.BackgroundThread);
+
+ // ── 探测器图像事件订阅(后台线程,仅缓存最新帧)──
+ _detectorImageCapturedToken = _eventAggregator
+ .GetEvent()
+ .Subscribe(OnDetectorImageCaptured, ThreadOption.BackgroundThread);
SubscribeToExistingServices();
- _logger.Info("AppStateService 已初始化");
+ _logger.Info("AppStateService initialized");
}
// ── 状态更新方法 ──
@@ -80,17 +131,30 @@ namespace XplorePlane.Services.AppState
public void UpdateMotionState(MotionState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateMotionState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateMotionState");
+ return;
+ }
- var old = Interlocked.Exchange(ref _motionState, newState);
- if (ReferenceEquals(old, newState)) return;
- RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState));
+ // Keep the legacy API surface, but let the hardware service layer
+ // remain the source of truth whenever a fresh hardware snapshot is available.
+ if (TryRefreshMotionStateFromHardware("UpdateMotionState"))
+ {
+ return;
+ }
+
+ SetMotionState(newState);
}
public void UpdateRaySourceState(RaySourceState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateRaySourceState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateRaySourceState");
+ return;
+ }
var old = Interlocked.Exchange(ref _raySourceState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -100,7 +164,11 @@ namespace XplorePlane.Services.AppState
public void UpdateDetectorState(DetectorState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateDetectorState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateDetectorState");
+ return;
+ }
var old = Interlocked.Exchange(ref _detectorState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -110,7 +178,11 @@ namespace XplorePlane.Services.AppState
public void UpdateSystemState(SystemState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateSystemState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateSystemState");
+ return;
+ }
var old = Interlocked.Exchange(ref _systemState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -120,7 +192,11 @@ namespace XplorePlane.Services.AppState
public void UpdateCameraState(CameraState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateCameraState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateCameraState");
+ return;
+ }
var old = Interlocked.Exchange(ref _cameraState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -130,21 +206,26 @@ namespace XplorePlane.Services.AppState
public void UpdateCalibrationMatrix(CalibrationMatrix newMatrix)
{
ArgumentNullException.ThrowIfNull(newMatrix);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateCalibrationMatrix 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateCalibrationMatrix");
+ return;
+ }
var old = Interlocked.Exchange(ref _calibrationMatrix, newMatrix);
if (ReferenceEquals(old, newMatrix)) return;
- _dispatcher.BeginInvoke(() =>
- {
- RaisePropertyChanged(nameof(CalibrationMatrix));
- });
+ _dispatcher.BeginInvoke(() => RaisePropertyChanged(nameof(CalibrationMatrix)));
}
public void UpdateLinkedViewState(LinkedViewState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateLinkedViewState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateLinkedViewState");
+ return;
+ }
var old = Interlocked.Exchange(ref _linkedViewState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -154,7 +235,11 @@ namespace XplorePlane.Services.AppState
public void UpdateRecipeExecutionState(RecipeExecutionState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateRecipeExecutionState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateRecipeExecutionState");
+ return;
+ }
var old = Interlocked.Exchange(ref _recipeExecutionState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -168,11 +253,11 @@ namespace XplorePlane.Services.AppState
var matrix = _calibrationMatrix;
if (matrix is null)
{
- _logger.Warn("CalibrationMatrix 未设置,无法执行画面联动 (pixelX={PixelX}, pixelY={PixelY})", pixelX, pixelY);
+ _logger.Warn("CalibrationMatrix is not configured, cannot execute linked view request (pixelX={PixelX}, pixelY={PixelY})", pixelX, pixelY);
UpdateSystemState(SystemState with
{
HasError = true,
- ErrorMessage = "CalibrationMatrix 未设置,无法执行画面联动"
+ ErrorMessage = "CalibrationMatrix is not configured, cannot execute linked view request"
});
return;
}
@@ -191,10 +276,48 @@ namespace XplorePlane.Services.AppState
});
}
- // ── 内部辅助方法 ──
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
- private void RaiseOnDispatcher(T oldVal, T newVal,
- EventHandler> handler, string propertyName)
+ if (_axisStatusChangedToken is not null)
+ {
+ _eventAggregator.GetEvent().Unsubscribe(_axisStatusChangedToken);
+ }
+
+ if (_geometryUpdatedToken is not null)
+ {
+ _eventAggregator.GetEvent().Unsubscribe(_geometryUpdatedToken);
+ }
+
+ if (_detectorStatusChangedToken is not null)
+ {
+ _eventAggregator.GetEvent().Unsubscribe(_detectorStatusChangedToken);
+ }
+
+ if (_detectorImageCapturedToken is not null)
+ {
+ _eventAggregator.GetEvent().Unsubscribe(_detectorImageCapturedToken);
+ }
+
+ MotionStateChanged = null;
+ RaySourceStateChanged = null;
+ DetectorStateChanged = null;
+ SystemStateChanged = null;
+ CameraStateChanged = null;
+ LinkedViewStateChanged = null;
+ RecipeExecutionStateChanged = null;
+ LinkedViewRequested = null;
+
+ _logger.Info("AppStateService disposed");
+ }
+
+ private void RaiseOnDispatcher(
+ T oldVal,
+ T newVal,
+ EventHandler> handler,
+ string propertyName)
{
_dispatcher.BeginInvoke(() =>
{
@@ -205,34 +328,144 @@ namespace XplorePlane.Services.AppState
}
catch (Exception ex)
{
- _logger.Error(ex, "状态变更事件处理器抛出异常 (property={PropertyName})", propertyName);
+ _logger.Error(ex, "State changed handler failed (property={PropertyName})", propertyName);
}
});
}
private void SubscribeToExistingServices()
{
- _logger.Info("AppStateService 已准备好接收外部服务状态更新");
+ if (TryRefreshMotionStateFromHardware("initialization"))
+ {
+ _logger.Info("AppStateService subscribed to motion hardware state");
+ return;
+ }
+
+ _logger.Warn("AppStateService could not initialize motion state from hardware");
}
- // ── Dispose ──
-
- public void Dispose()
+ private void OnAxisStatusChanged(AxisStatusChangedData _)
{
if (_disposed) return;
- _disposed = true;
+ TryRefreshMotionStateFromHardware("axis-status-changed");
+ }
- // 清除所有事件订阅
- MotionStateChanged = null;
- RaySourceStateChanged = null;
- DetectorStateChanged = null;
- SystemStateChanged = null;
- CameraStateChanged = null;
- LinkedViewStateChanged = null;
- RecipeExecutionStateChanged = null;
- LinkedViewRequested = null;
+ private void OnGeometryUpdated(GeometryData geometry)
+ {
+ if (_disposed) return;
- _logger.Info("AppStateService 已释放");
+ _latestGeometry = geometry;
+ TryRefreshMotionStateFromHardware("geometry-updated");
+ }
+
+ ///
+ /// 探测器状态变更回调。
+ /// 将硬件层 DetectorStatus 映射为应用层 DetectorState 并同步到 AppState。
+ /// 运行在后台线程(ThreadOption.BackgroundThread),不阻塞采集。
+ ///
+ 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);
+ }
+
+ ///
+ /// 探测器图像采集回调。
+ /// 仅缓存最新帧引用(volatile 写),不做任何图像处理,保持采集链路零阻塞。
+ /// 上层通过 LatestDetectorFrame 属性按需读取。
+ ///
+ private void OnDetectorImageCaptured(ImageCapturedEventArgs args)
+ {
+ if (_disposed || args?.ImageData == null) return;
+ _latestDetectorFrame = args;
+ }
+
+ private bool TryRefreshMotionStateFromHardware(string reason)
+ {
+ try
+ {
+ if (_latestGeometry is null)
+ {
+ var geometry = _motionControlService.GetCurrentGeometry();
+ _latestGeometry = new GeometryData(geometry.FOD, geometry.FDD, geometry.Magnification);
+ }
+
+ SetMotionState(BuildMotionStateSnapshot(_latestGeometry));
+ return true;
+ }
+ catch (Exception ex)
+ {
+ _logger.Warn("Failed to refresh motion state from hardware during {Reason}: {Message}", reason, ex.Message);
+ _logger.Error(ex, "Motion state refresh exception during {Reason}", reason);
+ return false;
+ }
+ }
+
+ private MotionState BuildMotionStateSnapshot(GeometryData geometry)
+ {
+ var stageX = _motionSystem.GetLinearAxis(AxisId.StageX);
+ var stageY = _motionSystem.GetLinearAxis(AxisId.StageY);
+ var sourceZ = _motionSystem.GetLinearAxis(AxisId.SourceZ);
+ var detectorZ = _motionSystem.GetLinearAxis(AxisId.DetectorZ);
+ var detectorSwing = _motionSystem.GetRotaryAxis(RotaryAxisId.DetectorSwing);
+ var stageRotation = _motionSystem.GetRotaryAxis(RotaryAxisId.StageRotation);
+ var fixtureRotation = _motionSystem.GetRotaryAxis(RotaryAxisId.FixtureRotation);
+
+ return new MotionState(
+ StageX: stageX.ActualPosition,
+ StageY: stageY.ActualPosition,
+ SourceZ: sourceZ.ActualPosition,
+ DetectorZ: detectorZ.ActualPosition,
+ DetectorSwing: detectorSwing.ActualAngle,
+ FDD: geometry?.FDD ?? 0,
+ StageXSpeed: 0,
+ StageYSpeed: 0,
+ SourceZSpeed: 0,
+ DetectorZSpeed: 0,
+ DetectorSwingSpeed: 0,
+ FDDSpeed: 0,
+ StageRotation: stageRotation.ActualAngle,
+ FixtureRotation: fixtureRotation.ActualAngle,
+ FOD: geometry?.FOD ?? 0,
+ Magnification: geometry?.Magnification ?? 0,
+ StageRotationSpeed: 0,
+ FixtureRotationSpeed: 0);
+ }
+
+ private void SetMotionState(MotionState newState)
+ {
+ var old = Interlocked.Exchange(ref _motionState, newState);
+ if (ReferenceEquals(old, newState)) return;
+ RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState));
}
}
-}
\ No newline at end of file
+}
diff --git a/XplorePlane/Services/AppState/IAppStateService.cs b/XplorePlane/Services/AppState/IAppStateService.cs
index 9a43fa3..fc1d168 100644
--- a/XplorePlane/Services/AppState/IAppStateService.cs
+++ b/XplorePlane/Services/AppState/IAppStateService.cs
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
+using XP.Hardware.Detector.Abstractions;
using XplorePlane.Models;
namespace XplorePlane.Services.AppState
@@ -21,6 +22,13 @@ namespace XplorePlane.Services.AppState
LinkedViewState LinkedViewState { get; }
RecipeExecutionState RecipeExecutionState { get; }
+ ///
+ /// 探测器最新采集帧(线程安全,可从任意线程读取)。
+ /// 由 ImageCapturedEvent 驱动更新,探测器未采集时为 null。
+ /// CNC 执行、图像处理等上层服务通过此属性按需取图,无需自行订阅事件。
+ ///
+ ImageCapturedEventArgs LatestDetectorFrame { get; }
+
// ── 状态更新方法(线程安全,可从任意线程调用)──
void UpdateMotionState(MotionState newState);
diff --git a/XplorePlane/Services/Cnc/CncExecutionService.cs b/XplorePlane/Services/Cnc/CncExecutionService.cs
index 9d1e0f8..da359e1 100644
--- a/XplorePlane/Services/Cnc/CncExecutionService.cs
+++ b/XplorePlane/Services/Cnc/CncExecutionService.cs
@@ -103,6 +103,63 @@ namespace XplorePlane.Services.Cnc
{
switch (node)
{
+ case ReferencePointNode rp:
+ _logger.ForModule().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().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().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().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:
try
{
diff --git a/XplorePlane/Services/Cnc/CncProgramService.cs b/XplorePlane/Services/Cnc/CncProgramService.cs
index 8765574..9d1b472 100644
--- a/XplorePlane/Services/Cnc/CncProgramService.cs
+++ b/XplorePlane/Services/Cnc/CncProgramService.cs
@@ -389,22 +389,26 @@ namespace XplorePlane.Services.Cnc
var raySource = _appStateService.RaySourceState;
return new ReferencePointNode(
id, index, $"参考点_{index}",
- XM: motion.XM,
- YM: motion.YM,
- ZT: motion.ZT,
- ZD: motion.ZD,
- TiltD: motion.TiltD,
- Dist: motion.Dist,
+ StageX: motion.StageX,
+ StageY: motion.StageY,
+ SourceZ: motion.SourceZ,
+ DetectorZ: motion.DetectorZ,
+ DetectorSwing: motion.DetectorSwing,
+ FDD: motion.FDD,
IsRayOn: raySource.IsOn,
Voltage: raySource.Voltage,
- Current: TryReadCurrent());
+ Current: TryReadCurrent(),
+ StageRotation: motion.StageRotation,
+ FixtureRotation: motion.FixtureRotation,
+ FOD: motion.FOD,
+ Magnification: motion.Magnification);
}
/// 创建保存节点(含图像)| Create save node with image
private SaveNodeWithImageNode CreateSaveNodeWithImageNode(Guid id, int index)
{
return new SaveNodeWithImageNode(
- id, index, $"保存节点(图像)_{index}",
+ id, index, $"保存节点_图像_{index}",
MotionState: _appStateService.MotionState,
RaySourceState: _appStateService.RaySourceState,
DetectorState: _appStateService.DetectorState,
diff --git a/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs b/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs
index 9b04275..c6e3ae7 100644
--- a/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs
+++ b/XplorePlane/ViewModels/Cnc/CncEditorViewModel.cs
@@ -417,7 +417,7 @@ namespace XplorePlane.ViewModels.Cnc
return;
var sb = new StringBuilder();
- sb.AppendLine("Index,NodeType,Name,XM,YM,ZT,ZD,TiltD,Dist,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;
@@ -425,16 +425,16 @@ namespace XplorePlane.ViewModels.Cnc
{
var row = node switch
{
- ReferencePointNode rp => $"{rp.Index},{rp.NodeType},{Esc(rp.Name)},{rp.XM.ToString(inv)},{rp.YM.ToString(inv)},{rp.ZT.ToString(inv)},{rp.ZD.ToString(inv)},{rp.TiltD.ToString(inv)},{rp.Dist.ToString(inv)},{rp.Voltage.ToString(inv)},{rp.Current.ToString(inv)},,{rp.IsRayOn},,,,,,,,,,",
- SaveNodeWithImageNode sni => $"{sni.Index},{sni.NodeType},{Esc(sni.Name)},{sni.MotionState.XM.ToString(inv)},{sni.MotionState.YM.ToString(inv)},{sni.MotionState.ZT.ToString(inv)},{sni.MotionState.ZD.ToString(inv)},{sni.MotionState.TiltD.ToString(inv)},{sni.MotionState.Dist.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.XM.ToString(inv)},{sn.MotionState.YM.ToString(inv)},{sn.MotionState.ZT.ToString(inv)},{sn.MotionState.ZD.ToString(inv)},{sn.MotionState.TiltD.ToString(inv)},{sn.MotionState.Dist.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.XM.ToString(inv)},{sp.MotionState.YM.ToString(inv)},{sp.MotionState.ZT.ToString(inv)},{sp.MotionState.ZD.ToString(inv)},{sp.MotionState.TiltD.ToString(inv)},{sp.MotionState.Dist.ToString(inv)},,,,,,,,,,,,,,",
- 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)},,,",
- PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
- WaitDelayNode wd => $"{wd.Index},{wd.NodeType},{Esc(wd.Name)},,,,,,,,,,,,,,,,,,,{wd.DelayMilliseconds},",
- CompleteProgramNode cp => $"{cp.Index},{cp.NodeType},{Esc(cp.Name)},,,,,,,,,,,,,,,,,,,,",
- _ => $"{node.Index},{node.NodeType},{Esc(node.Name)},,,,,,,,,,,,,,,,,,,,"
+ 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.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.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.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)}",
+ 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)},,",
+ WaitDelayNode wd => $"{wd.Index},{wd.NodeType},{Esc(wd.Name)},,,,,,,,,,,,,,,,,,,,,{wd.DelayMilliseconds},",
+ CompleteProgramNode cp => $"{cp.Index},{cp.NodeType},{Esc(cp.Name)},,,,,,,,,,,,,,,,,,,,,,",
+ _ => $"{node.Index},{node.NodeType},{Esc(node.Name)},,,,,,,,,,,,,,,,,,,,,,"
};
sb.AppendLine(row);
@@ -551,6 +551,7 @@ namespace XplorePlane.ViewModels.Cnc
finally
{
IsRunning = false;
+ ResetAllNodeStates();
_cts?.Dispose();
_cts = null;
}
diff --git a/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs b/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs
index f16a061..be00d31 100644
--- a/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs
+++ b/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs
@@ -1,4 +1,4 @@
-using Microsoft.Win32;
+using Microsoft.Win32;
using Prism.Commands;
using Prism.Mvvm;
using System;
diff --git a/XplorePlane/ViewModels/Cnc/CncNodeViewModel.cs b/XplorePlane/ViewModels/Cnc/CncNodeViewModel.cs
index ac46829..3cbb2fe 100644
--- a/XplorePlane/ViewModels/Cnc/CncNodeViewModel.cs
+++ b/XplorePlane/ViewModels/Cnc/CncNodeViewModel.cs
@@ -116,82 +116,134 @@ namespace XplorePlane.ViewModels.Cnc
_ => string.Empty
};
- public double XM
+ public double StageX
{
get => _model switch
{
- ReferencePointNode rp => rp.XM,
- SaveNodeNode sn => sn.MotionState.XM,
- SaveNodeWithImageNode sni => sni.MotionState.XM,
- SavePositionNode sp => sp.MotionState.XM,
+ ReferencePointNode rp => rp.StageX,
+ SaveNodeNode sn => sn.MotionState.StageX,
+ SaveNodeWithImageNode sni => sni.MotionState.StageX,
+ SavePositionNode sp => sp.MotionState.StageX,
_ => 0d
};
- set => UpdateMotion(value, MotionAxis.XM);
+ set => UpdateMotion(value, MotionAxis.StageX);
}
- public double YM
+ public double StageY
{
get => _model switch
{
- ReferencePointNode rp => rp.YM,
- SaveNodeNode sn => sn.MotionState.YM,
- SaveNodeWithImageNode sni => sni.MotionState.YM,
- SavePositionNode sp => sp.MotionState.YM,
+ ReferencePointNode rp => rp.StageY,
+ SaveNodeNode sn => sn.MotionState.StageY,
+ SaveNodeWithImageNode sni => sni.MotionState.StageY,
+ SavePositionNode sp => sp.MotionState.StageY,
_ => 0d
};
- set => UpdateMotion(value, MotionAxis.YM);
+ set => UpdateMotion(value, MotionAxis.StageY);
}
- public double ZT
+ public double SourceZ
{
get => _model switch
{
- ReferencePointNode rp => rp.ZT,
- SaveNodeNode sn => sn.MotionState.ZT,
- SaveNodeWithImageNode sni => sni.MotionState.ZT,
- SavePositionNode sp => sp.MotionState.ZT,
+ ReferencePointNode rp => rp.SourceZ,
+ SaveNodeNode sn => sn.MotionState.SourceZ,
+ SaveNodeWithImageNode sni => sni.MotionState.SourceZ,
+ SavePositionNode sp => sp.MotionState.SourceZ,
_ => 0d
};
- set => UpdateMotion(value, MotionAxis.ZT);
+ set => UpdateMotion(value, MotionAxis.SourceZ);
}
- public double ZD
+ public double DetectorZ
{
get => _model switch
{
- ReferencePointNode rp => rp.ZD,
- SaveNodeNode sn => sn.MotionState.ZD,
- SaveNodeWithImageNode sni => sni.MotionState.ZD,
- SavePositionNode sp => sp.MotionState.ZD,
+ ReferencePointNode rp => rp.DetectorZ,
+ SaveNodeNode sn => sn.MotionState.DetectorZ,
+ SaveNodeWithImageNode sni => sni.MotionState.DetectorZ,
+ SavePositionNode sp => sp.MotionState.DetectorZ,
_ => 0d
};
- set => UpdateMotion(value, MotionAxis.ZD);
+ set => UpdateMotion(value, MotionAxis.DetectorZ);
}
- public double TiltD
+ public double DetectorSwing
{
get => _model switch
{
- ReferencePointNode rp => rp.TiltD,
- SaveNodeNode sn => sn.MotionState.TiltD,
- SaveNodeWithImageNode sni => sni.MotionState.TiltD,
- SavePositionNode sp => sp.MotionState.TiltD,
+ ReferencePointNode rp => rp.DetectorSwing,
+ SaveNodeNode sn => sn.MotionState.DetectorSwing,
+ SaveNodeWithImageNode sni => sni.MotionState.DetectorSwing,
+ SavePositionNode sp => sp.MotionState.DetectorSwing,
_ => 0d
};
- set => UpdateMotion(value, MotionAxis.TiltD);
+ set => UpdateMotion(value, MotionAxis.DetectorSwing);
}
- public double Dist
+ public double StageRotation
{
get => _model switch
{
- ReferencePointNode rp => rp.Dist,
- SaveNodeNode sn => sn.MotionState.Dist,
- SaveNodeWithImageNode sni => sni.MotionState.Dist,
- SavePositionNode sp => sp.MotionState.Dist,
+ ReferencePointNode rp => rp.StageRotation,
+ SaveNodeNode sn => sn.MotionState.StageRotation,
+ SaveNodeWithImageNode sni => sni.MotionState.StageRotation,
+ SavePositionNode sp => sp.MotionState.StageRotation,
_ => 0d
};
- set => UpdateMotion(value, MotionAxis.Dist);
+ set => UpdateMotion(value, MotionAxis.StageRotation);
+ }
+
+ public double FixtureRotation
+ {
+ get => _model switch
+ {
+ ReferencePointNode rp => rp.FixtureRotation,
+ SaveNodeNode sn => sn.MotionState.FixtureRotation,
+ SaveNodeWithImageNode sni => sni.MotionState.FixtureRotation,
+ SavePositionNode sp => sp.MotionState.FixtureRotation,
+ _ => 0d
+ };
+ set => UpdateMotion(value, MotionAxis.FixtureRotation);
+ }
+
+ public double FOD
+ {
+ get => _model switch
+ {
+ ReferencePointNode rp => rp.FOD,
+ SaveNodeNode sn => sn.MotionState.FOD,
+ SaveNodeWithImageNode sni => sni.MotionState.FOD,
+ SavePositionNode sp => sp.MotionState.FOD,
+ _ => 0d
+ };
+ set => UpdateMotion(value, MotionAxis.FOD);
+ }
+
+ public double FDD
+ {
+ get => _model switch
+ {
+ ReferencePointNode rp => rp.FDD,
+ SaveNodeNode sn => sn.MotionState.FDD,
+ SaveNodeWithImageNode sni => sni.MotionState.FDD,
+ SavePositionNode sp => sp.MotionState.FDD,
+ _ => 0d
+ };
+ set => UpdateMotion(value, MotionAxis.FDD);
+ }
+
+ public double Magnification
+ {
+ get => _model switch
+ {
+ ReferencePointNode rp => rp.Magnification,
+ SaveNodeNode sn => sn.MotionState.Magnification,
+ SaveNodeWithImageNode sni => sni.MotionState.Magnification,
+ SavePositionNode sp => sp.MotionState.Magnification,
+ _ => 0d
+ };
+ set => UpdateMotion(value, MotionAxis.Magnification);
}
public bool IsRayOn
@@ -426,12 +478,16 @@ namespace XplorePlane.ViewModels.Cnc
case ReferencePointNode rp:
UpdateModel(axis switch
{
- MotionAxis.XM => rp with { XM = value },
- MotionAxis.YM => rp with { YM = value },
- MotionAxis.ZT => rp with { ZT = value },
- MotionAxis.ZD => rp with { ZD = value },
- MotionAxis.TiltD => rp with { TiltD = value },
- MotionAxis.Dist => rp with { Dist = value },
+ MotionAxis.StageX => rp with { StageX = value },
+ MotionAxis.StageY => rp with { StageY = value },
+ MotionAxis.SourceZ => rp with { SourceZ = value },
+ MotionAxis.DetectorZ => rp with { DetectorZ = value },
+ MotionAxis.DetectorSwing => rp with { DetectorSwing = value },
+ MotionAxis.StageRotation => rp with { StageRotation = value },
+ MotionAxis.FixtureRotation => rp with { FixtureRotation = value },
+ MotionAxis.FOD => rp with { FOD = value },
+ MotionAxis.FDD => rp with { FDD = value },
+ MotionAxis.Magnification => rp with { Magnification = value },
_ => rp
});
break;
@@ -519,12 +575,16 @@ namespace XplorePlane.ViewModels.Cnc
{
return axis switch
{
- MotionAxis.XM => state with { XM = value },
- MotionAxis.YM => state with { YM = value },
- MotionAxis.ZT => state with { ZT = value },
- MotionAxis.ZD => state with { ZD = value },
- MotionAxis.TiltD => state with { TiltD = value },
- MotionAxis.Dist => state with { Dist = value },
+ MotionAxis.StageX => state with { StageX = value },
+ MotionAxis.StageY => state with { StageY = value },
+ MotionAxis.SourceZ => state with { SourceZ = value },
+ MotionAxis.DetectorZ => state with { DetectorZ = value },
+ MotionAxis.DetectorSwing => state with { DetectorSwing = value },
+ MotionAxis.StageRotation => state with { StageRotation = value },
+ MotionAxis.FixtureRotation => state with { FixtureRotation = value },
+ MotionAxis.FOD => state with { FOD = value },
+ MotionAxis.FDD => state with { FDD = value },
+ MotionAxis.Magnification => state with { Magnification = value },
_ => state
};
}
@@ -557,12 +617,16 @@ namespace XplorePlane.ViewModels.Cnc
RaisePropertyChanged(nameof(IsPositionChild));
RaisePropertyChanged(nameof(IsMotionSnapshotNode));
RaisePropertyChanged(nameof(RelationTag));
- RaisePropertyChanged(nameof(XM));
- RaisePropertyChanged(nameof(YM));
- RaisePropertyChanged(nameof(ZT));
- RaisePropertyChanged(nameof(ZD));
- RaisePropertyChanged(nameof(TiltD));
- RaisePropertyChanged(nameof(Dist));
+ RaisePropertyChanged(nameof(StageX));
+ RaisePropertyChanged(nameof(StageY));
+ RaisePropertyChanged(nameof(SourceZ));
+ RaisePropertyChanged(nameof(DetectorZ));
+ RaisePropertyChanged(nameof(DetectorSwing));
+ RaisePropertyChanged(nameof(StageRotation));
+ RaisePropertyChanged(nameof(FixtureRotation));
+ RaisePropertyChanged(nameof(FOD));
+ RaisePropertyChanged(nameof(FDD));
+ RaisePropertyChanged(nameof(Magnification));
RaisePropertyChanged(nameof(IsRayOn));
RaisePropertyChanged(nameof(Voltage));
RaisePropertyChanged(nameof(Current));
@@ -591,12 +655,16 @@ namespace XplorePlane.ViewModels.Cnc
private enum MotionAxis
{
- XM,
- YM,
- ZT,
- ZD,
- TiltD,
- Dist
+ StageX,
+ StageY,
+ SourceZ,
+ DetectorZ,
+ DetectorSwing,
+ StageRotation,
+ FixtureRotation,
+ FOD,
+ FDD,
+ Magnification
}
}
}
diff --git a/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs
index c79ffe1..2494bfd 100644
--- a/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs
+++ b/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs
@@ -1,4 +1,4 @@
-using Microsoft.Win32;
+using Microsoft.Win32;
using Prism.Events;
using Prism.Commands;
using Prism.Mvvm;
diff --git a/XplorePlane/ViewModels/Main/MainViewModel.cs b/XplorePlane/ViewModels/Main/MainViewModel.cs
index 19f89d8..ade301e 100644
--- a/XplorePlane/ViewModels/Main/MainViewModel.cs
+++ b/XplorePlane/ViewModels/Main/MainViewModel.cs
@@ -1,4 +1,4 @@
-using Microsoft.Win32;
+using Microsoft.Win32;
using Prism.Commands;
using Prism.Events;
using Prism.Ioc;
diff --git a/XplorePlane/Views/Cnc/CncEditorWindow.xaml b/XplorePlane/Views/Cnc/CncEditorWindow.xaml
index a709fcf..18409ad 100644
--- a/XplorePlane/Views/Cnc/CncEditorWindow.xaml
+++ b/XplorePlane/Views/Cnc/CncEditorWindow.xaml
@@ -1,4 +1,4 @@
-
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/XplorePlane/Views/Hardware/MotionControlPanelView.xaml b/XplorePlane/Views/Hardware/MotionControlPanelView.xaml
index a632154..f466347 100644
--- a/XplorePlane/Views/Hardware/MotionControlPanelView.xaml
+++ b/XplorePlane/Views/Hardware/MotionControlPanelView.xaml
@@ -53,7 +53,7 @@
Margin="0,0,4,3"
VerticalAlignment="Center"
Foreground="#333333"
- Text="XM" />
+ Text="StageX" />
+ Text="StageY" />
+ Text="SourceZ" />
+ Text="DetectorZ" />
+ Text="DetectorSwing" />
+ Text="FDD" />
-
\ No newline at end of file
+
diff --git a/XplorePlane/Views/ImageProcessing/PipelineEditorView.xaml b/XplorePlane/Views/ImageProcessing/PipelineEditorView.xaml
index e11f2e0..fdbb0bf 100644
--- a/XplorePlane/Views/ImageProcessing/PipelineEditorView.xaml
+++ b/XplorePlane/Views/ImageProcessing/PipelineEditorView.xaml
@@ -1,4 +1,4 @@
-
+
WinExe
net8.0-windows
diff --git a/XplorePlane/XplorePlane.csproj.user b/XplorePlane/XplorePlane.csproj.user
index 88a5509..824d5a9 100644
--- a/XplorePlane/XplorePlane.csproj.user
+++ b/XplorePlane/XplorePlane.csproj.user
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file