diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f802644..40bad97 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -18,4 +18,5 @@
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6397818..714d4f4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,24 @@
# Changelog
+## 19/06/2022 - v3.5.0
+
+- **PCB Exposure:**
+ - (Add) Able to select multiple files and create a layer per file or merge them into one layer
+ - (Add) Able to import files from a zip file
+ - (Improvement) Round pixel coordinates and line thickness
+ - (Improvement) Better position precision for primitives
+ - (Improvement) Disable the ok button if no files were selected
+ - (Change) Do not auto mirror based on printer lcd mirror type
+ - (Fix) Limit line thickness to 1px minimum
+ - (Fix) Allow leading zero omit from XY coordinates (#492)
+ - (Fix) Mirror option was shifting the board position
+- (Add) Calibrate - Blooming effect: Generates test models with various strategies and increments to measure the blooming effect
+- (Add) Setting: Issues - Default order by, changes the default order on the issues list (#482)
+- (Improvement) CTBv4 and encrypted: Fetch `BottomWaitTimes` virtual property from first bottom layer that has at least 2 pixels (#483)
+- (Fix) Linux: Enable desktop integration for AppImages (#490)
+- (Fix) Extracting zip contents inside folders would cause a error and not extract those contents
+- (Upgrade) AvaloniaUI from 0.10.14 to 0.10.15 [Fixes auto-size problems]
+
## 21/05/2022 - v3.4.3
- (Add) Information about the loaded file when copying from the about box
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 0e44f02..9c7b302 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,17 @@
-- (Add) Information about the loaded file when copying from the about box
-- (Improvement) Tools are now disabled on the menu if not supported by the file format once each file load (#476)
-- (Fix) Tool - Edit tool parameters: Overlap label on "per layer override" mode (#478)
-- (Fix) Corruption of `GZip` and `Deflate` layer compression methods
+- **PCB Exposure:**
+ - (Add) Able to select multiple files and create a layer per file or merge them into one layer
+ - (Add) Able to import files from a zip file
+ - (Improvement) Round pixel coordinates and line thickness
+ - (Improvement) Better position precision for primitives
+ - (Improvement) Disable the ok button if no files were selected
+ - (Change) Do not auto mirror based on printer lcd mirror type
+ - (Fix) Limit line thickness to 1px minimum
+ - (Fix) Allow leading zero omit from XY coordinates (#492)
+ - (Fix) Mirror option was shifting the board position
+- (Add) Calibrate - Blooming effect: Generates test models with various strategies and increments to measure the blooming effect
+- (Add) Setting: Issues - Default order by, changes the default order on the issues list (#482)
+- (Improvement) CTBv4 and encrypted: Fetch `BottomWaitTimes` virtual property from first bottom layer that has at least 2 pixels (#483)
+- (Fix) Linux: Enable desktop integration for AppImages (#490)
+- (Fix) Extracting zip contents inside folders would cause a error and not extract those contents
+- (Upgrade) AvaloniaUI from 0.10.14 to 0.10.15 [Fixes auto-size problems]
diff --git a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
index 45106a3..c41bc1d 100644
--- a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
+++ b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
@@ -38,7 +38,7 @@
-
+
diff --git a/UVtools.Core/Enumerations.cs b/UVtools.Core/Enumerations.cs
index 1e4e9b8..28de992 100644
--- a/UVtools.Core/Enumerations.cs
+++ b/UVtools.Core/Enumerations.cs
@@ -124,6 +124,21 @@ public enum RemoveSourceFileAction : byte
Prompt
}
+///
+/// Default order of issues to show on the UI list
+///
+public enum IssuesOrderBy : byte
+{
+ [Description("Type (↑ASC) » Layer (↑ASC) » Area (↓DESC)")]
+ TypeAscLayerAscAreaDesc,
+
+ [Description("Type (↑ASC) » Area (↓DESC) » Layer (↑ASC)")]
+ TypeAscAreaDescLayerAsc,
+
+ [Description("Area (↓DESC) » Layer (↑ASC) » Type (↑ASC)")]
+ AreaDescLayerIndexAscTypeAsc
+}
+
/*
public static class Enumerations
{
diff --git a/UVtools.Core/Extensions/PathExtensions.cs b/UVtools.Core/Extensions/PathExtensions.cs
index 3a7b970..ad52eb4 100644
--- a/UVtools.Core/Extensions/PathExtensions.cs
+++ b/UVtools.Core/Extensions/PathExtensions.cs
@@ -37,7 +37,28 @@ public static class PathExtensions
return path;
}
- public static string GetTempFilePathWithFilename(string fileName)
+ ///
+ /// Gets a temporary directory path
+ ///
+ /// Prepend a string to temporary directory name
+ /// True to create that directory, otherwise false
+ /// The temporary directory path
+ public static string GetTemporaryDirectory(string? prepend, bool createDirectory = false)
+ {
+ string tempDirectory = Path.Combine(Path.GetTempPath(), $"{prepend}{Path.GetRandomFileName()}");
+ if (createDirectory) Directory.CreateDirectory(tempDirectory);
+ return tempDirectory;
+ }
+
+ ///
+ /// Gets a temporary directory path
+ ///
+ /// True to create that directory, otherwise false
+ /// The temporary directory path
+ public static string GetTemporaryDirectory(bool createDirectory = false) =>
+ GetTemporaryDirectory(null, createDirectory);
+
+ public static string GetTemporaryFilePathWithFilename(string fileName)
{
var path = Path.GetTempPath();
return Path.Combine(path, fileName);
@@ -49,7 +70,7 @@ public static class PathExtensions
/// Extension name without the dot (.)
/// Optional prepend file name
///
- public static string GetTempFilePathWithExtension(string extension, string? prepend = null)
+ public static string GetTemporaryFilePathWithExtension(string extension, string? prepend = null)
{
var path = Path.GetTempPath();
var fileName = $"{prepend}{Guid.NewGuid()}.{extension}";
diff --git a/UVtools.Core/Extensions/ZipArchiveExtensions.cs b/UVtools.Core/Extensions/ZipArchiveExtensions.cs
index 0d8154d..8345a74 100644
--- a/UVtools.Core/Extensions/ZipArchiveExtensions.cs
+++ b/UVtools.Core/Extensions/ZipArchiveExtensions.cs
@@ -59,7 +59,7 @@ public static class ZipArchiveExtensions
{
//Opens the zip file up to be read
using var archive = ZipFile.OpenRead(sourceArchiveFileName);
- archive.ImprovedExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, overwriteMethod);
+ archive.ImprovedExtractToDirectory(destinationDirectoryName, overwriteMethod);
}
///
@@ -67,8 +67,8 @@ public static class ZipArchiveExtensions
/// manner. This plans for missing paths and existing files
/// and handles them gracefully.
///
- ///
- /// The name of the zip file to be extracted
+ ///
+ /// The zip file to be extracted
///
///
/// The directory to extract the zip file to
@@ -77,36 +77,42 @@ public static class ZipArchiveExtensions
/// Specifies how we are going to handle an existing file.
/// The default is IfNewer.
///
- public static void ImprovedExtractToDirectory(this ZipArchive archive, string sourceArchiveFileName, string destinationDirectoryName, Overwrite overwriteMethod = Overwrite.IfNewer)
+ /// The number of extracted files
+ public static int ImprovedExtractToDirectory(this ZipArchive archive, string destinationDirectoryName, Overwrite overwriteMethod = Overwrite.IfNewer)
{
+ int count = 0;
//Loops through each file in the zip file
foreach (var file in archive.Entries)
{
- file.ImprovedExtractToFile(destinationDirectoryName, overwriteMethod);
+ if(!string.IsNullOrEmpty(file.ImprovedExtractToFile(destinationDirectoryName, true, overwriteMethod))) count++;
}
+
+ return count;
}
///
/// Safely extracts a single file from a zip file
///
- ///
+ ///
/// The zip entry we are pulling the file from
///
///
/// The root of where the file is going
///
+ /// True to preserve full name and create all directories up to the file, otherwise false to extract the file just to
///
/// Specifies how we are going to handle an existing file.
/// The default is Overwrite.IfNewer.
///
- public static void ImprovedExtractToFile(this ZipArchiveEntry file, string destinationPath, Overwrite overwriteMethod = Overwrite.IfNewer)
+ /// The extracted file path
+ public static string? ImprovedExtractToFile(this ZipArchiveEntry entry, string destinationPath, bool preserveFullName = true, Overwrite overwriteMethod = Overwrite.IfNewer)
{
//Gets the complete path for the destination file, including any
//relative paths that were in the zip file
- var destFileName = Path.GetFullPath(Path.Combine(destinationPath, file.FullName));
- var fullDestDirPath = Path.GetFullPath(destinationPath + Path.DirectorySeparatorChar);
- if (!destFileName.StartsWith(fullDestDirPath)) return; // Entry is outside the target dir
-
+ var destFileName = Path.GetFullPath(Path.Combine(destinationPath, preserveFullName ? entry.FullName : entry.Name));
+ var fullDestDirPath = Path.GetFullPath(Path.Combine(destinationPath, (preserveFullName ? Path.GetDirectoryName(entry.FullName) : string.Empty)!) + Path.DirectorySeparatorChar);
+ if (!destFileName.StartsWith(fullDestDirPath)) return null; // Entry is outside the target dir
+
//Creates the directory (if it doesn't exist) for the new path
Directory.CreateDirectory(fullDestDirPath);
@@ -116,16 +122,16 @@ public static class ZipArchiveExtensions
{
case Overwrite.Always:
//Just put the file in and overwrite anything that is found
- file.ExtractToFile(destFileName, true);
+ entry.ExtractToFile(destFileName, true);
break;
case Overwrite.IfNewer:
//Checks to see if the file exists, and if so, if it should
//be overwritten
- if (!File.Exists(destFileName) || File.GetLastWriteTime(destFileName) < file.LastWriteTime)
+ if (!File.Exists(destFileName) || File.GetLastWriteTime(destFileName) < entry.LastWriteTime)
{
//Either the file didn't exist or this file is newer, so
//we will extract it and overwrite any existing file
- file.ExtractToFile(destFileName, true);
+ entry.ExtractToFile(destFileName, true);
}
break;
case Overwrite.Never:
@@ -133,12 +139,14 @@ public static class ZipArchiveExtensions
//file if it already exists
if (!File.Exists(destFileName))
{
- file.ExtractToFile(destFileName);
+ entry.ExtractToFile(destFileName);
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(overwriteMethod), overwriteMethod, null);
}
+
+ return destFileName;
}
///
diff --git a/UVtools.Core/FileFormats/CTBEncryptedFile.cs b/UVtools.Core/FileFormats/CTBEncryptedFile.cs
index 14b0bd6..b19f122 100644
--- a/UVtools.Core/FileFormats/CTBEncryptedFile.cs
+++ b/UVtools.Core/FileFormats/CTBEncryptedFile.cs
@@ -797,7 +797,7 @@ public class CTBEncryptedFile : FileFormat
public override float BottomWaitTimeBeforeCure
{
- get => base.BottomWaitTimeBeforeCure > 0 ? base.BottomWaitTimeBeforeCure : FirstLayer?.WaitTimeBeforeCure ?? 0;
+ get => base.BottomWaitTimeBeforeCure > 0 ? base.BottomWaitTimeBeforeCure : this.FirstOrDefault(layer => layer is not null && layer.IsBottomLayer && layer.NonZeroPixelCount > 1)?.WaitTimeBeforeCure ?? 0;
set => base.BottomWaitTimeBeforeCure = value;
}
@@ -824,7 +824,7 @@ public class CTBEncryptedFile : FileFormat
public override float BottomWaitTimeAfterCure
{
- get => base.BottomWaitTimeAfterCure > 0 ? base.BottomWaitTimeAfterCure : FirstLayer?.WaitTimeAfterCure ?? 0;
+ get => base.BottomWaitTimeAfterCure > 0 ? base.BottomWaitTimeAfterCure : this.FirstOrDefault(layer => layer is not null && layer.IsBottomLayer && layer.NonZeroPixelCount > 1)?.WaitTimeAfterCure ?? 0;
set => base.BottomWaitTimeAfterCure = value;
}
@@ -920,7 +920,7 @@ public class CTBEncryptedFile : FileFormat
public override float BottomWaitTimeAfterLift
{
- get => base.BottomWaitTimeAfterLift > 0 ? base.BottomWaitTimeAfterLift : FirstLayer?.WaitTimeAfterLift ?? 0;
+ get => base.BottomWaitTimeAfterLift > 0 ? base.BottomWaitTimeAfterLift : this.FirstOrDefault(layer => layer is not null && layer.IsBottomLayer && layer.NonZeroPixelCount > 1)?.WaitTimeAfterLift ?? 0;
set => base.BottomWaitTimeAfterLift = value;
}
diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs
index aa14430..8c09d8a 100644
--- a/UVtools.Core/FileFormats/ChituboxFile.cs
+++ b/UVtools.Core/FileFormats/ChituboxFile.cs
@@ -1391,7 +1391,7 @@ public class ChituboxFile : FileFormat
public override float BottomWaitTimeBeforeCure
{
- get => base.BottomWaitTimeBeforeCure > 0 ? base.BottomWaitTimeBeforeCure : FirstLayer?.WaitTimeBeforeCure ?? 0;
+ get => base.BottomWaitTimeBeforeCure > 0 ? base.BottomWaitTimeBeforeCure : this.FirstOrDefault(layer => layer is not null && layer.IsBottomLayer && layer.NonZeroPixelCount > 1)?.WaitTimeBeforeCure ?? 0;
set
{
if (HeaderSettings.Version < 4)
@@ -1439,7 +1439,7 @@ public class ChituboxFile : FileFormat
public override float BottomWaitTimeAfterCure
{
- get => HeaderSettings.Version >= 4 ? (base.BottomWaitTimeAfterCure > 0 ? base.BottomWaitTimeAfterCure : FirstLayer?.WaitTimeAfterCure ?? 0) : 0;
+ get => HeaderSettings.Version >= 4 ? (base.BottomWaitTimeAfterCure > 0 ? base.BottomWaitTimeAfterCure : this.FirstOrDefault(layer => layer is not null && layer.IsBottomLayer && layer.NonZeroPixelCount > 1)?.WaitTimeAfterCure ?? 0) : 0;
set
{
if (HeaderSettings.Version < 4) return;
@@ -1560,7 +1560,7 @@ public class ChituboxFile : FileFormat
public override float BottomWaitTimeAfterLift
{
- get => HeaderSettings.Version >= 4 ? (base.BottomWaitTimeAfterLift > 0 ? base.BottomWaitTimeAfterLift : FirstLayer?.WaitTimeAfterLift ?? 0) : 0;
+ get => HeaderSettings.Version >= 4 ? (base.BottomWaitTimeAfterLift > 0 ? base.BottomWaitTimeAfterLift : this.FirstOrDefault(layer => layer is not null && layer.IsBottomLayer && layer.NonZeroPixelCount > 1)?.WaitTimeAfterLift ?? 0) : 0;
set
{
if (HeaderSettings.Version < 4) return;
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index b62099d..6609773 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -148,6 +148,9 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable
+ /// File decode type
+ ///
public enum FileDecodeType : byte
{
///
@@ -160,6 +163,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable
Partial,
}
+
#endregion
#region Sub Classes
diff --git a/UVtools.Core/Gerber/Apertures/Aperture.cs b/UVtools.Core/Gerber/Apertures/Aperture.cs
index f6fc560..68cb3c6 100644
--- a/UVtools.Core/Gerber/Apertures/Aperture.cs
+++ b/UVtools.Core/Gerber/Apertures/Aperture.cs
@@ -37,7 +37,7 @@ public abstract class Aperture
protected Aperture(string name) { Name = name; }
protected Aperture(int index, string name) : this(index) { Name = name; }
- public abstract void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color, LineType lineType = LineType.EightConnected);
+ public abstract void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected);
public static Aperture? Parse(string line, GerberDocument document)
{
diff --git a/UVtools.Core/Gerber/Apertures/CircleAperture.cs b/UVtools.Core/Gerber/Apertures/CircleAperture.cs
index 1d8e5b5..2f00787 100644
--- a/UVtools.Core/Gerber/Apertures/CircleAperture.cs
+++ b/UVtools.Core/Gerber/Apertures/CircleAperture.cs
@@ -6,6 +6,7 @@
* of this license document, but changing it is not allowed.
*/
+using System;
using System.Drawing;
using Emgu.CV;
using Emgu.CV.CvEnum;
@@ -29,9 +30,10 @@ public class CircleAperture : Aperture
}
#endregion
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color,
- LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected)
{
- CvInvoke.Circle(mat, at, (int)(Diameter * xyPpmm.Max() / 2), color, -1, lineType);
+ CvInvoke.Circle(mat,
+ GerberDocument.PositionMmToPx(at, xyPpmm),
+ GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()), color, -1, lineType);
}
}
\ No newline at end of file
diff --git a/UVtools.Core/Gerber/Apertures/EllipseAperture.cs b/UVtools.Core/Gerber/Apertures/EllipseAperture.cs
index 3174609..7ac6f2e 100644
--- a/UVtools.Core/Gerber/Apertures/EllipseAperture.cs
+++ b/UVtools.Core/Gerber/Apertures/EllipseAperture.cs
@@ -33,10 +33,11 @@ public class EllipseAperture : Aperture
}
#endregion
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color,
- LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected)
{
- var axes = new Size((int) (Axes.Width * xyPpmm.Width / 2), (int) (Axes.Height * xyPpmm.Height / 2));
- CvInvoke.Ellipse(mat, at, axes, 0, 0, 360, color, -1, lineType);
+ CvInvoke.Ellipse(mat,
+ GerberDocument.PositionMmToPx(at, xyPpmm),
+ GerberDocument.SizeMmToPx(Axes.Width / 2, Axes.Height / 2, xyPpmm),
+ 0, 0, 360, color, -1, lineType);
}
}
\ No newline at end of file
diff --git a/UVtools.Core/Gerber/Apertures/MacroAperture.cs b/UVtools.Core/Gerber/Apertures/MacroAperture.cs
index 54c199e..1f4cea1 100644
--- a/UVtools.Core/Gerber/Apertures/MacroAperture.cs
+++ b/UVtools.Core/Gerber/Apertures/MacroAperture.cs
@@ -16,7 +16,8 @@ namespace UVtools.Core.Gerber.Apertures;
public class MacroAperture : Aperture
{
#region Properties
- public Macro Macro { get; set; }
+ public Macro Macro { get; set; } = null!;
+
#endregion
#region Constructor
@@ -28,7 +29,7 @@ public class MacroAperture : Aperture
}
#endregion
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color, LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected)
{
foreach (var macro in Macro)
{
diff --git a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs
index 08ac091..e10c1c2 100644
--- a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs
+++ b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs
@@ -6,6 +6,7 @@
* of this license document, but changing it is not allowed.
*/
+using System;
using System.Drawing;
using Emgu.CV;
using Emgu.CV.CvEnum;
@@ -32,9 +33,8 @@ public class PolygonAperture : Aperture
#endregion
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color,
- LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected)
{
- mat.DrawPolygon(Vertices, (int)(Diameter * xyPpmm.Max() / 2), at, color, 0, -1, lineType);
+ mat.DrawPolygon(Vertices, GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()), GerberDocument.PositionMmToPx(at, xyPpmm), color, 0, -1, lineType);
}
}
\ No newline at end of file
diff --git a/UVtools.Core/Gerber/Apertures/RectangleAperture.cs b/UVtools.Core/Gerber/Apertures/RectangleAperture.cs
index 4f58eca..5dbd5a2 100644
--- a/UVtools.Core/Gerber/Apertures/RectangleAperture.cs
+++ b/UVtools.Core/Gerber/Apertures/RectangleAperture.cs
@@ -6,6 +6,7 @@
* of this license document, but changing it is not allowed.
*/
+using System;
using System.Drawing;
using Emgu.CV;
using Emgu.CV.CvEnum;
@@ -33,11 +34,10 @@ public class RectangleAperture : Aperture
}
#endregion
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color,
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color,
LineType lineType = LineType.EightConnected)
{
- var size = new Size((int) (Size.Width * xyPpmm.Width), (int) (Size.Height * xyPpmm.Height));
- at.Offset(-size.Width / 2, -size.Height / 2);
- CvInvoke.Rectangle(mat, new Rectangle(at, size), color, -1, lineType);
+ at = new PointF(Math.Max(0, at.X - Size.Width / 2), Math.Max(0, at.Y - Size.Height / 2));
+ CvInvoke.Rectangle(mat, new Rectangle(GerberDocument.PositionMmToPx(at, xyPpmm), GerberDocument.SizeMmToPx(Size, xyPpmm)), color, -1, lineType);
}
}
\ No newline at end of file
diff --git a/UVtools.Core/Gerber/GerberDocument.cs b/UVtools.Core/Gerber/GerberDocument.cs
index 8a16de4..e150e24 100644
--- a/UVtools.Core/Gerber/GerberDocument.cs
+++ b/UVtools.Core/Gerber/GerberDocument.cs
@@ -13,211 +13,216 @@ using System.IO;
using System.Text.RegularExpressions;
using Emgu.CV;
using Emgu.CV.CvEnum;
-using Emgu.CV.Structure;
using Emgu.CV.Util;
using UVtools.Core.Extensions;
using UVtools.Core.Gerber.Apertures;
-namespace UVtools.Core.Gerber
+namespace UVtools.Core.Gerber;
+
+public class GerberDocument
{
- public class GerberDocument
+ #region Properties
+
+ public GerberPositionType PositionType { get; set; } = GerberPositionType.Absolute;
+ public GerberUnitType UnitType { get; set; } = GerberUnitType.Millimeter;
+ public GerberPolarityType Polarity { get; set; } = GerberPolarityType.Dark;
+ public GerberMoveType MoveType { get; set; } = GerberMoveType.Linear;
+
+ public bool LeadingZeroOmitted { get; set; } = true;
+
+ public byte CoordinateXIntegers { get; set; } = 3;
+ public byte CoordinateXFractionalDigits { get; set; } = 6;
+
+ public byte CoordinateYIntegers { get; set; } = 3;
+ public byte CoordinateYFractionalDigits { get; set; } = 6;
+
+ public Dictionary Apertures { get; } = new();
+ public Dictionary Macros { get; } = new();
+
+ #endregion
+
+
+ public GerberDocument()
{
- #region Properties
+ }
- public GerberPositionType PositionType { get; set; } = GerberPositionType.Absolute;
- public GerberUnitType UnitType { get; set; } = GerberUnitType.Millimeter;
- public GerberPolarityType Polarity { get; set; } = GerberPolarityType.Dark;
- public GerberMoveType MoveType { get; set; } = GerberMoveType.Linear;
+ public GerberDocument(string filePath)
+ {
+ }
- public bool LeadingZeroOmitted { get; set; } = true;
+ public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, bool enableAntialiasing = false)
+ {
+ using var file = new StreamReader(filePath);
+ var document = new GerberDocument();
- public byte CoordinateXIntegers { get; set; } = 3;
- public byte CoordinateXFractionalDigits { get; set; } = 6;
+ int FSlength = "%FSLAX46Y46*%".Length;
+ int MOlength = "%MOMM*%".Length;
+ int LPlength = "%LPD*%".Length;
- public byte CoordinateYIntegers { get; set; } = 3;
- public byte CoordinateYFractionalDigits { get; set; } = 6;
-
- public Dictionary Apertures { get; } = new();
- public Dictionary Macros { get; } = new();
-
- #endregion
-
-
- public GerberDocument()
+ double currentX = 0;
+ double currentY = 0;
+ Aperture? currentAperture = null;
+ Macro? currentMacro = null;
+ var regionPoints = new List();
+ bool insideRegion = false;
+ string? line;
+ while ((line = file.ReadLine()) is not null)
{
- }
+ line = line.Trim();
+ if(line == string.Empty) continue;
+ if (line.StartsWith("M02")) break;
- public GerberDocument(string filePath)
- {
- }
-
- public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, bool enableAntialiasing = false)
- {
- using var file = new StreamReader(filePath);
- var document = new GerberDocument();
-
- int FSlength = "%FSLAX46Y46*%".Length;
- int MOlength = "%MOMM*%".Length;
- int LPlength = "%LPD*%".Length;
-
- double currentX = 0;
- double currentY = 0;
- Aperture? currentAperture = null;
- Macro? currentMacro = null;
- var regionPoints = new List();
- bool insideRegion = false;
- string? line;
- while ((line = file.ReadLine()) is not null)
+ var accumulatedLine = line;
+ while (!accumulatedLine.Contains('*') && (line = file.ReadLine()) is not null)
{
line = line.Trim();
- if(line == string.Empty) continue;
- if (line.StartsWith("M02")) break;
+ if (line == string.Empty) continue;
+ accumulatedLine += line;
+ }
- var accumulatedLine = line;
- while (!accumulatedLine.Contains('*') && (line = file.ReadLine()) is not null)
+ line = accumulatedLine;
+
+ if(currentMacro is not null)
+ {
+ currentMacro.ParsePrimitive(line);
+ if (line[^1] == '%') currentMacro = null;
+ continue;
+ }
+
+ 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;
+ continue;
+ }
+
+ if (line.StartsWith("%FS") && line.Length >= FSlength)
+ {
+ // %FSLAX34Y34*%
+ // 0123456789
+ document.LeadingZeroOmitted = line[3] switch
{
- line = line.Trim();
- if (line == string.Empty) continue;
- accumulatedLine += line;
+ 'L' => true,
+ 'T' => false,
+ _ => document.LeadingZeroOmitted
+ };
+ document.PositionType = line[4] switch
+ {
+ 'A' => GerberPositionType.Absolute,
+ 'I' => GerberPositionType.Relative,
+ _ => document.PositionType
+ };
+ if (line[5] != 'X') continue;
+ if (byte.TryParse(line[6].ToString(), out var x1)) document.CoordinateXIntegers = x1;
+ if (byte.TryParse(line[7].ToString(), out var x2)) document.CoordinateXFractionalDigits = x2;
+ if (line[8] != 'Y') continue;
+ if (byte.TryParse(line[9].ToString(), out var y1)) document.CoordinateYIntegers = y1;
+ if (byte.TryParse(line[10].ToString(), out var y2)) document.CoordinateYFractionalDigits = y2;
+ continue;
+ }
+
+ if (line.StartsWith("%LP") && line.Length >= LPlength)
+ {
+ document.Polarity = line[3] switch
+ {
+ 'D' => GerberPolarityType.Dark,
+ 'C' => GerberPolarityType.Clear,
+ _ => document.Polarity
+ };
+
+ continue;
+ }
+
+ if (line.StartsWith("G01"))
+ {
+ document.MoveType = GerberMoveType.Linear;
+ continue;
+ }
+
+ if (line.StartsWith("G02"))
+ {
+ document.MoveType = GerberMoveType.Arc;
+ continue;
+ }
+
+ if (line.StartsWith("G03"))
+ {
+ document.MoveType = GerberMoveType.ArcCounterClockwise;
+ continue;
+ }
+
+ if (line.StartsWith("G36"))
+ {
+ insideRegion = true;
+ regionPoints.Clear();
+ continue;
+ }
+
+ if (line.StartsWith("G37"))
+ {
+ insideRegion = false;
+ if (regionPoints.Count > 0)
+ {
+ using var vec = new VectorOfPoint(regionPoints.ToArray());
+ CvInvoke.FillPoly(mat, vec, document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
}
+ regionPoints.Clear();
+ continue;
+ }
- line = accumulatedLine;
+ if (line.StartsWith("%AM"))
+ {
+ currentMacro = Macro.Parse(line);
+ if (currentMacro is null) continue;
+ document.Macros.Add(currentMacro.Name, currentMacro);
+ //document.Apertures.Add(aperture.Index, aperture);
+ continue;
+ }
- if(currentMacro is not null)
+ if (line.StartsWith("%ADD"))
+ {
+ var aperture = Aperture.Parse(line, document);
+ if (aperture is null) continue;
+ currentAperture = aperture;
+ document.Apertures.Add(aperture.Index, aperture);
+ continue;
+ }
+
+ if (line[0] == 'D')
+ {
+ var matchD = Regex.Match(line, @"D(\d+)");
+ if (!matchD.Success || matchD.Groups.Count < 2) continue;
+
+ if (!int.TryParse(matchD.Groups[1].Value, out var d)) continue;
+
+ if (!document.Apertures.TryGetValue(d, out currentAperture)) continue;
+
+ continue;
+ }
+
+ if (line[0] == 'X' || line[0] == 'Y')
+ {
+ var matchX = Regex.Match(line, @"X-?(\d+)");
+ var matchY = Regex.Match(line, @"Y-?(\d+)");
+ var matchD = Regex.Match(line, @"D(\d+)");
+
+ double nowX = 0;
+ double nowY = 0;
+
+ if (!matchD.Success || matchD.Groups.Count < 2) continue;
+ if (!int.TryParse(matchD.Groups[1].Value, out var d)) continue;
+
+ if (matchX.Success && matchX.Groups.Count >= 2)
{
- currentMacro.ParsePrimitive(line);
- if (line[^1] == '%') currentMacro = null;
- continue;
- }
-
- 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;
- continue;
- }
-
- if (line.StartsWith("%FS") && line.Length >= FSlength)
- {
- // %FSLAX34Y34*%
- // 0123456789
- document.LeadingZeroOmitted = line[3] switch
+ if (double.TryParse(matchX.Groups[1].Value, out nowX))
{
- 'L' => true,
- 'T' => false,
- _ => document.LeadingZeroOmitted
- };
- document.PositionType = line[4] switch
- {
- 'A' => GerberPositionType.Absolute,
- 'I' => GerberPositionType.Relative,
- _ => document.PositionType
- };
- if (line[5] != 'X') continue;
- if (byte.TryParse(line[6].ToString(), out var x1)) document.CoordinateXIntegers = x1;
- if (byte.TryParse(line[7].ToString(), out var x2)) document.CoordinateXFractionalDigits = x2;
- if (line[8] != 'Y') continue;
- if (byte.TryParse(line[9].ToString(), out var y1)) document.CoordinateYIntegers = y1;
- if (byte.TryParse(line[10].ToString(), out var y2)) document.CoordinateYFractionalDigits = y2;
- continue;
- }
-
- if (line.StartsWith("%LP") && line.Length >= LPlength)
- {
- document.Polarity = line[3] switch
- {
- 'D' => GerberPolarityType.Dark,
- 'C' => GerberPolarityType.Clear,
- _ => document.Polarity
- };
-
- continue;
- }
-
- if (line.StartsWith("G01"))
- {
- document.MoveType = GerberMoveType.Linear;
- continue;
- }
-
- if (line.StartsWith("G02"))
- {
- document.MoveType = GerberMoveType.Arc;
- continue;
- }
-
- if (line.StartsWith("G03"))
- {
- document.MoveType = GerberMoveType.ArcCounterClockwise;
- continue;
- }
-
- if (line.StartsWith("G36"))
- {
- insideRegion = true;
- regionPoints.Clear();
- continue;
- }
-
- if (line.StartsWith("G37"))
- {
- insideRegion = false;
- if (regionPoints.Count > 0)
- {
- using var vec = new VectorOfPoint(regionPoints.ToArray());
- CvInvoke.FillPoly(mat, vec, document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
- }
- regionPoints.Clear();
- continue;
- }
-
- if (line.StartsWith("%AM"))
- {
- currentMacro = Macro.Parse(line);
- if (currentMacro is null) continue;
- document.Macros.Add(currentMacro.Name, currentMacro);
- //document.Apertures.Add(aperture.Index, aperture);
- continue;
- }
-
- if (line.StartsWith("%ADD"))
- {
- var aperture = Aperture.Parse(line, document);
- if (aperture is null) continue;
- currentAperture = aperture;
- document.Apertures.Add(aperture.Index, aperture);
- continue;
- }
-
- if (line[0] == 'D')
- {
- var matchD = Regex.Match(line, @"D(\d+)");
- if (!matchD.Success || matchD.Groups.Count < 2) continue;
-
- if (!int.TryParse(matchD.Groups[1].Value, out var d)) continue;
-
- if (!document.Apertures.TryGetValue(d, out currentAperture)) continue;
-
- continue;
- }
-
- if (line[0] == 'X' || line[0] == 'Y')
- {
- var matchX = Regex.Match(line, @"X-?(\d+)");
- var matchY = Regex.Match(line, @"Y-?(\d+)");
- var matchD = Regex.Match(line, @"D(\d+)");
-
- double nowX = 0;
- double nowY = 0;
-
- if (!matchD.Success || matchD.Groups.Count < 2) continue;
- if (!int.TryParse(matchD.Groups[1].Value, out var d)) continue;
-
- if (matchX.Success && matchX.Groups.Count >= 2)
- {
- if (double.TryParse(matchX.Groups[1].Value, out nowX))
+ if (nowX != 0)
{
- if (nowX != 0)
+ if (document.CoordinateXFractionalDigits > matchX.Groups[1].Value.Length) // Leading zero omitted
+ {
+ double.TryParse($"0.{matchX.Groups[1].Value}", out nowX);
+ }
+ else
{
var integers = matchX.Groups[1].Value[..^document.CoordinateXFractionalDigits];
var fraction = matchX.Groups[1].Value.Substring(matchX.Groups[1].Value.Length - document.CoordinateXFractionalDigits, document.CoordinateXFractionalDigits);
@@ -225,12 +230,19 @@ namespace UVtools.Core.Gerber
}
}
}
+ }
- if (matchY.Success && matchY.Groups.Count >= 2)
+ if (matchY.Success && matchY.Groups.Count >= 2)
+ {
+ if (double.TryParse(matchY.Groups[1].Value, out nowY))
{
- if (double.TryParse(matchY.Groups[1].Value, out nowY))
+ if (nowY != 0)
{
- if (nowY != 0)
+ if (document.CoordinateYFractionalDigits > matchY.Groups[1].Value.Length) // Leading zero omitted
+ {
+ double.TryParse($"0.{matchY.Groups[1].Value}", out nowY);
+ }
+ else
{
var integers = matchY.Groups[1].Value[..^document.CoordinateYFractionalDigits];
var fraction = matchY.Groups[1].Value.Substring(matchY.Groups[1].Value.Length - document.CoordinateYFractionalDigits, document.CoordinateYFractionalDigits);
@@ -238,106 +250,149 @@ namespace UVtools.Core.Gerber
}
}
}
+ }
- if (insideRegion)
+ if (insideRegion)
+ {
+ if (d == 2)
{
- if (d == 2)
+ if (regionPoints.Count > 0)
{
- if (regionPoints.Count > 0)
- {
- using var vec = new VectorOfPoint(regionPoints.ToArray());
- CvInvoke.FillPoly(mat, vec, document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
- }
- regionPoints.Clear();
+ using var vec = new VectorOfPoint(regionPoints.ToArray());
+ CvInvoke.FillPoly(mat, vec, document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
}
-
- var pt = new Point((int) (nowX * xyPpmm.Width), (int) (nowY * xyPpmm.Height));
- if (regionPoints.Count == 0 || (regionPoints.Count > 0 && regionPoints[^1] != pt)) regionPoints.Add(pt);
+ regionPoints.Clear();
}
- else if(currentAperture is not null)
+
+ var pt = PositionMmToPx(nowX, nowY, xyPpmm);
+ if (regionPoints.Count == 0 || (regionPoints.Count > 0 && regionPoints[^1] != pt)) regionPoints.Add(pt);
+ }
+ else if(currentAperture is not null)
+ {
+ if (d == 1)
{
- if (d == 1)
+ if (currentAperture is CircleAperture circleAperture)
{
- if (currentAperture is CircleAperture circleAperture)
+ if (document.MoveType is GerberMoveType.Arc or GerberMoveType.ArcCounterClockwise)
{
- if (document.MoveType is GerberMoveType.Arc or GerberMoveType.ArcCounterClockwise)
+ 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 (double.TryParse(matchI.Groups[1].Value, out var xOffset))
{
- 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 (double.TryParse(matchI.Groups[1].Value, out var xOffset))
+ if (xOffset != 0)
{
- if (xOffset != 0)
+ if (document.CoordinateXFractionalDigits > matchI.Groups[1].Value.Length) // Leading zero omitted
+ {
+ double.TryParse($"0.{matchI.Groups[1].Value}", out xOffset);
+ }
+ else
{
var integers = matchI.Groups[1].Value[..^document.CoordinateXFractionalDigits];
var fraction = matchI.Groups[1].Value.Substring(matchI.Groups[1].Value.Length - document.CoordinateXFractionalDigits, document.CoordinateXFractionalDigits);
double.TryParse($"{integers}.{fraction}", out xOffset);
}
+
}
+ }
- if (double.TryParse(matchJ.Groups[1].Value, out var yOffset))
+ if (double.TryParse(matchJ.Groups[1].Value, out var yOffset))
+ {
+ if(yOffset != 0)
{
- if(yOffset != 0)
+ if (document.CoordinateYFractionalDigits > matchJ.Groups[1].Value.Length) // Leading zero omitted
+ {
+ double.TryParse($"0.{matchJ.Groups[1].Value}", out yOffset);
+ }
+ else
{
var integers = matchJ.Groups[1].Value[..^document.CoordinateYFractionalDigits];
var fraction = matchJ.Groups[1].Value.Substring(matchJ.Groups[1].Value.Length - document.CoordinateYFractionalDigits, document.CoordinateYFractionalDigits);
double.TryParse($"{integers}.{fraction}", out yOffset);
}
}
+ }
- if (currentX == nowX && currentY == nowY) // Closed circle
- {
- CvInvoke.Ellipse(mat, new Point((int)((nowX + xOffset) * xyPpmm.Width), (int)((nowY + yOffset) * xyPpmm.Height)),
- new Size((int)(Math.Abs(xOffset) * xyPpmm.Width), (int)(Math.Abs(xOffset) * xyPpmm.Width)),
- 0, 0, 360.0, document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor,
- (int)(circleAperture.Diameter * xyPpmm.Max()),
- enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected
- );
- }
- else
- {
- // TODO: Fix this
- throw new NotImplementedException("Partial arcs are not yet implemented (G03)\nTo be fixed in the future...");
- /*CvInvoke.Ellipse(mat, new Point((int)((nowX + xOffset) * xyPpmm.Width), (int)((currentY) * xyPpmm.Height)),
- new Size((int)(Math.Abs(xOffset) * xyPpmm.Width), (int)(Math.Abs(yOffset) * xyPpmm.Width)),
- 0, Math.Abs(currentY - nowY), 360.0 / Math.Abs(currentX - nowX), document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor,
- (int)(circleAperture.Diameter * xyPpmm.Max()),
- enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected
- );*/
- }
-
+ if (currentX == nowX && currentY == nowY) // Closed circle
+ {
+ CvInvoke.Ellipse(mat,
+ PositionMmToPx(nowX + xOffset, nowY + yOffset, xyPpmm),
+ SizeMmToPx(Math.Abs(xOffset), Math.Abs(xOffset), xyPpmm),
+ 0, 0, 360.0, document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor,
+ SizeMmToPx(circleAperture.Diameter, xyPpmm.Max()),
+ enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected
+ );
}
else
{
- CvInvoke.Line(mat,
- new Point((int)(currentX * xyPpmm.Width), (int)(currentY * xyPpmm.Height)),
- new Point((int)(nowX * xyPpmm.Width), (int)(nowY * xyPpmm.Height)),
- document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor,
+ // TODO: Fix this
+ throw new NotImplementedException("Partial arcs are not yet implemented (G03)\nTo be fixed in the future...");
+ /*CvInvoke.Ellipse(mat, new Point((int)((nowX + xOffset) * xyPpmm.Width), (int)((currentY) * xyPpmm.Height)),
+ new Size((int)(Math.Abs(xOffset) * xyPpmm.Width), (int)(Math.Abs(yOffset) * xyPpmm.Width)),
+ 0, Math.Abs(currentY - nowY), 360.0 / Math.Abs(currentX - nowX), document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor,
(int)(circleAperture.Diameter * xyPpmm.Max()),
- enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
+ enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected
+ );*/
}
-
+
}
- }
- else if (d == 3)
- {
- currentAperture.DrawFlashD3(mat, xyPpmm, new Point((int)(nowX * xyPpmm.Width), (int)(nowY * xyPpmm.Height)), document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
+ else
+ {
+ CvInvoke.Line(mat,
+ PositionMmToPx(currentX, currentY, xyPpmm),
+ PositionMmToPx(nowX, nowY, xyPpmm),
+ document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor,
+ SizeMmToPx(circleAperture.Diameter, xyPpmm.Max()),
+ enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
+ }
+
}
}
-
- currentX = nowX;
- currentY = nowY;
- continue;
+ else if (d == 3)
+ {
+ currentAperture.DrawFlashD3(mat, xyPpmm, new PointF((float) nowX, (float) nowY),
+ document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
+ }
}
+
+ currentX = nowX;
+ currentY = nowY;
+ continue;
}
-
- return document;
}
- }
-}
+ return document;
+ }
+
+ public static Point PositionMmToPx(PointF atMm, SizeF xyPpmm)
+ => new((int)Math.Round(atMm.X * xyPpmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atMm.Y * xyPpmm.Height, MidpointRounding.AwayFromZero));
+
+ public static Point PositionMmToPx(double atXmm, double atYmm, SizeF xyPpmm)
+ => new((int)Math.Round(atXmm * xyPpmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atYmm * xyPpmm.Height, MidpointRounding.AwayFromZero));
+
+ public static Point PositionMmToPx(float atXmm, float atYmm, SizeF xyPpmm)
+ => new((int)Math.Round(atXmm * xyPpmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atYmm * xyPpmm.Height, MidpointRounding.AwayFromZero));
+
+ public static Size SizeMmToPx(SizeF sizeMm, SizeF xyPpmm)
+ => new((int)Math.Max(1, Math.Round(sizeMm.Width * xyPpmm.Width, MidpointRounding.AwayFromZero)),
+ (int)Math.Max(1, Math.Round(sizeMm.Height * xyPpmm.Height, MidpointRounding.AwayFromZero)));
+
+ public static Size SizeMmToPx(double sizeXmm, double sizeYmm, SizeF xyPpmm)
+ => new((int)Math.Max(1, Math.Round(sizeXmm * xyPpmm.Width, MidpointRounding.AwayFromZero)),
+ (int)Math.Max(1, Math.Round(sizeYmm * xyPpmm.Height, MidpointRounding.AwayFromZero)));
+
+ public static Size SizeMmToPx(float sizeXmm, float sizeYmm, SizeF xyPpmm)
+ => new((int)Math.Max(1, Math.Round(sizeXmm * xyPpmm.Width, MidpointRounding.AwayFromZero)),
+ (int)Math.Max(1, Math.Round(sizeYmm * xyPpmm.Height, MidpointRounding.AwayFromZero)));
+
+ public static int SizeMmToPx(float sizeMm, float ppmm)
+ => (int) Math.Max(1, Math.Round(sizeMm * ppmm, MidpointRounding.AwayFromZero));
+
+ public static int SizeMmToPx(double sizeMm, float ppmm)
+ => (int)Math.Max(1, Math.Round(sizeMm * ppmm, MidpointRounding.AwayFromZero));
+}
/* KIDCAD
diff --git a/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs b/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs
index d882eea..5ac51fd 100644
--- a/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs
+++ b/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs
@@ -6,6 +6,7 @@
* of this license document, but changing it is not allowed.
*/
+using System;
using System.Data;
using System.Drawing;
using System.Text.RegularExpressions;
@@ -87,7 +88,8 @@ public class CenterLinePrimitive : Primitive
}
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color, LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color,
+ LineType lineType = LineType.EightConnected)
{
if (!IsParsed) return;
if (Width <= 0 || Height <= 0) return;
@@ -96,9 +98,9 @@ public class CenterLinePrimitive : Primitive
else if(color.V0 == 0) color = EmguExtensions.WhiteColor;
var halfWidth = Width / 2;
- var pt1 = new Point(at.X + (int)((CenterX - halfWidth) * xyPpmm.Width), at.Y + (int)(CenterY * xyPpmm.Height));
- var pt2 = new Point(at.X + (int)((CenterX + halfWidth) * xyPpmm.Width), at.Y + (int)(CenterY * xyPpmm.Height));
- CvInvoke.Line(mat, pt1, pt2, color, (int)(Height * xyPpmm.Height), lineType);
+ var pt1 = GerberDocument.PositionMmToPx(at.X + CenterX - halfWidth, at.Y + CenterY, xyPpmm);
+ var pt2 = GerberDocument.PositionMmToPx(at.X + CenterX + halfWidth, at.Y + CenterY, xyPpmm);
+ CvInvoke.Line(mat, pt1, pt2, color, GerberDocument.SizeMmToPx(Height, xyPpmm.Height), lineType);
//CvInvoke.Rectangle(mat, rectangle, color, -1, lineType);
}
diff --git a/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs b/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs
index 73eb04d..601cf0f 100644
--- a/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs
+++ b/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs
@@ -78,7 +78,8 @@ public class CirclePrimitive : Primitive
RotationExpression = rotationExpression;
}
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color, LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color,
+ LineType lineType = LineType.EightConnected)
{
if (!IsParsed) return;
if (Diameter <= 0) return;
@@ -86,11 +87,9 @@ public class CirclePrimitive : Primitive
if (Exposure == 0) color = EmguExtensions.BlackColor;
else if (color.V0 == 0) color = EmguExtensions.WhiteColor;
- var position = new Point(
- (int) (at.X + CenterX * xyPpmm.Width),
- (int) (at.Y + CenterY * xyPpmm.Height)
- );
- CvInvoke.Circle(mat, position, (int)(Diameter * xyPpmm.Max() / 2), color, -1, lineType);
+ CvInvoke.Circle(mat,
+ GerberDocument.PositionMmToPx(at.X + CenterX, at.Y + CenterY, xyPpmm),
+ GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()), color, -1, lineType);
}
public override void ParseExpressions(params string[] args)
diff --git a/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs b/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs
index 66596c3..25a9b07 100644
--- a/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs
+++ b/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs
@@ -45,8 +45,7 @@ public class CommentPrimitive : Primitive
}
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color,
- LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected)
{
}
diff --git a/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs b/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs
index 0addbcf..93a505a 100644
--- a/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs
+++ b/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs
@@ -75,7 +75,7 @@ public class OutlinePrimitive : Primitive
}
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color, LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected)
{
if (Coordinates.Length < 3) return;
@@ -85,11 +85,7 @@ public class OutlinePrimitive : Primitive
var points = new List();
for (int i = 0; i < Coordinates.Length-1; i++)
{
- var pt = new Point(
- at.X + (int)(Coordinates[i].X * xyPpmm.Width),
- at.Y + (int)(Coordinates[i].Y * xyPpmm.Height)
- );
-
+ var pt = GerberDocument.PositionMmToPx(at.X + Coordinates[i].X, at.Y + Coordinates[i].Y, xyPpmm);
if(i > 0 && points[i-1] == pt) continue; // Prevent duplicates
points.Add(pt);
}
diff --git a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs
index 88d6fb8..1657336 100644
--- a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs
+++ b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs
@@ -6,6 +6,7 @@
* of this license document, but changing it is not allowed.
*/
+using System;
using System.Data;
using System.Drawing;
using System.Text.RegularExpressions;
@@ -84,7 +85,8 @@ public class PolygonPrimitive : Primitive
RotationExpression = rotationExpression;
}
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color, LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color,
+ LineType lineType = LineType.EightConnected)
{
if (!IsParsed) return;
if (Diameter <= 0) return;
@@ -92,12 +94,11 @@ public class PolygonPrimitive : Primitive
if (Exposure == 0) color = EmguExtensions.BlackColor;
else if (color.V0 == 0) color = EmguExtensions.WhiteColor;
- var position = new Point(
- (int) (at.X + CenterX * xyPpmm.Width),
- (int) (at.Y + CenterY * xyPpmm.Height)
- );
- mat.DrawPolygon(VerticesCount, (int)(Diameter * xyPpmm.Max() / 2), position, color, 0, -1, lineType);
+ mat.DrawPolygon(VerticesCount,
+ GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()),
+ GerberDocument.PositionMmToPx(at.X + CenterX, at.Y + CenterY, xyPpmm),
+ color, 0, -1, lineType);
}
public override void ParseExpressions(params string[] args)
diff --git a/UVtools.Core/Gerber/Primitives/Primitive.cs b/UVtools.Core/Gerber/Primitives/Primitive.cs
index 51dd0d9..9658a24 100644
--- a/UVtools.Core/Gerber/Primitives/Primitive.cs
+++ b/UVtools.Core/Gerber/Primitives/Primitive.cs
@@ -7,7 +7,6 @@
*/
using System.Drawing;
-using System.Text.RegularExpressions;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
@@ -25,7 +24,7 @@ public abstract class Primitive
protected Primitive() { }
- public abstract void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color, LineType lineType = LineType.EightConnected);
+ public abstract void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected);
public abstract void ParseExpressions(params string[] args);
}
\ No newline at end of file
diff --git a/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs b/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs
index 6c165dd..94f712a 100644
--- a/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs
+++ b/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs
@@ -6,6 +6,7 @@
* of this license document, but changing it is not allowed.
*/
+using System;
using System.Data;
using System.Drawing;
using System.Text.RegularExpressions;
@@ -96,7 +97,8 @@ public class VectorLinePrimitive : Primitive
RotationExpression = rotationExpression;
}
- public override void DrawFlashD3(Mat mat, SizeF xyPpmm, Point at, MCvScalar color, LineType lineType = LineType.EightConnected)
+ public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color,
+ LineType lineType = LineType.EightConnected)
{
if (!IsParsed) return;
if (LineWidth <= 0) return;
@@ -104,9 +106,9 @@ public class VectorLinePrimitive : Primitive
if (Exposure == 0) color = EmguExtensions.BlackColor;
else if (color.V0 == 0) color = EmguExtensions.WhiteColor;
- var pt1 = new Point(at.X + (int) (StartX * xyPpmm.Width), at.Y + (int) (StartY * xyPpmm.Height));
- var pt2 = new Point(at.X + (int) (EndX * xyPpmm.Height), at.Y + (int) (EndY * xyPpmm.Height));
- CvInvoke.Line(mat, pt1, pt2, color, (int)(LineWidth * xyPpmm.Height), lineType);
+ var pt1 = GerberDocument.PositionMmToPx(at.X + StartX, at.Y + StartY, xyPpmm);
+ var pt2 = GerberDocument.PositionMmToPx(at.X + EndX, at.Y + EndY, xyPpmm);
+ CvInvoke.Line(mat, pt1, pt2, color, GerberDocument.SizeMmToPx(LineWidth, xyPpmm.Height), lineType);
//CvInvoke.Rectangle(mat, rectangle, color, -1, lineType);
}
diff --git a/UVtools.Core/MeshFormats/AMFMeshFile.cs b/UVtools.Core/MeshFormats/AMFMeshFile.cs
index b4e43f4..19ce4fd 100644
--- a/UVtools.Core/MeshFormats/AMFMeshFile.cs
+++ b/UVtools.Core/MeshFormats/AMFMeshFile.cs
@@ -37,7 +37,7 @@ public class AMFMeshFile : MeshFile
public override void BeginWrite()
{
/* Create a stream to store the triangles (faces) as they come through */
- _triangleStream = new FileStream(PathExtensions.GetTempFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
+ _triangleStream = new FileStream(PathExtensions.GetTemporaryFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
MeshStream.WriteLineLF("");
MeshStream.WriteLineLF("");
@@ -122,7 +122,7 @@ public class AMFMeshFile : MeshFile
MeshStream.WriteLineLF("\t");
MeshStream.WriteLineLF("");
- var tmpFile = PathExtensions.GetTempFilePathWithExtension("tmp", $"{About.Software}_");
+ var tmpFile = PathExtensions.GetTemporaryFilePathWithExtension("tmp", $"{About.Software}_");
if (File.Exists(tmpFile)) File.Delete(tmpFile);
using (var zip = ZipFile.Open(tmpFile, ZipArchiveMode.Create))
{
diff --git a/UVtools.Core/MeshFormats/Consortium3MFMeshFile.cs b/UVtools.Core/MeshFormats/Consortium3MFMeshFile.cs
index 01a1601..84ca35a 100644
--- a/UVtools.Core/MeshFormats/Consortium3MFMeshFile.cs
+++ b/UVtools.Core/MeshFormats/Consortium3MFMeshFile.cs
@@ -37,7 +37,7 @@ public class Consortium3MFMeshFile : MeshFile
public override void BeginWrite()
{
/* Create a stream to store the triangles (faces) as they come through */
- _triangleStream = new FileStream(PathExtensions.GetTempFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
+ _triangleStream = new FileStream(PathExtensions.GetTemporaryFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
MeshStream.WriteLineLF("");
MeshStream.WriteLineLF("");
@@ -105,7 +105,7 @@ public class Consortium3MFMeshFile : MeshFile
MeshStream.WriteLineLF("\t");
MeshStream.WriteLineLF("");
- var tmpFile = PathExtensions.GetTempFilePathWithExtension("tmp", $"{About.Software}_");
+ var tmpFile = PathExtensions.GetTemporaryFilePathWithExtension("tmp", $"{About.Software}_");
if (File.Exists(tmpFile)) File.Delete(tmpFile);
bool haveThumbnail = SlicerFile is not null && SlicerFile.CreatedThumbnailsCount > 0;
using (var zip = ZipFile.Open(tmpFile, ZipArchiveMode.Create))
diff --git a/UVtools.Core/MeshFormats/OBJMeshFile.cs b/UVtools.Core/MeshFormats/OBJMeshFile.cs
index 9b0dc69..4827d19 100644
--- a/UVtools.Core/MeshFormats/OBJMeshFile.cs
+++ b/UVtools.Core/MeshFormats/OBJMeshFile.cs
@@ -39,7 +39,7 @@ public class OBJMeshFile : MeshFile
public override void BeginWrite()
{
/* Create a stream to store the triangles (faces) as they come through */
- _triangleStream = new FileStream(PathExtensions.GetTempFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
+ _triangleStream = new FileStream(PathExtensions.GetTemporaryFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
MeshStream.WriteLine($"# {HeaderComment}");
MeshStream.WriteLine($"o {ObjectName}");
diff --git a/UVtools.Core/MeshFormats/OFFMeshFile.cs b/UVtools.Core/MeshFormats/OFFMeshFile.cs
index 49b240d..1addab0 100644
--- a/UVtools.Core/MeshFormats/OFFMeshFile.cs
+++ b/UVtools.Core/MeshFormats/OFFMeshFile.cs
@@ -34,7 +34,7 @@ public class OFFMeshFile : MeshFile
public override void BeginWrite()
{
/* Create a stream to store the triangles (faces) as they come through */
- _triangleStream = new FileStream(PathExtensions.GetTempFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
+ _triangleStream = new FileStream(PathExtensions.GetTemporaryFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
MeshStream.WriteLineLF("OFF");
MeshStream.WriteLineLF($"# {HeaderComment}");
diff --git a/UVtools.Core/MeshFormats/PLYMeshFile.cs b/UVtools.Core/MeshFormats/PLYMeshFile.cs
index 5733f72..579af22 100644
--- a/UVtools.Core/MeshFormats/PLYMeshFile.cs
+++ b/UVtools.Core/MeshFormats/PLYMeshFile.cs
@@ -35,7 +35,7 @@ public class PLYMeshFile : MeshFile
public override void BeginWrite()
{
/* Create a stream to store the triangles (faces) as they come through */
- _triangleStream = new FileStream(PathExtensions.GetTempFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
+ _triangleStream = new FileStream(PathExtensions.GetTemporaryFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
MeshStream.WriteLineLF("ply");
MeshStream.WriteLineLF(FileFormat == MeshFileFormat.ASCII
diff --git a/UVtools.Core/MeshFormats/WRLMeshFile.cs b/UVtools.Core/MeshFormats/WRLMeshFile.cs
index 392299d..fe36de1 100644
--- a/UVtools.Core/MeshFormats/WRLMeshFile.cs
+++ b/UVtools.Core/MeshFormats/WRLMeshFile.cs
@@ -33,7 +33,7 @@ public class WRLMeshFile : MeshFile
public override void BeginWrite()
{
/* Create a stream to store the triangles (faces) as they come through */
- _triangleStream = new FileStream(PathExtensions.GetTempFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
+ _triangleStream = new FileStream(PathExtensions.GetTemporaryFilePathWithExtension("trig", $"{About.Software}_"), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 81920, FileOptions.DeleteOnClose);
MeshStream.WriteLineLF("#VRML V2.0 utf8");
MeshStream.WriteLineLF($"WorldInfo {{ info \"{HeaderComment}\" }}");
diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs
index d1b50b4..4bc6802 100644
--- a/UVtools.Core/Operations/Operation.cs
+++ b/UVtools.Core/Operations/Operation.cs
@@ -805,7 +805,7 @@ public abstract class Operation : BindableBase, IDisposable
return result;
}
- public virtual void Dispose() { GC.SuppressFinalize(this); }
+ public virtual void Dispose() { /*GC.SuppressFinalize(this);*/ }
#endregion
#region Static Methods
diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs
index 6d39391..b7ae234 100644
--- a/UVtools.Core/Operations/OperationBlur.cs
+++ b/UVtools.Core/Operations/OperationBlur.cs
@@ -27,7 +27,7 @@ public sealed class OperationBlur : Operation
#region Overrides
- public override string IconClass => "fas fa-burn";
+ public override string IconClass => "fa-solid fa-burn";
public override string Title => "Blur";
public override string Description =>
$"Blur layer images by applying a low pass filter.\n\n" +
diff --git a/UVtools.Core/Operations/OperationCalculator.cs b/UVtools.Core/Operations/OperationCalculator.cs
index fe407af..ec529cc 100644
--- a/UVtools.Core/Operations/OperationCalculator.cs
+++ b/UVtools.Core/Operations/OperationCalculator.cs
@@ -21,7 +21,7 @@ public class OperationCalculator : Operation
public override bool CanRunInPartialMode => true;
- public override string IconClass => "fas fa-calculator";
+ public override string IconClass => "fa-solid fa-calculator";
public override string Title => "Calculator";
public override string Description => null!;
diff --git a/UVtools.Core/Operations/OperationCalibrateBloomingEffect.cs b/UVtools.Core/Operations/OperationCalibrateBloomingEffect.cs
new file mode 100644
index 0000000..12d6945
--- /dev/null
+++ b/UVtools.Core/Operations/OperationCalibrateBloomingEffect.cs
@@ -0,0 +1,428 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc.
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+using Emgu.CV;
+using Emgu.CV.CvEnum;
+using Emgu.CV.Structure;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Text;
+using UVtools.Core.Extensions;
+using UVtools.Core.FileFormats;
+using UVtools.Core.Layers;
+
+namespace UVtools.Core.Operations;
+
+[Serializable]
+public sealed class OperationCalibrateBloomingEffect : Operation
+{
+ #region Members
+ private decimal _layerHeight;
+ private ushort _bottomLayers = 3;
+ private decimal _bottomExposure;
+ private decimal _normalExposure;
+ private ushort _leftRightMargin = 200;
+ private ushort _topBottomMargin = 200;
+ private decimal _waitTimeBeforeCureStart;
+ private decimal _waitTimeBeforeCureIncrement = 1;
+ private byte _objectCount = 15;
+ private ushort _objectDiameter = 400;
+ private decimal _objectHeight = 5;
+ private ushort _objectMargin = 20;
+ private bool _mirrorOutput;
+
+ #endregion
+
+ #region Overrides
+
+ public override bool CanROI => false;
+
+ public override bool CanCancel => false;
+
+ public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
+ public override string IconClass => "fa-solid fa-sun";
+ public override string Title => "Blooming effect";
+ public override string Description =>
+ "Generates test models with various strategies and increments to measure the blooming effect.\n" +
+ "You must repeat this test when change any of the following: printer, LEDs, resin and exposure times.\n" +
+ "Note: The current opened file will be overwritten with this test, use a dummy or a not needed file.";
+
+ public override string ConfirmationText =>
+ $"generate the bloom effect test?";
+
+ public override string ProgressTitle =>
+ $"Generating the bloom effect test";
+
+ public override string ProgressAction => "Generated";
+
+ public override string? ValidateSpawn()
+ {
+ if (!SlicerFile.CanUseLayerPositionZ)
+ {
+ return $"{NotSupportedMessage}\nReason: Can't use more than one layer per same position.";
+ }
+
+ if (!SlicerFile.CanUseLayerWaitTimeBeforeCure && !SlicerFile.CanUseLayerLightOffDelay)
+ {
+ return $"{NotSupportedMessage}\nReason: No per layer wait time before cure capabilities.";
+ }
+
+
+ return null;
+ }
+
+ public override string? ValidateInternally()
+ {
+ var sb = new StringBuilder();
+
+ if (SlicerFile.ResolutionX - _leftRightMargin * 2 <= 0)
+ sb.AppendLine("The top/bottom margin is too big, it overlaps the screen resolution.");
+
+ if (SlicerFile.ResolutionY - _topBottomMargin * 2 <= 0)
+ sb.AppendLine("The top/bottom margin is too big, it overlaps the screen resolution.");
+
+ if (_leftRightMargin + _objectDiameter > SlicerFile.ResolutionX - _leftRightMargin)
+ sb.AppendLine("The top/bottom margin or object diameter is too big, it overlaps the screen resolution.");
+
+ if (_topBottomMargin + _objectDiameter > SlicerFile.ResolutionY - _topBottomMargin)
+ sb.AppendLine("The top/bottom margin or object diameter is too big, it overlaps the screen resolution.");
+
+ return sb.ToString();
+ }
+
+ public override string ToString()
+ {
+ var result = $"[Layer Height: {_layerHeight}] " +
+ $"[Bottom layers: {_bottomLayers}] " +
+ $"[Exposure: {_bottomExposure}/{_normalExposure}s]" +
+ $"[Wait time: {_waitTimeBeforeCureStart}s] [Increment: {_waitTimeBeforeCureIncrement}s] " +
+ $"[Objects: {_objectCount}] [Height: {_objectHeight}mm] [Diameter: {_objectDiameter}px]";
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
+
+ #endregion
+
+ #region Properties
+
+ public decimal LayerHeight
+ {
+ get => _layerHeight;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
+ RaisePropertyChanged(nameof(BottomHeight));
+ }
+ }
+
+ public ushort BottomLayers
+ {
+ get => _bottomLayers;
+ set => RaiseAndSetIfChanged(ref _bottomLayers, value);
+ }
+
+ public ushort Microns => (ushort) (LayerHeight * 1000);
+
+ public decimal BottomHeight => Layer.RoundHeight(_layerHeight * _bottomLayers);
+
+ public decimal BottomExposure
+ {
+ get => _bottomExposure;
+ set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2));
+ }
+
+ public decimal NormalExposure
+ {
+ get => _normalExposure;
+ set => RaiseAndSetIfChanged(ref _normalExposure, Math.Round(value, 2));
+ }
+
+ public ushort LeftRightMargin
+ {
+ get => _leftRightMargin;
+ set => RaiseAndSetIfChanged(ref _leftRightMargin, value);
+ }
+
+ public ushort MaxLeftRightMargin => (ushort)((SlicerFile.ResolutionX - 100) / 2);
+
+ public ushort TopBottomMargin
+ {
+ get => _topBottomMargin;
+ set => RaiseAndSetIfChanged(ref _topBottomMargin, value);
+ }
+
+ public ushort MaxTopBottomMargin => (ushort) ((SlicerFile.ResolutionY - 100) / 2);
+
+ public decimal WaitTimeBeforeCureStart
+ {
+ get => _waitTimeBeforeCureStart;
+ set => RaiseAndSetIfChanged(ref _waitTimeBeforeCureStart, Math.Round(Math.Max(0, value), 2));
+ }
+
+ public decimal WaitTimeBeforeCureIncrement
+ {
+ get => _waitTimeBeforeCureIncrement;
+ set => RaiseAndSetIfChanged(ref _waitTimeBeforeCureIncrement, Math.Round(Math.Max(0.05m, value), 2));
+ }
+
+ public byte ObjectCount
+ {
+ get => _objectCount;
+ set => RaiseAndSetIfChanged(ref _objectCount, Math.Max((byte)1, value));
+ }
+
+ public decimal MaximumWaiTimeBeforeCure => Math.Round(_waitTimeBeforeCureStart + _waitTimeBeforeCureIncrement * _objectCount, 2);
+
+ public ushort ObjectDiameter
+ {
+ get => _objectDiameter;
+ set => RaiseAndSetIfChanged(ref _objectDiameter, Math.Max((ushort)20, value));
+ }
+
+ public decimal ObjectHeight
+ {
+ get => _objectHeight;
+ set => RaiseAndSetIfChanged(ref _objectHeight, value);
+ }
+
+ public ushort ObjectMargin
+ {
+ get => _objectMargin;
+ set => RaiseAndSetIfChanged(ref _objectMargin, value);
+ }
+
+ public bool MirrorOutput
+ {
+ get => _mirrorOutput;
+ set => RaiseAndSetIfChanged(ref _mirrorOutput, value);
+ }
+
+ #endregion
+
+ #region Constructor
+
+ public OperationCalibrateBloomingEffect() { }
+
+ public OperationCalibrateBloomingEffect(FileFormat slicerFile) : base(slicerFile)
+ { }
+
+ public override void InitWithSlicerFile()
+ {
+ base.InitWithSlicerFile();
+ if(_layerHeight <= 0) _layerHeight = (decimal)SlicerFile.LayerHeight;
+ if(_bottomExposure <= 0) _bottomExposure = (decimal)SlicerFile.BottomExposureTime;
+ if(_normalExposure <= 0) _normalExposure = (decimal)SlicerFile.ExposureTime;
+ _mirrorOutput = SlicerFile.DisplayMirror != FlipDirection.None;
+ }
+
+ #endregion
+
+ #region Equality
+
+
+ private bool Equals(OperationCalibrateBloomingEffect other)
+ {
+ return _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _leftRightMargin == other._leftRightMargin && _topBottomMargin == other._topBottomMargin && _waitTimeBeforeCureStart == other._waitTimeBeforeCureStart && _waitTimeBeforeCureIncrement == other._waitTimeBeforeCureIncrement && _objectCount == other._objectCount && _objectDiameter == other._objectDiameter && _objectHeight == other._objectHeight && _objectMargin == other._objectMargin && _mirrorOutput == other._mirrorOutput;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return ReferenceEquals(this, obj) || obj is OperationCalibrateBloomingEffect other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+ hashCode.Add(_layerHeight);
+ hashCode.Add(_bottomLayers);
+ hashCode.Add(_bottomExposure);
+ hashCode.Add(_normalExposure);
+ hashCode.Add(_leftRightMargin);
+ hashCode.Add(_topBottomMargin);
+ hashCode.Add(_waitTimeBeforeCureStart);
+ hashCode.Add(_waitTimeBeforeCureIncrement);
+ hashCode.Add(_objectCount);
+ hashCode.Add(_objectDiameter);
+ hashCode.Add(_objectHeight);
+ hashCode.Add(_objectMargin);
+ hashCode.Add(_mirrorOutput);
+ return hashCode.ToHashCode();
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Gets the layers
+ ///
+ ///
+ public Mat GetLayerPreview()
+ {
+ var mat = SlicerFile.CreateMat();
+
+ uint currentX = _leftRightMargin;
+ uint currentY = _topBottomMargin;
+ uint maxWidth = SlicerFile.ResolutionX - _leftRightMargin;
+ uint maxHeight = SlicerFile.ResolutionY - _topBottomMargin;
+ var maxWaitTime = MaximumWaiTimeBeforeCure;
+
+ for (decimal waitTime = _waitTimeBeforeCureStart; waitTime <= maxWaitTime; waitTime += _waitTimeBeforeCureIncrement)
+ {
+ if (currentX + _objectDiameter > maxWidth)
+ {
+ currentX = _leftRightMargin;
+ currentY += (uint)(_objectDiameter + _objectMargin);
+ if (currentY + _objectDiameter > maxHeight) break;
+ }
+
+ waitTime = Math.Round(waitTime, 2);
+
+ CvInvoke.Rectangle(mat, new Rectangle((int)currentX, (int)currentY, _objectDiameter, _objectDiameter), EmguExtensions.WhiteColor, -1);
+ mat.PutTextExtended($"E: {_bottomExposure}s/{_normalExposure}s\nW: {waitTime}s", new Point((int) currentX+20, (int) currentY + _objectDiameter / 2), FontFace.HersheyDuplex, 2.0, EmguExtensions.BlackColor, 2, 10);
+ currentX += (uint)(_objectDiameter + _objectMargin);
+ }
+
+ return mat;
+ }
+
+ public Mat GetThumbnail()
+ {
+ Mat thumbnail = EmguExtensions.InitMat(new Size(400, 200), 3);
+ var fontFace = FontFace.HersheyDuplex;
+ var fontScale = 1;
+ var fontThickness = 2;
+ const byte xSpacing = 45;
+ const byte ySpacing = 45;
+ CvInvoke.PutText(thumbnail, "UVtools", new Point(140, 35), fontFace, fontScale, new MCvScalar(255, 27, 245), fontThickness + 1);
+ CvInvoke.Line(thumbnail, new Point(xSpacing, 0), new Point(xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3);
+ CvInvoke.Line(thumbnail, new Point(xSpacing, ySpacing + 5), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3);
+ CvInvoke.Line(thumbnail, new Point(thumbnail.Width - xSpacing, 0), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3);
+ CvInvoke.PutText(thumbnail, "Bloom Effect Cal.", new Point(xSpacing, ySpacing * 2), fontFace, fontScale, new MCvScalar(0, 255, 255), fontThickness);
+ CvInvoke.PutText(thumbnail, $"{Microns}um @ {BottomExposure}s/{NormalExposure}s", new Point(xSpacing, ySpacing * 3), fontFace, fontScale, EmguExtensions.WhiteColor, fontThickness);
+ CvInvoke.PutText(thumbnail, $"Wait: {_waitTimeBeforeCureStart}s/+{_waitTimeBeforeCureIncrement}s", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.WhiteColor, fontThickness);
+
+ return thumbnail;
+ }
+
+ protected override bool ExecuteInternally(OperationProgress progress)
+ {
+ progress.ItemCount = 1;
+
+ var newLayers = new List();
+
+ if (SlicerFile.ThumbnailsCount > 0)
+ SlicerFile.SetThumbnails(GetThumbnail());
+
+ var maxWaitTime = MaximumWaiTimeBeforeCure;
+ var flip = SlicerFile.DisplayMirror;
+ if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
+
+ SlicerFile.SuppressRebuildPropertiesWork(() =>
+ {
+ float currentPosition = (float)_layerHeight;
+ SlicerFile.LayerHeight = currentPosition;
+ SlicerFile.BottomExposureTime = (float)_bottomExposure;
+ SlicerFile.ExposureTime = (float)_normalExposure;
+ SlicerFile.BottomLayerCount = _bottomLayers;
+ SlicerFile.TransitionLayerCount = 0;
+ SlicerFile.BottomLightOffDelay = 0;
+ SlicerFile.LightOffDelay = 0;
+ SlicerFile.SetBottomWaitTimeBeforeCureOrLightOffDelay((float)_waitTimeBeforeCureStart);
+ SlicerFile.SetNormalWaitTimeBeforeCureOrLightOffDelay((float)(_waitTimeBeforeCureStart + _waitTimeBeforeCureIncrement * _objectCount));
+
+
+ uint currentX = _leftRightMargin;
+ uint currentY = _topBottomMargin;
+ uint maxWidth = SlicerFile.ResolutionX - _leftRightMargin;
+ uint maxHeight = SlicerFile.ResolutionY - _topBottomMargin;
+
+ for (decimal waitTime = _waitTimeBeforeCureStart; waitTime <= maxWaitTime; waitTime += _waitTimeBeforeCureIncrement)
+ {
+ if (currentX + _objectDiameter > maxWidth)
+ {
+ currentX = _leftRightMargin;
+ currentY += (uint)(_objectDiameter + _objectMargin);
+ if(currentY + _objectDiameter > maxHeight) break;
+ }
+
+ waitTime = Math.Round(waitTime, 2);
+
+ using var mat = SlicerFile.CreateMat();
+ CvInvoke.Rectangle(mat, new Rectangle((int)currentX, (int)currentY, _objectDiameter, _objectDiameter), EmguExtensions.WhiteColor, -1);
+ if (_mirrorOutput) CvInvoke.Flip(mat, mat, (FlipType)flip);
+ var layer = new Layer(mat, SlicerFile)
+ {
+ PositionZ = currentPosition
+ };
+ layer.SetWaitTimeBeforeCureOrLightOffDelay((float) waitTime);
+ newLayers.Add(layer);
+
+ currentX += (uint)(_objectDiameter + _objectMargin);
+ }
+
+ var objects = newLayers.Count;
+ int heightLayers = (int)Math.Ceiling(_objectHeight / (decimal)SlicerFile.LayerHeight);
+
+ for (int h = 0; h < heightLayers; h++)
+ {
+ currentPosition += (float)_layerHeight;
+ for (int i = 0; i < objects; i++)
+ {
+ var layer = newLayers[i].Clone();
+ layer.PositionZ = currentPosition;
+ newLayers.Add(layer);
+ }
+ }
+
+ using var textMat = SlicerFile.CreateMat();
+ currentX = _leftRightMargin;
+ currentY = _topBottomMargin;
+ for (decimal waitTime = _waitTimeBeforeCureStart; waitTime <= maxWaitTime; waitTime += _waitTimeBeforeCureIncrement)
+ {
+ if (currentX + _objectDiameter > maxWidth)
+ {
+ currentX = _leftRightMargin;
+ currentY += (uint)(_objectDiameter + _objectMargin);
+ if (currentY > maxHeight) break;
+ }
+
+ waitTime = Math.Round(waitTime, 2);
+
+ textMat.PutTextExtended($"E: {_bottomExposure}s/{_normalExposure}s\nW: {waitTime}s", new Point((int)currentX + 20, (int)currentY + _objectDiameter / 2),
+ FontFace.HersheyDuplex, 2.0, EmguExtensions.WhiteColor, 2, 10);
+ currentX += (uint)(_objectDiameter + _objectMargin);
+ }
+
+ if (_mirrorOutput) CvInvoke.Flip(textMat, textMat, (FlipType) flip);
+
+ var textLayerCount = 1 / _layerHeight;
+ for (int i = 0; i < textLayerCount; i++)
+ {
+ currentPosition += (float)_layerHeight;
+ var layer = new Layer(textMat, SlicerFile)
+ {
+ PositionZ = currentPosition
+ };
+ newLayers.Add(layer);
+ }
+
+
+ SlicerFile.Layers = newLayers.ToArray();
+ });
+
+
+ progress++;
+
+
+ return !progress.Token.IsCancellationRequested;
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/UVtools.Core/Operations/OperationCalibrateExternalTests.cs b/UVtools.Core/Operations/OperationCalibrateExternalTests.cs
index 8cc5f3c..45cb326 100644
--- a/UVtools.Core/Operations/OperationCalibrateExternalTests.cs
+++ b/UVtools.Core/Operations/OperationCalibrateExternalTests.cs
@@ -22,7 +22,7 @@ public class OperationCalibrateExternalTests : Operation
public override bool CanROI => false;
public override bool CanHaveProfiles => false;
public override string ButtonOkText => null!;
- public override string IconClass => "fas fa-bookmark";
+ public override string IconClass => "fa-solid fa-bookmark";
public override string Title => "External tests";
public override string Description =>
"A set of useful external tests to run within your slicer.\nClick on a button to open website and instructions.";
diff --git a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
index 010e3a4..acf6e71 100644
--- a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
+++ b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
@@ -55,7 +55,7 @@ public sealed class OperationCalibrateGrayscale : Operation
public override bool CanCancel => false;
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
- public override string IconClass => "fas fa-chart-pie";
+ public override string IconClass => "fa-solid fa-chart-pie";
public override string Title => "Grayscale";
public override string Description =>
"Generates test models with various strategies and increments to verify the LED power against the grayscale levels.\n" +
diff --git a/UVtools.Core/Operations/OperationCalibrateStressTower.cs b/UVtools.Core/Operations/OperationCalibrateStressTower.cs
index ce33999..4ba78d6 100644
--- a/UVtools.Core/Operations/OperationCalibrateStressTower.cs
+++ b/UVtools.Core/Operations/OperationCalibrateStressTower.cs
@@ -50,7 +50,7 @@ public sealed class OperationCalibrateStressTower : Operation
public override bool CanCancel => false;
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
- public override string IconClass => "fas fa-chess-rook";
+ public override string IconClass => "fa-solid fa-chess-rook";
public override string Title => "Stress tower";
public override string Description =>
"Generates a stress tower to test the printer capabilities.\n" +
diff --git a/UVtools.Core/Operations/OperationCalibrateTolerance.cs b/UVtools.Core/Operations/OperationCalibrateTolerance.cs
index c6b9460..dd5d7ba 100644
--- a/UVtools.Core/Operations/OperationCalibrateTolerance.cs
+++ b/UVtools.Core/Operations/OperationCalibrateTolerance.cs
@@ -60,7 +60,7 @@ public sealed class OperationCalibrateTolerance : Operation
public override bool CanCancel => false;
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
- public override string IconClass => "fas fa-dot-circle";
+ public override string IconClass => "fa-solid fa-dot-circle";
public override string Title => "Tolerance";
public override string Description =>
"Generates test models with various strategies and increments to verify the part tolerances.\n" +
diff --git a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
index dcee1aa..6de4c4f 100644
--- a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
+++ b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
@@ -61,7 +61,7 @@ public sealed class OperationCalibrateXYZAccuracy : Operation
public override bool CanCancel => false;
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
- public override string IconClass => "fas fa-cubes";
+ public override string IconClass => "fa-solid fa-cubes";
public override string Title => "XYZ Accuracy";
public override string Description =>
"Generates test models with various strategies and increments to verify the XYZ accuracy.\n" +
diff --git a/UVtools.Core/Operations/OperationDoubleExposure.cs b/UVtools.Core/Operations/OperationDoubleExposure.cs
index 9cfd608..bdefe8c 100644
--- a/UVtools.Core/Operations/OperationDoubleExposure.cs
+++ b/UVtools.Core/Operations/OperationDoubleExposure.cs
@@ -42,7 +42,7 @@ public class OperationDoubleExposure : Operation
#region Overrides
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.Bottom;
- public override string IconClass => "fas fa-grip-lines";
+ public override string IconClass => "fa-solid fa-grip-lines";
public override string Title => "Double exposure";
public override string Description =>
"The double exposure method clones the selected layer range and print the same layer twice with different exposure times and strategies.\n" +
diff --git a/UVtools.Core/Operations/OperationDynamicLifts.cs b/UVtools.Core/Operations/OperationDynamicLifts.cs
index 10e9e1f..78c199c 100644
--- a/UVtools.Core/Operations/OperationDynamicLifts.cs
+++ b/UVtools.Core/Operations/OperationDynamicLifts.cs
@@ -67,7 +67,7 @@ public sealed class OperationDynamicLifts : Operation
public override bool CanRunInPartialMode => true;
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.Normal;
- public override string IconClass => "fas fa-chart-line";
+ public override string IconClass => "fa-solid fa-chart-line";
public override string Title => "Dynamic lifts";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationEditParameters.cs b/UVtools.Core/Operations/OperationEditParameters.cs
index 515989b..35726fd 100644
--- a/UVtools.Core/Operations/OperationEditParameters.cs
+++ b/UVtools.Core/Operations/OperationEditParameters.cs
@@ -32,7 +32,7 @@ public class OperationEditParameters : Operation
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
public override bool CanROI => false;
- public override string IconClass => "fas fa-edit";
+ public override string IconClass => "fa-solid fa-edit";
public override string Title => "Edit print parameters";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationFadeExposureTime.cs b/UVtools.Core/Operations/OperationFadeExposureTime.cs
index a2fa304..8ea6b08 100644
--- a/UVtools.Core/Operations/OperationFadeExposureTime.cs
+++ b/UVtools.Core/Operations/OperationFadeExposureTime.cs
@@ -29,7 +29,7 @@ public class OperationFadeExposureTime : Operation
public override bool CanRunInPartialMode => true;
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.Normal;
public override bool LayerIndexEndEnabled => false;
- public override string IconClass => "fas fa-history";
+ public override string IconClass => "fa-solid fa-history";
public override string Title => "Fade exposure time";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationIPrintedThisFile.cs b/UVtools.Core/Operations/OperationIPrintedThisFile.cs
index 466a378..8dbdf3d 100644
--- a/UVtools.Core/Operations/OperationIPrintedThisFile.cs
+++ b/UVtools.Core/Operations/OperationIPrintedThisFile.cs
@@ -34,7 +34,7 @@ public class OperationIPrintedThisFile : Operation
public override bool CanROI => false;
public override bool CanHaveProfiles => false;
public override string ButtonOkText => "Consume";
- public override string IconClass => "fas fa-flask";
+ public override string IconClass => "fa-solid fa-flask";
public override string Title => "I printed this file";
public override string Description => "Select a material and consume resin from stock and print time.";
diff --git a/UVtools.Core/Operations/OperationLayerArithmetic.cs b/UVtools.Core/Operations/OperationLayerArithmetic.cs
index 026bcde..b8ddb40 100644
--- a/UVtools.Core/Operations/OperationLayerArithmetic.cs
+++ b/UVtools.Core/Operations/OperationLayerArithmetic.cs
@@ -56,7 +56,7 @@ public class OperationLayerArithmetic : Operation
#region Overrides
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
- public override string IconClass => "fas fa-square-root-alt";
+ public override string IconClass => "fa-solid fa-square-root-alt";
public override string Title => "Layer arithmetic";
public override string Description =>
"Perform arithmetic operations over the layers\n" +
diff --git a/UVtools.Core/Operations/OperationLayerClone.cs b/UVtools.Core/Operations/OperationLayerClone.cs
index cce810f..9c591b9 100644
--- a/UVtools.Core/Operations/OperationLayerClone.cs
+++ b/UVtools.Core/Operations/OperationLayerClone.cs
@@ -28,7 +28,7 @@ public sealed class OperationLayerClone : Operation
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.Current;
public override bool CanROI => false;
public override bool PassActualLayerIndex => true;
- public override string IconClass => "fas fa-clone";
+ public override string IconClass => "fa-solid fa-clone";
public override string Title => "Clone layers";
public override string Description =>
"Clone layers.\n\n" +
diff --git a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
index 2ed99d2..3a2b9fc 100644
--- a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
+++ b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
@@ -33,7 +33,7 @@ public sealed class OperationLayerExportHeatMap : Operation
public override bool CanHaveProfiles => false;
- public override string IconClass => "fas fa-file-image";
+ public override string IconClass => "fa-solid fa-file-image";
public override string Title => "Export layers to heat map";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationLayerExportImage.cs b/UVtools.Core/Operations/OperationLayerExportImage.cs
index b8787a8..0c9cbea 100644
--- a/UVtools.Core/Operations/OperationLayerExportImage.cs
+++ b/UVtools.Core/Operations/OperationLayerExportImage.cs
@@ -70,7 +70,7 @@ public sealed class OperationLayerExportImage : Operation
public override bool CanHaveProfiles => false;
- public override string IconClass => "fas fa-file-image";
+ public override string IconClass => "fa-solid fa-file-image";
public override string Title => "Export layers to images";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationLayerExportMesh.cs b/UVtools.Core/Operations/OperationLayerExportMesh.cs
index fda3d5e..16ac082 100644
--- a/UVtools.Core/Operations/OperationLayerExportMesh.cs
+++ b/UVtools.Core/Operations/OperationLayerExportMesh.cs
@@ -56,7 +56,7 @@ public sealed class OperationLayerExportMesh : Operation
public override bool CanHaveProfiles => false;
- public override string IconClass => "fas fa-cubes";
+ public override string IconClass => "fa-solid fa-cubes";
public override string Title => "Export layers to mesh";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationLayerExportSkeleton.cs b/UVtools.Core/Operations/OperationLayerExportSkeleton.cs
index 713b694..9212a36 100644
--- a/UVtools.Core/Operations/OperationLayerExportSkeleton.cs
+++ b/UVtools.Core/Operations/OperationLayerExportSkeleton.cs
@@ -27,7 +27,7 @@ public sealed class OperationLayerExportSkeleton : Operation
public override bool CanHaveProfiles => false;
- public override string IconClass => "fas fa-file-image";
+ public override string IconClass => "fa-solid fa-file-image";
public override string Title => "Export layers to skeleton";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationLithophane.cs b/UVtools.Core/Operations/OperationLithophane.cs
index 798b67b..86b36b7 100644
--- a/UVtools.Core/Operations/OperationLithophane.cs
+++ b/UVtools.Core/Operations/OperationLithophane.cs
@@ -66,7 +66,7 @@ public class OperationLithophane : Operation
#region Overrides
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
- public override string IconClass => "fas fa-portrait";
+ public override string IconClass => "fa-solid fa-portrait";
public override string Title => "Lithophane";
public override string Description =>
"Generate lithophane from a picture.\n" +
diff --git a/UVtools.Core/Operations/OperationMask.cs b/UVtools.Core/Operations/OperationMask.cs
index bbade0a..c22a104 100644
--- a/UVtools.Core/Operations/OperationMask.cs
+++ b/UVtools.Core/Operations/OperationMask.cs
@@ -19,7 +19,7 @@ namespace UVtools.Core.Operations;
public class OperationMask : Operation
{
#region Overrides
- public override string IconClass => "fas fa-mask";
+ public override string IconClass => "fa-solid fa-mask";
public override string Title => "Mask";
public override string Description =>
"Mask the intensity of the LCD output using a greyscale input image.\n\n" +
diff --git a/UVtools.Core/Operations/OperationMorph.cs b/UVtools.Core/Operations/OperationMorph.cs
index 83ee073..284cba1 100644
--- a/UVtools.Core/Operations/OperationMorph.cs
+++ b/UVtools.Core/Operations/OperationMorph.cs
@@ -64,7 +64,7 @@ public sealed class OperationMorph : Operation
#endregion
#region Overrides
- public override string IconClass => "fas fa-dharmachakra";
+ public override string IconClass => "fa-solid fa-dharmachakra";
public override string Title => "Morph";
public override string Description =>
$"Morph Model - " +
diff --git a/UVtools.Core/Operations/OperationMove.cs b/UVtools.Core/Operations/OperationMove.cs
index b83a116..fd0062e 100644
--- a/UVtools.Core/Operations/OperationMove.cs
+++ b/UVtools.Core/Operations/OperationMove.cs
@@ -21,7 +21,7 @@ public class OperationMove : Operation
#region Overrides
public override bool CanMask => false;
- public override string IconClass => "fas fa-arrows-alt";
+ public override string IconClass => "fa-solid fa-arrows-alt";
public override string Title => "Move";
public override string Description =>
"Change or copy the position of the model on the build plate.\n" +
diff --git a/UVtools.Core/Operations/OperationPCBExposure.cs b/UVtools.Core/Operations/OperationPCBExposure.cs
index 3177263..7df3085 100644
--- a/UVtools.Core/Operations/OperationPCBExposure.cs
+++ b/UVtools.Core/Operations/OperationPCBExposure.cs
@@ -7,8 +7,10 @@
*/
using System;
-using System.Drawing;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.IO;
+using System.IO.Compression;
using System.Text;
using Emgu.CV;
using Emgu.CV.CvEnum;
@@ -16,26 +18,17 @@ using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
using UVtools.Core.Gerber;
using UVtools.Core.Layers;
+using UVtools.Core.Objects;
namespace UVtools.Core.Operations;
[Serializable]
public class OperationPCBExposure : Operation
{
- #region Enum
-
- public enum LithophaneBaseType : byte
- {
- None,
- Square,
- Model
- }
-
- #endregion
-
#region Members
- private string? _filePath;
-
+ private RangeObservableCollection _files = new();
+
+ private bool _mergeFiles;
private decimal _layerHeight;
private decimal _exposureTime;
private bool _mirror;
@@ -47,7 +40,7 @@ public class OperationPCBExposure : Operation
#region Overrides
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
- public override string IconClass => "fas fa-microchip";
+ public override string IconClass => "fa-solid fa-microchip";
public override string Title => "PCB exposure";
public override string Description =>
"Converts a gerber file to a pixel perfect image given your printer LCD/resolution to exposure the copper traces.\n" +
@@ -74,13 +67,16 @@ public class OperationPCBExposure : Operation
public override string? ValidateInternally()
{
var sb = new StringBuilder();
- if (string.IsNullOrWhiteSpace(_filePath))
+ if (_files.Count == 0)
{
- sb.AppendLine("The input file is empty");
+ sb.AppendLine("Select at least one gerber file");
}
- else if(!File.Exists(_filePath))
+ else
{
- sb.AppendLine("The input file does not exists");
+ foreach (var valueDescription in _files)
+ {
+ if(!File.Exists(valueDescription.ValueAsString)) sb.AppendLine($"The file {valueDescription} does not exists");
+ }
}
return sb.ToString();
@@ -88,7 +84,7 @@ public class OperationPCBExposure : Operation
public override string ToString()
{
- var result = $"{(FileExists ? $"{Path.GetFileName(_filePath)} [Exposure: {_exposureTime}s] [Invert: {_invertColor}]" : $"[Exposure: {_exposureTime}s] [Invert: {_invertColor}]")}";
+ var result = $"{string.Join(" / ", _files)} [Exposure: {_exposureTime}s] [Mirror: {_mirror}] [Invert: {_invertColor}]";
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
@@ -102,18 +98,32 @@ public class OperationPCBExposure : Operation
{
if (_layerHeight <= 0) _layerHeight = (decimal)SlicerFile.LayerHeight;
if (_exposureTime <= 0) _exposureTime = (decimal)SlicerFile.BottomExposureTime;
- _mirror = SlicerFile.DisplayMirror != FlipDirection.None;
+ //_mirror = SlicerFile.DisplayMirror != FlipDirection.None;
}
#endregion
#region Properties
- public string? FilePath
+ public RangeObservableCollection Files
+ {
+ get => _files;
+ set => RaiseAndSetIfChanged(ref _files, value);
+ }
+
+ public uint FileCount => (uint)_files.Count;
+
+ public bool MergeFiles
+ {
+ get => _mergeFiles;
+ set => RaiseAndSetIfChanged(ref _mergeFiles, value);
+ }
+
+ /*public string? FilePath
{
get => _filePath;
set => RaiseAndSetIfChanged(ref _filePath, value);
}
- public bool FileExists => !string.IsNullOrWhiteSpace(_filePath) && File.Exists(_filePath);
+ public bool FileExists => !string.IsNullOrWhiteSpace(_filePath) && File.Exists(_filePath);*/
public decimal LayerHeight
{
@@ -151,7 +161,7 @@ public class OperationPCBExposure : Operation
protected bool Equals(OperationPCBExposure other)
{
- return _filePath == other._filePath && _layerHeight == other._layerHeight && _exposureTime == other._exposureTime && _mirror == other._mirror && _invertColor == other._invertColor && _enableAntiAliasing == other._enableAntiAliasing;
+ return _files.Equals(other._files) && _mergeFiles == other._mergeFiles && _layerHeight == other._layerHeight && _exposureTime == other._exposureTime && _mirror == other._mirror && _invertColor == other._invertColor && _enableAntiAliasing == other._enableAntiAliasing;
}
public override bool Equals(object? obj)
@@ -164,18 +174,57 @@ public class OperationPCBExposure : Operation
public override int GetHashCode()
{
- return HashCode.Combine(_filePath, _layerHeight, _exposureTime, _mirror, _invertColor, _enableAntiAliasing);
+ return HashCode.Combine(_files, _mergeFiles, _layerHeight, _exposureTime, _mirror, _invertColor, _enableAntiAliasing);
}
#endregion
#region Methods
- public Mat GetMat()
+ public void AddFilesFromZip(string zipFile)
+ {
+ if (!File.Exists(zipFile)) return;
+ using var zip = ZipFile.Open(zipFile, ZipArchiveMode.Read);
+
+ var tmpPath = PathExtensions.GetTemporaryDirectory($"{About.Software}.");
+ foreach (var entry in zip.Entries)
+ {
+ if(!entry.Name.EndsWith(".gbr")) continue;
+ var filePath = entry.ImprovedExtractToFile(tmpPath, false);
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ AddFile(filePath);
+ }
+ }
+
+ }
+
+ public void AddFile(string file)
+ {
+ if (!File.Exists(file)) return;
+ var vd = new ValueDescription(file, Path.GetFileNameWithoutExtension(file));
+ if (_files.Contains(vd)) return;
+ _files.Add(vd);
+ }
+
+ public void AddFiles(string[] files)
+ {
+ foreach (var file in files)
+ {
+ AddFile(file);
+ }
+ }
+
+ public void Sort()
+ {
+ _files.Sort((file1, file2) => string.Compare(Path.GetFileNameWithoutExtension(file1.ValueAsString), Path.GetFileNameWithoutExtension(file2.ValueAsString), StringComparison.Ordinal));
+ }
+
+ public Mat GetMat(string file)
{
var mat = SlicerFile.CreateMat();
- if (!FileExists) return mat;
- GerberDocument.ParseAndDraw(_filePath!, mat, SlicerFile.Ppmm, _enableAntiAliasing);
+ if (!File.Exists(file)) return mat;
+ GerberDocument.ParseAndDraw(file, mat, SlicerFile.Ppmm, _enableAntiAliasing);
//var boundingRectangle = CvInvoke.BoundingRectangle(mat);
//var cropped = mat.Roi(new Size(boundingRectangle.Right, boundingRectangle.Bottom));
@@ -186,7 +235,7 @@ public class OperationPCBExposure : Operation
{
var flip = SlicerFile.DisplayMirror;
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
- CvInvoke.Flip(cropped, cropped, (FlipType)flip);
+ CvInvoke.Flip(mat, mat, (FlipType)flip);
}
return mat;
@@ -194,8 +243,33 @@ public class OperationPCBExposure : Operation
protected override bool ExecuteInternally(OperationProgress progress)
{
- using var mat = GetMat();
-
+ var layers = new List();
+ Mat? mergeMat = null;
+ progress.ItemCount = FileCount;
+ foreach (var file in _files)
+ {
+ using var mat = GetMat(file.ValueAsString);
+
+ if (mergeMat is null)
+ {
+ mergeMat = mat.Clone();
+ }
+ else
+ {
+ CvInvoke.Max(mergeMat, mat, mergeMat);
+ }
+
+ if (_mergeFiles) continue;
+ layers.Add(new Layer(mat, SlicerFile));
+
+ progress++;
+ }
+
+ if (_mergeFiles && mergeMat is not null)
+ {
+ layers.Add(new Layer(mergeMat, SlicerFile));
+ }
+
SlicerFile.SuppressRebuildPropertiesWork(() =>
{
SlicerFile.LayerHeight = (float) _layerHeight;
@@ -205,18 +279,50 @@ public class OperationPCBExposure : Operation
SlicerFile.LiftHeightTotal = 0;
SlicerFile.SetNoDelays();
- SlicerFile.Layers = new[] { new Layer(mat, SlicerFile) };
+ SlicerFile.Layers = layers.ToArray();
}, true);
+ if (_mirror) // Reposition layers
+ {
+ using var op = new OperationMove(SlicerFile, Anchor.TopLeft)
+ {
+ MarginLeft = SlicerFile.BoundingRectangle.X,
+ MarginTop = SlicerFile.BoundingRectangle.Y,
+ };
- using var croppedMat = mat.CropByBounds(20);
- using var bgrMat = new Mat();
- CvInvoke.CvtColor(croppedMat, bgrMat, ColorConversion.Gray2Bgr);
- SlicerFile.SetThumbnails(bgrMat);
+ var flip = SlicerFile.DisplayMirror;
+ if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
+ switch (flip)
+ {
+ case FlipDirection.Horizontally:
+ op.MarginLeft = (int)SlicerFile.ResolutionX - SlicerFile.BoundingRectangle.Right;
+ break;
+ case FlipDirection.Vertically:
+ op.MarginTop = (int)SlicerFile.ResolutionY - SlicerFile.BoundingRectangle.Bottom;
+ break;
+ case FlipDirection.Both:
+ op.MarginLeft = (int)SlicerFile.ResolutionX - SlicerFile.BoundingRectangle.Right;
+ op.MarginTop = (int)SlicerFile.ResolutionY - SlicerFile.BoundingRectangle.Bottom;
+ break;
+ }
+
+ op.Execute(progress);
+ }
+
+
+ if (mergeMat is not null)
+ {
+ using var croppedMat = mergeMat.CropByBounds(20);
+ using var bgrMat = new Mat();
+ CvInvoke.CvtColor(croppedMat, bgrMat, ColorConversion.Gray2Bgr);
+ SlicerFile.SetThumbnails(bgrMat);
+ mergeMat.Dispose();
+ }
return !progress.Token.IsCancellationRequested;
}
#endregion
+
}
\ No newline at end of file
diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs
index d38df67..6666582 100644
--- a/UVtools.Core/Operations/OperationPattern.cs
+++ b/UVtools.Core/Operations/OperationPattern.cs
@@ -34,7 +34,7 @@ public class OperationPattern : Operation
#region Overrides
public override bool CanMask => false;
- public override string IconClass => "fas fa-th-large";
+ public override string IconClass => "fa-solid fa-th-large";
public override string Title => "Pattern";
public override string Description =>
"Duplicates the model in a rectangular pattern around the build plate.\n" +
diff --git a/UVtools.Core/Operations/OperationRaftRelief.cs b/UVtools.Core/Operations/OperationRaftRelief.cs
index 3b4ccef..41d5e1a 100644
--- a/UVtools.Core/Operations/OperationRaftRelief.cs
+++ b/UVtools.Core/Operations/OperationRaftRelief.cs
@@ -63,7 +63,7 @@ public class OperationRaftRelief : Operation
#endregion
#region Overrides
- public override string IconClass => "fas fa-bowling-ball";
+ public override string IconClass => "fa-solid fa-bowling-ball";
public override string Title => "Raft relief";
public override string Description =>
"Relief raft with a strategy to remove mass, reduce FEP suction, spare resin and easier to remove the prints.";
diff --git a/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs b/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs
index c958e34..d8b083c 100644
--- a/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs
+++ b/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs
@@ -29,7 +29,7 @@ public class OperationRaiseOnPrintFinish : Operation
#region Overrides
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
- public override string IconClass => "fas fa-level-up-alt";
+ public override string IconClass => "fa-solid fa-level-up-alt";
public override string Title => "Raise platform on print finish";
public override string Description =>
"Raise the build platform to a set position after finish the print.\n\n" +
diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs
index f839390..e0e6abc 100644
--- a/UVtools.Core/Operations/OperationRepairLayers.cs
+++ b/UVtools.Core/Operations/OperationRepairLayers.cs
@@ -46,7 +46,7 @@ public class OperationRepairLayers : Operation
#region Overrides
public override bool CanROI => false;
- public override string IconClass => "fas fa-toolbox";
+ public override string IconClass => "fa-solid fa-toolbox";
public override string Title => "Repair layers and issues";
public override string Description => string.Empty;
diff --git a/UVtools.Core/Operations/OperationResize.cs b/UVtools.Core/Operations/OperationResize.cs
index b91c6f7..0f2fe56 100644
--- a/UVtools.Core/Operations/OperationResize.cs
+++ b/UVtools.Core/Operations/OperationResize.cs
@@ -26,7 +26,7 @@ public class OperationResize : Operation
#endregion
#region Overrides
- public override string IconClass => "fas fa-expand-alt";
+ public override string IconClass => "fa-solid fa-expand-alt";
public override string Title => "Resize";
public override string Description =>
"Resize the model by a percentage in the X/Y plane.\n\n" +
diff --git a/UVtools.Core/Operations/OperationRotate.cs b/UVtools.Core/Operations/OperationRotate.cs
index 663a773..cc747a8 100644
--- a/UVtools.Core/Operations/OperationRotate.cs
+++ b/UVtools.Core/Operations/OperationRotate.cs
@@ -22,7 +22,7 @@ public class OperationRotate : Operation
#endregion
#region Overrides
- public override string IconClass => "fas fa-sync-alt";
+ public override string IconClass => "fa-solid fa-sync-alt";
public override string Title => "Rotate";
public override string Description =>
"Rotate the layers of the model.\n";
diff --git a/UVtools.Core/Operations/OperationScripting.cs b/UVtools.Core/Operations/OperationScripting.cs
index 0ec7e6e..3161123 100644
--- a/UVtools.Core/Operations/OperationScripting.cs
+++ b/UVtools.Core/Operations/OperationScripting.cs
@@ -34,7 +34,7 @@ public sealed class OperationScripting : Operation
public override bool CanRunInPartialMode => true;
//public override bool CanHaveProfiles => false;
- public override string IconClass => "fas fa-code";
+ public override string IconClass => "fa-solid fa-code";
public override string Title => "Scripting";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs
index 6045735..ba2426e 100644
--- a/UVtools.Core/Operations/OperationSolidify.cs
+++ b/UVtools.Core/Operations/OperationSolidify.cs
@@ -34,7 +34,7 @@ public sealed class OperationSolidify : Operation
#region Overrides
- public override string IconClass => "fas fa-circle";
+ public override string IconClass => "fa-solid fa-circle";
public override string Title => "Solidify";
public override string Description =>
diff --git a/UVtools.Core/Operations/OperationTimelapse.cs b/UVtools.Core/Operations/OperationTimelapse.cs
index 41b5a1e..456ceb7 100644
--- a/UVtools.Core/Operations/OperationTimelapse.cs
+++ b/UVtools.Core/Operations/OperationTimelapse.cs
@@ -51,7 +51,7 @@ public class OperationTimelapse : Operation
#region Overrides
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.Normal;
- public override string IconClass => "fas fa-camera";
+ public override string IconClass => "fa-solid fa-camera";
public override string Title => "Timelapse";
public override string Description =>
"Raise the build platform to a set position every odd-even height to be able to take a photo and create a time-lapse video of the print.\n" +
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 6c235f7..1b93a05 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -10,7 +10,7 @@
https://github.com/sn4k3/UVtools
https://github.com/sn4k3/UVtools
MSLA/DLP, file analysis, calibration, repair, conversion and manipulation
- 3.4.3
+ 3.5.0
Copyright © 2020 PTRTECH
UVtools.png
AnyCPU;x64
@@ -69,9 +69,9 @@
-
+
-
+
diff --git a/UVtools.InstallerMM/UVtools.InstallerMM.wxs b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
index 181c00e..848eea0 100644
--- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs
+++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
@@ -2,7 +2,7 @@
-
+
@@ -1554,12 +1554,12 @@
-
-
-
+
+
+