mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
v3.8.1
- **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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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>");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user