using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using OpenNest.IO;
using OpenNest.Math;
namespace OpenNest.Desktop;
/// What File > Export writes for one plate.
public enum PlateExportFormat
{
/// The plate's parts as DXF geometry.
Dxf,
/// A picture of the plate view as shown; the front end renders it.
Image,
/// One line per part: source drawing path | X,Y | rotation in degrees.
PlacementText,
/// The plate's parts as SVG in the nest's units.
Svg,
}
///
/// File > Export, Export All and Plate > View in CAD for both desktop apps: the file each
/// plate is suggested to be saved as, the format a chosen file asks for, and the writers. The
/// image of the view is the one format the toolkit draws, so its writer is passed in.
///
public static class PlateExport
{
// Characters Windows refuses in a file name, refused on every platform so a suggested name
// works wherever the file is later copied. The platform's own set is added to them.
private static readonly char[] InvalidFileNameChars = "<>:\"/\\|?*"
.Concat(Enumerable.Range(0, 32).Select(c => (char)c))
.Concat(Path.GetInvalidFileNameChars())
.Distinct()
.ToArray();
///
/// The WinForms save dialog's file types in order: its 1-based filter index picks the format.
///
public static IReadOnlyList DialogFormats { get; } = new[]
{
PlateExportFormat.Dxf,
PlateExportFormat.Image,
PlateExportFormat.PlacementText,
PlateExportFormat.Svg,
};
/// The format for the save dialog's 1-based .
public static PlateExportFormat FromFilterIndex(int filterIndex) =>
filterIndex >= 1 && filterIndex <= DialogFormats.Count
? DialogFormats[filterIndex - 1]
: throw new ArgumentException("Unsupported plate export format.", nameof(filterIndex));
///
/// The format a chosen file's extension asks for (.dxf, .png/.jpg/.jpeg, .txt, .svg, any
/// case), or null for any other extension or none. Bitmap (.bmp) is not offered: no
/// cross-platform encoder writes it.
///
public static PlateExportFormat? FromPath(string path) =>
Path.GetExtension(path ?? "").ToLowerInvariant() switch
{
".dxf" => PlateExportFormat.Dxf,
".png" or ".jpg" or ".jpeg" => PlateExportFormat.Image,
".txt" => PlateExportFormat.PlacementText,
".svg" => PlateExportFormat.Svg,
_ => null,
};
///
/// The name a plate's export is suggested under, without an extension: the nest name and
/// "-P" with the 1-based plate number ("job-P2"). Characters a file name cannot hold on any
/// platform become underscores; a blank nest name is "Nest".
///
public static string SuggestedFileName(string nestName, int plateNumber)
{
var baseName = string.IsNullOrWhiteSpace(nestName) ? "Nest" : nestName.Trim();
var chars = baseName.Select(c => Array.IndexOf(InvalidFileNameChars, c) >= 0 ? '_' : c).ToArray();
return $"{new string(chars)}-P{plateNumber}";
}
/// The DXF that View in CAD writes for the plate and opens: .dxf.
public static string ExternalCadFileName(string nestName, int plateNumber) =>
SuggestedFileName(nestName, plateNumber) + ".dxf";
/// Where View in CAD writes the plate's DXF: the user's temp folder.
public static string ExternalCadPath(string nestName, int plateNumber) =>
Path.Combine(Path.GetTempPath(), ExternalCadFileName(nestName, plateNumber));
///
/// Writes to as
/// . SVG is written in ; the image is drawn by
/// (the shown view, which only the toolkit can draw). The file
/// is written in place: a failure part-way can leave a partial file, as the WinForms export
/// always has. The nest, plate and parts are only read.
///
public static void Write(
Plate plate,
Units units,
string destination,
PlateExportFormat format,
Action writeImage)
{
ArgumentNullException.ThrowIfNull(plate);
ArgumentException.ThrowIfNullOrWhiteSpace(destination);
switch (format)
{
case PlateExportFormat.Dxf:
Dxf.ExportPlate(plate, destination);
break;
case PlateExportFormat.Image:
if (writeImage == null)
throw new NotSupportedException("This view cannot be saved as an image.");
writeImage(destination);
break;
case PlateExportFormat.PlacementText:
using (var writer = new StreamWriter(destination))
WritePlacements(plate, writer);
break;
case PlateExportFormat.Svg:
Svg.ExportPlate(plate, destination, units);
break;
default:
throw new ArgumentException("Unsupported plate export format.", nameof(format));
}
}
///
/// The placement list: per part, in plate order, "source path|X,Y|degrees", where X,Y is the
/// drawing's source origin on the plate (the part location less its rotated source offset)
/// rounded to 8 places. Numbers use the current culture, as the WinForms export always has.
///
public static void WritePlacements(Plate plate, TextWriter writer)
{
ArgumentNullException.ThrowIfNull(plate);
ArgumentNullException.ThrowIfNull(writer);
foreach (var part in plate.Parts)
{
var pt = part.BaseDrawing.Source.Offset.Rotate(part.Rotation);
writer.WriteLine(
"{0}|{1},{2}|{3}",
part.BaseDrawing.Source.Path,
System.Math.Round(part.Location.X - pt.X, 8),
System.Math.Round(part.Location.Y - pt.Y, 8),
Angle.ToDegrees(part.Rotation));
}
}
///
/// Export All: exports plates 0..-1 in order through
/// (which shows the plate, asks for its file and writes it),
/// stopping at the first that is cancelled or fails. Returns how many were exported.
///
public static async Task ExportAllAsync(int plateCount, Func> exportPlate)
{
ArgumentNullException.ThrowIfNull(exportPlate);
var exported = 0;
for (var i = 0; i < plateCount; i++)
{
if (!await exportPlate(i))
break;
exported++;
}
return exported;
}
}