Plan 用于 CNC 默认保存和加载,Tools 用于流程图配方 xpm,Data 用于执行结果和中间图像,Report 为报告预留目录

This commit is contained in:
zhengxuan.zhang
2026-05-06 14:56:07 +08:00
parent 9a8831c945
commit 3bee2898c5
15 changed files with 406 additions and 18 deletions
@@ -6,6 +6,7 @@ using System.Windows.Media.Imaging;
using XP.Common.Logging.Interfaces; using XP.Common.Logging.Interfaces;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services; using XplorePlane.Services;
using XplorePlane.Services.Storage;
using XplorePlane.Tests.Helpers; using XplorePlane.Tests.Helpers;
using XplorePlane.ViewModels; using XplorePlane.ViewModels;
using Xunit; using Xunit;
@@ -22,6 +23,7 @@ namespace XplorePlane.Tests.Pipeline
private readonly Mock<IPipelineExecutionService> _mockExecSvc; private readonly Mock<IPipelineExecutionService> _mockExecSvc;
private readonly Mock<IPipelinePersistenceService> _mockPersistSvc; private readonly Mock<IPipelinePersistenceService> _mockPersistSvc;
private readonly Mock<ILoggerService> _mockLogger; private readonly Mock<ILoggerService> _mockLogger;
private readonly Mock<IXpDataPathService> _mockDataPathService;
public PipelineEditorViewModelTests() public PipelineEditorViewModelTests()
{ {
@@ -29,11 +31,19 @@ namespace XplorePlane.Tests.Pipeline
_mockExecSvc = new Mock<IPipelineExecutionService>(); _mockExecSvc = new Mock<IPipelineExecutionService>();
_mockPersistSvc = new Mock<IPipelinePersistenceService>(); _mockPersistSvc = new Mock<IPipelinePersistenceService>();
_mockLogger = new Mock<ILoggerService>(); _mockLogger = new Mock<ILoggerService>();
_mockDataPathService = new Mock<IXpDataPathService>();
_mockLogger.Setup(l => l.ForModule<PipelineEditorViewModel>()).Returns(_mockLogger.Object); _mockLogger.Setup(l => l.ForModule<PipelineEditorViewModel>()).Returns(_mockLogger.Object);
_mockDataPathService.SetupGet(s => s.ToolsPath).Returns(Path.GetTempPath());
} }
private PipelineEditorViewModel CreateVm() => private PipelineEditorViewModel CreateVm() =>
new PipelineEditorViewModel(_mockImageSvc.Object, _mockExecSvc.Object, _mockPersistSvc.Object, new EventAggregator(), _mockLogger.Object); new PipelineEditorViewModel(
_mockImageSvc.Object,
_mockExecSvc.Object,
_mockPersistSvc.Object,
new EventAggregator(),
_mockLogger.Object,
_mockDataPathService.Object);
// ── 6.1 AddOperatorCommand ──────────────────────────────────── // ── 6.1 AddOperatorCommand ────────────────────────────────────
@@ -1,8 +1,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Moq;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services; using XplorePlane.Services;
using XplorePlane.Services.Storage;
using XplorePlane.Tests.Helpers; using XplorePlane.Tests.Helpers;
using Xunit; using Xunit;
@@ -199,5 +201,21 @@ namespace XplorePlane.Tests.Pipeline
Assert.Equal(2, result.Count); Assert.Equal(2, result.Count);
} }
[Fact]
public async Task LoadAllAsync_UsesToolsPath_WhenConstructedWithDataPathService()
{
var mockImageSvc = TestHelpers.CreateMockImageService(new[] { "Blur" });
var mockDataPathSvc = new Mock<IXpDataPathService>();
mockDataPathSvc.SetupGet(s => s.ToolsPath).Returns(_tempDir);
var service = new PipelinePersistenceService(mockImageSvc.Object, mockDataPathSvc.Object);
await service.SaveAsync(BuildModel("P3", "Blur"), Path.Combine(_tempDir, "p3.xpm"));
var result = await service.LoadAllAsync(null);
Assert.Single(result);
Assert.Equal("P3", result[0].Name);
}
} }
} }
@@ -12,6 +12,7 @@ using System.Threading.Tasks;
using XP.Common.Logging.Interfaces; using XP.Common.Logging.Interfaces;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services; using XplorePlane.Services;
using XplorePlane.Services.Storage;
using XplorePlane.Tests.Helpers; using XplorePlane.Tests.Helpers;
using XplorePlane.ViewModels; using XplorePlane.ViewModels;
@@ -31,8 +32,16 @@ namespace XplorePlane.Tests.Pipeline
var mockExecSvc = new Mock<IPipelineExecutionService>(); var mockExecSvc = new Mock<IPipelineExecutionService>();
var mockPersistSvc = new Mock<IPipelinePersistenceService>(); var mockPersistSvc = new Mock<IPipelinePersistenceService>();
var mockLogger = new Mock<ILoggerService>(); var mockLogger = new Mock<ILoggerService>();
var mockDataPathService = new Mock<IXpDataPathService>();
mockLogger.Setup(l => l.ForModule<PipelineEditorViewModel>()).Returns(mockLogger.Object); mockLogger.Setup(l => l.ForModule<PipelineEditorViewModel>()).Returns(mockLogger.Object);
return new PipelineEditorViewModel(mockImageSvc.Object, mockExecSvc.Object, mockPersistSvc.Object, new EventAggregator(), mockLogger.Object); mockDataPathService.SetupGet(s => s.ToolsPath).Returns(Path.GetTempPath());
return new PipelineEditorViewModel(
mockImageSvc.Object,
mockExecSvc.Object,
mockPersistSvc.Object,
new EventAggregator(),
mockLogger.Object,
mockDataPathService.Object);
} }
/// <summary> /// <summary>
@@ -11,6 +11,7 @@ using XP.Common.Database.Interfaces;
using XP.Common.Logging.Interfaces; using XP.Common.Logging.Interfaces;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services.InspectionResults; using XplorePlane.Services.InspectionResults;
using XplorePlane.Services.Storage;
using Xunit; using Xunit;
namespace XplorePlane.Tests.Services namespace XplorePlane.Tests.Services
@@ -294,6 +295,33 @@ namespace XplorePlane.Tests.Services
Assert.Equal(originalHash, snapshot.PipelineHash); Assert.Equal(originalHash, snapshot.PipelineHash);
} }
[Fact]
public async Task Constructor_WithDataPathService_WritesIntoDataInspectionResultsDirectory()
{
var mockDataPathService = new Mock<IXpDataPathService>();
var dataRoot = Path.Combine(_tempRoot, "xpdata", "Data");
mockDataPathService.SetupGet(s => s.DataPath).Returns(dataRoot);
var store = new InspectionResultStore(_dbContext, _mockLogger.Object, mockDataPathService.Object);
var run = new InspectionRunRecord
{
ProgramName = "Program-By-Service",
WorkpieceId = "Part-03",
SerialNumber = "SN-DATA"
};
await store.BeginRunAsync(run);
await store.CompleteRunAsync(run.RunId);
var manifestPath = Path.Combine(
dataRoot,
"InspectionResults",
run.ResultRootPath.Replace('/', Path.DirectorySeparatorChar),
"manifest.json");
Assert.True(File.Exists(manifestPath));
}
public void Dispose() public void Dispose()
{ {
_dbContext.Dispose(); _dbContext.Dispose();
@@ -16,6 +16,7 @@ using XP.Common.Logging.Interfaces;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services.AppState; using XplorePlane.Services.AppState;
using XplorePlane.Services.Cnc; using XplorePlane.Services.Cnc;
using XplorePlane.Services.Storage;
using XplorePlane.ViewModels.Cnc; using XplorePlane.ViewModels.Cnc;
using Xunit; using Xunit;
@@ -32,7 +33,9 @@ namespace XplorePlane.Tests.ViewModels
var mockCncProgramSvc = new Mock<ICncProgramService>(); var mockCncProgramSvc = new Mock<ICncProgramService>();
var mockAppState = new Mock<IAppStateService>(); var mockAppState = new Mock<IAppStateService>();
var mockLogger = new Mock<ILoggerService>(); var mockLogger = new Mock<ILoggerService>();
var mockDataPathService = new Mock<IXpDataPathService>();
mockLogger.Setup(l => l.ForModule<CncEditorViewModel>()).Returns(mockLogger.Object); mockLogger.Setup(l => l.ForModule<CncEditorViewModel>()).Returns(mockLogger.Object);
mockDataPathService.SetupGet(s => s.PlanPath).Returns(System.IO.Path.GetTempPath());
mockExecSvc ??= new Mock<ICncExecutionService>(); mockExecSvc ??= new Mock<ICncExecutionService>();
@@ -52,7 +55,8 @@ namespace XplorePlane.Tests.ViewModels
mockAppState.Object, mockAppState.Object,
new EventAggregator(), new EventAggregator(),
mockLogger.Object, mockLogger.Object,
mockExecSvc.Object); mockExecSvc.Object,
mockDataPathService.Object);
if (initialProgram != null) if (initialProgram != null)
{ {
+1
View File
@@ -7,6 +7,7 @@
<appSettings> <appSettings>
<!-- 语言配置 可选值: ZhCN, ZhTW, EnUS| Language Configuration --> <!-- 语言配置 可选值: ZhCN, ZhTW, EnUS| Language Configuration -->
<add key="Language" value="ZhCN" /> <add key="Language" value="ZhCN" />
<add key="XpData:RootPath" value="D:\XPData" />
<add key="UserManual" value="D:\HMQProject\XplorePlane_CT\Code\XplorePlane\XP.App\bin\Debug\net8.0-windows7.0\UserManual.pdf" /> <add key="UserManual" value="D:\HMQProject\XplorePlane_CT\Code\XplorePlane\XP.App\bin\Debug\net8.0-windows7.0\UserManual.pdf" />
<!-- Serilog日志配置 --> <!-- Serilog日志配置 -->
+2
View File
@@ -40,6 +40,7 @@ using XplorePlane.Services.MainViewport;
using XplorePlane.Services.Matrix; using XplorePlane.Services.Matrix;
using XplorePlane.Services.Measurement; using XplorePlane.Services.Measurement;
using XplorePlane.Services.Recipe; using XplorePlane.Services.Recipe;
using XplorePlane.Services.Storage;
using XplorePlane.ViewModels; using XplorePlane.ViewModels;
using XplorePlane.ViewModels.Cnc; using XplorePlane.ViewModels.Cnc;
using XplorePlane.Views; using XplorePlane.Views;
@@ -377,6 +378,7 @@ namespace XplorePlane
// 注册全局状态服务(单例) // 注册全局状态服务(单例)
containerRegistry.RegisterSingleton<IAppStateService, AppStateService>(); containerRegistry.RegisterSingleton<IAppStateService, AppStateService>();
containerRegistry.RegisterSingleton<IXpDataPathService, XpDataPathService>();
// 注册检测配方服务(单例) // 注册检测配方服务(单例)
containerRegistry.RegisterSingleton<IRecipeService, RecipeService>(); containerRegistry.RegisterSingleton<IRecipeService, RecipeService>();
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using XP.Common.Database.Interfaces; using XP.Common.Database.Interfaces;
using XP.Common.Logging.Interfaces; using XP.Common.Logging.Interfaces;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services.Storage;
namespace XplorePlane.Services.InspectionResults namespace XplorePlane.Services.InspectionResults
{ {
@@ -160,6 +161,14 @@ WHERE run_id = @run_id";
"InspectionResults"); "InspectionResults");
} }
public InspectionResultStore(IDbContext db, ILoggerService logger, IXpDataPathService dataPathService)
: this(
db,
logger,
Path.Combine(dataPathService?.DataPath ?? throw new ArgumentNullException(nameof(dataPathService)), "InspectionResults"))
{
}
public async Task BeginRunAsync(InspectionRunRecord runRecord, InspectionAssetWriteRequest runSourceAsset = null) public async Task BeginRunAsync(InspectionRunRecord runRecord, InspectionAssetWriteRequest runSourceAsset = null)
{ {
ArgumentNullException.ThrowIfNull(runRecord); ArgumentNullException.ThrowIfNull(runRecord);
@@ -6,6 +6,7 @@ using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading.Tasks; using System.Threading.Tasks;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services.Storage;
namespace XplorePlane.Services namespace XplorePlane.Services
{ {
@@ -29,6 +30,11 @@ namespace XplorePlane.Services
"XplorePlane", "Pipelines"); "XplorePlane", "Pipelines");
} }
public PipelinePersistenceService(IImageProcessingService imageProcessingService, IXpDataPathService dataPathService)
: this(imageProcessingService, dataPathService?.ToolsPath)
{
}
public async Task SaveAsync(PipelineModel pipeline, string filePath) public async Task SaveAsync(PipelineModel pipeline, string filePath)
{ {
if (pipeline == null) throw new ArgumentNullException(nameof(pipeline)); if (pipeline == null) throw new ArgumentNullException(nameof(pipeline));
@@ -0,0 +1,23 @@
using System;
namespace XplorePlane.Services.Storage
{
public interface IXpDataPathService
{
string DefaultRootPath { get; }
string RootPath { get; }
string PlanPath { get; }
string ToolsPath { get; }
string DataPath { get; }
string ReportPath { get; }
string LegacyInspectionDataPath { get; }
void SaveRootPath(string rootPath);
}
}
@@ -0,0 +1,145 @@
using System;
using System.Configuration;
using System.IO;
using XP.Common.Logging.Interfaces;
namespace XplorePlane.Services.Storage
{
public class XpDataPathService : IXpDataPathService
{
internal const string RootPathSettingKey = "XpData:RootPath";
private const string DefaultRoot = @"D:\XPData";
private readonly ILoggerService _logger;
private readonly object _syncRoot = new();
private string _rootPath;
public XpDataPathService(ILoggerService logger)
{
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<XpDataPathService>();
_rootPath = LoadConfiguredRootPath();
EnsureManagedDirectories(_rootPath);
}
public string DefaultRootPath => DefaultRoot;
public string RootPath
{
get
{
lock (_syncRoot)
{
return _rootPath;
}
}
}
public string PlanPath => EnsureSubdirectory("Plan");
public string ToolsPath => EnsureSubdirectory("Tools");
public string DataPath => EnsureSubdirectory("Data");
public string ReportPath => EnsureSubdirectory("Report");
public string LegacyInspectionDataPath => Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"XplorePlane",
"InspectionResults");
public void SaveRootPath(string rootPath)
{
var normalizedRootPath = NormalizeRootPath(rootPath);
EnsureManagedDirectories(normalizedRootPath);
try
{
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var appSettings = config.AppSettings.Settings;
if (appSettings[RootPathSettingKey] == null)
{
appSettings.Add(RootPathSettingKey, normalizedRootPath);
}
else
{
appSettings[RootPathSettingKey].Value = normalizedRootPath;
}
config.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");
}
catch (ConfigurationErrorsException ex)
{
_logger.Error(ex, "保存 XP 数据根目录失败:配置文件写入异常");
throw new InvalidOperationException($"保存数据根目录失败:{ex.Message}", ex);
}
catch (UnauthorizedAccessException ex)
{
_logger.Error(ex, "保存 XP 数据根目录失败:无权写入配置文件");
throw new InvalidOperationException("保存数据根目录失败:没有配置文件写入权限。", ex);
}
lock (_syncRoot)
{
_rootPath = normalizedRootPath;
}
}
private string EnsureSubdirectory(string directoryName)
{
string rootPath;
lock (_syncRoot)
{
rootPath = _rootPath;
}
EnsureManagedDirectories(rootPath);
return Path.Combine(rootPath, directoryName);
}
private string LoadConfiguredRootPath()
{
try
{
return NormalizeRootPath(ConfigurationManager.AppSettings[RootPathSettingKey]);
}
catch (Exception ex)
{
_logger.Warn("读取 XP 数据根目录失败,回退默认目录:{Message}", ex.Message);
return NormalizeRootPath(null);
}
}
private static string NormalizeRootPath(string rootPath)
{
var candidate = string.IsNullOrWhiteSpace(rootPath) ? DefaultRoot : rootPath.Trim();
try
{
var normalized = Path.GetFullPath(candidate)
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
if (!Path.IsPathRooted(normalized))
{
return DefaultRoot;
}
return normalized;
}
catch
{
return DefaultRoot;
}
}
private static void EnsureManagedDirectories(string rootPath)
{
Directory.CreateDirectory(rootPath);
Directory.CreateDirectory(Path.Combine(rootPath, "Plan"));
Directory.CreateDirectory(Path.Combine(rootPath, "Tools"));
Directory.CreateDirectory(Path.Combine(rootPath, "Data"));
Directory.CreateDirectory(Path.Combine(rootPath, "Report"));
}
}
}
@@ -16,6 +16,7 @@ using XplorePlane.Events;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services.AppState; using XplorePlane.Services.AppState;
using XplorePlane.Services.Cnc; using XplorePlane.Services.Cnc;
using XplorePlane.Services.Storage;
namespace XplorePlane.ViewModels.Cnc namespace XplorePlane.ViewModels.Cnc
{ {
@@ -28,6 +29,7 @@ namespace XplorePlane.ViewModels.Cnc
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger; private readonly ILoggerService _logger;
private readonly ICncExecutionService _cncExecutionService; private readonly ICncExecutionService _cncExecutionService;
private readonly IXpDataPathService _dataPathService;
private CncProgram _currentProgram; private CncProgram _currentProgram;
private ObservableCollection<CncNodeViewModel> _nodes; private ObservableCollection<CncNodeViewModel> _nodes;
@@ -51,13 +53,15 @@ namespace XplorePlane.ViewModels.Cnc
IAppStateService appStateService, IAppStateService appStateService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
ILoggerService logger, ILoggerService logger,
ICncExecutionService cncExecutionService) ICncExecutionService cncExecutionService,
IXpDataPathService dataPathService)
{ {
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService)); _cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
ArgumentNullException.ThrowIfNull(appStateService); ArgumentNullException.ThrowIfNull(appStateService);
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<CncEditorViewModel>(); _logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<CncEditorViewModel>();
_cncExecutionService = cncExecutionService ?? throw new ArgumentNullException(nameof(cncExecutionService)); _cncExecutionService = cncExecutionService ?? throw new ArgumentNullException(nameof(cncExecutionService));
_dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService));
_nodes = new ObservableCollection<CncNodeViewModel>(); _nodes = new ObservableCollection<CncNodeViewModel>();
_treeNodes = new ObservableCollection<CncNodeViewModel>(); _treeNodes = new ObservableCollection<CncNodeViewModel>();
@@ -331,7 +335,8 @@ namespace XplorePlane.ViewModels.Cnc
Title = "保存 CNC 程序", Title = "保存 CNC 程序",
Filter = "CNC 程序文件 (*.xp)|*.xp|所有文件 (*.*)|*.*", Filter = "CNC 程序文件 (*.xp)|*.xp|所有文件 (*.*)|*.*",
DefaultExt = ".xp", DefaultExt = ".xp",
FileName = _currentProgram.Name FileName = _currentProgram.Name,
InitialDirectory = GetPlanDirectory()
}; };
if (dlg.ShowDialog() != true) if (dlg.ShowDialog() != true)
@@ -355,7 +360,8 @@ namespace XplorePlane.ViewModels.Cnc
{ {
Title = "加载 CNC 程序", Title = "加载 CNC 程序",
Filter = "CNC 程序文件 (*.xp)|*.xp|所有文件 (*.*)|*.*", Filter = "CNC 程序文件 (*.xp)|*.xp|所有文件 (*.*)|*.*",
DefaultExt = ".xp" DefaultExt = ".xp",
InitialDirectory = GetPlanDirectory()
}; };
if (dlg.ShowDialog() != true) if (dlg.ShowDialog() != true)
@@ -438,6 +444,13 @@ namespace XplorePlane.ViewModels.Cnc
} }
} }
private string GetPlanDirectory()
{
var directory = _dataPathService.PlanPath;
Directory.CreateDirectory(directory);
return directory;
}
private static string Esc(string value) private static string Esc(string value)
{ {
if (string.IsNullOrEmpty(value)) return string.Empty; if (string.IsNullOrEmpty(value)) return string.Empty;
@@ -14,6 +14,7 @@ using XP.Common.Logging.Interfaces;
using XplorePlane.Events; using XplorePlane.Events;
using XplorePlane.Models; using XplorePlane.Models;
using XplorePlane.Services; using XplorePlane.Services;
using XplorePlane.Services.Storage;
namespace XplorePlane.ViewModels namespace XplorePlane.ViewModels
{ {
@@ -28,6 +29,7 @@ namespace XplorePlane.ViewModels
private readonly IPipelinePersistenceService _persistenceService; private readonly IPipelinePersistenceService _persistenceService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly ILoggerService _logger; private readonly ILoggerService _logger;
private readonly IXpDataPathService _dataPathService;
private PipelineNodeViewModel _selectedNode; private PipelineNodeViewModel _selectedNode;
private BitmapSource _sourceImage; private BitmapSource _sourceImage;
@@ -49,13 +51,15 @@ namespace XplorePlane.ViewModels
IPipelineExecutionService executionService, IPipelineExecutionService executionService,
IPipelinePersistenceService persistenceService, IPipelinePersistenceService persistenceService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
ILoggerService logger) ILoggerService logger,
IXpDataPathService dataPathService)
{ {
_imageProcessingService = imageProcessingService ?? throw new ArgumentNullException(nameof(imageProcessingService)); _imageProcessingService = imageProcessingService ?? throw new ArgumentNullException(nameof(imageProcessingService));
_executionService = executionService ?? throw new ArgumentNullException(nameof(executionService)); _executionService = executionService ?? throw new ArgumentNullException(nameof(executionService));
_persistenceService = persistenceService ?? throw new ArgumentNullException(nameof(persistenceService)); _persistenceService = persistenceService ?? throw new ArgumentNullException(nameof(persistenceService));
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_logger = logger?.ForModule<PipelineEditorViewModel>() ?? throw new ArgumentNullException(nameof(logger)); _logger = logger?.ForModule<PipelineEditorViewModel>() ?? throw new ArgumentNullException(nameof(logger));
_dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService));
PipelineNodes = new ObservableCollection<PipelineNodeViewModel>(); PipelineNodes = new ObservableCollection<PipelineNodeViewModel>();
AvailableDevices = new ObservableCollection<string>(); AvailableDevices = new ObservableCollection<string>();
@@ -808,11 +812,9 @@ namespace XplorePlane.ViewModels
return $"已执行到“{ExecutionEndNode.DisplayName}” ({executionCount} 个有效节点)"; return $"已执行到“{ExecutionEndNode.DisplayName}” ({executionCount} 个有效节点)";
} }
private static string GetPipelineDirectory() private string GetPipelineDirectory()
{ {
var dir = Path.Combine( var dir = _dataPathService.ToolsPath;
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"XplorePlane", "Pipelines");
Directory.CreateDirectory(dir); Directory.CreateDirectory(dir);
return dir; return dir;
} }
+78 -1
View File
@@ -11,6 +11,7 @@ using System.Windows;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using XplorePlane.Events; using XplorePlane.Events;
using XplorePlane.Services.MainViewport; using XplorePlane.Services.MainViewport;
using XplorePlane.Services.Storage;
using XplorePlane.ViewModels.Cnc; using XplorePlane.ViewModels.Cnc;
using XplorePlane.Views; using XplorePlane.Views;
using XplorePlane.Views.Cnc; using XplorePlane.Views.Cnc;
@@ -29,6 +30,7 @@ namespace XplorePlane.ViewModels
private readonly IContainerProvider _containerProvider; private readonly IContainerProvider _containerProvider;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IMainViewportService _mainViewportService; private readonly IMainViewportService _mainViewportService;
private readonly IXpDataPathService _xpDataPathService;
private readonly CncEditorViewModel _cncEditorViewModel; private readonly CncEditorViewModel _cncEditorViewModel;
private readonly CncPageView _cncPageView; private readonly CncPageView _cncPageView;
@@ -66,6 +68,9 @@ namespace XplorePlane.ViewModels
public DelegateCommand OpenUserManualCommand { get; } public DelegateCommand OpenUserManualCommand { get; }
public DelegateCommand OpenCameraSettingsCommand { get; } public DelegateCommand OpenCameraSettingsCommand { get; }
public DelegateCommand OpenSettingsCommand { get; } public DelegateCommand OpenSettingsCommand { get; }
public DelegateCommand BrowseDataRootPathCommand { get; }
public DelegateCommand ResetDataRootPathCommand { get; }
public DelegateCommand SaveDataRootPathCommand { get; }
public DelegateCommand NewCncProgramCommand { get; } public DelegateCommand NewCncProgramCommand { get; }
public DelegateCommand SaveCncProgramCommand { get; } public DelegateCommand SaveCncProgramCommand { get; }
public DelegateCommand LoadCncProgramCommand { get; } public DelegateCommand LoadCncProgramCommand { get; }
@@ -119,6 +124,20 @@ namespace XplorePlane.ViewModels
public bool IsUsingLiveDetectorSource => _mainViewportService.CurrentSourceMode == MainViewportSourceMode.LiveDetector; public bool IsUsingLiveDetectorSource => _mainViewportService.CurrentSourceMode == MainViewportSourceMode.LiveDetector;
public string DataRootPath
{
get => _dataRootPath;
set => SetProperty(ref _dataRootPath, value);
}
public string PlanRootPath => _xpDataPathService.PlanPath;
public string ToolsRootPath => _xpDataPathService.ToolsPath;
public string ResultsRootPath => _xpDataPathService.DataPath;
public string ReportRootPath => _xpDataPathService.ReportPath;
/// <summary>右侧图像区域内容 | Right-side image panel content</summary> /// <summary>右侧图像区域内容 | Right-side image panel content</summary>
public object ImagePanelContent public object ImagePanelContent
{ {
@@ -155,16 +174,20 @@ namespace XplorePlane.ViewModels
private bool _isCncEditorMode; private bool _isCncEditorMode;
private string _licenseInfo = "当前时间"; private string _licenseInfo = "当前时间";
private string _dataRootPath = string.Empty;
public MainViewModel( public MainViewModel(
ILoggerService logger, ILoggerService logger,
IContainerProvider containerProvider, IContainerProvider containerProvider,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
IMainViewportService mainViewportService) IMainViewportService mainViewportService,
IXpDataPathService xpDataPathService)
{ {
_logger = logger?.ForModule<MainViewModel>() ?? throw new ArgumentNullException(nameof(logger)); _logger = logger?.ForModule<MainViewModel>() ?? throw new ArgumentNullException(nameof(logger));
_containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider)); _containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider));
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); _eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_mainViewportService = mainViewportService ?? throw new ArgumentNullException(nameof(mainViewportService)); _mainViewportService = mainViewportService ?? throw new ArgumentNullException(nameof(mainViewportService));
_xpDataPathService = xpDataPathService ?? throw new ArgumentNullException(nameof(xpDataPathService));
_cncEditorViewModel = _containerProvider.Resolve<CncEditorViewModel>(); _cncEditorViewModel = _containerProvider.Resolve<CncEditorViewModel>();
_cncPageView = new CncPageView { DataContext = _cncEditorViewModel }; _cncPageView = new CncPageView { DataContext = _cncEditorViewModel };
@@ -215,6 +238,9 @@ namespace XplorePlane.ViewModels
OpenUserManualCommand = new DelegateCommand(ExecuteOpenUserManual); OpenUserManualCommand = new DelegateCommand(ExecuteOpenUserManual);
OpenCameraSettingsCommand = new DelegateCommand(ExecuteOpenCameraSettings); OpenCameraSettingsCommand = new DelegateCommand(ExecuteOpenCameraSettings);
OpenSettingsCommand = new DelegateCommand(ExecuteOpenSettings); OpenSettingsCommand = new DelegateCommand(ExecuteOpenSettings);
BrowseDataRootPathCommand = new DelegateCommand(ExecuteBrowseDataRootPath);
ResetDataRootPathCommand = new DelegateCommand(ExecuteResetDataRootPath);
SaveDataRootPathCommand = new DelegateCommand(ExecuteSaveDataRootPath);
NewCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.NewProgramCommand.Execute())); NewCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.NewProgramCommand.Execute()));
SaveCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.SaveProgramCommand.Execute())); SaveCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.SaveProgramCommand.Execute()));
LoadCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.LoadProgramCommand.Execute())); LoadCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.LoadProgramCommand.Execute()));
@@ -259,6 +285,7 @@ namespace XplorePlane.ViewModels
ImagePanelContent = new PipelineEditorView(); ImagePanelContent = new PipelineEditorView();
ViewportPanelWidth = new GridLength(1, GridUnitType.Star); ViewportPanelWidth = new GridLength(1, GridUnitType.Star);
ImagePanelWidth = new GridLength(320); ImagePanelWidth = new GridLength(320);
DataRootPath = _xpDataPathService.RootPath;
_logger.Info("MainViewModel 已初始化"); _logger.Info("MainViewModel 已初始化");
} }
@@ -395,6 +422,56 @@ namespace XplorePlane.ViewModels
} }
} }
private void ExecuteBrowseDataRootPath()
{
try
{
var dialog = new OpenFolderDialog
{
Title = "选择 XP 数据根目录",
InitialDirectory = Directory.Exists(DataRootPath) ? DataRootPath : _xpDataPathService.RootPath
};
if (dialog.ShowDialog() == true)
{
DataRootPath = dialog.FolderName;
}
}
catch (Exception ex)
{
_logger.Error(ex, "浏览 XP 数据根目录失败");
MessageBox.Show($"浏览数据根目录失败:{ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ExecuteResetDataRootPath()
{
DataRootPath = _xpDataPathService.DefaultRootPath;
}
private void ExecuteSaveDataRootPath()
{
try
{
_xpDataPathService.SaveRootPath(DataRootPath);
DataRootPath = _xpDataPathService.RootPath;
RaisePropertyChanged(nameof(PlanRootPath));
RaisePropertyChanged(nameof(ToolsRootPath));
RaisePropertyChanged(nameof(ResultsRootPath));
RaisePropertyChanged(nameof(ReportRootPath));
MessageBox.Show("XP 数据根目录已保存。新的保存/加载对话框会立即使用新路径。",
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
_logger.Error(ex, "保存 XP 数据根目录失败");
MessageBox.Show($"保存数据根目录失败:{ex.Message}",
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ExecuteAxisReset() private void ExecuteAxisReset()
{ {
var result = MessageBox.Show("确认执行轴复位操作?", "轴复位", var result = MessageBox.Show("确认执行轴复位操作?", "轴复位",
+46 -5
View File
@@ -28,20 +28,61 @@
<Style x:Key="ActionButtonStyle" TargetType="Button"> <Style x:Key="ActionButtonStyle" TargetType="Button">
<Setter Property="Height" Value="32" /> <Setter Property="Height" Value="32" />
<Setter Property="MinWidth" Value="110" /> <Setter Property="MinWidth" Value="96" />
<Setter Property="Margin" Value="0,0,10,10" /> <Setter Property="Margin" Value="0,0,10,10" />
<Setter Property="Padding" Value="12,0" /> <Setter Property="Padding" Value="12,0" />
</Style> </Style>
</Window.Resources> </Window.Resources>
<DockPanel Margin="12"> <DockPanel Margin="12">
<TabControl Background="White"> <TabControl Background="White">
<TabItem Header="通用"> <TabItem Header="通用">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="16"> <StackPanel Margin="16">
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="通用设置" /> <TextBlock Style="{StaticResource SectionTitleStyle}" Text="通用设置" />
<Border Style="{StaticResource CardStyle}">
<StackPanel>
<TextBlock FontSize="13"
FontWeight="SemiBold"
Text="XP 数据目录" />
<TextBlock Margin="0,8,0,8"
Foreground="#666666"
Text="Plan 用于 CNC 默认保存和加载,Tools 用于流程图配方 xpm,Data 用于执行结果和中间图像,Report 为报告预留目录。" />
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
Text="数据根目录" />
<TextBox Grid.Column="1"
Height="30"
Margin="0,0,10,0"
Padding="8,0"
Text="{Binding DataRootPath, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Column="2"
Command="{Binding BrowseDataRootPathCommand}"
Content="浏览"
Style="{StaticResource ActionButtonStyle}" />
<Button Grid.Column="3"
Command="{Binding ResetDataRootPathCommand}"
Content="恢复默认"
Style="{StaticResource ActionButtonStyle}" />
<Button Grid.Column="4"
Command="{Binding SaveDataRootPathCommand}"
Content="保存"
Style="{StaticResource ActionButtonStyle}" />
</Grid>
</StackPanel>
</Border>
<Border Style="{StaticResource CardStyle}"> <Border Style="{StaticResource CardStyle}">
<StackPanel> <StackPanel>
<TextBlock FontSize="13" <TextBlock FontSize="13"
@@ -93,7 +134,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="报告标题" /> Text="报告标题" />
<TextBox Grid.Column="1" <TextBox Grid.Column="1"
Height="30" Height="30"
Padding="8,0" Padding="8,0"
@@ -107,11 +148,11 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" <TextBlock Grid.Column="0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="模板路径" /> Text="模板路径" />
<TextBox Grid.Column="1" <TextBox Grid.Column="1"
Height="30" Height="30"
Padding="8,0" Padding="8,0"
Text="C:\Reports\Templates\Default" /> Text="D:\XPData\Report\Templates\Default" />
</Grid> </Grid>
<CheckBox Margin="0,12,0,0" <CheckBox Margin="0,12,0,0"