From e727a097b31a0b71f8bc22649364e69f2aa72b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Wed, 2 Nov 2022 23:39:21 +0000 Subject: [PATCH] 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 --- CHANGELOG.md | 16 +- RELEASE_NOTES.md | 38 ++--- UVtools.Core/Extensions/EmguExtensions.cs | 56 +++++++ UVtools.Core/FileFormats/FileFormat.cs | 2 +- UVtools.Core/FileFormats/ZCodexFile.cs | 2 +- UVtools.Core/Gerber/GerberDocument.cs | 107 +++++++++---- UVtools.Core/Helpers.cs | 6 +- UVtools.Core/Objects/ExposureItem.cs | 9 +- .../Objects/GenericFileRepresentation.cs | 137 +++++++++++++++++ .../Objects/RangeObservableCollection.cs | 9 +- .../Operations/OperationLayerExportImage.cs | 43 +----- .../Operations/OperationLayerImport.cs | 43 +++--- .../Operations/OperationPCBExposure.cs | 144 +++++++++++++----- UVtools.Core/UVtools.Core.csproj | 2 +- UVtools.Core/Voxel/Voxelizer.cs | 4 +- .../Code/HeatGeneratedFileList.wxs | 4 - .../CalibrateElephantFootControl.axaml | 12 +- .../CalibrateGrayscaleControl.axaml | 14 +- .../CalibrateLiftHeightControl.axaml | 4 +- .../CalibrateStressTowerControl.axaml | 2 +- .../CalibrateToleranceControl.axaml | 6 +- .../CalibrateXYZAccuracyControl.axaml | 8 +- .../Tools/ToolDynamicLayerHeightControl.axaml | 2 +- .../Tools/ToolLayerImportControl.axaml | 2 +- .../Tools/ToolLayerImportControl.axaml.cs | 18 +-- .../Tools/ToolPCBExposureControl.axaml | 64 ++++++-- .../Tools/ToolPCBExposureControl.axaml.cs | 39 +++-- .../Tools/ToolPixelArithmeticControl.axaml | 2 +- .../Tools/ToolPixelDimmingControl.axaml | 2 +- .../Tools/ToolRaftReliefControl.axaml | 2 +- .../Tools/ToolRedrawModelControl.axaml | 2 +- UVtools.WPF/MainWindow.LayerPreview.cs | 69 ++++++++- UVtools.WPF/MainWindow.axaml | 3 + UVtools.WPF/MainWindow.axaml.cs | 6 +- UVtools.WPF/Program.cs | 60 ++++++-- UVtools.WPF/UVtools.WPF.csproj | 4 +- UVtools.WPF/UserSettings.cs | 49 +++++- .../Windows/MaterialManagerWindow.axaml | 20 +-- UVtools.WPF/Windows/ProgressWindow.axaml | 2 +- .../Windows/PrusaSlicerManagerWindow.axaml | 8 +- UVtools.WPF/Windows/SettingsWindow.axaml | 67 +++++--- UVtools.WPF/Windows/ToolWindow.axaml | 10 +- documentation/UVtools.Core.xml | 73 +++++++++ 43 files changed, 870 insertions(+), 302 deletions(-) create mode 100644 UVtools.Core/Objects/GenericFileRepresentation.cs 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 + + /// + /// From gets the SVG path's. Tags are not included. + /// + /// + /// Compression method for the contours + /// True to binary threshold first + /// Array of path's + public static IEnumerable 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 /// 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 /// Gets the total time in seconds the display will remain on exposing the layers during the print /// - 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); /// /// 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; + /// + /// Gets the current polarity as . will affect the return value + /// + public MCvScalar PolarityColor => + Polarity == GerberPolarityType.Dark + ? !InversePolarity + ? EmguExtensions.WhiteColor + : EmguExtensions.BlackColor + : !InversePolarity + ? EmguExtensions.BlackColor + : EmguExtensions.WhiteColor; + + /// + /// Gets or sets to inverse the polarity on drawing + /// + public bool InversePolarity { get; set; } + + /// + /// Gets or sets the scale to apply to each shape drawing size. + /// Positions and vectors aren't affected by this. + /// + 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()); 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. + * 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. + * 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, IEquatable, + IComparable, IEquatable +{ + #region Members + private string _filePath = null!; + #endregion + + #region Properties + /// + /// Gets or sets the file path + /// + public string FilePath + { + get => _filePath; + set + { + if (RaiseAndSetIfChanged(ref _filePath, value)) return; + RaisePropertyChanged(nameof(FileName)); + RaisePropertyChanged(nameof(Exists)); + RaisePropertyChanged(nameof(FileInfo)); + } + } + + /// + /// Gets the file name with extension + /// + public string FileName => Path.GetFileName(_filePath); + + /// + /// Gets the file name without extension + /// + public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(_filePath); + + /// + /// Gets the file extension. The returned value includes the period (".") + /// + public string FileExtension => Path.GetExtension(_filePath); + + /// + /// Gets if the file exists + /// + public bool Exists => File.Exists(FilePath); + + /// + /// Gets an instance on the + /// + public FileInfo FileInfo => new(_filePath); + #endregion + + #region Constructor + public GenericFileRepresentation() + { } + + public GenericFileRepresentation(string filePath) + { + _filePath = filePath; + } + #endregion + + #region Methods + + /// + /// Checks if the ends with + /// + /// Extension name + /// True if found, otherwise false + 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 : ObservableCollection } 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(" + + Text="{Binding Operation.Files.Count, StringFormat=Files: {0}}"/> - + + + + + + + + + - + + + + + + + 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("FilesListBox"); + FilesDataGrid = this.Find("FilesDataGrid"); + + AddHandler(DragDrop.DropEvent, (sender, args) => + { + Operation.AddFiles(args.Data.GetFileNames()?.ToArray() ?? Array.Empty()); + }); _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()); + Operation.Files.RemoveRange(FilesDataGrid.SelectedItems.OfType()); } 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 @@ + Text="{Binding Operation.PatternGenBrightnessPercent, StringFormat=({0}%)}"/> 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 @@ + Text="{Binding Operation.BrightnessPercent, StringFormat=({0}%)}"/> 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 + Text="{Binding Operation.BrightnessPercent, StringFormat={}{0:F2}%}" 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}"/> + Text="{Binding Operation.BrightnessPercent, StringFormat={}{0:F2}%}"/> 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(); + 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 @@ + 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(); + + 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(); + var list = new List(); + 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++; + + + //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) + ); } - - break; } - 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 @@ LICENSE https://github.com/sn4k3/UVtools Git - 3.8.0 + 3.8.1 AnyCPU;x64 UVtools.png README.md @@ -42,7 +42,7 @@ - + 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 @@ + Text="{Binding Manager.BottlesInStock, StringFormat={}{0:N0}}"/> + Text="{Binding Manager.OwnedBottles, StringFormat={}{0:N0}}"/> + Text="{Binding Manager.ConsumedVolumeLiters, StringFormat={}{0:N4}}"/> + Text="{Binding Manager.VolumeInStockLiters, StringFormat={}{0:N4}}"/> + Text="{Binding Manager.TotalCost, StringFormat={}{0:N2}}"/> + Text="{Binding Manager.PrintTimeSpan.TotalDays, StringFormat={}{0:N4}}"/> @@ -192,22 +192,22 @@ Width="Auto" /> 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}"/> + Margin="10,0,10,10" Text="{Binding Progress.ElapsedTimeStr, StringFormat=Elapsed Time: {0}}"/> 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 @@ - + @@ -189,7 +189,7 @@ - + @@ -248,7 +248,7 @@ - + @@ -291,7 +291,7 @@ - + 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 @@ - + + CommandParameter="TooltipOverlayBackgroundColor"/> + IsChecked="{Binding Settings.LayerPreview.TooltipOverlay}"/> @@ -557,16 +557,13 @@ VerticalAlignment="Center" Background="{Binding Settings.LayerPreview.VolumeBoundsOutlineBrush}" Command="{Binding SelectColor}" - CommandParameter="VolumeBoundsOutlineColor" - /> + CommandParameter="VolumeBoundsOutlineColor"/> + Value="{Binding Settings.LayerPreview.VolumeBoundsOutlineThickness}"/> + IsChecked="{Binding Settings.LayerPreview.VolumeBoundsOutline}"/> - + @@ -672,7 +668,7 @@ Content="Show by default" IsChecked="{Binding Settings.LayerPreview.HollowOutline}"/> - + @@ -707,12 +703,43 @@ IsChecked="{Binding Settings.LayerPreview.CentroidOutline}"/> - - + + Text="Triangulate:"/>