Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2022-11-03 02:39:21 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2022-11-03 02:39:21 +0300
commite727a097b31a0b71f8bc22649364e69f2aa72b3a (patch)
tree0e5571f2632faa4168f0de9eacbbd59bbb3ae7bb
parent55cbe02a7b756f3318ee2382584a1f52aa2180ce (diff)
v3.8.1v3.8.1
- **Tools:** - **PCB Exposure:** - (Add) Allow to scale the drawing sizes per gerber file - (Add) Allow to invert the drawing polarity per gerber file (#592) - (Add) Allow to drag and drop files into "Add files" button and grid header - (Improvement) Do not add empty layers when usable to draw or when draw a all black image - (Improvement) "Merge all gerbers into one layer" will now draw all gerber into one image instead of perform the Max(of all gerbers pixels), allowing to subtract areas as they are on gerbers - **Import layers:** Fix error when trying to insert layers - **Export layers images:** Better compression of contours for SVG export, resulting in smooth curves, better visuals and lower file size - (Add) Outline: Triangulate - (Remove) Avalonia.Diagnostics dependency in release mode
-rw-r--r--CHANGELOG.md16
-rw-r--r--RELEASE_NOTES.md38
-rw-r--r--UVtools.Core/Extensions/EmguExtensions.cs56
-rw-r--r--UVtools.Core/FileFormats/FileFormat.cs2
-rw-r--r--UVtools.Core/FileFormats/ZCodexFile.cs2
-rw-r--r--UVtools.Core/Gerber/GerberDocument.cs107
-rw-r--r--UVtools.Core/Helpers.cs6
-rw-r--r--UVtools.Core/Objects/ExposureItem.cs9
-rw-r--r--UVtools.Core/Objects/GenericFileRepresentation.cs137
-rw-r--r--UVtools.Core/Objects/RangeObservableCollection.cs9
-rw-r--r--UVtools.Core/Operations/OperationLayerExportImage.cs43
-rw-r--r--UVtools.Core/Operations/OperationLayerImport.cs43
-rw-r--r--UVtools.Core/Operations/OperationPCBExposure.cs142
-rw-r--r--UVtools.Core/UVtools.Core.csproj2
-rw-r--r--UVtools.Core/Voxel/Voxelizer.cs4
-rw-r--r--UVtools.Installer/Code/HeatGeneratedFileList.wxs4
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml12
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml14
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml4
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml6
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml8
-rw-r--r--UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs18
-rw-r--r--UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml66
-rw-r--r--UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs39
-rw-r--r--UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml2
-rw-r--r--UVtools.WPF/MainWindow.LayerPreview.cs69
-rw-r--r--UVtools.WPF/MainWindow.axaml3
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs6
-rw-r--r--UVtools.WPF/Program.cs60
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj4
-rw-r--r--UVtools.WPF/UserSettings.cs49
-rw-r--r--UVtools.WPF/Windows/MaterialManagerWindow.axaml20
-rw-r--r--UVtools.WPF/Windows/ProgressWindow.axaml2
-rw-r--r--UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml8
-rw-r--r--UVtools.WPF/Windows/SettingsWindow.axaml65
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml10
-rw-r--r--documentation/UVtools.Core.xml73
43 files changed, 869 insertions, 301 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4dcda5..5e5772c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 02/11/2022 - v3.8.1
+
+- **Tools:**
+ - **PCB Exposure:**
+ - (Add) Allow to scale the drawing sizes per gerber file
+ - (Add) Allow to invert the drawing polarity per gerber file (#592)
+ - (Add) Allow to drag and drop files into "Add files" button and grid header
+ - (Improvement) Do not add empty layers when usable to draw or when draw a all black image
+ - (Improvement) "Merge all gerbers into one layer" will now draw all gerber into one image instead of perform the Max(of all gerbers pixels), allowing to subtract areas as they are on gerbers
+ - **Import layers:** Fix error when trying to insert layers
+ - **Export layers images:** Better compression of contours for SVG export, resulting in smooth curves, better visuals and lower file size
+- (Add) Outline: Triangulate
+- (Remove) Avalonia.Diagnostics dependency in release mode
+
## 30/10/2022 - v3.8.0
- **File formats:**
@@ -13,7 +27,7 @@
- (Improvement) Show print time label formatted as hh:mm:ss
- **PCB Exposure:**
- (Improvement) Increase "Exposure time" maximum from 200s to 1000s (#592)
- - (Fix) Set `BottomLightHeight` to 0
+ - (Fix) Set `BottomLiftHeight` to 0
- (Fix) Set `TransitionLayerCount` to 0
- **Issues:**
- (Improvement) Overhang detection by using a dynamic cross kernel
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 5af7a2e..a355dcb 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,29 +1,11 @@
-- **File formats:**
- - (Add) Property: DisplayTotalOnTime
- - (Add) Property: DisplayTotalOffTime
- - (Add) SL1File Property: high_viscosity_tilt_time
-- **Tools**
- - **I printed this file**
- - (Add) Display on time for the print information
- - (Improvement) Labels on numeric box
- - (Improvement) Show print time label formatted as hh:mm:ss
+- **Tools:**
- **PCB Exposure:**
- - (Improvement) Increase "Exposure time" maximum from 200s to 1000s (#592)
- - (Fix) Set `BottomLightHeight` to 0
- - (Fix) Set `TransitionLayerCount` to 0
-- **Issues:**
- - (Improvement) Overhang detection by using a dynamic cross kernel
- - (Improvement) Bring back the discard logic of "false-positive" islands based on overhang detection but improve the threshold of the detection to be safer (#591, #591)
-- **PrusaSlicer:**
- - (Change) Elegoo Mars 2 to use file version 4
- - (Change) Elegoo Mars 2 Pro to use file version 4
-- (Add) Status bar: On and Off time (hh:mm:ss) as tooltip in the Print time label
-- (Improvement) When any of libcvextern dependencies are missing, it try to show the missing libraries in the error message (Linux only)
-- (Improvement) Auto-upgrade procedure for non-Windows systems
-- (Improvement) macOS arm64 (M1/M2) can now run natively if installed via the auto installer script
-- (Fix) Error on Mat cache manager when file have only one layer
-- (Fix) Suggestion "Transition layer count" shows as never applied when using with file formats that use in-firmware transition layers
-- (Upgrade) openCV from 4.5.4 to 4.6.0
- - Custom library is now built to strip unused dependencies and reduce library size
-- (Remove) arch and rhel packages in favor of a generic linux
-
+ - (Add) Allow to scale the drawing sizes per gerber file
+ - (Add) Allow to invert the drawing polarity per gerber file (#592)
+ - (Add) Allow to drag and drop files into "Add files" button and grid header
+ - (Improvement) Do not add empty layers when usable to draw or when draw a all black image
+ - (Improvement) "Merge all gerbers into one layer" will now draw all gerber into one image instead of perform the Max(of all gerbers pixels), allowing to subtract areas as they are on gerbers
+ - **Import layers:** Fix error when trying to insert layers
+ - **Export layers images:** Better compression of contours for SVG export, resulting in smooth curves, better visuals and lower file size
+- (Add) Outline: Triangulate
+- (Remove) Avalonia.Diagnostics dependency in release mode
diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs
index eb74c44..6dd3694 100644
--- a/UVtools.Core/Extensions/EmguExtensions.cs
+++ b/UVtools.Core/Extensions/EmguExtensions.cs
@@ -12,9 +12,11 @@ using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;
using System;
+using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
+using System.Text;
using CommunityToolkit.HighPerformance;
using UVtools.Core.EmguCV;
using UVtools.Core.Objects;
@@ -1332,6 +1334,60 @@ public static class EmguExtensions
}
#endregion
+ #region Other Images Types
+
+ /// <summary>
+ /// From <paramref name="src"/> gets the SVG path's. Tags are not included.
+ /// </summary>
+ /// <param name="src"></param>
+ /// <param name="compression">Compression method for the contours</param>
+ /// <param name="threshold">True to binary threshold first</param>
+ /// <returns>Array of path's</returns>
+ public static IEnumerable<string> GetSvgPath(this Mat src, ChainApproxMethod compression = ChainApproxMethod.ChainApproxSimple, bool threshold = true)
+ {
+ var mat = src;
+ if (threshold)
+ {
+ mat = new();
+ CvInvoke.Threshold(src, mat, 127, byte.MaxValue, ThresholdType.Binary);
+ }
+
+ using var contours = mat.FindContours(out var hierarchy, RetrType.Tree, compression);
+
+ var sb = new StringBuilder();
+ for (int i = 0; i < contours.Size; i++)
+ {
+ if (hierarchy[i, EmguContour.HierarchyParent] == -1) // Top hierarchy
+ {
+ if (sb.Length > 0)
+ {
+ yield return sb.ToString();
+ sb.Clear();
+ }
+ }
+ else
+ {
+ sb.Append(" ");
+ }
+
+ sb.Append($"M {contours[i][0].X} {contours[i][0].Y} L");
+ for (int x = 1; x < contours[i].Size; x++)
+ {
+ sb.Append($" {contours[i][x].X} {contours[i][x].Y}");
+ }
+ sb.Append(" Z");
+ }
+
+ if (sb.Length > 0)
+ {
+ yield return sb.ToString();
+ sb.Clear();
+ }
+
+ if (!ReferenceEquals(src, mat)) mat.Dispose();
+ }
+ #endregion
+
#region Utilities methods
/// <summary>
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index 136fe24..742b023 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -2854,7 +2854,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
/// <summary>
/// Gets the total time in seconds the display will remain on exposing the layers during the print
/// </summary>
- public float DisplayTotalOnTime => (float)Math.Round(this.Sum(layer => layer.ExposureTime), 2);
+ public float DisplayTotalOnTime => (float)Math.Round(this.Where(layer => layer is not null).Sum(layer => layer.ExposureTime), 2);
/// <summary>
/// Gets the total time formatted in hours, minutes and seconds the display will remain on exposing the layers during the print
diff --git a/UVtools.Core/FileFormats/ZCodexFile.cs b/UVtools.Core/FileFormats/ZCodexFile.cs
index ed49591..9f465e0 100644
--- a/UVtools.Core/FileFormats/ZCodexFile.cs
+++ b/UVtools.Core/FileFormats/ZCodexFile.cs
@@ -532,7 +532,7 @@ M106 S0
if (line.StartsWith(GCodeKeywordDelaySupportFull) || line.StartsWith(GCodeKeywordDelayModel))
{
var startStr = $"{GCodeKeywordSlice} {layerIndex}";
- var stripGcode = gcode[(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length)..].Trim(' ', '\n', '\r', '\t');
+ var stripGcode = gcode[(gcode.IndexOf(startStr, StringComparison.OrdinalIgnoreCase) + startStr.Length)..].Trim(' ', '\n', '\r', '\t');
float liftHeight = 0;
float liftSpeed = GetBottomOrNormalValue((uint)layerIndex, BottomLiftSpeed, LiftSpeed);
diff --git a/UVtools.Core/Gerber/GerberDocument.cs b/UVtools.Core/Gerber/GerberDocument.cs
index eca46fb..271d371 100644
--- a/UVtools.Core/Gerber/GerberDocument.cs
+++ b/UVtools.Core/Gerber/GerberDocument.cs
@@ -17,6 +17,7 @@ using Emgu.CV.Structure;
using Emgu.CV.Util;
using UVtools.Core.Extensions;
using UVtools.Core.Gerber.Apertures;
+using UVtools.Core.Operations;
namespace UVtools.Core.Gerber;
@@ -49,7 +50,28 @@ public class GerberDocument
public SizeF XYppmm { get; init; }
- public MCvScalar PolarityColor => Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor;
+ /// <summary>
+ /// Gets the current polarity as <see cref="MCvScalar"/>. <see cref="InversePolarity"/> will affect the return value
+ /// </summary>
+ public MCvScalar PolarityColor =>
+ Polarity == GerberPolarityType.Dark
+ ? !InversePolarity
+ ? EmguExtensions.WhiteColor
+ : EmguExtensions.BlackColor
+ : !InversePolarity
+ ? EmguExtensions.BlackColor
+ : EmguExtensions.WhiteColor;
+
+ /// <summary>
+ /// Gets or sets to inverse the polarity on drawing
+ /// </summary>
+ public bool InversePolarity { get; set; }
+
+ /// <summary>
+ /// Gets or sets the scale to apply to each shape drawing size.
+ /// Positions and vectors aren't affected by this.
+ /// </summary>
+ public double SizeScale { get; set; } = 1;
#endregion
@@ -62,14 +84,9 @@ public class GerberDocument
{
}
- public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntialiasing = false)
+ public static void ParseAndDraw(GerberDocument document, string filePath, Mat mat, bool enableAntiAliasing = false)
{
using var file = new StreamReader(filePath);
- var document = new GerberDocument
- {
- SizeMidpointRounding = sizeMidpointRounding,
- XYppmm = xyPpmm
- };
int FSlength = "%FSLAX46Y46*%".Length;
int MOlength = "%MOMM*%".Length;
@@ -85,7 +102,7 @@ public class GerberDocument
while ((line = file.ReadLine()) is not null)
{
line = line.Trim();
- if(line == string.Empty) continue;
+ if (line == string.Empty) continue;
if (line.StartsWith("M02")) break;
var accumulatedLine = line;
@@ -98,7 +115,7 @@ public class GerberDocument
line = accumulatedLine;
- if(currentMacro is not null)
+ if (currentMacro is not null)
{
currentMacro.ParsePrimitive(line);
if (line[^1] == '%') currentMacro = null;
@@ -107,8 +124,8 @@ public class GerberDocument
if (line.StartsWith("%MO") && line.Length >= MOlength)
{
- if(line[3] == 'M' && line[4] == 'M') document.UnitType = GerberUnitType.Millimeter;
- else if(line[3] == 'I' && line[4] == 'N') document.UnitType = GerberUnitType.Inch;
+ if (line[3] == 'M' && line[4] == 'M') document.UnitType = GerberUnitType.Millimeter;
+ else if (line[3] == 'I' && line[4] == 'N') document.UnitType = GerberUnitType.Inch;
continue;
}
@@ -138,7 +155,7 @@ public class GerberDocument
continue;
}
- if (line.StartsWith("%FS") && line.Length >= FSlength)
+ if (line.StartsWith("%FS") && line.Length >= FSlength)
{
// %FSLAX34Y34*%
// 0123456789
@@ -207,7 +224,7 @@ public class GerberDocument
if (regionPoints.Count > 0)
{
using var vec = new VectorOfPoint(regionPoints.ToArray());
- CvInvoke.FillPoly(mat, vec, document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
+ CvInvoke.FillPoly(mat, vec, document.PolarityColor, enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
}
//CvInvoke.Imshow("G37", mat);
//CvInvoke.WaitKey();
@@ -311,7 +328,7 @@ public class GerberDocument
if (regionPoints.Count > 0)
{
using var vec = new VectorOfPoint(regionPoints.ToArray());
- CvInvoke.FillPoly(mat, vec, document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
+ CvInvoke.FillPoly(mat, vec, document.PolarityColor, enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
}
regionPoints.Clear();
}
@@ -319,7 +336,7 @@ public class GerberDocument
var pt = document.PositionMmToPx(nowX, nowY);
if (regionPoints.Count == 0 || (regionPoints.Count > 0 && regionPoints[^1] != pt)) regionPoints.Add(pt);
}
- else if(currentAperture is not null)
+ else if (currentAperture is not null)
{
if (d == 1)
{
@@ -331,7 +348,7 @@ public class GerberDocument
double yOffset = 0;
var matchI = Regex.Match(line, @"I(-?\d+)");
var matchJ = Regex.Match(line, @"J(-?\d+)");
- if(!matchI.Success || !matchJ.Success || matchI.Groups.Count < 2 || matchJ.Groups.Count < 2) continue;
+ if (!matchI.Success || !matchJ.Success || matchI.Groups.Count < 2 || matchJ.Groups.Count < 2) continue;
var valueStr = document.ZerosSuppressionType switch
@@ -367,7 +384,7 @@ public class GerberDocument
document.SizeMmToPx(Math.Abs(xOffset), Math.Abs(xOffset)),
0, 0, 360.0, document.PolarityColor,
document.SizeMmToPx(circleAperture.Diameter),
- enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected
+ enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected
);
}
else
@@ -381,7 +398,7 @@ public class GerberDocument
enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected
);*/
}
-
+
}
else
{
@@ -390,7 +407,7 @@ public class GerberDocument
document.PositionMmToPx(nowX, nowY),
document.PolarityColor,
EmguExtensions.CorrectThickness(document.SizeMmToPx(circleAperture.Diameter)),
- enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
+ enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
/*mat.DrawLineAccurate(PositionMmToPx(currentX, currentY, xyPpmm),
PositionMmToPx(nowX, nowY, xyPpmm),
document.PolarityColor
@@ -408,13 +425,13 @@ public class GerberDocument
//CvInvoke.Imshow("Line", mat);
//CvInvoke.WaitKey();
}
-
+
}
}
else if (d == 3)
{
- currentAperture.DrawFlashD3(mat, new PointF((float) nowX, (float) nowY),
- document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);
+ currentAperture.DrawFlashD3(mat, new PointF((float)nowX, (float)nowY),
+ document.PolarityColor, enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
//CvInvoke.Imshow("G37", mat);
//CvInvoke.WaitKey();
}
@@ -425,6 +442,32 @@ public class GerberDocument
continue;
}
}
+ }
+
+ public static GerberDocument ParseAndDraw(OperationPCBExposure.PCBExposureFile file, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntiAliasing = false)
+ {
+ var document = new GerberDocument
+ {
+ SizeMidpointRounding = sizeMidpointRounding,
+ XYppmm = xyPpmm,
+ InversePolarity = file.InvertPolarity,
+ SizeScale = file.SizeScale
+ };
+
+ ParseAndDraw(document, file.FilePath, mat, enableAntiAliasing);
+
+ return document;
+ }
+
+ public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntiAliasing = false)
+ {
+ var document = new GerberDocument
+ {
+ SizeMidpointRounding = sizeMidpointRounding,
+ XYppmm = xyPpmm
+ };
+
+ ParseAndDraw(document, filePath, mat, enableAntiAliasing);
return document;
}
@@ -463,28 +506,28 @@ public class GerberDocument
=> new((int)Math.Round(atXmm * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atYmm * XYppmm.Height, MidpointRounding.AwayFromZero));
public Size SizeMmToPx(SizeF sizeMm)
- => new((int)Math.Max(1, Math.Round(sizeMm.Width * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)),
- (int)Math.Max(1, Math.Round(sizeMm.Height * XYppmm.Height, (MidpointRounding)SizeMidpointRounding)));
+ => new((int)Math.Max(1, Math.Round(sizeMm.Width * XYppmm.Width * SizeScale, (MidpointRounding)SizeMidpointRounding)),
+ (int)Math.Max(1, Math.Round(sizeMm.Height * XYppmm.Height * SizeScale, (MidpointRounding)SizeMidpointRounding)));
public Size SizeMmToPx(double sizeXmm, double sizeYmm)
- => new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)),
- (int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height, (MidpointRounding)SizeMidpointRounding)));
+ => new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width * SizeScale, (MidpointRounding)SizeMidpointRounding)),
+ (int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height * SizeScale, (MidpointRounding)SizeMidpointRounding)));
public Size SizeMmToPx(float sizeXmm, float sizeYmm)
- => new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)),
- (int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height, (MidpointRounding)SizeMidpointRounding)));
+ => new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width * SizeScale, (MidpointRounding)SizeMidpointRounding)),
+ (int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height * SizeScale, (MidpointRounding)SizeMidpointRounding)));
public int SizeMmToPx(float sizeMm)
- => (int) Math.Max(1, Math.Round(sizeMm * XYppmm.Max(), (MidpointRounding)SizeMidpointRounding));
+ => (int) Math.Max(1, Math.Round(sizeMm * XYppmm.Max() * SizeScale, (MidpointRounding)SizeMidpointRounding));
public int SizeMmToPx(double sizeMm)
- => (int)Math.Max(1, Math.Round(sizeMm * XYppmm.Max(), (MidpointRounding)SizeMidpointRounding));
+ => (int)Math.Max(1, Math.Round(sizeMm * XYppmm.Max() * SizeScale, (MidpointRounding)SizeMidpointRounding));
public int SizeMmToPxOverride(float sizeMm, float ppmm)
- => (int)Math.Max(1, Math.Round(sizeMm * ppmm, (MidpointRounding)SizeMidpointRounding));
+ => (int)Math.Max(1, Math.Round(sizeMm * ppmm * SizeScale, (MidpointRounding)SizeMidpointRounding));
public int SizeMmToPxOverride(double sizeMm, float ppmm)
- => (int)Math.Max(1, Math.Round(sizeMm * ppmm, (MidpointRounding)SizeMidpointRounding));
+ => (int)Math.Max(1, Math.Round(sizeMm * ppmm * SizeScale, (MidpointRounding)SizeMidpointRounding));
}
diff --git a/UVtools.Core/Helpers.cs b/UVtools.Core/Helpers.cs
index 7807a12..3ac95cb 100644
--- a/UVtools.Core/Helpers.cs
+++ b/UVtools.Core/Helpers.cs
@@ -54,11 +54,11 @@ public static class Helpers
return true;
case "boolean":
if(char.IsDigit(value[0])) attribute.SetValue(obj, !value.Equals("0"));
- else attribute.SetValue(obj, value.Equals("True", StringComparison.InvariantCultureIgnoreCase));
+ else attribute.SetValue(obj, value.Equals("True", StringComparison.OrdinalIgnoreCase));
return true;
case "byte":
- if (value.Equals("true", StringComparison.InvariantCultureIgnoreCase)) attribute.SetValue(obj, (byte)1);
- else if (value.Equals("false", StringComparison.InvariantCultureIgnoreCase)) attribute.SetValue(obj, byte.MinValue);
+ if (value.Equals("true", StringComparison.OrdinalIgnoreCase)) attribute.SetValue(obj, (byte)1);
+ else if (value.Equals("false", StringComparison.OrdinalIgnoreCase)) attribute.SetValue(obj, byte.MinValue);
else attribute.SetValue(obj, value.Convert<byte>());
return true;
case "sbyte":
diff --git a/UVtools.Core/Objects/ExposureItem.cs b/UVtools.Core/Objects/ExposureItem.cs
index 8c94b17..90a16ad 100644
--- a/UVtools.Core/Objects/ExposureItem.cs
+++ b/UVtools.Core/Objects/ExposureItem.cs
@@ -1,4 +1,11 @@
-using System;
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+using System;
using UVtools.Core.Layers;
namespace UVtools.Core.Objects;
diff --git a/UVtools.Core/Objects/GenericFileRepresentation.cs b/UVtools.Core/Objects/GenericFileRepresentation.cs
new file mode 100644
index 0000000..f4b7c2d
--- /dev/null
+++ b/UVtools.Core/Objects/GenericFileRepresentation.cs
@@ -0,0 +1,137 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+using System;
+using System.IO;
+
+namespace UVtools.Core.Objects;
+
+[Serializable]
+public class GenericFileRepresentation : BindableBase, ICloneable,
+ IComparable<GenericFileRepresentation>, IEquatable<GenericFileRepresentation>,
+ IComparable<string>, IEquatable<string>
+{
+ #region Members
+ private string _filePath = null!;
+ #endregion
+
+ #region Properties
+ /// <summary>
+ /// Gets or sets the file path
+ /// </summary>
+ public string FilePath
+ {
+ get => _filePath;
+ set
+ {
+ if (RaiseAndSetIfChanged(ref _filePath, value)) return;
+ RaisePropertyChanged(nameof(FileName));
+ RaisePropertyChanged(nameof(Exists));
+ RaisePropertyChanged(nameof(FileInfo));
+ }
+ }
+
+ /// <summary>
+ /// Gets the file name with extension
+ /// </summary>
+ public string FileName => Path.GetFileName(_filePath);
+
+ /// <summary>
+ /// Gets the file name without extension
+ /// </summary>
+ public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(_filePath);
+
+ /// <summary>
+ /// Gets the file extension. The returned value includes the period (".")
+ /// </summary>
+ public string FileExtension => Path.GetExtension(_filePath);
+
+ /// <summary>
+ /// Gets if the file exists
+ /// </summary>
+ public bool Exists => File.Exists(FilePath);
+
+ /// <summary>
+ /// Gets an <see cref="FileInfo"/> instance on the <see cref="FilePath"/>
+ /// </summary>
+ public FileInfo FileInfo => new(_filePath);
+ #endregion
+
+ #region Constructor
+ public GenericFileRepresentation()
+ { }
+
+ public GenericFileRepresentation(string filePath)
+ {
+ _filePath = filePath;
+ }
+ #endregion
+
+ #region Methods
+
+ /// <summary>
+ /// Checks if the <see cref="FilePath"/> ends with <paramref name="extension"/>
+ /// </summary>
+ /// <param name="extension">Extension name</param>
+ /// <returns>True if found, otherwise false</returns>
+ public bool IsExtension(string extension)
+ {
+ if (string.IsNullOrWhiteSpace(extension)) return false;
+ if (extension[0] != '.') extension = $".{extension}";
+ return _filePath.EndsWith(extension, StringComparison.OrdinalIgnoreCase);
+ }
+
+ #endregion
+
+ #region Overrides
+
+ public override string ToString()
+ {
+ return FileName;
+ }
+
+ public object Clone()
+ {
+ return MemberwiseClone();
+ }
+
+ public int CompareTo(GenericFileRepresentation? other)
+ {
+ if (ReferenceEquals(this, other)) return 0;
+ if (ReferenceEquals(null, other)) return 1;
+ return string.Compare(FileName, other.FileName, StringComparison.Ordinal);
+ }
+
+ public int CompareTo(string? other)
+ {
+ return FileName.CompareTo(other);
+ }
+
+ public bool Equals(GenericFileRepresentation? other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return _filePath == other._filePath;
+ }
+
+
+ public bool Equals(string? other)
+ {
+ return _filePath.Equals(other);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return ReferenceEquals(this, obj) || obj is GenericFileRepresentation other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return _filePath.GetHashCode();
+ }
+ #endregion
+} \ No newline at end of file
diff --git a/UVtools.Core/Objects/RangeObservableCollection.cs b/UVtools.Core/Objects/RangeObservableCollection.cs
index 6e76c3a..66a5b2f 100644
--- a/UVtools.Core/Objects/RangeObservableCollection.cs
+++ b/UVtools.Core/Objects/RangeObservableCollection.cs
@@ -522,7 +522,14 @@ public class RangeObservableCollection<T> : ObservableCollection<T>
}
var list = this.ToList();
- list.Sort(comparison!);
+ if (comparison is null)
+ {
+ list.Sort();
+ }
+ else
+ {
+ list.Sort(comparison);
+ }
ReplaceCollection(list);
}
diff --git a/UVtools.Core/Operations/OperationLayerExportImage.cs b/UVtools.Core/Operations/OperationLayerExportImage.cs
index 0c9cbea..0579ac6 100644
--- a/UVtools.Core/Operations/OperationLayerExportImage.cs
+++ b/UVtools.Core/Operations/OperationLayerExportImage.cs
@@ -209,10 +209,8 @@ public sealed class OperationLayerExportImage : Operation
else
{
// SVG
-
- CvInvoke.Threshold(matRoi, matRoi, 127, byte.MaxValue, ThresholdType.Binary); // Remove AA
- using var contours = matRoi.FindContours(out var hierarchy, RetrType.Tree);
+ var paths = matRoi.GetSvgPath(ChainApproxMethod.ChainApproxTc89Kcos);
using TextWriter tw = new StreamWriter(fileFullPath);
tw.WriteLine("<!--");
@@ -244,16 +242,13 @@ public sealed class OperationLayerExportImage : Operation
tw.WriteLine($"\t<g id=\"layer{layerIndex}\">");
tw.WriteLine($"\t<rect class=\"background\" width=\"{mat.Width}\" height=\"{mat.Height}\"/>");
-
- //
- //hierarchy[i][0]: the index of the next contour of the same level
- //hierarchy[i][1]: the index of the previous contour of the same level
- //hierarchy[i][2]: the index of the first child
- //hierarchy[i][3]: the index of the parent
- //
+ foreach (var path in paths)
+ {
+ tw.WriteLine($"\t<path d=\"{path}\"/>");
+ }
- bool firstTime = true;
+ /*bool firstTime = true;
for (int i = 0; i < contours.Size; i++)
{
if (hierarchy[i, EmguContour.HierarchyParent] == -1) // Top hierarchy
@@ -282,32 +277,8 @@ public sealed class OperationLayerExportImage : Operation
tw.Write(" Z");
}
- if(!firstTime) tw.WriteLine("\"/>");
-
- // Old method!
- /*for (int i = 0; i < contours.Size; i++)
- {
- if (contours[i].Size == 0) continue;
-
- var style = "white";
+ if(!firstTime) tw.WriteLine("\"/>");*/
- int parentIndex = i;
- int count = 0;
- while ((parentIndex = (int)hierarchyJagged.GetValue(0, parentIndex, 3)) != -1)
- {
- count++;
- }
-
- if (count % 2 != 0)
- style = "black";
-
- tw.Write($"\t<path class=\"{style}\" d=\"M{contours[i][0].X} {contours[i][0].Y}");
- for (int x = 1; x < contours[i].Size; x++)
- {
- tw.Write($",L{contours[i][x].X} {contours[i][x].Y}");
- }
- tw.WriteLine("Z\"/>");
- }*/
tw.WriteLine("\t</g>");
tw.WriteLine("</svg>");
diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs
index be2655b..d7a75ba 100644
--- a/UVtools.Core/Operations/OperationLayerImport.cs
+++ b/UVtools.Core/Operations/OperationLayerImport.cs
@@ -12,7 +12,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
-using System.IO;
+using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UVtools.Core.Extensions;
@@ -57,7 +57,7 @@ public sealed class OperationLayerImport : Operation
private bool _extendBeyondLayerCount = true;
private bool _discardUnmodifiedLayers;
private ushort _stackMargin = 50;
- private RangeObservableCollection<ValueDescription> _files = new();
+ private RangeObservableCollection<GenericFileRepresentation> _files = new();
#endregion
#region Overrides
@@ -127,9 +127,9 @@ public sealed class OperationLayerImport : Operation
{
foreach (var keyValue in _files)
{
- if (!File.Exists(keyValue.ValueAsString))
+ if (!keyValue.Exists)
{
- sb.AppendLine($"The file '{keyValue.ValueAsString}' does not exists.");
+ sb.AppendLine($"The file '{keyValue.FilePath}' does not exists.");
}
}
}
@@ -140,6 +140,15 @@ public sealed class OperationLayerImport : Operation
#region Properties
+ public static string[] ValidImageExtensions => new[]
+ {
+ "png",
+ "bmp",
+ "jpeg",
+ "jpg",
+ "gif",
+ };
+
public ImportTypes ImportType
{
get => _importType;
@@ -182,7 +191,7 @@ public sealed class OperationLayerImport : Operation
set => RaiseAndSetIfChanged(ref _stackMargin, value);
}
- public RangeObservableCollection<ValueDescription> Files
+ public RangeObservableCollection<GenericFileRepresentation> Files
{
get => _files;
set => RaiseAndSetIfChanged(ref _files, value);
@@ -204,12 +213,12 @@ public sealed class OperationLayerImport : Operation
public void AddFile(string file)
{
- _files.Add(new ValueDescription(file, Path.GetFileNameWithoutExtension(file)));
+ _files.Add(new GenericFileRepresentation(file));
}
public void Sort()
{
- _files.Sort((file1, file2) => string.Compare(Path.GetFileNameWithoutExtension(file1.ValueAsString), Path.GetFileNameWithoutExtension(file2.ValueAsString), StringComparison.Ordinal));
+ _files.Sort();
}
@@ -246,12 +255,8 @@ public sealed class OperationLayerImport : Operation
// Order raw images
for (int i = 0; i < Count; i++)
{
- if (!_files[i].ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) &&
- !_files[i].ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) &&
- !_files[i].ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) &&
- !_files[i].ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) &&
- !_files[i].ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) continue;
- keyImage.Add(new KeyValuePair<uint, string>((uint)keyImage.Count, _files[i].ValueAsString));
+ if(!ValidImageExtensions.Any(extension => _files[i].IsExtension(extension))) continue;
+ keyImage.Add(new KeyValuePair<uint, string>((uint)keyImage.Count, _files[i].FilePath));
}
// Create virtual file format with images
@@ -273,18 +278,14 @@ public sealed class OperationLayerImport : Operation
fileFormats.Add(format);
}
- // Order remaining possible file formats
+ // Order remaining possible file formats that are not images
for (int i = 0; i < Count; i++)
{
- if (_files[i].ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
- _files[i].ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) ||
- _files[i].ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) ||
- _files[i].ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
- _files[i].ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) continue;
+ if (ValidImageExtensions.Any(extension => _files[i].IsExtension(extension))) continue;
- var fileFormat = FileFormat.FindByExtensionOrFilePath(_files[i].ValueAsString, true);
+ var fileFormat = FileFormat.FindByExtensionOrFilePath(_files[i].FilePath, true);
if (fileFormat is null) continue;
- fileFormat.FileFullPath = _files[i].ValueAsString;
+ fileFormat.FileFullPath = _files[i].FilePath;
fileFormats.Add(fileFormat);
}
diff --git a/UVtools.Core/Operations/OperationPCBExposure.cs b/UVtools.Core/Operations/OperationPCBExposure.cs
index 017a1aa..7b0fea9 100644
--- a/UVtools.Core/Operations/OperationPCBExposure.cs
+++ b/UVtools.Core/Operations/OperationPCBExposure.cs
@@ -26,6 +26,40 @@ namespace UVtools.Core.Operations;
[Serializable]
public class OperationPCBExposure : Operation
{
+ #region Sub Classes
+
+ public sealed class PCBExposureFile : GenericFileRepresentation
+ {
+ private bool _invertPolarity;
+ private double _sizeScale = 1;
+
+ /// <summary>
+ /// Gets or sets to invert the polarity when drawing
+ /// </summary>
+ public bool InvertPolarity
+ {
+ get => _invertPolarity;
+ set => RaiseAndSetIfChanged(ref _invertPolarity, value);
+ }
+
+ /// <summary>
+ /// Gets or sets the scale to apply to each shape drawing size.
+ /// Positions and vectors aren't affected by this.
+ /// </summary>
+ public double SizeScale
+ {
+ get => _sizeScale;
+ set => RaiseAndSetIfChanged(ref _sizeScale, Math.Max(0.001, Math.Round(value, 4)));
+ }
+
+ public PCBExposureFile() { }
+
+ public PCBExposureFile(string filePath, bool invertPolarity = false) : base(filePath)
+ {
+ _invertPolarity = invertPolarity;
+ }
+ }
+ #endregion
#region Static
public static string[] ValidExtensions => new[]
@@ -43,7 +77,7 @@ public class OperationPCBExposure : Operation
#endregion
#region Members
- private RangeObservableCollection<ValueDescription> _files = new();
+ private RangeObservableCollection<PCBExposureFile> _files = new();
private bool _mergeFiles;
private decimal _layerHeight;
@@ -91,9 +125,9 @@ public class OperationPCBExposure : Operation
}
else
{
- foreach (var valueDescription in _files)
+ foreach (var file in _files)
{
- if(!File.Exists(valueDescription.ValueAsString)) sb.AppendLine($"The file {valueDescription} does not exists");
+ if(!file.Exists) sb.AppendLine($"The file {file} does not exists");
}
}
@@ -122,7 +156,7 @@ public class OperationPCBExposure : Operation
#endregion
#region Properties
- public RangeObservableCollection<ValueDescription> Files
+ public RangeObservableCollection<PCBExposureFile> Files
{
get => _files;
set => RaiseAndSetIfChanged(ref _files, value);
@@ -200,13 +234,13 @@ public class OperationPCBExposure : Operation
public void AddFilesFromZip(string zipFile)
{
- if (!File.Exists(zipFile)) return;
+ if (!File.Exists(zipFile) || !zipFile.EndsWith(".zip")) return;
using var zip = ZipFile.Open(zipFile, ZipArchiveMode.Read);
var tmpPath = PathExtensions.GetTemporaryDirectory($"{About.Software}.");
foreach (var entry in zip.Entries)
{
- if(!ValidExtensions.Any(extension => entry.Name.EndsWith($".{extension}", StringComparison.InvariantCultureIgnoreCase))) continue;
+ if(!ValidExtensions.Any(extension => entry.Name.EndsWith($".{extension}", StringComparison.OrdinalIgnoreCase))) continue;
var filePath = entry.ImprovedExtractToFile(tmpPath, false);
if (!string.IsNullOrEmpty(filePath))
@@ -217,15 +251,21 @@ public class OperationPCBExposure : Operation
}
- public void AddFile(string file)
+ public void AddFile(string filePath, bool handleZipFiles = true)
{
- if (!File.Exists(file)) return;
- var vd = new ValueDescription(file, Path.GetFileNameWithoutExtension(file));
- if (_files.Contains(vd)) return;
- _files.Add(vd);
+ if (!File.Exists(filePath)) return;
+ if (filePath.EndsWith(".zip"))
+ {
+ if(handleZipFiles) AddFilesFromZip(filePath);
+ return;
+ }
+ if(!ValidExtensions.Any(extension => filePath.EndsWith($".{extension}", StringComparison.OrdinalIgnoreCase))) return;
+ var file = new PCBExposureFile(filePath);
+ if (_files.Contains(file)) return;
+ _files.Add(file);
}
- public void AddFiles(string[] files)
+ public void AddFiles(string[] files, bool handleZipFiles = true)
{
foreach (var file in files)
{
@@ -233,15 +273,19 @@ public class OperationPCBExposure : Operation
}
}
- public void Sort()
+ public void Sort() => _files.Sort();
+
+ public Mat GetMat(PCBExposureFile file)
{
- _files.Sort((file1, file2) => string.Compare(Path.GetFileNameWithoutExtension(file1.ValueAsString), Path.GetFileNameWithoutExtension(file2.ValueAsString), StringComparison.Ordinal));
+ var mat = SlicerFile.CreateMat();
+ DrawMat(file, mat);
+ return mat;
}
- public Mat GetMat(string file)
+ public void DrawMat(PCBExposureFile file, Mat mat)
{
- var mat = SlicerFile.CreateMat();
- if (!File.Exists(file)) return mat;
+ if (!file.Exists) return;
+
GerberDocument.ParseAndDraw(file, mat, SlicerFile.Ppmm, _sizeMidpointRounding, _enableAntiAliasing);
//var boundingRectangle = CvInvoke.BoundingRectangle(mat);
@@ -256,17 +300,18 @@ public class OperationPCBExposure : Operation
CvInvoke.Flip(mat, mat, (FlipType)flip);
}
- return mat;
+ return;
}
protected override bool ExecuteInternally(OperationProgress progress)
{
+ if (_files.Count == 0) return false;
var layers = new List<Layer>();
- Mat? mergeMat = null;
+ using var mergeMat = SlicerFile.CreateMat();
progress.ItemCount = FileCount;
- foreach (var file in _files)
+ for (var i = 0; i < _files.Count; i++)
{
- using var mat = GetMat(file.ValueAsString);
+ /*using var mat = GetMat(file);
if (mergeMat is null)
{
@@ -275,17 +320,39 @@ public class OperationPCBExposure : Operation
else
{
CvInvoke.Max(mergeMat, mat, mergeMat);
- }
+ }*/
+
+ DrawMat(_files[i], mergeMat);
- if (_mergeFiles) continue;
- layers.Add(new Layer(mat, SlicerFile));
+ if (!_mergeFiles)
+ {
+ if (i == 0)
+ {
+ if (CvInvoke.CountNonZero(mergeMat) > 0)
+ {
+ layers.Add(new Layer(mergeMat, SlicerFile));
+ }
+ }
+ else
+ {
+ using var mat = SlicerFile.CreateMat();
+ DrawMat(_files[i], mat);
+ if (CvInvoke.CountNonZero(mat) > 0)
+ {
+ layers.Add(new Layer(mat, SlicerFile));
+ }
+ }
+ }
progress++;
}
- if (_mergeFiles && mergeMat is not null)
+ if (_mergeFiles)
{
- layers.Add(new Layer(mergeMat, SlicerFile));
+ if (CvInvoke.CountNonZero(mergeMat) > 0)
+ {
+ layers.Add(new Layer(mergeMat, SlicerFile));
+ }
}
SlicerFile.SuppressRebuildPropertiesWork(() =>
@@ -333,21 +400,16 @@ public class OperationPCBExposure : Operation
op.Execute(progress);
}
-
- if (mergeMat is not null)
+ using var croppedMat = mergeMat.CropByBounds(20);
+ /*if (_mirror)
{
- using var croppedMat = mergeMat.CropByBounds(20);
- /*if (_mirror)
- {
- var flip = SlicerFile.DisplayMirror;
- if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
- CvInvoke.Flip(croppedMat, croppedMat, (FlipType)flip);
- }*/
- using var bgrMat = new Mat();
- CvInvoke.CvtColor(croppedMat, bgrMat, ColorConversion.Gray2Bgr);
- SlicerFile.SetThumbnails(bgrMat);
- mergeMat.Dispose();
- }
+ var flip = SlicerFile.DisplayMirror;
+ if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
+ CvInvoke.Flip(croppedMat, croppedMat, (FlipType)flip);
+ }*/
+ using var bgrMat = new Mat();
+ CvInvoke.CvtColor(croppedMat, bgrMat, ColorConversion.Gray2Bgr);
+ SlicerFile.SetThumbnails(bgrMat);
return !progress.Token.IsCancellationRequested;
}
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 350bf5a..aac6bf6 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
- <Version>3.8.0</Version>
+ <Version>3.8.1</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
diff --git a/UVtools.Core/Voxel/Voxelizer.cs b/UVtools.Core/Voxel/Voxelizer.cs
index 579b533..40bbbf4 100644
--- a/UVtools.Core/Voxel/Voxelizer.cs
+++ b/UVtools.Core/Voxel/Voxelizer.cs
@@ -108,12 +108,12 @@ public class Voxelizer
return foundFaces;
}
- public static Mat BuildVoxelLayerImage(Mat curLayer, Mat? layerAbove = null, Mat? layerBelow = null)
+ public static Mat BuildVoxelLayerImage(Mat curLayer, Mat? layerAbove = null, Mat? layerBelow = null, ChainApproxMethod contourCompressionMethod = ChainApproxMethod.ChainApproxSimple)
{
/* The goal of the VoxelLayerImage is to reduce as much as possible, the number of pixels we need to do 6 direction neighbor checking on */
/* the outer contours of the current layer should always be checked, they by definition should have an exposed face */
- using var contours = curLayer.FindContours(RetrType.Tree);
+ using var contours = curLayer.FindContours(RetrType.Tree, contourCompressionMethod);
var onlyContours = curLayer.NewBlank();
CvInvoke.DrawContours(onlyContours, contours, -1, EmguExtensions.WhiteColor, 1);
diff --git a/UVtools.Installer/Code/HeatGeneratedFileList.wxs b/UVtools.Installer/Code/HeatGeneratedFileList.wxs
index 6eb3907..f0dda83 100644
--- a/UVtools.Installer/Code/HeatGeneratedFileList.wxs
+++ b/UVtools.Installer/Code/HeatGeneratedFileList.wxs
@@ -152,9 +152,6 @@
<Component Id="cmp24EC37127AAA575AAD606EA913282F5A" Guid="*">
<File Id="filEFC8009F9BC797FA42370209DCEA0584" KeyPath="yes" Source="$(var.HarvestPath)\Avalonia.DesktopRuntime.dll" />
</Component>
- <Component Id="cmp27A31D9C3C1AA272F0D0D90DEE7CF252" Guid="*">
- <File Id="fil9F4E461681E037FB6442FBDE1321EB4C" KeyPath="yes" Source="$(var.HarvestPath)\Avalonia.Diagnostics.dll" />
- </Component>
<Component Id="cmp753A550BCADD7E2DE4FB25F55CBFA512" Guid="*">
<File Id="filD453DD71599C82513C77A8EACD721801" KeyPath="yes" Source="$(var.HarvestPath)\Avalonia.Dialogs.dll" />
</Component>
@@ -1634,7 +1631,6 @@
<ComponentRef Id="cmp93743B50FD3FC8AB2EA47897093C2467" />
<ComponentRef Id="cmp46C92BABDACBE495A5CED58407D280D8" />
<ComponentRef Id="cmp24EC37127AAA575AAD606EA913282F5A" />
- <ComponentRef Id="cmp27A31D9C3C1AA272F0D0D90DEE7CF252" />
<ComponentRef Id="cmp753A550BCADD7E2DE4FB25F55CBFA512" />
<ComponentRef Id="cmp1F6A512565E2FBF645A3230C236BE532" />
<ComponentRef Id="cmpDF229E3F98CA2E37326E841BEB412F73" />
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml
index 70381c3..7624b96 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml
@@ -39,7 +39,7 @@
Value="{Binding Operation.BottomLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="4"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat={}{0:F3}mm}"/>
@@ -57,7 +57,7 @@
<TextBlock Grid.Row="2" Grid.Column="10"
IsEnabled="{Binding !Operation.SyncLayers}"
VerticalAlignment="Center"
- Text="{Binding Operation.NormalHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.NormalHeight, StringFormat={}{0:F3}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
@@ -172,7 +172,7 @@
</Grid>
<Expander
- Header="{Binding Operation.ErodeObjects, StringFormat=Morph - Erode [\{0\} objects]}"
+ Header="{Binding Operation.ErodeObjects, StringFormat=Morph - Erode [{0} objects]}"
IsExpanded="True">
<StackPanel>
@@ -240,7 +240,7 @@
<Expander
- Header="{Binding Operation.DimmingObjects, StringFormat=Wall dimming [\{0\} objects]}"
+ Header="{Binding Operation.DimmingObjects, StringFormat=Wall dimming [{0} objects]}"
IsExpanded="True">
<StackPanel Orientation="Vertical" Spacing="10">
@@ -325,11 +325,11 @@
<TextBlock Grid.Row="6" Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
- Text="{Binding Operation.DimmingStartBrightnessPercent, StringFormat=(\{0\}%)}"/>
+ Text="{Binding Operation.DimmingStartBrightnessPercent, StringFormat=({0}%)}"/>
<TextBlock Grid.Row="6" Grid.Column="6"
VerticalAlignment="Center"
HorizontalAlignment="Center"
- Text="{Binding Operation.DimmingEndBrightnessPercent, StringFormat=(\{0\}%)}"/>
+ Text="{Binding Operation.DimmingEndBrightnessPercent, StringFormat=({0}%)}"/>
</Grid>
<Border Margin="0,10,0,0">
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml
index b4453dd..a212d69 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml
@@ -41,7 +41,7 @@
<TextBlock
FontWeight="Bold"
- Text="{Binding Operation.TotalHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.TotalHeight, StringFormat={}{0:F3}mm}"/>
</StackPanel>
@@ -57,7 +57,7 @@
Value="{Binding Operation.BottomLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="4"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat={}{0:F3}mm}"/>
<TextBlock Grid.Row="2" Grid.Column="6"
VerticalAlignment="Center"
@@ -71,7 +71,7 @@
Value="{Binding Operation.InterfaceLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="10"
VerticalAlignment="Center"
- Text="{Binding Operation.InterfaceHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.InterfaceHeight, StringFormat={}{0:F3}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
@@ -84,7 +84,7 @@
Value="{Binding Operation.NormalLayers}"/>
<TextBlock Grid.Row="4" Grid.Column="4"
VerticalAlignment="Center"
- Text="{Binding Operation.NormalHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.NormalHeight, StringFormat={}{0:F3}mm}"/>
<TextBlock Grid.Row="6" Grid.Column="0"
VerticalAlignment="Center"
@@ -196,11 +196,11 @@
<TextBlock Grid.Row="2" Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
- Text="{Binding Operation.StartBrightnessPercent, StringFormat=(\{0\}%)}"/>
+ Text="{Binding Operation.StartBrightnessPercent, StringFormat=({0}%)}"/>
<TextBlock Grid.Row="2" Grid.Column="6"
VerticalAlignment="Center"
HorizontalAlignment="Center"
- Text="{Binding Operation.EndBrightnessPercent, StringFormat=(\{0\}%)}"/>
+ Text="{Binding Operation.EndBrightnessPercent, StringFormat=({0}%)}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
@@ -269,7 +269,7 @@
<TextBlock
VerticalAlignment="Center"
IsEnabled="{Binding Operation.EnableLineDivisions}"
- Text="{Binding Operation.LineDivisionBrightnessPercent, StringFormat=(\{0\}%)}"/>
+ Text="{Binding Operation.LineDivisionBrightnessPercent, StringFormat=({0}%)}"/>
</StackPanel>
<TextBlock Grid.Row="10" Grid.Column="0"
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml
index 68718fc..9f8af8c 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateLiftHeightControl.axaml
@@ -48,7 +48,7 @@
Value="{Binding Operation.BottomLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="4"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat={}{0:F3}mm}"/>
@@ -63,7 +63,7 @@
Value="{Binding Operation.NormalLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="10"
VerticalAlignment="Center"
- Text="{Binding Operation.NormalHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.NormalHeight, StringFormat={}{0:F3}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml
index 1cd91e8..55f8fb9 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml
@@ -147,7 +147,7 @@
<TextBlock
FontWeight="Bold"
- Text="{Binding Operation.TotalHeight, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.TotalHeight, StringFormat={}{0:F3}mm}"/>
</StackPanel>
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml
index 4eda850..2620dca 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml
@@ -113,7 +113,7 @@
<TextBlock
FontWeight="Bold"
- Text="{Binding Operation.RealZSize, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.RealZSize, StringFormat={}{0:F3}mm}"/>
</StackPanel>
@@ -242,13 +242,13 @@
VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="2"
- Text="{Binding Operation.FemaleDiameterRealXSize, StringFormat=\{0:F2\}mm}"
+ Text="{Binding Operation.FemaleDiameterRealXSize, StringFormat={}{0:F2}mm}"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="8"
- Text="{Binding Operation.FemaleHoleDiameterRealXSize, StringFormat=\{0:F2\}mm}"
+ Text="{Binding Operation.FemaleHoleDiameterRealXSize, StringFormat={}{0:F2}mm}"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml
index 5a974d6..9bedecc 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml
@@ -137,7 +137,7 @@
<TextBlock
FontWeight="Bold"
- Text="{Binding Operation.RealZSize, StringFormat=\{0:F3\}mm}"/>
+ Text="{Binding Operation.RealZSize, StringFormat={}{0:F3}mm}"/>
</StackPanel>
@@ -370,7 +370,7 @@
<TextBlock Grid.Row="0" Grid.Column="2" FontWeight="Bold"
ToolTip.Tip="The calculated expected size for the part based on your input">
<TextBlock.Text>
- <MultiBinding StringFormat="\{0\}mm x \{1\}mm x \{2\}mm">
+ <MultiBinding StringFormat="{}{0}mm x {1}mm x {2}mm">
<Binding Path="Operation.RealXSize"/>
<Binding Path="Operation.RealYSize"/>
<Binding Path="Operation.RealZSize"/>
@@ -389,7 +389,7 @@
IsVisible="{Binding Operation.HollowModel}"
ToolTip.Tip="The calculated expected wall thickness size for the part based on your input">
<TextBlock.Text>
- <MultiBinding StringFormat="\{0\}mm x \{1\}mm">
+ <MultiBinding StringFormat="{}{0}mm x {1}mm">
<Binding Path="Operation.WallThicknessRealXSize"/>
<Binding Path="Operation.WallThicknessRealYSize"/>
</MultiBinding>
@@ -405,7 +405,7 @@
<TextBlock Grid.Row="4" Grid.Column="2" FontWeight="Bold"
ToolTip.Tip="The resultant scale factor you should resize your model with">
<TextBlock.Text>
- <MultiBinding StringFormat="\{0\}% x \{1\}% x \{2\}%">
+ <MultiBinding StringFormat="{}{0}% x {1}% x {2}%">
<Binding Path="Operation.ScaleXFactor"/>
<Binding Path="Operation.ScaleYFactor"/>
<Binding Path="Operation.ScaleZFactor"/>
diff --git a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml
index df065eb..60e7f9e 100644
--- a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml
@@ -35,7 +35,7 @@
<TextBlock Grid.Row="0" Grid.Column="6"
VerticalAlignment="Center"
- Text="{Binding Operation.CacheObjectCount, StringFormat=±\{0\}}"/>
+ Text="{Binding Operation.CacheObjectCount, StringFormat=±{0}}"/>
<TextBlock Grid.Row="2" Grid.Column="0"
VerticalAlignment="Center"
diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml
index 513f40c..b15be07 100644
--- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml
@@ -116,7 +116,7 @@
<TextBlock
VerticalAlignment="Center"
- Text="{Binding Operation.Files.Count, StringFormat=Files: \{0\}}"/>
+ Text="{Binding Operation.Files.Count, StringFormat=Files: {0}}"/>
</StackPanel>
</Grid>
</Border>
diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs
index 4b213a2..6a7e135 100644
--- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs
@@ -16,7 +16,7 @@ namespace UVtools.WPF.Controls.Tools;
public class ToolLayerImportControl : ToolControl
{
private bool _isAutoSortLayersByFileNameChecked;
- private ValueDescription _selectedFile;
+ private GenericFileRepresentation _selectedFile;
private Bitmap _previewImage;
private ListBox FilesListBox;
@@ -56,7 +56,7 @@ public class ToolLayerImportControl : ToolControl
}
- public ValueDescription SelectedFile
+ public GenericFileRepresentation SelectedFile
{
get => _selectedFile;
set
@@ -67,12 +67,8 @@ public class ToolLayerImportControl : ToolControl
PreviewImage = null;
return;
}
- if (!_selectedFile.ValueAsString.EndsWith(".png", StringComparison.OrdinalIgnoreCase) &&
- !_selectedFile.ValueAsString.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase) &&
- !_selectedFile.ValueAsString.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) &&
- !_selectedFile.ValueAsString.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) &&
- !_selectedFile.ValueAsString.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) return;
- PreviewImage = new Bitmap(_selectedFile.ValueAsString);
+ if (!OperationLayerImport.ValidImageExtensions.Any(extension => _selectedFile.IsExtension(extension))) return;
+ PreviewImage = new Bitmap(_selectedFile.FilePath);
}
}
@@ -92,8 +88,8 @@ public class ToolLayerImportControl : ToolControl
FilesListBox = this.Find<ListBox>("FilesListBox");
FilesListBox.DoubleTapped += (sender, args) =>
{
- if (FilesListBox.SelectedItem is not ValueDescription file) return;
- SystemAware.StartProcess(file.ValueAsString);
+ if (FilesListBox.SelectedItem is not GenericFileRepresentation file) return;
+ SystemAware.StartProcess(file.FilePath);
};
FilesListBox.KeyUp += (sender, e) =>
{
@@ -195,7 +191,7 @@ public class ToolLayerImportControl : ToolControl
public void RemoveFiles()
{
- Operation.Files.RemoveRange(FilesListBox.SelectedItems.OfType<ValueDescription>());
+ Operation.Files.RemoveRange(FilesListBox.SelectedItems.OfType<GenericFileRepresentation>());
}
public void ClearFiles()
diff --git a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml
index f81b4d1..35553f0 100644
--- a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml
@@ -9,7 +9,8 @@
<Grid ColumnDefinitions="Auto,10,350">
<StackPanel Spacing="10">
- <Border BorderBrush="Gray" BorderThickness="1" Padding="5">
+ <Border BorderBrush="Gray" BorderThickness="1" Padding="5"
+ DragDrop.AllowDrop="True">
<Grid>
<StackPanel Spacing="5" Orientation="Horizontal">
<controls:ButtonWithIcon Padding="5"
@@ -25,7 +26,7 @@
Icon="fa-solid fa-file-zipper"/>
<controls:ButtonWithIcon Padding="5"
- IsEnabled="{Binding #FilesListBox.SelectedItems.Count}"
+ IsEnabled="{Binding SelectedFile, Converter={x:Static ObjectConverters.IsNotNull}}"
Command="{Binding RemoveFiles}"
Text="Remove"
Spacing="5"
@@ -34,14 +35,14 @@
<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"
+ Text="Sort ASC"
Spacing="5"
Icon="fa-solid fa-sort-alpha-up"/>
- -->
+
<controls:ButtonWithIcon
IsEnabled="{Binding Operation.Files.Count}"
Padding="5" Command="{Binding ClearFiles}"
@@ -51,24 +52,56 @@
<TextBlock
VerticalAlignment="Center"
- Text="{Binding Operation.Files.Count, StringFormat=Files: \{0\}}"/>
+ 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,10,Auto"
+ <DataGrid Name="FilesDataGrid"
+ BorderBrush="Gray" BorderThickness="1"
+ CanUserReorderColumns="False"
+ CanUserResizeColumns="False"
+ CanUserSortColumns="True"
+ GridLinesVisibility="Horizontal"
+ SelectionMode="Extended"
+ ClipboardCopyMode="IncludeHeader"
+ MinHeight="150"
+ MaxHeight="400"
+ Width="508"
+ Items="{Binding Operation.Files}"
+ SelectedItem="{Binding SelectedFile}"
+ DragDrop.AllowDrop="True">
+ <DataGrid.Columns>
+ <DataGridTextColumn Header="Filename"
+ Binding="{Binding FileName}"
+ IsReadOnly="True"
+ Width="*" />
+ <DataGridTemplateColumn Header="Size scale"
+ Width="130">
+ <DataTemplate>
+ <NumericUpDown Increment="0.05"
+ Minimum="0.01"
+ Maximum="100"
+ FormatString="F2"
+ Value="{Binding SizeScale}">
+ </NumericUpDown>
+ </DataTemplate>
+ </DataGridTemplateColumn>
+
+ <DataGridCheckBoxColumn Header="Invert"
+ Binding="{Binding InvertPolarity}"
+ Width="Auto" />
+ </DataGrid.Columns>
+
+ </DataGrid>
+
+
+ <Grid RowDefinitions="Auto,10,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"
+ OnContent="Draw all gerber files into same layer"
OffContent="Create one layer per gerber file"/>
<TextBlock Grid.Row="2" Grid.Column="0"
@@ -103,6 +136,9 @@
Items="{Binding Operation.SizeMidpointRounding, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
SelectedItem="{Binding Operation.SizeMidpointRounding, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
+ <TextBlock Grid.Row="8" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Options:"/>
<StackPanel Grid.Row="8" Grid.Column="2"
Orientation="Horizontal" Spacing="20">
diff --git a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs
index ceb8d25..4bbc62f 100644
--- a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs
@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
using System.Linq;
using System.Timers;
using Avalonia.Controls;
+using Avalonia.Input;
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;
@@ -22,10 +21,10 @@ namespace UVtools.WPF.Controls.Tools
public OperationPCBExposure Operation => BaseOperation as OperationPCBExposure;
private readonly Timer _timer;
- private ListBox FilesListBox;
+ private DataGrid FilesDataGrid;
private Bitmap _previewImage;
- private ValueDescription _selectedFile;
+ private OperationPCBExposure.PCBExposureFile _selectedFile;
public Bitmap PreviewImage
{
@@ -33,7 +32,7 @@ namespace UVtools.WPF.Controls.Tools
set => RaiseAndSetIfChanged(ref _previewImage, value);
}
- public ValueDescription SelectedFile
+ public OperationPCBExposure.PCBExposureFile SelectedFile
{
get => _selectedFile;
set
@@ -49,7 +48,12 @@ namespace UVtools.WPF.Controls.Tools
if (!ValidateSpawn()) return;
InitializeComponent();
- FilesListBox = this.Find<ListBox>("FilesListBox");
+ FilesDataGrid = this.Find<DataGrid>("FilesDataGrid");
+
+ AddHandler(DragDrop.DropEvent, (sender, args) =>
+ {
+ Operation.AddFiles(args.Data.GetFileNames()?.ToArray() ?? Array.Empty<string>());
+ });
_timer = new Timer(20)
{
@@ -75,6 +79,21 @@ namespace UVtools.WPF.Controls.Tools
_timer.Stop();
_timer.Start();
};
+
+ Operation.Files.CollectionChanged += (sender, e) =>
+ {
+ if (e.NewItems is null) return;
+ foreach (OperationPCBExposure.PCBExposureFile file in e.NewItems)
+ {
+ file.PropertyChanged += (o, args) =>
+ {
+ if (!ReferenceEquals(_selectedFile, file)) return;
+ _timer.Stop();
+ _timer.Start();
+ };
+ }
+ };
+
_timer.Stop();
_timer.Start();
if(ParentWindow is not null) ParentWindow.ButtonOkEnabled = Operation.FileCount > 0;
@@ -93,8 +112,10 @@ namespace UVtools.WPF.Controls.Tools
return;
}
- if (!OperationPCBExposure.ValidExtensions.Any(extension => _selectedFile.ValueAsString.EndsWith($".{extension}", StringComparison.InvariantCultureIgnoreCase)) || !File.Exists(_selectedFile.ValueAsString)) return;
- using var mat = Operation.GetMat(_selectedFile.ValueAsString);
+ if (!OperationPCBExposure.ValidExtensions.Any(extension => _selectedFile.IsExtension(extension)) || !_selectedFile.Exists) return;
+ var file = (OperationPCBExposure.PCBExposureFile)_selectedFile.Clone();
+ file.InvertPolarity = false;
+ using var mat = Operation.GetMat(file);
using var matCropped = mat.CropByBounds(20);
_previewImage?.Dispose();
PreviewImage = matCropped.ToBitmap();
@@ -143,7 +164,7 @@ namespace UVtools.WPF.Controls.Tools
public void RemoveFiles()
{
- Operation.Files.RemoveRange(FilesListBox.SelectedItems.OfType<ValueDescription>());
+ Operation.Files.RemoveRange(FilesDataGrid.SelectedItems.OfType<OperationPCBExposure.PCBExposureFile>());
}
public void ClearFiles()
diff --git a/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml
index cf81890..d27edee 100644
--- a/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml
@@ -384,7 +384,7 @@
<TextBlock
VerticalAlignment="Center"
- Text="{Binding Operation.PatternGenBrightnessPercent, StringFormat=(\{0\}%)}"/>
+ Text="{Binding Operation.PatternGenBrightnessPercent, StringFormat=({0}%)}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="10">
diff --git a/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml b/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml
index 6128e4f..9d67ef0 100644
--- a/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml
@@ -123,7 +123,7 @@
<TextBlock
VerticalAlignment="Center"
- Text="{Binding Operation.BrightnessPercent, StringFormat=(\{0\}%)}"/>
+ Text="{Binding Operation.BrightnessPercent, StringFormat=({0}%)}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="10">
diff --git a/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml
index eb49faa..dfb8ee3 100644
--- a/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml
@@ -92,7 +92,7 @@ you must manually input the layer index of the last raft where it ends and suppo
<TextBlock Grid.Row="6" Grid.Column="4"
IsVisible="{Binding !Operation.IsDecimate}"
- Text="{Binding Operation.BrightnessPercent, StringFormat=\{0:F2\}%}" VerticalAlignment="Center"/>
+ Text="{Binding Operation.BrightnessPercent, StringFormat={}{0:F2}%}" VerticalAlignment="Center"/>
<TextBlock Grid.Row="8" Grid.Column="0"
Text="Supports margin:" VerticalAlignment="Center">
diff --git a/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml
index 7eeeef3..e4ada00 100644
--- a/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml
@@ -66,7 +66,7 @@
Value="{Binding Operation.Brightness}"/>
<TextBlock
VerticalAlignment="Center"
- Text="{Binding Operation.BrightnessPercent, StringFormat=\{0:F2\}%}"/>
+ Text="{Binding Operation.BrightnessPercent, StringFormat={}{0:F2}%}"/>
</StackPanel>
</Grid>
diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs
index 1ece76d..c258ecd 100644
--- a/UVtools.WPF/MainWindow.LayerPreview.cs
+++ b/UVtools.WPF/MainWindow.LayerPreview.cs
@@ -83,6 +83,7 @@ public partial class MainWindow
private bool _showLayerOutlineContourBoundary;
private bool _showLayerOutlineHollowAreas;
private bool _showLayerOutlineCentroids;
+ private bool _showLayerOutlineTriangulate;
private bool _showLayerOutlineEdgeDetection;
private bool _showLayerOutlineDistanceDetection;
private bool _showLayerOutlineSkeletonize;
@@ -416,6 +417,16 @@ public partial class MainWindow
}
}
+ public bool ShowLayerOutlineTriangulate
+ {
+ get => _showLayerOutlineTriangulate;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _showLayerOutlineTriangulate, value)) return;
+ ShowLayer();
+ }
+ }
+
public bool ShowLayerOutlineEdgeDetection
{
get => _showLayerOutlineEdgeDetection;
@@ -1219,7 +1230,8 @@ public partial class MainWindow
{
int lastParent = -1;
uint reps = 0;
- for (int i = 0; i < LayerCache.LayerContours.Size; i++)
+ var size = LayerCache.LayerContours.Size;
+ for (int i = 0; i < size; i++)
{
var parent = LayerCache.LayerContourHierarchy[i, EmguContour.HierarchyParent];
if (parent == -1)
@@ -1253,6 +1265,61 @@ public partial class MainWindow
}
}
+ if (_showLayerOutlineTriangulate)
+ {
+ var groups = EmguContours.GetPositiveContoursInGroups(LayerCache.LayerContours, LayerCache.LayerContourHierarchy);
+ var lineColor = new MCvScalar(
+ Settings.LayerPreview.TriangulateOutlineColor.B,
+ Settings.LayerPreview.TriangulateOutlineColor.G,
+ Settings.LayerPreview.TriangulateOutlineColor.R);
+ var dotColor = new MCvScalar(
+ byte.MaxValue - Settings.LayerPreview.TriangulateOutlineColor.B,
+ byte.MaxValue - Settings.LayerPreview.TriangulateOutlineColor.G,
+ byte.MaxValue - Settings.LayerPreview.TriangulateOutlineColor.R);
+
+ uint triangleCount = 0;
+
+ foreach (var group in groups)
+ {
+ var size = group.Size;
+ var pointFs = new List<PointF>();
+ for (int i = 0; i < size; i++)
+ {
+ var subSize = group[i].Size;
+ for (int x = 0; x < subSize; x++)
+ {
+ pointFs.Add(group[i][x]);
+ }
+ }
+
+ using var sub = new Subdiv2D(pointFs.ToArray());
+ var triangles = sub.GetDelaunayTriangles();
+ foreach (var triangle in triangles)
+ {
+ var points = new[]
+ {
+ triangle.V0.ToPoint(),
+ triangle.V1.ToPoint(),
+ triangle.V2.ToPoint()
+ };
+
+ CvInvoke.Polylines(LayerCache.ImageBgr, points, true, lineColor, Settings.LayerPreview.TriangulateOutlineLineThickness);
+
+ CvInvoke.Circle(LayerCache.ImageBgr, points[0], 2, dotColor, -1);
+ CvInvoke.Circle(LayerCache.ImageBgr, points[1], 2, dotColor, -1);
+ CvInvoke.Circle(LayerCache.ImageBgr, points[2], 2, dotColor, -1);
+ }
+
+ triangleCount += (uint)triangles.Length;
+ }
+
+ if (triangleCount > 0 && Settings.LayerPreview.TriangulateOutlineShowCount)
+ {
+ CvInvoke.PutText(LayerCache.ImageBgr, $"Triangles: {triangleCount:N0}", new Point(10, 80), FontFace.HersheyDuplex, 3, dotColor, 3);
+ }
+
+ }
+
if (_maskPoints is not null && _maskPoints.Count > 0)
{
using var vec = new VectorOfVectorOfPoint(_maskPoints.ToArray());
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index ae05032..af73fa0 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -1942,6 +1942,9 @@
<CheckBox
IsChecked="{Binding ShowLayerOutlineCentroids}"
Content="Centroids"/>
+ <CheckBox
+ IsChecked="{Binding ShowLayerOutlineTriangulate}"
+ Content="Triangulate"/>
<CheckBox
IsChecked="{Binding ShowLayerOutlineEdgeDetection}"
Content="Edge detection">
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index 08f20c2..f755a47 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -153,10 +153,8 @@ public partial class MainWindow : WindowEx
//ProgressWindow = new ProgressWindow();
return;
}
- else
- {
- DragDrop.SetAllowDrop(this, true);
- }
+
+ DragDrop.SetAllowDrop(this, true);
LastStopWatch = Progress.StopWatch;
ProgressFinish();
diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs
index 433aaed..e812193 100644
--- a/UVtools.WPF/Program.cs
+++ b/UVtools.WPF/Program.cs
@@ -55,9 +55,25 @@ public static class Program
/*using var mat = CvInvoke.Imread(@"D:\layer0.png", ImreadModes.Grayscale);
var contours = mat.FindContours(out var hierarchy, RetrType.Tree, ChainApproxMethod.ChainApproxTc89Kcos);
+ var triangulator = new Incremental();
- using var mesh = new STLMeshFile(@"D:\test.stl", FileMode.Create);
- mesh.BeginWrite();
+ var poly = new Polygon();
+
+ // Generate mesh.
+ var points = new List<Vertex>();
+
+ for (int i = 0; i < contours.Size; i++)
+ for (int x = 0; x < contours[i].Size; x++)
+ {
+ points.Add(new Vertex(contours[i][x].X, contours[i][x].Y));
+ }
+
+ //var mesh = triangulator.Triangulate(points, new Configuration());
+ //var triangles = mesh.Triangles.ToArray();
+
+
+ using var stl = new STLMeshFile(@"D:\test.stl", FileMode.Create);
+ stl.BeginWrite();
var groups = EmguContours.GetPositiveContoursInGroups(contours, hierarchy);
@@ -66,33 +82,55 @@ public static class Program
var z = 0;
for (int i = 0; i < group.Size; i++)
{
- var list = new List<PointF>();
+ var list = new List<Vertex>();
+
for (int x = 0; x < contours[i].Size; x++)
{
- list.Add(contours[i][x]);
+ //list.Add(contours[i][x]);
+ list.Add(new Vertex(contours[i][x].X, contours[i][x].Y));
}
- using var sub = new Subdiv2D(list.ToArray());
+ poly.Add(new Contour(list), i > 0);
+
+ /*using var sub = new Subdiv2D(list.ToArray());
var triangles = sub.GetDelaunayTriangles();
foreach (var triangle2Df in triangles)
{
- mesh.WriteTriangle(
+ stl.WriteTriangle(
new Vector3(triangle2Df.V0.X, triangle2Df.V0.Y, z),
new Vector3(triangle2Df.V1.X, triangle2Df.V1.Y, z),
new Vector3(triangle2Df.V2.X, triangle2Df.V2.Y, z),
new Vector3(triangle2Df.Centeroid.X, triangle2Df.Centeroid.Y, z)
);
- }
+ }*/
- z++;
- }
- break;
+
+ //z++;
+ /* }
+
+
+ //break;
+ }
+
+ var mesh = poly.Triangulate(new ConstraintOptions());
+ for (int i = 0; i < 50; i++)
+ {
+ foreach (var meshTriangle in mesh.Triangles)
+ {
+ stl.WriteTriangle(
+ new Vector3((float)meshTriangle.GetVertex(0).X, (float)meshTriangle.GetVertex(0).Y, i),
+ new Vector3((float)meshTriangle.GetVertex(1).X, (float)meshTriangle.GetVertex(1).Y, i),
+ new Vector3((float)meshTriangle.GetVertex(2).X, (float)meshTriangle.GetVertex(2).Y, i),
+ new Vector3(1f, 1f, i)
+ );
+ }
}
- mesh.EndWrite();
+
+ stl.EndWrite();
return;*/
/*Slicer slicer = new(Size.Empty, SizeF.Empty, "D:\\Cube100x100x100.stl");
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index b0ef763..7fcd899 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -12,7 +12,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
- <Version>3.8.0</Version>
+ <Version>3.8.1</Version>
<Platforms>AnyCPU;x64</Platforms>
<PackageIcon>UVtools.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
@@ -42,7 +42,7 @@
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
- <PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
+ <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="MessageBox.Avalonia" Version="2.1.0" />
<PackageReference Include="Projektanker.Icons.Avalonia" Version="5.5.0" />
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="5.5.0" />
diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs
index fdbb0a2..a77dab2 100644
--- a/UVtools.WPF/UserSettings.cs
+++ b/UVtools.WPF/UserSettings.cs
@@ -280,6 +280,9 @@ public sealed class UserSettings : BindableBase
private byte _centroidOutlineDiameter = 8;
private bool _centroidOutlineHollow = false;
private bool _centroidOutline = false;
+ private Color _triangulateOutlineColor = new(255, 0, 0, 255);
+ private byte _triangulateOutlineLineThickness = 2;
+ private bool _triangulateOutlineShowCount = true;
private Color _maskOutlineColor = new(255, 42, 157, 244);
private sbyte _maskOutlineLineThickness = 10;
private bool _maskClearRoiAfterSet = true;
@@ -312,6 +315,7 @@ public sealed class UserSettings : BindableBase
private bool _showBackgroudGrid;
private ushort _layerSliderDebounce;
+
public Color TooltipOverlayBackgroundColor
{
get => _tooltipOverlayBackgroundColor;
@@ -486,6 +490,35 @@ public sealed class UserSettings : BindableBase
set => RaiseAndSetIfChanged(ref _centroidOutline, value);
}
+ public Color TriangulateOutlineColor
+ {
+ get => _triangulateOutlineColor;
+ set
+ {
+ RaiseAndSetIfChanged(ref _triangulateOutlineColor, value);
+ RaisePropertyChanged(nameof(TriangulateOutlineBrush));
+ }
+ }
+
+ [XmlIgnore]
+ public SolidColorBrush TriangulateOutlineBrush
+ {
+ get => new(_triangulateOutlineColor.ToAvalonia());
+ set => TriangulateOutlineColor = new Color(value);
+ }
+
+ public byte TriangulateOutlineLineThickness
+ {
+ get => _triangulateOutlineLineThickness;
+ set => RaiseAndSetIfChanged(ref _triangulateOutlineLineThickness, value);
+ }
+
+ public bool TriangulateOutlineShowCount
+ {
+ get => _triangulateOutlineShowCount;
+ set => RaiseAndSetIfChanged(ref _triangulateOutlineShowCount, value);
+ }
+
public Color MaskOutlineColor
{
get => _maskOutlineColor;
@@ -1779,7 +1812,7 @@ public sealed class UserSettings : BindableBase
continue;
}*/
- if (directory.StartsWith($"{path}\\Chitubox", StringComparison.InvariantCultureIgnoreCase))
+ if (directory.StartsWith($"{path}\\Chitubox", StringComparison.OrdinalIgnoreCase))
{
var executable = $"{directory}\\CHITUBOX.exe";
//var executablePro = $"{directory}\\CHITUBOXPro.exe";
@@ -1800,7 +1833,7 @@ public sealed class UserSettings : BindableBase
continue;
}
- if (directory.StartsWith($"{path}\\Photon_WorkShop", StringComparison.InvariantCultureIgnoreCase))
+ if (directory.StartsWith($"{path}\\Photon_WorkShop", StringComparison.OrdinalIgnoreCase))
{
var directoryName = Path.GetFileName(directory);
var executable = $"{directory}\\{directoryName}.exe";
@@ -1822,7 +1855,7 @@ public sealed class UserSettings : BindableBase
continue;
}
- if (directory.StartsWith($"{path}\\UNIZ", StringComparison.InvariantCultureIgnoreCase))
+ if (directory.StartsWith($"{path}\\UNIZ", StringComparison.OrdinalIgnoreCase))
{
var executable = $"{directory}\\UnizMaker\\UnizMaker.exe";
if (File.Exists(executable))
@@ -1835,7 +1868,7 @@ public sealed class UserSettings : BindableBase
continue;
}
- if (directory.StartsWith($"{path}\\Zortrax", StringComparison.InvariantCultureIgnoreCase))
+ if (directory.StartsWith($"{path}\\Zortrax", StringComparison.OrdinalIgnoreCase))
{
var executable = $"{directory}\\Z-Suite\\Z-SUITE.exe";
if (File.Exists(executable))
@@ -1848,7 +1881,7 @@ public sealed class UserSettings : BindableBase
continue;
}
- if (directory.StartsWith($"{path}\\WinRAR", StringComparison.InvariantCultureIgnoreCase))
+ if (directory.StartsWith($"{path}\\WinRAR", StringComparison.OrdinalIgnoreCase))
{
var executable = $"{directory}\\WinRAR.exe";
if (File.Exists(executable))
@@ -1863,7 +1896,7 @@ public sealed class UserSettings : BindableBase
continue;
}
- if (directory.StartsWith($"{path}\\7-Zip", StringComparison.InvariantCultureIgnoreCase))
+ if (directory.StartsWith($"{path}\\7-Zip", StringComparison.OrdinalIgnoreCase))
{
var executable = $"{directory}\\7zFM.exe";
if (File.Exists(executable))
@@ -1879,7 +1912,7 @@ public sealed class UserSettings : BindableBase
}
- if (directory.StartsWith($"{path}\\010 Editor", StringComparison.InvariantCultureIgnoreCase))
+ if (directory.StartsWith($"{path}\\010 Editor", StringComparison.OrdinalIgnoreCase))
{
var executable = $"{directory}\\010Editor.exe";
if (File.Exists(executable))
@@ -1891,7 +1924,7 @@ public sealed class UserSettings : BindableBase
continue;
}
- if (directory.StartsWith($"{path}\\HxD", StringComparison.InvariantCultureIgnoreCase))
+ if (directory.StartsWith($"{path}\\HxD", StringComparison.OrdinalIgnoreCase))
{
var executable = $"{directory}\\HxD.exe";
if (File.Exists(executable))
diff --git a/UVtools.WPF/Windows/MaterialManagerWindow.axaml b/UVtools.WPF/Windows/MaterialManagerWindow.axaml
index 7ac5ab2..4de3cb0 100644
--- a/UVtools.WPF/Windows/MaterialManagerWindow.axaml
+++ b/UVtools.WPF/Windows/MaterialManagerWindow.axaml
@@ -22,39 +22,39 @@
<StackPanel Orientation="Horizontal">
<TextBlock Text="Bottles in stock:" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBox Classes="TransparentReadOnlyMultiLineNoBorder"
- Text="{Binding Manager.BottlesInStock, StringFormat=\{0:N0\}}"/>
+ Text="{Binding Manager.BottlesInStock, StringFormat={}{0:N0}}"/>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="Owned bottles:" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBox Classes="TransparentReadOnlyMultiLineNoBorder"
- Text="{Binding Manager.OwnedBottles, StringFormat=\{0:N0\}}"/>
+ Text="{Binding Manager.OwnedBottles, StringFormat={}{0:N0}}"/>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="Consumed volume:" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBox Classes="TransparentReadOnlyMultiLineNoBorder"
- Text="{Binding Manager.ConsumedVolumeLiters, StringFormat=\{0:N4\}}"/>
+ Text="{Binding Manager.ConsumedVolumeLiters, StringFormat={}{0:N4}}"/>
<TextBlock Text="liters" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="Volume in stock:" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBox Classes="TransparentReadOnlyMultiLineNoBorder"
- Text="{Binding Manager.VolumeInStockLiters, StringFormat=\{0:N4\}}"/>
+ Text="{Binding Manager.VolumeInStockLiters, StringFormat={}{0:N4}}"/>
<TextBlock Text="liters" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="Spent:" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBox Classes="TransparentReadOnlyMultiLineNoBorder"
- Text="{Binding Manager.TotalCost, StringFormat=\{0:N2\}}"/>
+ Text="{Binding Manager.TotalCost, StringFormat={}{0:N2}}"/>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock Text="Print time:" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBox Classes="TransparentReadOnlyMultiLineNoBorder"
- Text="{Binding Manager.PrintTimeSpan.TotalDays, StringFormat=\{0:N4\}}"/>
+ Text="{Binding Manager.PrintTimeSpan.TotalDays, StringFormat={}{0:N4}}"/>
<TextBlock Text="days" VerticalAlignment="Center"/>
</StackPanel>
</WrapPanel>
@@ -192,22 +192,22 @@
Width="Auto" />
<DataGridTextColumn Header="Consumed bottles"
IsReadOnly="True"
- Binding="{Binding ConsumedBottles, StringFormat=\{0:N0\}}"
+ Binding="{Binding ConsumedBottles, StringFormat={}{0:N0}}"
Width="Auto" />
<DataGridTextColumn Header="Spent"
IsReadOnly="True"
- Binding="{Binding TotalCost, StringFormat=\{0:N2\}}"
+ Binding="{Binding TotalCost, StringFormat={}{0:N2}}"
Width="Auto" />
<DataGridTextColumn Header="Remaining volume (ml)"
Binding="{Binding BottleRemainingVolume}"
Width="Auto" />
<DataGridTextColumn Header="Consumed volume (l)"
IsReadOnly="True"
- Binding="{Binding ConsumedVolumeLiters, StringFormat=\{0:N3\}}"
+ Binding="{Binding ConsumedVolumeLiters, StringFormat={}{0:N3}}"
Width="Auto" />
<DataGridTextColumn Header="Print time (days)"
IsReadOnly="True"
- Binding="{Binding PrintTimeSpan.TotalDays, StringFormat=\{0:N4\}}"
+ Binding="{Binding PrintTimeSpan.TotalDays, StringFormat={}{0:N4}}"
Width="Auto" />
</DataGrid.Columns>
diff --git a/UVtools.WPF/Windows/ProgressWindow.axaml b/UVtools.WPF/Windows/ProgressWindow.axaml
index 6426d5d..1969447 100644
--- a/UVtools.WPF/Windows/ProgressWindow.axaml
+++ b/UVtools.WPF/Windows/ProgressWindow.axaml
@@ -28,7 +28,7 @@
Margin="10" Text="{Binding Progress.Title}"/>
<TextBlock
Grid.Row="1"
- Margin="10,0,10,10" Text="{Binding Progress.ElapsedTimeStr, StringFormat=Elapsed Time: \{0\}}"/>
+ Margin="10,0,10,10" Text="{Binding Progress.ElapsedTimeStr, StringFormat=Elapsed Time: {0}}"/>
<TextBlock
Grid.Row="2"
Margin="10,0,10,10" Text="{Binding Progress.Description}" HorizontalAlignment="Center"/>
diff --git a/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml
index 5174fcd..fcd179b 100644
--- a/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml
+++ b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml
@@ -146,7 +146,7 @@
<TextBlock Grid.Row="0" HorizontalAlignment="Right"
Padding="10">
<TextBlock.Text>
- <MultiBinding StringFormat="\{0\} Update(s) | {1} Installed | {2} Profiles">
+ <MultiBinding StringFormat="{}{0} Update(s) | {1} Installed | {2} Profiles">
<Binding Path="PrusaSlicerProfiles[0].Updates"/>
<Binding Path="PrusaSlicerProfiles[0].Installed"/>
<Binding Path="PrusaSlicerProfiles[0].Items.Count"/>
@@ -189,7 +189,7 @@
<TextBlock Grid.Row="0" HorizontalAlignment="Right"
Padding="10">
<TextBlock.Text>
- <MultiBinding StringFormat="\{0\} Update(s) | {1} Installed | {2} Profiles">
+ <MultiBinding StringFormat="{}{0} Update(s) | {1} Installed | {2} Profiles">
<Binding Path="PrusaSlicerProfiles[1].Updates"/>
<Binding Path="PrusaSlicerProfiles[1].Installed"/>
<Binding Path="PrusaSlicerProfiles[1].Items.Count"/>
@@ -248,7 +248,7 @@
<TextBlock Grid.Row="0" HorizontalAlignment="Right"
Padding="10">
<TextBlock.Text>
- <MultiBinding StringFormat="\{0\} Update(s) | {1} Installed | {2} Profiles">
+ <MultiBinding StringFormat="{}{0} Update(s) | {1} Installed | {2} Profiles">
<Binding Path="SuperSlicerProfiles[0].Updates"/>
<Binding Path="SuperSlicerProfiles[0].Installed"/>
<Binding Path="SuperSlicerProfiles[0].Items.Count"/>
@@ -291,7 +291,7 @@
<TextBlock Grid.Row="0" HorizontalAlignment="Right"
Padding="10">
<TextBlock.Text>
- <MultiBinding StringFormat="\{0\} Update(s) | {1} Installed | {2} Profiles">
+ <MultiBinding StringFormat="{}{0} Update(s) | {1} Installed | {2} Profiles">
<Binding Path="SuperSlicerProfiles[1].Updates"/>
<Binding Path="SuperSlicerProfiles[1].Installed"/>
<Binding Path="SuperSlicerProfiles[1].Items.Count"/>
diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml
index a895c9b..51129dd 100644
--- a/UVtools.WPF/Windows/SettingsWindow.axaml
+++ b/UVtools.WPF/Windows/SettingsWindow.axaml
@@ -519,7 +519,9 @@
<StackPanel Orientation="Vertical">
<TextBlock Classes="GroupBoxHeader" Text="Layer colors"/>
- <Grid Margin="10" RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto" ColumnDefinitions="Auto,Auto,Auto,Auto,*">
+ <Grid Margin="10"
+ RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,Auto,Auto,Auto,*">
<!--Tooltip overlay-->
<TextBlock Grid.Row="0" Grid.Column="0"
@@ -533,15 +535,13 @@
VerticalAlignment="Center"
Background="{Binding Settings.LayerPreview.TooltipOverlayBackgroundBrush}"
Command="{Binding SelectColor}"
- CommandParameter="TooltipOverlayBackgroundColor"
- />
+ CommandParameter="TooltipOverlayBackgroundColor"/>
<CheckBox Grid.Row="0" Grid.Column="4"
Margin="10,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="Show by default"
- IsChecked="{Binding Settings.LayerPreview.TooltipOverlay}"
- />
+ IsChecked="{Binding Settings.LayerPreview.TooltipOverlay}"/>
<!--Print volume boundary-->
@@ -557,16 +557,13 @@
VerticalAlignment="Center"
Background="{Binding Settings.LayerPreview.VolumeBoundsOutlineBrush}"
Command="{Binding SelectColor}"
- CommandParameter="VolumeBoundsOutlineColor"
- />
+ CommandParameter="VolumeBoundsOutlineColor"/>
<NumericUpDown Grid.Row="2" Grid.Column="2"
Margin="10,0,0,0"
VerticalAlignment="Center"
-
Minimum="1"
Maximum="50"
- Value="{Binding Settings.LayerPreview.VolumeBoundsOutlineThickness}"
- />
+ Value="{Binding Settings.LayerPreview.VolumeBoundsOutlineThickness}"/>
<TextBlock Grid.Row="2" Grid.Column="3"
Margin="5,0,0,0"
VerticalAlignment="Center"
@@ -576,8 +573,7 @@
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="Show by default"
- IsChecked="{Binding Settings.LayerPreview.VolumeBoundsOutline}"
- />
+ IsChecked="{Binding Settings.LayerPreview.VolumeBoundsOutline}"/>
<!--Layer boundary-->
<TextBlock Grid.Row="4" Grid.Column="0"
@@ -641,7 +637,7 @@
IsChecked="{Binding Settings.LayerPreview.ContourBoundsOutline}"/>
- <!--Hallow area boundary-->
+ <!--Hollow area boundary-->
<TextBlock Grid.Row="8" Grid.Column="0"
VerticalAlignment="Center"
Text="Hollow area outline:"/>
@@ -672,7 +668,7 @@
Content="Show by default"
IsChecked="{Binding Settings.LayerPreview.HollowOutline}"/>
- <!--Blob boundary-->
+ <!--Centroids boundary-->
<TextBlock Grid.Row="10" Grid.Column="0"
VerticalAlignment="Center"
Text="Centroids:"/>
@@ -707,12 +703,43 @@
IsChecked="{Binding Settings.LayerPreview.CentroidOutline}"/>
</StackPanel>
+ <!-- Triangulate -->
+ <TextBlock Grid.Row="12" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Triangulate:"/>
+ <Button Grid.Row="12" Grid.Column="1"
+ Margin="10,0,0,0"
+ Padding="10"
+ BorderBrush="Black"
+ BorderThickness="2"
+ VerticalAlignment="Center"
+ Background="{Binding Settings.LayerPreview.TriangulateOutlineBrush}"
+ Command="{Binding SelectColor}"
+ CommandParameter="TriangulateOutlineColor"/>
+ <NumericUpDown Grid.Row="12" Grid.Column="2"
+ Margin="10,0,0,0"
+ VerticalAlignment="Center"
+ Minimum="1"
+ Maximum="50"
+ Value="{Binding Settings.LayerPreview.TriangulateOutlineLineThickness}"/>
+ <TextBlock Grid.Row="12" Grid.Column="3"
+ Margin="5,0,0,0"
+ VerticalAlignment="Center"
+ Text="Line thickness"/>
+
+ <StackPanel
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ Grid.Row="12" Grid.Column="3" Grid.ColumnSpan="2" Orientation="Horizontal" Spacing="10">
+ <CheckBox Content="Show triangle count"
+ IsChecked="{Binding Settings.LayerPreview.TriangulateOutlineShowCount}"/>
+ </StackPanel>
<!--Masks-->
- <TextBlock Grid.Row="12" Grid.Column="0"
+ <TextBlock Grid.Row="14" Grid.Column="0"
VerticalAlignment="Center"
Text="Mask area outline:"/>
- <Button Grid.Row="12" Grid.Column="1"
+ <Button Grid.Row="14" Grid.Column="1"
Margin="10,0,0,0"
Padding="10"
BorderBrush="Black"
@@ -721,18 +748,18 @@
Background="{Binding Settings.LayerPreview.MaskOutlineBrush}"
Command="{Binding SelectColor}"
CommandParameter="MaskOutlineColor"/>
- <NumericUpDown Grid.Row="12" Grid.Column="2"
+ <NumericUpDown Grid.Row="14" Grid.Column="2"
Margin="10,0,0,0"
VerticalAlignment="Center"
Minimum="-1"
Maximum="127"
Value="{Binding Settings.LayerPreview.MaskOutlineLineThickness}"/>
- <TextBlock Grid.Row="12" Grid.Column="3"
+ <TextBlock Grid.Row="14" Grid.Column="3"
Margin="5,0,0,0"
VerticalAlignment="Center"
ToolTip.Tip="Set -1 to fill the area"
Text="Line thickness"/>
- <CheckBox Grid.Row="12" Grid.Column="4"
+ <CheckBox Grid.Row="14" Grid.Column="4"
Margin="10,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
diff --git a/UVtools.WPF/Windows/ToolWindow.axaml b/UVtools.WPF/Windows/ToolWindow.axaml
index 71d485a..0921835 100644
--- a/UVtools.WPF/Windows/ToolWindow.axaml
+++ b/UVtools.WPF/Windows/ToolWindow.axaml
@@ -165,12 +165,12 @@
<TextBlock
Grid.Row="2" Grid.Column="1"
HorizontalAlignment="Center"
- Text="{Binding LayerStartMM, StringFormat=(\{0:F3\}mm)}" />
+ Text="{Binding LayerStartMM, StringFormat=({0:F3}mm)}" />
<TextBlock
Grid.Row="2" Grid.Column="3"
HorizontalAlignment="Center"
- Text="{Binding LayerEndMM, StringFormat=(\{0:F3\}mm)}" />
+ Text="{Binding LayerEndMM, StringFormat=({0:F3}mm)}" />
<TextBlock
Grid.Row="2" Grid.Column="4"
@@ -207,7 +207,7 @@
Command="{Binding ClearROI}"/>
<Button
VerticalAlignment="Center"
- Content="{Binding Masks.Length, StringFormat=Clear \{0\} mask(s)}"
+ Content="{Binding Masks.Length, StringFormat=Clear {0} mask(s)}"
IsVisible="{Binding IsMasksVisible}"
Command="{Binding ClearMasks}"/>
@@ -218,7 +218,7 @@
<StackPanel Margin="15" Spacing="5">
<TextBlock VerticalAlignment="Center"
IsVisible="{Binding IsROIVisible}"
- Text="{Binding ROI, StringFormat=Region: \{0\}}" />
+ Text="{Binding ROI, StringFormat=Region: {0}}" />
<CheckBox
Content="Clear ROI and Masks after perform the operation"
@@ -241,7 +241,7 @@
<TextBlock Grid.Row="0" Grid.Column="0"
Padding="10" FontWeight="Bold"
VerticalAlignment="Center"
- Text="{Binding Profiles.Count, StringFormat=Profiles: \{0\}}" />
+ Text="{Binding Profiles.Count, StringFormat=Profiles: {0}}" />
<Button Grid.Row="0" Grid.Column="1"
VerticalAlignment="Center"
diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml
index 7b7634d..1345158 100644
--- a/documentation/UVtools.Core.xml
+++ b/documentation/UVtools.Core.xml
@@ -982,6 +982,15 @@
Extended OpenCV PutText to accepting line breaks, line alignment and rotation
</summary>
</member>
+ <member name="M:UVtools.Core.Extensions.EmguExtensions.GetSvgPath(Emgu.CV.Mat,Emgu.CV.CvEnum.ChainApproxMethod,System.Boolean)">
+ <summary>
+ From <paramref name="src"/> gets the SVG path's. Tags are not included.
+ </summary>
+ <param name="src"></param>
+ <param name="compression">Compression method for the contours</param>
+ <param name="threshold">True to binary threshold first</param>
+ <returns>Array of path's</returns>
+ </member>
<member name="M:UVtools.Core.Extensions.EmguExtensions.FindContours(Emgu.CV.IInputOutputArray,Emgu.CV.CvEnum.RetrType,Emgu.CV.CvEnum.ChainApproxMethod,System.Drawing.Point)">
<summary>
Retrieves contours from the binary image as a contour tree. The pointer firstContour is filled by the function. It is provided as a convenient way to obtain the hierarchy value as int[,].
@@ -4317,6 +4326,22 @@
https://www.ucamco.com/files/downloads/file_en/456/gerber-layer-format-specification-revision-2022-02_en.pdf?ac97011bf6bce9aaf0b1aac43d84b05f
</summary>
</member>
+ <member name="P:UVtools.Core.Gerber.GerberDocument.PolarityColor">
+ <summary>
+ Gets the current polarity as <see cref="T:Emgu.CV.Structure.MCvScalar"/>. <see cref="P:UVtools.Core.Gerber.GerberDocument.InversePolarity"/> will affect the return value
+ </summary>
+ </member>
+ <member name="P:UVtools.Core.Gerber.GerberDocument.InversePolarity">
+ <summary>
+ Gets or sets to inverse the polarity on drawing
+ </summary>
+ </member>
+ <member name="P:UVtools.Core.Gerber.GerberDocument.SizeScale">
+ <summary>
+ Gets or sets the scale to apply to each shape drawing size.
+ Positions and vectors aren't affected by this.
+ </summary>
+ </member>
<member name="P:UVtools.Core.Gerber.Macro.Name">
<summary>
Gets the macro name
@@ -5616,6 +5641,43 @@
Gets or sets the brightness level
</summary>
</member>
+ <member name="P:UVtools.Core.Objects.GenericFileRepresentation.FilePath">
+ <summary>
+ Gets or sets the file path
+ </summary>
+ </member>
+ <member name="P:UVtools.Core.Objects.GenericFileRepresentation.FileName">
+ <summary>
+ Gets the file name with extension
+ </summary>
+ </member>
+ <member name="P:UVtools.Core.Objects.GenericFileRepresentation.FileNameWithoutExtension">
+ <summary>
+ Gets the file name without extension
+ </summary>
+ </member>
+ <member name="P:UVtools.Core.Objects.GenericFileRepresentation.FileExtension">
+ <summary>
+ Gets the file extension. The returned value includes the period (".")
+ </summary>
+ </member>
+ <member name="P:UVtools.Core.Objects.GenericFileRepresentation.Exists">
+ <summary>
+ Gets if the file exists
+ </summary>
+ </member>
+ <member name="P:UVtools.Core.Objects.GenericFileRepresentation.FileInfo">
+ <summary>
+ Gets an <see cref="P:UVtools.Core.Objects.GenericFileRepresentation.FileInfo"/> instance on the <see cref="P:UVtools.Core.Objects.GenericFileRepresentation.FilePath"/>
+ </summary>
+ </member>
+ <member name="M:UVtools.Core.Objects.GenericFileRepresentation.IsExtension(System.String)">
+ <summary>
+ Checks if the <see cref="P:UVtools.Core.Objects.GenericFileRepresentation.FilePath"/> ends with <paramref name="extension"/>
+ </summary>
+ <param name="extension">Extension name</param>
+ <returns>True if found, otherwise false</returns>
+ </member>
<member name="P:UVtools.Core.Objects.MappedProcess.IsEnabled">
<summary>
Gets or sets if this device is enabled
@@ -6309,6 +6371,17 @@
Fills the plate with maximum cols and rows
</summary>
</member>
+ <member name="P:UVtools.Core.Operations.OperationPCBExposure.PCBExposureFile.InvertPolarity">
+ <summary>
+ Gets or sets to invert the polarity when drawing
+ </summary>
+ </member>
+ <member name="P:UVtools.Core.Operations.OperationPCBExposure.PCBExposureFile.SizeScale">
+ <summary>
+ Gets or sets the scale to apply to each shape drawing size.
+ Positions and vectors aren't affected by this.
+ </summary>
+ </member>
<member name="P:UVtools.Core.Operations.OperationPixelDimming.AlternatePatternPerLayers">
<summary>
Use the alternate pattern every <see cref="P:UVtools.Core.Operations.OperationPixelDimming.AlternatePatternPerLayers"/> layers