diff --git a/OpenNest.WinForms.Tests/Forms/PlateExportTests.cs b/OpenNest.WinForms.Tests/Forms/PlateExportTests.cs index ec4d9adc..da318b6f 100644 --- a/OpenNest.WinForms.Tests/Forms/PlateExportTests.cs +++ b/OpenNest.WinForms.Tests/Forms/PlateExportTests.cs @@ -327,6 +327,50 @@ public class PlateExportTests Assert.Same(form.Nest.Plates[0], form.PlateView.Plate); }); + [Fact] + public void ViewInCadWritesDxfAndOpensItThroughTheShell() => RunSta(() => + { + using var form = ShowJob(); + form.PlateManager.LoadAt(1); + + form.OpenCurrentPlate(); + + var path = Assert.Single(form.Launches); + try + { + Assert.Equal(Path.Combine(Path.GetTempPath(), "export job-P2.dxf"), path); + Assert.Same(form.Nest.Plates[1], Assert.Single(form.WriteRequests).Plate); + Assert.True(new FileInfo(path).Length > 0); + Assert.Empty(form.Failures); + Assert.True(EditNestForm.ExternalCadStartInfo(path).UseShellExecute); + } + finally + { + File.Delete(path); + } + }); + + [Fact] + public void ViewInCadReportsLaunchFailureInsteadOfThrowing() => RunSta(() => + { + using var form = ShowJob(); + var error = new System.ComponentModel.Win32Exception("No application is associated with .dxf."); + form.LaunchAction = _ => throw error; + + form.OpenCurrentPlate(); + + var failure = Assert.Single(form.Failures); + File.Delete(failure.Destination); + Assert.Same(error, failure.Error); + }); + + [Theory] + [InlineData("job 1", 3, "job 1-P3.dxf")] + [InlineData("A/B:C*?", 1, "A_B_C__-P1.dxf")] + [InlineData(" ", 2, "Nest-P2.dxf")] + public void ViewInCadFileNameIsAlwaysUsable(string nestName, int plateNumber, string expected) => + Assert.Equal(expected, EditNestForm.ExternalCadFileName(nestName, plateNumber)); + private static TestEditNestForm ShowJob() { var form = new TestEditNestForm(CreateJob()); @@ -409,6 +453,18 @@ public class PlateExportTests internal override void ReportPlateExportFailure(string destination, Exception error) => Failures.Add((destination, error)); + + public List Launches { get; } = []; + public System.Action? LaunchAction { get; set; } + + internal override void LaunchExternalCad(string path) + { + Launches.Add(path); + LaunchAction?.Invoke(path); + } + + internal override void ReportExternalCadFailure(string path, Exception error) => + Failures.Add((path, error)); } private sealed class FlushFailureStream(string path) : FileStream(path, FileMode.Create) diff --git a/OpenNest/Forms/EditNestForm.cs b/OpenNest/Forms/EditNestForm.cs index 8391b267..b3db88e0 100644 --- a/OpenNest/Forms/EditNestForm.cs +++ b/OpenNest/Forms/EditNestForm.cs @@ -1057,13 +1057,44 @@ namespace OpenNest.Forms public void OpenCurrentPlate() { var plate = PlateView.Plate; - var name = string.Format("{0}-P{1}.dxf", Nest.Name, PlateManager.CurrentIndex + 1); + var name = ExternalCadFileName(Nest.Name, PlateManager.CurrentIndex + 1); var path = Path.Combine(Path.GetTempPath(), name); - Dxf.ExportPlate(plate, path); - - Process.Start(path); + try + { + WritePlateExport(plate, path, 1); + LaunchExternalCad(path); + } + catch (Exception ex) + { + ReportExternalCadFailure(path, ex); + } } + /// + /// Temp DXF name for View in CAD; characters a file name cannot hold become underscores. + /// + internal static string ExternalCadFileName(string nestName, int plateNumber) + { + var baseName = string.IsNullOrWhiteSpace(nestName) ? "Nest" : nestName.Trim(); + var invalid = Path.GetInvalidFileNameChars(); + var chars = baseName.Select(c => Array.IndexOf(invalid, c) >= 0 ? '_' : c).ToArray(); + return $"{new string(chars)}-P{plateNumber}.dxf"; + } + + // .NET 8 defaults UseShellExecute to false, which tries to run the DXF as an executable; + // the shell opens it in the program associated with .dxf instead. + internal static ProcessStartInfo ExternalCadStartInfo(string path) => + new ProcessStartInfo(path) { UseShellExecute = true }; + + internal virtual void LaunchExternalCad(string path) + { + using var process = Process.Start(ExternalCadStartInfo(path)); + } + + internal virtual void ReportExternalCadFailure(string path, Exception error) => + MessageBox.Show(this, $"Could not open '{path}' in CAD: {error.Message}", + "View in CAD", MessageBoxButtons.OK, MessageBoxIcon.Error); + public void AddPlate() { using var change = BeginChange("Add Plate");