Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8beed66ef0 | |||
| b966e0b8e8 | |||
| 238e97d110 | |||
| d9d3e31e57 | |||
| 3b4b794dd0 | |||
| c91b55785e |
@@ -1878,10 +1878,7 @@
|
|||||||
"Telerik.UI.for.Wpf.NetCore.Xaml": "2024.1.408"
|
"Telerik.UI.for.Wpf.NetCore.Xaml": "2024.1.408"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"XP.Common.dll": {
|
"XP.Common.dll": {}
|
||||||
"assemblyVersion": "1.4.16.1",
|
|
||||||
"fileVersion": "1.4.16.1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
"en-US/XP.Common.resources.dll": {
|
"en-US/XP.Common.resources.dll": {
|
||||||
|
|||||||
@@ -0,0 +1,340 @@
|
|||||||
|
using Moq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using XP.Common.Configs;
|
||||||
|
using XP.Common.Database.Implementations;
|
||||||
|
using XP.Common.Database.Interfaces;
|
||||||
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XplorePlane.Models;
|
||||||
|
using XplorePlane.Services.InspectionResults;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace XplorePlane.Tests.Services
|
||||||
|
{
|
||||||
|
public class InspectionResultStoreTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _tempRoot;
|
||||||
|
private readonly Mock<ILoggerService> _mockLogger;
|
||||||
|
private readonly IDbContext _dbContext;
|
||||||
|
private readonly InspectionResultStore _store;
|
||||||
|
|
||||||
|
public InspectionResultStoreTests()
|
||||||
|
{
|
||||||
|
_tempRoot = Path.Combine(Path.GetTempPath(), "XplorePlane.Tests", Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(_tempRoot);
|
||||||
|
|
||||||
|
_mockLogger = new Mock<ILoggerService>();
|
||||||
|
_mockLogger.Setup(l => l.ForModule(It.IsAny<string>())).Returns(_mockLogger.Object);
|
||||||
|
_mockLogger.Setup(l => l.ForModule<InspectionResultStore>()).Returns(_mockLogger.Object);
|
||||||
|
|
||||||
|
var sqliteConfig = new SqliteConfig
|
||||||
|
{
|
||||||
|
DbFilePath = Path.Combine(_tempRoot, "inspection-results.db"),
|
||||||
|
CreateIfNotExists = true,
|
||||||
|
EnableWalMode = false,
|
||||||
|
EnableSqlLogging = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_dbContext = new SqliteContext(sqliteConfig, _mockLogger.Object);
|
||||||
|
_store = new InspectionResultStore(_dbContext, _mockLogger.Object, Path.Combine(_tempRoot, "assets"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task FullRun_WithTwoNodes_CanRoundTripDetailAndQuery()
|
||||||
|
{
|
||||||
|
var startedAt = new DateTime(2026, 4, 21, 10, 0, 0, DateTimeKind.Utc);
|
||||||
|
var run = new InspectionRunRecord
|
||||||
|
{
|
||||||
|
ProgramName = "NewCncProgram",
|
||||||
|
WorkpieceId = "QFN_1",
|
||||||
|
SerialNumber = "SN-001",
|
||||||
|
StartedAt = startedAt
|
||||||
|
};
|
||||||
|
|
||||||
|
var runSource = CreateTempFile("run-source.bmp", "run-source");
|
||||||
|
await _store.BeginRunAsync(run, new InspectionAssetWriteRequest
|
||||||
|
{
|
||||||
|
AssetType = InspectionAssetType.RunSourceImage,
|
||||||
|
SourceFilePath = runSource,
|
||||||
|
FileFormat = "bmp"
|
||||||
|
});
|
||||||
|
|
||||||
|
var pipelineA = BuildPipeline("Recipe-A", ("GaussianBlur", 0), ("Threshold", 1));
|
||||||
|
var node1Id = Guid.NewGuid();
|
||||||
|
await _store.AppendNodeResultAsync(
|
||||||
|
new InspectionNodeResult
|
||||||
|
{
|
||||||
|
RunId = run.RunId,
|
||||||
|
NodeId = node1Id,
|
||||||
|
NodeIndex = 1,
|
||||||
|
NodeName = "检测节点1",
|
||||||
|
PipelineId = pipelineA.Id,
|
||||||
|
PipelineName = pipelineA.Name,
|
||||||
|
NodePass = true,
|
||||||
|
DurationMs = 135
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new InspectionMetricResult
|
||||||
|
{
|
||||||
|
MetricKey = "bridge.rate",
|
||||||
|
MetricName = "Bridge Rate",
|
||||||
|
MetricValue = 0.12,
|
||||||
|
Unit = "%",
|
||||||
|
UpperLimit = 0.2,
|
||||||
|
IsPass = true,
|
||||||
|
DisplayOrder = 1
|
||||||
|
},
|
||||||
|
new InspectionMetricResult
|
||||||
|
{
|
||||||
|
MetricKey = "void.area",
|
||||||
|
MetricName = "Void Area",
|
||||||
|
MetricValue = 5.6,
|
||||||
|
Unit = "px",
|
||||||
|
UpperLimit = 8,
|
||||||
|
IsPass = true,
|
||||||
|
DisplayOrder = 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new PipelineExecutionSnapshot
|
||||||
|
{
|
||||||
|
PipelineName = pipelineA.Name,
|
||||||
|
PipelineDefinitionJson = JsonSerializer.Serialize(pipelineA)
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new InspectionAssetWriteRequest
|
||||||
|
{
|
||||||
|
AssetType = InspectionAssetType.NodeInputImage,
|
||||||
|
SourceFilePath = CreateTempFile("node1-input.bmp", "node1-input"),
|
||||||
|
FileFormat = "bmp"
|
||||||
|
},
|
||||||
|
new InspectionAssetWriteRequest
|
||||||
|
{
|
||||||
|
AssetType = InspectionAssetType.NodeResultImage,
|
||||||
|
SourceFilePath = CreateTempFile("node1-result.bmp", "node1-result"),
|
||||||
|
FileFormat = "bmp"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var pipelineB = BuildPipeline("Recipe-B", ("MeanFilter", 0), ("ContourDetection", 1));
|
||||||
|
var node2Id = Guid.NewGuid();
|
||||||
|
await _store.AppendNodeResultAsync(
|
||||||
|
new InspectionNodeResult
|
||||||
|
{
|
||||||
|
RunId = run.RunId,
|
||||||
|
NodeId = node2Id,
|
||||||
|
NodeIndex = 2,
|
||||||
|
NodeName = "检测节点2",
|
||||||
|
PipelineId = pipelineB.Id,
|
||||||
|
PipelineName = pipelineB.Name,
|
||||||
|
NodePass = false,
|
||||||
|
Status = InspectionNodeStatus.Failed,
|
||||||
|
DurationMs = 240
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new InspectionMetricResult
|
||||||
|
{
|
||||||
|
MetricKey = "solder.height",
|
||||||
|
MetricName = "Solder Height",
|
||||||
|
MetricValue = 1.7,
|
||||||
|
Unit = "mm",
|
||||||
|
LowerLimit = 1.8,
|
||||||
|
IsPass = false,
|
||||||
|
DisplayOrder = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new PipelineExecutionSnapshot
|
||||||
|
{
|
||||||
|
PipelineName = pipelineB.Name,
|
||||||
|
PipelineDefinitionJson = JsonSerializer.Serialize(pipelineB)
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new InspectionAssetWriteRequest
|
||||||
|
{
|
||||||
|
AssetType = InspectionAssetType.NodeResultImage,
|
||||||
|
SourceFilePath = CreateTempFile("node2-result.bmp", "node2-result"),
|
||||||
|
FileFormat = "bmp"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await _store.CompleteRunAsync(run.RunId);
|
||||||
|
|
||||||
|
var queried = await _store.QueryRunsAsync(new InspectionRunQuery
|
||||||
|
{
|
||||||
|
ProgramName = "NewCncProgram",
|
||||||
|
WorkpieceId = "QFN_1",
|
||||||
|
PipelineName = "Recipe-A"
|
||||||
|
});
|
||||||
|
|
||||||
|
var detail = await _store.GetRunDetailAsync(run.RunId);
|
||||||
|
|
||||||
|
Assert.Single(queried);
|
||||||
|
Assert.Equal(run.RunId, queried[0].RunId);
|
||||||
|
Assert.False(detail.Run.OverallPass);
|
||||||
|
Assert.Equal(2, detail.Run.NodeCount);
|
||||||
|
Assert.Equal(2, detail.Nodes.Count);
|
||||||
|
Assert.Equal(3, detail.Metrics.Count);
|
||||||
|
Assert.Equal(4, detail.Assets.Count);
|
||||||
|
Assert.Equal(2, detail.PipelineSnapshots.Count);
|
||||||
|
Assert.Contains(detail.Nodes, n => n.NodeId == node1Id && n.NodePass);
|
||||||
|
Assert.Contains(detail.Nodes, n => n.NodeId == node2Id && !n.NodePass);
|
||||||
|
Assert.All(detail.PipelineSnapshots, snapshot => Assert.False(string.IsNullOrWhiteSpace(snapshot.PipelineHash)));
|
||||||
|
|
||||||
|
var manifestPath = Path.Combine(_tempRoot, "assets", detail.Run.ResultRootPath.Replace('/', Path.DirectorySeparatorChar), "manifest.json");
|
||||||
|
Assert.True(File.Exists(manifestPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AppendNodeResult_MissingAsset_DoesNotCrashAndMarksAssetMissing()
|
||||||
|
{
|
||||||
|
var run = new InspectionRunRecord
|
||||||
|
{
|
||||||
|
ProgramName = "Program-A",
|
||||||
|
WorkpieceId = "Part-01",
|
||||||
|
SerialNumber = "SN-404"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _store.BeginRunAsync(run);
|
||||||
|
|
||||||
|
var nodeId = Guid.NewGuid();
|
||||||
|
await _store.AppendNodeResultAsync(
|
||||||
|
new InspectionNodeResult
|
||||||
|
{
|
||||||
|
RunId = run.RunId,
|
||||||
|
NodeId = nodeId,
|
||||||
|
NodeIndex = 1,
|
||||||
|
NodeName = "缺图节点",
|
||||||
|
PipelineId = Guid.NewGuid(),
|
||||||
|
PipelineName = "Recipe-Missing",
|
||||||
|
NodePass = true
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new InspectionMetricResult
|
||||||
|
{
|
||||||
|
MetricKey = "metric.only",
|
||||||
|
MetricName = "Metric Only",
|
||||||
|
MetricValue = 1,
|
||||||
|
Unit = "pcs",
|
||||||
|
IsPass = true,
|
||||||
|
DisplayOrder = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new PipelineExecutionSnapshot
|
||||||
|
{
|
||||||
|
PipelineName = "Recipe-Missing",
|
||||||
|
PipelineDefinitionJson = "{\"nodes\":[\"gaussian\"]}"
|
||||||
|
},
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new InspectionAssetWriteRequest
|
||||||
|
{
|
||||||
|
AssetType = InspectionAssetType.NodeResultImage,
|
||||||
|
SourceFilePath = Path.Combine(_tempRoot, "missing-file.bmp"),
|
||||||
|
FileFormat = "bmp"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var detail = await _store.GetRunDetailAsync(run.RunId);
|
||||||
|
var node = Assert.Single(detail.Nodes);
|
||||||
|
|
||||||
|
Assert.Equal(InspectionNodeStatus.AssetMissing, node.Status);
|
||||||
|
Assert.Single(detail.Metrics);
|
||||||
|
Assert.Empty(detail.Assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PipelineSnapshot_IsStoredAsExecutionSnapshot_NotDependentOnLaterChanges()
|
||||||
|
{
|
||||||
|
var run = new InspectionRunRecord
|
||||||
|
{
|
||||||
|
ProgramName = "Program-Snapshot",
|
||||||
|
WorkpieceId = "Part-02",
|
||||||
|
SerialNumber = "SN-SNAP"
|
||||||
|
};
|
||||||
|
|
||||||
|
await _store.BeginRunAsync(run);
|
||||||
|
|
||||||
|
var pipeline = BuildPipeline("Recipe-Snapshot", ("GaussianBlur", 0), ("ContourDetection", 1));
|
||||||
|
var snapshotJson = JsonSerializer.Serialize(pipeline);
|
||||||
|
var originalHash = ComputeExpectedHash(snapshotJson);
|
||||||
|
|
||||||
|
await _store.AppendNodeResultAsync(
|
||||||
|
new InspectionNodeResult
|
||||||
|
{
|
||||||
|
RunId = run.RunId,
|
||||||
|
NodeId = Guid.NewGuid(),
|
||||||
|
NodeIndex = 1,
|
||||||
|
NodeName = "快照节点",
|
||||||
|
PipelineId = pipeline.Id,
|
||||||
|
PipelineName = pipeline.Name,
|
||||||
|
NodePass = true
|
||||||
|
},
|
||||||
|
pipelineSnapshot: new PipelineExecutionSnapshot
|
||||||
|
{
|
||||||
|
PipelineName = pipeline.Name,
|
||||||
|
PipelineDefinitionJson = snapshotJson
|
||||||
|
});
|
||||||
|
|
||||||
|
pipeline.Name = "Recipe-Snapshot-Changed";
|
||||||
|
pipeline.Nodes[0].OperatorKey = "MeanFilter";
|
||||||
|
|
||||||
|
var detail = await _store.GetRunDetailAsync(run.RunId);
|
||||||
|
var snapshot = Assert.Single(detail.PipelineSnapshots);
|
||||||
|
|
||||||
|
Assert.Equal("Recipe-Snapshot", snapshot.PipelineName);
|
||||||
|
Assert.Equal(snapshotJson, snapshot.PipelineDefinitionJson);
|
||||||
|
Assert.Equal(originalHash, snapshot.PipelineHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_dbContext.Dispose();
|
||||||
|
if (Directory.Exists(_tempRoot))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete(_tempRoot, true);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
// SQLite file handles may release slightly after test teardown.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CreateTempFile(string fileName, string content)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(_tempRoot, fileName);
|
||||||
|
File.WriteAllText(path, content);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PipelineModel BuildPipeline(string name, params (string OperatorKey, int Order)[] nodes)
|
||||||
|
{
|
||||||
|
return new PipelineModel
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Nodes = nodes.Select(node => new PipelineNodeModel
|
||||||
|
{
|
||||||
|
OperatorKey = node.OperatorKey,
|
||||||
|
Order = node.Order
|
||||||
|
}).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ComputeExpectedHash(string value)
|
||||||
|
{
|
||||||
|
using var sha = System.Security.Cryptography.SHA256.Create();
|
||||||
|
var bytes = System.Text.Encoding.UTF8.GetBytes(value);
|
||||||
|
return Convert.ToHexString(sha.ComputeHash(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,11 +34,4 @@
|
|||||||
<ProjectReference Include="..\XplorePlane\XplorePlane.csproj" />
|
<ProjectReference Include="..\XplorePlane\XplorePlane.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="ImageProcessing.Core">
|
|
||||||
<HintPath>..\XplorePlane\Libs\ImageProcessing\ImageProcessing.Core.dll</HintPath>
|
|
||||||
<Private>True</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ using XplorePlane.Services.Camera;
|
|||||||
using XplorePlane.Services.Cnc;
|
using XplorePlane.Services.Cnc;
|
||||||
using XplorePlane.Services.Matrix;
|
using XplorePlane.Services.Matrix;
|
||||||
using XplorePlane.Services.Measurement;
|
using XplorePlane.Services.Measurement;
|
||||||
|
using XplorePlane.Services.InspectionResults;
|
||||||
using XplorePlane.Services.Recipe;
|
using XplorePlane.Services.Recipe;
|
||||||
using XplorePlane.ViewModels;
|
using XplorePlane.ViewModels;
|
||||||
using XplorePlane.ViewModels.Cnc;
|
using XplorePlane.ViewModels.Cnc;
|
||||||
@@ -317,6 +318,7 @@ namespace XplorePlane
|
|||||||
containerRegistry.RegisterSingleton<ICncProgramService, CncProgramService>();
|
containerRegistry.RegisterSingleton<ICncProgramService, CncProgramService>();
|
||||||
containerRegistry.RegisterSingleton<IMatrixService, MatrixService>();
|
containerRegistry.RegisterSingleton<IMatrixService, MatrixService>();
|
||||||
containerRegistry.RegisterSingleton<IMeasurementDataService, MeasurementDataService>();
|
containerRegistry.RegisterSingleton<IMeasurementDataService, MeasurementDataService>();
|
||||||
|
containerRegistry.RegisterSingleton<IInspectionResultStore, InspectionResultStore>();
|
||||||
|
|
||||||
// ── CNC / 矩阵 ViewModel(瞬态)──
|
// ── CNC / 矩阵 ViewModel(瞬态)──
|
||||||
containerRegistry.Register<CncEditorViewModel>();
|
containerRegistry.Register<CncEditorViewModel>();
|
||||||
@@ -354,4 +356,4 @@ namespace XplorePlane
|
|||||||
base.ConfigureModuleCatalog(moduleCatalog);
|
base.ConfigureModuleCatalog(moduleCatalog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 174 KiB |
@@ -46,7 +46,8 @@ namespace XplorePlane.Models
|
|||||||
/// <summary>参考点节点 | Reference point node</summary>
|
/// <summary>参考点节点 | Reference point node</summary>
|
||||||
public record ReferencePointNode(
|
public record ReferencePointNode(
|
||||||
Guid Id, int Index, string Name,
|
Guid Id, int Index, string Name,
|
||||||
double XM, double YM, double ZT, double ZD, double TiltD, double Dist
|
double XM, double YM, double ZT, double ZD, double TiltD, double Dist,
|
||||||
|
bool IsRayOn, double Voltage, double Current
|
||||||
) : CncNode(Id, Index, CncNodeType.ReferencePoint, Name);
|
) : CncNode(Id, Index, CncNodeType.ReferencePoint, Name);
|
||||||
|
|
||||||
/// <summary>保存节点(含图像)| Save node with image</summary>
|
/// <summary>保存节点(含图像)| Save node with image</summary>
|
||||||
@@ -113,4 +114,4 @@ namespace XplorePlane.Models
|
|||||||
DateTime UpdatedAt,
|
DateTime UpdatedAt,
|
||||||
IReadOnlyList<CncNode> Nodes
|
IReadOnlyList<CncNode> Nodes
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace XplorePlane.Models
|
||||||
|
{
|
||||||
|
public enum InspectionAssetType
|
||||||
|
{
|
||||||
|
RunSourceImage,
|
||||||
|
NodeInputImage,
|
||||||
|
NodeResultImage
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum InspectionNodeStatus
|
||||||
|
{
|
||||||
|
Succeeded,
|
||||||
|
Failed,
|
||||||
|
PartialSuccess,
|
||||||
|
AssetMissing
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InspectionRunRecord
|
||||||
|
{
|
||||||
|
public Guid RunId { get; set; } = Guid.NewGuid();
|
||||||
|
public string ProgramName { get; set; } = string.Empty;
|
||||||
|
public string WorkpieceId { get; set; } = string.Empty;
|
||||||
|
public string SerialNumber { get; set; } = string.Empty;
|
||||||
|
public DateTime StartedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
public DateTime? CompletedAt { get; set; }
|
||||||
|
public bool OverallPass { get; set; }
|
||||||
|
public string SourceImagePath { get; set; } = string.Empty;
|
||||||
|
public string ResultRootPath { get; set; } = string.Empty;
|
||||||
|
public int NodeCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InspectionNodeResult
|
||||||
|
{
|
||||||
|
public Guid RunId { get; set; }
|
||||||
|
public Guid NodeId { get; set; } = Guid.NewGuid();
|
||||||
|
public int NodeIndex { get; set; }
|
||||||
|
public string NodeName { get; set; } = string.Empty;
|
||||||
|
public Guid PipelineId { get; set; }
|
||||||
|
public string PipelineName { get; set; } = string.Empty;
|
||||||
|
public string PipelineVersionHash { get; set; } = string.Empty;
|
||||||
|
public bool NodePass { get; set; }
|
||||||
|
public string SourceImagePath { get; set; } = string.Empty;
|
||||||
|
public string ResultImagePath { get; set; } = string.Empty;
|
||||||
|
public InspectionNodeStatus Status { get; set; } = InspectionNodeStatus.Succeeded;
|
||||||
|
public long DurationMs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InspectionMetricResult
|
||||||
|
{
|
||||||
|
public Guid RunId { get; set; }
|
||||||
|
public Guid NodeId { get; set; }
|
||||||
|
public string MetricKey { get; set; } = string.Empty;
|
||||||
|
public string MetricName { get; set; } = string.Empty;
|
||||||
|
public double MetricValue { get; set; }
|
||||||
|
public string Unit { get; set; } = string.Empty;
|
||||||
|
public double? LowerLimit { get; set; }
|
||||||
|
public double? UpperLimit { get; set; }
|
||||||
|
public bool IsPass { get; set; }
|
||||||
|
public int DisplayOrder { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InspectionAssetRecord
|
||||||
|
{
|
||||||
|
public Guid RunId { get; set; }
|
||||||
|
public Guid? NodeId { get; set; }
|
||||||
|
public InspectionAssetType AssetType { get; set; }
|
||||||
|
public string RelativePath { get; set; } = string.Empty;
|
||||||
|
public string FileFormat { get; set; } = string.Empty;
|
||||||
|
public int Width { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PipelineExecutionSnapshot
|
||||||
|
{
|
||||||
|
public Guid RunId { get; set; }
|
||||||
|
public Guid NodeId { get; set; }
|
||||||
|
public string PipelineName { get; set; } = string.Empty;
|
||||||
|
public string PipelineDefinitionJson { get; set; } = string.Empty;
|
||||||
|
public string PipelineHash { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InspectionAssetWriteRequest
|
||||||
|
{
|
||||||
|
public InspectionAssetType AssetType { get; set; }
|
||||||
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
public string SourceFilePath { get; set; } = string.Empty;
|
||||||
|
public byte[] Content { get; set; }
|
||||||
|
public string FileFormat { get; set; } = string.Empty;
|
||||||
|
public int Width { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InspectionRunQuery
|
||||||
|
{
|
||||||
|
public string ProgramName { get; set; } = string.Empty;
|
||||||
|
public string WorkpieceId { get; set; } = string.Empty;
|
||||||
|
public string SerialNumber { get; set; } = string.Empty;
|
||||||
|
public string PipelineName { get; set; } = string.Empty;
|
||||||
|
public DateTime? From { get; set; }
|
||||||
|
public DateTime? To { get; set; }
|
||||||
|
public int? Skip { get; set; }
|
||||||
|
public int? Take { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InspectionRunDetail
|
||||||
|
{
|
||||||
|
public InspectionRunRecord Run { get; set; } = new();
|
||||||
|
public IReadOnlyList<InspectionNodeResult> Nodes { get; set; } = Array.Empty<InspectionNodeResult>();
|
||||||
|
public IReadOnlyList<InspectionMetricResult> Metrics { get; set; } = Array.Empty<InspectionMetricResult>();
|
||||||
|
public IReadOnlyList<InspectionAssetRecord> Assets { get; set; } = Array.Empty<InspectionAssetRecord>();
|
||||||
|
public IReadOnlyList<PipelineExecutionSnapshot> PipelineSnapshots { get; set; } = Array.Empty<PipelineExecutionSnapshot>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XP.Hardware.RaySource.Services;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
using XplorePlane.Services.AppState;
|
using XplorePlane.Services.AppState;
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
public class CncProgramService : ICncProgramService
|
public class CncProgramService : ICncProgramService
|
||||||
{
|
{
|
||||||
private readonly IAppStateService _appStateService;
|
private readonly IAppStateService _appStateService;
|
||||||
|
private readonly IRaySourceService _raySourceService;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
// ── 序列化配置 | Serialization options ──
|
// ── 序列化配置 | Serialization options ──
|
||||||
@@ -32,12 +34,15 @@ namespace XplorePlane.Services.Cnc
|
|||||||
|
|
||||||
public CncProgramService(
|
public CncProgramService(
|
||||||
IAppStateService appStateService,
|
IAppStateService appStateService,
|
||||||
|
IRaySourceService raySourceService,
|
||||||
ILoggerService logger)
|
ILoggerService logger)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(appStateService);
|
ArgumentNullException.ThrowIfNull(appStateService);
|
||||||
|
ArgumentNullException.ThrowIfNull(raySourceService);
|
||||||
ArgumentNullException.ThrowIfNull(logger);
|
ArgumentNullException.ThrowIfNull(logger);
|
||||||
|
|
||||||
_appStateService = appStateService;
|
_appStateService = appStateService;
|
||||||
|
_raySourceService = raySourceService;
|
||||||
_logger = logger.ForModule<CncProgramService>();
|
_logger = logger.ForModule<CncProgramService>();
|
||||||
|
|
||||||
_logger.Info("CncProgramService 已初始化 | CncProgramService initialized");
|
_logger.Info("CncProgramService 已初始化 | CncProgramService initialized");
|
||||||
@@ -200,6 +205,32 @@ namespace XplorePlane.Services.Cnc
|
|||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public CncProgram UpdateNode(CncProgram program, int index, CncNode node)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(program);
|
||||||
|
ArgumentNullException.ThrowIfNull(node);
|
||||||
|
|
||||||
|
if (index < 0 || index >= program.Nodes.Count)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index),
|
||||||
|
$"Index out of range: {index}, Count={program.Nodes.Count}");
|
||||||
|
|
||||||
|
var nodes = new List<CncNode>(program.Nodes)
|
||||||
|
{
|
||||||
|
[index] = node with { Index = index }
|
||||||
|
};
|
||||||
|
|
||||||
|
var updated = program with
|
||||||
|
{
|
||||||
|
Nodes = nodes.AsReadOnly(),
|
||||||
|
UpdatedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.Info("Updated node: Index={Index}, Type={NodeType}, Program={ProgramName}",
|
||||||
|
index, node.NodeType, program.Name);
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task SaveAsync(CncProgram program, string filePath)
|
public async Task SaveAsync(CncProgram program, string filePath)
|
||||||
{
|
{
|
||||||
@@ -344,6 +375,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
private ReferencePointNode CreateReferencePointNode(Guid id, int index)
|
private ReferencePointNode CreateReferencePointNode(Guid id, int index)
|
||||||
{
|
{
|
||||||
var motion = _appStateService.MotionState;
|
var motion = _appStateService.MotionState;
|
||||||
|
var raySource = _appStateService.RaySourceState;
|
||||||
return new ReferencePointNode(
|
return new ReferencePointNode(
|
||||||
id, index, $"参考点_{index}",
|
id, index, $"参考点_{index}",
|
||||||
XM: motion.XM,
|
XM: motion.XM,
|
||||||
@@ -351,7 +383,10 @@ namespace XplorePlane.Services.Cnc
|
|||||||
ZT: motion.ZT,
|
ZT: motion.ZT,
|
||||||
ZD: motion.ZD,
|
ZD: motion.ZD,
|
||||||
TiltD: motion.TiltD,
|
TiltD: motion.TiltD,
|
||||||
Dist: motion.Dist);
|
Dist: motion.Dist,
|
||||||
|
IsRayOn: raySource.IsOn,
|
||||||
|
Voltage: raySource.Voltage,
|
||||||
|
Current: TryReadCurrent());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>创建保存节点(含图像)| Create save node with image</summary>
|
/// <summary>创建保存节点(含图像)| Create save node with image</summary>
|
||||||
@@ -382,5 +417,24 @@ namespace XplorePlane.Services.Cnc
|
|||||||
id, index, $"保存位置_{index}",
|
id, index, $"保存位置_{index}",
|
||||||
MotionState: _appStateService.MotionState);
|
MotionState: _appStateService.MotionState);
|
||||||
}
|
}
|
||||||
|
private double TryReadCurrent()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = _raySourceService.ReadCurrent();
|
||||||
|
if (result?.Success == true)
|
||||||
|
{
|
||||||
|
return result.GetFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Warn("Failed to read ray source current, ReferencePoint node will use 0");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Warn("Failed to read ray source current: {Message}", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0d;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,36 +4,28 @@ using XplorePlane.Models;
|
|||||||
namespace XplorePlane.Services.Cnc
|
namespace XplorePlane.Services.Cnc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CNC 程序管理服务接口,负责程序的创建、节点编辑、序列化/反序列化和文件读写
|
/// CNC program management service interface.
|
||||||
/// CNC program management service interface for creation, node editing, serialization and file I/O
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICncProgramService
|
public interface ICncProgramService
|
||||||
{
|
{
|
||||||
/// <summary>创建空的 CNC 程序 | Create an empty CNC program</summary>
|
|
||||||
CncProgram CreateProgram(string name);
|
CncProgram CreateProgram(string name);
|
||||||
|
|
||||||
/// <summary>根据节点类型创建节点(从 IAppStateService 捕获设备状态)| Create a node by type (captures device state from IAppStateService)</summary>
|
|
||||||
CncNode CreateNode(CncNodeType type);
|
CncNode CreateNode(CncNodeType type);
|
||||||
|
|
||||||
/// <summary>在指定索引之后插入节点并重新编号 | Insert a node after the given index and renumber</summary>
|
|
||||||
CncProgram InsertNode(CncProgram program, int afterIndex, CncNode node);
|
CncProgram InsertNode(CncProgram program, int afterIndex, CncNode node);
|
||||||
|
|
||||||
/// <summary>移除指定索引的节点并重新编号 | Remove the node at the given index and renumber</summary>
|
|
||||||
CncProgram RemoveNode(CncProgram program, int index);
|
CncProgram RemoveNode(CncProgram program, int index);
|
||||||
|
|
||||||
/// <summary>将节点从旧索引移动到新索引并重新编号 | Move a node from old index to new index and renumber</summary>
|
|
||||||
CncProgram MoveNode(CncProgram program, int oldIndex, int newIndex);
|
CncProgram MoveNode(CncProgram program, int oldIndex, int newIndex);
|
||||||
|
|
||||||
/// <summary>将 CNC 程序保存到 .xp 文件 | Save CNC program to .xp file</summary>
|
CncProgram UpdateNode(CncProgram program, int index, CncNode node);
|
||||||
|
|
||||||
Task SaveAsync(CncProgram program, string filePath);
|
Task SaveAsync(CncProgram program, string filePath);
|
||||||
|
|
||||||
/// <summary>从 .xp 文件加载 CNC 程序 | Load CNC program from .xp file</summary>
|
|
||||||
Task<CncProgram> LoadAsync(string filePath);
|
Task<CncProgram> LoadAsync(string filePath);
|
||||||
|
|
||||||
/// <summary>将 CNC 程序序列化为 JSON 字符串 | Serialize CNC program to JSON string</summary>
|
|
||||||
string Serialize(CncProgram program);
|
string Serialize(CncProgram program);
|
||||||
|
|
||||||
/// <summary>从 JSON 字符串反序列化 CNC 程序 | Deserialize CNC program from JSON string</summary>
|
|
||||||
CncProgram Deserialize(string json);
|
CncProgram Deserialize(string json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using XplorePlane.Models;
|
||||||
|
|
||||||
|
namespace XplorePlane.Services.InspectionResults
|
||||||
|
{
|
||||||
|
public interface IInspectionResultStore
|
||||||
|
{
|
||||||
|
Task BeginRunAsync(InspectionRunRecord runRecord, InspectionAssetWriteRequest runSourceAsset = null);
|
||||||
|
|
||||||
|
Task AppendNodeResultAsync(
|
||||||
|
InspectionNodeResult nodeResult,
|
||||||
|
IEnumerable<InspectionMetricResult> metrics = null,
|
||||||
|
PipelineExecutionSnapshot pipelineSnapshot = null,
|
||||||
|
IEnumerable<InspectionAssetWriteRequest> assets = null);
|
||||||
|
|
||||||
|
Task CompleteRunAsync(Guid runId, bool? overallPass = null, DateTime? completedAt = null);
|
||||||
|
|
||||||
|
Task<IReadOnlyList<InspectionRunRecord>> QueryRunsAsync(InspectionRunQuery query = null);
|
||||||
|
|
||||||
|
Task<InspectionRunDetail> GetRunDetailAsync(Guid runId);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ using Prism.Commands;
|
|||||||
using Prism.Events;
|
using Prism.Events;
|
||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -18,27 +19,21 @@ using XplorePlane.Services.Cnc;
|
|||||||
namespace XplorePlane.ViewModels.Cnc
|
namespace XplorePlane.ViewModels.Cnc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CNC 编辑器 ViewModel,管理 CNC 程序的节点列表、编辑操作和文件操作
|
/// CNC editor ViewModel that manages the node tree, editing operations and file operations.
|
||||||
/// CNC editor ViewModel that manages the node list, editing operations and file operations
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CncEditorViewModel : BindableBase
|
public class CncEditorViewModel : BindableBase
|
||||||
{
|
{
|
||||||
private readonly ICncProgramService _cncProgramService;
|
private readonly ICncProgramService _cncProgramService;
|
||||||
private readonly IAppStateService _appStateService;
|
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
|
|
||||||
// 当前 CNC 程序 | Current CNC program
|
|
||||||
private CncProgram _currentProgram;
|
private CncProgram _currentProgram;
|
||||||
|
|
||||||
private ObservableCollection<CncNodeViewModel> _nodes;
|
private ObservableCollection<CncNodeViewModel> _nodes;
|
||||||
|
private ObservableCollection<CncNodeViewModel> _treeNodes;
|
||||||
private CncNodeViewModel _selectedNode;
|
private CncNodeViewModel _selectedNode;
|
||||||
private bool _isModified;
|
private bool _isModified;
|
||||||
private string _programName;
|
private string _programName;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数 | Constructor
|
|
||||||
/// </summary>
|
|
||||||
public CncEditorViewModel(
|
public CncEditorViewModel(
|
||||||
ICncProgramService cncProgramService,
|
ICncProgramService cncProgramService,
|
||||||
IAppStateService appStateService,
|
IAppStateService appStateService,
|
||||||
@@ -46,13 +41,13 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
ILoggerService logger)
|
ILoggerService logger)
|
||||||
{
|
{
|
||||||
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
|
_cncProgramService = cncProgramService ?? throw new ArgumentNullException(nameof(cncProgramService));
|
||||||
_appStateService = appStateService ?? throw new ArgumentNullException(nameof(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>();
|
||||||
|
|
||||||
_nodes = new ObservableCollection<CncNodeViewModel>();
|
_nodes = new ObservableCollection<CncNodeViewModel>();
|
||||||
|
_treeNodes = new ObservableCollection<CncNodeViewModel>();
|
||||||
|
|
||||||
// ── 节点插入命令 | Node insertion commands ──
|
|
||||||
InsertReferencePointCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.ReferencePoint));
|
InsertReferencePointCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.ReferencePoint));
|
||||||
InsertSaveNodeWithImageCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.SaveNodeWithImage));
|
InsertSaveNodeWithImageCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.SaveNodeWithImage));
|
||||||
InsertSaveNodeCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.SaveNode));
|
InsertSaveNodeCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.SaveNode));
|
||||||
@@ -63,117 +58,79 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
InsertWaitDelayCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.WaitDelay));
|
InsertWaitDelayCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.WaitDelay));
|
||||||
InsertCompleteProgramCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.CompleteProgram));
|
InsertCompleteProgramCommand = new DelegateCommand(() => ExecuteInsertNode(CncNodeType.CompleteProgram));
|
||||||
|
|
||||||
// ── 节点编辑命令 | Node editing commands ──
|
|
||||||
DeleteNodeCommand = new DelegateCommand(ExecuteDeleteNode, CanExecuteDeleteNode)
|
DeleteNodeCommand = new DelegateCommand(ExecuteDeleteNode, CanExecuteDeleteNode)
|
||||||
.ObservesProperty(() => SelectedNode);
|
.ObservesProperty(() => SelectedNode);
|
||||||
MoveNodeUpCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeUp);
|
MoveNodeUpCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeUp);
|
||||||
MoveNodeDownCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeDown);
|
MoveNodeDownCommand = new DelegateCommand<CncNodeViewModel>(ExecuteMoveNodeDown);
|
||||||
|
|
||||||
// ── 文件操作命令 | File operation commands ──
|
|
||||||
SaveProgramCommand = new DelegateCommand(async () => await ExecuteSaveProgramAsync());
|
SaveProgramCommand = new DelegateCommand(async () => await ExecuteSaveProgramAsync());
|
||||||
LoadProgramCommand = new DelegateCommand(async () => await ExecuteLoadProgramAsync());
|
LoadProgramCommand = new DelegateCommand(async () => await ExecuteLoadProgramAsync());
|
||||||
NewProgramCommand = new DelegateCommand(ExecuteNewProgram);
|
NewProgramCommand = new DelegateCommand(ExecuteNewProgram);
|
||||||
ExportCsvCommand = new DelegateCommand(ExecuteExportCsv);
|
ExportCsvCommand = new DelegateCommand(ExecuteExportCsv);
|
||||||
|
|
||||||
_logger.Info("CncEditorViewModel 已初始化 | CncEditorViewModel initialized");
|
_logger.Info("CncEditorViewModel initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 属性 | Properties ──────────────────────────────────────────
|
|
||||||
|
|
||||||
/// <summary>节点列表 | Node list</summary>
|
|
||||||
public ObservableCollection<CncNodeViewModel> Nodes
|
public ObservableCollection<CncNodeViewModel> Nodes
|
||||||
{
|
{
|
||||||
get => _nodes;
|
get => _nodes;
|
||||||
set => SetProperty(ref _nodes, value);
|
private set => SetProperty(ref _nodes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<CncNodeViewModel> TreeNodes
|
||||||
|
{
|
||||||
|
get => _treeNodes;
|
||||||
|
private set => SetProperty(ref _treeNodes, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>当前选中的节点 | Currently selected node</summary>
|
|
||||||
public CncNodeViewModel SelectedNode
|
public CncNodeViewModel SelectedNode
|
||||||
{
|
{
|
||||||
get => _selectedNode;
|
get => _selectedNode;
|
||||||
set => SetProperty(ref _selectedNode, value);
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedNode, value))
|
||||||
|
{
|
||||||
|
RaisePropertyChanged(nameof(HasSelection));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>程序是否已修改 | Whether the program has been modified</summary>
|
public bool HasSelection => SelectedNode != null;
|
||||||
|
|
||||||
public bool IsModified
|
public bool IsModified
|
||||||
{
|
{
|
||||||
get => _isModified;
|
get => _isModified;
|
||||||
set => SetProperty(ref _isModified, value);
|
set => SetProperty(ref _isModified, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>当前程序名称 | Current program name</summary>
|
|
||||||
public string ProgramName
|
public string ProgramName
|
||||||
{
|
{
|
||||||
get => _programName;
|
get => _programName;
|
||||||
set => SetProperty(ref _programName, value);
|
set => SetProperty(ref _programName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 节点插入命令 | Node insertion commands ──────────────────────
|
|
||||||
|
|
||||||
/// <summary>插入参考点命令 | Insert reference point command</summary>
|
|
||||||
public DelegateCommand InsertReferencePointCommand { get; }
|
public DelegateCommand InsertReferencePointCommand { get; }
|
||||||
|
|
||||||
/// <summary>插入保存节点(含图像)命令 | Insert save node with image command</summary>
|
|
||||||
public DelegateCommand InsertSaveNodeWithImageCommand { get; }
|
public DelegateCommand InsertSaveNodeWithImageCommand { get; }
|
||||||
|
|
||||||
/// <summary>插入保存节点命令 | Insert save node command</summary>
|
|
||||||
public DelegateCommand InsertSaveNodeCommand { get; }
|
public DelegateCommand InsertSaveNodeCommand { get; }
|
||||||
|
|
||||||
/// <summary>插入保存位置命令 | Insert save position command</summary>
|
|
||||||
public DelegateCommand InsertSavePositionCommand { get; }
|
public DelegateCommand InsertSavePositionCommand { get; }
|
||||||
|
|
||||||
/// <summary>插入检测模块命令 | Insert inspection module command</summary>
|
|
||||||
public DelegateCommand InsertInspectionModuleCommand { get; }
|
public DelegateCommand InsertInspectionModuleCommand { get; }
|
||||||
|
|
||||||
/// <summary>插入检测标记命令 | Insert inspection marker command</summary>
|
|
||||||
public DelegateCommand InsertInspectionMarkerCommand { get; }
|
public DelegateCommand InsertInspectionMarkerCommand { get; }
|
||||||
|
|
||||||
/// <summary>插入停顿对话框命令 | Insert pause dialog command</summary>
|
|
||||||
public DelegateCommand InsertPauseDialogCommand { get; }
|
public DelegateCommand InsertPauseDialogCommand { get; }
|
||||||
|
|
||||||
/// <summary>插入等待延时命令 | Insert wait delay command</summary>
|
|
||||||
public DelegateCommand InsertWaitDelayCommand { get; }
|
public DelegateCommand InsertWaitDelayCommand { get; }
|
||||||
|
|
||||||
/// <summary>插入完成程序命令 | Insert complete program command</summary>
|
|
||||||
public DelegateCommand InsertCompleteProgramCommand { get; }
|
public DelegateCommand InsertCompleteProgramCommand { get; }
|
||||||
|
|
||||||
// ── 节点编辑命令 | Node editing commands ────────────────────────
|
|
||||||
|
|
||||||
/// <summary>删除选中节点命令 | Delete selected node command</summary>
|
|
||||||
public DelegateCommand DeleteNodeCommand { get; }
|
public DelegateCommand DeleteNodeCommand { get; }
|
||||||
|
|
||||||
/// <summary>上移节点命令 | Move node up command</summary>
|
|
||||||
public DelegateCommand<CncNodeViewModel> MoveNodeUpCommand { get; }
|
public DelegateCommand<CncNodeViewModel> MoveNodeUpCommand { get; }
|
||||||
|
|
||||||
/// <summary>下移节点命令 | Move node down command</summary>
|
|
||||||
public DelegateCommand<CncNodeViewModel> MoveNodeDownCommand { get; }
|
public DelegateCommand<CncNodeViewModel> MoveNodeDownCommand { get; }
|
||||||
|
|
||||||
// ── 文件操作命令 | File operation commands ──────────────────────
|
|
||||||
|
|
||||||
/// <summary>保存程序命令 | Save program command</summary>
|
|
||||||
public DelegateCommand SaveProgramCommand { get; }
|
public DelegateCommand SaveProgramCommand { get; }
|
||||||
|
|
||||||
/// <summary>加载程序命令 | Load program command</summary>
|
|
||||||
public DelegateCommand LoadProgramCommand { get; }
|
public DelegateCommand LoadProgramCommand { get; }
|
||||||
|
|
||||||
/// <summary>新建程序命令 | New program command</summary>
|
|
||||||
public DelegateCommand NewProgramCommand { get; }
|
public DelegateCommand NewProgramCommand { get; }
|
||||||
|
|
||||||
/// <summary>导出 CSV 命令 | Export CSV command</summary>
|
|
||||||
public DelegateCommand ExportCsvCommand { get; }
|
public DelegateCommand ExportCsvCommand { get; }
|
||||||
|
|
||||||
// ── 命令执行方法 | Command execution methods ────────────────────
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插入指定类型的节点到选中节点之后
|
|
||||||
/// Insert a node of the specified type after the selected node
|
|
||||||
/// </summary>
|
|
||||||
private void ExecuteInsertNode(CncNodeType nodeType)
|
private void ExecuteInsertNode(CncNodeType nodeType)
|
||||||
{
|
{
|
||||||
if (_currentProgram == null)
|
if (_currentProgram == null)
|
||||||
{
|
{
|
||||||
_logger.Warn("无法插入节点:当前无程序 | Cannot insert node: no current program");
|
ExecuteNewProgram();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -183,18 +140,14 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node);
|
_currentProgram = _cncProgramService.InsertNode(_currentProgram, afterIndex, node);
|
||||||
|
|
||||||
OnProgramEdited();
|
OnProgramEdited();
|
||||||
_logger.Info("已插入节点 | Inserted node: Type={NodeType}", nodeType);
|
_logger.Info("Inserted node: Type={NodeType}", nodeType);
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
// 重复插入 CompleteProgram 等业务规则异常 | Business rule exceptions like duplicate CompleteProgram
|
_logger.Warn("Node insertion blocked: {Message}", ex.Message);
|
||||||
_logger.Warn("插入节点被阻止 | Node insertion blocked: {Message}", ex.Message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 删除选中节点 | Delete the selected node
|
|
||||||
/// </summary>
|
|
||||||
private void ExecuteDeleteNode()
|
private void ExecuteDeleteNode()
|
||||||
{
|
{
|
||||||
if (_currentProgram == null || SelectedNode == null)
|
if (_currentProgram == null || SelectedNode == null)
|
||||||
@@ -204,18 +157,14 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
{
|
{
|
||||||
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, SelectedNode.Index);
|
_currentProgram = _cncProgramService.RemoveNode(_currentProgram, SelectedNode.Index);
|
||||||
OnProgramEdited();
|
OnProgramEdited();
|
||||||
_logger.Info("已删除节点 | Deleted node at index: {Index}", SelectedNode.Index);
|
_logger.Info("Deleted node at index: {Index}", SelectedNode.Index);
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException ex)
|
catch (ArgumentOutOfRangeException ex)
|
||||||
{
|
{
|
||||||
_logger.Warn("删除节点失败 | Delete node failed: {Message}", ex.Message);
|
_logger.Warn("Delete node failed: {Message}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 判断是否可以删除节点(至少保留 1 个节点)
|
|
||||||
/// Determines whether delete is allowed (at least 1 node must remain)
|
|
||||||
/// </summary>
|
|
||||||
private bool CanExecuteDeleteNode()
|
private bool CanExecuteDeleteNode()
|
||||||
{
|
{
|
||||||
return SelectedNode != null
|
return SelectedNode != null
|
||||||
@@ -223,9 +172,6 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
&& _currentProgram.Nodes.Count > 1;
|
&& _currentProgram.Nodes.Count > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 上移节点 | Move node up
|
|
||||||
/// </summary>
|
|
||||||
private void ExecuteMoveNodeUp(CncNodeViewModel nodeVm)
|
private void ExecuteMoveNodeUp(CncNodeViewModel nodeVm)
|
||||||
{
|
{
|
||||||
if (_currentProgram == null || nodeVm == null || nodeVm.Index <= 0)
|
if (_currentProgram == null || nodeVm == null || nodeVm.Index <= 0)
|
||||||
@@ -235,17 +181,13 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
{
|
{
|
||||||
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index - 1);
|
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index - 1);
|
||||||
OnProgramEdited();
|
OnProgramEdited();
|
||||||
_logger.Info("已上移节点 | Moved node up: {OldIndex} -> {NewIndex}", nodeVm.Index, nodeVm.Index - 1);
|
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException ex)
|
catch (ArgumentOutOfRangeException ex)
|
||||||
{
|
{
|
||||||
_logger.Warn("上移节点失败 | Move node up failed: {Message}", ex.Message);
|
_logger.Warn("Move node up failed: {Message}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 下移节点 | Move node down
|
|
||||||
/// </summary>
|
|
||||||
private void ExecuteMoveNodeDown(CncNodeViewModel nodeVm)
|
private void ExecuteMoveNodeDown(CncNodeViewModel nodeVm)
|
||||||
{
|
{
|
||||||
if (_currentProgram == null || nodeVm == null || nodeVm.Index >= _currentProgram.Nodes.Count - 1)
|
if (_currentProgram == null || nodeVm == null || nodeVm.Index >= _currentProgram.Nodes.Count - 1)
|
||||||
@@ -255,22 +197,18 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
{
|
{
|
||||||
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index + 1);
|
_currentProgram = _cncProgramService.MoveNode(_currentProgram, nodeVm.Index, nodeVm.Index + 1);
|
||||||
OnProgramEdited();
|
OnProgramEdited();
|
||||||
_logger.Info("已下移节点 | Moved node down: {OldIndex} -> {NewIndex}", nodeVm.Index, nodeVm.Index + 1);
|
|
||||||
}
|
}
|
||||||
catch (ArgumentOutOfRangeException ex)
|
catch (ArgumentOutOfRangeException ex)
|
||||||
{
|
{
|
||||||
_logger.Warn("下移节点失败 | Move node down failed: {Message}", ex.Message);
|
_logger.Warn("Move node down failed: {Message}", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存当前程序到文件 | Save current program to file
|
|
||||||
/// </summary>
|
|
||||||
private async Task ExecuteSaveProgramAsync()
|
private async Task ExecuteSaveProgramAsync()
|
||||||
{
|
{
|
||||||
if (_currentProgram == null)
|
if (_currentProgram == null)
|
||||||
{
|
{
|
||||||
_logger.Warn("无法保存:当前无程序 | Cannot save: no current program");
|
_logger.Warn("Cannot save: no current program");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,17 +227,13 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
|
|
||||||
await _cncProgramService.SaveAsync(_currentProgram, dlg.FileName);
|
await _cncProgramService.SaveAsync(_currentProgram, dlg.FileName);
|
||||||
IsModified = false;
|
IsModified = false;
|
||||||
_logger.Info("程序已保存 | Program saved: {FilePath}", dlg.FileName);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "保存程序失败 | Failed to save program");
|
_logger.Error(ex, "Failed to save program");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从文件加载程序 | Load program from file
|
|
||||||
/// </summary>
|
|
||||||
private async Task ExecuteLoadProgramAsync()
|
private async Task ExecuteLoadProgramAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -318,35 +252,27 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
ProgramName = _currentProgram.Name;
|
ProgramName = _currentProgram.Name;
|
||||||
IsModified = false;
|
IsModified = false;
|
||||||
RefreshNodes();
|
RefreshNodes();
|
||||||
_logger.Info("程序已加载 | Program loaded: {ProgramName}", _currentProgram.Name);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "加载程序失败 | Failed to load program");
|
_logger.Error(ex, "Failed to load program");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建新程序 | Create a new program
|
|
||||||
/// </summary>
|
|
||||||
private void ExecuteNewProgram()
|
private void ExecuteNewProgram()
|
||||||
{
|
{
|
||||||
var name = string.IsNullOrWhiteSpace(ProgramName) ? "新程序" : ProgramName;
|
var name = string.IsNullOrWhiteSpace(ProgramName) ? "NewCncProgram" : ProgramName;
|
||||||
_currentProgram = _cncProgramService.CreateProgram(name);
|
_currentProgram = _cncProgramService.CreateProgram(name);
|
||||||
ProgramName = _currentProgram.Name;
|
ProgramName = _currentProgram.Name;
|
||||||
IsModified = false;
|
IsModified = false;
|
||||||
RefreshNodes();
|
RefreshNodes();
|
||||||
_logger.Info("已创建新程序 | Created new program: {ProgramName}", name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 导出当前程序为 CSV 文件 | Export current program to CSV file
|
|
||||||
/// </summary>
|
|
||||||
private void ExecuteExportCsv()
|
private void ExecuteExportCsv()
|
||||||
{
|
{
|
||||||
if (_currentProgram == null || _currentProgram.Nodes.Count == 0)
|
if (_currentProgram == null || _currentProgram.Nodes.Count == 0)
|
||||||
{
|
{
|
||||||
_logger.Warn("无法导出 CSV:当前无程序或节点为空 | Cannot export CSV: no program or empty nodes");
|
_logger.Warn("Cannot export CSV: no program or empty nodes");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -364,8 +290,7 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
// CSV 表头 | CSV header
|
sb.AppendLine("Index,NodeType,Name,XM,YM,ZT,ZD,TiltD,Dist,Voltage_kV,Current_uA,Power_W,RayOn,DetectorConnected,FrameRate,Resolution,ImageFile,MarkerType,MarkerX,MarkerY,DialogTitle,DialogMessage,DelayMs,Pipeline");
|
||||||
sb.AppendLine("Index,NodeType,Name,XM,YM,ZT,ZD,TiltD,Dist,Voltage_kV,Power_W,RayOn,DetectorConnected,FrameRate,Resolution,ImageFile,MarkerType,MarkerX,MarkerY,DialogTitle,DialogMessage,DelayMs,Pipeline");
|
|
||||||
|
|
||||||
var inv = CultureInfo.InvariantCulture;
|
var inv = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
@@ -373,24 +298,15 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
{
|
{
|
||||||
var row = node switch
|
var row = node switch
|
||||||
{
|
{
|
||||||
ReferencePointNode rp => $"{rp.Index},{rp.NodeType},{Esc(rp.Name)},{rp.XM.ToString(inv)},{rp.YM.ToString(inv)},{rp.ZT.ToString(inv)},{rp.ZD.ToString(inv)},{rp.TiltD.ToString(inv)},{rp.Dist.ToString(inv)},,,,,,,,,,,,,,",
|
ReferencePointNode rp => $"{rp.Index},{rp.NodeType},{Esc(rp.Name)},{rp.XM.ToString(inv)},{rp.YM.ToString(inv)},{rp.ZT.ToString(inv)},{rp.ZD.ToString(inv)},{rp.TiltD.ToString(inv)},{rp.Dist.ToString(inv)},{rp.Voltage.ToString(inv)},{rp.Current.ToString(inv)},,{rp.IsRayOn},,,,,,,,,,",
|
||||||
|
SaveNodeWithImageNode sni => $"{sni.Index},{sni.NodeType},{Esc(sni.Name)},{sni.MotionState.XM.ToString(inv)},{sni.MotionState.YM.ToString(inv)},{sni.MotionState.ZT.ToString(inv)},{sni.MotionState.ZD.ToString(inv)},{sni.MotionState.TiltD.ToString(inv)},{sni.MotionState.Dist.ToString(inv)},{sni.RaySourceState.Voltage.ToString(inv)},,{sni.RaySourceState.Power.ToString(inv)},{sni.RaySourceState.IsOn},{sni.DetectorState.IsConnected},{sni.DetectorState.FrameRate.ToString(inv)},{Esc(sni.DetectorState.Resolution)},{Esc(sni.ImageFileName)},,,,,,",
|
||||||
SaveNodeWithImageNode sni => $"{sni.Index},{sni.NodeType},{Esc(sni.Name)},{sni.MotionState.XM.ToString(inv)},{sni.MotionState.YM.ToString(inv)},{sni.MotionState.ZT.ToString(inv)},{sni.MotionState.ZD.ToString(inv)},{sni.MotionState.TiltD.ToString(inv)},{sni.MotionState.Dist.ToString(inv)},{sni.RaySourceState.Voltage.ToString(inv)},{sni.RaySourceState.Power.ToString(inv)},{sni.RaySourceState.IsOn},{sni.DetectorState.IsConnected},{sni.DetectorState.FrameRate.ToString(inv)},{Esc(sni.DetectorState.Resolution)},{Esc(sni.ImageFileName)},,,,,,",
|
SaveNodeNode sn => $"{sn.Index},{sn.NodeType},{Esc(sn.Name)},{sn.MotionState.XM.ToString(inv)},{sn.MotionState.YM.ToString(inv)},{sn.MotionState.ZT.ToString(inv)},{sn.MotionState.ZD.ToString(inv)},{sn.MotionState.TiltD.ToString(inv)},{sn.MotionState.Dist.ToString(inv)},{sn.RaySourceState.Voltage.ToString(inv)},,{sn.RaySourceState.Power.ToString(inv)},{sn.RaySourceState.IsOn},{sn.DetectorState.IsConnected},{sn.DetectorState.FrameRate.ToString(inv)},{Esc(sn.DetectorState.Resolution)},,,,,,,",
|
||||||
|
|
||||||
SaveNodeNode sn => $"{sn.Index},{sn.NodeType},{Esc(sn.Name)},{sn.MotionState.XM.ToString(inv)},{sn.MotionState.YM.ToString(inv)},{sn.MotionState.ZT.ToString(inv)},{sn.MotionState.ZD.ToString(inv)},{sn.MotionState.TiltD.ToString(inv)},{sn.MotionState.Dist.ToString(inv)},{sn.RaySourceState.Voltage.ToString(inv)},{sn.RaySourceState.Power.ToString(inv)},{sn.RaySourceState.IsOn},{sn.DetectorState.IsConnected},{sn.DetectorState.FrameRate.ToString(inv)},{Esc(sn.DetectorState.Resolution)},,,,,,,",
|
|
||||||
|
|
||||||
SavePositionNode sp => $"{sp.Index},{sp.NodeType},{Esc(sp.Name)},{sp.MotionState.XM.ToString(inv)},{sp.MotionState.YM.ToString(inv)},{sp.MotionState.ZT.ToString(inv)},{sp.MotionState.ZD.ToString(inv)},{sp.MotionState.TiltD.ToString(inv)},{sp.MotionState.Dist.ToString(inv)},,,,,,,,,,,,,,",
|
SavePositionNode sp => $"{sp.Index},{sp.NodeType},{Esc(sp.Name)},{sp.MotionState.XM.ToString(inv)},{sp.MotionState.YM.ToString(inv)},{sp.MotionState.ZT.ToString(inv)},{sp.MotionState.ZD.ToString(inv)},{sp.MotionState.TiltD.ToString(inv)},{sp.MotionState.Dist.ToString(inv)},,,,,,,,,,,,,,",
|
||||||
|
|
||||||
InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}",
|
InspectionModuleNode im => $"{im.Index},{im.NodeType},{Esc(im.Name)},,,,,,,,,,,,,,,,,,,{Esc(im.Pipeline?.Name ?? string.Empty)}",
|
||||||
|
|
||||||
InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,",
|
InspectionMarkerNode mk => $"{mk.Index},{mk.NodeType},{Esc(mk.Name)},,,,,,,,,,,,,,{Esc(mk.MarkerType)},{mk.MarkerX.ToString(inv)},{mk.MarkerY.ToString(inv)},,,",
|
||||||
|
|
||||||
PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
|
PauseDialogNode pd => $"{pd.Index},{pd.NodeType},{Esc(pd.Name)},,,,,,,,,,,,,,,,,{Esc(pd.DialogTitle)},{Esc(pd.DialogMessage)},,",
|
||||||
|
|
||||||
WaitDelayNode wd => $"{wd.Index},{wd.NodeType},{Esc(wd.Name)},,,,,,,,,,,,,,,,,,,{wd.DelayMilliseconds},",
|
WaitDelayNode wd => $"{wd.Index},{wd.NodeType},{Esc(wd.Name)},,,,,,,,,,,,,,,,,,,{wd.DelayMilliseconds},",
|
||||||
|
|
||||||
CompleteProgramNode cp => $"{cp.Index},{cp.NodeType},{Esc(cp.Name)},,,,,,,,,,,,,,,,,,,,",
|
CompleteProgramNode cp => $"{cp.Index},{cp.NodeType},{Esc(cp.Name)},,,,,,,,,,,,,,,,,,,,",
|
||||||
|
|
||||||
_ => $"{node.Index},{node.NodeType},{Esc(node.Name)},,,,,,,,,,,,,,,,,,,,"
|
_ => $"{node.Index},{node.NodeType},{Esc(node.Name)},,,,,,,,,,,,,,,,,,,,"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -398,18 +314,13 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllText(dlg.FileName, sb.ToString(), Encoding.UTF8);
|
File.WriteAllText(dlg.FileName, sb.ToString(), Encoding.UTF8);
|
||||||
_logger.Info("CSV 已导出 | CSV exported: {FilePath}, 节点数={Count}", dlg.FileName, _currentProgram.Nodes.Count);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "导出 CSV 失败 | Failed to export CSV");
|
_logger.Error(ex, "Failed to export CSV");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CSV 字段转义:含逗号、引号或换行时用双引号包裹
|
|
||||||
/// Escape CSV field: wrap with double quotes if it contains comma, quote, or newline
|
|
||||||
/// </summary>
|
|
||||||
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;
|
||||||
@@ -418,12 +329,6 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 辅助方法 | Helper methods ───────────────────────────────────
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编辑操作后的统一处理:刷新节点、标记已修改、发布变更事件
|
|
||||||
/// Unified post-edit handling: refresh nodes, mark modified, publish change event
|
|
||||||
/// </summary>
|
|
||||||
private void OnProgramEdited()
|
private void OnProgramEdited()
|
||||||
{
|
{
|
||||||
IsModified = true;
|
IsModified = true;
|
||||||
@@ -431,34 +336,70 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
PublishProgramChanged();
|
PublishProgramChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void HandleNodeModelChanged(CncNodeViewModel nodeVm, CncNode updatedNode)
|
||||||
/// 从 _currentProgram.Nodes 重建 Nodes 集合
|
|
||||||
/// Rebuild the Nodes collection from _currentProgram.Nodes
|
|
||||||
/// </summary>
|
|
||||||
private void RefreshNodes()
|
|
||||||
{
|
{
|
||||||
Nodes.Clear();
|
if (_currentProgram == null)
|
||||||
|
|
||||||
if (_currentProgram?.Nodes == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var node in _currentProgram.Nodes)
|
_currentProgram = _cncProgramService.UpdateNode(_currentProgram, nodeVm.Index, updatedNode);
|
||||||
{
|
IsModified = true;
|
||||||
Nodes.Add(new CncNodeViewModel(node));
|
ProgramName = _currentProgram.Name;
|
||||||
}
|
PublishProgramChanged();
|
||||||
|
}
|
||||||
// 尝试保持选中状态 | Try to preserve selection
|
|
||||||
if (SelectedNode != null)
|
private void RefreshNodes()
|
||||||
{
|
{
|
||||||
var match = Nodes.FirstOrDefault(n => n.Index == SelectedNode.Index);
|
var selectedId = SelectedNode?.Id;
|
||||||
SelectedNode = match ?? Nodes.LastOrDefault();
|
var expansionState = Nodes.ToDictionary(node => node.Id, node => node.IsExpanded);
|
||||||
}
|
|
||||||
|
var flatNodes = new List<CncNodeViewModel>();
|
||||||
|
var rootNodes = new List<CncNodeViewModel>();
|
||||||
|
CncNodeViewModel currentModule = null;
|
||||||
|
|
||||||
|
if (_currentProgram?.Nodes != null)
|
||||||
|
{
|
||||||
|
foreach (var node in _currentProgram.Nodes)
|
||||||
|
{
|
||||||
|
var vm = new CncNodeViewModel(node, HandleNodeModelChanged)
|
||||||
|
{
|
||||||
|
IsExpanded = expansionState.TryGetValue(node.Id, out var isExpanded) ? isExpanded : true
|
||||||
|
};
|
||||||
|
|
||||||
|
flatNodes.Add(vm);
|
||||||
|
|
||||||
|
if (vm.IsInspectionModule)
|
||||||
|
{
|
||||||
|
rootNodes.Add(vm);
|
||||||
|
currentModule = vm;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentModule != null && IsModuleChild(vm.NodeType))
|
||||||
|
{
|
||||||
|
currentModule.Children.Add(vm);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootNodes.Add(vm);
|
||||||
|
currentModule = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Nodes = new ObservableCollection<CncNodeViewModel>(flatNodes);
|
||||||
|
TreeNodes = new ObservableCollection<CncNodeViewModel>(rootNodes);
|
||||||
|
|
||||||
|
SelectedNode = selectedId.HasValue
|
||||||
|
? Nodes.FirstOrDefault(node => node.Id == selectedId.Value) ?? Nodes.LastOrDefault()
|
||||||
|
: Nodes.LastOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsModuleChild(CncNodeType type)
|
||||||
|
{
|
||||||
|
return type is CncNodeType.InspectionMarker
|
||||||
|
or CncNodeType.PauseDialog
|
||||||
|
or CncNodeType.WaitDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 通过 IEventAggregator 发布 CNC 程序变更事件
|
|
||||||
/// Publish CNC program changed event via IEventAggregator
|
|
||||||
/// </summary>
|
|
||||||
private void PublishProgramChanged()
|
private void PublishProgramChanged()
|
||||||
{
|
{
|
||||||
_eventAggregator
|
_eventAggregator
|
||||||
@@ -466,4 +407,4 @@ namespace XplorePlane.ViewModels.Cnc
|
|||||||
.Publish(new CncProgramChangedPayload(ProgramName ?? string.Empty, IsModified));
|
.Publish(new CncProgramChangedPayload(ProgramName ?? string.Empty, IsModified));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,89 +1,543 @@
|
|||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
|
|
||||||
namespace XplorePlane.ViewModels.Cnc
|
namespace XplorePlane.ViewModels.Cnc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CNC 节点 ViewModel,将 CncNode 模型封装为可绑定的 WPF ViewModel
|
/// CNC node ViewModel with editable properties and tree children.
|
||||||
/// CNC node ViewModel that wraps a CncNode model into a bindable WPF ViewModel
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CncNodeViewModel : BindableBase
|
public class CncNodeViewModel : BindableBase
|
||||||
{
|
{
|
||||||
private int _index;
|
private readonly Action<CncNodeViewModel, CncNode> _modelChangedCallback;
|
||||||
private string _name;
|
private CncNode _model;
|
||||||
private CncNodeType _nodeType;
|
|
||||||
private string _icon;
|
private string _icon;
|
||||||
|
private bool _isExpanded = true;
|
||||||
|
|
||||||
/// <summary>
|
public CncNodeViewModel(CncNode model, Action<CncNodeViewModel, CncNode> modelChangedCallback)
|
||||||
/// 构造函数,从 CncNode 模型初始化 ViewModel
|
|
||||||
/// Constructor that initializes the ViewModel from a CncNode model
|
|
||||||
/// </summary>
|
|
||||||
public CncNodeViewModel(CncNode model)
|
|
||||||
{
|
{
|
||||||
Model = model;
|
_model = model ?? throw new ArgumentNullException(nameof(model));
|
||||||
_index = model.Index;
|
_modelChangedCallback = modelChangedCallback ?? throw new ArgumentNullException(nameof(modelChangedCallback));
|
||||||
_name = model.Name;
|
|
||||||
_nodeType = model.NodeType;
|
|
||||||
_icon = GetIconForNodeType(model.NodeType);
|
_icon = GetIconForNodeType(model.NodeType);
|
||||||
|
Children = new ObservableCollection<CncNodeViewModel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>底层 CNC 节点模型(只读)| Underlying CNC node model (read-only)</summary>
|
public ObservableCollection<CncNodeViewModel> Children { get; }
|
||||||
public CncNode Model { get; }
|
|
||||||
|
|
||||||
/// <summary>节点在程序中的索引 | Node index in the program</summary>
|
public CncNode Model => _model;
|
||||||
public int Index
|
|
||||||
{
|
public Guid Id => _model.Id;
|
||||||
get => _index;
|
|
||||||
set => SetProperty(ref _index, value);
|
public int Index => _model.Index;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>节点显示名称 | Node display name</summary>
|
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get => _name;
|
get => _model.Name;
|
||||||
set => SetProperty(ref _name, value);
|
set => UpdateModel(_model with { Name = value ?? string.Empty });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>节点类型 | Node type</summary>
|
public CncNodeType NodeType => _model.NodeType;
|
||||||
public CncNodeType NodeType
|
|
||||||
|
public string NodeTypeDisplay => NodeType.ToString();
|
||||||
|
|
||||||
|
public string Icon
|
||||||
{
|
{
|
||||||
get => _nodeType;
|
get => _icon;
|
||||||
|
private set => SetProperty(ref _icon, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => _isExpanded;
|
||||||
|
set => SetProperty(ref _isExpanded, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasChildren => Children.Count > 0;
|
||||||
|
|
||||||
|
public bool IsReferencePoint => _model is ReferencePointNode;
|
||||||
|
public bool IsSaveNode => _model is SaveNodeNode;
|
||||||
|
public bool IsSaveNodeWithImage => _model is SaveNodeWithImageNode;
|
||||||
|
public bool IsSavePosition => _model is SavePositionNode;
|
||||||
|
public bool IsInspectionModule => _model is InspectionModuleNode;
|
||||||
|
public bool IsInspectionMarker => _model is InspectionMarkerNode;
|
||||||
|
public bool IsPauseDialog => _model is PauseDialogNode;
|
||||||
|
public bool IsWaitDelay => _model is WaitDelayNode;
|
||||||
|
public bool IsCompleteProgram => _model is CompleteProgramNode;
|
||||||
|
public bool IsMotionSnapshotNode => _model is ReferencePointNode or SaveNodeNode or SaveNodeWithImageNode or SavePositionNode;
|
||||||
|
|
||||||
|
public double XM
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
ReferencePointNode rp => rp.XM,
|
||||||
|
SaveNodeNode sn => sn.MotionState.XM,
|
||||||
|
SaveNodeWithImageNode sni => sni.MotionState.XM,
|
||||||
|
SavePositionNode sp => sp.MotionState.XM,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateMotion(value, MotionAxis.XM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double YM
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
ReferencePointNode rp => rp.YM,
|
||||||
|
SaveNodeNode sn => sn.MotionState.YM,
|
||||||
|
SaveNodeWithImageNode sni => sni.MotionState.YM,
|
||||||
|
SavePositionNode sp => sp.MotionState.YM,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateMotion(value, MotionAxis.YM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double ZT
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
ReferencePointNode rp => rp.ZT,
|
||||||
|
SaveNodeNode sn => sn.MotionState.ZT,
|
||||||
|
SaveNodeWithImageNode sni => sni.MotionState.ZT,
|
||||||
|
SavePositionNode sp => sp.MotionState.ZT,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateMotion(value, MotionAxis.ZT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double ZD
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
ReferencePointNode rp => rp.ZD,
|
||||||
|
SaveNodeNode sn => sn.MotionState.ZD,
|
||||||
|
SaveNodeWithImageNode sni => sni.MotionState.ZD,
|
||||||
|
SavePositionNode sp => sp.MotionState.ZD,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateMotion(value, MotionAxis.ZD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double TiltD
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
ReferencePointNode rp => rp.TiltD,
|
||||||
|
SaveNodeNode sn => sn.MotionState.TiltD,
|
||||||
|
SaveNodeWithImageNode sni => sni.MotionState.TiltD,
|
||||||
|
SavePositionNode sp => sp.MotionState.TiltD,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateMotion(value, MotionAxis.TiltD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double Dist
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
ReferencePointNode rp => rp.Dist,
|
||||||
|
SaveNodeNode sn => sn.MotionState.Dist,
|
||||||
|
SaveNodeWithImageNode sni => sni.MotionState.Dist,
|
||||||
|
SavePositionNode sp => sp.MotionState.Dist,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateMotion(value, MotionAxis.Dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsRayOn
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
ReferencePointNode rp => rp.IsRayOn,
|
||||||
|
SaveNodeNode sn => sn.RaySourceState.IsOn,
|
||||||
|
SaveNodeWithImageNode sni => sni.RaySourceState.IsOn,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
set => UpdateRaySource(isOn: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double Voltage
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
ReferencePointNode rp => rp.Voltage,
|
||||||
|
SaveNodeNode sn => sn.RaySourceState.Voltage,
|
||||||
|
SaveNodeWithImageNode sni => sni.RaySourceState.Voltage,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateRaySource(voltage: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double Current
|
||||||
|
{
|
||||||
|
get => _model is ReferencePointNode rp ? rp.Current : 0d;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref _nodeType, value))
|
if (_model is ReferencePointNode rp)
|
||||||
{
|
{
|
||||||
// 类型变更时自动更新图标 | Auto-update icon when type changes
|
UpdateModel(rp with { Current = value });
|
||||||
Icon = GetIconForNodeType(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>节点图标路径 | Node icon path</summary>
|
public double Power
|
||||||
public string Icon
|
|
||||||
{
|
{
|
||||||
get => _icon;
|
get => _model switch
|
||||||
set => SetProperty(ref _icon, value);
|
{
|
||||||
|
SaveNodeNode sn => sn.RaySourceState.Power,
|
||||||
|
SaveNodeWithImageNode sni => sni.RaySourceState.Power,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateRaySource(power: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DetectorConnected
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
SaveNodeNode sn => sn.DetectorState.IsConnected,
|
||||||
|
SaveNodeWithImageNode sni => sni.DetectorState.IsConnected,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
set => UpdateDetector(isConnected: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DetectorAcquiring
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
SaveNodeNode sn => sn.DetectorState.IsAcquiring,
|
||||||
|
SaveNodeWithImageNode sni => sni.DetectorState.IsAcquiring,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
set => UpdateDetector(isAcquiring: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double FrameRate
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
SaveNodeNode sn => sn.DetectorState.FrameRate,
|
||||||
|
SaveNodeWithImageNode sni => sni.DetectorState.FrameRate,
|
||||||
|
_ => 0d
|
||||||
|
};
|
||||||
|
set => UpdateDetector(frameRate: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Resolution
|
||||||
|
{
|
||||||
|
get => _model switch
|
||||||
|
{
|
||||||
|
SaveNodeNode sn => sn.DetectorState.Resolution,
|
||||||
|
SaveNodeWithImageNode sni => sni.DetectorState.Resolution,
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
set => UpdateDetector(resolution: value ?? string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ImageFileName
|
||||||
|
{
|
||||||
|
get => _model is SaveNodeWithImageNode sni ? sni.ImageFileName : string.Empty;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is SaveNodeWithImageNode sni)
|
||||||
|
{
|
||||||
|
UpdateModel(sni with { ImageFileName = value ?? string.Empty });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PipelineName
|
||||||
|
{
|
||||||
|
get => _model is InspectionModuleNode im ? im.Pipeline?.Name ?? string.Empty : string.Empty;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is InspectionModuleNode im)
|
||||||
|
{
|
||||||
|
var pipeline = im.Pipeline ?? new PipelineModel();
|
||||||
|
pipeline.Name = value ?? string.Empty;
|
||||||
|
UpdateModel(im with { Pipeline = pipeline });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PipelineModel Pipeline
|
||||||
|
{
|
||||||
|
get => _model is InspectionModuleNode im ? im.Pipeline : null;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is InspectionModuleNode im)
|
||||||
|
{
|
||||||
|
UpdateModel(im with { Pipeline = value ?? new PipelineModel() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MarkerType
|
||||||
|
{
|
||||||
|
get => _model is InspectionMarkerNode mk ? mk.MarkerType : string.Empty;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is InspectionMarkerNode mk)
|
||||||
|
{
|
||||||
|
UpdateModel(mk with { MarkerType = value ?? string.Empty });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MarkerX
|
||||||
|
{
|
||||||
|
get => _model is InspectionMarkerNode mk ? mk.MarkerX : 0d;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is InspectionMarkerNode mk)
|
||||||
|
{
|
||||||
|
UpdateModel(mk with { MarkerX = value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MarkerY
|
||||||
|
{
|
||||||
|
get => _model is InspectionMarkerNode mk ? mk.MarkerY : 0d;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is InspectionMarkerNode mk)
|
||||||
|
{
|
||||||
|
UpdateModel(mk with { MarkerY = value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DialogTitle
|
||||||
|
{
|
||||||
|
get => _model is PauseDialogNode pd ? pd.DialogTitle : string.Empty;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is PauseDialogNode pd)
|
||||||
|
{
|
||||||
|
UpdateModel(pd with { DialogTitle = value ?? string.Empty });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DialogMessage
|
||||||
|
{
|
||||||
|
get => _model is PauseDialogNode pd ? pd.DialogMessage : string.Empty;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is PauseDialogNode pd)
|
||||||
|
{
|
||||||
|
UpdateModel(pd with { DialogMessage = value ?? string.Empty });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DelayMilliseconds
|
||||||
|
{
|
||||||
|
get => _model is WaitDelayNode wd ? wd.DelayMilliseconds : 0;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_model is WaitDelayNode wd)
|
||||||
|
{
|
||||||
|
UpdateModel(wd with { DelayMilliseconds = value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplaceModel(CncNode model)
|
||||||
|
{
|
||||||
|
_model = model ?? throw new ArgumentNullException(nameof(model));
|
||||||
|
Icon = GetIconForNodeType(model.NodeType);
|
||||||
|
RaiseAllPropertiesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 根据节点类型返回对应的图标路径
|
|
||||||
/// Returns the icon path for the given node type
|
|
||||||
/// </summary>
|
|
||||||
public static string GetIconForNodeType(CncNodeType nodeType)
|
public static string GetIconForNodeType(CncNodeType nodeType)
|
||||||
{
|
{
|
||||||
return nodeType switch
|
return nodeType switch
|
||||||
{
|
{
|
||||||
CncNodeType.ReferencePoint => "/Resources/Icons/cnc_reference_point.png",
|
CncNodeType.ReferencePoint => "/Assets/Icons/reference.png",
|
||||||
CncNodeType.SaveNodeWithImage => "/Resources/Icons/cnc_save_with_image.png",
|
CncNodeType.SaveNodeWithImage => "/Assets/Icons/saveall.png",
|
||||||
CncNodeType.SaveNode => "/Resources/Icons/cnc_save_node.png",
|
CncNodeType.SaveNode => "/Assets/Icons/save.png",
|
||||||
CncNodeType.SavePosition => "/Resources/Icons/cnc_save_position.png",
|
CncNodeType.SavePosition => "/Assets/Icons/add-pos.png",
|
||||||
CncNodeType.InspectionModule => "/Resources/Icons/cnc_inspection_module.png",
|
CncNodeType.InspectionModule => "/Assets/Icons/Module.png",
|
||||||
CncNodeType.InspectionMarker => "/Resources/Icons/cnc_inspection_marker.png",
|
CncNodeType.InspectionMarker => "/Assets/Icons/mark.png",
|
||||||
CncNodeType.PauseDialog => "/Resources/Icons/cnc_pause_dialog.png",
|
CncNodeType.PauseDialog => "/Assets/Icons/message.png",
|
||||||
CncNodeType.WaitDelay => "/Resources/Icons/cnc_wait_delay.png",
|
CncNodeType.WaitDelay => "/Assets/Icons/wait.png",
|
||||||
CncNodeType.CompleteProgram => "/Resources/Icons/cnc_complete_program.png",
|
CncNodeType.CompleteProgram => "/Assets/Icons/finish.png",
|
||||||
_ => "/Resources/Icons/cnc_default.png",
|
_ => "/Assets/Icons/cnc.png",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateMotion(double value, MotionAxis axis)
|
||||||
|
{
|
||||||
|
switch (_model)
|
||||||
|
{
|
||||||
|
case ReferencePointNode rp:
|
||||||
|
UpdateModel(axis switch
|
||||||
|
{
|
||||||
|
MotionAxis.XM => rp with { XM = value },
|
||||||
|
MotionAxis.YM => rp with { YM = value },
|
||||||
|
MotionAxis.ZT => rp with { ZT = value },
|
||||||
|
MotionAxis.ZD => rp with { ZD = value },
|
||||||
|
MotionAxis.TiltD => rp with { TiltD = value },
|
||||||
|
MotionAxis.Dist => rp with { Dist = value },
|
||||||
|
_ => rp
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SaveNodeNode sn:
|
||||||
|
UpdateModel(sn with { MotionState = UpdateMotionState(sn.MotionState, axis, value) });
|
||||||
|
break;
|
||||||
|
case SaveNodeWithImageNode sni:
|
||||||
|
UpdateModel(sni with { MotionState = UpdateMotionState(sni.MotionState, axis, value) });
|
||||||
|
break;
|
||||||
|
case SavePositionNode sp:
|
||||||
|
UpdateModel(sp with { MotionState = UpdateMotionState(sp.MotionState, axis, value) });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateRaySource(bool? isOn = null, double? voltage = null, double? current = null, double? power = null)
|
||||||
|
{
|
||||||
|
switch (_model)
|
||||||
|
{
|
||||||
|
case ReferencePointNode rp:
|
||||||
|
UpdateModel(rp with
|
||||||
|
{
|
||||||
|
IsRayOn = isOn ?? rp.IsRayOn,
|
||||||
|
Voltage = voltage ?? rp.Voltage,
|
||||||
|
Current = current ?? rp.Current
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SaveNodeNode sn:
|
||||||
|
UpdateModel(sn with
|
||||||
|
{
|
||||||
|
RaySourceState = sn.RaySourceState with
|
||||||
|
{
|
||||||
|
IsOn = isOn ?? sn.RaySourceState.IsOn,
|
||||||
|
Voltage = voltage ?? sn.RaySourceState.Voltage,
|
||||||
|
Power = power ?? sn.RaySourceState.Power
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SaveNodeWithImageNode sni:
|
||||||
|
UpdateModel(sni with
|
||||||
|
{
|
||||||
|
RaySourceState = sni.RaySourceState with
|
||||||
|
{
|
||||||
|
IsOn = isOn ?? sni.RaySourceState.IsOn,
|
||||||
|
Voltage = voltage ?? sni.RaySourceState.Voltage,
|
||||||
|
Power = power ?? sni.RaySourceState.Power
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDetector(bool? isConnected = null, bool? isAcquiring = null, double? frameRate = null, string resolution = null)
|
||||||
|
{
|
||||||
|
switch (_model)
|
||||||
|
{
|
||||||
|
case SaveNodeNode sn:
|
||||||
|
UpdateModel(sn with
|
||||||
|
{
|
||||||
|
DetectorState = sn.DetectorState with
|
||||||
|
{
|
||||||
|
IsConnected = isConnected ?? sn.DetectorState.IsConnected,
|
||||||
|
IsAcquiring = isAcquiring ?? sn.DetectorState.IsAcquiring,
|
||||||
|
FrameRate = frameRate ?? sn.DetectorState.FrameRate,
|
||||||
|
Resolution = resolution ?? sn.DetectorState.Resolution
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case SaveNodeWithImageNode sni:
|
||||||
|
UpdateModel(sni with
|
||||||
|
{
|
||||||
|
DetectorState = sni.DetectorState with
|
||||||
|
{
|
||||||
|
IsConnected = isConnected ?? sni.DetectorState.IsConnected,
|
||||||
|
IsAcquiring = isAcquiring ?? sni.DetectorState.IsAcquiring,
|
||||||
|
FrameRate = frameRate ?? sni.DetectorState.FrameRate,
|
||||||
|
Resolution = resolution ?? sni.DetectorState.Resolution
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MotionState UpdateMotionState(MotionState state, MotionAxis axis, double value)
|
||||||
|
{
|
||||||
|
return axis switch
|
||||||
|
{
|
||||||
|
MotionAxis.XM => state with { XM = value },
|
||||||
|
MotionAxis.YM => state with { YM = value },
|
||||||
|
MotionAxis.ZT => state with { ZT = value },
|
||||||
|
MotionAxis.ZD => state with { ZD = value },
|
||||||
|
MotionAxis.TiltD => state with { TiltD = value },
|
||||||
|
MotionAxis.Dist => state with { Dist = value },
|
||||||
|
_ => state
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateModel(CncNode updatedModel)
|
||||||
|
{
|
||||||
|
_model = updatedModel ?? throw new ArgumentNullException(nameof(updatedModel));
|
||||||
|
RaiseAllPropertiesChanged();
|
||||||
|
_modelChangedCallback(this, updatedModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseAllPropertiesChanged()
|
||||||
|
{
|
||||||
|
RaisePropertyChanged(nameof(Model));
|
||||||
|
RaisePropertyChanged(nameof(Id));
|
||||||
|
RaisePropertyChanged(nameof(Index));
|
||||||
|
RaisePropertyChanged(nameof(Name));
|
||||||
|
RaisePropertyChanged(nameof(NodeType));
|
||||||
|
RaisePropertyChanged(nameof(NodeTypeDisplay));
|
||||||
|
RaisePropertyChanged(nameof(Icon));
|
||||||
|
RaisePropertyChanged(nameof(IsReferencePoint));
|
||||||
|
RaisePropertyChanged(nameof(IsSaveNode));
|
||||||
|
RaisePropertyChanged(nameof(IsSaveNodeWithImage));
|
||||||
|
RaisePropertyChanged(nameof(IsSavePosition));
|
||||||
|
RaisePropertyChanged(nameof(IsInspectionModule));
|
||||||
|
RaisePropertyChanged(nameof(IsInspectionMarker));
|
||||||
|
RaisePropertyChanged(nameof(IsPauseDialog));
|
||||||
|
RaisePropertyChanged(nameof(IsWaitDelay));
|
||||||
|
RaisePropertyChanged(nameof(IsCompleteProgram));
|
||||||
|
RaisePropertyChanged(nameof(IsMotionSnapshotNode));
|
||||||
|
RaisePropertyChanged(nameof(XM));
|
||||||
|
RaisePropertyChanged(nameof(YM));
|
||||||
|
RaisePropertyChanged(nameof(ZT));
|
||||||
|
RaisePropertyChanged(nameof(ZD));
|
||||||
|
RaisePropertyChanged(nameof(TiltD));
|
||||||
|
RaisePropertyChanged(nameof(Dist));
|
||||||
|
RaisePropertyChanged(nameof(IsRayOn));
|
||||||
|
RaisePropertyChanged(nameof(Voltage));
|
||||||
|
RaisePropertyChanged(nameof(Current));
|
||||||
|
RaisePropertyChanged(nameof(Power));
|
||||||
|
RaisePropertyChanged(nameof(DetectorConnected));
|
||||||
|
RaisePropertyChanged(nameof(DetectorAcquiring));
|
||||||
|
RaisePropertyChanged(nameof(FrameRate));
|
||||||
|
RaisePropertyChanged(nameof(Resolution));
|
||||||
|
RaisePropertyChanged(nameof(ImageFileName));
|
||||||
|
RaisePropertyChanged(nameof(Pipeline));
|
||||||
|
RaisePropertyChanged(nameof(PipelineName));
|
||||||
|
RaisePropertyChanged(nameof(MarkerType));
|
||||||
|
RaisePropertyChanged(nameof(MarkerX));
|
||||||
|
RaisePropertyChanged(nameof(MarkerY));
|
||||||
|
RaisePropertyChanged(nameof(DialogTitle));
|
||||||
|
RaisePropertyChanged(nameof(DialogMessage));
|
||||||
|
RaisePropertyChanged(nameof(DelayMilliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum MotionAxis
|
||||||
|
{
|
||||||
|
XM,
|
||||||
|
YM,
|
||||||
|
ZT,
|
||||||
|
ZD,
|
||||||
|
TiltD,
|
||||||
|
Dist
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
using XplorePlane.Events;
|
using XplorePlane.Events;
|
||||||
@@ -16,7 +17,7 @@ using XplorePlane.Services;
|
|||||||
|
|
||||||
namespace XplorePlane.ViewModels
|
namespace XplorePlane.ViewModels
|
||||||
{
|
{
|
||||||
public class PipelineEditorViewModel : BindableBase
|
public class PipelineEditorViewModel : BindableBase, IPipelineEditorHostViewModel
|
||||||
{
|
{
|
||||||
private const int MaxPipelineLength = 20;
|
private const int MaxPipelineLength = 20;
|
||||||
private const int DebounceDelayMs = 300;
|
private const int DebounceDelayMs = 300;
|
||||||
@@ -165,6 +166,15 @@ namespace XplorePlane.ViewModels
|
|||||||
public DelegateCommand<PipelineNodeViewModel> MoveNodeUpCommand { get; }
|
public DelegateCommand<PipelineNodeViewModel> MoveNodeUpCommand { get; }
|
||||||
public DelegateCommand<PipelineNodeViewModel> MoveNodeDownCommand { get; }
|
public DelegateCommand<PipelineNodeViewModel> MoveNodeDownCommand { get; }
|
||||||
|
|
||||||
|
ICommand IPipelineEditorHostViewModel.AddOperatorCommand => AddOperatorCommand;
|
||||||
|
ICommand IPipelineEditorHostViewModel.RemoveOperatorCommand => RemoveOperatorCommand;
|
||||||
|
ICommand IPipelineEditorHostViewModel.MoveNodeUpCommand => MoveNodeUpCommand;
|
||||||
|
ICommand IPipelineEditorHostViewModel.MoveNodeDownCommand => MoveNodeDownCommand;
|
||||||
|
ICommand IPipelineEditorHostViewModel.NewPipelineCommand => NewPipelineCommand;
|
||||||
|
ICommand IPipelineEditorHostViewModel.SavePipelineCommand => SavePipelineCommand;
|
||||||
|
ICommand IPipelineEditorHostViewModel.SaveAsPipelineCommand => SaveAsPipelineCommand;
|
||||||
|
ICommand IPipelineEditorHostViewModel.LoadPipelineCommand => LoadPipelineCommand;
|
||||||
|
|
||||||
// ── Command Implementations ───────────────────────────────────
|
// ── Command Implementations ───────────────────────────────────
|
||||||
|
|
||||||
private bool CanAddOperator(string operatorKey) =>
|
private bool CanAddOperator(string operatorKey) =>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Events;
|
using Prism.Events;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
@@ -10,6 +10,9 @@ using System.IO;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using XplorePlane.Events;
|
using XplorePlane.Events;
|
||||||
|
using XplorePlane.ViewModels.Cnc;
|
||||||
|
using XplorePlane.Views;
|
||||||
|
using XplorePlane.Views.Cnc;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
using XP.Common.PdfViewer.Interfaces;
|
using XP.Common.PdfViewer.Interfaces;
|
||||||
using XP.Hardware.MotionControl.Abstractions;
|
using XP.Hardware.MotionControl.Abstractions;
|
||||||
@@ -18,9 +21,12 @@ namespace XplorePlane.ViewModels
|
|||||||
{
|
{
|
||||||
public class MainViewModel : BindableBase
|
public class MainViewModel : BindableBase
|
||||||
{
|
{
|
||||||
|
private const double CncEditorHostWidth = 710d;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
private readonly IContainerProvider _containerProvider;
|
private readonly IContainerProvider _containerProvider;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly CncEditorViewModel _cncEditorViewModel;
|
||||||
|
private readonly CncPageView _cncPageView;
|
||||||
private string _licenseInfo = "当前时间";
|
private string _licenseInfo = "当前时间";
|
||||||
|
|
||||||
public string LicenseInfo
|
public string LicenseInfo
|
||||||
@@ -49,6 +55,17 @@ namespace XplorePlane.ViewModels
|
|||||||
public DelegateCommand OpenLibraryVersionsCommand { get; }
|
public DelegateCommand OpenLibraryVersionsCommand { get; }
|
||||||
public DelegateCommand OpenUserManualCommand { get; }
|
public DelegateCommand OpenUserManualCommand { get; }
|
||||||
public DelegateCommand OpenCameraSettingsCommand { get; }
|
public DelegateCommand OpenCameraSettingsCommand { get; }
|
||||||
|
public DelegateCommand NewCncProgramCommand { get; }
|
||||||
|
public DelegateCommand SaveCncProgramCommand { get; }
|
||||||
|
public DelegateCommand LoadCncProgramCommand { get; }
|
||||||
|
public DelegateCommand InsertReferencePointCommand { get; }
|
||||||
|
public DelegateCommand InsertSavePositionCommand { get; }
|
||||||
|
public DelegateCommand InsertCompleteProgramCommand { get; }
|
||||||
|
public DelegateCommand InsertInspectionMarkerCommand { get; }
|
||||||
|
public DelegateCommand InsertInspectionModuleCommand { get; }
|
||||||
|
public DelegateCommand InsertSaveNodeCommand { get; }
|
||||||
|
public DelegateCommand InsertPauseDialogCommand { get; }
|
||||||
|
public DelegateCommand InsertWaitDelayCommand { get; }
|
||||||
|
|
||||||
// 硬件命令
|
// 硬件命令
|
||||||
public DelegateCommand AxisResetCommand { get; }
|
public DelegateCommand AxisResetCommand { get; }
|
||||||
@@ -62,6 +79,27 @@ namespace XplorePlane.ViewModels
|
|||||||
public DelegateCommand OpenLanguageSwitcherCommand { get; }
|
public DelegateCommand OpenLanguageSwitcherCommand { get; }
|
||||||
public DelegateCommand OpenRealTimeLogViewerCommand { get; }
|
public DelegateCommand OpenRealTimeLogViewerCommand { get; }
|
||||||
|
|
||||||
|
/// <summary>右侧图像区域内容 | Right-side image panel content</summary>
|
||||||
|
public object ImagePanelContent
|
||||||
|
{
|
||||||
|
get => _imagePanelContent;
|
||||||
|
set => SetProperty(ref _imagePanelContent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>右侧图像区域宽度 | Right-side image panel width</summary>
|
||||||
|
public GridLength ImagePanelWidth
|
||||||
|
{
|
||||||
|
get => _imagePanelWidth;
|
||||||
|
set => SetProperty(ref _imagePanelWidth, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>主视图区宽度 | Main viewport width</summary>
|
||||||
|
public GridLength ViewportPanelWidth
|
||||||
|
{
|
||||||
|
get => _viewportPanelWidth;
|
||||||
|
set => SetProperty(ref _viewportPanelWidth, value);
|
||||||
|
}
|
||||||
|
|
||||||
// 窗口引用(单例窗口防止重复打开)
|
// 窗口引用(单例窗口防止重复打开)
|
||||||
private Window _motionDebugWindow;
|
private Window _motionDebugWindow;
|
||||||
private Window _detectorConfigWindow;
|
private Window _detectorConfigWindow;
|
||||||
@@ -69,12 +107,18 @@ namespace XplorePlane.ViewModels
|
|||||||
private Window _realTimeLogViewerWindow;
|
private Window _realTimeLogViewerWindow;
|
||||||
private Window _toolboxWindow;
|
private Window _toolboxWindow;
|
||||||
private Window _raySourceConfigWindow;
|
private Window _raySourceConfigWindow;
|
||||||
|
private object _imagePanelContent;
|
||||||
|
private GridLength _viewportPanelWidth = new GridLength(1, GridUnitType.Star);
|
||||||
|
private GridLength _imagePanelWidth = new GridLength(320);
|
||||||
|
private bool _isCncEditorMode;
|
||||||
|
|
||||||
public MainViewModel(ILoggerService logger, IContainerProvider containerProvider, IEventAggregator eventAggregator)
|
public MainViewModel(ILoggerService logger, IContainerProvider containerProvider, IEventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_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));
|
||||||
|
_cncEditorViewModel = _containerProvider.Resolve<CncEditorViewModel>();
|
||||||
|
_cncPageView = new CncPageView { DataContext = _cncEditorViewModel };
|
||||||
|
|
||||||
NavigationTree = new ObservableCollection<object>();
|
NavigationTree = new ObservableCollection<object>();
|
||||||
|
|
||||||
@@ -90,12 +134,23 @@ namespace XplorePlane.ViewModels
|
|||||||
OpenImageProcessingCommand = new DelegateCommand(() => ShowWindow(new Views.ImageProcessingWindow(), "图像处理"));
|
OpenImageProcessingCommand = new DelegateCommand(() => ShowWindow(new Views.ImageProcessingWindow(), "图像处理"));
|
||||||
LoadImageCommand = new DelegateCommand(ExecuteLoadImage);
|
LoadImageCommand = new DelegateCommand(ExecuteLoadImage);
|
||||||
OpenPipelineEditorCommand = new DelegateCommand(() => ShowWindow(new Views.PipelineEditorWindow(), "流水线编辑器"));
|
OpenPipelineEditorCommand = new DelegateCommand(() => ShowWindow(new Views.PipelineEditorWindow(), "流水线编辑器"));
|
||||||
OpenCncEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.CncEditorWindow(), "CNC 编辑器"));
|
OpenCncEditorCommand = new DelegateCommand(ExecuteOpenCncEditor);
|
||||||
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编排"));
|
OpenMatrixEditorCommand = new DelegateCommand(() => ShowWindow(new Views.Cnc.MatrixEditorWindow(), "矩阵编排"));
|
||||||
OpenToolboxCommand = new DelegateCommand(ExecuteOpenToolbox);
|
OpenToolboxCommand = new DelegateCommand(ExecuteOpenToolbox);
|
||||||
OpenLibraryVersionsCommand = new DelegateCommand(() => ShowWindow(new Views.LibraryVersionsWindow(), "关于"));
|
OpenLibraryVersionsCommand = new DelegateCommand(() => ShowWindow(new Views.LibraryVersionsWindow(), "关于"));
|
||||||
OpenUserManualCommand = new DelegateCommand(ExecuteOpenUserManual);
|
OpenUserManualCommand = new DelegateCommand(ExecuteOpenUserManual);
|
||||||
OpenCameraSettingsCommand = new DelegateCommand(ExecuteOpenCameraSettings);
|
OpenCameraSettingsCommand = new DelegateCommand(ExecuteOpenCameraSettings);
|
||||||
|
NewCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.NewProgramCommand.Execute()));
|
||||||
|
SaveCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.SaveProgramCommand.Execute()));
|
||||||
|
LoadCncProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.LoadProgramCommand.Execute()));
|
||||||
|
InsertReferencePointCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertReferencePointCommand.Execute()));
|
||||||
|
InsertSavePositionCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertSavePositionCommand.Execute()));
|
||||||
|
InsertCompleteProgramCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertCompleteProgramCommand.Execute()));
|
||||||
|
InsertInspectionMarkerCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertInspectionMarkerCommand.Execute()));
|
||||||
|
InsertInspectionModuleCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertInspectionModuleCommand.Execute()));
|
||||||
|
InsertSaveNodeCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertSaveNodeCommand.Execute()));
|
||||||
|
InsertPauseDialogCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertPauseDialogCommand.Execute()));
|
||||||
|
InsertWaitDelayCommand = new DelegateCommand(() => ExecuteCncEditorAction(vm => vm.InsertWaitDelayCommand.Execute()));
|
||||||
|
|
||||||
// 硬件命令
|
// 硬件命令
|
||||||
AxisResetCommand = new DelegateCommand(ExecuteAxisReset);
|
AxisResetCommand = new DelegateCommand(ExecuteAxisReset);
|
||||||
@@ -109,6 +164,10 @@ namespace XplorePlane.ViewModels
|
|||||||
OpenLanguageSwitcherCommand = new DelegateCommand(ExecuteOpenLanguageSwitcher);
|
OpenLanguageSwitcherCommand = new DelegateCommand(ExecuteOpenLanguageSwitcher);
|
||||||
OpenRealTimeLogViewerCommand = new DelegateCommand(ExecuteOpenRealTimeLogViewer);
|
OpenRealTimeLogViewerCommand = new DelegateCommand(ExecuteOpenRealTimeLogViewer);
|
||||||
|
|
||||||
|
ImagePanelContent = new PipelineEditorView();
|
||||||
|
ViewportPanelWidth = new GridLength(1, GridUnitType.Star);
|
||||||
|
ImagePanelWidth = new GridLength(320);
|
||||||
|
|
||||||
_logger.Info("MainViewModel 已初始化");
|
_logger.Info("MainViewModel 已初始化");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +212,38 @@ namespace XplorePlane.ViewModels
|
|||||||
ShowOrActivate(_toolboxWindow, w => _toolboxWindow = w, () => new Views.OperatorToolboxWindow(), "算子工具箱");
|
ShowOrActivate(_toolboxWindow, w => _toolboxWindow = w, () => new Views.OperatorToolboxWindow(), "算子工具箱");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ExecuteOpenCncEditor()
|
||||||
|
{
|
||||||
|
if (_isCncEditorMode)
|
||||||
|
{
|
||||||
|
ImagePanelContent = new PipelineEditorView();
|
||||||
|
ViewportPanelWidth = new GridLength(1, GridUnitType.Star);
|
||||||
|
ImagePanelWidth = new GridLength(320);
|
||||||
|
_isCncEditorMode = false;
|
||||||
|
_logger.Info("已退出 CNC 编辑模式");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowCncEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteCncEditorAction(Action<CncEditorViewModel> action)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(action);
|
||||||
|
|
||||||
|
ShowCncEditor();
|
||||||
|
action(_cncEditorViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowCncEditor()
|
||||||
|
{
|
||||||
|
ImagePanelContent = _cncPageView;
|
||||||
|
ViewportPanelWidth = new GridLength(1, GridUnitType.Star);
|
||||||
|
ImagePanelWidth = new GridLength(CncEditorHostWidth);
|
||||||
|
_isCncEditorMode = true;
|
||||||
|
_logger.Info("CNC 编辑器已切换到主界面图像区域");
|
||||||
|
}
|
||||||
|
|
||||||
private void ExecuteOpenUserManual()
|
private void ExecuteOpenUserManual()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<Window
|
<Window
|
||||||
x:Class="XplorePlane.Views.Cnc.CncEditorWindow"
|
x:Class="XplorePlane.Views.Cnc.CncEditorWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:cnc="clr-namespace:XplorePlane.Views.Cnc"
|
xmlns:cnc="clr-namespace:XplorePlane.Views.Cnc"
|
||||||
Title="CNC 编辑器"
|
Title="CNC 编辑器"
|
||||||
Width="350"
|
Width="1040"
|
||||||
Height="750"
|
Height="780"
|
||||||
|
MinWidth="960"
|
||||||
|
MinHeight="720"
|
||||||
|
ResizeMode="CanResize"
|
||||||
ShowInTaskbar="False"
|
ShowInTaskbar="False"
|
||||||
WindowStartupLocation="CenterOwner">
|
WindowStartupLocation="CenterOwner">
|
||||||
<cnc:CncPageView />
|
<cnc:CncPageView />
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,297 +1,457 @@
|
|||||||
<!-- CNC 编辑器主页面视图 | CNC editor main page view -->
|
|
||||||
<UserControl
|
<UserControl
|
||||||
x:Class="XplorePlane.Views.Cnc.CncPageView"
|
x:Class="XplorePlane.Views.Cnc.CncPageView"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:XplorePlane.Views.Cnc"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:prism="http://prismlibrary.com/"
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
d:DesignHeight="700"
|
xmlns:views="clr-namespace:XplorePlane.Views"
|
||||||
d:DesignWidth="350"
|
xmlns:vm="clr-namespace:XplorePlane.ViewModels.Cnc"
|
||||||
|
d:DesignHeight="760"
|
||||||
|
d:DesignWidth="702"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<!-- 面板背景和边框颜色 | Panel background and border colors -->
|
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||||
<SolidColorBrush x:Key="PanelBg" Color="White" />
|
<local:NullToVisibilityConverter x:Key="NullToVisibilityConverter" />
|
||||||
<SolidColorBrush x:Key="PanelBorder" Color="#cdcbcb" />
|
|
||||||
<SolidColorBrush x:Key="SeparatorBrush" Color="#E0E0E0" />
|
|
||||||
<SolidColorBrush x:Key="AccentBlue" Color="#E3F0FF" />
|
|
||||||
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
|
|
||||||
|
|
||||||
<!-- 节点列表项样式 | Node list item style -->
|
<SolidColorBrush x:Key="PanelBg" Color="White" />
|
||||||
<Style x:Key="CncNodeItemStyle" TargetType="ListBoxItem">
|
<SolidColorBrush x:Key="PanelBorder" Color="#CDCBCB" />
|
||||||
|
<SolidColorBrush x:Key="SeparatorBrush" Color="#E5E5E5" />
|
||||||
|
<SolidColorBrush x:Key="HeaderBg" Color="#F7F7F7" />
|
||||||
|
<FontFamily x:Key="UiFont">Microsoft YaHei UI</FontFamily>
|
||||||
|
|
||||||
|
<Style x:Key="TreeItemStyle" TargetType="TreeViewItem">
|
||||||
|
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
|
||||||
<Setter Property="Padding" Value="0" />
|
<Setter Property="Padding" Value="0" />
|
||||||
<Setter Property="Margin" Value="0" />
|
<Setter Property="Margin" Value="0" />
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- 工具栏按钮样式 | Toolbar button style -->
|
<Style x:Key="EditorTitle" TargetType="TextBlock">
|
||||||
<Style x:Key="ToolbarBtn" TargetType="Button">
|
<Setter Property="FontFamily" Value="{StaticResource UiFont}" />
|
||||||
<Setter Property="Height" Value="28" />
|
<Setter Property="FontSize" Value="13" />
|
||||||
<Setter Property="Margin" Value="2,0" />
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
<Setter Property="Padding" Value="6,0" />
|
<Setter Property="Margin" Value="0,0,0,8" />
|
||||||
<Setter Property="Background" Value="Transparent" />
|
</Style>
|
||||||
<Setter Property="BorderBrush" Value="#cdcbcb" />
|
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
<Style x:Key="LabelStyle" TargetType="TextBlock">
|
||||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
<Setter Property="FontFamily" Value="{StaticResource UiFont}" />
|
||||||
|
<Setter Property="FontSize" Value="11" />
|
||||||
|
<Setter Property="Foreground" Value="#666666" />
|
||||||
|
<Setter Property="Margin" Value="0,0,0,3" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="EditorBox" TargetType="TextBox">
|
||||||
|
<Setter Property="Height" Value="28" />
|
||||||
|
<Setter Property="Padding" Value="8,3" />
|
||||||
|
<Setter Property="Margin" Value="0,0,0,8" />
|
||||||
|
<Setter Property="BorderBrush" Value="#CFCFCF" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource UiFont}" />
|
||||||
|
<Setter Property="FontSize" Value="11" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="EditorCheck" TargetType="CheckBox">
|
||||||
|
<Setter Property="Margin" Value="0,2,0,8" />
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource UiFont}" />
|
||||||
|
<Setter Property="FontSize" Value="11" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="CompactGroupBox" TargetType="GroupBox">
|
||||||
|
<Setter Property="Margin" Value="0,0,0,10" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="FontFamily" Value="{StaticResource UiFont}" />
|
||||||
<Setter Property="FontSize" Value="11" />
|
<Setter Property="FontSize" Value="11" />
|
||||||
<Setter Property="Cursor" Value="Hand" />
|
|
||||||
</Style>
|
</Style>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Border
|
<Border
|
||||||
|
Width="702"
|
||||||
|
MinWidth="702"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
Background="{StaticResource PanelBg}"
|
Background="{StaticResource PanelBg}"
|
||||||
BorderBrush="{StaticResource PanelBorder}"
|
BorderBrush="{StaticResource PanelBorder}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="4">
|
CornerRadius="4">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<!-- Row 0: 工具栏 | Toolbar -->
|
<ColumnDefinition Width="200" />
|
||||||
<RowDefinition Height="Auto" />
|
<ColumnDefinition Width="1" />
|
||||||
<!-- Row 1: 主内容区(左侧节点列表 + 右侧参数面板)| Main content (left: node list, right: parameter panel) -->
|
<ColumnDefinition Width="250" />
|
||||||
<RowDefinition Height="*" />
|
<ColumnDefinition Width="1" />
|
||||||
</Grid.RowDefinitions>
|
<ColumnDefinition Width="250" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- ═══ 工具栏:节点插入命令 + 文件操作命令 | Toolbar: node insert commands + file operation commands ═══ -->
|
<Grid Grid.Column="0">
|
||||||
<Border
|
<Grid.RowDefinitions>
|
||||||
Grid.Row="0"
|
<RowDefinition Height="Auto" />
|
||||||
Padding="6,4"
|
<RowDefinition Height="*" />
|
||||||
Background="#F5F5F5"
|
</Grid.RowDefinitions>
|
||||||
BorderBrush="{StaticResource PanelBorder}"
|
|
||||||
BorderThickness="0,0,0,1">
|
|
||||||
<WrapPanel Orientation="Horizontal">
|
|
||||||
<!-- 文件操作按钮 | File operation buttons -->
|
|
||||||
<Button
|
|
||||||
Command="{Binding NewProgramCommand}"
|
|
||||||
Content="新建"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="新建程序 | New Program" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding SaveProgramCommand}"
|
|
||||||
Content="保存"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="保存程序 | Save Program" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding LoadProgramCommand}"
|
|
||||||
Content="加载"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="加载程序 | Load Program" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding ExportCsvCommand}"
|
|
||||||
Content="导出CSV"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="导出 CSV | Export CSV" />
|
|
||||||
|
|
||||||
<!-- 分隔线 | Separator -->
|
<Border
|
||||||
<Rectangle
|
Grid.Row="0"
|
||||||
Width="1"
|
Padding="10,8"
|
||||||
Height="20"
|
Background="{StaticResource HeaderBg}"
|
||||||
Margin="4,0"
|
BorderBrush="{StaticResource SeparatorBrush}"
|
||||||
Fill="{StaticResource SeparatorBrush}" />
|
BorderThickness="0,0,0,1">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
FontFamily="{StaticResource UiFont}"
|
||||||
|
FontSize="13"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{Binding ProgramName, TargetNullValue=CNC编辑}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,3,0,0"
|
||||||
|
FontFamily="{StaticResource UiFont}"
|
||||||
|
FontSize="10"
|
||||||
|
Foreground="#666666"
|
||||||
|
Text="模块节点下会自动显示标记、等待、消息等子节点。"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- 节点插入按钮(9 种节点类型)| Node insert buttons (9 node types) -->
|
<TreeView
|
||||||
<Button
|
x:Name="CncTreeView"
|
||||||
Command="{Binding InsertReferencePointCommand}"
|
Grid.Row="1"
|
||||||
Content="参考点"
|
Padding="4,6"
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="插入参考点 | Insert Reference Point" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding InsertSaveNodeWithImageCommand}"
|
|
||||||
Content="保存+图"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="保存节点并保存图片 | Save Node With Image" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding InsertSaveNodeCommand}"
|
|
||||||
Content="保存节点"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="仅保存节点 | Save Node" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding InsertSavePositionCommand}"
|
|
||||||
Content="保存位置"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="保存位置 | Save Position" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding InsertInspectionModuleCommand}"
|
|
||||||
Content="检测模块"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="插入检测模块 | Insert Inspection Module" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding InsertInspectionMarkerCommand}"
|
|
||||||
Content="检测标记"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="插入检测标记 | Insert Inspection Marker" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding InsertPauseDialogCommand}"
|
|
||||||
Content="停顿"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="插入停顿对话框 | Insert Pause Dialog" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding InsertWaitDelayCommand}"
|
|
||||||
Content="延时"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="设置等待延时 | Insert Wait Delay" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding InsertCompleteProgramCommand}"
|
|
||||||
Content="完成"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="完成程序 | Complete Program" />
|
|
||||||
</WrapPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- ═══ 主内容区:左侧节点列表 + 右侧参数面板 | Main content: left node list + right parameter panel ═══ -->
|
|
||||||
<Grid Grid.Row="1">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<!-- 左侧:节点列表 | Left: node list -->
|
|
||||||
<ColumnDefinition Width="3*" MinWidth="150" />
|
|
||||||
<!-- 分隔线 | Splitter -->
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<!-- 右侧:参数面板 | Right: parameter panel -->
|
|
||||||
<ColumnDefinition Width="2*" MinWidth="150" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<!-- ── 左侧:CNC 节点列表 | Left: CNC node list ── -->
|
|
||||||
<ListBox
|
|
||||||
x:Name="CncNodeListBox"
|
|
||||||
Grid.Column="0"
|
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
ItemContainerStyle="{StaticResource CncNodeItemStyle}"
|
ItemsSource="{Binding TreeNodes}"
|
||||||
ItemsSource="{Binding Nodes}"
|
SelectedItemChanged="CncTreeView_SelectedItemChanged">
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
<TreeView.Resources>
|
||||||
SelectedItem="{Binding SelectedNode, Mode=TwoWay}">
|
<HierarchicalDataTemplate DataType="{x:Type vm:CncNodeViewModel}" ItemsSource="{Binding Children}">
|
||||||
<ListBox.ItemTemplate>
|
<Grid x:Name="NodeRoot" MinHeight="34">
|
||||||
<DataTemplate>
|
|
||||||
<Grid x:Name="NodeRoot" MinHeight="40">
|
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<!-- 图标列 | Icon column -->
|
<ColumnDefinition Width="30" />
|
||||||
<ColumnDefinition Width="40" />
|
|
||||||
<!-- 名称列 | Name column -->
|
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<!-- 操作按钮列 | Action buttons column -->
|
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- 节点图标 | Node icon -->
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Width="28"
|
Width="22"
|
||||||
Height="28"
|
Height="22"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Background="#E8F0FE"
|
Background="Transparent"
|
||||||
BorderBrush="#5B9BD5"
|
|
||||||
BorderThickness="1.5"
|
|
||||||
CornerRadius="4">
|
CornerRadius="4">
|
||||||
<Image
|
<Image
|
||||||
Width="20"
|
Width="15"
|
||||||
Height="20"
|
Height="15"
|
||||||
Source="{Binding Icon}"
|
Source="{Binding Icon}"
|
||||||
Stretch="Uniform" />
|
Stretch="Uniform" />
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 节点序号和名称 | Node index and name -->
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="6,0,0,0"
|
Margin="4,0,0,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
FontFamily="Microsoft YaHei UI"
|
FontFamily="{StaticResource UiFont}"
|
||||||
FontSize="11"
|
FontSize="10.5"
|
||||||
Foreground="#888"
|
Foreground="#888888"
|
||||||
Text="{Binding Index, StringFormat='[{0}] '}" />
|
Text="{Binding Index, StringFormat='[{0}] '}" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
FontFamily="Microsoft YaHei UI"
|
FontFamily="{StaticResource UiFont}"
|
||||||
FontSize="12"
|
FontSize="11.5"
|
||||||
Text="{Binding Name}" />
|
Text="{Binding Name}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 悬停操作按钮:上移 / 下移 / 删除 | Hover actions: MoveUp / MoveDown / Delete -->
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
x:Name="NodeActions"
|
x:Name="NodeActions"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
Margin="0,0,4,0"
|
Margin="0,0,2,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Visibility="Collapsed">
|
Visibility="Collapsed">
|
||||||
<Button
|
<Button
|
||||||
Width="22"
|
Width="20"
|
||||||
Height="22"
|
Height="20"
|
||||||
Margin="1,0"
|
Margin="1,0"
|
||||||
Background="Transparent"
|
Background="White"
|
||||||
BorderBrush="#cdcbcb"
|
BorderBrush="#CDCBCB"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Command="{Binding DataContext.MoveNodeUpCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
Command="{Binding DataContext.MoveNodeUpCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Content="▲"
|
Content="上"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
ToolTip="上移 | Move Up" />
|
ToolTip="上移" />
|
||||||
<Button
|
<Button
|
||||||
Width="22"
|
Width="20"
|
||||||
Height="22"
|
Height="20"
|
||||||
Margin="1,0"
|
Margin="1,0"
|
||||||
Background="Transparent"
|
Background="White"
|
||||||
BorderBrush="#cdcbcb"
|
BorderBrush="#CDCBCB"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Command="{Binding DataContext.MoveNodeDownCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
Command="{Binding DataContext.MoveNodeDownCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Content="▼"
|
Content="下"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
ToolTip="下移 | Move Down" />
|
ToolTip="下移" />
|
||||||
<Button
|
<Button
|
||||||
Width="22"
|
Width="20"
|
||||||
Height="22"
|
Height="20"
|
||||||
Margin="1,0"
|
Margin="1,0"
|
||||||
Background="Transparent"
|
Background="White"
|
||||||
BorderBrush="#E05050"
|
BorderBrush="#E05050"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Command="{Binding DataContext.DeleteNodeCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
Command="{Binding DataContext.DeleteNodeCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"
|
||||||
Content="✕"
|
Content="删"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
ToolTip="删除 | Delete" />
|
ToolTip="删除" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<DataTemplate.Triggers>
|
<DataTemplate.Triggers>
|
||||||
<!-- 鼠标悬停时显示操作按钮 | Show action buttons on mouse hover -->
|
|
||||||
<Trigger SourceName="NodeRoot" Property="IsMouseOver" Value="True">
|
<Trigger SourceName="NodeRoot" Property="IsMouseOver" Value="True">
|
||||||
<Setter TargetName="NodeActions" Property="Visibility" Value="Visible" />
|
<Setter TargetName="NodeActions" Property="Visibility" Value="Visible" />
|
||||||
</Trigger>
|
</Trigger>
|
||||||
</DataTemplate.Triggers>
|
</DataTemplate.Triggers>
|
||||||
</DataTemplate>
|
</HierarchicalDataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</TreeView.Resources>
|
||||||
</ListBox>
|
<TreeView.ItemContainerStyle>
|
||||||
|
<Style BasedOn="{StaticResource TreeItemStyle}" TargetType="TreeViewItem" />
|
||||||
|
</TreeView.ItemContainerStyle>
|
||||||
|
</TreeView>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<!-- 垂直分隔线 | Vertical separator -->
|
<Rectangle
|
||||||
<Rectangle
|
Grid.Column="1"
|
||||||
Grid.Column="1"
|
Width="1"
|
||||||
Width="1"
|
Fill="{StaticResource SeparatorBrush}" />
|
||||||
Fill="{StaticResource SeparatorBrush}" />
|
|
||||||
|
|
||||||
<!-- ── 右侧:参数面板(根据节点类型动态渲染)| Right: parameter panel (dynamic rendering by node type) ── -->
|
<ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto">
|
||||||
<ScrollViewer
|
<Grid Margin="10">
|
||||||
Grid.Column="2"
|
<StackPanel Visibility="{Binding SelectedNode, Converter={StaticResource NullToVisibilityConverter}}">
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
<TextBlock Style="{StaticResource EditorTitle}" Text="节点属性" />
|
||||||
VerticalScrollBarVisibility="Auto">
|
|
||||||
<StackPanel Margin="8,6">
|
|
||||||
<TextBlock
|
|
||||||
Margin="0,0,0,4"
|
|
||||||
FontFamily="{StaticResource CsdFont}"
|
|
||||||
FontSize="11"
|
|
||||||
FontWeight="Bold"
|
|
||||||
Foreground="#555"
|
|
||||||
Text="参数配置" />
|
|
||||||
|
|
||||||
<!-- 动态参数内容区域(占位:根据 SelectedNode 类型渲染)| Dynamic parameter content area (placeholder for node-type-based rendering) -->
|
<TextBlock Style="{StaticResource LabelStyle}" Text="名称" />
|
||||||
<ContentControl Content="{Binding SelectedNode}" />
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Name, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
|
||||||
|
<UniformGrid Columns="2" Margin="0,0,0,8">
|
||||||
|
<StackPanel Margin="0,0,6,0">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="索引" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" IsReadOnly="True" Text="{Binding SelectedNode.Index, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="类型" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" IsReadOnly="True" Text="{Binding SelectedNode.NodeTypeDisplay, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</UniformGrid>
|
||||||
|
|
||||||
|
<GroupBox
|
||||||
|
Style="{StaticResource CompactGroupBox}"
|
||||||
|
Header="运动参数"
|
||||||
|
Visibility="{Binding SelectedNode.IsMotionSnapshotNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<UniformGrid Margin="8,8,8,6" Columns="2">
|
||||||
|
<StackPanel Margin="0,0,6,0">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="XM" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.XM, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="YM" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.YM, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="0,0,6,0">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="ZT" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZT, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="ZD" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ZD, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="0,0,6,0">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="TiltD" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.TiltD, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="Dist" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Dist, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
</UniformGrid>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<GroupBox
|
||||||
|
Style="{StaticResource CompactGroupBox}"
|
||||||
|
Header="射线源"
|
||||||
|
Visibility="{Binding SelectedNode.IsReferencePoint, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<StackPanel Margin="8,8,8,6">
|
||||||
|
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="电流 (uA)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Current, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<GroupBox
|
||||||
|
Style="{StaticResource CompactGroupBox}"
|
||||||
|
Header="保存参数"
|
||||||
|
Visibility="{Binding SelectedNode.IsSaveNode, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<StackPanel Margin="8,8,8,6">
|
||||||
|
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="功率 (W)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Power, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器已连接" IsChecked="{Binding SelectedNode.DetectorConnected}" />
|
||||||
|
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器正在采集" IsChecked="{Binding SelectedNode.DetectorAcquiring}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="帧率" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FrameRate, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="分辨率" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Resolution, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<GroupBox
|
||||||
|
Style="{StaticResource CompactGroupBox}"
|
||||||
|
Header="保存参数"
|
||||||
|
Visibility="{Binding SelectedNode.IsSaveNodeWithImage, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<StackPanel Margin="8,8,8,6">
|
||||||
|
<CheckBox Style="{StaticResource EditorCheck}" Content="射线源开启" IsChecked="{Binding SelectedNode.IsRayOn}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="电压 (kV)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Voltage, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="功率 (W)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Power, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器已连接" IsChecked="{Binding SelectedNode.DetectorConnected}" />
|
||||||
|
<CheckBox Style="{StaticResource EditorCheck}" Content="探测器正在采集" IsChecked="{Binding SelectedNode.DetectorAcquiring}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="帧率" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.FrameRate, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="分辨率" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.Resolution, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="图像文件" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.ImageFileName, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<GroupBox
|
||||||
|
Style="{StaticResource CompactGroupBox}"
|
||||||
|
Header="检测模块"
|
||||||
|
Visibility="{Binding SelectedNode.IsInspectionModule, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<StackPanel Margin="8,8,8,6">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="Pipeline 名称" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.PipelineName, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<GroupBox
|
||||||
|
Style="{StaticResource CompactGroupBox}"
|
||||||
|
Header="标记参数"
|
||||||
|
Visibility="{Binding SelectedNode.IsInspectionMarker, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<StackPanel Margin="8,8,8,6">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="标记类型" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerType, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="MarkerX" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerX, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="MarkerY" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.MarkerY, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<GroupBox
|
||||||
|
Style="{StaticResource CompactGroupBox}"
|
||||||
|
Header="消息弹窗"
|
||||||
|
Visibility="{Binding SelectedNode.IsPauseDialog, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<StackPanel Margin="8,8,8,6">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="标题" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DialogTitle, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="消息内容" />
|
||||||
|
<TextBox
|
||||||
|
MinHeight="68"
|
||||||
|
Margin="0,0,0,8"
|
||||||
|
Padding="8,6"
|
||||||
|
AcceptsReturn="True"
|
||||||
|
BorderBrush="#CFCFCF"
|
||||||
|
BorderThickness="1"
|
||||||
|
FontFamily="{StaticResource UiFont}"
|
||||||
|
FontSize="11"
|
||||||
|
Text="{Binding SelectedNode.DialogMessage, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<GroupBox
|
||||||
|
Style="{StaticResource CompactGroupBox}"
|
||||||
|
Header="等待参数"
|
||||||
|
Visibility="{Binding SelectedNode.IsWaitDelay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<StackPanel Margin="10,8,10,6">
|
||||||
|
<TextBlock Style="{StaticResource LabelStyle}" Text="延时 (ms)" />
|
||||||
|
<TextBox Style="{StaticResource EditorBox}" Text="{Binding SelectedNode.DelayMilliseconds, UpdateSourceTrigger=LostFocus}" />
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
|
||||||
|
<Border
|
||||||
|
Padding="12"
|
||||||
|
Background="#FAFAFA"
|
||||||
|
BorderBrush="#E6E6E6"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="6"
|
||||||
|
Visibility="{Binding SelectedNode, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Invert}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
FontFamily="{StaticResource UiFont}"
|
||||||
|
FontSize="13"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="未选择节点"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,6,0,0"
|
||||||
|
FontFamily="{StaticResource UiFont}"
|
||||||
|
FontSize="10"
|
||||||
|
Foreground="#666666"
|
||||||
|
Text="从左侧树中选择一个节点后,这里会显示可编辑的参数。"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Rectangle
|
||||||
|
Grid.Column="3"
|
||||||
|
Width="1"
|
||||||
|
Fill="{StaticResource SeparatorBrush}" />
|
||||||
|
|
||||||
|
<Grid Grid.Column="4">
|
||||||
|
<views:PipelineEditorView
|
||||||
|
x:Name="InspectionModulePipelineEditor"
|
||||||
|
Visibility="{Binding EditorVisibility}" />
|
||||||
|
|
||||||
|
<Border
|
||||||
|
x:Name="InspectionModulePipelineEmptyState"
|
||||||
|
Margin="12"
|
||||||
|
Padding="16"
|
||||||
|
Background="#FAFAFA"
|
||||||
|
BorderBrush="#E6E6E6"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="6"
|
||||||
|
Visibility="{Binding EmptyStateVisibility}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock
|
||||||
|
FontFamily="{StaticResource UiFont}"
|
||||||
|
FontSize="13"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="未选择检测模块"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,6,0,0"
|
||||||
|
FontFamily="{StaticResource UiFont}"
|
||||||
|
FontSize="10"
|
||||||
|
Foreground="#666666"
|
||||||
|
Text="请选择一个检测模块节点后,在这里拖拽算子并配置参数。"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -1,16 +1,94 @@
|
|||||||
|
using Prism.Ioc;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XplorePlane.Services;
|
||||||
|
using XplorePlane.ViewModels.Cnc;
|
||||||
|
|
||||||
namespace XplorePlane.Views.Cnc
|
namespace XplorePlane.Views.Cnc
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// CNC 编辑器主页面视图(MVVM 模式,逻辑在 ViewModel 中)
|
/// CNC editor main page view.
|
||||||
/// CNC editor main page view (MVVM pattern, logic in ViewModel)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CncPageView : UserControl
|
public partial class CncPageView : UserControl
|
||||||
{
|
{
|
||||||
|
private CncInspectionModulePipelineViewModel _inspectionModulePipelineViewModel;
|
||||||
|
|
||||||
public CncPageView()
|
public CncPageView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
Loaded += OnLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
CncEditorViewModel editorViewModel = DataContext as CncEditorViewModel;
|
||||||
|
if (editorViewModel == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
editorViewModel = ContainerLocator.Current?.Resolve<CncEditorViewModel>();
|
||||||
|
DataContext = editorViewModel;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// keep existing DataContext if resolution fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editorViewModel == null || _inspectionModulePipelineViewModel != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var imageProcessingService = ContainerLocator.Current.Resolve<IImageProcessingService>();
|
||||||
|
var persistenceService = ContainerLocator.Current.Resolve<IPipelinePersistenceService>();
|
||||||
|
var logger = ContainerLocator.Current.Resolve<ILoggerService>();
|
||||||
|
|
||||||
|
_inspectionModulePipelineViewModel = new CncInspectionModulePipelineViewModel(
|
||||||
|
editorViewModel,
|
||||||
|
imageProcessingService,
|
||||||
|
persistenceService,
|
||||||
|
logger);
|
||||||
|
|
||||||
|
InspectionModulePipelineEditor.DataContext = _inspectionModulePipelineViewModel;
|
||||||
|
InspectionModulePipelineEmptyState.DataContext = _inspectionModulePipelineViewModel;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// keep page usable even if pipeline editor host setup fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CncTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||||
|
{
|
||||||
|
if (DataContext is CncEditorViewModel viewModel)
|
||||||
|
{
|
||||||
|
viewModel.SelectedNode = e.NewValue as CncNodeViewModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public class NullToVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
var invert = string.Equals(parameter as string, "Invert", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var isVisible = value != null;
|
||||||
|
if (invert)
|
||||||
|
{
|
||||||
|
isVisible = !isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isVisible ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,25 +11,23 @@
|
|||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<SolidColorBrush x:Key="PanelBg" Color="White" />
|
<SolidColorBrush x:Key="PanelBg" Color="White" />
|
||||||
<SolidColorBrush x:Key="PanelBorder" Color="#cdcbcb" />
|
<SolidColorBrush x:Key="PanelBorder" Color="#CDCBCB" />
|
||||||
<SolidColorBrush x:Key="SeparatorBrush" Color="#E0E0E0" />
|
<SolidColorBrush x:Key="SeparatorBrush" Color="#E0E0E0" />
|
||||||
<SolidColorBrush x:Key="AccentBlue" Color="#E3F0FF" />
|
<SolidColorBrush x:Key="AccentBlue" Color="#E3F0FF" />
|
||||||
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
|
<FontFamily x:Key="CsdFont">Microsoft YaHei UI</FontFamily>
|
||||||
|
|
||||||
<!-- 节点项样式 -->
|
|
||||||
<Style x:Key="PipelineNodeItemStyle" TargetType="ListBoxItem">
|
<Style x:Key="PipelineNodeItemStyle" TargetType="ListBoxItem">
|
||||||
<Setter Property="Padding" Value="0" />
|
<Setter Property="Padding" Value="0" />
|
||||||
<Setter Property="Margin" Value="0" />
|
<Setter Property="Margin" Value="0" />
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<!-- 工具栏按钮样式 -->
|
|
||||||
<Style x:Key="ToolbarBtn" TargetType="Button">
|
<Style x:Key="ToolbarBtn" TargetType="Button">
|
||||||
<Setter Property="Width" Value="28" />
|
<Setter Property="Width" Value="52" />
|
||||||
<Setter Property="Height" Value="28" />
|
<Setter Property="Height" Value="28" />
|
||||||
<Setter Property="Margin" Value="2,0" />
|
<Setter Property="Margin" Value="2,0" />
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="BorderBrush" Value="#cdcbcb" />
|
<Setter Property="BorderBrush" Value="#CDCBCB" />
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
<Setter Property="FontFamily" Value="Microsoft YaHei UI" />
|
||||||
<Setter Property="FontSize" Value="11" />
|
<Setter Property="FontSize" Value="11" />
|
||||||
@@ -44,21 +42,13 @@
|
|||||||
CornerRadius="4">
|
CornerRadius="4">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<!-- Row 0: 工具栏 -->
|
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
|
||||||
<!-- Row 2: 流水线节点列表 -->
|
|
||||||
<RowDefinition Height="2*" MinHeight="180" />
|
<RowDefinition Height="2*" MinHeight="180" />
|
||||||
<!-- Row 3: 分隔线 -->
|
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<!-- Row 4: 参数面板 -->
|
|
||||||
<RowDefinition Height="2*" MinHeight="80" />
|
<RowDefinition Height="2*" MinHeight="80" />
|
||||||
<!-- Row 5: 状态栏 -->
|
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- 标题栏:流水线名称 + 设备选择 -->
|
|
||||||
<!-- 工具栏 -->
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Padding="6,4"
|
Padding="6,4"
|
||||||
@@ -76,50 +66,23 @@
|
|||||||
Command="{Binding SavePipelineCommand}"
|
Command="{Binding SavePipelineCommand}"
|
||||||
Content="保存"
|
Content="保存"
|
||||||
Style="{StaticResource ToolbarBtn}"
|
Style="{StaticResource ToolbarBtn}"
|
||||||
ToolTip="保存流水线" />
|
ToolTip="保存当前检测模块流水线" />
|
||||||
<Button
|
<Button
|
||||||
Width="43"
|
Width="60"
|
||||||
Command="{Binding SaveAsPipelineCommand}"
|
Command="{Binding SaveAsPipelineCommand}"
|
||||||
Content="另存为"
|
Content="另存为"
|
||||||
Style="{StaticResource ToolbarBtn}"
|
Style="{StaticResource ToolbarBtn}"
|
||||||
ToolTip="另存为" />
|
ToolTip="导出当前检测模块流水线" />
|
||||||
<Button
|
<Button
|
||||||
Width="43"
|
Width="52"
|
||||||
Command="{Binding LoadPipelineCommand}"
|
Command="{Binding LoadPipelineCommand}"
|
||||||
Content="加载"
|
Content="加载"
|
||||||
Style="{StaticResource ToolbarBtn}"
|
Style="{StaticResource ToolbarBtn}"
|
||||||
ToolTip="加载流水线" />
|
ToolTip="将流水线模板加载到当前检测模块" />
|
||||||
<!--
|
|
||||||
<Button
|
|
||||||
Width="64"
|
|
||||||
Command="{Binding LoadImageCommand}"
|
|
||||||
Content="加载图像"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="加载输入图像" />
|
|
||||||
-->
|
|
||||||
<Button
|
|
||||||
Command="{Binding ExecutePipelineCommand}"
|
|
||||||
Content="▶"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="执行流水线" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding CancelExecutionCommand}"
|
|
||||||
Content="■"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="取消执行" />
|
|
||||||
<Button
|
|
||||||
Command="{Binding DeletePipelineCommand}"
|
|
||||||
Content="🗑"
|
|
||||||
Style="{StaticResource ToolbarBtn}"
|
|
||||||
ToolTip="工具箱" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 流水线节点列表(拖拽目标) -->
|
|
||||||
<ListBox
|
<ListBox
|
||||||
x:Name="PipelineListBox"
|
x:Name="PipelineListBox"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
@@ -138,7 +101,6 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- 连接线列:上半段 + 下半段 -->
|
|
||||||
<Line
|
<Line
|
||||||
x:Name="TopLine"
|
x:Name="TopLine"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
@@ -159,7 +121,6 @@
|
|||||||
Y1="0"
|
Y1="0"
|
||||||
Y2="14" />
|
Y2="14" />
|
||||||
|
|
||||||
<!-- 算子图标 -->
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Width="28"
|
Width="28"
|
||||||
@@ -177,7 +138,6 @@
|
|||||||
Text="{Binding IconPath}" />
|
Text="{Binding IconPath}" />
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 算子名称 -->
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="6,0,0,0"
|
Margin="6,0,0,0"
|
||||||
@@ -186,7 +146,6 @@
|
|||||||
FontSize="12"
|
FontSize="12"
|
||||||
Text="{Binding DisplayName}" />
|
Text="{Binding DisplayName}" />
|
||||||
|
|
||||||
<!-- 操作按钮:上移 / 下移 / 删除(悬停显示) -->
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
x:Name="NodeActions"
|
x:Name="NodeActions"
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
@@ -199,11 +158,11 @@
|
|||||||
Height="22"
|
Height="22"
|
||||||
Margin="1,0"
|
Margin="1,0"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderBrush="#cdcbcb"
|
BorderBrush="#CDCBCB"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Command="{Binding DataContext.MoveNodeUpCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
Command="{Binding DataContext.MoveNodeUpCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Content="▲"
|
Content="上"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
ToolTip="上移" />
|
ToolTip="上移" />
|
||||||
@@ -212,11 +171,11 @@
|
|||||||
Height="22"
|
Height="22"
|
||||||
Margin="1,0"
|
Margin="1,0"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
BorderBrush="#cdcbcb"
|
BorderBrush="#CDCBCB"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Command="{Binding DataContext.MoveNodeDownCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
Command="{Binding DataContext.MoveNodeDownCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Content="▼"
|
Content="下"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
ToolTip="下移" />
|
ToolTip="下移" />
|
||||||
@@ -229,7 +188,7 @@
|
|||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
Command="{Binding DataContext.RemoveOperatorCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
Command="{Binding DataContext.RemoveOperatorCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Content="✕"
|
Content="删"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
FontSize="10"
|
FontSize="10"
|
||||||
ToolTip="删除" />
|
ToolTip="删除" />
|
||||||
@@ -246,13 +205,12 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
<!-- 分隔线 -->
|
|
||||||
<Rectangle
|
<Rectangle
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Height="1"
|
Height="1"
|
||||||
Fill="{StaticResource SeparatorBrush}" />
|
Fill="{StaticResource SeparatorBrush}" />
|
||||||
|
|
||||||
<!-- 参数面板 -->
|
|
||||||
<ScrollViewer
|
<ScrollViewer
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
HorizontalScrollBarVisibility="Disabled"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
@@ -283,14 +241,14 @@
|
|||||||
<TextBox
|
<TextBox
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Padding="4,2"
|
Padding="4,2"
|
||||||
BorderBrush="#cdcbcb"
|
BorderBrush="#CDCBCB"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
FontFamily="Microsoft YaHei UI"
|
FontFamily="Microsoft YaHei UI"
|
||||||
FontSize="11"
|
FontSize="11"
|
||||||
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}">
|
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}">
|
||||||
<TextBox.Style>
|
<TextBox.Style>
|
||||||
<Style TargetType="TextBox">
|
<Style TargetType="TextBox">
|
||||||
<Setter Property="BorderBrush" Value="#cdcbcb" />
|
<Setter Property="BorderBrush" Value="#CDCBCB" />
|
||||||
<Setter Property="Background" Value="White" />
|
<Setter Property="Background" Value="White" />
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding IsValueValid}" Value="False">
|
<DataTrigger Binding="{Binding IsValueValid}" Value="False">
|
||||||
@@ -308,7 +266,6 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
<!-- 状态栏 -->
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="4"
|
Grid.Row="4"
|
||||||
Padding="6,4"
|
Padding="6,4"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
|
using System;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
@@ -23,7 +23,7 @@ namespace XplorePlane.Views
|
|||||||
|
|
||||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not PipelineEditorViewModel)
|
if (DataContext is not IPipelineEditorHostViewModel)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -46,36 +46,35 @@ namespace XplorePlane.Views
|
|||||||
|
|
||||||
private void OnDragOver(object sender, DragEventArgs e)
|
private void OnDragOver(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Data.GetDataPresent(OperatorToolboxView.DragFormat))
|
e.Effects = e.Data.GetDataPresent(OperatorToolboxView.DragFormat)
|
||||||
e.Effects = DragDropEffects.Copy;
|
? DragDropEffects.Copy
|
||||||
else
|
: DragDropEffects.None;
|
||||||
e.Effects = DragDropEffects.None;
|
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnOperatorDropped(object sender, DragEventArgs e)
|
private void OnOperatorDropped(object sender, DragEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not PipelineEditorViewModel vm)
|
if (DataContext is not IPipelineEditorHostViewModel vm)
|
||||||
{
|
{
|
||||||
_logger?.Warn("Drop 事件触发但 DataContext 不是 PipelineEditorViewModel");
|
_logger?.Warn("Drop 事件触发,但 DataContext 不是流水线宿主 ViewModel");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!e.Data.GetDataPresent(OperatorToolboxView.DragFormat))
|
if (!e.Data.GetDataPresent(OperatorToolboxView.DragFormat))
|
||||||
{
|
{
|
||||||
_logger?.Warn("Drop 事件触发但数据中没有 {Format}", OperatorToolboxView.DragFormat);
|
_logger?.Warn("Drop 事件触发,但数据中没有 {Format}", OperatorToolboxView.DragFormat);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var operatorKey = e.Data.GetData(OperatorToolboxView.DragFormat) as string;
|
var operatorKey = e.Data.GetData(OperatorToolboxView.DragFormat) as string;
|
||||||
if (string.IsNullOrEmpty(operatorKey))
|
if (string.IsNullOrWhiteSpace(operatorKey))
|
||||||
{
|
{
|
||||||
_logger?.Warn("Drop 事件触发但 OperatorKey 为空");
|
_logger?.Warn("Drop 事件触发,但 OperatorKey 为空");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger?.Info("算子已放入流水线:{OperatorKey},VM HashCode={Hash},当前节点数(执行前)={Count}",
|
_logger?.Info("算子已放入流水线:{OperatorKey},当前节点数(执行前)={Count}",
|
||||||
operatorKey, vm.GetHashCode(), vm.PipelineNodes.Count);
|
operatorKey, vm.PipelineNodes.Count);
|
||||||
vm.AddOperatorCommand.Execute(operatorKey);
|
vm.AddOperatorCommand.Execute(operatorKey);
|
||||||
_logger?.Info("AddOperator 执行后节点数={Count},PipelineListBox.Items.Count={ItemsCount}",
|
_logger?.Info("AddOperator 执行后节点数={Count},PipelineListBox.Items.Count={ItemsCount}",
|
||||||
vm.PipelineNodes.Count, PipelineListBox.Items.Count);
|
vm.PipelineNodes.Count, PipelineListBox.Items.Count);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<UserControl
|
<UserControl
|
||||||
x:Class="XplorePlane.Views.ImagePanelView"
|
x:Class="XplorePlane.Views.ImagePanelView"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:views="clr-namespace:XplorePlane.Views"
|
|
||||||
d:DesignHeight="400"
|
d:DesignHeight="400"
|
||||||
d:DesignWidth="250"
|
d:DesignWidth="250"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
@@ -14,9 +13,9 @@
|
|||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Border Grid.Row="0" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,0,0,1">
|
<Border Grid.Row="0" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,0,0,1">
|
||||||
<TextBlock Margin="8,4" HorizontalAlignment="Left" VerticalAlignment="Center"
|
<TextBlock Margin="4,2" HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||||
FontWeight="SemiBold" Foreground="#333333" Text="图像" />
|
FontWeight="SemiBold" Foreground="#333333" Text="图像" />
|
||||||
</Border>
|
</Border>
|
||||||
<views:PipelineEditorView Grid.Row="1" />
|
<ContentControl Grid.Row="1" Content="{Binding ImagePanelContent}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
Height="1040"
|
Height="1040"
|
||||||
d:DesignWidth="1580"
|
d:DesignWidth="1580"
|
||||||
Background="#F5F5F5"
|
Background="#F5F5F5"
|
||||||
Icon="pack://application:,,,/XplorerPlane.ico"
|
Icon="pack://application:,,,/XplorePlane;component/XplorerPlane.ico"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
<StackPanel>
|
<StackPanel>
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="新建CNC"
|
telerik:ScreenTip.Title="新建CNC"
|
||||||
Command="{Binding Path=SetStyle.Command}"
|
Command="{Binding NewCncProgramCommand}"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
SmallImage="/Assets/Icons/new-doc.png"
|
SmallImage="/Assets/Icons/new-doc.png"
|
||||||
Text="新建CNC" />
|
Text="新建CNC" />
|
||||||
@@ -82,11 +82,12 @@
|
|||||||
telerik:ScreenTip.Description="保存当前X射线实时图像"
|
telerik:ScreenTip.Description="保存当前X射线实时图像"
|
||||||
telerik:ScreenTip.Title="保存图像"
|
telerik:ScreenTip.Title="保存图像"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding SaveCncProgramCommand}"
|
||||||
SmallImage="/Assets/Icons/save.png"
|
SmallImage="/Assets/Icons/save.png"
|
||||||
Text="保存" />
|
Text="保存" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="另存为"
|
telerik:ScreenTip.Title="另存为"
|
||||||
Command="{Binding OpenFileCommand}"
|
Command="{Binding SaveCncProgramCommand}"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
SmallImage="/Assets/Icons/saveas.png"
|
SmallImage="/Assets/Icons/saveas.png"
|
||||||
Text="另存为" />
|
Text="另存为" />
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
<StackPanel>
|
<StackPanel>
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="加载CNC"
|
telerik:ScreenTip.Title="加载CNC"
|
||||||
Command="{Binding OpenFileCommand}"
|
Command="{Binding LoadCncProgramCommand}"
|
||||||
Size="Large"
|
Size="Large"
|
||||||
SmallImage="/Assets/Icons/open.png"
|
SmallImage="/Assets/Icons/open.png"
|
||||||
Text="加载CNC" />
|
Text="加载CNC" />
|
||||||
@@ -284,32 +285,28 @@
|
|||||||
Command="{Binding OpenCncEditorCommand}"
|
Command="{Binding OpenCncEditorCommand}"
|
||||||
Size="Large"
|
Size="Large"
|
||||||
SmallImage="/Assets/Icons/cnc.png"
|
SmallImage="/Assets/Icons/cnc.png"
|
||||||
Text="CNC 编辑器" />
|
Text="CNC 编辑" />
|
||||||
|
|
||||||
<!-- 矩阵编排入口按钮 -->
|
|
||||||
<telerik:RadRibbonButton
|
|
||||||
telerik:ScreenTip.Description="打开矩阵编排窗口,配置多工件阵列检测方案"
|
|
||||||
telerik:ScreenTip.Title="矩阵编排"
|
|
||||||
Command="{Binding OpenMatrixEditorCommand}"
|
|
||||||
Size="Large"
|
|
||||||
SmallImage="/Assets/Icons/matrix.png"
|
|
||||||
Text="矩阵编排" />
|
|
||||||
|
|
||||||
<!-- CNC 节点快捷工具 -->
|
<!-- CNC 节点快捷工具 -->
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="参考点"
|
telerik:ScreenTip.Title="参考点"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding InsertReferencePointCommand}"
|
||||||
SmallImage="/Assets/Icons/reference.png"
|
SmallImage="/Assets/Icons/reference.png"
|
||||||
Text="参考点" />
|
Text="参考点" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="添加位置"
|
telerik:ScreenTip.Title="添加位置"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding InsertSavePositionCommand}"
|
||||||
SmallImage="/Assets/Icons/add-pos.png"
|
SmallImage="/Assets/Icons/add-pos.png"
|
||||||
Text="添加位置" />
|
Text="添加位置" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="完成"
|
telerik:ScreenTip.Title="完成"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding InsertCompleteProgramCommand}"
|
||||||
SmallImage="/Assets/Icons/finish.png"
|
SmallImage="/Assets/Icons/finish.png"
|
||||||
Text="完成" />
|
Text="完成" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -317,16 +314,19 @@
|
|||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="标记"
|
telerik:ScreenTip.Title="标记"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding InsertInspectionMarkerCommand}"
|
||||||
SmallImage="/Assets/Icons/mark.png"
|
SmallImage="/Assets/Icons/mark.png"
|
||||||
Text="标记" />
|
Text="标记" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="模块"
|
telerik:ScreenTip.Title="模块"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding InsertInspectionModuleCommand}"
|
||||||
SmallImage="/Assets/Icons/Module.png"
|
SmallImage="/Assets/Icons/Module.png"
|
||||||
Text="检测模块" />
|
Text="检测模块" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="全部保存"
|
telerik:ScreenTip.Title="全部保存"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding SaveCncProgramCommand}"
|
||||||
SmallImage="/Assets/Icons/saveall.png"
|
SmallImage="/Assets/Icons/saveall.png"
|
||||||
Text="全部保存" />
|
Text="全部保存" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -334,14 +334,26 @@
|
|||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="消息"
|
telerik:ScreenTip.Title="消息"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding InsertPauseDialogCommand}"
|
||||||
SmallImage="/Assets/Icons/message.png"
|
SmallImage="/Assets/Icons/message.png"
|
||||||
Text="消息弹窗" />
|
Text="消息弹窗" />
|
||||||
<telerik:RadRibbonButton
|
<telerik:RadRibbonButton
|
||||||
telerik:ScreenTip.Title="等待"
|
telerik:ScreenTip.Title="等待"
|
||||||
Size="Medium"
|
Size="Medium"
|
||||||
|
Command="{Binding InsertWaitDelayCommand}"
|
||||||
SmallImage="/Assets/Icons/wait.png"
|
SmallImage="/Assets/Icons/wait.png"
|
||||||
Text="插入等待" />
|
Text="插入等待" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 矩阵编排入口按钮 -->
|
||||||
|
<telerik:RadRibbonButton
|
||||||
|
telerik:ScreenTip.Description="打开矩阵编排窗口,配置多工件阵列检测方案"
|
||||||
|
telerik:ScreenTip.Title="矩阵编排"
|
||||||
|
Command="{Binding OpenMatrixEditorCommand}"
|
||||||
|
Size="Large"
|
||||||
|
SmallImage="/Assets/Icons/matrix.png"
|
||||||
|
Text="矩阵编排" />
|
||||||
|
|
||||||
</telerik:RadRibbonGroup>
|
</telerik:RadRibbonGroup>
|
||||||
|
|
||||||
<telerik:RadRibbonGroup Header="高级模块" IsEnabled="{Binding Path=CellsGroup.IsEnabled}">
|
<telerik:RadRibbonGroup Header="高级模块" IsEnabled="{Binding Path=CellsGroup.IsEnabled}">
|
||||||
@@ -388,7 +400,7 @@
|
|||||||
Size="Large"
|
Size="Large"
|
||||||
SmallImage="/Assets/Icons/spiral.png" />
|
SmallImage="/Assets/Icons/spiral.png" />
|
||||||
</telerik:RadRibbonGroup>
|
</telerik:RadRibbonGroup>
|
||||||
<!--
|
<!--
|
||||||
<telerik:RadRibbonGroup Header="图像处理">
|
<telerik:RadRibbonGroup Header="图像处理">
|
||||||
<telerik:RadRibbonGroup.Variants>
|
<telerik:RadRibbonGroup.Variants>
|
||||||
<telerik:GroupVariant Priority="0" Variant="Large" />
|
<telerik:GroupVariant Priority="0" Variant="Large" />
|
||||||
@@ -458,9 +470,9 @@
|
|||||||
Margin="0">
|
Margin="0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition x:Name="NavColumn" Width="0" />
|
<ColumnDefinition x:Name="NavColumn" Width="0" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="{Binding ViewportPanelWidth}" />
|
||||||
<ColumnDefinition Width="350" />
|
<ColumnDefinition Width="{Binding ImagePanelWidth}" />
|
||||||
<ColumnDefinition Width="350" />
|
<ColumnDefinition Width="300" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- 左侧: 计划导航 (默认隐藏,点击CNC AccountingNumberFormatButton显示) -->
|
<!-- 左侧: 计划导航 (默认隐藏,点击CNC AccountingNumberFormatButton显示) -->
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<!-- 标题栏 -->
|
<!-- 标题栏 -->
|
||||||
<Border Grid.Row="0" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,0,0,1">
|
<Border Grid.Row="0" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,0,0,1">
|
||||||
<TextBlock Margin="8,4" HorizontalAlignment="Left" VerticalAlignment="Center"
|
<TextBlock Margin="4,2" HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||||
FontWeight="SemiBold" Foreground="#333333" Text="实时图像" />
|
FontWeight="SemiBold" Foreground="#333333" Text="实时图像" />
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<!-- 图像信息栏 -->
|
<!-- 图像信息栏 -->
|
||||||
<Border Grid.Row="2" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,1,0,0">
|
<Border Grid.Row="2" Background="#F0F0F0" BorderBrush="#DDDDDD" BorderThickness="0,1,0,0">
|
||||||
<TextBlock Margin="8,2" FontSize="11" Foreground="#666666"
|
<TextBlock Margin="4,2" FontSize="11" Foreground="#666666"
|
||||||
Text="{Binding ImageInfo}" />
|
Text="{Binding ImageInfo}" />
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -146,6 +146,9 @@
|
|||||||
<Link>Libs\Hardware\zh-TW\%(Filename)%(Extension)</Link>
|
<Link>Libs\Hardware\zh-TW\%(Filename)%(Extension)</Link>
|
||||||
</None>
|
</None>
|
||||||
<Compile Remove="Views\Main\MainWindowB.xaml.cs" />
|
<Compile Remove="Views\Main\MainWindowB.xaml.cs" />
|
||||||
|
<Content Include="XplorerPlane.ico">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
|
||||||
<Resource Include="XplorerPlane.ico" />
|
<Resource Include="XplorerPlane.ico" />
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 92 KiB |
Reference in New Issue
Block a user