Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c0f9dab73 | |||
| 3bee2898c5 | |||
| 9a8831c945 | |||
| db8a37410f |
@@ -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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Feature: cnc-run-execution
|
||||
// Feature: cnc-run-execution
|
||||
// Properties 1, 2, 12: CncEditorViewModel execution control
|
||||
|
||||
using System;
|
||||
@@ -16,6 +16,8 @@ using XP.Common.Logging.Interfaces;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.AppState;
|
||||
using XplorePlane.Services.Cnc;
|
||||
using XplorePlane.Services.Storage;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.ViewModels.Cnc;
|
||||
using Xunit;
|
||||
|
||||
@@ -23,16 +25,20 @@ namespace XplorePlane.Tests.ViewModels
|
||||
{
|
||||
public class CncEditorViewModelTests
|
||||
{
|
||||
// ── Helpers ──────────────────────────────────────────────────────────
|
||||
// 鈹€鈹€ Helpers 鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€鈹€
|
||||
|
||||
private static CncEditorViewModel CreateVm(
|
||||
Mock<ICncExecutionService> mockExecSvc = null,
|
||||
CncProgram initialProgram = null)
|
||||
CncProgram initialProgram = null,
|
||||
Mock<IPipelinePersistenceService> mockPipelinePersistenceService = null)
|
||||
{
|
||||
var mockCncProgramSvc = new Mock<ICncProgramService>();
|
||||
var mockAppState = new Mock<IAppStateService>();
|
||||
var mockLogger = new Mock<ILoggerService>();
|
||||
var mockDataPathService = new Mock<IXpDataPathService>();
|
||||
mockPipelinePersistenceService ??= new Mock<IPipelinePersistenceService>();
|
||||
mockLogger.Setup(l => l.ForModule<CncEditorViewModel>()).Returns(mockLogger.Object);
|
||||
mockDataPathService.SetupGet(s => s.PlanPath).Returns(System.IO.Path.GetTempPath());
|
||||
|
||||
mockExecSvc ??= new Mock<ICncExecutionService>();
|
||||
|
||||
@@ -44,15 +50,52 @@ namespace XplorePlane.Tests.ViewModels
|
||||
DateTime.UtcNow, DateTime.UtcNow,
|
||||
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()));
|
||||
|
||||
mockCncProgramSvc
|
||||
.Setup(s => s.CreateNode(It.IsAny<CncNodeType>()))
|
||||
.Returns((CncNodeType nodeType) => nodeType switch
|
||||
{
|
||||
CncNodeType.InspectionModule => new InspectionModuleNode(Guid.NewGuid(), 0, "检测模块_0", new PipelineModel()),
|
||||
CncNodeType.ReferencePoint => new ReferencePointNode(Guid.NewGuid(), 0, "参考点_0", 0, 0, 0, 0, 0, 0, false, 0, 0),
|
||||
_ => throw new InvalidOperationException($"Unsupported node type in test: {nodeType}")
|
||||
});
|
||||
|
||||
mockCncProgramSvc
|
||||
.Setup(s => s.InsertNode(It.IsAny<CncProgram>(), It.IsAny<int>(), It.IsAny<CncNode>()))
|
||||
.Returns((CncProgram program, int afterIndex, CncNode node) =>
|
||||
{
|
||||
var nodes = program.Nodes.ToList();
|
||||
var insertIndex = Math.Clamp(afterIndex + 1, 0, nodes.Count);
|
||||
nodes.Insert(insertIndex, node with { Index = insertIndex });
|
||||
|
||||
for (var i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
nodes[i] = nodes[i] with { Index = i };
|
||||
}
|
||||
|
||||
return program with { Nodes = nodes.AsReadOnly(), UpdatedAt = DateTime.UtcNow };
|
||||
});
|
||||
|
||||
mockCncProgramSvc
|
||||
.Setup(s => s.UpdateNode(It.IsAny<CncProgram>(), It.IsAny<int>(), It.IsAny<CncNode>()))
|
||||
.Returns((CncProgram program, int index, CncNode updatedNode) =>
|
||||
{
|
||||
var nodes = program.Nodes.ToList();
|
||||
nodes[index] = updatedNode with { Index = index };
|
||||
return program with { Nodes = nodes.AsReadOnly(), UpdatedAt = DateTime.UtcNow };
|
||||
});
|
||||
|
||||
|
||||
var vm = new CncEditorViewModel(
|
||||
mockCncProgramSvc.Object,
|
||||
mockAppState.Object,
|
||||
new EventAggregator(),
|
||||
mockLogger.Object,
|
||||
mockExecSvc.Object);
|
||||
mockExecSvc.Object,
|
||||
mockDataPathService.Object,
|
||||
mockPipelinePersistenceService.Object);
|
||||
|
||||
if (initialProgram != null)
|
||||
{
|
||||
@@ -78,9 +121,9 @@ namespace XplorePlane.Tests.ViewModels
|
||||
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
|
||||
[Property(MaxTest = 100)]
|
||||
public Property RunStop_Commands_AreMutuallyExclusive()
|
||||
@@ -125,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
|
||||
[Property(MaxTest = 100)]
|
||||
public Property AfterExecution_IsRunningFalse_AllNodesIdle()
|
||||
@@ -176,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
|
||||
[Property(MaxTest = 100)]
|
||||
public Property WhileRunning_AllEditCommands_AreDisabled()
|
||||
@@ -228,5 +271,50 @@ namespace XplorePlane.Tests.ViewModels
|
||||
return allDisabled;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InsertInspectionModuleFromPipelineFileAsync_LoadsPipelineAndInsertsNode()
|
||||
{
|
||||
var pipelineFile = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"{Guid.NewGuid():N}.xpm");
|
||||
await System.IO.File.WriteAllTextAsync(pipelineFile, "{}");
|
||||
|
||||
try
|
||||
{
|
||||
var expectedPipeline = new PipelineModel
|
||||
{
|
||||
Name = "BuiltIn/ModuleA"
|
||||
};
|
||||
|
||||
var mockPipelinePersistenceService = new Mock<IPipelinePersistenceService>();
|
||||
mockPipelinePersistenceService
|
||||
.Setup(s => s.LoadAsync(pipelineFile))
|
||||
.ReturnsAsync(expectedPipeline);
|
||||
|
||||
var initialProgram = new CncProgram(
|
||||
Guid.NewGuid(),
|
||||
"TestProgram",
|
||||
DateTime.UtcNow,
|
||||
DateTime.UtcNow,
|
||||
new List<CncNode>
|
||||
{
|
||||
new SavePositionNode(Guid.NewGuid(), 0, "保存位置_0", MotionState.Default)
|
||||
}.AsReadOnly());
|
||||
|
||||
var vm = CreateVm(
|
||||
initialProgram: initialProgram,
|
||||
mockPipelinePersistenceService: mockPipelinePersistenceService);
|
||||
|
||||
await vm.InsertInspectionModuleFromPipelineFileAsync(pipelineFile);
|
||||
|
||||
var insertedNode = Assert.IsType<InspectionModuleNode>(vm.Nodes.Last().Model);
|
||||
Assert.Same(expectedPipeline, insertedNode.Pipeline);
|
||||
Assert.Equal("BuiltIn/ModuleA", insertedNode.Pipeline.Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (System.IO.File.Exists(pipelineFile))
|
||||
System.IO.File.Delete(pipelineFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 @@ using XplorePlane.Events;
|
||||
using XplorePlane.Models;
|
||||
using XplorePlane.Services.AppState;
|
||||
using XplorePlane.Services.Cnc;
|
||||
using XplorePlane.Services;
|
||||
using XplorePlane.Services.Storage;
|
||||
|
||||
namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
@@ -28,6 +30,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ILoggerService _logger;
|
||||
private readonly ICncExecutionService _cncExecutionService;
|
||||
private readonly IXpDataPathService _dataPathService;
|
||||
private readonly IPipelinePersistenceService _pipelinePersistenceService;
|
||||
|
||||
private CncProgram _currentProgram;
|
||||
private ObservableCollection<CncNodeViewModel> _nodes;
|
||||
@@ -51,13 +55,17 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
IAppStateService appStateService,
|
||||
IEventAggregator eventAggregator,
|
||||
ILoggerService logger,
|
||||
ICncExecutionService cncExecutionService)
|
||||
ICncExecutionService cncExecutionService,
|
||||
IXpDataPathService dataPathService,
|
||||
IPipelinePersistenceService pipelinePersistenceService)
|
||||
{
|
||||
_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));
|
||||
_pipelinePersistenceService = pipelinePersistenceService ?? throw new ArgumentNullException(nameof(pipelinePersistenceService));
|
||||
|
||||
_nodes = new ObservableCollection<CncNodeViewModel>();
|
||||
_treeNodes = new ObservableCollection<CncNodeViewModel>();
|
||||
@@ -331,7 +339,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 +364,8 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
{
|
||||
Title = "加载 CNC 程序",
|
||||
Filter = "CNC 程序文件 (*.xp)|*.xp|所有文件 (*.*)|*.*",
|
||||
DefaultExt = ".xp"
|
||||
DefaultExt = ".xp",
|
||||
InitialDirectory = GetPlanDirectory()
|
||||
};
|
||||
|
||||
if (dlg.ShowDialog() != true)
|
||||
@@ -438,6 +448,64 @@ namespace XplorePlane.ViewModels.Cnc
|
||||
}
|
||||
}
|
||||
|
||||
public async Task InsertInspectionModuleFromPipelineFileAsync(string filePath)
|
||||
{
|
||||
if (IsRunning)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filePath))
|
||||
throw new ArgumentException("检测模块文件路径不能为空。", nameof(filePath));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException("检测模块文件不存在。", filePath);
|
||||
|
||||
if (_currentProgram == null)
|
||||
{
|
||||
ExecuteNewProgram();
|
||||
}
|
||||
|
||||
var pipeline = await _pipelinePersistenceService.LoadAsync(filePath);
|
||||
|
||||
try
|
||||
{
|
||||
var node = _cncProgramService.CreateNode(CncNodeType.InspectionModule);
|
||||
if (node is not InspectionModuleNode inspectionModuleNode)
|
||||
throw new InvalidOperationException("无法创建检测模块节点。");
|
||||
|
||||
var pipelineName = string.IsNullOrWhiteSpace(pipeline.Name)
|
||||
? Path.GetFileNameWithoutExtension(filePath)
|
||||
: pipeline.Name;
|
||||
pipeline.Name = pipelineName;
|
||||
|
||||
var configuredNode = inspectionModuleNode with
|
||||
{
|
||||
Pipeline = pipeline,
|
||||
Name = pipelineName
|
||||
};
|
||||
|
||||
int afterIndex = ResolveInsertAfterIndex(CncNodeType.InspectionModule);
|
||||
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, configuredNode);
|
||||
_preferredSelectedNodeId = configuredNode.Id;
|
||||
ClearPendingInsertAnchor();
|
||||
|
||||
OnProgramEdited();
|
||||
StatusMessage = $"已插入检测模块:{pipelineName}";
|
||||
_logger.Info("Inserted built-in inspection module from file: {FilePath}", filePath);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.Warn("Built-in inspection module insertion blocked: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Win32;
|
||||
using Microsoft.Win32;
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Ioc;
|
||||
@@ -7,10 +7,13 @@ using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Configuration;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
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 +32,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;
|
||||
|
||||
@@ -65,6 +69,10 @@ namespace XplorePlane.ViewModels
|
||||
public DelegateCommand OpenLibraryVersionsCommand { get; }
|
||||
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; }
|
||||
@@ -73,6 +81,7 @@ namespace XplorePlane.ViewModels
|
||||
public DelegateCommand InsertCompleteProgramCommand { get; }
|
||||
public DelegateCommand InsertInspectionMarkerCommand { get; }
|
||||
public DelegateCommand InsertInspectionModuleCommand { get; }
|
||||
public DelegateCommand InsertBuiltInInspectionModuleCommand { get; }
|
||||
public DelegateCommand InsertSaveNodeCommand { get; }
|
||||
public DelegateCommand InsertPauseDialogCommand { get; }
|
||||
public DelegateCommand InsertWaitDelayCommand { get; }
|
||||
@@ -118,6 +127,34 @@ 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;
|
||||
|
||||
public ObservableCollection<BuiltInInspectionModuleItem> BuiltInInspectionModules { get; } = new();
|
||||
|
||||
public BuiltInInspectionModuleItem SelectedBuiltInInspectionModule
|
||||
{
|
||||
get => _selectedBuiltInInspectionModule;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedBuiltInInspectionModule, value))
|
||||
{
|
||||
InsertBuiltInInspectionModuleCommand?.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>右侧图像区域内容 | Right-side image panel content</summary>
|
||||
public object ImagePanelContent
|
||||
{
|
||||
@@ -140,11 +177,11 @@ namespace XplorePlane.ViewModels
|
||||
}
|
||||
|
||||
// 窗口引用(单例窗口防止重复打开)
|
||||
|
||||
private Window _motionDebugWindow;
|
||||
private Window _detectorConfigWindow;
|
||||
private Window _plcAddrConfigWindow;
|
||||
private Window _realTimeLogViewerWindow;
|
||||
private Window _settingsWindow;
|
||||
private Window _toolboxWindow;
|
||||
private Window _raySourceConfigWindow;
|
||||
private object _imagePanelContent;
|
||||
@@ -153,16 +190,21 @@ namespace XplorePlane.ViewModels
|
||||
private bool _isCncEditorMode;
|
||||
private string _licenseInfo = "当前时间";
|
||||
|
||||
private string _dataRootPath = string.Empty;
|
||||
private BuiltInInspectionModuleItem _selectedBuiltInInspectionModule;
|
||||
|
||||
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 };
|
||||
|
||||
@@ -183,8 +225,8 @@ namespace XplorePlane.ViewModels
|
||||
var node = _cncEditorViewModel.SelectedNode;
|
||||
if (node?.ResultImage != null)
|
||||
{
|
||||
_logger.Info("[图像链路] 切换到节点 [{Name}],显示缓存结果图像", node.Name);
|
||||
_mainViewportService.SetManualImage(node.ResultImage, $"CNC节点:{node.Name}");
|
||||
_logger.Info("[Image] Switched to node [{Name}], showing cached result image.", node.Name);
|
||||
_mainViewportService.SetManualImage(node.ResultImage, $"CNC node: {node.Name}");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -207,11 +249,15 @@ namespace XplorePlane.ViewModels
|
||||
LoadImageCommand = new DelegateCommand(ExecuteLoadImage);
|
||||
|
||||
OpenCncEditorCommand = new DelegateCommand(ExecuteOpenCncEditor);
|
||||
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编排"));
|
||||
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编辑"));
|
||||
OpenToolboxCommand = new DelegateCommand(ExecuteOpenToolbox);
|
||||
OpenLibraryVersionsCommand = new DelegateCommand(() => ShowWindow(new Views.LibraryVersionsWindow(), "关于"));
|
||||
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()));
|
||||
@@ -220,6 +266,9 @@ namespace XplorePlane.ViewModels
|
||||
InsertCompleteProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertCompleteProgramCommand.Execute()));
|
||||
InsertInspectionMarkerCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertInspectionMarkerCommand.Execute()));
|
||||
InsertInspectionModuleCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertInspectionModuleCommand.Execute()));
|
||||
InsertBuiltInInspectionModuleCommand = new DelegateCommand(
|
||||
async () => await ExecuteInsertBuiltInInspectionModuleAsync(),
|
||||
CanExecuteInsertBuiltInInspectionModule);
|
||||
InsertSaveNodeCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertSaveNodeCommand.Execute()));
|
||||
InsertPauseDialogCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertPauseDialogCommand.Execute()));
|
||||
InsertWaitDelayCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertWaitDelayCommand.Execute()));
|
||||
@@ -238,7 +287,6 @@ namespace XplorePlane.ViewModels
|
||||
BubbleMeasureCommand = new DelegateCommand(ExecuteBubbleMeasure);
|
||||
|
||||
// 辅助线命令
|
||||
|
||||
ToggleCrosshairCommand = new DelegateCommand(() =>
|
||||
_eventAggregator.GetEvent<ToggleCrosshairEvent>().Publish());
|
||||
|
||||
@@ -256,6 +304,8 @@ namespace XplorePlane.ViewModels
|
||||
ImagePanelContent = new PipelineEditorView();
|
||||
ViewportPanelWidth = new GridLength(1, GridUnitType.Star);
|
||||
ImagePanelWidth = new GridLength(320);
|
||||
DataRootPath = _xpDataPathService.RootPath;
|
||||
LoadBuiltInInspectionModules();
|
||||
|
||||
_logger.Info("MainViewModel 已初始化");
|
||||
}
|
||||
@@ -289,7 +339,7 @@ namespace XplorePlane.ViewModels
|
||||
|
||||
private void ExecuteOpenToolbox()
|
||||
{
|
||||
ShowOrActivate(_toolboxWindow, w => _toolboxWindow = w, () => new Views.OperatorToolboxWindow(), "算子工具箱");
|
||||
ShowOrActivate(_toolboxWindow, w => _toolboxWindow = w, () => new Views.OperatorToolboxWindow(), "Operator Toolbox");
|
||||
}
|
||||
|
||||
private void ExecuteOpenCncEditor()
|
||||
@@ -330,15 +380,15 @@ namespace XplorePlane.ViewModels
|
||||
var manualPath = ConfigurationManager.AppSettings["UserManual"];
|
||||
if (string.IsNullOrEmpty(manualPath))
|
||||
{
|
||||
_logger.Warn("未配置用户手册路径");
|
||||
MessageBox.Show("未配置用户手册路径,请检查 App.config 中的 UserManual 配置项。",
|
||||
_logger.Warn("User manual path is not configured.");
|
||||
MessageBox.Show("User manual path is not configured. Please check the UserManual setting in App.config.",
|
||||
"提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(manualPath))
|
||||
{
|
||||
_logger.Warn("用户手册文件不存在:{Path}", manualPath);
|
||||
_logger.Warn("User manual file not found: {Path}", manualPath);
|
||||
MessageBox.Show($"用户手册文件不存在:\n{manualPath}",
|
||||
"提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
@@ -351,8 +401,8 @@ namespace XplorePlane.ViewModels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "打开用户手册失败");
|
||||
MessageBox.Show($"打开用户手册失败:{ex.Message}",
|
||||
_logger.Error(ex, "Failed to open user manual.");
|
||||
MessageBox.Show($"Failed to open user manual: {ex.Message}",
|
||||
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
@@ -373,13 +423,146 @@ namespace XplorePlane.ViewModels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "打开相机设置失败");
|
||||
_logger.Error(ex, "Failed to open camera settings.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteOpenSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
ShowOrActivate(_settingsWindow, w => _settingsWindow = w,
|
||||
() => new Views.SettingsWindow(this), "Settings");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to open settings window");
|
||||
MessageBox.Show($"Failed to open settings window: {ex.Message}",
|
||||
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
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, "Failed to browse XP data root.");
|
||||
MessageBox.Show($"Failed to browse data root: {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));
|
||||
LoadBuiltInInspectionModules();
|
||||
|
||||
MessageBox.Show("XP data root saved. New save/load dialogs will use the new path immediately.",
|
||||
"提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to save XP data root.");
|
||||
MessageBox.Show($"Failed to save data root: {ex.Message}",
|
||||
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanExecuteInsertBuiltInInspectionModule()
|
||||
{
|
||||
return SelectedBuiltInInspectionModule != null;
|
||||
}
|
||||
|
||||
private async Task ExecuteInsertBuiltInInspectionModuleAsync()
|
||||
{
|
||||
var module = SelectedBuiltInInspectionModule;
|
||||
if (module == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
ShowCncEditor();
|
||||
await _cncEditorViewModel.InsertInspectionModuleFromPipelineFileAsync(module.FilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Failed to insert built-in inspection module: {FilePath}", module.FilePath);
|
||||
MessageBox.Show($"Failed to insert built-in inspection module: {ex.Message}",
|
||||
"Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadBuiltInInspectionModules()
|
||||
{
|
||||
BuiltInInspectionModules.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
var toolsPath = _xpDataPathService.ToolsPath;
|
||||
if (!Directory.Exists(toolsPath))
|
||||
{
|
||||
SelectedBuiltInInspectionModule = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var files = Directory
|
||||
.EnumerateFiles(toolsPath, "*.xpm", SearchOption.AllDirectories)
|
||||
.OrderBy(path => path, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(path => new BuiltInInspectionModuleItem(
|
||||
GetBuiltInModuleDisplayName(toolsPath, path),
|
||||
path))
|
||||
.ToList();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
BuiltInInspectionModules.Add(file);
|
||||
}
|
||||
|
||||
SelectedBuiltInInspectionModule = BuiltInInspectionModules.FirstOrDefault();
|
||||
_logger.Info("Loaded {Count} built-in inspection modules from {ToolsPath}", BuiltInInspectionModules.Count, toolsPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SelectedBuiltInInspectionModule = null;
|
||||
_logger.Error(ex, "Failed to load built-in inspection modules.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetBuiltInModuleDisplayName(string toolsPath, string filePath)
|
||||
{
|
||||
var relativePath = Path.GetRelativePath(toolsPath, filePath);
|
||||
var withoutExtension = Path.ChangeExtension(relativePath, null) ?? relativePath;
|
||||
return withoutExtension.Replace(Path.DirectorySeparatorChar, '/');
|
||||
}
|
||||
|
||||
private void ExecuteAxisReset()
|
||||
{
|
||||
var result = MessageBox.Show("确认执行轴复位操作?", "轴复位",
|
||||
var result = MessageBox.Show("Confirm axis reset?", "Axis Reset",
|
||||
MessageBoxButton.OKCancel, MessageBoxImage.Question);
|
||||
if (result != MessageBoxResult.OK)
|
||||
return;
|
||||
@@ -396,7 +579,7 @@ namespace XplorePlane.ViewModels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "轴复位异常");
|
||||
_logger.Error(ex, "Axis reset failed.");
|
||||
MessageBox.Show($"轴复位异常:{ex.Message}", "错误",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
@@ -407,12 +590,12 @@ namespace XplorePlane.ViewModels
|
||||
try
|
||||
{
|
||||
ShowOrActivate(_detectorConfigWindow, w => _detectorConfigWindow = w,
|
||||
() => new XP.Hardware.Detector.Views.DetectorConfigWindow(), "探测器配置");
|
||||
() => new XP.Hardware.Detector.Views.DetectorConfigWindow(), "Detector Config");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "打开探测器配置窗口失败");
|
||||
MessageBox.Show($"打开探测器配置窗口失败:\n{ex.InnerException?.Message ?? ex.Message}",
|
||||
_logger.Error(ex, "Failed to open detector config window.");
|
||||
MessageBox.Show($"Failed to open detector config window:\n{ex.InnerException?.Message ?? ex.Message}",
|
||||
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
@@ -432,7 +615,7 @@ namespace XplorePlane.ViewModels
|
||||
private void ExecuteOpenRaySourceConfig()
|
||||
{
|
||||
ShowOrActivate(_raySourceConfigWindow, w => _raySourceConfigWindow = w,
|
||||
() => new XP.Hardware.RaySource.Views.RaySourceConfigWindow(), "射线源配置");
|
||||
() => new XP.Hardware.RaySource.Views.RaySourceConfigWindow(), "Ray Source Config");
|
||||
}
|
||||
|
||||
private void ExecuteLoadImage()
|
||||
@@ -455,20 +638,20 @@ namespace XplorePlane.ViewModels
|
||||
bitmap.EndInit();
|
||||
bitmap.Freeze();
|
||||
|
||||
_logger.Info("[图像链路] ExecuteLoadImage:加载图像 {Path},准备推送到 MainViewportService 和 ManualImageLoadedEvent", dialog.FileName);
|
||||
_logger.Info("[Image] ExecuteLoadImage loaded image {Path} and will push it to MainViewportService and ManualImageLoadedEvent.", dialog.FileName);
|
||||
_mainViewportService.SetManualImage(bitmap, dialog.FileName);
|
||||
|
||||
// 同时发布事件,让 PipelineEditorViewModel 收到图像并触发流水线执行
|
||||
// Publish the image to the pipeline editor at the same time.
|
||||
_eventAggregator.GetEvent<ManualImageLoadedEvent>()
|
||||
.Publish(new ManualImageLoadedPayload(bitmap, dialog.FileName));
|
||||
_logger.Info("[图像链路] ManualImageLoadedEvent 已发布");
|
||||
_logger.Info("[Image] ManualImageLoadedEvent published.");
|
||||
|
||||
RaisePropertyChanged(nameof(IsUsingLiveDetectorSource));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "加载图像失败:{Path}", dialog.FileName);
|
||||
MessageBox.Show($"加载图像失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
_logger.Error(ex, "Failed to load image: {Path}", dialog.FileName);
|
||||
MessageBox.Show($"Failed to load image: {ex.Message}", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,18 +674,18 @@ namespace XplorePlane.ViewModels
|
||||
var result = raySourceService.WarmUp();
|
||||
if (!result.Success)
|
||||
{
|
||||
MessageBox.Show($"暖机失败:{result.ErrorMessage}", "错误",
|
||||
MessageBox.Show($"Warm-up failed: {result.ErrorMessage}", "Error",
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("暖机命令已发送");
|
||||
_logger.Info("Warm-up command sent.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "暖机异常");
|
||||
MessageBox.Show($"暖机异常:{ex.Message}", "错误",
|
||||
_logger.Error(ex, "Warm-up failed.");
|
||||
MessageBox.Show($"Warm-up error: {ex.Message}", "Error",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
@@ -525,28 +708,28 @@ namespace XplorePlane.ViewModels
|
||||
private void ExecutePointDistanceMeasure()
|
||||
{
|
||||
if (!CheckImageLoaded()) return;
|
||||
_logger.Info("点点距测量功能已触发");
|
||||
_logger.Info("Point distance measurement triggered.");
|
||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointDistance);
|
||||
}
|
||||
|
||||
private void ExecutePointLineDistanceMeasure()
|
||||
{
|
||||
if (!CheckImageLoaded()) return;
|
||||
_logger.Info("点线距测量功能已触发");
|
||||
_logger.Info("Point-line distance measurement triggered.");
|
||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.PointLineDistance);
|
||||
}
|
||||
|
||||
private void ExecuteAngleMeasure()
|
||||
{
|
||||
if (!CheckImageLoaded()) return;
|
||||
_logger.Info("角度测量功能已触发");
|
||||
_logger.Info("Angle measurement triggered.");
|
||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.Angle);
|
||||
}
|
||||
|
||||
private void ExecuteThroughHoleFillRateMeasure()
|
||||
{
|
||||
if (!CheckImageLoaded()) return;
|
||||
_logger.Info("通孔填锡率测量功能已触发");
|
||||
_logger.Info("Through-hole fill-rate measurement triggered.");
|
||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.ThroughHoleFillRate);
|
||||
}
|
||||
|
||||
@@ -555,7 +738,7 @@ namespace XplorePlane.ViewModels
|
||||
private void ExecuteBgaVoidMeasure()
|
||||
{
|
||||
if (!CheckImageLoaded()) return;
|
||||
_logger.Info("BGA空隙测量功能已触发");
|
||||
_logger.Info("BGA void measurement triggered.");
|
||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.BgaVoid);
|
||||
|
||||
if (_bgaMeasurePanel != null && _bgaMeasurePanel.IsVisible)
|
||||
@@ -580,12 +763,12 @@ namespace XplorePlane.ViewModels
|
||||
private void ExecuteBubbleMeasure()
|
||||
{
|
||||
if (!CheckImageLoaded()) return;
|
||||
_logger.Info("气泡测量功能已触发");
|
||||
_logger.Info("Bubble measurement triggered.");
|
||||
|
||||
// 进入气泡测量模式
|
||||
// Enter bubble measurement mode.
|
||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.BubbleMeasure);
|
||||
|
||||
// 弹出工具面板
|
||||
// Open the tool panel.
|
||||
if (_bubbleMeasurePanel != null && _bubbleMeasurePanel.IsVisible)
|
||||
{
|
||||
_bubbleMeasurePanel.Activate();
|
||||
@@ -598,7 +781,7 @@ namespace XplorePlane.ViewModels
|
||||
};
|
||||
_bubbleMeasurePanel.Closed += (s, e) =>
|
||||
{
|
||||
// 关闭面板时退出气泡测量模式
|
||||
// Exit bubble measurement mode when the panel closes.
|
||||
_eventAggregator.GetEvent<MeasurementToolEvent>().Publish(MeasurementToolMode.None);
|
||||
};
|
||||
_bubbleMeasurePanel.Show();
|
||||
@@ -619,7 +802,7 @@ namespace XplorePlane.ViewModels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "打开语言设置失败");
|
||||
_logger.Error(ex, "Failed to open language settings.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,38 +814,38 @@ namespace XplorePlane.ViewModels
|
||||
|
||||
private void OnNavigateHome()
|
||||
{
|
||||
_logger.Info("导航到主页");
|
||||
LicenseInfo = "主页";
|
||||
_logger.Info("Navigated to home.");
|
||||
LicenseInfo = "首页";
|
||||
}
|
||||
|
||||
private void OnNavigateInspect()
|
||||
{
|
||||
_logger.Info("导航到检测页面");
|
||||
LicenseInfo = "检测页面";
|
||||
_logger.Info("Navigated to inspection page.");
|
||||
LicenseInfo = "Inspection";
|
||||
}
|
||||
|
||||
private void OnOpenFile()
|
||||
{
|
||||
_logger.Info("打开文件");
|
||||
_logger.Info("Open file.");
|
||||
LicenseInfo = "打开文件";
|
||||
}
|
||||
|
||||
private void OnExport()
|
||||
{
|
||||
_logger.Info("导出数据");
|
||||
_logger.Info("Export data.");
|
||||
LicenseInfo = "导出数据";
|
||||
}
|
||||
|
||||
private void OnClear()
|
||||
{
|
||||
_logger.Info("清除数据");
|
||||
_logger.Info("Clear data.");
|
||||
LicenseInfo = "清除数据";
|
||||
}
|
||||
|
||||
private void OnEditProperties()
|
||||
{
|
||||
_logger.Info("编辑属性");
|
||||
LicenseInfo = "编辑属性";
|
||||
_logger.Info("Edit properties.");
|
||||
LicenseInfo = "Edit properties";
|
||||
}
|
||||
|
||||
private void OnMainViewportStateChanged(object sender, EventArgs e)
|
||||
@@ -678,12 +861,14 @@ namespace XplorePlane.ViewModels
|
||||
{
|
||||
if (payload?.Image == null)
|
||||
{
|
||||
_logger.Warn("[图像链路] OnPipelinePreviewUpdated:payload 或 Image 为 null,跳过");
|
||||
_logger.Warn("[Image] OnPipelinePreviewUpdated skipped because payload or image is null.");
|
||||
return;
|
||||
}
|
||||
_logger.Info("[图像链路] OnPipelinePreviewUpdated:收到流水线结果图像,推送到 MainViewportService");
|
||||
_logger.Info("[Image] OnPipelinePreviewUpdated received a pipeline preview image and pushed it to MainViewportService.");
|
||||
_mainViewportService.SetManualImage(payload.Image, string.Empty);
|
||||
}
|
||||
public sealed record BuiltInInspectionModuleItem(string DisplayName, string FilePath);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<UserControl
|
||||
<UserControl
|
||||
x:Class="XplorePlane.Views.PipelineEditorView"
|
||||
x:Name="RootControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
@@ -38,15 +38,18 @@
|
||||
|
||||
<Style x:Key="ToolbarBtn" TargetType="Button">
|
||||
<Setter Property="Height" Value="28" />
|
||||
<Setter Property="MinWidth" Value="52" />
|
||||
<Setter Property="Width" Value="32" />
|
||||
<Setter Property="MinWidth" Value="32" />
|
||||
<Setter Property="Margin" Value="2,0" />
|
||||
<Setter Property="Padding" Value="8,0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderBrush" Value="#CDCBCB" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="FontFamily" Value="{StaticResource UiFont}" />
|
||||
<Setter Property="FontSize" Value="11" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
@@ -80,25 +83,40 @@
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding NewPipelineCommand}"
|
||||
Content="新建配方"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="新建配方" />
|
||||
ToolTip="新建配方">
|
||||
<TextBlock
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14"
|
||||
Text="" />
|
||||
</Button>
|
||||
<Button
|
||||
Command="{Binding SavePipelineCommand}"
|
||||
Content="保存配方"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="保存当前配方" />
|
||||
ToolTip="保存当前配方">
|
||||
<TextBlock
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14"
|
||||
Text="" />
|
||||
</Button>
|
||||
<Button
|
||||
Width="64"
|
||||
Command="{Binding SaveAsPipelineCommand}"
|
||||
Content="另存为"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="另存当前配方" />
|
||||
ToolTip="另存当前配方">
|
||||
<TextBlock
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14"
|
||||
Text="" />
|
||||
</Button>
|
||||
<Button
|
||||
Command="{Binding LoadPipelineCommand}"
|
||||
Content="加载配方"
|
||||
Style="{StaticResource ToolbarBtn}"
|
||||
ToolTip="加载配方" />
|
||||
ToolTip="加载配方">
|
||||
<TextBlock
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="14"
|
||||
Text="" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
@@ -433,3 +451,4 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
||||
|
||||
@@ -279,6 +279,33 @@
|
||||
Size="Large"
|
||||
SmallImage="/Assets/Icons/cnc.png"
|
||||
Text="CNC 编辑" />
|
||||
<telerik:RadRibbonButton
|
||||
telerik:ScreenTip.Description="打开矩阵编排窗口,配置多工件阵列检测方案"
|
||||
telerik:ScreenTip.Title="矩阵编排"
|
||||
Command="{Binding OpenMatrixEditorCommand}"
|
||||
Size="Large"
|
||||
SmallImage="/Assets/Icons/matrix.png"
|
||||
Text="矩阵编排" />
|
||||
<StackPanel Width="170">
|
||||
<TextBlock
|
||||
Margin="0,0,0,4"
|
||||
HorizontalAlignment="Center"
|
||||
Text="内置检测模块" />
|
||||
<telerik:RadRibbonComboBox
|
||||
Width="160"
|
||||
ItemsSource="{Binding BuiltInInspectionModules}"
|
||||
DisplayMemberPath="DisplayName"
|
||||
SelectedItem="{Binding SelectedBuiltInInspectionModule}"
|
||||
IsEditable="False" />
|
||||
<telerik:RadRibbonButton
|
||||
Margin="0,4,0,0"
|
||||
telerik:ScreenTip.Description="从 Tools 目录扫描到的 .xpm 中选择一个配方,并插入到当前 CNC 程序中"
|
||||
telerik:ScreenTip.Title="插入内置检测模块"
|
||||
Command="{Binding InsertBuiltInInspectionModuleCommand}"
|
||||
Size="Medium"
|
||||
SmallImage="/Assets/Icons/Module.png"
|
||||
Text="插入模块" />
|
||||
</StackPanel>
|
||||
<!--
|
||||
<StackPanel>
|
||||
<telerik:RadRibbonButton
|
||||
@@ -331,13 +358,7 @@
|
||||
</StackPanel>
|
||||
-->
|
||||
|
||||
<telerik:RadRibbonButton
|
||||
telerik:ScreenTip.Description="打开矩阵编排窗口,配置多工件阵列检测方案"
|
||||
telerik:ScreenTip.Title="矩阵编排"
|
||||
Command="{Binding OpenMatrixEditorCommand}"
|
||||
Size="Large"
|
||||
SmallImage="/Assets/Icons/matrix.png"
|
||||
Text="矩阵编排" />
|
||||
|
||||
</telerik:RadRibbonGroup>
|
||||
|
||||
<telerik:RadRibbonGroup Header="高级模块" IsEnabled="{Binding Path=CellsGroup.IsEnabled}">
|
||||
@@ -391,6 +412,19 @@
|
||||
</telerik:RadRibbonTab>
|
||||
|
||||
<telerik:RadRibbonTab Header="设置">
|
||||
<telerik:RadRibbonGroup Header="全局设置">
|
||||
<telerik:RadRibbonGroup.Variants>
|
||||
<telerik:GroupVariant Priority="0" Variant="Large" />
|
||||
</telerik:RadRibbonGroup.Variants>
|
||||
<telerik:RadRibbonButton
|
||||
|
||||
Size="Large"
|
||||
SmallImage="/Assets/Icons/setting.png"
|
||||
Command="{Binding OpenSettingsCommand}"
|
||||
Text="全局设置" />
|
||||
|
||||
</telerik:RadRibbonGroup>
|
||||
|
||||
<telerik:RadRibbonGroup
|
||||
telerik:ScreenTip.Description="Show the Alignment tab of the Format Cells dialog box."
|
||||
telerik:ScreenTip.Title="Format Cells: Alignment"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Linq;
|
||||
using Telerik.Windows.Controls;
|
||||
using XplorePlane.ViewModels;
|
||||
|
||||
namespace XplorePlane.Views
|
||||
@@ -12,6 +16,8 @@ namespace XplorePlane.Views
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<Window x:Class="XplorePlane.Views.SettingsWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="系统设置"
|
||||
Width="860"
|
||||
Height="620"
|
||||
MinWidth="760"
|
||||
MinHeight="540"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
ShowInTaskbar="False"
|
||||
Background="#F5F5F5">
|
||||
<Window.Resources>
|
||||
<Style x:Key="SectionTitleStyle" TargetType="TextBlock">
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="Foreground" Value="#1F1F1F" />
|
||||
<Setter Property="Margin" Value="0,0,0,12" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="White" />
|
||||
<Setter Property="BorderBrush" Value="#D9D9D9" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="Padding" Value="16" />
|
||||
<Setter Property="Margin" Value="0,0,0,12" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ActionButtonStyle" TargetType="Button">
|
||||
<Setter Property="Height" Value="32" />
|
||||
<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"
|
||||
FontWeight="SemiBold"
|
||||
Text="界面与使用习惯" />
|
||||
<CheckBox Margin="0,12,0,0"
|
||||
Content="启动时默认显示实时图像" />
|
||||
<CheckBox Margin="0,8,0,0"
|
||||
Content="允许自动恢复上次工作状态" />
|
||||
<CheckBox Margin="0,8,0,0"
|
||||
Content="启用状态栏详细提示" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Text="调试" />
|
||||
<WrapPanel>
|
||||
<Button Command="{Binding OpenLibraryVersionsCommand}"
|
||||
Content="版本信息"
|
||||
Style="{StaticResource ActionButtonStyle}" />
|
||||
</WrapPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
|
||||
<TabItem Header="报告">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="16">
|
||||
<TextBlock Style="{StaticResource SectionTitleStyle}" Text="报告设置" />
|
||||
|
||||
<Border Style="{StaticResource CardStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Text="报告模板" />
|
||||
<TextBlock Margin="0,10,0,8"
|
||||
Foreground="#666666"
|
||||
Text="这里预留报告输出相关设置,可继续扩展公司信息、模板路径、签核信息和导出规则。" />
|
||||
|
||||
<Grid Margin="0,4,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="报告标题" />
|
||||
<TextBox Grid.Column="1"
|
||||
Height="30"
|
||||
Padding="8,0"
|
||||
Text="检测报告" />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="0,10,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="模板路径" />
|
||||
<TextBox Grid.Column="1"
|
||||
Height="30"
|
||||
Padding="8,0"
|
||||
Text="D:\XPData\Report\Templates\Default" />
|
||||
</Grid>
|
||||
|
||||
<CheckBox Margin="0,12,0,0"
|
||||
Content="导出时自动附带原始图像" />
|
||||
<CheckBox Margin="0,8,0,0"
|
||||
Content="导出时自动附带处理结果图像" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace XplorePlane.Views
|
||||
{
|
||||
public partial class SettingsWindow : Window
|
||||
{
|
||||
public SettingsWindow(object viewModel)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user