4 Commits

Author SHA1 Message Date
zhengxuan.zhang 996b0c5796 修复测试用例AfterExecution_IsRunningFalse_AllNodesIdle 2026-05-06 17:27:18 +08:00
zhengxuan.zhang 3a3ea5b5c9 修复乱码 2026-05-06 17:23:51 +08:00
zhengxuan.zhang aeef1feee3 以硬件库层面运动硬件轴定义为准,同步修改appstate, 包括CNC 节点属性面板和 XP导出 2026-05-06 17:11:35 +08:00
zhengxuan.zhang 1ef876db2c 调整硬件appstate 保持与硬件库层面定义一致 2026-05-06 16:48:09 +08:00
20 changed files with 613 additions and 286 deletions
+23 -19
View File
@@ -20,21 +20,25 @@ namespace XplorePlane.Tests.Models
public void MotionState_Default_AllZeros() public void MotionState_Default_AllZeros()
{ {
var state = MotionState.Default; 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($"MotionState.Default: StageX={state.StageX}, StageY={state.StageY}, SourceZ={state.SourceZ}, DetectorZ={state.DetectorZ}, DetectorSwing={state.DetectorSwing}, FDD={state.FDD}");
_output.WriteLine($" Speeds: XM={state.XMSpeed}, YM={state.YMSpeed}, ZT={state.ZTSpeed}, ZD={state.ZDSpeed}, TiltD={state.TiltDSpeed}, Dist={state.DistSpeed}"); _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.StageX);
Assert.Equal(0, state.YM); Assert.Equal(0, state.StageY);
Assert.Equal(0, state.ZT); Assert.Equal(0, state.SourceZ);
Assert.Equal(0, state.ZD); Assert.Equal(0, state.DetectorZ);
Assert.Equal(0, state.TiltD); Assert.Equal(0, state.DetectorSwing);
Assert.Equal(0, state.Dist); Assert.Equal(0, state.FDD);
Assert.Equal(0, state.XMSpeed); Assert.Equal(0, state.StageXSpeed);
Assert.Equal(0, state.YMSpeed); Assert.Equal(0, state.StageYSpeed);
Assert.Equal(0, state.ZTSpeed); Assert.Equal(0, state.SourceZSpeed);
Assert.Equal(0, state.ZDSpeed); Assert.Equal(0, state.DetectorZSpeed);
Assert.Equal(0, state.TiltDSpeed); Assert.Equal(0, state.DetectorSwingSpeed);
Assert.Equal(0, state.DistSpeed); 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] [Fact]
@@ -116,15 +120,15 @@ namespace XplorePlane.Tests.Models
public void MotionState_WithExpression_ProducesNewInstance() public void MotionState_WithExpression_ProducesNewInstance()
{ {
var original = MotionState.Default; var original = MotionState.Default;
var modified = original with { XM = 100 }; var modified = original with { StageX = 100 };
_output.WriteLine($"Original.XM={original.XM}, Modified.XM={modified.XM}, SameRef={ReferenceEquals(original, modified)}"); _output.WriteLine($"Original.StageX={original.StageX}, Modified.StageX={modified.StageX}, SameRef={ReferenceEquals(original, modified)}");
// New instance is different from original // New instance is different from original
Assert.NotSame(original, modified); Assert.NotSame(original, modified);
Assert.Equal(100, modified.XM); Assert.Equal(100, modified.StageX);
// Original is unchanged // Original is unchanged
Assert.Equal(0, original.XM); Assert.Equal(0, original.StageX);
} }
// ── CalibrationMatrix Transform Tests ───────────────────────── // ── CalibrationMatrix Transform Tests ─────────────────────────
@@ -168,4 +172,4 @@ namespace XplorePlane.Tests.Models
Assert.Equal(0, z, precision: 10); Assert.Equal(0, z, precision: 10);
} }
} }
} }
@@ -1,7 +1,12 @@
using Moq; using Moq;
using Prism.Events;
using System; using System;
using System.Windows; using System.Windows;
using XP.Common.Logging.Interfaces; using XP.Common.Logging.Interfaces;
using XP.Hardware.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 XP.Hardware.RaySource.Services;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services.AppState; using XplorePlane.Services.AppState;
@@ -11,30 +16,68 @@ using Xunit.Abstractions;
namespace XplorePlane.Tests.Services namespace XplorePlane.Tests.Services
{ {
/// <summary> /// <summary>
/// AppStateService 单元测试。 /// AppStateService unit tests.
/// 验证默认状态值、Dispose 后行为、null 参数校验、CalibrationMatrix 缺失时的错误处理。 /// Verifies default values, null guards, dispose behavior, and hardware-driven motion-state sync.
/// </summary> /// </summary>
public class AppStateServiceTests : IDisposable public class AppStateServiceTests : IDisposable
{ {
private readonly AppStateService _service; private readonly AppStateService _service;
private readonly Mock<IRaySourceService> _mockRaySource; private readonly Mock<IRaySourceService> _mockRaySource;
private readonly Mock<IMotionSystem> _mockMotionSystem;
private readonly Mock<IMotionControlService> _mockMotionControlService;
private readonly Mock<ILinearAxis> _mockStageX;
private readonly Mock<ILinearAxis> _mockStageY;
private readonly Mock<ILinearAxis> _mockSourceZ;
private readonly Mock<ILinearAxis> _mockDetectorZ;
private readonly Mock<IRotaryAxis> _mockDetectorSwing;
private readonly Mock<IRotaryAxis> _mockStageRotation;
private readonly Mock<IRotaryAxis> _mockFixtureRotation;
private readonly Mock<ILoggerService> _mockLogger; private readonly Mock<ILoggerService> _mockLogger;
private readonly EventAggregator _eventAggregator;
private readonly ITestOutputHelper _output; private readonly ITestOutputHelper _output;
public AppStateServiceTests(ITestOutputHelper output) public AppStateServiceTests(ITestOutputHelper output)
{ {
_output = output; _output = output;
// Ensure WPF Application exists for Dispatcher
if (Application.Current == null) if (Application.Current == null)
{ {
new Application(); new Application();
} }
_mockRaySource = new Mock<IRaySourceService>(); _mockRaySource = new Mock<IRaySourceService>();
_mockMotionSystem = new Mock<IMotionSystem>();
_mockMotionControlService = new Mock<IMotionControlService>();
_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<ILoggerService>(); _mockLogger = new Mock<ILoggerService>();
_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));
_mockLogger.Setup(l => l.ForModule<AppStateService>()).Returns(_mockLogger.Object); _mockLogger.Setup(l => l.ForModule<AppStateService>()).Returns(_mockLogger.Object);
_service = new AppStateService(_mockRaySource.Object, _mockLogger.Object);
_service = new AppStateService(
_mockRaySource.Object,
_mockMotionSystem.Object,
_mockMotionControlService.Object,
_eventAggregator,
_mockLogger.Object);
} }
public void Dispose() public void Dispose()
@@ -42,13 +85,15 @@ namespace XplorePlane.Tests.Services
_service.Dispose(); _service.Dispose();
} }
// ── 默认状态值验证 ──
[Fact] [Fact]
public void DefaultState_MotionState_IsDefault() public void DefaultState_MotionState_IsHardwareSnapshot()
{ {
_output.WriteLine($"MotionState == MotionState.Default: {ReferenceEquals(MotionState.Default, _service.MotionState)}"); Assert.Equal(0, _service.MotionState.StageX);
Assert.Same(MotionState.Default, _service.MotionState); 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] [Fact]
@@ -72,8 +117,6 @@ namespace XplorePlane.Tests.Services
Assert.Null(_service.CalibrationMatrix); Assert.Null(_service.CalibrationMatrix);
} }
// ── null 参数抛出 ArgumentNullException ──
[Fact] [Fact]
public void UpdateMotionState_NullArgument_ThrowsArgumentNullException() public void UpdateMotionState_NullArgument_ThrowsArgumentNullException()
{ {
@@ -102,36 +145,66 @@ namespace XplorePlane.Tests.Services
_output.WriteLine($"UpdateSystemState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}"); _output.WriteLine($"UpdateSystemState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
} }
// ── Dispose 后 Update 被忽略 ──
[Fact] [Fact]
public void Dispose_ThenUpdate_IsIgnored() public void Dispose_ThenUpdate_IsIgnored()
{ {
var originalState = _service.MotionState; var originalState = _service.MotionState;
_service.Dispose(); _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); var newState = new MotionState(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
_service.UpdateMotionState(newState); _service.UpdateMotionState(newState);
_output.WriteLine($"State unchanged after dispose: {ReferenceEquals(originalState, _service.MotionState)}");
Assert.Same(originalState, _service.MotionState); Assert.Same(originalState, _service.MotionState);
} }
// ── CalibrationMatrix 为 null 时 RequestLinkedView 设置错误状态 ──
[Fact] [Fact]
public void RequestLinkedView_NoCalibrationMatrix_SetsErrorState() public void RequestLinkedView_NoCalibrationMatrix_SetsErrorState()
{ {
// CalibrationMatrix is null by default
Assert.Null(_service.CalibrationMatrix); Assert.Null(_service.CalibrationMatrix);
_service.RequestLinkedView(100.0, 200.0); _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.True(_service.SystemState.HasError);
Assert.NotEmpty(_service.SystemState.ErrorMessage); 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<GeometryUpdatedEvent>()
.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);
}
private static Mock<ILinearAxis> CreateLinearAxis(AxisId axisId, double position)
{
var axis = new Mock<ILinearAxis>();
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<IRotaryAxis> CreateRotaryAxis(RotaryAxisId axisId, double angle)
{
var axis = new Mock<IRotaryAxis>();
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;
}
} }
} }
@@ -96,7 +96,7 @@ namespace XplorePlane.Tests.Services
var pipeline = new PipelineModel { Name = "TestPipeline" }; var pipeline = new PipelineModel { Name = "TestPipeline" };
var step = _service.RecordCurrentStep(recipe, pipeline); 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.Equal(0, step.StepIndex);
Assert.Same(motionState, step.MotionState); Assert.Same(motionState, step.MotionState);
@@ -138,4 +138,4 @@ namespace XplorePlane.Tests.Services
} }
} }
} }
} }
@@ -25,7 +25,7 @@ namespace XplorePlane.Tests.ViewModels
{ {
public class CncEditorViewModelTests public class CncEditorViewModelTests
{ {
// 鈹€鈹€ Helpers 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€ // ── Helpers ──────────────────────────────────────────────────────────────────
private static CncEditorViewModel CreateVm( private static CncEditorViewModel CreateVm(
Mock<ICncExecutionService> mockExecSvc = null, Mock<ICncExecutionService> mockExecSvc = null,
@@ -50,7 +50,7 @@ namespace XplorePlane.Tests.ViewModels
DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow,
new List<CncNode> new List<CncNode>
{ {
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())); }.AsReadOnly()));
mockCncProgramSvc mockCncProgramSvc
@@ -121,9 +121,9 @@ namespace XplorePlane.Tests.ViewModels
return new CncProgram(Guid.NewGuid(), "TestProgram", DateTime.UtcNow, DateTime.UtcNow, nodes); 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 // Validates: Requirements 1.1, 1.3, 1.4
[Property(MaxTest = 100)] [Property(MaxTest = 100)]
public Property RunStop_Commands_AreMutuallyExclusive() 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 // Validates: Requirements 1.7, 6.5
[Property(MaxTest = 100)] [Property(MaxTest = 100)]
public Property AfterExecution_IsRunningFalse_AllNodesIdle() 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 // Validates: Requirements 6.7
[Property(MaxTest = 100)] [Property(MaxTest = 100)]
public Property WhileRunning_AllEditCommands_AreDisabled() public Property WhileRunning_AllEditCommands_AreDisabled()
+49 -29
View File
@@ -40,69 +40,89 @@ namespace XplorePlane.Models
Guid Id, Guid Id,
int Index, int Index,
CncNodeType NodeType, CncNodeType NodeType,
string Name string Name);
);
/// <summary>参考点节点 | Reference point node</summary> /// <summary>参考点节点 | Reference point node</summary>
public record ReferencePointNode( public record ReferencePointNode(
Guid Id, int Index, string Name, Guid Id,
double XM, double YM, double ZT, double ZD, double TiltD, double Dist, int Index,
bool IsRayOn, double Voltage, double Current string Name,
) : CncNode(Id, Index, CncNodeType.ReferencePoint, 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);
/// <summary>保存节点(含图像)| Save node with image</summary> /// <summary>保存节点(含图像)| Save node with image</summary>
public record SaveNodeWithImageNode( public record SaveNodeWithImageNode(
Guid Id, int Index, string Name, Guid Id,
int Index,
string Name,
MotionState MotionState, MotionState MotionState,
RaySourceState RaySourceState, RaySourceState RaySourceState,
DetectorState DetectorState, DetectorState DetectorState,
string ImageFileName string ImageFileName) : CncNode(Id, Index, CncNodeType.SaveNodeWithImage, Name);
) : CncNode(Id, Index, CncNodeType.SaveNodeWithImage, Name);
/// <summary>保存节点(不含图像)| Save node without image</summary> /// <summary>保存节点(不含图像)| Save node without image</summary>
public record SaveNodeNode( public record SaveNodeNode(
Guid Id, int Index, string Name, Guid Id,
int Index,
string Name,
MotionState MotionState, MotionState MotionState,
RaySourceState RaySourceState, RaySourceState RaySourceState,
DetectorState DetectorState DetectorState DetectorState) : CncNode(Id, Index, CncNodeType.SaveNode, Name);
) : CncNode(Id, Index, CncNodeType.SaveNode, Name);
/// <summary>保存位置节点 | Save position node</summary> /// <summary>保存位置节点 | Save position node</summary>
public record SavePositionNode( public record SavePositionNode(
Guid Id, int Index, string Name, Guid Id,
MotionState MotionState int Index,
) : CncNode(Id, Index, CncNodeType.SavePosition, Name); string Name,
MotionState MotionState) : CncNode(Id, Index, CncNodeType.SavePosition, Name);
/// <summary>检测模块节点 | Inspection module node</summary> /// <summary>检测模块节点 | Inspection module node</summary>
public record InspectionModuleNode( public record InspectionModuleNode(
Guid Id, int Index, string Name, Guid Id,
PipelineModel Pipeline int Index,
) : CncNode(Id, Index, CncNodeType.InspectionModule, Name); string Name,
PipelineModel Pipeline) : CncNode(Id, Index, CncNodeType.InspectionModule, Name);
/// <summary>检测标记节点 | Inspection marker node</summary> /// <summary>检测标记节点 | Inspection marker node</summary>
public record InspectionMarkerNode( public record InspectionMarkerNode(
Guid Id, int Index, string Name, Guid Id,
int Index,
string Name,
string MarkerType, string MarkerType,
double MarkerX, double MarkerY double MarkerX,
) : CncNode(Id, Index, CncNodeType.InspectionMarker, Name); double MarkerY) : CncNode(Id, Index, CncNodeType.InspectionMarker, Name);
/// <summary>停顿对话框节点 | Pause dialog node</summary> /// <summary>停顿对话框节点 | Pause dialog node</summary>
public record PauseDialogNode( public record PauseDialogNode(
Guid Id, int Index, string Name, Guid Id,
int Index,
string Name,
string DialogTitle, string DialogTitle,
string DialogMessage string DialogMessage) : CncNode(Id, Index, CncNodeType.PauseDialog, Name);
) : CncNode(Id, Index, CncNodeType.PauseDialog, Name);
/// <summary>等待延时节点 | Wait delay node</summary> /// <summary>等待延时节点 | Wait delay node</summary>
public record WaitDelayNode( public record WaitDelayNode(
Guid Id, int Index, string Name, Guid Id,
int DelayMilliseconds int Index,
) : CncNode(Id, Index, CncNodeType.WaitDelay, Name); string Name,
int DelayMilliseconds) : CncNode(Id, Index, CncNodeType.WaitDelay, Name);
/// <summary>完成程序节点 | Complete program node</summary> /// <summary>完成程序节点 | Complete program node</summary>
public record CompleteProgramNode( public record CompleteProgramNode(
Guid Id, int Index, string Name Guid Id,
) : CncNode(Id, Index, CncNodeType.CompleteProgram, Name); int Index,
string Name) : CncNode(Id, Index, CncNodeType.CompleteProgram, Name);
// ── CNC 程序 | CNC Program ──────────────────────────────────────── // ── CNC 程序 | CNC Program ────────────────────────────────────────
+51 -51
View File
@@ -2,8 +2,6 @@ using System;
namespace XplorePlane.Models namespace XplorePlane.Models
{ {
// ── Enumerations ──────────────────────────────────────────────────
/// <summary>系统操作模式</summary> /// <summary>系统操作模式</summary>
public enum OperationMode public enum OperationMode
{ {
@@ -23,82 +21,85 @@ namespace XplorePlane.Models
Error // 出错 Error // 出错
} }
// ── State Records ───────────────────────────────────────────────── // State Records
/// <summary>运动控制状态(不可变)</summary> /// <summary>
/// 运动控制状态(不可变)。
/// 统一的运动与几何快照,与运动硬件模型对齐。
/// </summary>
public record MotionState( public record MotionState(
double XM, // X 轴位置 (μm) double StageX, // X 轴位置μm
double YM, // Y 轴位置 (μm) double StageY, // Y 轴位置μm
double ZT, // Z 上轴位置 (μm) double SourceZ, // Z 上轴位置μm
double ZD, // Z 下轴位置 (μm) double DetectorZ, // Z 下轴位置μm
double TiltD, // 倾斜角度 (m°) double DetectorSwing, // 探测器摆角(°)
double Dist, // 距离 (μm) double FDD, // 焦点-探测器距离(μm
double XMSpeed, // X 轴速度 (μm/s) double StageXSpeed, // X 轴速度μm/s
double YMSpeed, // Y 轴速度 (μm/s) double StageYSpeed, // Y 轴速度μm/s
double ZTSpeed, // Z 上轴速度 (μm/s) double SourceZSpeed, // Z 上轴速度μm/s
double ZDSpeed, // Z 下轴速度 (μm/s) double DetectorZSpeed, // Z 下轴速度μm/s
double TiltDSpeed, // 倾斜速度 (m°/s) double DetectorSwingSpeed, // 探测器摆角速度(°/s
double DistSpeed // 距离速度 (μm/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 static readonly MotionState Default = new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
} }
/// <summary>射线源状态(不可变)</summary> /// <summary>射线源状态(不可变)</summary>
public record RaySourceState( public record RaySourceState(
bool IsOn, // 开关状态 bool IsOn, // 是否开启
double Voltage, // 电压 (kV) double Voltage, // 电压kV
double Power // 功率 (W) double Power) // 功率W
)
{ {
public static readonly RaySourceState Default = new(false, 0, 0); public static readonly RaySourceState Default = new(false, 0, 0);
} }
/// <summary>探测器状态(不可变)</summary> /// <summary>探测器状态(不可变)</summary>
public record DetectorState( public record DetectorState(
bool IsConnected, // 连接状态 bool IsConnected, // 是否已连接
bool IsAcquiring, // 是否正在采集 bool IsAcquiring, // 是否正在采集
double FrameRate, // 当前帧率 (fps) double FrameRate, // 帧率(fps
string Resolution // 分辨率描述,如 "2048x2048" string Resolution) // 分辨率描述
)
{ {
public static readonly DetectorState Default = new(false, false, 0, string.Empty); public static readonly DetectorState Default = new(false, false, 0, string.Empty);
} }
/// <summary>系统状态(不可变)</summary> /// <summary>系统整体状态(不可变)</summary>
public record SystemState( public record SystemState(
OperationMode OperationMode, // 当前操作模式 OperationMode OperationMode, // 当前操作模式
bool HasError, // 是否存在系统错误 bool HasError, // 是否存在错误
string ErrorMessage // 错误描述 string ErrorMessage) // 错误信息
)
{ {
public static readonly SystemState Default = new(OperationMode.Idle, false, string.Empty); public static readonly SystemState Default = new(OperationMode.Idle, false, string.Empty);
} }
/// <summary>摄像头视频流状态(不可变)</summary> /// <summary>相机状态(不可变)</summary>
public record CameraState( public record CameraState(
bool IsConnected, // 连接状态 bool IsConnected, // 是否已连接
bool IsStreaming, // 是否正在推流 bool IsStreaming, // 是否正在推流
object CurrentFrame, // 当前帧数据引用(BitmapSource 或 byte[]Frozen object CurrentFrame, // 当前帧数据
int Width, // 分辨率宽 int Width, // 图像宽度(px
int Height, // 分辨率高 int Height, // 图像高度(px
double FrameRate // 帧率 (fps) double FrameRate) // 帧率fps
)
{ {
public static readonly CameraState Default = new(false, false, null, 0, 0, 0); public static readonly CameraState Default = new(false, false, null, 0, 0, 0);
} }
/// <summary>物理坐标</summary> /// <summary>物理坐标位置</summary>
public record PhysicalPosition(double X, double Y, double Z); public record PhysicalPosition(double X, double Y, double Z);
/// <summary>图像标定矩阵,像素坐标 → 物理坐标映射</summary> /// <summary>标定矩阵3×3 仿射变换)</summary>
public record CalibrationMatrix( public record CalibrationMatrix(
double M11, double M12, double M13, // 3x3 仿射变换矩阵 double M11, double M12, double M13,
double M21, double M22, double M23, double M21, double M22, double M23,
double M31, double M32, double M33 double M31, double M32, double M33)
)
{ {
/// <summary>将像素坐标换为物理坐标</summary> /// <summary>将像素坐标换为物理坐标</summary>
public (double X, double Y, double Z) Transform(double pixelX, double pixelY) public (double X, double Y, double Z) Transform(double pixelX, double pixelY)
{ {
double x = M11 * pixelX + M12 * pixelY + M13; double x = M11 * pixelX + M12 * pixelY + M13;
@@ -108,13 +109,12 @@ namespace XplorePlane.Models
} }
} }
/// <summary>画面联动状态(不可变)</summary> /// <summary>联动视图状态(不可变)</summary>
public record LinkedViewState( public record LinkedViewState(
PhysicalPosition TargetPosition, // 目标物理坐标 PhysicalPosition TargetPosition, // 目标物理位置
bool IsExecuting, // 联动是否正在执行 bool IsExecuting, // 是否正在执行移动
DateTime LastRequestTime // 最近一次联动请求时间 DateTime LastRequestTime) // 最近一次请求时间
)
{ {
public static readonly LinkedViewState Default = new(new PhysicalPosition(0, 0, 0), false, DateTime.MinValue); public static readonly LinkedViewState Default = new(new PhysicalPosition(0, 0, 0), false, DateTime.MinValue);
} }
} }
+192 -51
View File
@@ -1,29 +1,40 @@
using Prism.Events;
using Prism.Mvvm; using Prism.Mvvm;
using System; using System;
using System.Threading; using System.Threading;
using System.Windows; using System.Windows;
using System.Windows.Threading; using System.Windows.Threading;
using XP.Common.Logging.Interfaces; using XP.Common.Logging.Interfaces;
using XP.Hardware.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 XP.Hardware.RaySource.Services;
using XplorePlane.Models; using XplorePlane.Models;
namespace XplorePlane.Services.AppState namespace XplorePlane.Services.AppState
{ {
/// <summary> /// <summary>
/// 全局应用状态管理服务实现。 /// Global application state service.
/// 继承 BindableBase 以支持 WPF 数据绑定,使用 Interlocked.Exchange 保证线程安全写入, /// Motion state is synchronized from the motion hardware service layer and
/// 通过 Dispatcher.BeginInvoke 将事件调度到 UI 线程。 /// mapped into the legacy business model for compatibility.
/// </summary> /// </summary>
public class AppStateService : BindableBase, IAppStateService public class AppStateService : BindableBase, IAppStateService
{ {
private readonly Dispatcher _dispatcher; private readonly Dispatcher _dispatcher;
private readonly IRaySourceService _raySourceService; private readonly IRaySourceService _raySourceService;
private readonly IMotionSystem _motionSystem;
private readonly IMotionControlService _motionControlService;
private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger; private readonly ILoggerService _logger;
private readonly SubscriptionToken _axisStatusChangedToken;
private readonly SubscriptionToken _geometryUpdatedToken;
private bool _disposed; private bool _disposed;
private GeometryData _latestGeometry;
// ── 状态字段(通过 Interlocked.Exchange 原子替换)── // ── 状态字段(通过 Interlocked.Exchange 原子替换)──
private MotionState _motionState = MotionState.Default; private MotionState _motionState = MotionState.Default;
private RaySourceState _raySourceState = RaySourceState.Default; private RaySourceState _raySourceState = RaySourceState.Default;
private DetectorState _detectorState = DetectorState.Default; private DetectorState _detectorState = DetectorState.Default;
private SystemState _systemState = SystemState.Default; private SystemState _systemState = SystemState.Default;
@@ -34,24 +45,16 @@ namespace XplorePlane.Services.AppState
// ── 类型化状态变更事件 ── // ── 类型化状态变更事件 ──
public event EventHandler<StateChangedEventArgs<MotionState>> MotionStateChanged; public event EventHandler<StateChangedEventArgs<MotionState>> MotionStateChanged;
public event EventHandler<StateChangedEventArgs<RaySourceState>> RaySourceStateChanged; public event EventHandler<StateChangedEventArgs<RaySourceState>> RaySourceStateChanged;
public event EventHandler<StateChangedEventArgs<DetectorState>> DetectorStateChanged; public event EventHandler<StateChangedEventArgs<DetectorState>> DetectorStateChanged;
public event EventHandler<StateChangedEventArgs<SystemState>> SystemStateChanged; public event EventHandler<StateChangedEventArgs<SystemState>> SystemStateChanged;
public event EventHandler<StateChangedEventArgs<CameraState>> CameraStateChanged; public event EventHandler<StateChangedEventArgs<CameraState>> CameraStateChanged;
public event EventHandler<StateChangedEventArgs<LinkedViewState>> LinkedViewStateChanged; public event EventHandler<StateChangedEventArgs<LinkedViewState>> LinkedViewStateChanged;
public event EventHandler<StateChangedEventArgs<RecipeExecutionState>> RecipeExecutionStateChanged; public event EventHandler<StateChangedEventArgs<RecipeExecutionState>> RecipeExecutionStateChanged;
public event EventHandler<LinkedViewRequestEventArgs> LinkedViewRequested; public event EventHandler<LinkedViewRequestEventArgs> LinkedViewRequested;
// ── 状态属性(只读)── // ── 状态属性(只读)──
public MotionState MotionState => _motionState; public MotionState MotionState => _motionState;
public RaySourceState RaySourceState => _raySourceState; public RaySourceState RaySourceState => _raySourceState;
public DetectorState DetectorState => _detectorState; public DetectorState DetectorState => _detectorState;
public SystemState SystemState => _systemState; public SystemState SystemState => _systemState;
@@ -62,17 +65,34 @@ namespace XplorePlane.Services.AppState
public AppStateService( public AppStateService(
IRaySourceService raySourceService, IRaySourceService raySourceService,
IMotionSystem motionSystem,
IMotionControlService motionControlService,
IEventAggregator eventAggregator,
ILoggerService logger) ILoggerService logger)
{ {
ArgumentNullException.ThrowIfNull(raySourceService); ArgumentNullException.ThrowIfNull(raySourceService);
ArgumentNullException.ThrowIfNull(motionSystem);
ArgumentNullException.ThrowIfNull(motionControlService);
ArgumentNullException.ThrowIfNull(eventAggregator);
ArgumentNullException.ThrowIfNull(logger); ArgumentNullException.ThrowIfNull(logger);
_raySourceService = raySourceService; _raySourceService = raySourceService;
_motionSystem = motionSystem;
_motionControlService = motionControlService;
_eventAggregator = eventAggregator;
_logger = logger.ForModule<AppStateService>(); _logger = logger.ForModule<AppStateService>();
_dispatcher = Application.Current.Dispatcher; _dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
_geometryUpdatedToken = _eventAggregator
.GetEvent<GeometryUpdatedEvent>()
.Subscribe(OnGeometryUpdated);
_axisStatusChangedToken = _eventAggregator
.GetEvent<AxisStatusChangedEvent>()
.Subscribe(OnAxisStatusChanged);
SubscribeToExistingServices(); SubscribeToExistingServices();
_logger.Info("AppStateService 已初始化"); _logger.Info("AppStateService initialized");
} }
// ── 状态更新方法 ── // ── 状态更新方法 ──
@@ -80,17 +100,30 @@ namespace XplorePlane.Services.AppState
public void UpdateMotionState(MotionState newState) public void UpdateMotionState(MotionState newState)
{ {
ArgumentNullException.ThrowIfNull(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); // Keep the legacy API surface, but let the hardware service layer
if (ReferenceEquals(old, newState)) return; // remain the source of truth whenever a fresh hardware snapshot is available.
RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState)); if (TryRefreshMotionStateFromHardware("UpdateMotionState"))
{
return;
}
SetMotionState(newState);
} }
public void UpdateRaySourceState(RaySourceState newState) public void UpdateRaySourceState(RaySourceState newState)
{ {
ArgumentNullException.ThrowIfNull(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); var old = Interlocked.Exchange(ref _raySourceState, newState);
if (ReferenceEquals(old, newState)) return; if (ReferenceEquals(old, newState)) return;
@@ -100,7 +133,11 @@ namespace XplorePlane.Services.AppState
public void UpdateDetectorState(DetectorState newState) public void UpdateDetectorState(DetectorState newState)
{ {
ArgumentNullException.ThrowIfNull(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); var old = Interlocked.Exchange(ref _detectorState, newState);
if (ReferenceEquals(old, newState)) return; if (ReferenceEquals(old, newState)) return;
@@ -110,7 +147,11 @@ namespace XplorePlane.Services.AppState
public void UpdateSystemState(SystemState newState) public void UpdateSystemState(SystemState newState)
{ {
ArgumentNullException.ThrowIfNull(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); var old = Interlocked.Exchange(ref _systemState, newState);
if (ReferenceEquals(old, newState)) return; if (ReferenceEquals(old, newState)) return;
@@ -120,7 +161,11 @@ namespace XplorePlane.Services.AppState
public void UpdateCameraState(CameraState newState) public void UpdateCameraState(CameraState newState)
{ {
ArgumentNullException.ThrowIfNull(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); var old = Interlocked.Exchange(ref _cameraState, newState);
if (ReferenceEquals(old, newState)) return; if (ReferenceEquals(old, newState)) return;
@@ -130,21 +175,26 @@ namespace XplorePlane.Services.AppState
public void UpdateCalibrationMatrix(CalibrationMatrix newMatrix) public void UpdateCalibrationMatrix(CalibrationMatrix newMatrix)
{ {
ArgumentNullException.ThrowIfNull(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); var old = Interlocked.Exchange(ref _calibrationMatrix, newMatrix);
if (ReferenceEquals(old, newMatrix)) return; if (ReferenceEquals(old, newMatrix)) return;
_dispatcher.BeginInvoke(() => _dispatcher.BeginInvoke(() => RaisePropertyChanged(nameof(CalibrationMatrix)));
{
RaisePropertyChanged(nameof(CalibrationMatrix));
});
} }
public void UpdateLinkedViewState(LinkedViewState newState) public void UpdateLinkedViewState(LinkedViewState newState)
{ {
ArgumentNullException.ThrowIfNull(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); var old = Interlocked.Exchange(ref _linkedViewState, newState);
if (ReferenceEquals(old, newState)) return; if (ReferenceEquals(old, newState)) return;
@@ -154,7 +204,11 @@ namespace XplorePlane.Services.AppState
public void UpdateRecipeExecutionState(RecipeExecutionState newState) public void UpdateRecipeExecutionState(RecipeExecutionState newState)
{ {
ArgumentNullException.ThrowIfNull(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); var old = Interlocked.Exchange(ref _recipeExecutionState, newState);
if (ReferenceEquals(old, newState)) return; if (ReferenceEquals(old, newState)) return;
@@ -168,11 +222,11 @@ namespace XplorePlane.Services.AppState
var matrix = _calibrationMatrix; var matrix = _calibrationMatrix;
if (matrix is null) 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 UpdateSystemState(SystemState with
{ {
HasError = true, HasError = true,
ErrorMessage = "CalibrationMatrix 未设置,无法执行画面联动" ErrorMessage = "CalibrationMatrix is not configured, cannot execute linked view request"
}); });
return; return;
} }
@@ -191,10 +245,38 @@ namespace XplorePlane.Services.AppState
}); });
} }
// ── 内部辅助方法 ── public void Dispose()
{
if (_disposed) return;
_disposed = true;
private void RaiseOnDispatcher<T>(T oldVal, T newVal, if (_axisStatusChangedToken is not null)
EventHandler<StateChangedEventArgs<T>> handler, string propertyName) {
_eventAggregator.GetEvent<AxisStatusChangedEvent>().Unsubscribe(_axisStatusChangedToken);
}
if (_geometryUpdatedToken is not null)
{
_eventAggregator.GetEvent<GeometryUpdatedEvent>().Unsubscribe(_geometryUpdatedToken);
}
MotionStateChanged = null;
RaySourceStateChanged = null;
DetectorStateChanged = null;
SystemStateChanged = null;
CameraStateChanged = null;
LinkedViewStateChanged = null;
RecipeExecutionStateChanged = null;
LinkedViewRequested = null;
_logger.Info("AppStateService disposed");
}
private void RaiseOnDispatcher<T>(
T oldVal,
T newVal,
EventHandler<StateChangedEventArgs<T>> handler,
string propertyName)
{ {
_dispatcher.BeginInvoke(() => _dispatcher.BeginInvoke(() =>
{ {
@@ -205,34 +287,93 @@ namespace XplorePlane.Services.AppState
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Error(ex, "状态变更事件处理器抛出异常 (property={PropertyName})", propertyName); _logger.Error(ex, "State changed handler failed (property={PropertyName})", propertyName);
} }
}); });
} }
private void SubscribeToExistingServices() 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 ── private void OnAxisStatusChanged(AxisStatusChangedData _)
public void Dispose()
{ {
if (_disposed) return; if (_disposed) return;
_disposed = true; TryRefreshMotionStateFromHardware("axis-status-changed");
}
// 清除所有事件订阅 private void OnGeometryUpdated(GeometryData geometry)
MotionStateChanged = null; {
RaySourceStateChanged = null; if (_disposed) return;
DetectorStateChanged = null;
SystemStateChanged = null;
CameraStateChanged = null;
LinkedViewStateChanged = null;
RecipeExecutionStateChanged = null;
LinkedViewRequested = null;
_logger.Info("AppStateService 已释放"); _latestGeometry = geometry;
TryRefreshMotionStateFromHardware("geometry-updated");
}
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));
} }
} }
} }
+11 -7
View File
@@ -389,15 +389,19 @@ namespace XplorePlane.Services.Cnc
var raySource = _appStateService.RaySourceState; var raySource = _appStateService.RaySourceState;
return new ReferencePointNode( return new ReferencePointNode(
id, index, $"参考点_{index}", id, index, $"参考点_{index}",
XM: motion.XM, StageX: motion.StageX,
YM: motion.YM, StageY: motion.StageY,
ZT: motion.ZT, SourceZ: motion.SourceZ,
ZD: motion.ZD, DetectorZ: motion.DetectorZ,
TiltD: motion.TiltD, DetectorSwing: motion.DetectorSwing,
Dist: motion.Dist, FDD: motion.FDD,
IsRayOn: raySource.IsOn, IsRayOn: raySource.IsOn,
Voltage: raySource.Voltage, Voltage: raySource.Voltage,
Current: TryReadCurrent()); Current: TryReadCurrent(),
StageRotation: motion.StageRotation,
FixtureRotation: motion.FixtureRotation,
FOD: motion.FOD,
Magnification: motion.Magnification);
} }
/// <summary>创建保存节点(含图像)| Create save node with image</summary> /// <summary>创建保存节点(含图像)| Create save node with image</summary>
@@ -417,7 +417,7 @@ namespace XplorePlane.ViewModels.Cnc
return; return;
var sb = new StringBuilder(); 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,StageX,StageY,SourceZ,DetectorZ,DetectorSwing,StageRotation,FixtureRotation,FOD,FDD,Magnification,Voltage_kV,Current_uA,Power_W,RayOn,DetectorConnected,FrameRate,Resolution,ImageFile,MarkerType,MarkerX,MarkerY,DialogTitle,DialogMessage,DelayMs,Pipeline");
var inv = CultureInfo.InvariantCulture; var inv = CultureInfo.InvariantCulture;
@@ -425,16 +425,16 @@ namespace XplorePlane.ViewModels.Cnc
{ {
var row = node switch 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},,,,,,,,,,", ReferencePointNode rp => $"{rp.Index},{rp.NodeType},{Esc(rp.Name)},{rp.StageX.ToString(inv)},{rp.StageY.ToString(inv)},{rp.SourceZ.ToString(inv)},{rp.DetectorZ.ToString(inv)},{rp.DetectorSwing.ToString(inv)},{rp.StageRotation.ToString(inv)},{rp.FixtureRotation.ToString(inv)},{rp.FOD.ToString(inv)},{rp.FDD.ToString(inv)},{rp.Magnification.ToString(inv)},{rp.Voltage.ToString(inv)},{rp.Current.ToString(inv)},,{rp.IsRayOn},,,,,,,,,,",
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)},,,,,,", SaveNodeWithImageNode sni => $"{sni.Index},{sni.NodeType},{Esc(sni.Name)},{sni.MotionState.StageX.ToString(inv)},{sni.MotionState.StageY.ToString(inv)},{sni.MotionState.SourceZ.ToString(inv)},{sni.MotionState.DetectorZ.ToString(inv)},{sni.MotionState.DetectorSwing.ToString(inv)},{sni.MotionState.StageRotation.ToString(inv)},{sni.MotionState.FixtureRotation.ToString(inv)},{sni.MotionState.FOD.ToString(inv)},{sni.MotionState.FDD.ToString(inv)},{sni.MotionState.Magnification.ToString(inv)},{sni.RaySourceState.Voltage.ToString(inv)},,{sni.RaySourceState.Power.ToString(inv)},{sni.RaySourceState.IsOn},{sni.DetectorState.IsConnected},{sni.DetectorState.FrameRate.ToString(inv)},{Esc(sni.DetectorState.Resolution)},{Esc(sni.ImageFileName)},,,,,,",
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)},,,,,,,", SaveNodeNode sn => $"{sn.Index},{sn.NodeType},{Esc(sn.Name)},{sn.MotionState.StageX.ToString(inv)},{sn.MotionState.StageY.ToString(inv)},{sn.MotionState.SourceZ.ToString(inv)},{sn.MotionState.DetectorZ.ToString(inv)},{sn.MotionState.DetectorSwing.ToString(inv)},{sn.MotionState.StageRotation.ToString(inv)},{sn.MotionState.FixtureRotation.ToString(inv)},{sn.MotionState.FOD.ToString(inv)},{sn.MotionState.FDD.ToString(inv)},{sn.MotionState.Magnification.ToString(inv)},{sn.RaySourceState.Voltage.ToString(inv)},,{sn.RaySourceState.Power.ToString(inv)},{sn.RaySourceState.IsOn},{sn.DetectorState.IsConnected},{sn.DetectorState.FrameRate.ToString(inv)},{Esc(sn.DetectorState.Resolution)},,,,,,,",
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)},,,,,,,,,,,,,,", SavePositionNode sp => $"{sp.Index},{sp.NodeType},{Esc(sp.Name)},{sp.MotionState.StageX.ToString(inv)},{sp.MotionState.StageY.ToString(inv)},{sp.MotionState.SourceZ.ToString(inv)},{sp.MotionState.DetectorZ.ToString(inv)},{sp.MotionState.DetectorSwing.ToString(inv)},{sp.MotionState.StageRotation.ToString(inv)},{sp.MotionState.FixtureRotation.ToString(inv)},{sp.MotionState.FOD.ToString(inv)},{sp.MotionState.FDD.ToString(inv)},{sp.MotionState.Magnification.ToString(inv)},,,,,,,,,,,,,,",
InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}", InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}",
InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,", InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,",
PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,", PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
WaitDelayNode wd => $"{wd.Index},{wd.NodeType},{Esc(wd.Name)},,,,,,,,,,,,,,,,,,,{wd.DelayMilliseconds},", WaitDelayNode wd => $"{wd.Index},{wd.NodeType},{Esc(wd.Name)},,,,,,,,,,,,,,,,,,,,,{wd.DelayMilliseconds},",
CompleteProgramNode cp => $"{cp.Index},{cp.NodeType},{Esc(cp.Name)},,,,,,,,,,,,,,,,,,,,", CompleteProgramNode cp => $"{cp.Index},{cp.NodeType},{Esc(cp.Name)},,,,,,,,,,,,,,,,,,,,,,",
_ => $"{node.Index},{node.NodeType},{Esc(node.Name)},,,,,,,,,,,,,,,,,,,," _ => $"{node.Index},{node.NodeType},{Esc(node.Name)},,,,,,,,,,,,,,,,,,,,,,"
}; };
sb.AppendLine(row); sb.AppendLine(row);
@@ -551,6 +551,7 @@ namespace XplorePlane.ViewModels.Cnc
finally finally
{ {
IsRunning = false; IsRunning = false;
ResetAllNodeStates();
_cts?.Dispose(); _cts?.Dispose();
_cts = null; _cts = null;
} }
@@ -1,4 +1,4 @@
using Microsoft.Win32; using Microsoft.Win32;
using Prism.Commands; using Prism.Commands;
using Prism.Mvvm; using Prism.Mvvm;
using System; using System;
+128 -60
View File
@@ -116,82 +116,134 @@ namespace XplorePlane.ViewModels.Cnc
_ => string.Empty _ => string.Empty
}; };
public double XM public double StageX
{ {
get => _model switch get => _model switch
{ {
ReferencePointNode rp => rp.XM, ReferencePointNode rp => rp.StageX,
SaveNodeNode sn => sn.MotionState.XM, SaveNodeNode sn => sn.MotionState.StageX,
SaveNodeWithImageNode sni => sni.MotionState.XM, SaveNodeWithImageNode sni => sni.MotionState.StageX,
SavePositionNode sp => sp.MotionState.XM, SavePositionNode sp => sp.MotionState.StageX,
_ => 0d _ => 0d
}; };
set => UpdateMotion(value, MotionAxis.XM); set => UpdateMotion(value, MotionAxis.StageX);
} }
public double YM public double StageY
{ {
get => _model switch get => _model switch
{ {
ReferencePointNode rp => rp.YM, ReferencePointNode rp => rp.StageY,
SaveNodeNode sn => sn.MotionState.YM, SaveNodeNode sn => sn.MotionState.StageY,
SaveNodeWithImageNode sni => sni.MotionState.YM, SaveNodeWithImageNode sni => sni.MotionState.StageY,
SavePositionNode sp => sp.MotionState.YM, SavePositionNode sp => sp.MotionState.StageY,
_ => 0d _ => 0d
}; };
set => UpdateMotion(value, MotionAxis.YM); set => UpdateMotion(value, MotionAxis.StageY);
} }
public double ZT public double SourceZ
{ {
get => _model switch get => _model switch
{ {
ReferencePointNode rp => rp.ZT, ReferencePointNode rp => rp.SourceZ,
SaveNodeNode sn => sn.MotionState.ZT, SaveNodeNode sn => sn.MotionState.SourceZ,
SaveNodeWithImageNode sni => sni.MotionState.ZT, SaveNodeWithImageNode sni => sni.MotionState.SourceZ,
SavePositionNode sp => sp.MotionState.ZT, SavePositionNode sp => sp.MotionState.SourceZ,
_ => 0d _ => 0d
}; };
set => UpdateMotion(value, MotionAxis.ZT); set => UpdateMotion(value, MotionAxis.SourceZ);
} }
public double ZD public double DetectorZ
{ {
get => _model switch get => _model switch
{ {
ReferencePointNode rp => rp.ZD, ReferencePointNode rp => rp.DetectorZ,
SaveNodeNode sn => sn.MotionState.ZD, SaveNodeNode sn => sn.MotionState.DetectorZ,
SaveNodeWithImageNode sni => sni.MotionState.ZD, SaveNodeWithImageNode sni => sni.MotionState.DetectorZ,
SavePositionNode sp => sp.MotionState.ZD, SavePositionNode sp => sp.MotionState.DetectorZ,
_ => 0d _ => 0d
}; };
set => UpdateMotion(value, MotionAxis.ZD); set => UpdateMotion(value, MotionAxis.DetectorZ);
} }
public double TiltD public double DetectorSwing
{ {
get => _model switch get => _model switch
{ {
ReferencePointNode rp => rp.TiltD, ReferencePointNode rp => rp.DetectorSwing,
SaveNodeNode sn => sn.MotionState.TiltD, SaveNodeNode sn => sn.MotionState.DetectorSwing,
SaveNodeWithImageNode sni => sni.MotionState.TiltD, SaveNodeWithImageNode sni => sni.MotionState.DetectorSwing,
SavePositionNode sp => sp.MotionState.TiltD, SavePositionNode sp => sp.MotionState.DetectorSwing,
_ => 0d _ => 0d
}; };
set => UpdateMotion(value, MotionAxis.TiltD); set => UpdateMotion(value, MotionAxis.DetectorSwing);
} }
public double Dist public double StageRotation
{ {
get => _model switch get => _model switch
{ {
ReferencePointNode rp => rp.Dist, ReferencePointNode rp => rp.StageRotation,
SaveNodeNode sn => sn.MotionState.Dist, SaveNodeNode sn => sn.MotionState.StageRotation,
SaveNodeWithImageNode sni => sni.MotionState.Dist, SaveNodeWithImageNode sni => sni.MotionState.StageRotation,
SavePositionNode sp => sp.MotionState.Dist, SavePositionNode sp => sp.MotionState.StageRotation,
_ => 0d _ => 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 public bool IsRayOn
@@ -426,12 +478,16 @@ namespace XplorePlane.ViewModels.Cnc
case ReferencePointNode rp: case ReferencePointNode rp:
UpdateModel(axis switch UpdateModel(axis switch
{ {
MotionAxis.XM => rp with { XM = value }, MotionAxis.StageX => rp with { StageX = value },
MotionAxis.YM => rp with { YM = value }, MotionAxis.StageY => rp with { StageY = value },
MotionAxis.ZT => rp with { ZT = value }, MotionAxis.SourceZ => rp with { SourceZ = value },
MotionAxis.ZD => rp with { ZD = value }, MotionAxis.DetectorZ => rp with { DetectorZ = value },
MotionAxis.TiltD => rp with { TiltD = value }, MotionAxis.DetectorSwing => rp with { DetectorSwing = value },
MotionAxis.Dist => rp with { Dist = 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 _ => rp
}); });
break; break;
@@ -519,12 +575,16 @@ namespace XplorePlane.ViewModels.Cnc
{ {
return axis switch return axis switch
{ {
MotionAxis.XM => state with { XM = value }, MotionAxis.StageX => state with { StageX = value },
MotionAxis.YM => state with { YM = value }, MotionAxis.StageY => state with { StageY = value },
MotionAxis.ZT => state with { ZT = value }, MotionAxis.SourceZ => state with { SourceZ = value },
MotionAxis.ZD => state with { ZD = value }, MotionAxis.DetectorZ => state with { DetectorZ = value },
MotionAxis.TiltD => state with { TiltD = value }, MotionAxis.DetectorSwing => state with { DetectorSwing = value },
MotionAxis.Dist => state with { Dist = 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 _ => state
}; };
} }
@@ -557,12 +617,16 @@ namespace XplorePlane.ViewModels.Cnc
RaisePropertyChanged(nameof(IsPositionChild)); RaisePropertyChanged(nameof(IsPositionChild));
RaisePropertyChanged(nameof(IsMotionSnapshotNode)); RaisePropertyChanged(nameof(IsMotionSnapshotNode));
RaisePropertyChanged(nameof(RelationTag)); RaisePropertyChanged(nameof(RelationTag));
RaisePropertyChanged(nameof(XM)); RaisePropertyChanged(nameof(StageX));
RaisePropertyChanged(nameof(YM)); RaisePropertyChanged(nameof(StageY));
RaisePropertyChanged(nameof(ZT)); RaisePropertyChanged(nameof(SourceZ));
RaisePropertyChanged(nameof(ZD)); RaisePropertyChanged(nameof(DetectorZ));
RaisePropertyChanged(nameof(TiltD)); RaisePropertyChanged(nameof(DetectorSwing));
RaisePropertyChanged(nameof(Dist)); RaisePropertyChanged(nameof(StageRotation));
RaisePropertyChanged(nameof(FixtureRotation));
RaisePropertyChanged(nameof(FOD));
RaisePropertyChanged(nameof(FDD));
RaisePropertyChanged(nameof(Magnification));
RaisePropertyChanged(nameof(IsRayOn)); RaisePropertyChanged(nameof(IsRayOn));
RaisePropertyChanged(nameof(Voltage)); RaisePropertyChanged(nameof(Voltage));
RaisePropertyChanged(nameof(Current)); RaisePropertyChanged(nameof(Current));
@@ -591,12 +655,16 @@ namespace XplorePlane.ViewModels.Cnc
private enum MotionAxis private enum MotionAxis
{ {
XM, StageX,
YM, StageY,
ZT, SourceZ,
ZD, DetectorZ,
TiltD, DetectorSwing,
Dist StageRotation,
FixtureRotation,
FOD,
FDD,
Magnification
} }
} }
} }
@@ -1,4 +1,4 @@
using Microsoft.Win32; using Microsoft.Win32;
using Prism.Events; using Prism.Events;
using Prism.Commands; using Prism.Commands;
using Prism.Mvvm; using Prism.Mvvm;
+1 -1
View File
@@ -1,4 +1,4 @@
using Microsoft.Win32; using Microsoft.Win32;
using Prism.Commands; using Prism.Commands;
using Prism.Events; using Prism.Events;
using Prism.Ioc; using Prism.Ioc;
+1 -1
View File
@@ -1,4 +1,4 @@
<Window <Window
x:Class="XplorePlane.Views.Cnc.CncEditorWindow" x:Class="XplorePlane.Views.Cnc.CncEditorWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+28 -12
View File
@@ -489,28 +489,44 @@
Visibility="{Binding SelectedNode.IsMotionSnapshotNode, Converter={StaticResource BoolToVisibilityConverter}}"> Visibility="{Binding SelectedNode.IsMotionSnapshotNode, Converter={StaticResource BoolToVisibilityConverter}}">
<UniformGrid Margin="8,8,8,6" Columns="2"> <UniformGrid Margin="8,8,8,6" Columns="2">
<StackPanel Margin="0,0,6,0"> <StackPanel Margin="0,0,6,0">
<TextBlock Style="{StaticResource LabelStyle}" Text="XM" /> <TextBlock Style="{StaticResource LabelStyle}" Text="StageX" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.XM, UpdateSourceTrigger=LostFocus}" /> <TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageX, UpdateSourceTrigger=LostFocus}" />
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource LabelStyle}" Text="YM" /> <TextBlock Style="{StaticResource LabelStyle}" Text="StageY" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.YM, UpdateSourceTrigger=LostFocus}" /> <TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageY, UpdateSourceTrigger=LostFocus}" />
</StackPanel> </StackPanel>
<StackPanel Margin="0,0,6,0"> <StackPanel Margin="0,0,6,0">
<TextBlock Style="{StaticResource LabelStyle}" Text="ZT" /> <TextBlock Style="{StaticResource LabelStyle}" Text="SourceZ" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZT, UpdateSourceTrigger=LostFocus}" /> <TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.SourceZ, UpdateSourceTrigger=LostFocus}" />
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource LabelStyle}" Text="ZD" /> <TextBlock Style="{StaticResource LabelStyle}" Text="DetectorZ" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZD, UpdateSourceTrigger=LostFocus}" /> <TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DetectorZ, UpdateSourceTrigger=LostFocus}" />
</StackPanel> </StackPanel>
<StackPanel Margin="0,0,6,0"> <StackPanel Margin="0,0,6,0">
<TextBlock Style="{StaticResource LabelStyle}" Text="TiltD" /> <TextBlock Style="{StaticResource LabelStyle}" Text="DetectorSwing" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.TiltD, UpdateSourceTrigger=LostFocus}" /> <TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DetectorSwing, UpdateSourceTrigger=LostFocus}" />
</StackPanel> </StackPanel>
<StackPanel> <StackPanel>
<TextBlock Style="{StaticResource LabelStyle}" Text="Dist" /> <TextBlock Style="{StaticResource LabelStyle}" Text="StageRotation" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Dist, UpdateSourceTrigger=LostFocus}" /> <TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.StageRotation, UpdateSourceTrigger=LostFocus}" />
</StackPanel>
<StackPanel Margin="0,0,6,0">
<TextBlock Style="{StaticResource LabelStyle}" Text="FixtureRotation" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FixtureRotation, UpdateSourceTrigger=LostFocus}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource LabelStyle}" Text="FOD" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FOD, UpdateSourceTrigger=LostFocus}" />
</StackPanel>
<StackPanel Margin="0,0,6,0">
<TextBlock Style="{StaticResource LabelStyle}" Text="FDD" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FDD, UpdateSourceTrigger=LostFocus}" />
</StackPanel>
<StackPanel>
<TextBlock Style="{StaticResource LabelStyle}" Text="Magnification" />
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Magnification, UpdateSourceTrigger=LostFocus}" />
</StackPanel> </StackPanel>
</UniformGrid> </UniformGrid>
</GroupBox> </GroupBox>
@@ -53,7 +53,7 @@
Margin="0,0,4,3" Margin="0,0,4,3"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="#333333" Foreground="#333333"
Text="XM" /> Text="StageX" />
<TextBox <TextBox
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
@@ -94,7 +94,7 @@
Margin="0,0,4,3" Margin="0,0,4,3"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="#333333" Foreground="#333333"
Text="YM" /> Text="StageY" />
<TextBox <TextBox
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
@@ -127,7 +127,7 @@
Margin="0,0,4,3" Margin="0,0,4,3"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="#333333" Foreground="#333333"
Text="ZT" /> Text="SourceZ" />
<TextBox <TextBox
Grid.Row="2" Grid.Row="2"
Grid.Column="1" Grid.Column="1"
@@ -151,7 +151,7 @@
Margin="0,0,4,3" Margin="0,0,4,3"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="#333333" Foreground="#333333"
Text="ZD" /> Text="DetectorZ" />
<TextBox <TextBox
Grid.Row="3" Grid.Row="3"
Grid.Column="1" Grid.Column="1"
@@ -175,7 +175,7 @@
Margin="0,0,4,3" Margin="0,0,4,3"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="#333333" Foreground="#333333"
Text="TiltD" /> Text="DetectorSwing" />
<TextBox <TextBox
Grid.Row="4" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
@@ -199,7 +199,7 @@
Margin="0,0,4,0" Margin="0,0,4,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="#333333" Foreground="#333333"
Text="Dist" /> Text="FDD" />
<TextBox <TextBox
Grid.Row="5" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
@@ -228,4 +228,4 @@
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</UserControl> </UserControl>
@@ -1,4 +1,4 @@
<UserControl <UserControl
x:Class="XplorePlane.Views.PipelineEditorView" x:Class="XplorePlane.Views.PipelineEditorView"
x:Name="RootControl" x:Name="RootControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+1 -1
View File
@@ -1,4 +1,4 @@
<UserControl <UserControl
x:Class="XplorePlane.Views.ImagePanelView" x:Class="XplorePlane.Views.ImagePanelView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+1 -1
View File
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework> <TargetFramework>net8.0-windows</TargetFramework>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup /> <PropertyGroup />
</Project> </Project>