修改说明
This commit is contained in:
@@ -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 ─────────────────────────
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
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.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 XP.Hardware.RaySource.Services;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
using XplorePlane.Services.AppState;
|
using XplorePlane.Services.AppState;
|
||||||
@@ -11,30 +19,82 @@ 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<IDetectorService> _mockDetectorService;
|
||||||
|
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>();
|
||||||
|
_mockDetectorService = new Mock<IDetectorService>();
|
||||||
|
_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));
|
||||||
|
|
||||||
|
// DetectorService:GetInfo 在未初始化时抛出,模拟此行为
|
||||||
|
_mockDetectorService
|
||||||
|
.Setup(x => x.GetInfo())
|
||||||
|
.Throws(new InvalidOperationException("探测器未初始化"));
|
||||||
|
_mockDetectorService
|
||||||
|
.SetupGet(x => x.Status)
|
||||||
|
.Returns(DetectorStatus.Uninitialized);
|
||||||
|
_mockDetectorService
|
||||||
|
.SetupGet(x => x.IsConnected)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
_mockLogger.Setup(l => l.ForModule<AppStateService>()).Returns(_mockLogger.Object);
|
_mockLogger.Setup(l => l.ForModule<AppStateService>()).Returns(_mockLogger.Object);
|
||||||
_service = new AppStateService(_mockRaySource.Object, _mockLogger.Object);
|
|
||||||
|
_service = new AppStateService(
|
||||||
|
_mockRaySource.Object,
|
||||||
|
_mockMotionSystem.Object,
|
||||||
|
_mockMotionControlService.Object,
|
||||||
|
_mockDetectorService.Object,
|
||||||
|
_eventAggregator,
|
||||||
|
_mockLogger.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -42,13 +102,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 +134,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 +162,117 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StatusChangedEvent_Acquiring_SyncsDetectorState()
|
||||||
|
{
|
||||||
|
// 模拟探测器进入采集状态
|
||||||
|
_mockDetectorService.SetupGet(x => x.Status).Returns(DetectorStatus.Acquiring);
|
||||||
|
_mockDetectorService.Setup(x => x.GetInfo()).Throws(new InvalidOperationException());
|
||||||
|
|
||||||
|
_eventAggregator.GetEvent<StatusChangedEvent>()
|
||||||
|
.Publish(DetectorStatus.Acquiring);
|
||||||
|
|
||||||
|
// 等待后台线程处理(BackgroundThread 订阅)
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
|
||||||
|
Assert.True(_service.DetectorState.IsConnected);
|
||||||
|
Assert.True(_service.DetectorState.IsAcquiring);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StatusChangedEvent_Uninitialized_SyncsDetectorStateDisconnected()
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<StatusChangedEvent>()
|
||||||
|
.Publish(DetectorStatus.Uninitialized);
|
||||||
|
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
|
||||||
|
Assert.False(_service.DetectorState.IsConnected);
|
||||||
|
Assert.False(_service.DetectorState.IsAcquiring);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ImageCapturedEvent_UpdatesLatestDetectorFrame()
|
||||||
|
{
|
||||||
|
Assert.Null(_service.LatestDetectorFrame);
|
||||||
|
|
||||||
|
var args = new XP.Hardware.Detector.Abstractions.ImageCapturedEventArgs
|
||||||
|
{
|
||||||
|
ImageData = new ushort[4],
|
||||||
|
Width = 2,
|
||||||
|
Height = 2,
|
||||||
|
FrameNumber = 1,
|
||||||
|
CaptureTime = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
_eventAggregator.GetEvent<ImageCapturedEvent>().Publish(args);
|
||||||
|
|
||||||
|
// 等待后台线程处理
|
||||||
|
System.Threading.Thread.Sleep(100);
|
||||||
|
|
||||||
|
Assert.Same(args, _service.LatestDetectorFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Mock<ILinearAxis> CreateLinearAxis(AxisId axisId, double position)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Binary file not shown.
@@ -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 ────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -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,12 +109,11 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,48 @@
|
|||||||
|
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.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 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.
|
||||||
|
/// Detector state and latest frame are synchronized via Prism EventAggregator subscriptions.
|
||||||
/// </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 IDetectorService _detectorService;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
|
private readonly SubscriptionToken _axisStatusChangedToken;
|
||||||
|
private readonly SubscriptionToken _geometryUpdatedToken;
|
||||||
|
private readonly SubscriptionToken _detectorStatusChangedToken;
|
||||||
|
private readonly SubscriptionToken _detectorImageCapturedToken;
|
||||||
|
|
||||||
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;
|
||||||
@@ -32,26 +51,21 @@ namespace XplorePlane.Services.AppState
|
|||||||
private LinkedViewState _linkedViewState = LinkedViewState.Default;
|
private LinkedViewState _linkedViewState = LinkedViewState.Default;
|
||||||
private RecipeExecutionState _recipeExecutionState = RecipeExecutionState.Default;
|
private RecipeExecutionState _recipeExecutionState = RecipeExecutionState.Default;
|
||||||
|
|
||||||
|
// ── 探测器最新帧(volatile,供任意线程读取)──
|
||||||
|
private volatile ImageCapturedEventArgs _latestDetectorFrame;
|
||||||
|
|
||||||
// ── 类型化状态变更事件 ──
|
// ── 类型化状态变更事件 ──
|
||||||
public event EventHandler<StateChangedEventArgs<MotionState>> MotionStateChanged;
|
public event EventHandler<StateChangedEventArgs<MotionState>> MotionStateChanged;
|
||||||
|
|
||||||
public event EventHandler<StateChangedEventArgs<RaySourceState>> RaySourceStateChanged;
|
public event EventHandler<StateChangedEventArgs<RaySourceState>> RaySourceStateChanged;
|
||||||
|
|
||||||
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;
|
||||||
@@ -60,19 +74,56 @@ namespace XplorePlane.Services.AppState
|
|||||||
public LinkedViewState LinkedViewState => _linkedViewState;
|
public LinkedViewState LinkedViewState => _linkedViewState;
|
||||||
public RecipeExecutionState RecipeExecutionState => _recipeExecutionState;
|
public RecipeExecutionState RecipeExecutionState => _recipeExecutionState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器最新采集帧(线程安全,可从任意线程读取)。
|
||||||
|
/// 由 ImageCapturedEvent 驱动更新,无采集时为 null。
|
||||||
|
/// </summary>
|
||||||
|
public ImageCapturedEventArgs LatestDetectorFrame => _latestDetectorFrame;
|
||||||
|
|
||||||
public AppStateService(
|
public AppStateService(
|
||||||
IRaySourceService raySourceService,
|
IRaySourceService raySourceService,
|
||||||
|
IMotionSystem motionSystem,
|
||||||
|
IMotionControlService motionControlService,
|
||||||
|
IDetectorService detectorService,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
ILoggerService logger)
|
ILoggerService logger)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(raySourceService);
|
ArgumentNullException.ThrowIfNull(raySourceService);
|
||||||
|
ArgumentNullException.ThrowIfNull(motionSystem);
|
||||||
|
ArgumentNullException.ThrowIfNull(motionControlService);
|
||||||
|
ArgumentNullException.ThrowIfNull(detectorService);
|
||||||
|
ArgumentNullException.ThrowIfNull(eventAggregator);
|
||||||
ArgumentNullException.ThrowIfNull(logger);
|
ArgumentNullException.ThrowIfNull(logger);
|
||||||
|
|
||||||
_raySourceService = raySourceService;
|
_raySourceService = raySourceService;
|
||||||
|
_motionSystem = motionSystem;
|
||||||
|
_motionControlService = motionControlService;
|
||||||
|
_detectorService = detectorService;
|
||||||
|
_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);
|
||||||
|
|
||||||
|
// ── 探测器状态事件订阅(后台线程,避免阻塞采集)──
|
||||||
|
_detectorStatusChangedToken = _eventAggregator
|
||||||
|
.GetEvent<StatusChangedEvent>()
|
||||||
|
.Subscribe(OnDetectorStatusChanged, ThreadOption.BackgroundThread);
|
||||||
|
|
||||||
|
// ── 探测器图像事件订阅(后台线程,仅缓存最新帧)──
|
||||||
|
_detectorImageCapturedToken = _eventAggregator
|
||||||
|
.GetEvent<ImageCapturedEvent>()
|
||||||
|
.Subscribe(OnDetectorImageCaptured, ThreadOption.BackgroundThread);
|
||||||
|
|
||||||
SubscribeToExistingServices();
|
SubscribeToExistingServices();
|
||||||
_logger.Info("AppStateService 已初始化");
|
_logger.Info("AppStateService initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 状态更新方法 ──
|
// ── 状态更新方法 ──
|
||||||
@@ -80,17 +131,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 +164,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 +178,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 +192,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 +206,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 +235,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 +253,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 +276,48 @@ 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_detectorStatusChangedToken is not null)
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<StatusChangedEvent>().Unsubscribe(_detectorStatusChangedToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_detectorImageCapturedToken is not null)
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<ImageCapturedEvent>().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>(
|
||||||
|
T oldVal,
|
||||||
|
T newVal,
|
||||||
|
EventHandler<StateChangedEventArgs<T>> handler,
|
||||||
|
string propertyName)
|
||||||
{
|
{
|
||||||
_dispatcher.BeginInvoke(() =>
|
_dispatcher.BeginInvoke(() =>
|
||||||
{
|
{
|
||||||
@@ -205,34 +328,144 @@ 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器状态变更回调。
|
||||||
|
/// 将硬件层 DetectorStatus 映射为应用层 DetectorState 并同步到 AppState。
|
||||||
|
/// 运行在后台线程(ThreadOption.BackgroundThread),不阻塞采集。
|
||||||
|
/// </summary>
|
||||||
|
private void OnDetectorStatusChanged(DetectorStatus status)
|
||||||
|
{
|
||||||
|
if (_disposed) return;
|
||||||
|
|
||||||
|
// 从 IDetectorService 读取分辨率等补充信息
|
||||||
|
string resolution = string.Empty;
|
||||||
|
double frameRate = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var info = _detectorService.GetInfo();
|
||||||
|
if (info != null)
|
||||||
|
resolution = $"{info.MaxWidth}x{info.MaxHeight}";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 探测器未初始化时 GetInfo 会抛出,忽略即可
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isConnected = status != DetectorStatus.Uninitialized && status != DetectorStatus.Error;
|
||||||
|
bool isAcquiring = status == DetectorStatus.Acquiring;
|
||||||
|
|
||||||
|
var newState = new DetectorState(
|
||||||
|
IsConnected: isConnected,
|
||||||
|
IsAcquiring: isAcquiring,
|
||||||
|
FrameRate: frameRate,
|
||||||
|
Resolution: resolution);
|
||||||
|
|
||||||
|
UpdateDetectorState(newState);
|
||||||
|
|
||||||
|
_logger.Info(
|
||||||
|
"探测器状态已同步:{Status} → IsConnected={IsConnected} IsAcquiring={IsAcquiring} | " +
|
||||||
|
"Detector state synced: {Status} → IsConnected={IsConnected} IsAcquiring={IsAcquiring}",
|
||||||
|
status, isConnected, isAcquiring);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器图像采集回调。
|
||||||
|
/// 仅缓存最新帧引用(volatile 写),不做任何图像处理,保持采集链路零阻塞。
|
||||||
|
/// 上层通过 LatestDetectorFrame 属性按需读取。
|
||||||
|
/// </summary>
|
||||||
|
private void OnDetectorImageCaptured(ImageCapturedEventArgs args)
|
||||||
|
{
|
||||||
|
if (_disposed || args?.ImageData == null) return;
|
||||||
|
_latestDetectorFrame = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryRefreshMotionStateFromHardware(string reason)
|
||||||
|
{
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using XP.Hardware.Detector.Abstractions;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
|
|
||||||
namespace XplorePlane.Services.AppState
|
namespace XplorePlane.Services.AppState
|
||||||
@@ -21,6 +22,13 @@ namespace XplorePlane.Services.AppState
|
|||||||
LinkedViewState LinkedViewState { get; }
|
LinkedViewState LinkedViewState { get; }
|
||||||
RecipeExecutionState RecipeExecutionState { get; }
|
RecipeExecutionState RecipeExecutionState { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 探测器最新采集帧(线程安全,可从任意线程读取)。
|
||||||
|
/// 由 ImageCapturedEvent 驱动更新,探测器未采集时为 null。
|
||||||
|
/// CNC 执行、图像处理等上层服务通过此属性按需取图,无需自行订阅事件。
|
||||||
|
/// </summary>
|
||||||
|
ImageCapturedEventArgs LatestDetectorFrame { get; }
|
||||||
|
|
||||||
// ── 状态更新方法(线程安全,可从任意线程调用)──
|
// ── 状态更新方法(线程安全,可从任意线程调用)──
|
||||||
|
|
||||||
void UpdateMotionState(MotionState newState);
|
void UpdateMotionState(MotionState newState);
|
||||||
|
|||||||
@@ -103,6 +103,63 @@ namespace XplorePlane.Services.Cnc
|
|||||||
{
|
{
|
||||||
switch (node)
|
switch (node)
|
||||||
{
|
{
|
||||||
|
case ReferencePointNode rp:
|
||||||
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
|
"执行参考点节点 [{Index}] {Name} | " +
|
||||||
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
|
"StageRotation={StageRotation} FixtureRotation={FixtureRotation} " +
|
||||||
|
"RayOn={RayOn} Voltage={Voltage}kV Current={Current}uA",
|
||||||
|
rp.Index, rp.Name,
|
||||||
|
rp.StageX, rp.StageY, rp.SourceZ, rp.DetectorZ,
|
||||||
|
rp.DetectorSwing, rp.FDD, rp.FOD, rp.Magnification,
|
||||||
|
rp.StageRotation, rp.FixtureRotation,
|
||||||
|
rp.IsRayOn, rp.Voltage, rp.Current);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SavePositionNode sp:
|
||||||
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
|
"执行保存位置节点 [{Index}] {Name} | " +
|
||||||
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
|
"StageRotation={StageRotation} FixtureRotation={FixtureRotation}",
|
||||||
|
sp.Index, sp.Name,
|
||||||
|
sp.MotionState.StageX, sp.MotionState.StageY,
|
||||||
|
sp.MotionState.SourceZ, sp.MotionState.DetectorZ,
|
||||||
|
sp.MotionState.DetectorSwing, sp.MotionState.FDD,
|
||||||
|
sp.MotionState.FOD, sp.MotionState.Magnification,
|
||||||
|
sp.MotionState.StageRotation, sp.MotionState.FixtureRotation);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SaveNodeNode sn:
|
||||||
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
|
"执行保存节点 [{Index}] {Name} | " +
|
||||||
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
|
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W",
|
||||||
|
sn.Index, sn.Name,
|
||||||
|
sn.MotionState.StageX, sn.MotionState.StageY,
|
||||||
|
sn.MotionState.SourceZ, sn.MotionState.DetectorZ,
|
||||||
|
sn.MotionState.DetectorSwing, sn.MotionState.FDD,
|
||||||
|
sn.MotionState.FOD, sn.MotionState.Magnification,
|
||||||
|
sn.RaySourceState.IsOn, sn.RaySourceState.Voltage, sn.RaySourceState.Power);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SaveNodeWithImageNode sni:
|
||||||
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
|
"执行保存节点(图像) [{Index}] {Name} | " +
|
||||||
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
|
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W ImageFile={ImageFile}",
|
||||||
|
sni.Index, sni.Name,
|
||||||
|
sni.MotionState.StageX, sni.MotionState.StageY,
|
||||||
|
sni.MotionState.SourceZ, sni.MotionState.DetectorZ,
|
||||||
|
sni.MotionState.DetectorSwing, sni.MotionState.FDD,
|
||||||
|
sni.MotionState.FOD, sni.MotionState.Magnification,
|
||||||
|
sni.RaySourceState.IsOn, sni.RaySourceState.Voltage, sni.RaySourceState.Power,
|
||||||
|
sni.ImageFileName);
|
||||||
|
break;
|
||||||
|
|
||||||
case WaitDelayNode waitNode:
|
case WaitDelayNode waitNode:
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -389,22 +389,26 @@ 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>
|
||||||
private SaveNodeWithImageNode CreateSaveNodeWithImageNode(Guid id, int index)
|
private SaveNodeWithImageNode CreateSaveNodeWithImageNode(Guid id, int index)
|
||||||
{
|
{
|
||||||
return new SaveNodeWithImageNode(
|
return new SaveNodeWithImageNode(
|
||||||
id, index, $"保存节点(图像)_{index}",
|
id, index, $"保存节点_图像_{index}",
|
||||||
MotionState: _appStateService.MotionState,
|
MotionState: _appStateService.MotionState,
|
||||||
RaySourceState: _appStateService.RaySourceState,
|
RaySourceState: _appStateService.RaySourceState,
|
||||||
DetectorState: _appStateService.DetectorState,
|
DetectorState: _appStateService.DetectorState,
|
||||||
|
|||||||
@@ -417,7 +417,7 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine("Index,NodeType,Name,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;
|
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.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.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.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.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.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.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.SourceZ.ToString(inv)},{sp.MotionState.DetectorZ.ToString(inv)},{sp.MotionState.StageX.ToString(inv)},{sp.MotionState.StageY.ToString(inv)},{sp.MotionState.DetectorSwing.ToString(inv)},{sp.MotionState.StageRotation.ToString(inv)},{sp.MotionState.FixtureRotation.ToString(inv)},{sp.MotionState.FOD.ToString(inv)},{sp.MotionState.FDD.ToString(inv)},{sp.MotionState.Magnification.ToString(inv)},,,,,,,,,,,,,,",
|
||||||
InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}",
|
InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}",
|
||||||
InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,",
|
InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,",
|
||||||
PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
|
PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
|
||||||
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;
|
||||||
|
|||||||
@@ -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,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,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"
|
||||||
|
|||||||
@@ -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="载物台 X (μm)" />
|
||||||
<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="载物台 Y (μm)" />
|
||||||
<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="射线源 Z (μm)" />
|
||||||
<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="探测器 Z (μm)" />
|
||||||
<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="探测器摆动 (°)" />
|
||||||
<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="载物台旋转 (°)" />
|
||||||
<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="夹具旋转 (°)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FixtureRotation, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="FOD (μm)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FOD, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="0,0,6,0">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="FDD (μm)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FDD, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="放大倍率" />
|
||||||
|
<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"
|
||||||
|
|||||||
@@ -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,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,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,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>
|
||||||
Reference in New Issue
Block a user