Plan 用于 CNC 默认保存和加载,Tools 用于流程图配方 xpm,Data 用于执行结果和中间图像,Report 为报告预留目录
This commit is contained in:
@@ -6,6 +6,7 @@ using System.Windows.Media.Imaging;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.Services.Storage;
|
||||
using XplorePlane.Tests.Helpers;
|
||||
using XplorePlane.ViewModels;
|
||||
using Xunit;
|
||||
@@ -22,6 +23,7 @@ namespace XplorePlane.Tests.Pipeline
|
||||
private readonly Mock<IPipelineExecutionService> _mockExecSvc;
|
||||
private readonly Mock<IPipelinePersistenceService> _mockPersistSvc;
|
||||
private readonly Mock<ILoggerService> _mockLogger;
|
||||
private readonly Mock<IXpDataPathService> _mockDataPathService;
|
||||
|
||||
public PipelineEditorViewModelTests()
|
||||
{
|
||||
@@ -29,11 +31,19 @@ namespace XplorePlane.Tests.Pipeline
|
||||
_mockExecSvc = new Mock<IPipelineExecutionService>();
|
||||
_mockPersistSvc = new Mock<IPipelinePersistenceService>();
|
||||
_mockLogger = new Mock<ILoggerService>();
|
||||
_mockDataPathService = new Mock<IXpDataPathService>();
|
||||
_mockLogger.Setup(l => l.ForModule<PipelineEditorViewModel>()).Returns(_mockLogger.Object);
|
||||
_mockDataPathService.SetupGet(s => s.ToolsPath).Returns(Path.GetTempPath());
|
||||
}
|
||||
|
||||
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 ────────────────────────────────────
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.Services.Storage;
|
||||
using XplorePlane.Tests.Helpers;
|
||||
using Xunit;
|
||||
|
||||
@@ -199,5 +201,21 @@ namespace XplorePlane.Tests.Pipeline
|
||||
|
||||
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 XplorePlane.Models;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.Services.Storage;
|
||||
using XplorePlane.Tests.Helpers;
|
||||
using XplorePlane.ViewModels;
|
||||
|
||||
@@ -31,8 +32,16 @@ namespace XplorePlane.Tests.Pipeline
|
||||
var mockExecSvc = new Mock<IPipelineExecutionService>();
|
||||
var mockPersistSvc = new Mock<IPipelinePersistenceService>();
|
||||
var mockLogger = new Mock<ILoggerService>();
|
||||
var mockDataPathService = new Mock<IXpDataPathService>();
|
||||
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>
|
||||
|
||||
@@ -11,6 +11,7 @@ using XP.Common.Database.Interfaces;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.InspectionResults;
|
||||
using XplorePlane.Services.Storage;
|
||||
using Xunit;
|
||||
|
||||
namespace XplorePlane.Tests.Services
|
||||
@@ -294,6 +295,33 @@ namespace XplorePlane.Tests.Services
|
||||
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()
|
||||
{
|
||||
_dbContext.Dispose();
|
||||
|
||||
@@ -16,6 +16,7 @@ using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.AppState;
|
||||
using XplorePlane.Services.Cnc;
|
||||
using XplorePlane.Services.Storage;
|
||||
using XplorePlane.ViewModels.Cnc;
|
||||
using Xunit;
|
||||
|
||||
@@ -32,7 +33,9 @@ namespace XplorePlane.Tests.ViewModels
|
||||
var mockCncProgramSvc = new Mock<ICncProgramService>();
|
||||
var mockAppState = new Mock<IAppStateService>();
|
||||
var mockLogger = new Mock<ILoggerService>();
|
||||
var mockDataPathService = new Mock<IXpDataPathService>();
|
||||
mockLogger.Setup(l => l.ForModule<CncEditorViewModel>()).Returns(mockLogger.Object);
|
||||
mockDataPathService.SetupGet(s => s.PlanPath).Returns(System.IO.Path.GetTempPath());
|
||||
|
||||
mockExecSvc ??= new Mock<ICncExecutionService>();
|
||||
|
||||
@@ -52,7 +55,8 @@ namespace XplorePlane.Tests.ViewModels
|
||||
mockAppState.Object,
|
||||
new EventAggregator(),
|
||||
mockLogger.Object,
|
||||
mockExecSvc.Object);
|
||||
mockExecSvc.Object,
|
||||
mockDataPathService.Object);
|
||||
|
||||
if (initialProgram != null)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<appSettings>
|
||||
<!-- 语言配置 可选值: ZhCN, ZhTW, EnUS| Language Configuration -->
|
||||
<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" />
|
||||
|
||||
<!-- Serilog日志配置 -->
|
||||
|
||||
@@ -40,6 +40,7 @@ using XplorePlane.Services.MainViewport;
|
||||
using XplorePlane.Services.Matrix;
|
||||
using XplorePlane.Services.Measurement;
|
||||
using XplorePlane.Services.Recipe;
|
||||
using XplorePlane.Services.Storage;
|
||||
using XplorePlane.ViewModels;
|
||||
using XplorePlane.ViewModels.Cnc;
|
||||
using XplorePlane.Views;
|
||||
@@ -377,6 +378,7 @@ namespace XplorePlane
|
||||
|
||||
// 注册全局状态服务(单例)
|
||||
containerRegistry.RegisterSingleton<IAppStateService, AppStateService>();
|
||||
containerRegistry.RegisterSingleton<IXpDataPathService, XpDataPathService>();
|
||||
|
||||
// 注册检测配方服务(单例)
|
||||
containerRegistry.RegisterSingleton<IRecipeService, RecipeService>();
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
using XP.Common.Database.Interfaces;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.Storage;
|
||||
|
||||
namespace XplorePlane.Services.InspectionResults
|
||||
{
|
||||
@@ -160,6 +161,14 @@ WHERE run_id = @run_id";
|
||||
"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)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(runRecord);
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.Storage;
|
||||
|
||||
namespace XplorePlane.Services
|
||||
{
|
||||
@@ -29,6 +30,11 @@ namespace XplorePlane.Services
|
||||
"XplorePlane", "Pipelines");
|
||||
}
|
||||
|
||||
public PipelinePersistenceService(IImageProcessingService imageProcessingService, IXpDataPathService dataPathService)
|
||||
: this(imageProcessingService, dataPathService?.ToolsPath)
|
||||
{
|
||||
}
|
||||
|
||||
public async Task SaveAsync(PipelineModel pipeline, string filePath)
|
||||
{
|
||||
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.Services.AppState;
|
||||
using XplorePlane.Services.Cnc;
|
||||
using XplorePlane.Services.Storage;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
@@ -28,6 +29,7 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly ICncExecutionService _cncExecutionService;
|
||||
private readonly IXpDataPathService _dataPathService;
|
||||
|
||||
private CncProgram _currentProgram;
|
||||
private ObservableCollection<CncNodeViewModel> _nodes;
|
||||
@@ -51,13 +53,15 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
IAppStateService appStateService,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger,
|
||||
ICncExecutionService cncExecutionService)
|
||||
ICncExecutionService cncExecutionService,
|
||||
IXpDataPathService dataPathService)
|
||||
{
|
||||
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
|
||||
ArgumentNullException.ThrowIfNull(appStateService);
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<CncEditorViewModel>();
|
||||
_cncExecutionService = cncExecutionService ?? throw new ArgumentNullException(nameof(cncExecutionService));
|
||||
_dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService));
|
||||
|
||||
_nodes = new ObservableCollection<CncNodeViewModel>();
|
||||
_treeNodes = new ObservableCollection<CncNodeViewModel>();
|
||||
@@ -331,7 +335,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
Title = "保存 CNC 程序",
|
||||
Filter = "CNC 程序文件 (*.xp)|*.xp|所有文件 (*.*)|*.*",
|
||||
DefaultExt = ".xp",
|
||||
FileName = _currentProgram.Name
|
||||
FileName = _currentProgram.Name,
|
||||
InitialDirectory = GetPlanDirectory()
|
||||
};
|
||||
|
||||
if (dlg.ShowDialog() != true)
|
||||
@@ -355,7 +360,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
Title = "加载 CNC 程序",
|
||||
Filter = "CNC 程序文件 (*.xp)|*.xp|所有文件 (*.*)|*.*",
|
||||
DefaultExt = ".xp"
|
||||
DefaultExt = ".xp",
|
||||
InitialDirectory = GetPlanDirectory()
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return string.Empty;
|
||||
|
||||
@@ -14,6 +14,7 @@ using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Events;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.Services.Storage;
|
||||
|
||||
namespace XplorePlane.ViewModels
|
||||
{
|
||||
@@ -28,6 +29,7 @@ namespace XplorePlane.ViewModels
|
||||
private readonly IPipelinePersistenceService _persistenceService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly IXpDataPathService _dataPathService;
|
||||
|
||||
private PipelineNodeViewModel _selectedNode;
|
||||
private BitmapSource _sourceImage;
|
||||
@@ -49,13 +51,15 @@ namespace XplorePlane.ViewModels
|
||||
IPipelineExecutionService executionService,
|
||||
IPipelinePersistenceService persistenceService,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger)
|
||||
ILoggerService logger,
|
||||
IXpDataPathService dataPathService)
|
||||
{
|
||||
_imageProcessingService = imageProcessingService ?? throw new ArgumentNullException(nameof(imageProcessingService));
|
||||
_executionService = executionService ?? throw new ArgumentNullException(nameof(executionService));
|
||||
_persistenceService = persistenceService ?? throw new ArgumentNullException(nameof(persistenceService));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_logger = logger?.ForModule<PipelineEditorViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
_dataPathService = dataPathService ?? throw new ArgumentNullException(nameof(dataPathService));
|
||||
|
||||
PipelineNodes = new ObservableCollection<PipelineNodeViewModel>();
|
||||
AvailableDevices = new ObservableCollection<string>();
|
||||
@@ -808,11 +812,9 @@ namespace XplorePlane.ViewModels
|
||||
return $"已执行到“{ExecutionEndNode.DisplayName}” ({executionCount} 个有效节点)";
|
||||
}
|
||||
|
||||
private static string GetPipelineDirectory()
|
||||
private string GetPipelineDirectory()
|
||||
{
|
||||
var dir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"XplorePlane", "Pipelines");
|
||||
var dir = _dataPathService.ToolsPath;
|
||||
Directory.CreateDirectory(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using XplorePlane.Events;
|
||||
using XplorePlane.Services.MainViewport;
|
||||
using XplorePlane.Services.Storage;
|
||||
using XplorePlane.ViewModels.Cnc;
|
||||
using XplorePlane.Views;
|
||||
using XplorePlane.Views.Cnc;
|
||||
@@ -29,6 +30,7 @@ namespace XplorePlane.ViewModels
|
||||
private readonly IContainerProvider _containerProvider;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMainViewportService _mainViewportService;
|
||||
private readonly IXpDataPathService _xpDataPathService;
|
||||
private readonly CncEditorViewModel _cncEditorViewModel;
|
||||
private readonly CncPageView _cncPageView;
|
||||
|
||||
@@ -66,6 +68,9 @@ namespace XplorePlane.ViewModels
|
||||
public DelegateCommand OpenUserManualCommand { get; }
|
||||
public DelegateCommand OpenCameraSettingsCommand { get; }
|
||||
public DelegateCommand OpenSettingsCommand { get; }
|
||||
public DelegateCommand BrowseDataRootPathCommand { get; }
|
||||
public DelegateCommand ResetDataRootPathCommand { get; }
|
||||
public DelegateCommand SaveDataRootPathCommand { get; }
|
||||
public DelegateCommand NewCncProgramCommand { get; }
|
||||
public DelegateCommand SaveCncProgramCommand { get; }
|
||||
public DelegateCommand LoadCncProgramCommand { get; }
|
||||
@@ -119,6 +124,20 @@ namespace XplorePlane.ViewModels
|
||||
|
||||
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>
|
||||
public object ImagePanelContent
|
||||
{
|
||||
@@ -155,16 +174,20 @@ namespace XplorePlane.ViewModels
|
||||
private bool _isCncEditorMode;
|
||||
private string _licenseInfo = "当前时间";
|
||||
|
||||
private string _dataRootPath = string.Empty;
|
||||
|
||||
public MainViewModel(
|
||||
ILoggerService logger,
|
||||
IContainerProvider containerProvider,
|
||||
IEventAggregator eventAggregator,
|
||||
IMainViewportService mainViewportService)
|
||||
IMainViewportService mainViewportService,
|
||||
IXpDataPathService xpDataPathService)
|
||||
{
|
||||
_logger = logger?.ForModule<MainViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||||
_containerProvider = containerProvider ?? throw new ArgumentNullException(nameof(containerProvider));
|
||||
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||
_mainViewportService = mainViewportService ?? throw new ArgumentNullException(nameof(mainViewportService));
|
||||
_xpDataPathService = xpDataPathService ?? throw new ArgumentNullException(nameof(xpDataPathService));
|
||||
_cncEditorViewModel = _containerProvider.Resolve<CncEditorViewModel>();
|
||||
_cncPageView = new CncPageView { DataContext = _cncEditorViewModel };
|
||||
|
||||
@@ -215,6 +238,9 @@ namespace XplorePlane.ViewModels
|
||||
OpenUserManualCommand = new DelegateCommand(ExecuteOpenUserManual);
|
||||
OpenCameraSettingsCommand = new DelegateCommand(ExecuteOpenCameraSettings);
|
||||
OpenSettingsCommand = new DelegateCommand(ExecuteOpenSettings);
|
||||
BrowseDataRootPathCommand = new DelegateCommand(ExecuteBrowseDataRootPath);
|
||||
ResetDataRootPathCommand = new DelegateCommand(ExecuteResetDataRootPath);
|
||||
SaveDataRootPathCommand = new DelegateCommand(ExecuteSaveDataRootPath);
|
||||
NewCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.NewProgramCommand.Execute()));
|
||||
SaveCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.SaveProgramCommand.Execute()));
|
||||
LoadCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.LoadProgramCommand.Execute()));
|
||||
@@ -259,6 +285,7 @@ namespace XplorePlane.ViewModels
|
||||
ImagePanelContent = new PipelineEditorView();
|
||||
ViewportPanelWidth = new GridLength(1, GridUnitType.Star);
|
||||
ImagePanelWidth = new GridLength(320);
|
||||
DataRootPath = _xpDataPathService.RootPath;
|
||||
|
||||
_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()
|
||||
{
|
||||
var result = MessageBox.Show("确认执行轴复位操作?", "轴复位",
|
||||
|
||||
@@ -28,20 +28,61 @@
|
||||
|
||||
<Style x:Key="ActionButtonStyle" TargetType="Button">
|
||||
<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="Padding" Value="12,0" />
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<DockPanel Margin="12">
|
||||
|
||||
<TabControl Background="White">
|
||||
<TabItem Header="通用">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="16">
|
||||
<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}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="13"
|
||||
@@ -93,7 +134,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="报告标题:" />
|
||||
Text="报告标题" />
|
||||
<TextBox Grid.Column="1"
|
||||
Height="30"
|
||||
Padding="8,0"
|
||||
@@ -107,11 +148,11 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="模板路径:" />
|
||||
Text="模板路径" />
|
||||
<TextBox Grid.Column="1"
|
||||
Height="30"
|
||||
Padding="8,0"
|
||||
Text="C:\Reports\Templates\Default" />
|
||||
Text="D:\XPData\Report\Templates\Default" />
|
||||
</Grid>
|
||||
|
||||
<CheckBox Margin="0,12,0,0"
|
||||
|
||||
Reference in New Issue
Block a user