From 15c500ecaf54ba078a45db7520564eca16e342c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sun, 19 Jun 2022 04:19:23 +0100 Subject: [PATCH] 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] --- .github/PULL_REQUEST_TEMPLATE.md | 1 + CHANGELOG.md | 19 + RELEASE_NOTES.md | 20 +- .../UVtools.AvaloniaControls.csproj | 2 +- UVtools.Core/Enumerations.cs | 15 + UVtools.Core/Extensions/PathExtensions.cs | 25 +- .../Extensions/ZipArchiveExtensions.cs | 38 +- UVtools.Core/FileFormats/CTBEncryptedFile.cs | 6 +- UVtools.Core/FileFormats/ChituboxFile.cs | 6 +- UVtools.Core/FileFormats/FileFormat.cs | 4 + UVtools.Core/Gerber/Apertures/Aperture.cs | 2 +- .../Gerber/Apertures/CircleAperture.cs | 8 +- .../Gerber/Apertures/EllipseAperture.cs | 9 +- .../Gerber/Apertures/MacroAperture.cs | 5 +- .../Gerber/Apertures/PoygonAperture.cs | 6 +- .../Gerber/Apertures/RectangleAperture.cs | 8 +- UVtools.Core/Gerber/GerberDocument.cs | 553 ++++++++++-------- .../Gerber/Primitives/CenterLinePrimitive.cs | 10 +- .../Gerber/Primitives/CirclePrimitive.cs | 11 +- .../Gerber/Primitives/CommentPrimitive.cs | 3 +- .../Gerber/Primitives/OutlinePrimitive.cs | 8 +- .../Gerber/Primitives/PolygonPrimitive.cs | 13 +- UVtools.Core/Gerber/Primitives/Primitive.cs | 3 +- .../Gerber/Primitives/VectorLinePrimitive.cs | 10 +- UVtools.Core/MeshFormats/AMFMeshFile.cs | 4 +- .../MeshFormats/Consortium3MFMeshFile.cs | 4 +- UVtools.Core/MeshFormats/OBJMeshFile.cs | 2 +- UVtools.Core/MeshFormats/OFFMeshFile.cs | 2 +- UVtools.Core/MeshFormats/PLYMeshFile.cs | 2 +- UVtools.Core/MeshFormats/WRLMeshFile.cs | 2 +- UVtools.Core/Operations/Operation.cs | 2 +- UVtools.Core/Operations/OperationBlur.cs | 2 +- .../Operations/OperationCalculator.cs | 2 +- .../OperationCalibrateBloomingEffect.cs | 428 ++++++++++++++ .../OperationCalibrateExternalTests.cs | 2 +- .../Operations/OperationCalibrateGrayscale.cs | 2 +- .../OperationCalibrateStressTower.cs | 2 +- .../Operations/OperationCalibrateTolerance.cs | 2 +- .../OperationCalibrateXYZAccuracy.cs | 2 +- .../Operations/OperationDoubleExposure.cs | 2 +- .../Operations/OperationDynamicLifts.cs | 2 +- .../Operations/OperationEditParameters.cs | 2 +- .../Operations/OperationFadeExposureTime.cs | 2 +- .../Operations/OperationIPrintedThisFile.cs | 2 +- .../Operations/OperationLayerArithmetic.cs | 2 +- .../Operations/OperationLayerClone.cs | 2 +- .../Operations/OperationLayerExportHeatMap.cs | 2 +- .../Operations/OperationLayerExportImage.cs | 2 +- .../Operations/OperationLayerExportMesh.cs | 2 +- .../OperationLayerExportSkeleton.cs | 2 +- .../Operations/OperationLithophane.cs | 2 +- UVtools.Core/Operations/OperationMask.cs | 2 +- UVtools.Core/Operations/OperationMorph.cs | 2 +- UVtools.Core/Operations/OperationMove.cs | 2 +- .../Operations/OperationPCBExposure.cs | 178 ++++-- UVtools.Core/Operations/OperationPattern.cs | 2 +- .../Operations/OperationRaftRelief.cs | 2 +- .../Operations/OperationRaiseOnPrintFinish.cs | 2 +- .../Operations/OperationRepairLayers.cs | 2 +- UVtools.Core/Operations/OperationResize.cs | 2 +- UVtools.Core/Operations/OperationRotate.cs | 2 +- UVtools.Core/Operations/OperationScripting.cs | 2 +- UVtools.Core/Operations/OperationSolidify.cs | 2 +- UVtools.Core/Operations/OperationTimelapse.cs | 2 +- UVtools.Core/UVtools.Core.csproj | 6 +- UVtools.InstallerMM/UVtools.InstallerMM.wxs | 8 +- UVtools.WPF/App.axaml.cs | 10 +- .../CalibrateBloomingEffectControl.axaml | 158 +++++ .../CalibrateBloomingEffectControl.axaml.cs | 70 +++ .../CalibrateExposureFinderControl.axaml | 8 +- .../CalibrateLiftHeightControl.axaml.cs | 1 - .../CalibrateXYZAccuracyControl.axaml | 2 +- UVtools.WPF/Controls/Helpers.cs | 12 + .../Tools/ToolEditParametersControl.axaml.cs | 2 +- .../Tools/ToolLayerExportGifControl.axaml | 2 +- .../Tools/ToolLayerExportHeatMapControl.axaml | 2 +- .../Tools/ToolLayerExportImageControl.axaml | 2 +- .../Tools/ToolLayerExportMeshControl.axaml | 2 +- .../ToolLayerExportSkeletonControl.axaml | 2 +- .../Tools/ToolLayerImportControl.axaml | 8 +- .../Tools/ToolLithophaneControl.axaml | 2 +- .../Tools/ToolPCBExposureControl.axaml | 76 ++- .../Tools/ToolPCBExposureControl.axaml.cs | 60 +- .../Controls/Tools/ToolPatternControl.axaml | 4 +- .../Tools/ToolRedrawModelControl.axaml | 2 +- .../Controls/Tools/ToolScriptingControl.axaml | 8 +- .../Tools/ToolScriptingControl.axaml.cs | 7 +- UVtools.WPF/Controls/WindowEx.cs | 21 +- UVtools.WPF/MainWindow.Issues.cs | 51 +- UVtools.WPF/MainWindow.axaml | 184 +++--- UVtools.WPF/MainWindow.axaml.cs | 9 +- UVtools.WPF/Structures/OperationProfiles.cs | 1 + UVtools.WPF/UVtools.WPF.csproj | 20 +- UVtools.WPF/UserSettings.cs | 10 +- UVtools.WPF/Windows/AboutWindow.axaml | 6 +- UVtools.WPF/Windows/CantRunWindow.axaml | 8 +- UVtools.WPF/Windows/ColorPickerWindow.axaml | 4 +- .../Windows/MaterialManagerWindow.axaml | 6 +- UVtools.WPF/Windows/MessageWindow.axaml | 6 +- .../Windows/MissingInformationWindow.axaml | 4 +- .../Windows/PrusaSlicerManagerWindow.axaml | 22 +- UVtools.WPF/Windows/SettingsWindow.axaml | 69 ++- UVtools.WPF/Windows/SettingsWindow.axaml.cs | 23 +- .../Windows/SuggestionSettingsWindow.axaml | 8 +- UVtools.WPF/Windows/ToolWindow.axaml | 40 +- UVtools.WPF/Windows/ToolWindow.axaml.cs | 46 +- .../Windows/VersionSelectorWindow.axaml | 4 +- 107 files changed, 1741 insertions(+), 726 deletions(-) create mode 100644 UVtools.Core/Operations/OperationCalibrateBloomingEffect.cs create mode 100644 UVtools.WPF/Controls/Calibrators/CalibrateBloomingEffectControl.axaml create mode 100644 UVtools.WPF/Controls/Calibrators/CalibrateBloomingEffectControl.axaml.cs 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 @@ - - - + + + diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index 892f5a9..d959c17 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -239,7 +239,7 @@ public class App : Application } - var reportButton = MessageWindow.CreateButton("Report", "fas fa-bug"); + var reportButton = MessageWindow.CreateButton("Report", "fa-solid fa-bug"); reportButton.Click += (sender, e) => { Current?.Clipboard?.SetTextAsync(bugReportMessageMk); @@ -248,7 +248,7 @@ public class App : Application e.Handled = true; }; - var helpButton = MessageWindow.CreateButton("Help", "fas fa-question"); + var helpButton = MessageWindow.CreateButton("Help", "fa-solid fa-question"); helpButton.Click += (sender, e) => { Current?.Clipboard?.SetTextAsync(bugReportMessageMk); @@ -256,14 +256,14 @@ public class App : Application e.Handled = true; }; - var restartButton = MessageWindow.CreateButton("Restart", "fas fa-redo-alt"); + var restartButton = MessageWindow.CreateButton("Restart", "fa-solid fa-redo-alt"); restartButton.Click += (sender, e) => { SystemAware.StartThisApplication(); }; desktop.MainWindow = new MessageWindow($"{About.SoftwareWithVersion} - Crash report", - "far fa-frown", + "fa-regular fa-frown", $"{About.Software} crashed due an unexpected {category.ToLowerInvariant()} error.\nYou can report this error if you find necessary.\nFind more details below:\n", message, new[] @@ -271,7 +271,7 @@ public class App : Application reportButton, helpButton, restartButton, - MessageWindow.CreateCloseButton("fas fa-sign-out-alt") + MessageWindow.CreateCloseButton("fa-solid fa-sign-out-alt") }); } else diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateBloomingEffectControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateBloomingEffectControl.axaml new file mode 100644 index 0000000..b5c64e9 --- /dev/null +++ b/UVtools.WPF/Controls/Calibrators/CalibrateBloomingEffectControl.axaml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateBloomingEffectControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateBloomingEffectControl.axaml.cs new file mode 100644 index 0000000..76a94f3 --- /dev/null +++ b/UVtools.WPF/Controls/Calibrators/CalibrateBloomingEffectControl.axaml.cs @@ -0,0 +1,70 @@ +using System.Timers; +using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; +using Avalonia.Threading; +using UVtools.Core.Operations; +using UVtools.WPF.Controls.Tools; +using UVtools.WPF.Extensions; +using UVtools.WPF.Windows; + +namespace UVtools.WPF.Controls.Calibrators +{ + public partial class CalibrateBloomingEffectControl : ToolControl + { + public OperationCalibrateBloomingEffect Operation => BaseOperation as OperationCalibrateBloomingEffect; + + private readonly Timer _timer; + + private Bitmap _previewImage; + public Bitmap PreviewImage + { + get => _previewImage; + set => RaiseAndSetIfChanged(ref _previewImage, value); + } + + public CalibrateBloomingEffectControl() + { + BaseOperation = new OperationCalibrateBloomingEffect(SlicerFile); + if (!ValidateSpawn()) return; + + InitializeComponent(); + + + _timer = new Timer(20) + { + AutoReset = false + }; + _timer.Elapsed += (sender, e) => Dispatcher.UIThread.InvokeAsync(UpdatePreview); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public override void Callback(ToolWindow.Callbacks callback) + { + if (App.SlicerFile is null) return; + switch (callback) + { + case ToolWindow.Callbacks.Init: + case ToolWindow.Callbacks.Loaded: + Operation.PropertyChanged += (sender, e) => + { + _timer.Stop(); + _timer.Start(); + }; + _timer.Stop(); + _timer.Start(); + break; + } + } + + public void UpdatePreview() + { + using var mat = Operation.GetLayerPreview(); + _previewImage?.Dispose(); + PreviewImage = mat.ToBitmap(); + } + } +} diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml index f60c1bc..5caf8f3 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml +++ b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml @@ -580,7 +580,7 @@ VerticalAlignment="Stretch" Command="{Binding BrightnessExposureGenAdd}" Text="Add" - Icon="fas fa-plus"/> + Icon="fa-solid fa-plus"/> + Icon="fa-solid fa-plus"/> + Icon="fa-solid fa-trash-alt"/> + Icon="fa-solid fa-xmark"/> diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml.cs index ade93a4..bb7a2d3 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml.cs +++ b/UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml.cs @@ -1,5 +1,4 @@ using System.Timers; -using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Threading; diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml index 0c8061e..5a974d6 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml +++ b/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml @@ -436,7 +436,7 @@ @@ -281,7 +281,7 @@ IsEnabled="{Binding SelectedProfileItem, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{Binding Profiles.Count}" Command="{Binding DeselectProfile}" - i:Attached.Icon="far fa-square"/> + i:Attached.Icon="fa-regular fa-square"/> @@ -387,7 +387,7 @@ @@ -407,14 +407,14 @@ IsDefault="True" IsVisible="{Binding ButtonOkVisible}" IsEnabled="{Binding ButtonOkEnabled}" - Icon="fas fa-check" + Icon="fa-solid fa-check" Text="{Binding ButtonOkText}" Command="{Binding Process}"> diff --git a/UVtools.WPF/Windows/ToolWindow.axaml.cs b/UVtools.WPF/Windows/ToolWindow.axaml.cs index 0dc2542..ca7abda 100644 --- a/UVtools.WPF/Windows/ToolWindow.axaml.cs +++ b/UVtools.WPF/Windows/ToolWindow.axaml.cs @@ -12,7 +12,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Markup.Xaml; -using Avalonia.Threading; using MessageBox.Avalonia.Enums; using UVtools.Core; using UVtools.Core.Extensions; @@ -40,7 +39,7 @@ public class ToolWindow : WindowEx private KeyModifiers _globalModifiers; public ToolControl ToolControl; private string _description; - private double _descriptionMaxWidth; + private double _descriptionMaxWidth = 500; private double _profileBoxMaxWidth = double.NaN; private bool _layerRangeVisible = true; private bool _layerRangeSync; @@ -679,32 +678,6 @@ public class ToolWindow : WindowEx //RaisePropertyChanged(nameof(IsContentVisible)); //RaisePropertyChanged(nameof(IsROIVisible)); - // Ensure the description don't stretch window - /*DispatcherTimer.Run(() => - { - if (Bounds.Width == 0) return true; - //ScrollViewerMaxHeight = this.GetScreenWorkingArea().Height - Bounds.Height + ToolControl.Bounds.Height - UserSettings.Instance.General.WindowsVerticalMargin; - DescriptionMaxWidth = Math.Max(Bounds.Width, ToolControl.Bounds.Width) - 20; - ExpanderHeaderMaxWidth = DescriptionMaxWidth - 40; - Height = MaxHeight; - - DispatcherTimer.Run(() => - { - if (Math.Max((int)_contentScrollViewer.Extent.Height - (int)_contentScrollViewer.Viewport.Height, 0) == 0) - { - Height = 10; - SizeToContent = SizeToContent.WidthAndHeight; - } - Position = new PixelPoint( - (int)(App.MainWindow.Position.X + App.MainWindow.Width / 2 - Width / 2), - App.MainWindow.Position.Y + 20 - ); - return false; - }, TimeSpan.FromMilliseconds(2)); - - return false; - }, TimeSpan.FromMilliseconds(1));*/ - toolControl.Callback(Callbacks.Init); toolControl.DataContext = toolControl; DataContext = this; @@ -718,12 +691,13 @@ public class ToolWindow : WindowEx protected override void OnOpened(EventArgs e) { base.OnOpened(e); - var profileTextBox = this.FindControl("ProfileName"); - DescriptionMaxWidth = Math.Max(Bounds.Width, ToolControl.Bounds.Width) - 20; - ProfileBoxMaxWidth = profileTextBox.Bounds.Width; - Height = MaxHeight; - Dispatcher.UIThread.Post(() => + DescriptionMaxWidth = Math.Max(Bounds.Width, ToolControl.Bounds.Width) - 20; + var profileTextBox = this.FindControl("ProfileName"); + ProfileBoxMaxWidth = profileTextBox.Bounds.Width; + //Height = MaxHeight; + + /*Dispatcher.UIThread.Post(() => { if (Math.Max((int)_contentScrollViewer.Extent.Height - (int)_contentScrollViewer.Viewport.Height, 0) == 0) { @@ -737,10 +711,10 @@ public class ToolWindow : WindowEx ); CanResize = Settings.General.WindowsCanResize; - }, DispatcherPriority.Loaded); + }, DispatcherPriority.Loaded);*/ } - public void FitToSize() + /*public void FitToSize() { SizeToContent = SizeToContent.Manual; Height = MaxHeight; @@ -758,7 +732,7 @@ public class ToolWindow : WindowEx ); }, DispatcherPriority.Loaded); - } + }*/ #endregion diff --git a/UVtools.WPF/Windows/VersionSelectorWindow.axaml b/UVtools.WPF/Windows/VersionSelectorWindow.axaml index 82eb2ed..e182eb9 100644 --- a/UVtools.WPF/Windows/VersionSelectorWindow.axaml +++ b/UVtools.WPF/Windows/VersionSelectorWindow.axaml @@ -27,12 +27,12 @@