- **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:
Tiago Conceição
2022-06-19 04:19:23 +01:00
parent 2ad9b7b476
commit 15c500ecaf
107 changed files with 1741 additions and 726 deletions
+1
View File
@@ -18,4 +18,5 @@
<!--- If the pull request fixes issue(s) list them like this:
- Fixes #123
- Fixes #456
Otherwise remove this section
-->
+19
View File
@@ -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
View File
@@ -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>
+15
View File
@@ -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
{
+23 -2
View File
@@ -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}";
+23 -15
View File
@@ -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>
+3 -3
View File
@@ -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;
}
+3 -3
View File
@@ -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;
+4
View File
@@ -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
+1 -1
View File
@@ -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);
}
}
+304 -249
View File
@@ -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)
+1 -2
View File
@@ -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);
}
+2 -2
View File
@@ -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))
+1 -1
View File
@@ -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}");
+1 -1
View File
@@ -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}");
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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}\" }}");
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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" +
+1 -1
View File
@@ -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" +
+1 -1
View File
@@ -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 - " +
+1 -1
View File
@@ -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" +
+142 -36
View File
@@ -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
}
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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" +
+1 -1
View File
@@ -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 =>
+1 -1
View File
@@ -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" +
+3 -3
View File
@@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
<Version>3.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">
+4 -4
View File
@@ -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" />
+5 -5
View File
@@ -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>
+12
View File
@@ -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();
}
}
+2 -19
View File
@@ -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)
{
+44 -7
View File
@@ -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)
+92 -92
View File
@@ -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 @@
&#x0a;Shift + Ctrl + Click: Remove file from list
&#x0a;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.
&#x0a;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 &amp; 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&amp;labels=&amp;template=bug_report.md&amp;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 @@
&#x0a;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.
&#x0a;To save the file with your custom gcode this setting must remain active while saving the file or else it will be re-generated.
&#x0a;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.
&#x0a;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 @@
&#x0a;SuctionCup: Drills a vertical vent hole.
&#x0a;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.
&#x0a;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]
&#x0a;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 @@
&#x0a;ESC: Clear ROI &amp; 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 @@
&#x0a;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
+5 -4
View File
@@ -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))]
+10 -10
View File
@@ -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" />
+8 -2
View File
@@ -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;
+3 -3
View File
@@ -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">
+4 -4
View File
@@ -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>
+2 -2
View File
@@ -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}"/>
+2 -4
View File
@@ -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