diff --git a/XplorePlane.Tests/Services/AppStateServiceTests.cs b/XplorePlane.Tests/Services/AppStateServiceTests.cs
index 9e94c1d..769db21 100644
--- a/XplorePlane.Tests/Services/AppStateServiceTests.cs
+++ b/XplorePlane.Tests/Services/AppStateServiceTests.cs
@@ -1,7 +1,12 @@
using Moq;
+using Prism.Events;
using System;
using System.Windows;
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 XplorePlane.Models;
using XplorePlane.Services.AppState;
@@ -11,30 +16,62 @@ using Xunit.Abstractions;
namespace XplorePlane.Tests.Services
{
///
- /// AppStateService 单元测试。
- /// 验证默认状态值、Dispose 后行为、null 参数校验、CalibrationMatrix 缺失时的错误处理。
+ /// AppStateService unit tests.
+ /// Verifies default values, null guards, dispose behavior, and hardware-driven motion-state sync.
///
public class AppStateServiceTests : IDisposable
{
private readonly AppStateService _service;
private readonly Mock _mockRaySource;
+ private readonly Mock _mockMotionSystem;
+ private readonly Mock _mockMotionControlService;
+ private readonly Mock _mockStageX;
+ private readonly Mock _mockStageY;
+ private readonly Mock _mockSourceZ;
+ private readonly Mock _mockDetectorZ;
+ private readonly Mock _mockDetectorSwing;
private readonly Mock _mockLogger;
+ private readonly EventAggregator _eventAggregator;
private readonly ITestOutputHelper _output;
public AppStateServiceTests(ITestOutputHelper output)
{
_output = output;
- // Ensure WPF Application exists for Dispatcher
if (Application.Current == null)
{
new Application();
}
_mockRaySource = new Mock();
+ _mockMotionSystem = new Mock();
+ _mockMotionControlService = new Mock();
+ _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);
_mockLogger = new Mock();
+ _eventAggregator = new EventAggregator();
+
+ _mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.StageX)).Returns(_mockStageX.Object);
+ _mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.StageY)).Returns(_mockStageY.Object);
+ _mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.SourceZ)).Returns(_mockSourceZ.Object);
+ _mockMotionSystem.Setup(x => x.GetLinearAxis(AxisId.DetectorZ)).Returns(_mockDetectorZ.Object);
+ _mockMotionSystem.Setup(x => x.GetRotaryAxis(RotaryAxisId.DetectorSwing)).Returns(_mockDetectorSwing.Object);
+
+ _mockMotionControlService
+ .Setup(x => x.GetCurrentGeometry())
+ .Returns((0d, 0d, 1d));
+
_mockLogger.Setup(l => l.ForModule()).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()
@@ -42,13 +79,15 @@ namespace XplorePlane.Tests.Services
_service.Dispose();
}
- // ── 默认状态值验证 ──
-
[Fact]
- public void DefaultState_MotionState_IsDefault()
+ public void DefaultState_MotionState_IsHardwareSnapshot()
{
- _output.WriteLine($"MotionState == MotionState.Default: {ReferenceEquals(MotionState.Default, _service.MotionState)}");
- Assert.Same(MotionState.Default, _service.MotionState);
+ Assert.Equal(0, _service.MotionState.XM);
+ Assert.Equal(0, _service.MotionState.YM);
+ Assert.Equal(0, _service.MotionState.ZT);
+ Assert.Equal(0, _service.MotionState.ZD);
+ Assert.Equal(0, _service.MotionState.TiltD);
+ Assert.Equal(0, _service.MotionState.Dist);
}
[Fact]
@@ -72,8 +111,6 @@ namespace XplorePlane.Tests.Services
Assert.Null(_service.CalibrationMatrix);
}
- // ── null 参数抛出 ArgumentNullException ──
-
[Fact]
public void UpdateMotionState_NullArgument_ThrowsArgumentNullException()
{
@@ -102,36 +139,66 @@ namespace XplorePlane.Tests.Services
_output.WriteLine($"UpdateSystemState(null) threw: {ex.GetType().Name}, Param={ex.ParamName}");
}
- // ── Dispose 后 Update 被忽略 ──
-
[Fact]
public void Dispose_ThenUpdate_IsIgnored()
{
var originalState = _service.MotionState;
_service.Dispose();
- _output.WriteLine("Service disposed, attempting UpdateMotionState...");
- // Should not throw, and state should remain unchanged
var newState = new MotionState(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
_service.UpdateMotionState(newState);
- _output.WriteLine($"State unchanged after dispose: {ReferenceEquals(originalState, _service.MotionState)}");
Assert.Same(originalState, _service.MotionState);
}
- // ── CalibrationMatrix 为 null 时 RequestLinkedView 设置错误状态 ──
-
[Fact]
public void RequestLinkedView_NoCalibrationMatrix_SetsErrorState()
{
- // CalibrationMatrix is null by default
Assert.Null(_service.CalibrationMatrix);
_service.RequestLinkedView(100.0, 200.0);
- _output.WriteLine($"RequestLinkedView(100, 200) without CalibrationMatrix: HasError={_service.SystemState.HasError}, ErrorMessage='{_service.SystemState.ErrorMessage}'");
Assert.True(_service.SystemState.HasError);
Assert.NotEmpty(_service.SystemState.ErrorMessage);
}
+
+ [Fact]
+ public void GeometryUpdatedEvent_RefreshesMotionStateFromHardware()
+ {
+ _mockStageX.SetupGet(x => x.ActualPosition).Returns(12.5);
+ _mockStageY.SetupGet(x => x.ActualPosition).Returns(34.5);
+ _mockSourceZ.SetupGet(x => x.ActualPosition).Returns(56.5);
+ _mockDetectorZ.SetupGet(x => x.ActualPosition).Returns(78.5);
+ _mockDetectorSwing.SetupGet(x => x.ActualAngle).Returns(9.5);
+
+ _eventAggregator.GetEvent()
+ .Publish(new GeometryData(100, 222.2, 2.22));
+
+ Assert.Equal(12.5, _service.MotionState.XM);
+ Assert.Equal(34.5, _service.MotionState.YM);
+ Assert.Equal(56.5, _service.MotionState.ZT);
+ Assert.Equal(78.5, _service.MotionState.ZD);
+ Assert.Equal(9.5, _service.MotionState.TiltD);
+ Assert.Equal(222.2, _service.MotionState.Dist);
+ }
+
+ private static Mock CreateLinearAxis(AxisId axisId, double position)
+ {
+ var axis = new Mock();
+ axis.SetupGet(x => x.Id).Returns(axisId);
+ axis.SetupGet(x => x.ActualPosition).Returns(position);
+ axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
+ return axis;
+ }
+
+ private static Mock CreateRotaryAxis(RotaryAxisId axisId, double angle)
+ {
+ var axis = new Mock();
+ axis.SetupGet(x => x.Id).Returns(axisId);
+ axis.SetupGet(x => x.ActualAngle).Returns(angle);
+ axis.SetupGet(x => x.Status).Returns(AxisStatus.Idle);
+ axis.SetupGet(x => x.Enabled).Returns(true);
+ return axis;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/XplorePlane/Services/AppState/AppStateService.cs b/XplorePlane/Services/AppState/AppStateService.cs
index 9e6abcc..83579c4 100644
--- a/XplorePlane/Services/AppState/AppStateService.cs
+++ b/XplorePlane/Services/AppState/AppStateService.cs
@@ -1,29 +1,40 @@
+using Prism.Events;
using Prism.Mvvm;
using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using XP.Common.Logging.Interfaces;
+using XP.Hardware.MotionControl.Abstractions;
+using XP.Hardware.MotionControl.Abstractions.Enums;
+using XP.Hardware.MotionControl.Abstractions.Events;
+using XP.Hardware.MotionControl.Services;
using XP.Hardware.RaySource.Services;
using XplorePlane.Models;
namespace XplorePlane.Services.AppState
{
///
- /// 全局应用状态管理服务实现。
- /// 继承 BindableBase 以支持 WPF 数据绑定,使用 Interlocked.Exchange 保证线程安全写入,
- /// 通过 Dispatcher.BeginInvoke 将事件调度到 UI 线程。
+ /// Global application state service.
+ /// Motion state is synchronized from the motion hardware service layer and
+ /// mapped into the legacy business model for compatibility.
///
public class AppStateService : BindableBase, IAppStateService
{
private readonly Dispatcher _dispatcher;
private readonly IRaySourceService _raySourceService;
+ private readonly IMotionSystem _motionSystem;
+ private readonly IMotionControlService _motionControlService;
+ private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger;
+ private readonly SubscriptionToken _axisStatusChangedToken;
+ private readonly SubscriptionToken _geometryUpdatedToken;
+
private bool _disposed;
+ private GeometryData _latestGeometry;
// ── 状态字段(通过 Interlocked.Exchange 原子替换)──
private MotionState _motionState = MotionState.Default;
-
private RaySourceState _raySourceState = RaySourceState.Default;
private DetectorState _detectorState = DetectorState.Default;
private SystemState _systemState = SystemState.Default;
@@ -34,24 +45,16 @@ namespace XplorePlane.Services.AppState
// ── 类型化状态变更事件 ──
public event EventHandler> MotionStateChanged;
-
public event EventHandler> RaySourceStateChanged;
-
public event EventHandler> DetectorStateChanged;
-
public event EventHandler> SystemStateChanged;
-
public event EventHandler> CameraStateChanged;
-
public event EventHandler> LinkedViewStateChanged;
-
public event EventHandler> RecipeExecutionStateChanged;
-
public event EventHandler LinkedViewRequested;
// ── 状态属性(只读)──
public MotionState MotionState => _motionState;
-
public RaySourceState RaySourceState => _raySourceState;
public DetectorState DetectorState => _detectorState;
public SystemState SystemState => _systemState;
@@ -62,17 +65,34 @@ namespace XplorePlane.Services.AppState
public AppStateService(
IRaySourceService raySourceService,
+ IMotionSystem motionSystem,
+ IMotionControlService motionControlService,
+ IEventAggregator eventAggregator,
ILoggerService logger)
{
ArgumentNullException.ThrowIfNull(raySourceService);
+ ArgumentNullException.ThrowIfNull(motionSystem);
+ ArgumentNullException.ThrowIfNull(motionControlService);
+ ArgumentNullException.ThrowIfNull(eventAggregator);
ArgumentNullException.ThrowIfNull(logger);
_raySourceService = raySourceService;
+ _motionSystem = motionSystem;
+ _motionControlService = motionControlService;
+ _eventAggregator = eventAggregator;
_logger = logger.ForModule();
- _dispatcher = Application.Current.Dispatcher;
+ _dispatcher = Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher;
+
+ _geometryUpdatedToken = _eventAggregator
+ .GetEvent()
+ .Subscribe(OnGeometryUpdated);
+
+ _axisStatusChangedToken = _eventAggregator
+ .GetEvent()
+ .Subscribe(OnAxisStatusChanged);
SubscribeToExistingServices();
- _logger.Info("AppStateService 已初始化");
+ _logger.Info("AppStateService initialized");
}
// ── 状态更新方法 ──
@@ -80,17 +100,30 @@ namespace XplorePlane.Services.AppState
public void UpdateMotionState(MotionState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateMotionState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateMotionState");
+ return;
+ }
- var old = Interlocked.Exchange(ref _motionState, newState);
- if (ReferenceEquals(old, newState)) return;
- RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState));
+ // Keep the legacy API surface, but let the hardware service layer
+ // remain the source of truth whenever a fresh hardware snapshot is available.
+ if (TryRefreshMotionStateFromHardware("UpdateMotionState"))
+ {
+ return;
+ }
+
+ SetMotionState(newState);
}
public void UpdateRaySourceState(RaySourceState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateRaySourceState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateRaySourceState");
+ return;
+ }
var old = Interlocked.Exchange(ref _raySourceState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -100,7 +133,11 @@ namespace XplorePlane.Services.AppState
public void UpdateDetectorState(DetectorState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateDetectorState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateDetectorState");
+ return;
+ }
var old = Interlocked.Exchange(ref _detectorState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -110,7 +147,11 @@ namespace XplorePlane.Services.AppState
public void UpdateSystemState(SystemState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateSystemState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateSystemState");
+ return;
+ }
var old = Interlocked.Exchange(ref _systemState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -120,7 +161,11 @@ namespace XplorePlane.Services.AppState
public void UpdateCameraState(CameraState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateCameraState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateCameraState");
+ return;
+ }
var old = Interlocked.Exchange(ref _cameraState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -130,21 +175,26 @@ namespace XplorePlane.Services.AppState
public void UpdateCalibrationMatrix(CalibrationMatrix newMatrix)
{
ArgumentNullException.ThrowIfNull(newMatrix);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateCalibrationMatrix 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateCalibrationMatrix");
+ return;
+ }
var old = Interlocked.Exchange(ref _calibrationMatrix, newMatrix);
if (ReferenceEquals(old, newMatrix)) return;
- _dispatcher.BeginInvoke(() =>
- {
- RaisePropertyChanged(nameof(CalibrationMatrix));
- });
+ _dispatcher.BeginInvoke(() => RaisePropertyChanged(nameof(CalibrationMatrix)));
}
public void UpdateLinkedViewState(LinkedViewState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateLinkedViewState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateLinkedViewState");
+ return;
+ }
var old = Interlocked.Exchange(ref _linkedViewState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -154,7 +204,11 @@ namespace XplorePlane.Services.AppState
public void UpdateRecipeExecutionState(RecipeExecutionState newState)
{
ArgumentNullException.ThrowIfNull(newState);
- if (_disposed) { _logger.Warn("AppStateService 已释放,忽略 UpdateRecipeExecutionState 调用"); return; }
+ if (_disposed)
+ {
+ _logger.Warn("AppStateService is disposed, ignoring UpdateRecipeExecutionState");
+ return;
+ }
var old = Interlocked.Exchange(ref _recipeExecutionState, newState);
if (ReferenceEquals(old, newState)) return;
@@ -168,11 +222,11 @@ namespace XplorePlane.Services.AppState
var matrix = _calibrationMatrix;
if (matrix is null)
{
- _logger.Warn("CalibrationMatrix 未设置,无法执行画面联动 (pixelX={PixelX}, pixelY={PixelY})", pixelX, pixelY);
+ _logger.Warn("CalibrationMatrix is not configured, cannot execute linked view request (pixelX={PixelX}, pixelY={PixelY})", pixelX, pixelY);
UpdateSystemState(SystemState with
{
HasError = true,
- ErrorMessage = "CalibrationMatrix 未设置,无法执行画面联动"
+ ErrorMessage = "CalibrationMatrix is not configured, cannot execute linked view request"
});
return;
}
@@ -191,10 +245,38 @@ namespace XplorePlane.Services.AppState
});
}
- // ── 内部辅助方法 ──
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
- private void RaiseOnDispatcher(T oldVal, T newVal,
- EventHandler> handler, string propertyName)
+ if (_axisStatusChangedToken is not null)
+ {
+ _eventAggregator.GetEvent().Unsubscribe(_axisStatusChangedToken);
+ }
+
+ if (_geometryUpdatedToken is not null)
+ {
+ _eventAggregator.GetEvent().Unsubscribe(_geometryUpdatedToken);
+ }
+
+ MotionStateChanged = null;
+ RaySourceStateChanged = null;
+ DetectorStateChanged = null;
+ SystemStateChanged = null;
+ CameraStateChanged = null;
+ LinkedViewStateChanged = null;
+ RecipeExecutionStateChanged = null;
+ LinkedViewRequested = null;
+
+ _logger.Info("AppStateService disposed");
+ }
+
+ private void RaiseOnDispatcher(
+ T oldVal,
+ T newVal,
+ EventHandler> handler,
+ string propertyName)
{
_dispatcher.BeginInvoke(() =>
{
@@ -205,34 +287,85 @@ namespace XplorePlane.Services.AppState
}
catch (Exception ex)
{
- _logger.Error(ex, "状态变更事件处理器抛出异常 (property={PropertyName})", propertyName);
+ _logger.Error(ex, "State changed handler failed (property={PropertyName})", propertyName);
}
});
}
private void SubscribeToExistingServices()
{
- _logger.Info("AppStateService 已准备好接收外部服务状态更新");
+ if (TryRefreshMotionStateFromHardware("initialization"))
+ {
+ _logger.Info("AppStateService subscribed to motion hardware state");
+ return;
+ }
+
+ _logger.Warn("AppStateService could not initialize motion state from hardware");
}
- // ── Dispose ──
-
- public void Dispose()
+ private void OnAxisStatusChanged(AxisStatusChangedData _)
{
if (_disposed) return;
- _disposed = true;
+ TryRefreshMotionStateFromHardware("axis-status-changed");
+ }
- // 清除所有事件订阅
- MotionStateChanged = null;
- RaySourceStateChanged = null;
- DetectorStateChanged = null;
- SystemStateChanged = null;
- CameraStateChanged = null;
- LinkedViewStateChanged = null;
- RecipeExecutionStateChanged = null;
- LinkedViewRequested = null;
+ private void OnGeometryUpdated(GeometryData geometry)
+ {
+ if (_disposed) return;
- _logger.Info("AppStateService 已释放");
+ _latestGeometry = geometry;
+ TryRefreshMotionStateFromHardware("geometry-updated");
+ }
+
+ 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);
+
+ return new MotionState(
+ XM: stageX.ActualPosition,
+ YM: stageY.ActualPosition,
+ ZT: sourceZ.ActualPosition,
+ ZD: detectorZ.ActualPosition,
+ TiltD: detectorSwing.ActualAngle,
+ Dist: geometry?.FDD ?? 0,
+ XMSpeed: 0,
+ YMSpeed: 0,
+ ZTSpeed: 0,
+ ZDSpeed: 0,
+ TiltDSpeed: 0,
+ DistSpeed: 0);
+ }
+
+ private void SetMotionState(MotionState newState)
+ {
+ var old = Interlocked.Exchange(ref _motionState, newState);
+ if (ReferenceEquals(old, newState)) return;
+ RaiseOnDispatcher(old, newState, MotionStateChanged, nameof(MotionState));
}
}
-}
\ No newline at end of file
+}
diff --git a/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs b/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs
index f16a061..be00d31 100644
--- a/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs
+++ b/XplorePlane/ViewModels/Cnc/CncInspectionModulePipelineViewModel.cs
@@ -1,4 +1,4 @@
-using Microsoft.Win32;
+using Microsoft.Win32;
using Prism.Commands;
using Prism.Mvvm;
using System;
diff --git a/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs b/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs
index c79ffe1..2494bfd 100644
--- a/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs
+++ b/XplorePlane/ViewModels/ImageProcessing/PipelineEditorViewModel.cs
@@ -1,4 +1,4 @@
-using Microsoft.Win32;
+using Microsoft.Win32;
using Prism.Events;
using Prism.Commands;
using Prism.Mvvm;
diff --git a/XplorePlane/ViewModels/Main/MainViewModel.cs b/XplorePlane/ViewModels/Main/MainViewModel.cs
index fbb4497..8b12f86 100644
--- a/XplorePlane/ViewModels/Main/MainViewModel.cs
+++ b/XplorePlane/ViewModels/Main/MainViewModel.cs
@@ -1,4 +1,4 @@
-using Microsoft.Win32;
+using Microsoft.Win32;
using Prism.Commands;
using Prism.Events;
using Prism.Ioc;
diff --git a/XplorePlane/Views/Cnc/CncEditorWindow.xaml b/XplorePlane/Views/Cnc/CncEditorWindow.xaml
index a709fcf..18409ad 100644
--- a/XplorePlane/Views/Cnc/CncEditorWindow.xaml
+++ b/XplorePlane/Views/Cnc/CncEditorWindow.xaml
@@ -1,4 +1,4 @@
-
+
WinExe
net8.0-windows
diff --git a/XplorePlane/XplorePlane.csproj.user b/XplorePlane/XplorePlane.csproj.user
index 88a5509..824d5a9 100644
--- a/XplorePlane/XplorePlane.csproj.user
+++ b/XplorePlane/XplorePlane.csproj.user
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file