- **Tools:**
  - **PCB Exposure:**
    - (Add) Allow to scale the drawing sizes per gerber file
    - (Add) Allow to invert the drawing polarity per gerber file (#592)
    - (Add) Allow to drag and drop files into "Add files" button and grid header
    - (Improvement) Do not add empty layers when usable to draw or when draw a all black image
    - (Improvement) "Merge all gerbers into one layer" will now draw all gerber into one image instead of perform the Max(of all gerbers pixels), allowing to subtract areas as they are on gerbers
  - **Import layers:** Fix error when trying to insert layers
  - **Export layers images:** Better compression of contours for SVG export, resulting in smooth curves, better visuals and lower file size
- (Add) Outline: Triangulate
- (Remove) Avalonia.Diagnostics dependency in release mode
This commit is contained in:
Tiago Conceição
2022-11-02 23:39:21 +00:00
parent 55cbe02a7b
commit e727a097b3
43 changed files with 870 additions and 302 deletions
+56
View File
@@ -12,9 +12,11 @@ using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using CommunityToolkit.HighPerformance;
using UVtools.Core.EmguCV;
using UVtools.Core.Objects;
@@ -1332,6 +1334,60 @@ public static class EmguExtensions
}
#endregion
#region Other Images Types
/// <summary>
/// From <paramref name="src"/> gets the SVG path's. Tags are not included.
/// </summary>
/// <param name="src"></param>
/// <param name="compression">Compression method for the contours</param>
/// <param name="threshold">True to binary threshold first</param>
/// <returns>Array of path's</returns>
public static IEnumerable<string> GetSvgPath(this Mat src, ChainApproxMethod compression = ChainApproxMethod.ChainApproxSimple, bool threshold = true)
{
var mat = src;
if (threshold)
{
mat = new();
CvInvoke.Threshold(src, mat, 127, byte.MaxValue, ThresholdType.Binary);
}
using var contours = mat.FindContours(out var hierarchy, RetrType.Tree, compression);
var sb = new StringBuilder();
for (int i = 0; i < contours.Size; i++)
{
if (hierarchy[i, EmguContour.HierarchyParent] == -1) // Top hierarchy
{
if (sb.Length > 0)
{
yield return sb.ToString();
sb.Clear();
}
}
else
{
sb.Append(" ");
}
sb.Append($"M {contours[i][0].X} {contours[i][0].Y} L");
for (int x = 1; x < contours[i].Size; x++)
{
sb.Append($" {contours[i][x].X} {contours[i][x].Y}");
}
sb.Append(" Z");
}
if (sb.Length > 0)
{
yield return sb.ToString();
sb.Clear();
}
if (!ReferenceEquals(src, mat)) mat.Dispose();
}
#endregion
#region Utilities methods
/// <summary>
+1 -1
View File
@@ -2854,7 +2854,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
/// <summary>
/// Gets the total time in seconds the display will remain on exposing the layers during the print
/// </summary>
public float DisplayTotalOnTime => (float)Math.Round(this.Sum(layer => layer.ExposureTime), 2);
public float DisplayTotalOnTime => (float)Math.Round(this.Where(layer => layer is not null).Sum(layer => layer.ExposureTime), 2);
/// <summary>
/// Gets the total time formatted in hours, minutes and seconds the display will remain on exposing the layers during the print
+1 -1
View File
@@ -532,7 +532,7 @@ M106 S0
if (line.StartsWith(GCodeKeywordDelaySupportFull) || line.StartsWith(GCodeKeywordDelayModel))
{
var startStr = $"{GCodeKeywordSlice} {layerIndex}";
var stripGcode = gcode[(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length)..].Trim(' ', '\n', '\r', '\t');
var stripGcode = gcode[(gcode.IndexOf(startStr, StringComparison.OrdinalIgnoreCase) + startStr.Length)..].Trim(' ', '\n', '\r', '\t');
float liftHeight = 0;
float liftSpeed = GetBottomOrNormalValue((uint)layerIndex, BottomLiftSpeed, LiftSpeed);
+75 -32
View File
@@ -17,6 +17,7 @@ using Emgu.CV.Structure;
using Emgu.CV.Util;
using UVtools.Core.Extensions;
using UVtools.Core.Gerber.Apertures;
using UVtools.Core.Operations;
namespace UVtools.Core.Gerber;
@@ -49,7 +50,28 @@ public class GerberDocument
public SizeF XYppmm { get; init; }
public MCvScalar PolarityColor => Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor;
/// <summary>
/// Gets the current polarity as <see cref="MCvScalar"/>. <see cref="InversePolarity"/> will affect the return value
/// </summary>
public MCvScalar PolarityColor =>
Polarity == GerberPolarityType.Dark
? !InversePolarity
? EmguExtensions.WhiteColor
: EmguExtensions.BlackColor
: !InversePolarity
? EmguExtensions.BlackColor
: EmguExtensions.WhiteColor;
/// <summary>
/// Gets or sets to inverse the polarity on drawing
/// </summary>
public bool InversePolarity { get; set; }
/// <summary>
/// Gets or sets the scale to apply to each shape drawing size.
/// Positions and vectors aren't affected by this.
/// </summary>
public double SizeScale { get; set; } = 1;
#endregion
@@ -62,14 +84,9 @@ public class GerberDocument
{
}
public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntialiasing = false)
public static void ParseAndDraw(GerberDocument document, string filePath, Mat mat, bool enableAntiAliasing = false)
{
using var file = new StreamReader(filePath);
var document = new GerberDocument
{
SizeMidpointRounding = sizeMidpointRounding,
XYppmm = xyPpmm
};
int FSlength = "%FSLAX46Y46*%".Length;
int MOlength = "%MOMM*%".Length;
@@ -85,7 +102,7 @@ public class GerberDocument
while ((line = file.ReadLine()) is not null)
{
line = line.Trim();
if(line == string.Empty) continue;
if (line == string.Empty) continue;
if (line.StartsWith("M02")) break;
var accumulatedLine = line;
@@ -98,7 +115,7 @@ public class GerberDocument
line = accumulatedLine;
if(currentMacro is not null)
if (currentMacro is not null)
{
currentMacro.ParsePrimitive(line);
if (line[^1] == '%') currentMacro = null;
@@ -107,8 +124,8 @@ public class GerberDocument
if (line.StartsWith("%MO") && line.Length >= MOlength)
{
if(line[3] == 'M' && line[4] == 'M') document.UnitType = GerberUnitType.Millimeter;
else if(line[3] == 'I' && line[4] == 'N') document.UnitType = GerberUnitType.Inch;
if (line[3] == 'M' && line[4] == 'M') document.UnitType = GerberUnitType.Millimeter;
else if (line[3] == 'I' && line[4] == 'N') document.UnitType = GerberUnitType.Inch;
continue;
}
@@ -138,7 +155,7 @@ public class GerberDocument
continue;
}
if (line.StartsWith("%FS") && line.Length >= FSlength)
if (line.StartsWith("%FS") && line.Length >= FSlength)
{
// %FSLAX34Y34*%
// 0123456789
@@ -207,7 +224,7 @@ public class GerberDocument
if (regionPoints.Count > 0)
{
using var vec = new VectorOfPoint(regionPoints.ToArray());
CvInvoke.FillPoly(mat, vec, document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
CvInvoke.FillPoly(mat, vec, document.PolarityColor, enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
}
//CvInvoke.Imshow("G37", mat);
//CvInvoke.WaitKey();
@@ -311,7 +328,7 @@ public class GerberDocument
if (regionPoints.Count > 0)
{
using var vec = new VectorOfPoint(regionPoints.ToArray());
CvInvoke.FillPoly(mat, vec, document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
CvInvoke.FillPoly(mat, vec, document.PolarityColor, enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
}
regionPoints.Clear();
}
@@ -319,7 +336,7 @@ public class GerberDocument
var pt = document.PositionMmToPx(nowX, nowY);
if (regionPoints.Count == 0 || (regionPoints.Count > 0 && regionPoints[^1] != pt)) regionPoints.Add(pt);
}
else if(currentAperture is not null)
else if (currentAperture is not null)
{
if (d == 1)
{
@@ -331,7 +348,7 @@ public class GerberDocument
double yOffset = 0;
var matchI = Regex.Match(line, @"I(-?\d+)");
var matchJ = Regex.Match(line, @"J(-?\d+)");
if(!matchI.Success || !matchJ.Success || matchI.Groups.Count < 2 || matchJ.Groups.Count < 2) continue;
if (!matchI.Success || !matchJ.Success || matchI.Groups.Count < 2 || matchJ.Groups.Count < 2) continue;
var valueStr = document.ZerosSuppressionType switch
@@ -367,7 +384,7 @@ public class GerberDocument
document.SizeMmToPx(Math.Abs(xOffset), Math.Abs(xOffset)),
0, 0, 360.0, document.PolarityColor,
document.SizeMmToPx(circleAperture.Diameter),
enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected
enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected
);
}
else
@@ -381,7 +398,7 @@ public class GerberDocument
enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected
);*/
}
}
else
{
@@ -390,7 +407,7 @@ public class GerberDocument
document.PositionMmToPx(nowX, nowY),
document.PolarityColor,
EmguExtensions.CorrectThickness(document.SizeMmToPx(circleAperture.Diameter)),
enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
/*mat.DrawLineAccurate(PositionMmToPx(currentX, currentY, xyPpmm),
PositionMmToPx(nowX, nowY, xyPpmm),
document.PolarityColor
@@ -408,13 +425,13 @@ public class GerberDocument
//CvInvoke.Imshow("Line", mat);
//CvInvoke.WaitKey();
}
}
}
else if (d == 3)
{
currentAperture.DrawFlashD3(mat, new PointF((float) nowX, (float) nowY),
document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
currentAperture.DrawFlashD3(mat, new PointF((float)nowX, (float)nowY),
document.PolarityColor, enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
//CvInvoke.Imshow("G37", mat);
//CvInvoke.WaitKey();
}
@@ -425,6 +442,32 @@ public class GerberDocument
continue;
}
}
}
public static GerberDocument ParseAndDraw(OperationPCBExposure.PCBExposureFile file, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntiAliasing = false)
{
var document = new GerberDocument
{
SizeMidpointRounding = sizeMidpointRounding,
XYppmm = xyPpmm,
InversePolarity = file.InvertPolarity,
SizeScale = file.SizeScale
};
ParseAndDraw(document, file.FilePath, mat, enableAntiAliasing);
return document;
}
public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntiAliasing = false)
{
var document = new GerberDocument
{
SizeMidpointRounding = sizeMidpointRounding,
XYppmm = xyPpmm
};
ParseAndDraw(document, filePath, mat, enableAntiAliasing);
return document;
}
@@ -463,28 +506,28 @@ public class GerberDocument
=> new((int)Math.Round(atXmm * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atYmm * XYppmm.Height, MidpointRounding.AwayFromZero));
public Size SizeMmToPx(SizeF sizeMm)
=> new((int)Math.Max(1, Math.Round(sizeMm.Width * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)),
(int)Math.Max(1, Math.Round(sizeMm.Height * XYppmm.Height, (MidpointRounding)SizeMidpointRounding)));
=> new((int)Math.Max(1, Math.Round(sizeMm.Width * XYppmm.Width * SizeScale, (MidpointRounding)SizeMidpointRounding)),
(int)Math.Max(1, Math.Round(sizeMm.Height * XYppmm.Height * SizeScale, (MidpointRounding)SizeMidpointRounding)));
public Size SizeMmToPx(double sizeXmm, double sizeYmm)
=> new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)),
(int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height, (MidpointRounding)SizeMidpointRounding)));
=> new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width * SizeScale, (MidpointRounding)SizeMidpointRounding)),
(int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height * SizeScale, (MidpointRounding)SizeMidpointRounding)));
public Size SizeMmToPx(float sizeXmm, float sizeYmm)
=> new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)),
(int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height, (MidpointRounding)SizeMidpointRounding)));
=> new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width * SizeScale, (MidpointRounding)SizeMidpointRounding)),
(int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height * SizeScale, (MidpointRounding)SizeMidpointRounding)));
public int SizeMmToPx(float sizeMm)
=> (int) Math.Max(1, Math.Round(sizeMm * XYppmm.Max(), (MidpointRounding)SizeMidpointRounding));
=> (int) Math.Max(1, Math.Round(sizeMm * XYppmm.Max() * SizeScale, (MidpointRounding)SizeMidpointRounding));
public int SizeMmToPx(double sizeMm)
=> (int)Math.Max(1, Math.Round(sizeMm * XYppmm.Max(), (MidpointRounding)SizeMidpointRounding));
=> (int)Math.Max(1, Math.Round(sizeMm * XYppmm.Max() * SizeScale, (MidpointRounding)SizeMidpointRounding));
public int SizeMmToPxOverride(float sizeMm, float ppmm)
=> (int)Math.Max(1, Math.Round(sizeMm * ppmm, (MidpointRounding)SizeMidpointRounding));
=> (int)Math.Max(1, Math.Round(sizeMm * ppmm * SizeScale, (MidpointRounding)SizeMidpointRounding));
public int SizeMmToPxOverride(double sizeMm, float ppmm)
=> (int)Math.Max(1, Math.Round(sizeMm * ppmm, (MidpointRounding)SizeMidpointRounding));
=> (int)Math.Max(1, Math.Round(sizeMm * ppmm * SizeScale, (MidpointRounding)SizeMidpointRounding));
}
+3 -3
View File
@@ -54,11 +54,11 @@ public static class Helpers
return true;
case "boolean":
if(char.IsDigit(value[0])) attribute.SetValue(obj, !value.Equals("0"));
else attribute.SetValue(obj, value.Equals("True", StringComparison.InvariantCultureIgnoreCase));
else attribute.SetValue(obj, value.Equals("True", StringComparison.OrdinalIgnoreCase));
return true;
case "byte":
if (value.Equals("true", StringComparison.InvariantCultureIgnoreCase)) attribute.SetValue(obj, (byte)1);
else if (value.Equals("false", StringComparison.InvariantCultureIgnoreCase)) attribute.SetValue(obj, byte.MinValue);
if (value.Equals("true", StringComparison.OrdinalIgnoreCase)) attribute.SetValue(obj, (byte)1);
else if (value.Equals("false", StringComparison.OrdinalIgnoreCase)) attribute.SetValue(obj, byte.MinValue);
else attribute.SetValue(obj, value.Convert<byte>());
return true;
case "sbyte":
+8 -1
View File
@@ -1,4 +1,11 @@
using System;
/*
* GNU AFFERO GENERAL PUBLIC LICENSE
* Version 3, 19 November 2007
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
using System;
using UVtools.Core.Layers;
namespace UVtools.Core.Objects;
@@ -0,0 +1,137 @@
/*
* GNU AFFERO GENERAL PUBLIC LICENSE
* Version 3, 19 November 2007
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
using System;
using System.IO;
namespace UVtools.Core.Objects;
[Serializable]
public class GenericFileRepresentation : BindableBase, ICloneable,
IComparable<GenericFileRepresentation>, IEquatable<GenericFileRepresentation>,
IComparable<string>, IEquatable<string>
{
#region Members
private string _filePath = null!;
#endregion
#region Properties
/// <summary>
/// Gets or sets the file path
/// </summary>
public string FilePath
{
get => _filePath;
set
{
if (RaiseAndSetIfChanged(ref _filePath, value)) return;
RaisePropertyChanged(nameof(FileName));
RaisePropertyChanged(nameof(Exists));
RaisePropertyChanged(nameof(FileInfo));
}
}
/// <summary>
/// Gets the file name with extension
/// </summary>
public string FileName => Path.GetFileName(_filePath);
/// <summary>
/// Gets the file name without extension
/// </summary>
public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(_filePath);
/// <summary>
/// Gets the file extension. The returned value includes the period (".")
/// </summary>
public string FileExtension => Path.GetExtension(_filePath);
/// <summary>
/// Gets if the file exists
/// </summary>
public bool Exists => File.Exists(FilePath);
/// <summary>
/// Gets an <see cref="FileInfo"/> instance on the <see cref="FilePath"/>
/// </summary>
public FileInfo FileInfo => new(_filePath);
#endregion
#region Constructor
public GenericFileRepresentation()
{ }
public GenericFileRepresentation(string filePath)
{
_filePath = filePath;
}
#endregion
#region Methods
/// <summary>
/// Checks if the <see cref="FilePath"/> ends with <paramref name="extension"/>
/// </summary>
/// <param name="extension">Extension name</param>
/// <returns>True if found, otherwise false</returns>
public bool IsExtension(string extension)
{
if (string.IsNullOrWhiteSpace(extension)) return false;
if (extension[0] != '.') extension = $".{extension}";
return _filePath.EndsWith(extension, StringComparison.OrdinalIgnoreCase);
}
#endregion
#region Overrides
public override string ToString()
{
return FileName;
}
public object Clone()
{
return MemberwiseClone();
}
public int CompareTo(GenericFileRepresentation? other)
{
if (ReferenceEquals(this, other)) return 0;
if (ReferenceEquals(null, other)) return 1;
return string.Compare(FileName, other.FileName, StringComparison.Ordinal);
}
public int CompareTo(string? other)
{
return FileName.CompareTo(other);
}
public bool Equals(GenericFileRepresentation? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return _filePath == other._filePath;
}
public bool Equals(string? other)
{
return _filePath.Equals(other);
}
public override bool Equals(object? obj)
{
return ReferenceEquals(this, obj) || obj is GenericFileRepresentation other && Equals(other);
}
public override int GetHashCode()
{
return _filePath.GetHashCode();
}
#endregion
}
@@ -522,7 +522,14 @@ public class RangeObservableCollection<T> : ObservableCollection<T>
}
var list = this.ToList();
list.Sort(comparison!);
if (comparison is null)
{
list.Sort();
}
else
{
list.Sort(comparison);
}
ReplaceCollection(list);
}
@@ -209,10 +209,8 @@ public sealed class OperationLayerExportImage : Operation
else
{
// SVG
CvInvoke.Threshold(matRoi, matRoi, 127, byte.MaxValue, ThresholdType.Binary); // Remove AA
using var contours = matRoi.FindContours(out var hierarchy, RetrType.Tree);
var paths = matRoi.GetSvgPath(ChainApproxMethod.ChainApproxTc89Kcos);
using TextWriter tw = new StreamWriter(fileFullPath);
tw.WriteLine("<!--");
@@ -244,16 +242,13 @@ public sealed class OperationLayerExportImage : Operation
tw.WriteLine($"\t<g id=\"layer{layerIndex}\">");
tw.WriteLine($"\t<rect class=\"background\" width=\"{mat.Width}\" height=\"{mat.Height}\"/>");
//
//hierarchy[i][0]: the index of the next contour of the same level
//hierarchy[i][1]: the index of the previous contour of the same level
//hierarchy[i][2]: the index of the first child
//hierarchy[i][3]: the index of the parent
//
foreach (var path in paths)
{
tw.WriteLine($"\t<path d=\"{path}\"/>");
}
bool firstTime = true;
/*bool firstTime = true;
for (int i = 0; i < contours.Size; i++)
{
if (hierarchy[i, EmguContour.HierarchyParent] == -1) // Top hierarchy
@@ -282,32 +277,8 @@ public sealed class OperationLayerExportImage : Operation
tw.Write(" Z");
}
if(!firstTime) tw.WriteLine("\"/>");
if(!firstTime) tw.WriteLine("\"/>");*/
// Old method!
/*for (int i = 0; i < contours.Size; i++)
{
if (contours[i].Size == 0) continue;
var style = "white";
int parentIndex = i;
int count = 0;
while ((parentIndex = (int)hierarchyJagged.GetValue(0, parentIndex, 3)) != -1)
{
count++;
}
if (count % 2 != 0)
style = "black";
tw.Write($"\t<path class=\"{style}\" d=\"M{contours[i][0].X} {contours[i][0].Y}");
for (int x = 1; x < contours[i].Size; x++)
{
tw.Write($",L{contours[i][x].X} {contours[i][x].Y}");
}
tw.WriteLine("Z\"/>");
}*/
tw.WriteLine("\t</g>");
tw.WriteLine("</svg>");
+22 -21
View File
@@ -12,7 +12,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UVtools.Core.Extensions;
@@ -57,7 +57,7 @@ public sealed class OperationLayerImport : Operation
private bool _extendBeyondLayerCount = true;
private bool _discardUnmodifiedLayers;
private ushort _stackMargin = 50;
private RangeObservableCollection<ValueDescription> _files = new();
private RangeObservableCollection<GenericFileRepresentation> _files = new();
#endregion
#region Overrides
@@ -127,9 +127,9 @@ public sealed class OperationLayerImport : Operation
{
foreach (var keyValue in _files)
{
if (!File.Exists(keyValue.ValueAsString))
if (!keyValue.Exists)
{
sb.AppendLine($"The file '{keyValue.ValueAsString}' does not exists.");
sb.AppendLine($"The file '{keyValue.FilePath}' does not exists.");
}
}
}
@@ -140,6 +140,15 @@ public sealed class OperationLayerImport : Operation
#region Properties
public static string[] ValidImageExtensions => new[]
{
"png",
"bmp",
"jpeg",
"jpg",
"gif",
};
public ImportTypes ImportType
{
get => _importType;
@@ -182,7 +191,7 @@ public sealed class OperationLayerImport : Operation
set => RaiseAndSetIfChanged(ref _stackMargin, value);
}
public RangeObservableCollection<ValueDescription> Files
public RangeObservableCollection<GenericFileRepresentation> Files
{
get => _files;
set => RaiseAndSetIfChanged(ref _files, value);
@@ -204,12 +213,12 @@ public sealed class OperationLayerImport : Operation
public void AddFile(string file)
{
_files.Add(new ValueDescription(file, Path.GetFileNameWithoutExtension(file)));
_files.Add(new GenericFileRepresentation(file));
}
public void Sort()
{
_files.Sort((file1, file2) => string.Compare(Path.GetFileNameWithoutExtension(file1.ValueAsString), Path.GetFileNameWithoutExtension(file2.ValueAsString), StringComparison.Ordinal));
_files.Sort();
}
@@ -246,12 +255,8 @@ public sealed class OperationLayerImport : Operation
// Order raw images
for (int i = 0; i < Count; i++)
{
if (!_files[i].ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) &&
!_files[i].ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) &&
!_files[i].ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) &&
!_files[i].ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) &&
!_files[i].ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) continue;
keyImage.Add(new KeyValuePair<uint, string>((uint)keyImage.Count, _files[i].ValueAsString));
if(!ValidImageExtensions.Any(extension => _files[i].IsExtension(extension))) continue;
keyImage.Add(new KeyValuePair<uint, string>((uint)keyImage.Count, _files[i].FilePath));
}
// Create virtual file format with images
@@ -273,18 +278,14 @@ public sealed class OperationLayerImport : Operation
fileFormats.Add(format);
}
// Order remaining possible file formats
// Order remaining possible file formats that are not images
for (int i = 0; i < Count; i++)
{
if (_files[i].ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
_files[i].ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) ||
_files[i].ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) ||
_files[i].ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
_files[i].ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) continue;
if (ValidImageExtensions.Any(extension => _files[i].IsExtension(extension))) continue;
var fileFormat = FileFormat.FindByExtensionOrFilePath(_files[i].ValueAsString, true);
var fileFormat = FileFormat.FindByExtensionOrFilePath(_files[i].FilePath, true);
if (fileFormat is null) continue;
fileFormat.FileFullPath = _files[i].ValueAsString;
fileFormat.FileFullPath = _files[i].FilePath;
fileFormats.Add(fileFormat);
}
+103 -41
View File
@@ -26,6 +26,40 @@ namespace UVtools.Core.Operations;
[Serializable]
public class OperationPCBExposure : Operation
{
#region Sub Classes
public sealed class PCBExposureFile : GenericFileRepresentation
{
private bool _invertPolarity;
private double _sizeScale = 1;
/// <summary>
/// Gets or sets to invert the polarity when drawing
/// </summary>
public bool InvertPolarity
{
get => _invertPolarity;
set => RaiseAndSetIfChanged(ref _invertPolarity, value);
}
/// <summary>
/// Gets or sets the scale to apply to each shape drawing size.
/// Positions and vectors aren't affected by this.
/// </summary>
public double SizeScale
{
get => _sizeScale;
set => RaiseAndSetIfChanged(ref _sizeScale, Math.Max(0.001, Math.Round(value, 4)));
}
public PCBExposureFile() { }
public PCBExposureFile(string filePath, bool invertPolarity = false) : base(filePath)
{
_invertPolarity = invertPolarity;
}
}
#endregion
#region Static
public static string[] ValidExtensions => new[]
@@ -43,7 +77,7 @@ public class OperationPCBExposure : Operation
#endregion
#region Members
private RangeObservableCollection<ValueDescription> _files = new();
private RangeObservableCollection<PCBExposureFile> _files = new();
private bool _mergeFiles;
private decimal _layerHeight;
@@ -91,9 +125,9 @@ public class OperationPCBExposure : Operation
}
else
{
foreach (var valueDescription in _files)
foreach (var file in _files)
{
if(!File.Exists(valueDescription.ValueAsString)) sb.AppendLine($"The file {valueDescription} does not exists");
if(!file.Exists) sb.AppendLine($"The file {file} does not exists");
}
}
@@ -122,7 +156,7 @@ public class OperationPCBExposure : Operation
#endregion
#region Properties
public RangeObservableCollection<ValueDescription> Files
public RangeObservableCollection<PCBExposureFile> Files
{
get => _files;
set => RaiseAndSetIfChanged(ref _files, value);
@@ -200,13 +234,13 @@ public class OperationPCBExposure : Operation
public void AddFilesFromZip(string zipFile)
{
if (!File.Exists(zipFile)) return;
if (!File.Exists(zipFile) || !zipFile.EndsWith(".zip")) return;
using var zip = ZipFile.Open(zipFile, ZipArchiveMode.Read);
var tmpPath = PathExtensions.GetTemporaryDirectory($"{About.Software}.");
foreach (var entry in zip.Entries)
{
if(!ValidExtensions.Any(extension => entry.Name.EndsWith($".{extension}", StringComparison.InvariantCultureIgnoreCase))) continue;
if(!ValidExtensions.Any(extension => entry.Name.EndsWith($".{extension}", StringComparison.OrdinalIgnoreCase))) continue;
var filePath = entry.ImprovedExtractToFile(tmpPath, false);
if (!string.IsNullOrEmpty(filePath))
@@ -217,15 +251,21 @@ public class OperationPCBExposure : Operation
}
public void AddFile(string file)
public void AddFile(string filePath, bool handleZipFiles = true)
{
if (!File.Exists(file)) return;
var vd = new ValueDescription(file, Path.GetFileNameWithoutExtension(file));
if (_files.Contains(vd)) return;
_files.Add(vd);
if (!File.Exists(filePath)) return;
if (filePath.EndsWith(".zip"))
{
if(handleZipFiles) AddFilesFromZip(filePath);
return;
}
if(!ValidExtensions.Any(extension => filePath.EndsWith($".{extension}", StringComparison.OrdinalIgnoreCase))) return;
var file = new PCBExposureFile(filePath);
if (_files.Contains(file)) return;
_files.Add(file);
}
public void AddFiles(string[] files)
public void AddFiles(string[] files, bool handleZipFiles = true)
{
foreach (var file in files)
{
@@ -233,15 +273,19 @@ public class OperationPCBExposure : Operation
}
}
public void Sort()
{
_files.Sort((file1, file2) => string.Compare(Path.GetFileNameWithoutExtension(file1.ValueAsString), Path.GetFileNameWithoutExtension(file2.ValueAsString), StringComparison.Ordinal));
}
public void Sort() => _files.Sort();
public Mat GetMat(string file)
public Mat GetMat(PCBExposureFile file)
{
var mat = SlicerFile.CreateMat();
if (!File.Exists(file)) return mat;
DrawMat(file, mat);
return mat;
}
public void DrawMat(PCBExposureFile file, Mat mat)
{
if (!file.Exists) return;
GerberDocument.ParseAndDraw(file, mat, SlicerFile.Ppmm, _sizeMidpointRounding, _enableAntiAliasing);
//var boundingRectangle = CvInvoke.BoundingRectangle(mat);
@@ -256,17 +300,18 @@ public class OperationPCBExposure : Operation
CvInvoke.Flip(mat, mat, (FlipType)flip);
}
return mat;
return;
}
protected override bool ExecuteInternally(OperationProgress progress)
{
if (_files.Count == 0) return false;
var layers = new List<Layer>();
Mat? mergeMat = null;
using var mergeMat = SlicerFile.CreateMat();
progress.ItemCount = FileCount;
foreach (var file in _files)
for (var i = 0; i < _files.Count; i++)
{
using var mat = GetMat(file.ValueAsString);
/*using var mat = GetMat(file);
if (mergeMat is null)
{
@@ -275,17 +320,39 @@ public class OperationPCBExposure : Operation
else
{
CvInvoke.Max(mergeMat, mat, mergeMat);
}
}*/
if (_mergeFiles) continue;
layers.Add(new Layer(mat, SlicerFile));
DrawMat(_files[i], mergeMat);
if (!_mergeFiles)
{
if (i == 0)
{
if (CvInvoke.CountNonZero(mergeMat) > 0)
{
layers.Add(new Layer(mergeMat, SlicerFile));
}
}
else
{
using var mat = SlicerFile.CreateMat();
DrawMat(_files[i], mat);
if (CvInvoke.CountNonZero(mat) > 0)
{
layers.Add(new Layer(mat, SlicerFile));
}
}
}
progress++;
}
if (_mergeFiles && mergeMat is not null)
if (_mergeFiles)
{
layers.Add(new Layer(mergeMat, SlicerFile));
if (CvInvoke.CountNonZero(mergeMat) > 0)
{
layers.Add(new Layer(mergeMat, SlicerFile));
}
}
SlicerFile.SuppressRebuildPropertiesWork(() =>
@@ -333,21 +400,16 @@ public class OperationPCBExposure : Operation
op.Execute(progress);
}
if (mergeMat is not null)
using var croppedMat = mergeMat.CropByBounds(20);
/*if (_mirror)
{
using var croppedMat = mergeMat.CropByBounds(20);
/*if (_mirror)
{
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
CvInvoke.Flip(croppedMat, croppedMat, (FlipType)flip);
}*/
using var bgrMat = new Mat();
CvInvoke.CvtColor(croppedMat, bgrMat, ColorConversion.Gray2Bgr);
SlicerFile.SetThumbnails(bgrMat);
mergeMat.Dispose();
}
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
CvInvoke.Flip(croppedMat, croppedMat, (FlipType)flip);
}*/
using var bgrMat = new Mat();
CvInvoke.CvtColor(croppedMat, bgrMat, ColorConversion.Gray2Bgr);
SlicerFile.SetThumbnails(bgrMat);
return !progress.Token.IsCancellationRequested;
}
+1 -1
View File
@@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
<Version>3.8.0</Version>
<Version>3.8.1</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
+2 -2
View File
@@ -108,12 +108,12 @@ public class Voxelizer
return foundFaces;
}
public static Mat BuildVoxelLayerImage(Mat curLayer, Mat? layerAbove = null, Mat? layerBelow = null)
public static Mat BuildVoxelLayerImage(Mat curLayer, Mat? layerAbove = null, Mat? layerBelow = null, ChainApproxMethod contourCompressionMethod = ChainApproxMethod.ChainApproxSimple)
{
/* The goal of the VoxelLayerImage is to reduce as much as possible, the number of pixels we need to do 6 direction neighbor checking on */
/* the outer contours of the current layer should always be checked, they by definition should have an exposed face */
using var contours = curLayer.FindContours(RetrType.Tree);
using var contours = curLayer.FindContours(RetrType.Tree, contourCompressionMethod);
var onlyContours = curLayer.NewBlank();
CvInvoke.DrawContours(onlyContours, contours, -1, EmguExtensions.WhiteColor, 1);