mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-19 15:06:29 +02:00
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]
This commit is contained in:
@@ -18,4 +18,5 @@
|
||||
<!--- If the pull request fixes issue(s) list them like this:
|
||||
- Fixes #123
|
||||
- Fixes #456
|
||||
Otherwise remove this section
|
||||
-->
|
||||
|
||||
@@ -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
|
||||
|
||||
+16
-4
@@ -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]
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.14" />
|
||||
<PackageReference Include="Avalonia" Version="0.10.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -124,6 +124,21 @@ public enum RemoveSourceFileAction : byte
|
||||
Prompt
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default order of issues to show on the UI list
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
|
||||
@@ -37,7 +37,28 @@ public static class PathExtensions
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string GetTempFilePathWithFilename(string fileName)
|
||||
/// <summary>
|
||||
/// Gets a temporary directory path
|
||||
/// </summary>
|
||||
/// <param name="prepend">Prepend a string to temporary directory name</param>
|
||||
/// <param name="createDirectory">True to create that directory, otherwise false</param>
|
||||
/// <returns>The temporary directory path</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a temporary directory path
|
||||
/// </summary>
|
||||
/// <param name="createDirectory">True to create that directory, otherwise false</param>
|
||||
/// <returns>The temporary directory path</returns>
|
||||
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
|
||||
/// <param name="extension">Extension name without the dot (.)</param>
|
||||
/// <param name="prepend">Optional prepend file name</param>
|
||||
/// <returns></returns>
|
||||
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}";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,8 +67,8 @@ public static class ZipArchiveExtensions
|
||||
/// manner. This plans for missing paths and existing files
|
||||
/// and handles them gracefully.
|
||||
/// </summary>
|
||||
/// <param name="sourceArchiveFileName">
|
||||
/// The name of the zip file to be extracted
|
||||
/// <param name="archive">
|
||||
/// The zip file to be extracted
|
||||
/// </param>
|
||||
/// <param name="destinationDirectoryName">
|
||||
/// 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.
|
||||
/// </param>
|
||||
public static void ImprovedExtractToDirectory(this ZipArchive archive, string sourceArchiveFileName, string destinationDirectoryName, Overwrite overwriteMethod = Overwrite.IfNewer)
|
||||
/// <returns>The number of extracted files</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely extracts a single file from a zip file
|
||||
/// </summary>
|
||||
/// <param name="file">
|
||||
/// <param name="entry">
|
||||
/// The zip entry we are pulling the file from
|
||||
/// </param>
|
||||
/// <param name="destinationPath">
|
||||
/// The root of where the file is going
|
||||
/// </param>
|
||||
/// <param name="preserveFullName">True to preserve full name and create all directories up to the file, otherwise false to extract the file just to <see cref="destinationPath"/></param>
|
||||
/// <param name="overwriteMethod">
|
||||
/// Specifies how we are going to handle an existing file.
|
||||
/// The default is Overwrite.IfNewer.
|
||||
/// </param>
|
||||
public static void ImprovedExtractToFile(this ZipArchiveEntry file, string destinationPath, Overwrite overwriteMethod = Overwrite.IfNewer)
|
||||
/// <returns>The extracted file path</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -148,6 +148,9 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
|
||||
Software
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// File decode type
|
||||
/// </summary>
|
||||
public enum FileDecodeType : byte
|
||||
{
|
||||
/// <summary>
|
||||
@@ -160,6 +163,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
|
||||
/// </summary>
|
||||
Partial,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sub Classes
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<int, Aperture> Apertures { get; } = new();
|
||||
public Dictionary<string, Macro> 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<int, Aperture> Apertures { get; } = new();
|
||||
public Dictionary<string, Macro> Macros { get; } = new();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public GerberDocument()
|
||||
double currentX = 0;
|
||||
double currentY = 0;
|
||||
Aperture? currentAperture = null;
|
||||
Macro? currentMacro = null;
|
||||
var regionPoints = new List<Point>();
|
||||
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<Point>();
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Point>();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
MeshStream.WriteLineLF("<amf unit=\"millimeter\" version=\"1.1\">");
|
||||
@@ -122,7 +122,7 @@ public class AMFMeshFile : MeshFile
|
||||
MeshStream.WriteLineLF("\t</object>");
|
||||
MeshStream.WriteLineLF("</amf>");
|
||||
|
||||
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))
|
||||
{
|
||||
|
||||
@@ -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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
MeshStream.WriteLineLF("<model unit=\"millimeter\" xml:lang=\"en-US\" xmlns:m=\"http://schemas.microsoft.com/3dmanufacturing/material/2015/02\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\">");
|
||||
@@ -105,7 +105,7 @@ public class Consortium3MFMeshFile : MeshFile
|
||||
MeshStream.WriteLineLF("\t</build>");
|
||||
MeshStream.WriteLineLF("</model>");
|
||||
|
||||
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))
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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}");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}\" }}");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
* GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* Version 3, 19 November 2007
|
||||
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*/
|
||||
|
||||
using 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
|
||||
|
||||
/// <summary>
|
||||
/// Gets the layers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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<Layer>();
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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.";
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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.";
|
||||
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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 - " +
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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<ValueDescription> _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<ValueDescription> 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<Layer>();
|
||||
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
|
||||
|
||||
}
|
||||
@@ -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" +
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
|
||||
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
|
||||
<Version>3.4.3</Version>
|
||||
<Version>3.5.0</Version>
|
||||
<Copyright>Copyright © 2020 PTRTECH</Copyright>
|
||||
<PackageIcon>UVtools.png</PackageIcon>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
@@ -69,9 +69,9 @@
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.4" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.4" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreparePackageReleaseNotesFromFile" BeforeTargets="GenerateNuspec">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<?define ComponentRules="OneToOne"?>
|
||||
<!-- SourceDir instructs IsWiX the location of the directory that contains files for this merge module -->
|
||||
<?define SourceDir="..\publish\UVtools_win-x64_v3.4.3"?>
|
||||
<?define SourceDir="..\publish\UVtools_win-x64_v3.5.0"?>
|
||||
<Module Id="UVtools" Language="1033" Version="1.0.0.0">
|
||||
<Package Id="12aaa1cf-ff06-4a02-abd5-2ac01ac4f83b" Manufacturer="PTRTECH" InstallerVersion="200" Keywords="MSLA, DLP" Description="MSLA/DLP, file analysis, repair, conversion and manipulation" InstallScope="perMachine" Platform="x64" />
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
@@ -1554,12 +1554,12 @@
|
||||
<Component Id="owcD2245B900E5C448485AD96FE57629D9D" Guid="D2245B90-0E5C-4484-85AD-96FE57629D9D">
|
||||
<File Id="owfD2245B900E5C448485AD96FE57629D9D" Source="$(var.SourceDir)\UVtoolsCmd.runtimeconfig.json" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="owc5D9B7363F82B4971A54B614C6E9F5EF1" Guid="5D9B7363-F82B-4971-A54B-614C6E9F5EF1">
|
||||
<File Id="owf5D9B7363F82B4971A54B614C6E9F5EF1" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_6.0.522.21309.dll" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="owc3110926C9437495884D1A256F611E7C5" Guid="3110926C-9437-4958-84D1-A256F611E7C5">
|
||||
<File Id="owf3110926C9437495884D1A256F611E7C5" Source="$(var.SourceDir)\Microsoft.Toolkit.HighPerformance.dll" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="owcCD68D9AAF66447D9A67F431CEFC402E2" Guid="CD68D9AA-F664-47D9-A67F-431CEFC402E2">
|
||||
<File Id="owfCD68D9AAF66447D9A67F431CEFC402E2" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_6.0.622.26707.dll" KeyPath="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="scd220707349D4C8FA275285514283F3E2A" Name="UVtools" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="UVtools.WPF.Controls.Calibrators.CalibrateBloomingEffectControl">
|
||||
<Grid ColumnDefinitions="Auto,10,350">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto"
|
||||
ColumnDefinitions="Auto,10,200,20,Auto,10,200">
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Layer height:"/>
|
||||
<NumericUpDown Grid.Row="0" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_mm"
|
||||
Increment="0.01"
|
||||
Minimum="0.01"
|
||||
Maximum="0.30"
|
||||
FormatString="F3"
|
||||
Value="{Binding Operation.LayerHeight}"/>
|
||||
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Text="Bottom layers:"/>
|
||||
<NumericUpDown Grid.Row="0" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_layers"
|
||||
Increment="1"
|
||||
Minimum="1"
|
||||
Maximum="1000"
|
||||
Value="{Binding Operation.BottomLayers}"/>
|
||||
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Bottom exposure:"/>
|
||||
<NumericUpDown Grid.Row="2" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_s"
|
||||
Increment="0.5"
|
||||
Minimum="0.1"
|
||||
Maximum="200"
|
||||
Value="{Binding Operation.BottomExposure}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Text="Normal exposure:"/>
|
||||
<NumericUpDown Grid.Row="2" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_s"
|
||||
Increment="0.5"
|
||||
Minimum="0.1"
|
||||
Maximum="200"
|
||||
Value="{Binding Operation.NormalExposure}"/>
|
||||
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Left/right margin:"/>
|
||||
<NumericUpDown Grid.Row="4" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Increment="5"
|
||||
Minimum="0"
|
||||
Maximum="{Binding Operation.MaxLeftRightMargin}"
|
||||
Value="{Binding Operation.LeftRightMargin}"/>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Text="Top/right margin:"/>
|
||||
<NumericUpDown Grid.Row="4" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Increment="5"
|
||||
Minimum="0"
|
||||
Maximum="{Binding Operation.MaxTopBottomMargin}"
|
||||
Value="{Binding Operation.TopBottomMargin}"/>
|
||||
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="The wait time before cure for the first object"
|
||||
Text="Starting wait time:"/>
|
||||
<NumericUpDown Grid.Row="6" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_s"
|
||||
Increment="0.5"
|
||||
Minimum="0"
|
||||
Maximum="1000"
|
||||
FormatString="F2"
|
||||
Value="{Binding Operation.WaitTimeBeforeCureStart}"/>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="The wait time before cure increment per object"
|
||||
Text="Wait time increment:"/>
|
||||
<NumericUpDown Grid.Row="6" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_s"
|
||||
Increment="0.50"
|
||||
Minimum="0.05"
|
||||
Maximum="100"
|
||||
FormatString="F2"
|
||||
Value="{Binding Operation.WaitTimeBeforeCureIncrement}"/>
|
||||
|
||||
|
||||
<TextBlock Grid.Row="8" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Number of objects:"/>
|
||||
<NumericUpDown Grid.Row="8" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_times"
|
||||
Increment="1"
|
||||
Minimum="1"
|
||||
Maximum="1000"
|
||||
Value="{Binding Operation.ObjectCount}"/>
|
||||
|
||||
<TextBlock Grid.Row="8" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip.Tip="Margin between objects in XY"
|
||||
Text="Object margin:"/>
|
||||
<NumericUpDown Grid.Row="8" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Increment="1"
|
||||
Minimum="1"
|
||||
Maximum="1000"
|
||||
Value="{Binding Operation.ObjectMargin}"/>
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Object diameter:"/>
|
||||
<NumericUpDown Grid.Row="10" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Increment="10"
|
||||
Minimum="1"
|
||||
Maximum="10000"
|
||||
Value="{Binding Operation.ObjectDiameter}"/>
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
Text="Object height:"/>
|
||||
<NumericUpDown Grid.Row="10" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_mm"
|
||||
Increment="1"
|
||||
Minimum="0.5"
|
||||
Maximum="1000"
|
||||
FormatString="F2"
|
||||
Value="{Binding Operation.ObjectHeight}"/>
|
||||
|
||||
<CheckBox Grid.Row="12" Grid.Column="2"
|
||||
Grid.ColumnSpan="5"
|
||||
ToolTip.Tip="Most of the printers requires a mirror output to print with the correct orientation"
|
||||
IsChecked="{Binding Operation.MirrorOutput}"
|
||||
Content="Mirror output" />
|
||||
</Grid>
|
||||
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<Image Grid.Column="2"
|
||||
Stretch="Uniform"
|
||||
Source="{Binding PreviewImage}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -580,7 +580,7 @@
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding BrightnessExposureGenAdd}"
|
||||
Text="Add"
|
||||
Icon="fas fa-plus"/>
|
||||
Icon="fa-solid fa-plus"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
Text="Brightnesses:"
|
||||
@@ -874,7 +874,7 @@
|
||||
IsEnabled="{Binding Operation.ExposureGenManualLayerHeight}"
|
||||
Command="{Binding ExposureTableAddManual}"
|
||||
Text="Add"
|
||||
Icon="fas fa-plus"/>
|
||||
Icon="fa-solid fa-plus"/>
|
||||
</Grid>
|
||||
|
||||
<Grid RowDefinitions="Auto" ColumnDefinitions="Auto,*,Auto"
|
||||
@@ -889,7 +889,7 @@
|
||||
IsEnabled="{Binding #ExposureTable.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Command="{Binding ExposureTableRemoveSelectedEntries}"
|
||||
Text="Remove selected entries"
|
||||
Icon="fas fa-trash-alt"/>
|
||||
Icon="fa-solid fa-trash-alt"/>
|
||||
|
||||
<controls:ButtonWithIcon
|
||||
Grid.Column="2" VerticalContentAlignment="Center"
|
||||
@@ -899,7 +899,7 @@
|
||||
IsEnabled="{Binding Operation.ExposureTable.Count}"
|
||||
Command="{Binding ExposureTableClearEntries}"
|
||||
Text="{Binding Operation.ExposureTable.Count, StringFormat=Clear {0} entries}"
|
||||
Icon="fas fa-times"/>
|
||||
Icon="fa-solid fa-xmark"/>
|
||||
</Grid>
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Timers;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
|
||||
@@ -436,7 +436,7 @@
|
||||
<Button Grid.Column="6" VerticalAlignment="Center"
|
||||
IsEnabled="{Binding IsProfileAddEnabled}"
|
||||
Command="{Binding AddProfile}"
|
||||
i:Attached.Icon="fas fa-plus"/>
|
||||
i:Attached.Icon="fa-solid fa-plus"/>
|
||||
</Grid>
|
||||
</Expander>
|
||||
|
||||
|
||||
@@ -132,6 +132,18 @@ public static class Helpers
|
||||
}
|
||||
};
|
||||
|
||||
public static readonly List<FileDialogFilter> ZipFileFilter = new()
|
||||
{
|
||||
new()
|
||||
{
|
||||
Name = "Zip Files",
|
||||
Extensions = new List<string>
|
||||
{
|
||||
"zip",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static List<FileDialogFilter> ToAvaloniaFileFilter(List<KeyValuePair<string, List<string>>> data)
|
||||
{
|
||||
var result = new List<FileDialogFilter>(data.Capacity);
|
||||
|
||||
@@ -93,7 +93,7 @@ public class ToolEditParametersControl : ToolControl
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Tag = this,
|
||||
Padding = new Thickness(5),
|
||||
Content = new Projektanker.Icons.Avalonia.Icon{Value = "fas fa-undo-alt"},
|
||||
Content = new Projektanker.Icons.Avalonia.Icon{Value = "fa-solid fa-undo-alt"},
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch
|
||||
};
|
||||
ResetButton.Click += ResetButtonOnClick;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<Button
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding ChooseFilePath}"
|
||||
i:Attached.Icon="fas fa-folder"/>
|
||||
i:Attached.Icon="fa-solid fa-folder"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<Button
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding ChooseFilePath}"
|
||||
i:Attached.Icon="fas fa-folder"/>
|
||||
i:Attached.Icon="fa-solid fa-folder"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid RowDefinitions="Auto,10,Auto"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<Button Grid.Row="0" Grid.Column="4"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding ChooseFolder}"
|
||||
i:Attached.Icon="fas fa-folder"/>
|
||||
i:Attached.Icon="fa-solid fa-folder"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<Button
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding ChooseFilePath}"
|
||||
i:Attached.Icon="fas fa-folder"/>
|
||||
i:Attached.Icon="fa-solid fa-folder"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<Button
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding ChooseFilePath}"
|
||||
i:Attached.Icon="fas fa-folder"/>
|
||||
i:Attached.Icon="fa-solid fa-folder"/>
|
||||
</StackPanel>
|
||||
|
||||
<CheckBox
|
||||
|
||||
@@ -87,14 +87,14 @@
|
||||
Command="{Binding AddFiles}"
|
||||
Text="Add"
|
||||
Spacing="5"
|
||||
Icon="fas fa-plus"/>
|
||||
Icon="fa-solid fa-plus"/>
|
||||
|
||||
<controls:ButtonWithIcon Padding="5"
|
||||
IsEnabled="{Binding #FilesListBox.SelectedItems.Count}"
|
||||
Command="{Binding RemoveFiles}"
|
||||
Text="Remove"
|
||||
Spacing="5"
|
||||
Icon="fas fa-minus"/>
|
||||
Icon="fa-solid fa-minus"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel
|
||||
@@ -105,14 +105,14 @@
|
||||
Command="{Binding Operation.Sort}"
|
||||
Text="Sort by file name"
|
||||
Spacing="5"
|
||||
Icon="fas fa-sort-alpha-up"/>
|
||||
Icon="fa-solid fa-sort-alpha-up"/>
|
||||
|
||||
<controls:ButtonWithIcon
|
||||
IsEnabled="{Binding Operation.Files.Count}"
|
||||
Padding="5" Command="{Binding ClearFiles}"
|
||||
Text="Clear"
|
||||
Spacing="5"
|
||||
Icon="fas fa-times"/>
|
||||
Icon="fa-solid fa-xmark"/>
|
||||
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<Button Grid.Column="1"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding SelectFile}"
|
||||
i:Attached.Icon="fas fa-file-import"/>
|
||||
i:Attached.Icon="fa-solid fa-file-import"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0"
|
||||
|
||||
@@ -3,29 +3,73 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
|
||||
xmlns:controls="clr-namespace:UVtools.WPF.Controls"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="UVtools.WPF.Controls.Tools.ToolPCBExposureControl">
|
||||
<Grid ColumnDefinitions="Auto,10,350">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid
|
||||
RowDefinitions="Auto,10,Auto,10,Auto,10,Auto"
|
||||
ColumnDefinitions="Auto,10,400">
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Gerber file:"/>
|
||||
<Border BorderBrush="Gray" BorderThickness="1" Padding="5">
|
||||
<Grid>
|
||||
<StackPanel Spacing="5" Orientation="Horizontal">
|
||||
<controls:ButtonWithIcon Padding="5"
|
||||
Command="{Binding AddFiles}"
|
||||
Text="Add"
|
||||
Spacing="5"
|
||||
Icon="fa-solid fa-plus"/>
|
||||
|
||||
<Grid Grid.Row="0" Grid.Column="2"
|
||||
ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
IsReadOnly="True"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Operation.FilePath}"/>
|
||||
<Button Grid.Column="1"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding SelectFile}"
|
||||
i:Attached.Icon="fas fa-file-import"/>
|
||||
<controls:ButtonWithIcon Padding="5"
|
||||
Command="{Binding AddFilesFromZip}"
|
||||
Text="Import zip"
|
||||
Spacing="5"
|
||||
Icon="fa-solid fa-file-zipper"/>
|
||||
|
||||
<controls:ButtonWithIcon Padding="5"
|
||||
IsEnabled="{Binding #FilesListBox.SelectedItems.Count}"
|
||||
Command="{Binding RemoveFiles}"
|
||||
Text="Remove"
|
||||
Spacing="5"
|
||||
Icon="fa-solid fa-minus"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel HorizontalAlignment="Right"
|
||||
Spacing="5" Orientation="Horizontal">
|
||||
<!--
|
||||
<controls:ButtonWithIcon Padding="5"
|
||||
IsEnabled="{Binding Operation.Files.Count}"
|
||||
Command="{Binding Operation.Sort}"
|
||||
Text="Sort by file name"
|
||||
Spacing="5"
|
||||
Icon="fa-solid fa-sort-alpha-up"/>
|
||||
-->
|
||||
<controls:ButtonWithIcon
|
||||
IsEnabled="{Binding Operation.Files.Count}"
|
||||
Padding="5" Command="{Binding ClearFiles}"
|
||||
Text="Clear"
|
||||
Spacing="5"
|
||||
Icon="fa-solid fa-xmark"/>
|
||||
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Operation.Files.Count, StringFormat=Files: \{0\}}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<ListBox Name="FilesListBox"
|
||||
SelectionMode="Multiple"
|
||||
SelectedItem="{Binding SelectedFile}"
|
||||
MinHeight="50"
|
||||
MaxHeight="400"
|
||||
Items="{Binding Operation.Files}" />
|
||||
|
||||
<Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto"
|
||||
ColumnDefinitions="Auto,10,400">
|
||||
|
||||
<ToggleSwitch Grid.Row="0" Grid.Column="2"
|
||||
IsChecked="{Binding Operation.MergeFiles}"
|
||||
OnContent="Merge all gerber files into one layer"
|
||||
OffContent="Create one layer per gerber file"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Timers;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using UVtools.Core.Extensions;
|
||||
using UVtools.Core.Objects;
|
||||
using UVtools.Core.Operations;
|
||||
using UVtools.WPF.Extensions;
|
||||
using UVtools.WPF.Windows;
|
||||
@@ -20,20 +22,35 @@ namespace UVtools.WPF.Controls.Tools
|
||||
public OperationPCBExposure Operation => BaseOperation as OperationPCBExposure;
|
||||
|
||||
private readonly Timer _timer;
|
||||
private ListBox FilesListBox;
|
||||
|
||||
private Bitmap _previewImage;
|
||||
private ValueDescription _selectedFile;
|
||||
|
||||
public Bitmap PreviewImage
|
||||
{
|
||||
get => _previewImage;
|
||||
set => RaiseAndSetIfChanged(ref _previewImage, value);
|
||||
}
|
||||
|
||||
public ValueDescription SelectedFile
|
||||
{
|
||||
get => _selectedFile;
|
||||
set
|
||||
{
|
||||
if (!RaiseAndSetIfChanged(ref _selectedFile, value)) return;
|
||||
UpdatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
public ToolPCBExposureControl()
|
||||
{
|
||||
BaseOperation = new OperationPCBExposure(SlicerFile);
|
||||
if (!ValidateSpawn()) return;
|
||||
InitializeComponent();
|
||||
|
||||
FilesListBox = this.Find<ListBox>("FilesListBox");
|
||||
|
||||
_timer = new Timer(20)
|
||||
{
|
||||
AutoReset = false
|
||||
@@ -60,6 +77,8 @@ namespace UVtools.WPF.Controls.Tools
|
||||
};
|
||||
_timer.Stop();
|
||||
_timer.Start();
|
||||
ParentWindow.ButtonOkEnabled = Operation.FileCount > 0;
|
||||
Operation.Files.CollectionChanged += (sender, e) => ParentWindow.ButtonOkEnabled = Operation.FileCount > 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -68,7 +87,13 @@ namespace UVtools.WPF.Controls.Tools
|
||||
{
|
||||
try
|
||||
{
|
||||
using var mat = Operation.GetMat();
|
||||
PreviewImage = null;
|
||||
if (_selectedFile is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!_selectedFile.ValueAsString.EndsWith(".gbr", StringComparison.OrdinalIgnoreCase) || !File.Exists(_selectedFile.ValueAsString)) return;
|
||||
using var mat = Operation.GetMat(_selectedFile.ValueAsString);
|
||||
using var matCropped = mat.CropByBounds(20);
|
||||
_previewImage?.Dispose();
|
||||
PreviewImage = matCropped.ToBitmap();
|
||||
@@ -80,11 +105,11 @@ namespace UVtools.WPF.Controls.Tools
|
||||
|
||||
}
|
||||
|
||||
public async void SelectFile()
|
||||
public async void AddFiles()
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
AllowMultiple = false,
|
||||
AllowMultiple = true,
|
||||
Filters = new List<FileDialogFilter>
|
||||
{
|
||||
new()
|
||||
@@ -98,7 +123,32 @@ namespace UVtools.WPF.Controls.Tools
|
||||
var files = await dialog.ShowAsync(ParentWindow);
|
||||
if (files is null || files.Length == 0) return;
|
||||
|
||||
Operation.FilePath = files[0];
|
||||
Operation.AddFiles(files);
|
||||
}
|
||||
|
||||
public async void AddFilesFromZip()
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
AllowMultiple = false,
|
||||
Filters = Helpers.ZipFileFilter
|
||||
};
|
||||
|
||||
var files = await dialog.ShowAsync(ParentWindow);
|
||||
if (files is null || files.Length == 0) return;
|
||||
|
||||
Operation.AddFilesFromZip(files[0]);
|
||||
}
|
||||
|
||||
public void RemoveFiles()
|
||||
{
|
||||
Operation.Files.RemoveRange(FilesListBox.SelectedItems.OfType<ValueDescription>());
|
||||
}
|
||||
|
||||
public void ClearFiles()
|
||||
{
|
||||
Operation.Files.Clear();
|
||||
PreviewImage = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
Padding="10,5"
|
||||
ToolTip.Tip="Fill spacing given the current number of columns, object size and left over space."
|
||||
Command="{Binding Operation.FillColumnSpacing}"
|
||||
i:Attached.Icon="fas fa-expand-alt"/>
|
||||
i:Attached.Icon="fa-solid fa-expand-alt"/>
|
||||
<TextBlock
|
||||
Grid.Column="10"
|
||||
Grid.Row="0"
|
||||
@@ -86,7 +86,7 @@
|
||||
Padding="10,5"
|
||||
ToolTip.Tip="Fill spacing given the current number of rows, object size and left over space."
|
||||
Command="{Binding Operation.FillRowSpacing}"
|
||||
i:Attached.Icon="fas fa-expand-alt"/>
|
||||
i:Attached.Icon="fa-solid fa-expand-alt"/>
|
||||
<TextBlock
|
||||
Grid.Column="10"
|
||||
Grid.Row="2"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
VerticalAlignment="Stretch"
|
||||
ToolTip.Tip="Select the sliced file without supports and raft. (Model B)"
|
||||
Command="{Binding ImportFile}"
|
||||
i:Attached.Icon="fas fa-file-import"/>
|
||||
i:Attached.Icon="fa-solid fa-file-import"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
|
||||
@@ -23,28 +23,28 @@
|
||||
ToolTip.Tip="Load script file"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding LoadScript}"
|
||||
i:Attached.Icon="fas fa-file-import"/>
|
||||
i:Attached.Icon="fa-solid fa-file-import"/>
|
||||
|
||||
<Button
|
||||
IsEnabled="{Binding Operation.HaveFile}"
|
||||
ToolTip.Tip="Reloads the script"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding ReloadScript}"
|
||||
i:Attached.Icon="fas fa-sync-alt"/>
|
||||
i:Attached.Icon="fa-solid fa-sync-alt"/>
|
||||
|
||||
<Button
|
||||
IsEnabled="{Binding Operation.HaveFile}"
|
||||
ToolTip.Tip="Open the script folder"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenScriptFolder}"
|
||||
i:Attached.Icon="fas fa-folder"/>
|
||||
i:Attached.Icon="fa-solid fa-folder"/>
|
||||
|
||||
<Button
|
||||
IsEnabled="{Binding Operation.HaveFile}"
|
||||
ToolTip.Tip="Open the script file"
|
||||
VerticalAlignment="Center"
|
||||
Command="{Binding OpenScriptFile}"
|
||||
i:Attached.Icon="fas fa-file-code"/>
|
||||
i:Attached.Icon="fa-solid fa-file-code"/>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
@@ -43,10 +43,7 @@ public class ToolScriptingControl : ToolControl
|
||||
case ToolWindow.Callbacks.Loaded:
|
||||
if(ParentWindow is not null) ParentWindow.ButtonOkEnabled = Operation.CanExecute;
|
||||
ReloadGUI();
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ReloadScript();
|
||||
}, DispatcherPriority.Loaded);
|
||||
Dispatcher.UIThread.Post(ReloadScript, DispatcherPriority.Loaded);
|
||||
Operation.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Operation.CanExecute))
|
||||
@@ -705,6 +702,6 @@ public class ToolScriptingControl : ToolControl
|
||||
}
|
||||
}
|
||||
|
||||
ParentWindow?.FitToSize();
|
||||
//ParentWindow?.FitToSize();
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public class WindowEx : Window, INotifyPropertyChanged, IStyleable
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
AutoConstainsWindowMaxSize();
|
||||
ConstainsWindowMaxSize();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
@@ -133,24 +133,7 @@ public class WindowEx : Window, INotifyPropertyChanged, IStyleable
|
||||
AutoConstainsWindowMaxSize();
|
||||
}*/
|
||||
|
||||
/*protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var result = base.MeasureOverride(availableSize);
|
||||
if (SizeToContent == SizeToContent.Manual) return result;
|
||||
|
||||
if (MaxWidth > 0 && MaxWidth < result.Width)
|
||||
{
|
||||
result = result.WithWidth(MaxWidth);
|
||||
}
|
||||
if (MaxHeight > 0 && MaxHeight < result.Height)
|
||||
{
|
||||
result = result.WithHeight(MaxHeight);
|
||||
}
|
||||
|
||||
return result;
|
||||
}*/
|
||||
|
||||
public void AutoConstainsWindowMaxSize()
|
||||
public void ConstainsWindowMaxSize()
|
||||
{
|
||||
if (WindowStartupLocation == WindowStartupLocation.CenterOwner && Owner is null)
|
||||
{
|
||||
|
||||
@@ -20,8 +20,7 @@ using Avalonia.Threading;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.Util;
|
||||
using MessageBox.Avalonia.Enums;
|
||||
using UVtools.Core;
|
||||
using UVtools.Core.EmguCV;
|
||||
using UVtools.Core;
|
||||
using UVtools.Core.Extensions;
|
||||
using UVtools.Core.FileFormats;
|
||||
using UVtools.Core.Layers;
|
||||
@@ -97,7 +96,7 @@ public partial class MainWindow
|
||||
IssueSelectedIndex++;
|
||||
}
|
||||
|
||||
public List<IssueOfContours> GetOverlappingIssues(IssueOfContours targetIssue, int indexOffset)
|
||||
/*public List<IssueOfContours> GetOverlappingIssues(IssueOfContours targetIssue, int indexOffset)
|
||||
{
|
||||
var retValue = new List<IssueOfContours>();
|
||||
|
||||
@@ -114,7 +113,7 @@ public partial class MainWindow
|
||||
}
|
||||
|
||||
return retValue;
|
||||
}
|
||||
}*/
|
||||
|
||||
public async void OnClickIssueRemove()
|
||||
{
|
||||
@@ -376,9 +375,27 @@ public partial class MainWindow
|
||||
|
||||
if (resultIssues is not null && resultIssues.Count > 0) issueList.AddRange(resultIssues);
|
||||
|
||||
issueList = issueList.OrderBy(issue => issue.Type)
|
||||
.ThenBy(issue => issue.StartLayerIndex)
|
||||
.ThenBy(issue => issue.Area).ToList();
|
||||
switch (Settings.Issues.DataGridOrderBy)
|
||||
{
|
||||
case IssuesOrderBy.TypeAscLayerAscAreaDesc:
|
||||
issueList = issueList.OrderBy(issue => issue.Type)
|
||||
.ThenBy(issue => issue.StartLayerIndex)
|
||||
.ThenByDescending(issue => issue.Area).ToList();
|
||||
break;
|
||||
case IssuesOrderBy.TypeAscAreaDescLayerAsc:
|
||||
issueList = issueList.OrderBy(issue => issue.Type)
|
||||
.ThenByDescending(issue => issue.Area)
|
||||
.ThenBy(issue => issue.StartLayerIndex).ToList();
|
||||
break;
|
||||
case IssuesOrderBy.AreaDescLayerIndexAscTypeAsc:
|
||||
issueList = issueList.OrderByDescending(issue => issue.Area)
|
||||
.ThenBy(issue => issue.StartLayerIndex)
|
||||
.ThenBy(issue => issue.Type).ToList();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(Settings.Issues.DataGridOrderBy));
|
||||
}
|
||||
|
||||
|
||||
SlicerFile.IssueManager.ReplaceCollection(issueList);
|
||||
}
|
||||
@@ -500,6 +517,26 @@ public partial class MainWindow
|
||||
{
|
||||
var issues = SlicerFile.IssueManager.DetectIssues(islandConfig, overhangConfig, resinTrapConfig, touchingBoundConfig,
|
||||
printHeightConfig, emptyLayersConfig, Progress);
|
||||
|
||||
switch (Settings.Issues.DataGridOrderBy)
|
||||
{
|
||||
case IssuesOrderBy.TypeAscLayerAscAreaDesc:
|
||||
// This order is already made on the detection
|
||||
break;
|
||||
case IssuesOrderBy.TypeAscAreaDescLayerAsc:
|
||||
issues = issues.OrderBy(issue => issue.Type)
|
||||
.ThenByDescending(issue => issue.Area)
|
||||
.ThenBy(issue => issue.StartLayerIndex).ToList();
|
||||
break;
|
||||
case IssuesOrderBy.AreaDescLayerIndexAscTypeAsc:
|
||||
issues = issues.OrderByDescending(issue => issue.Area)
|
||||
.ThenBy(issue => issue.StartLayerIndex)
|
||||
.ThenBy(issue => issue.Type).ToList();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(Settings.Issues.DataGridOrderBy));
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
Header="_Open"
|
||||
HotKey="Ctrl+O" InputGesture="Ctrl+O"
|
||||
Command="{Binding MenuFileOpenClicked}"
|
||||
i:MenuItem.Icon="fas fa-file-import"/>
|
||||
i:MenuItem.Icon="fa-solid fa-file-import"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.OpenNewWindow"
|
||||
Header="Open in _new window"
|
||||
HotKey="Ctrl+Shift+O" InputGesture="Ctrl+Shift+O"
|
||||
Command="{Binding MenuFileOpenNewWindowClicked}"
|
||||
i:MenuItem.Icon="fas fa-file-import"/>
|
||||
i:MenuItem.Icon="fa-solid fa-file-import"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.OpenRecent"
|
||||
Header="Open recent"
|
||||
@@ -37,14 +37,14 @@
|
||||

Shift + Ctrl + Click: Remove file from list
|
||||

Ctrl + Click: Purge non-existing files"
|
||||
Items="{Binding MenuFileOpenRecentItems}"
|
||||
i:MenuItem.Icon="fas fa-file-import"/>
|
||||
i:MenuItem.Icon="fa-solid fa-file-import"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.OpenInPartialMode"
|
||||
Header="Open in partial mode"
|
||||
Command="{Binding MenuFileOpenInPartialModeClicked}"
|
||||
ToolTip.Tip="Open a file only to see and/or edit properties.
|
||||

Layer images won't be loaded and most tools won't run in this mode."
|
||||
i:MenuItem.Icon="fas fa-file-import"/>
|
||||
i:MenuItem.Icon="fa-solid fa-file-import"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.Reload"
|
||||
Header="_Reload"
|
||||
@@ -58,54 +58,54 @@
|
||||
HotKey="Ctrl+S" InputGesture="Ctrl+S"
|
||||
IsEnabled="{Binding CanSave}"
|
||||
Command="{Binding MenuFileSaveClicked}"
|
||||
i:MenuItem.Icon="fas fa-save"/>
|
||||
i:MenuItem.Icon="fa-solid fa-floppy-disk"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.SaveAs"
|
||||
Header="Save _as"
|
||||
HotKey="Ctrl+Shift+S" InputGesture="Ctrl+Shift+S"
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
Command="{Binding MenuFileSaveAsClicked}"
|
||||
i:MenuItem.Icon="fas fa-save"/>
|
||||
i:MenuItem.Icon="fa-solid fa-floppy-disk"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.SendTo"
|
||||
Header="Send to"
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
Items="{Binding MenuFileSendToItems}"
|
||||
i:MenuItem.Icon="fas fa-share-square"/>
|
||||
i:MenuItem.Icon="fa-solid fa-share-square"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.Close"
|
||||
Header="_Close"
|
||||
HotKey="Ctrl+W" InputGesture="Ctrl+W"
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
Command="{Binding OnMenuFileCloseFile}"
|
||||
i:MenuItem.Icon="fas fa-sign-out-alt"/>
|
||||
i:MenuItem.Icon="fa-solid fa-sign-out-alt"/>
|
||||
|
||||
<Separator/>
|
||||
|
||||
<MenuItem Header="I _printed this file" HotKey="Ctrl+P" InputGesture="Ctrl+P"
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
Command="{Binding IPrintedThisFile}"
|
||||
i:MenuItem.Icon="fas fa-flask"/>
|
||||
i:MenuItem.Icon="fa-solid fa-flask"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.OpenContainingFileFolder"
|
||||
Header="Open containing fo_lder"
|
||||
HotKey="Ctrl+Shift+L" InputGesture="Ctrl+Shift+L"
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
Command="{Binding MenuFileOpenContainingFolderClicked}"
|
||||
i:MenuItem.Icon="fas fa-folder-open"/>
|
||||
i:MenuItem.Icon="fa-solid fa-folder-open"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.Extract"
|
||||
Header="_Extract file contents" HotKey="Ctrl+E" InputGesture="Ctrl+E"
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
Command="{Binding ExtractFile}"
|
||||
i:MenuItem.Icon="fas fa-box-open"/>
|
||||
i:MenuItem.Icon="fa-solid fa-box-open"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.Terminal"
|
||||
HotKey="Ctrl+Shift+T" InputGesture="Ctrl+Shift+T"
|
||||
Header="_Terminal"
|
||||
Command="{Binding OpenTerminal}"
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
i:MenuItem.Icon="fas fa-terminal"/>
|
||||
i:MenuItem.Icon="fa-solid fa-terminal"/>
|
||||
|
||||
|
||||
<MenuItem Name="MainMenu.File.Convert"
|
||||
@@ -113,7 +113,7 @@
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
IsVisible="{Binding MenuFileConvertItems, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Items="{Binding MenuFileConvertItems}"
|
||||
i:MenuItem.Icon="fas fa-exchange-alt"/>
|
||||
i:MenuItem.Icon="fa-solid fa-exchange-alt"/>
|
||||
|
||||
<Separator/>
|
||||
|
||||
@@ -121,13 +121,13 @@
|
||||
Header="_Fullscreen"
|
||||
InputGesture="F11" HotKey="F11"
|
||||
Command="{Binding OnMenuFileFullscreen}"
|
||||
i:MenuItem.Icon="fas fa-window-maximize"/>
|
||||
i:MenuItem.Icon="fa-solid fa-window-maximize"/>
|
||||
|
||||
<MenuItem Name="MainMenu.File.Settings"
|
||||
Header="_Settings"
|
||||
InputGesture="F12" HotKey="F12"
|
||||
Command="{Binding MenuFileSettingsClicked}"
|
||||
i:MenuItem.Icon="fas fa-cog"/>
|
||||
i:MenuItem.Icon="fa-solid fa-cog"/>
|
||||
|
||||
<Separator/>
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
Header="_Exit"
|
||||
InputGesture="Alt+F4"
|
||||
Command="{Binding Close}"
|
||||
i:MenuItem.Icon="fas fa-door-open"/>
|
||||
i:MenuItem.Icon="fa-solid fa-door-open"/>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem Header="_Tools"
|
||||
@@ -155,31 +155,31 @@
|
||||
<MenuItem Header="_About"
|
||||
InputGesture="F1" HotKey="F1"
|
||||
Command="{Binding MenuHelpAboutClicked}"
|
||||
i:MenuItem.Icon="fas fa-info-circle"/>
|
||||
i:MenuItem.Icon="fa-solid fa-info-circle"/>
|
||||
|
||||
<MenuItem Header="_Website"
|
||||
InputGesture="Ctrl + F1" HotKey="Ctrl + F1"
|
||||
Command="{Binding OpenHomePage}"
|
||||
i:MenuItem.Icon="fab fa-edge"/>
|
||||
i:MenuItem.Icon="fa-brands fa-edge"/>
|
||||
|
||||
<MenuItem Header="Wi_ki & tutorials"
|
||||
Command="{Binding OpenWebsite}"
|
||||
CommandParameter="https://github.com/sn4k3/UVtools/wiki"
|
||||
i:MenuItem.Icon="fab fa-wikipedia-w"/>
|
||||
i:MenuItem.Icon="fa-brands fa-wikipedia-w"/>
|
||||
|
||||
<MenuItem Header="_Facebook group"
|
||||
Command="{Binding OpenWebsite}"
|
||||
CommandParameter="https://www.facebook.com/groups/uvtools"
|
||||
i:MenuItem.Icon="fab fa-facebook"/>
|
||||
i:MenuItem.Icon="fa-brands fa-facebook"/>
|
||||
|
||||
<MenuItem Header="_Donate"
|
||||
Command="{Binding OpenDonateWebsite}"
|
||||
i:MenuItem.Icon="fas fa-donate"/>
|
||||
i:MenuItem.Icon="fa-solid fa-donate"/>
|
||||
|
||||
<MenuItem Header="_Sponsor"
|
||||
Command="{Binding OpenWebsite}"
|
||||
CommandParameter="https://github.com/sponsors/sn4k3"
|
||||
i:MenuItem.Icon="fas fa-heart"/>
|
||||
i:MenuItem.Icon="fa-solid fa-heart"/>
|
||||
|
||||
<Separator/>
|
||||
|
||||
@@ -187,40 +187,40 @@
|
||||
HotKey="F10"
|
||||
InputGesture="F10"
|
||||
Command="{Binding MenuHelpMaterialManagerClicked}"
|
||||
i:MenuItem.Icon="fas fa-flask"/>
|
||||
i:MenuItem.Icon="fa-solid fa-flask"/>
|
||||
|
||||
<MenuItem Header="_Install profiles into PrusaSlicer"
|
||||
Command="{Binding MenuHelpInstallProfilesClicked}"
|
||||
i:MenuItem.Icon="fas fa-list"/>
|
||||
i:MenuItem.Icon="fa-solid fa-list"/>
|
||||
|
||||
<Separator/>
|
||||
|
||||
<MenuItem Header="_Benchmark"
|
||||
Command="{Binding MenuHelpBenchmarkClicked}"
|
||||
i:MenuItem.Icon="fas fa-microchip"/>
|
||||
i:MenuItem.Icon="fa-solid fa-microchip"/>
|
||||
|
||||
<Separator/>
|
||||
|
||||
<MenuItem Header="_Open settings folder"
|
||||
Command="{Binding MenuHelpOpenSettingsFolderClicked}"
|
||||
i:MenuItem.Icon="fas fa-folder-open"/>
|
||||
i:MenuItem.Icon="fa-solid fa-folder-open"/>
|
||||
|
||||
<Separator/>
|
||||
|
||||
<MenuItem Header="_Report a issue"
|
||||
Command="{Binding OpenWebsite}"
|
||||
CommandParameter="https://github.com/sn4k3/UVtools/issues/new?assignees=sn4k3&labels=&template=bug_report.md&title=%5BBUG%5D+"
|
||||
i:MenuItem.Icon="fas fa-bug"/>
|
||||
i:MenuItem.Icon="fa-solid fa-bug"/>
|
||||
|
||||
<MenuItem Header="Ask a _question"
|
||||
Command="{Binding OpenWebsite}"
|
||||
CommandParameter="https://github.com/sn4k3/UVtools/discussions/categories/q-a"
|
||||
i:MenuItem.Icon="fas fa-question"/>
|
||||
i:MenuItem.Icon="fa-solid fa-question"/>
|
||||
|
||||
<MenuItem Header="Suggest an improvement or new features"
|
||||
Command="{Binding OpenWebsite}"
|
||||
CommandParameter="https://github.com/sn4k3/UVtools/discussions/categories/ideas"
|
||||
i:MenuItem.Icon="fas fa-lightbulb"/>
|
||||
i:MenuItem.Icon="fa-solid fa-lightbulb"/>
|
||||
|
||||
</MenuItem>
|
||||
|
||||
@@ -306,7 +306,7 @@
|
||||
IsEnabled="{Binding IsFileLoaded}">
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<i:Icon Value="fas fa-info-circle"/>
|
||||
<i:Icon Value="fa-solid fa-info-circle"/>
|
||||
<!--<TextBlock Margin="5,0,0,0">Information</TextBlock>!-->
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -328,7 +328,7 @@
|
||||
Spacing="5"
|
||||
VerticalAlignment="Center">
|
||||
<Button IsEnabled="{Binding ThumbnailCanGoPrevious}"
|
||||
i:Attached.Icon="fas fa-caret-left"
|
||||
i:Attached.Icon="fa-solid fa-caret-left"
|
||||
Command="{Binding ThumbnailGoPrevious}"/>
|
||||
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
@@ -341,7 +341,7 @@
|
||||
</TextBlock>
|
||||
|
||||
<Button IsEnabled="{Binding ThumbnailCanGoNext}"
|
||||
i:Attached.Icon="fas fa-caret-right"
|
||||
i:Attached.Icon="fa-solid fa-caret-right"
|
||||
Command="{Binding ThumbnailGoNext}"/>
|
||||
|
||||
</StackPanel>
|
||||
@@ -357,12 +357,12 @@
|
||||
|
||||
<Button IsEnabled="{Binding VisibleThumbnailIndex}"
|
||||
ToolTip.Tip="Replace the current preview image"
|
||||
i:Attached.Icon="fas fa-file-image"
|
||||
i:Attached.Icon="fa-solid fa-file-image"
|
||||
Command="{Binding OnClickThumbnailImport}"/>
|
||||
|
||||
<Button IsEnabled="{Binding VisibleThumbnailIndex}"
|
||||
ToolTip.Tip="Save thumbnail image to a file"
|
||||
i:Attached.Icon="fas fa-save"
|
||||
i:Attached.Icon="fa-solid fa-floppy-disk"
|
||||
Command="{Binding OnClickThumbnailSave}"/>
|
||||
|
||||
</StackPanel>
|
||||
@@ -407,7 +407,7 @@
|
||||
Name="PropertiesSaveButton"
|
||||
IsEnabled="{Binding SlicerFile.CreatedThumbnailsCount}"
|
||||
ToolTip.Tip="Save properties to a file or clipboard"
|
||||
Icon="fas fa-save"
|
||||
Icon="fa-solid fa-floppy-disk"
|
||||
Spacing="3"
|
||||
Text="⮟"
|
||||
Command="{Binding OpenContextMenu}"
|
||||
@@ -417,12 +417,12 @@
|
||||
<MenuItem
|
||||
Command="{Binding OnClickPropertiesSaveFile}"
|
||||
Header="To File"
|
||||
i:MenuItem.Icon="far fa-save"/>
|
||||
i:MenuItem.Icon="fa-regular fa-floppy-disk"/>
|
||||
|
||||
<MenuItem
|
||||
Command="{Binding OnClickPropertiesSaveClipboard}"
|
||||
Header="To Clipboard"
|
||||
i:MenuItem.Icon="far fa-clipboard"/>
|
||||
i:MenuItem.Icon="fa-regular fa-clipboard"/>
|
||||
|
||||
</ContextMenu>
|
||||
</uc:ButtonWithIcon.ContextMenu>
|
||||
@@ -499,7 +499,7 @@
|
||||
IsEnabled="{Binding HaveGCode}">
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<i:Icon Value="fas fa-code"/>
|
||||
<i:Icon Value="fa-solid fa-code"/>
|
||||
<!--<TextBlock
|
||||
IsVisible="{Binding $parent[TabItem].IsSelected}"
|
||||
VerticalAlignment="Center"
|
||||
@@ -531,16 +531,16 @@
|
||||

While this is active, UVtools won't update/generate the gcode, meaning any future change won't be replicated to gcode, unless you press the 'Refresh' button.
|
||||

To save the file with your custom gcode this setting must remain active while saving the file or else it will be re-generated.
|
||||

Use with caution and only if you know what you are doing!"
|
||||
i:Attached.Icon="far fa-edit"/>
|
||||
i:Attached.Icon="fa-regular fa-edit"/>
|
||||
|
||||
<Button
|
||||
ToolTip.Tip="Rebuild GCode with current settings"
|
||||
i:Attached.Icon="fas fa-sync-alt"
|
||||
i:Attached.Icon="fa-solid fa-sync-alt"
|
||||
Command="{Binding OnClickRebuildGcode}"/>
|
||||
|
||||
<uc:ButtonWithIcon Name="GcodeSaveButton"
|
||||
ToolTip.Tip="Save gcode to a file or clipboard"
|
||||
Icon="fas fa-save"
|
||||
Icon="fa-solid fa-floppy-disk"
|
||||
Spacing="3"
|
||||
Text="⮟"
|
||||
Command="{Binding OpenContextMenu}"
|
||||
@@ -550,12 +550,12 @@
|
||||
<MenuItem
|
||||
Command="{Binding OnClickGCodeSaveFile}"
|
||||
Header="To File"
|
||||
i:MenuItem.Icon="far fa-save"/>
|
||||
i:MenuItem.Icon="fa-regular fa-floppy-disk"/>
|
||||
|
||||
<MenuItem
|
||||
Command="{Binding OnClickGCodeSaveClipboard}"
|
||||
Header="To Clipboard"
|
||||
i:MenuItem.Icon="far fa-clipboard"/>
|
||||
i:MenuItem.Icon="fa-regular fa-clipboard"/>
|
||||
|
||||
</ContextMenu>
|
||||
</uc:ButtonWithIcon.ContextMenu>
|
||||
@@ -583,7 +583,7 @@
|
||||
IsEnabled="{Binding IsFileLoaded}" >
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<i:Icon Value="fas fa-radiation-alt"/>
|
||||
<i:Icon Value="fa-solid fa-radiation-alt"/>
|
||||
<!--<TextBlock Margin="5,0,0,0">Issues</TextBlock>!-->
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -602,7 +602,7 @@
|
||||
IsEnabled="{Binding IssueCanGoPrevious}"
|
||||
Command="{Binding IssueGoPrevious}"
|
||||
Interval="100"
|
||||
i:Attached.Icon="fas fa-caret-left"/>
|
||||
i:Attached.Icon="fa-solid fa-caret-left"/>
|
||||
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
@@ -619,13 +619,13 @@
|
||||
IsEnabled="{Binding IssueCanGoNext}"
|
||||
Command="{Binding IssueGoNext}"
|
||||
Interval="100"
|
||||
i:Attached.Icon="fas fa-caret-right"/>
|
||||
i:Attached.Icon="fa-solid fa-caret-right"/>
|
||||
|
||||
<Button VerticalAlignment="Stretch"
|
||||
ToolTip.Tip="Hides and ignores the selected issues, they won't be re-detected.
|
||||

ALT + Click to re-enable the ignored issues."
|
||||
IsEnabled="{Binding #IssuesGrid.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
i:Attached.Icon="fas fa-eye-slash"
|
||||
i:Attached.Icon="fa-solid fa-eye-slash"
|
||||
Command="{Binding OnClickIssueIgnore}"/>
|
||||
|
||||
<Button VerticalAlignment="Stretch"
|
||||
@@ -635,7 +635,7 @@
|
||||

SuctionCup: Drills a vertical vent hole.
|
||||

EmptyLayers: Layers are removed."
|
||||
IsEnabled="{Binding #IssuesGrid.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
i:Attached.Icon="fas fa-trash-alt"
|
||||
i:Attached.Icon="fa-solid fa-trash-alt"
|
||||
Command="{Binding OnClickIssueRemove}"/>
|
||||
|
||||
</StackPanel>
|
||||
@@ -651,13 +651,13 @@
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
ToolTip.Tip="Attempt to repair issues"
|
||||
VerticalAlignment="Stretch"
|
||||
i:Attached.Icon="fas fa-toolbox"
|
||||
i:Attached.Icon="fa-solid fa-toolbox"
|
||||
Command="{Binding OnClickRepairIssues}"/>
|
||||
|
||||
<uc:ButtonWithIcon VerticalAlignment="Stretch"
|
||||
ToolTip.Tip="Compute Issues.
|
||||

Right click to access settings."
|
||||
Icon="fas fa-sync-alt"
|
||||
Icon="fa-solid fa-sync-alt"
|
||||
Spacing="5"
|
||||
Text="Detect ⮟"
|
||||
Command="{Binding OnClickDetectIssues}">
|
||||
@@ -767,7 +767,7 @@
|
||||
IsEnabled="{Binding IsFileLoaded}" >
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<i:Icon Value="fas fa-shield-virus"/>
|
||||
<i:Icon Value="fa-solid fa-shield-virus"/>
|
||||
<!--<TextBlock Margin="5,0,0,0">Suggestions</TextBlock>!-->
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -784,18 +784,18 @@
|
||||
<uc:ButtonWithIcon Grid.Column="0"
|
||||
Text="Unselect all"
|
||||
Spacing="5"
|
||||
Icon="far fa-square"
|
||||
Icon="fa-regular fa-square"
|
||||
Command="{Binding #SuggestionsAvailableListBox.UnselectAll}"/>
|
||||
|
||||
<uc:ButtonWithIcon Grid.Column="2"
|
||||
Text="Select all"
|
||||
Spacing="5"
|
||||
Icon="far fa-check-square"
|
||||
Icon="fa-regular fa-check-square"
|
||||
Command="{Binding #SuggestionsAvailableListBox.SelectAll}"/>
|
||||
|
||||
<StackPanel Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="2">
|
||||
<uc:ButtonWithIcon IsEnabled="{Binding #SuggestionsAvailableListBox.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Icon="fas fa-check"
|
||||
Icon="fa-solid fa-check"
|
||||
Spacing="5"
|
||||
Text="Apply"
|
||||
Command="{Binding ApplySuggestionsClicked}"/>
|
||||
@@ -803,7 +803,7 @@
|
||||
<Button VerticalAlignment="Stretch"
|
||||
ToolTip.Tip="Configure suggestions"
|
||||
Command="{Binding ConfigureSuggestionsClicked}"
|
||||
i:Attached.Icon="fas fa-cog"/>
|
||||
i:Attached.Icon="fa-solid fa-cog"/>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
@@ -832,18 +832,18 @@
|
||||
CommandParameter="{Binding .}"
|
||||
Header="Apply"
|
||||
IsVisible="{Binding !IsInformativeOnly}"
|
||||
i:MenuItem.Icon="fas fa-check"/>
|
||||
i:MenuItem.Icon="fa-solid fa-check"/>
|
||||
<MenuItem
|
||||
Command="{Binding $parent[uc:WindowEx].DataContext.OpenWebsite}"
|
||||
CommandParameter="{Binding InformationUrl}"
|
||||
Header="More information (Web)"
|
||||
IsVisible="{Binding InformationUrl, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
i:MenuItem.Icon="fas fa-info-circle"/>
|
||||
i:MenuItem.Icon="fa-solid fa-info-circle"/>
|
||||
<MenuItem
|
||||
Command="{Binding $parent[uc:WindowEx].DataContext.ConfigureSuggestionClicked}"
|
||||
CommandParameter="{Binding .}"
|
||||
Header="Configure"
|
||||
i:MenuItem.Icon="fas fa-cog"/>
|
||||
i:MenuItem.Icon="fa-solid fa-cog"/>
|
||||
</ContextMenu>
|
||||
</TextBlock.ContextMenu>
|
||||
</TextBlock>
|
||||
@@ -878,12 +878,12 @@
|
||||
CommandParameter="{Binding InformationUrl}"
|
||||
Header="More information (Web)"
|
||||
IsVisible="{Binding InformationUrl, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
i:MenuItem.Icon="fas fa-info-circle"/>
|
||||
i:MenuItem.Icon="fa-solid fa-info-circle"/>
|
||||
<MenuItem
|
||||
Command="{Binding $parent[uc:WindowEx].DataContext.ConfigureSuggestionClicked}"
|
||||
CommandParameter="{Binding .}"
|
||||
Header="Configure"
|
||||
i:MenuItem.Icon="fas fa-cog"/>
|
||||
i:MenuItem.Icon="fa-solid fa-cog"/>
|
||||
</ContextMenu>
|
||||
</TextBlock.ContextMenu>
|
||||
</TextBlock>
|
||||
@@ -902,7 +902,7 @@
|
||||
IsEnabled="{Binding IsPixelEditorActive}">
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<i:Icon Value="fas fa-drafting-compass"/>
|
||||
<i:Icon Value="fa-solid fa-drafting-compass"/>
|
||||
<!--<TextBlock Margin="5,0,0,0">Pixel editor</TextBlock>!-->
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -920,7 +920,7 @@
|
||||
<TabItem ToolTip.Tip="Drawing">
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
|
||||
<i:Icon Value="fas fa-paint-brush"/>
|
||||
<i:Icon Value="fa-solid fa-paintbrush"/>
|
||||
<TextBlock IsVisible="{Binding $parent[TabItem].IsSelected}" Text="Drawing" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -1057,7 +1057,7 @@
|
||||
<TabItem ToolTip.Tip="Text">
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
|
||||
<i:Icon Value="fas fa-font"/>
|
||||
<i:Icon Value="fa-solid fa-font"/>
|
||||
<TextBlock IsVisible="{Binding $parent[TabItem].IsSelected}" Text="Text" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -1239,7 +1239,7 @@
|
||||
<TabItem ToolTip.Tip="Eraser">
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
|
||||
<i:Icon Value="fas fa-eraser"/>
|
||||
<i:Icon Value="fa-solid fa-eraser"/>
|
||||
<TextBlock IsVisible="{Binding $parent[TabItem].IsSelected}" Text="Eraser" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -1304,7 +1304,7 @@
|
||||
>
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
|
||||
<i:Icon Value="fas fa-code-branch"/>
|
||||
<i:Icon Value="fa-solid fa-code-branch"/>
|
||||
<TextBlock IsVisible="{Binding $parent[TabItem].IsSelected}" Text="Supports" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -1378,7 +1378,7 @@
|
||||
>
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
|
||||
<i:Icon Value="fas fa-ring"/>
|
||||
<i:Icon Value="fa-solid fa-ring"/>
|
||||
<TextBlock IsVisible="{Binding $parent[TabItem].IsSelected}" Text="Drain holes" />
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -1413,13 +1413,13 @@
|
||||
Grid.Row="2"
|
||||
Orientation="Horizontal" Spacing="1">
|
||||
<uc:ButtonWithIcon IsEnabled="{Binding #DrawingsGrid.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Icon="fas fa-trash-alt"
|
||||
Icon="fa-solid fa-trash-alt"
|
||||
Spacing="5"
|
||||
Text="Remove"
|
||||
Command="{Binding OnClickDrawingRemove}"/>
|
||||
|
||||
<uc:ButtonWithIcon IsEnabled="{Binding Drawings.Count}"
|
||||
Icon="fas fa-times"
|
||||
Icon="fa-solid fa-xmark"
|
||||
Spacing="5"
|
||||
Text="Clear"
|
||||
Command="{Binding OnClickDrawingClear}"/>
|
||||
@@ -1432,7 +1432,7 @@
|
||||
Spacing="10" >
|
||||
|
||||
<uc:ButtonWithIcon IsEnabled="{Binding Drawings.Count}"
|
||||
Icon="fas fa-check"
|
||||
Icon="fa-solid fa-check"
|
||||
Spacing="5"
|
||||
Text="{Binding Drawings.Count, StringFormat=Apply \{0\} operations}"
|
||||
Command="{Binding DrawModifications}"
|
||||
@@ -1478,7 +1478,7 @@
|
||||
ToolTip.Tip="Clipboard">
|
||||
<TabItem.Header>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<i:Icon Value="fas fa-clipboard-list"/>
|
||||
<i:Icon Value="fa-solid fa-clipboard-list"/>
|
||||
<!--<TextBlock Margin="5,0,0,0">Clipboard</TextBlock>!-->
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
@@ -1497,7 +1497,7 @@
|
||||
HotKey="Ctrl + Z"
|
||||
ToolTip.Tip="Undo [Ctrl + Z]
|
||||

Shift + Click to Undo and edit last operation [Ctrl + Shift + Z]"
|
||||
i:Attached.Icon="fas fa-undo-alt"/>
|
||||
i:Attached.Icon="fa-solid fa-undo-alt"/>
|
||||
|
||||
<TextBlock VerticalAlignment="Center">
|
||||
<TextBlock.Text>
|
||||
@@ -1513,7 +1513,7 @@
|
||||
Command="{Binding ClipboardRedo}"
|
||||
HotKey="Ctrl + Y"
|
||||
ToolTip.Tip="Redo [Ctrl + Y]"
|
||||
i:Attached.Icon="fas fa-redo-alt"/>
|
||||
i:Attached.Icon="fa-solid fa-redo-alt"/>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
@@ -1522,7 +1522,7 @@
|
||||
IsEnabled="{Binding Clipboard.Items.Count}"
|
||||
Command="{Binding ClipboardClear}"
|
||||
ToolTip.Tip="Clear all clips"
|
||||
i:Attached.Icon="fas fa-times"/>
|
||||
i:Attached.Icon="fa-solid fa-xmark"/>
|
||||
</StackPanel>
|
||||
|
||||
<ListBox
|
||||
@@ -1559,7 +1559,7 @@
|
||||
<Button VerticalAlignment="Center"
|
||||
Command="{Binding Logs.Clear}"
|
||||
ToolTip.Tip="Clear all logs"
|
||||
i:Attached.Icon="fas fa-times"/>
|
||||
i:Attached.Icon="fa-solid fa-xmark"/>
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
@@ -1617,7 +1617,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="{Binding CanGoUp}"
|
||||
Command="{Binding GoNextLayer}"
|
||||
i:Attached.Icon="fas fa-angle-up"/>
|
||||
i:Attached.Icon="fa-solid fa-angle-up"/>
|
||||
|
||||
<Grid
|
||||
Name="LayerNavigationSliderGrid"
|
||||
@@ -1669,7 +1669,7 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="{Binding CanGoDown}"
|
||||
Command="{Binding GoPreviousLayer}"
|
||||
i:Attached.Icon="fas fa-angle-down"/>
|
||||
i:Attached.Icon="fa-solid fa-angle-down"/>
|
||||
|
||||
<NumericUpDown Grid.Row="4"
|
||||
Margin="0,5"
|
||||
@@ -1683,7 +1683,7 @@
|
||||
HotKey="Ctrl + Left"
|
||||
IsEnabled="{Binding CanGoDown}"
|
||||
Command="{Binding GoFirstLayer}"
|
||||
i:Attached.Icon="fas fa-angle-double-down"/>
|
||||
i:Attached.Icon="fa-solid fa-angle-double-down"/>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
@@ -1698,7 +1698,7 @@
|
||||
HotKey="Ctrl + Right"
|
||||
IsEnabled="{Binding CanGoUp}"
|
||||
Command="{Binding GoLastLayer}"
|
||||
i:Attached.Icon="fas fa-angle-double-up"/>
|
||||
i:Attached.Icon="fa-solid fa-angle-double-up"/>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Row="6"
|
||||
@@ -1753,7 +1753,7 @@
|
||||
Margin="0,0,1,0"
|
||||
Text="Rotate ⮟"
|
||||
Spacing="5"
|
||||
Icon="fas fa-sync-alt">
|
||||
Icon="fa-solid fa-sync-alt">
|
||||
<uc:ToggleButtonWithIcon.ContextMenu>
|
||||
<ContextMenu PlacementMode="Bottom">
|
||||
<RadioButton
|
||||
@@ -1798,7 +1798,7 @@
|
||||
Margin="0,0,1,0"
|
||||
Text="Difference ⮟"
|
||||
Spacing="5"
|
||||
Icon="fas fa-layer-group">
|
||||
Icon="fa-solid fa-layer-group">
|
||||
<uc:ToggleButtonWithIcon.ContextMenu>
|
||||
<ContextMenu PlacementMode="Bottom">
|
||||
<CheckBox
|
||||
@@ -1816,14 +1816,14 @@
|
||||
Margin="0,0,1,0"
|
||||
Text="Issues ⮟"
|
||||
Spacing="5"
|
||||
Icon="fas fa-radiation-alt">
|
||||
Icon="fa-solid fa-radiation-alt">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu PlacementMode="Bottom">
|
||||
<CheckBox
|
||||
ToolTip.Tip="Show crosshairs for selected issues on the current layer"
|
||||
IsChecked="{Binding ShowLayerImageCrosshairs}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<i:Icon Value="fas fa-crosshairs"/>
|
||||
<i:Icon Value="fa-solid fa-crosshairs"/>
|
||||
<TextBlock Margin="5,0,5,0" Text="Crosshairs"/>
|
||||
</StackPanel>
|
||||
</CheckBox>
|
||||
@@ -1841,7 +1841,7 @@
|
||||
Margin="1,0,2,0"
|
||||
Text="Outline ⮟"
|
||||
Spacing="5"
|
||||
Icon="fas fa-vector-square">
|
||||
Icon="fa-solid fa-vector-square">
|
||||
<uc:ButtonWithIcon.ContextMenu>
|
||||
<ContextMenu Name="LayerPreviewOutlineContextMenu" PlacementMode="Bottom">
|
||||
<CheckBox
|
||||
@@ -1902,7 +1902,7 @@
|
||||
Margin="-1,0,0,0"
|
||||
Text="Pixel editor"
|
||||
Spacing="5"
|
||||
Icon="fas fa-drafting-compass"/>
|
||||
Icon="fa-solid fa-drafting-compass"/>
|
||||
</WrapPanel>
|
||||
|
||||
|
||||
@@ -1926,7 +1926,7 @@
|
||||
ToolTip.Tip="Refresh current layer [F5]"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="1,0,0,0"
|
||||
i:Attached.Icon="fas fa-sync-alt"/>
|
||||
i:Attached.Icon="fa-solid fa-sync-alt"/>
|
||||
|
||||
<uc:ButtonWithIcon
|
||||
Command="{Binding SaveCurrentLayerImage}"
|
||||
@@ -1936,13 +1936,13 @@
|
||||
Margin="1,0,0,0"
|
||||
Text="⮟"
|
||||
Spacing="3"
|
||||
Icon="fas fa-save">
|
||||
Icon="fa-solid fa-floppy-disk">
|
||||
<uc:ButtonWithIcon.ContextMenu>
|
||||
<ContextMenu PlacementMode="Bottom">
|
||||
<MenuItem
|
||||
Command="{Binding SaveCurrentROIImage}"
|
||||
Header="Save the selected region (ROI)"
|
||||
i:MenuItem.Icon="far fa-object-group"/>
|
||||
i:MenuItem.Icon="fa-regular fa-object-group"/>
|
||||
</ContextMenu>
|
||||
</uc:ButtonWithIcon.ContextMenu>
|
||||
</uc:ButtonWithIcon>
|
||||
@@ -1996,7 +1996,7 @@
|
||||
Margin="5,0,0,0"
|
||||
Text="{Binding LayerBoundsStr}"
|
||||
Spacing="5"
|
||||
Icon="fas fa-expand"/>
|
||||
Icon="fa-solid fa-expand"/>
|
||||
|
||||
<uc:ButtonWithIcon
|
||||
IsEnabled="{Binding IsFileLoaded}"
|
||||
@@ -2008,7 +2008,7 @@
|
||||

ESC: Clear ROI & Masks | ESC + Shift: Clear ROI"
|
||||
Text="{Binding LayerROIStr}"
|
||||
Spacing="5"
|
||||
Icon="far fa-object-group">
|
||||
Icon="fa-regular fa-object-group">
|
||||
<uc:ButtonWithIcon.ContextMenu>
|
||||
<ContextMenu Name="ROIContextMenu" PlacementMode="Top">
|
||||
<MenuItem
|
||||
@@ -2046,7 +2046,7 @@
|
||||

Click: Center at position"
|
||||
Text="{Binding LayerPixelPicker}"
|
||||
Spacing="5"
|
||||
Icon="fas fa-map-marker-alt"/>
|
||||
Icon="fa-solid fa-map-marker-alt"/>
|
||||
|
||||
<uc:ButtonWithIcon
|
||||
ToolTip.Tip="Layer image zoom level, use mouse scroll to zoom in/out into image.
|
||||
@@ -2057,7 +2057,7 @@
|
||||
Command="{Binding ZoomToNormal}"
|
||||
Text="{Binding LayerZoomStr}"
|
||||
Spacing="5"
|
||||
Icon="fas fa-search-plus"/>
|
||||
Icon="fa-solid fa-search-plus"/>
|
||||
|
||||
<uc:ButtonWithIcon
|
||||
ToolTip.Tip="Layer Resolution and display size.
|
||||
@@ -2066,7 +2066,7 @@
|
||||
Margin="2,0,0,0"
|
||||
Text="{Binding LayerResolutionStr}"
|
||||
Spacing="5"
|
||||
Icon="fas fa-expand"/>
|
||||
Icon="fa-solid fa-expand"/>
|
||||
|
||||
|
||||
<TextBlock
|
||||
|
||||
@@ -97,6 +97,7 @@ public partial class MainWindow : WindowEx
|
||||
new() { Tag = new OperationCalibrateElephantFoot()},
|
||||
new() { Tag = new OperationCalibrateXYZAccuracy()},
|
||||
new() { Tag = new OperationCalibrateLiftHeight()},
|
||||
new() { Tag = new OperationCalibrateBloomingEffect()},
|
||||
new() { Tag = new OperationCalibrateTolerance()},
|
||||
new() { Tag = new OperationCalibrateGrayscale()},
|
||||
new() { Tag = new OperationCalibrateStressTower()},
|
||||
@@ -350,7 +351,7 @@ public partial class MainWindow : WindowEx
|
||||
{
|
||||
Header = header,
|
||||
Tag = drive,
|
||||
Icon = new Projektanker.Icons.Avalonia.Icon{ Value = "fab fa-usb" }
|
||||
Icon = new Projektanker.Icons.Avalonia.Icon{ Value = "fa-brands fa-usb" }
|
||||
};
|
||||
menuItem.Click += FileSendToItemClick;
|
||||
|
||||
@@ -381,7 +382,7 @@ public partial class MainWindow : WindowEx
|
||||
{
|
||||
Header = location.ToString(),
|
||||
Tag = location,
|
||||
Icon = new Projektanker.Icons.Avalonia.Icon { Value = "fas fa-folder" }
|
||||
Icon = new Projektanker.Icons.Avalonia.Icon { Value = "fa-solid fa-folder" }
|
||||
};
|
||||
menuItem.Click += FileSendToItemClick;
|
||||
|
||||
@@ -412,7 +413,7 @@ public partial class MainWindow : WindowEx
|
||||
{
|
||||
Header = remotePrinter.ToString(),
|
||||
Tag = remotePrinter,
|
||||
Icon = new Projektanker.Icons.Avalonia.Icon { Value = "fas fa-network-wired" }
|
||||
Icon = new Projektanker.Icons.Avalonia.Icon { Value = "fa-solid fa-network-wired" }
|
||||
};
|
||||
menuItem.Click += FileSendToItemClick;
|
||||
|
||||
@@ -443,7 +444,7 @@ public partial class MainWindow : WindowEx
|
||||
{
|
||||
Header = application.Name,
|
||||
Tag = application,
|
||||
Icon = new Projektanker.Icons.Avalonia.Icon { Value = "fas fa-cog" }
|
||||
Icon = new Projektanker.Icons.Avalonia.Icon { Value = "fa-solid fa-cog" }
|
||||
};
|
||||
menuItem.Click += FileSendToItemClick;
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ public class OperationProfiles //: IList<Operation>
|
||||
[XmlElement(typeof(OperationCalibrateElephantFoot))]
|
||||
[XmlElement(typeof(OperationCalibrateXYZAccuracy))]
|
||||
[XmlElement(typeof(OperationCalibrateLiftHeight))]
|
||||
[XmlElement(typeof(OperationCalibrateBloomingEffect))]
|
||||
[XmlElement(typeof(OperationCalibrateTolerance))]
|
||||
[XmlElement(typeof(OperationCalibrateGrayscale))]
|
||||
[XmlElement(typeof(OperationCalibrateStressTower))]
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<Version>3.4.3</Version>
|
||||
<Version>3.5.0</Version>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<PackageIcon>UVtools.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
@@ -39,15 +39,15 @@
|
||||
<NoWarn>1701;1702;</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.14" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.14" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.14" />
|
||||
<PackageReference Include="MessageBox.Avalonia" Version="2.0.0" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia" Version="4.4.0" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="4.4.0" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="4.4.0" />
|
||||
<PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.13" />
|
||||
<PackageReference Include="Avalonia" Version="0.10.15" />
|
||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" />
|
||||
<PackageReference Include="MessageBox.Avalonia" Version="2.0.1" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia" Version="5.0.2" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="5.0.2" />
|
||||
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="5.0.2" />
|
||||
<PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.14" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UVtools.AvaloniaControls\UVtools.AvaloniaControls.csproj" />
|
||||
|
||||
@@ -12,7 +12,6 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Xml.Serialization;
|
||||
using Avalonia.Media;
|
||||
using JetBrains.Annotations;
|
||||
@@ -53,7 +52,7 @@ public sealed class UserSettings : BindableBase
|
||||
private bool _windowsCanResize;
|
||||
private bool _windowsTakeIntoAccountScreenScaling = true;
|
||||
private ushort _windowsHorizontalMargin = 100;
|
||||
private ushort _windowsVerticalMargin = 60;
|
||||
private ushort _windowsVerticalMargin = 80;
|
||||
private byte _defaultOpenFileExtensionIndex;
|
||||
private string _defaultDirectoryOpenFile;
|
||||
private string _defaultDirectorySaveFile;
|
||||
@@ -865,6 +864,7 @@ public sealed class UserSettings : BindableBase
|
||||
private byte _touchingBoundMarginBottom = 5;
|
||||
private bool _touchingBoundSyncMargins = true;
|
||||
private decimal _printHeightOffset;
|
||||
private IssuesOrderBy _dataGridOrderBy = IssuesOrderBy.TypeAscLayerAscAreaDesc;
|
||||
|
||||
public bool ComputeIssuesOnLoad
|
||||
{
|
||||
@@ -926,6 +926,12 @@ public sealed class UserSettings : BindableBase
|
||||
set => RaiseAndSetIfChanged(ref _computeEmptyLayers, value);
|
||||
}
|
||||
|
||||
public IssuesOrderBy DataGridOrderBy
|
||||
{
|
||||
get => _dataGridOrderBy;
|
||||
set => RaiseAndSetIfChanged(ref _dataGridOrderBy, value);
|
||||
}
|
||||
|
||||
public bool DataGridGroupByType
|
||||
{
|
||||
get => _dataGridGroupByType;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Padding="10"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Icon="far fa-clipboard"
|
||||
Icon="fa-regular fa-clipboard"
|
||||
Text="Copy information ⮝">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu PlacementAnchor="Top" PlacementMode="Top">
|
||||
@@ -41,7 +41,7 @@
|
||||
Padding="10"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Icon="fas fa-sign-out-alt"
|
||||
Icon="fa-solid fa-sign-out-alt"
|
||||
Text="Close"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
@@ -155,7 +155,7 @@
|
||||
<controls:ButtonWithIcon Grid.Row="8" VerticalAlignment="Center"
|
||||
Command="{Binding OpenLicense}"
|
||||
Text="{Binding Source={x:Static core:About.License}}"
|
||||
Icon="fas fa-balance-scale"/>
|
||||
Icon="fa-solid fa-balance-scale"/>
|
||||
|
||||
<TabControl Grid.Row="10">
|
||||
<TabItem Header="Description">
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
Spacing="10">
|
||||
<i:Icon FontSize="64"
|
||||
FontWeight="Bold"
|
||||
Value="far fa-frown"
|
||||
Value="fa-regular fa-frown"
|
||||
Foreground="{StaticResource LogoColor}"
|
||||
HorizontalAlignment="Center"/>
|
||||
|
||||
@@ -73,14 +73,14 @@
|
||||
|
||||
<uc:ButtonWithIcon Padding="10"
|
||||
VerticalContentAlignment="Center"
|
||||
Icon="fab fa-edge"
|
||||
Icon="fa-brands fa-edge"
|
||||
Text="Open manual"
|
||||
Command="{Binding OpenBrowser}"
|
||||
CommandParameter="https://github.com/sn4k3/UVtools#requirements"/>
|
||||
|
||||
<uc:ButtonWithIcon Padding="10"
|
||||
VerticalContentAlignment="Center"
|
||||
Icon="fas fa-question"
|
||||
Icon="fa-solid fa-question"
|
||||
Text="Ask for help"
|
||||
Command="{Binding OpenBrowser}"
|
||||
CommandParameter="https://github.com/sn4k3/UVtools/discussions/categories/q-a"/>
|
||||
@@ -89,7 +89,7 @@
|
||||
IsCancel="True"
|
||||
Padding="10"
|
||||
VerticalContentAlignment="Center"
|
||||
Icon="fas fa-sign-out-alt"
|
||||
Icon="fa-solid fa-sign-out-alt"
|
||||
Text="Exit"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<Border Classes="FooterActions">
|
||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="10">
|
||||
<controls:ButtonWithIcon HorizontalAlignment="Right" Name="Actions.Save" Padding="10" IsDefault="True"
|
||||
Icon="fas fa-check"
|
||||
Icon="fa-solid fa-check"
|
||||
Text="Select"
|
||||
Command="{Binding OnClickOk}">
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
<controls:ButtonWithIcon HorizontalAlignment="Right"
|
||||
Name="Actions.Cancel" Padding="10" IsCancel="True"
|
||||
Icon="fas fa-sign-out-alt"
|
||||
Icon="fa-solid fa-sign-out-alt"
|
||||
Text="Cancel"
|
||||
Command="{Binding Close}"/>
|
||||
</StackPanel>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Icon="fas fa-plus"
|
||||
Icon="fa-solid fa-plus"
|
||||
Text="Add new material"
|
||||
Command="{Binding AddNewMaterial}"/>
|
||||
|
||||
@@ -152,13 +152,13 @@
|
||||
|
||||
<controls:ButtonWithIcon VerticalAlignment="Center"
|
||||
IsEnabled="{Binding #MaterialsTable.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Icon="fas fa-trash-alt"
|
||||
Icon="fa-solid fa-trash-alt"
|
||||
Text="Remove selected materials"
|
||||
Command="{Binding RemoveSelectedMaterials}"/>
|
||||
|
||||
<controls:ButtonWithIcon VerticalAlignment="Center"
|
||||
IsEnabled="{Binding Manager.Count}"
|
||||
Icon="fas fa-times"
|
||||
Icon="fa-solid fa-xmark"
|
||||
Text="{Binding Manager.Count, StringFormat=Clear {0} materials}"
|
||||
Command="{Binding ClearMaterials}"/>
|
||||
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
Icon="/Assets/Icons/UVtools.ico"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
SizeToContent="Manual"
|
||||
SizeToContent="WidthAndHeight"
|
||||
MinWidth="300"
|
||||
MinHeight="200"
|
||||
Width="800"
|
||||
Height="600"
|
||||
WindowConstrainMaxSize="Ratio"
|
||||
WindowsWidthMaxSizeRatio="0.75"
|
||||
WindowsHeightMaxSizeRatio="0.75"
|
||||
@@ -77,7 +75,7 @@
|
||||
IsCancel="True"
|
||||
Padding="10"
|
||||
VerticalContentAlignment="Center"
|
||||
Icon="fas fa-sign-out-alt"
|
||||
Icon="fa-solid fa-sign-out-alt"
|
||||
Text="Close"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -86,13 +86,13 @@
|
||||
<StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
|
||||
<uc:ButtonWithIcon Padding="10"
|
||||
IsDefault="True"
|
||||
Icon="fas fa-check"
|
||||
Icon="fa-solid fa-check"
|
||||
Text="Apply"
|
||||
Command="{Binding Apply}"/>
|
||||
|
||||
<uc:ButtonWithIcon Padding="10"
|
||||
IsCancel="True"
|
||||
Icon="fas fa-sign-out-alt"
|
||||
Icon="fa-solid fa-sign-out-alt"
|
||||
Text="Cancel"
|
||||
Command="{Binding Close}"/>
|
||||
</StackPanel>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user