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