diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2020-11-14 08:26:49 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2020-11-14 08:26:49 +0300 |
commit | 6d661821b522c6c8cc2c5b645693e8dd3dab76b4 (patch) | |
tree | ab5b947f47baf1a9c51bf84f6e365461c86d6215 | |
parent | 3455f09c24daa26c12346a7e0cd451e6d947e8a7 (diff) |
v1.3.0v1.3.0
* (Add) Changelog description to the new version update dialog
* (Add) Tool - Infill: Proper configurable infills
* (Add) Pixel area as "px²" to the layer bounds and ROI at layer bottom information bar
* (Add) Pixel dimming: Alternate pattern every x layers
* (Add) Pixel dimming: Lattice infill
* (Add) Solidify: Required minimum/maximum area to solidify found areas (Default values will produce the old behaviour)
* (Add) Issues: Allow to hide and ignore selected issues
* (Add) Issue - Touch boundary: Allow to configure Left, Top, Right, Bottom margins in pixels, defaults to 5px (#94)
* (Add) UVJ: Allow convert to another formats (#96)
* (Add) Setters to some internal Core properties for more abstraction
* (Improvement) Issue - Touch boundary: Only check boundary pixels if layer bounds overlap the set margins, otherwise, it will not waste cycles on check individual rows of pixels when not need to
* (Change) Place .ctb extension show first than .cbddlp due more popular this days
* (Change) Pixel dimming: Text "Borders" to "Walls"
* (Change) Issues: Remove "Remove" text from button, keep only the icon to free up space
* (Change) Ungroup extensions on "covert to" menu (#97)
* (Fix) Issues: Detect button has a incorrect "save" icon
* (Fix) SL1: Increase NumSlow property limit
* (Fix) UVJ: not decoding nor showing preview images
* (Fix) "Convert to" menu shows same options than previous loaded file when current file dont support convertions (#96)
* (Fix) Hides "Convert to" menu when unable to convert to another format (#96)
* (Fix) Program crash when demo file is disabled and tries to load a file in
* (Fix) Rare crash on startup when mouse dont move in startup period and user types a key in meanwhile
* (Fix) On a slow startup on progress window it will show "Decoded layers" as default text, changed to "Initializing"
46 files changed, 1867 insertions, 230 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 582fc37..d1910c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,32 @@ # Changelog -## 07/11/2020 - v1.2.1 +## 14/11/2020 - v1.3.0 + +* (Add) Changelog description to the new version update dialog +* (Add) Tool - Infill: Proper configurable infills +* (Add) Pixel area as "px²" to the layer bounds and ROI at layer bottom information bar +* (Add) Pixel dimming: Alternate pattern every x layers +* (Add) Pixel dimming: Lattice infill +* (Add) Solidify: Required minimum/maximum area to solidify found areas (Default values will produce the old behaviour) +* (Add) Issues: Allow to hide and ignore selected issues +* (Add) Issue - Touch boundary: Allow to configure Left, Top, Right, Bottom margins in pixels, defaults to 5px (#94) +* (Add) UVJ: Allow convert to another formats (#96) +* (Add) Setters to some internal Core properties for more abstraction +* (Improvement) Issue - Touch boundary: Only check boundary pixels if layer bounds overlap the set margins, otherwise, it will not waste cycles on check individual rows of pixels when not need to +* (Change) Place .ctb extension show first than .cbddlp due more popular this days +* (Change) Pixel dimming: Text "Borders" to "Walls" +* (Change) Issues: Remove "Remove" text from button, keep only the icon to free up space +* (Change) Ungroup extensions on "covert to" menu (#97) +* (Fix) Issues: Detect button has a incorrect "save" icon +* (Fix) SL1: Increase NumSlow property limit +* (Fix) UVJ: not decoding nor showing preview images +* (Fix) "Convert to" menu shows same options than previous loaded file when current file dont support convertions (#96) +* (Fix) Hides "Convert to" menu when unable to convert to another format (#96) +* (Fix) Program crash when demo file is disabled and tries to load a file in +* (Fix) Rare crash on startup when mouse dont move in startup period and user types a key in meanwhile +* (Fix) On a slow startup on progress window it will show "Decoded layers" as default text, changed to "Initializing" + +## 08/11/2020 - v1.2.1 * (Add) IsModified property to current layer information, indicates if layer have unsaved changes * (Add) Splitter between preview image and properties to resize the vertical space between that two controls diff --git a/UVtools.Cmd/Program.cs b/UVtools.Cmd/Program.cs index 744d463..e19e5ef 100644 --- a/UVtools.Cmd/Program.cs +++ b/UVtools.Cmd/Program.cs @@ -187,7 +187,7 @@ namespace UVtools.Cmd { Console.WriteLine("Computing Issues, please wait."); sw.Restart(); - var issueList = fileFormat.LayerManager.GetAllIssues(null, null, null, null, true, progress); + var issueList = fileFormat.LayerManager.GetAllIssues(null, null, null, null, true, null, progress); sw.Stop(); Console.WriteLine("Issues:"); diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index 0791ec4..a1a2436 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -17,6 +17,7 @@ namespace UVtools.Core.Extensions { public static class EmguExtensions { + public static readonly MCvScalar WhiteByte = new MCvScalar(255); public static readonly MCvScalar BlackByte = new MCvScalar(0); public static readonly MCvScalar Black3Byte = new MCvScalar(0, 0, 0); public static readonly MCvScalar Transparent4Byte = new MCvScalar(0, 0, 0, 0); diff --git a/UVtools.Core/Extensions/PointExtensions.cs b/UVtools.Core/Extensions/PointExtensions.cs new file mode 100644 index 0000000..4703ca5 --- /dev/null +++ b/UVtools.Core/Extensions/PointExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace UVtools.Core.Extensions +{ + public static class PointExtensions + { + public static Point Rotate(this Point point, double angleDegree, Point pivot = default) + { + double angle = angleDegree * Math.PI / 180; + double cos = Math.Cos(angle); + double sin = Math.Sin(angle); + int dx = point.X - pivot.X; + int dy = point.Y - pivot.Y; + double x = cos * dx - sin * dy + pivot.X; + double y = sin * dx + cos * dy + pivot.X; + + Point rotated = new Point((int)Math.Round(x), (int)Math.Round(y)); + return rotated; + } + } +} diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs index c57af3a..3dcbb25 100644 --- a/UVtools.Core/FileFormats/CWSFile.cs +++ b/UVtools.Core/FileFormats/CWSFile.cs @@ -126,7 +126,7 @@ namespace UVtools.Core.FileFormats public PrinterType Printer { get; set; } = PrinterType.Unknown; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("cws", "NovaMaker CWS Files"), + new FileExtension("cws", "NovaMaker CWS"), //new FileExtension("cws", "NovaMaker Bene Mono CWS Files", "Bene") }; @@ -306,7 +306,7 @@ namespace UVtools.Core.FileFormats } - public override float PrintTime => 0; + /*public override float PrintTime => 0; public override float UsedMaterial => 0; @@ -314,7 +314,7 @@ namespace UVtools.Core.FileFormats public override string MaterialName => string.Empty; - public override string MachineName => "Unknown"; + public override string MachineName => "Unknown";*/ public override object[] Configs => new object[] { SliceSettings, OutputSettings}; #endregion diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs index 1b5a19a..2561172 100644 --- a/UVtools.Core/FileFormats/ChituboxFile.cs +++ b/UVtools.Core/FileFormats/ChituboxFile.cs @@ -957,17 +957,17 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Binary; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("cbddlp", "Chitubox DLP Files"), - new FileExtension("ctb", "Chitubox CTB Files"), - new FileExtension("photon", "Chitubox Photon Files"), + new FileExtension("ctb", "Chitubox CTB"), + new FileExtension("cbddlp", "Chitubox CBDDLP"), + new FileExtension("photon", "Chitubox Photon"), }; public override Type[] ConvertToFormats { get; } = { typeof(ChituboxFile), typeof(ChituboxZipFile), - typeof(PWSFile), typeof(PHZFile), + typeof(PWSFile), typeof(ZCodexFile), typeof(CWSFile), typeof(UVJFile), @@ -1209,15 +1209,48 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => HeaderSettings.PrintTime; + public override float PrintTime + { + get => HeaderSettings.PrintTime; + set + { + HeaderSettings.PrintTime = (uint) value; + RaisePropertyChanged(); + } + } + + public override float UsedMaterial + { + get => (float) Math.Round(PrintParametersSettings.VolumeMl, 2); + set + { + PrintParametersSettings.VolumeMl = (float) Math.Round(value, 2); + RaisePropertyChanged(); + } + } - public override float UsedMaterial => (float) Math.Round(PrintParametersSettings.VolumeMl, 2); + public override float MaterialCost + { + get => (float) Math.Round(PrintParametersSettings.CostDollars, 2); + set + { + PrintParametersSettings.CostDollars = (float) Math.Round(value, 2); + RaisePropertyChanged(); + } + } - public override float MaterialCost => (float) Math.Round(PrintParametersSettings.CostDollars, 2); + public override string MachineName + { + get => SlicerInfoSettings.MachineName; + set + { + SlicerInfoSettings.MachineName = value; + SlicerInfoSettings.MachineNameSize = (uint) SlicerInfoSettings.MachineName.Length; + RequireFullEncode = true; + RaisePropertyChanged(); + } + } - public override string MaterialName => "Unknown"; - public override string MachineName => SlicerInfoSettings.MachineName; - public override object[] Configs => new[] { (object)HeaderSettings, PrintParametersSettings, SlicerInfoSettings }; public bool IsCbddlpFile => HeaderSettings.Magic == MAGIC_CBDDLP; diff --git a/UVtools.Core/FileFormats/ChituboxZipFile.cs b/UVtools.Core/FileFormats/ChituboxZipFile.cs index 9e37497..bdb8fe5 100644 --- a/UVtools.Core/FileFormats/ChituboxZipFile.cs +++ b/UVtools.Core/FileFormats/ChituboxZipFile.cs @@ -93,7 +93,7 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Archive; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("zip", "Chitubox Zip Files") + new FileExtension("zip", "Chitubox Zip") }; public override Type[] ConvertToFormats { get; } = { @@ -317,15 +317,56 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => HeaderSettings.EstimatedPrintTime; + public override float PrintTime + { + get => HeaderSettings.EstimatedPrintTime; + set + { + HeaderSettings.EstimatedPrintTime = value; + RaisePropertyChanged(); + } + } - public override float UsedMaterial => HeaderSettings.Weight; + public override float UsedMaterial + { + get => HeaderSettings.Weight; + set + { + HeaderSettings.Weight = value; + RaisePropertyChanged(); + } + } - public override float MaterialCost => HeaderSettings.Price; + public override float MaterialCost + { + get => HeaderSettings.Price; + set + { + HeaderSettings.Price = value; + RaisePropertyChanged(); + } + } - public override string MaterialName => HeaderSettings.Resin; + public override string MaterialName + { + get => HeaderSettings.Resin; + set + { + HeaderSettings.Resin = value; + RaisePropertyChanged(); + } + } - public override string MachineName => HeaderSettings.MachineType; + public override string MachineName + { + get => HeaderSettings.MachineType; + set + { + HeaderSettings.MachineType = value; + RequireFullEncode = true; + RaisePropertyChanged(); + } + } public override object[] Configs => new object[] { HeaderSettings }; diff --git a/UVtools.Core/FileFormats/FileExtension.cs b/UVtools.Core/FileFormats/FileExtension.cs index 8f72fb5..4d15556 100644 --- a/UVtools.Core/FileFormats/FileExtension.cs +++ b/UVtools.Core/FileFormats/FileExtension.cs @@ -94,5 +94,13 @@ namespace UVtools.Core.FileFormats public static IEqualityComparer<FileExtension> ExtensionComparer { get; } = new ExtensionEqualityComparer(); #endregion + + #region Methods + + public FileFormat GetFileFormat() + { + return FileFormat.FindByExtension(Extension); + } + #endregion } } diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index fe0f667..6f0b41c 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -7,15 +7,12 @@ */ using System; -using System.CodeDom.Compiler; using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using Emgu.CV; @@ -193,8 +190,8 @@ namespace UVtools.Core.FileFormats new SL1File(), // Prusa SL1 new ChituboxZipFile(), // Zip new ChituboxFile(), // cbddlp, cbt, photon - new PhotonSFile(), // photons new PHZFile(), // phz + new PhotonSFile(), // photons new PWSFile(), // PSW new ZCodexFile(), // zcodex new CWSFile(), // CWS @@ -330,6 +327,8 @@ namespace UVtools.Core.FileFormats } } + public bool SuppressRebuildProperties { get; set; } + public string FileFullPath { get; set; } public abstract byte ThumbnailsCount { get; } @@ -439,7 +438,7 @@ namespace UVtools.Core.FileFormats public virtual byte LightPWM { get; set; } = DefaultLightPWM; - public abstract float PrintTime { get; } + public virtual float PrintTime { get; set; } //(header.numberOfLayers - header.bottomLayers) * (double) header.exposureTimeSeconds + (double) header.bottomLayers * (double) header.exposureBottomTimeSeconds + (double) header.offTimeSeconds * (double) header.numberOfLayers); public virtual float PrintTimeOrComputed { @@ -455,13 +454,13 @@ namespace UVtools.Core.FileFormats public float PrintTimeHours => (float) Math.Round(PrintTimeOrComputed / 3600, 2); - public abstract float UsedMaterial { get; } + public virtual float UsedMaterial { get; set; } - public abstract float MaterialCost { get; } + public virtual float MaterialCost { get; set; } - public abstract string MaterialName { get; } - - public abstract string MachineName { get; } + public virtual string MaterialName { get; set; } + + public virtual string MachineName { get; set; } = "Unknown"; public StringBuilder GCode { get; set; } @@ -482,7 +481,7 @@ namespace UVtools.Core.FileFormats private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { - Debug.WriteLine(e.PropertyName); + if (SuppressRebuildProperties) return; if (e.PropertyName == nameof(LayerCount)) { if (this[LayerCount - 1] is null) return; // Not initialized @@ -1084,9 +1083,7 @@ namespace UVtools.Core.FileFormats public abstract bool Convert(Type to, string fileFullPath, OperationProgress progress = null); public bool Convert(FileFormat to, string fileFullPath, OperationProgress progress = null) - { - return Convert(to.GetType(), fileFullPath, progress); - } + => Convert(to.GetType(), fileFullPath, progress); public byte ValidateAntiAliasingLevel() { diff --git a/UVtools.Core/FileFormats/IFileFormat.cs b/UVtools.Core/FileFormats/IFileFormat.cs index 6329e9b..91893e1 100644 --- a/UVtools.Core/FileFormats/IFileFormat.cs +++ b/UVtools.Core/FileFormats/IFileFormat.cs @@ -57,6 +57,11 @@ namespace UVtools.Core.FileFormats string FileFilterExtensionsOnly { get; } /// <summary> + /// Gets or sets if change a global property should rebuild every layer data based on them + /// </summary> + bool SuppressRebuildProperties { get; set; } + + /// <summary> /// Gets the input file path loaded into this <see cref="FileFormat"/> /// </summary> string FileFullPath { get; set; } @@ -216,7 +221,7 @@ namespace UVtools.Core.FileFormats /// <summary> /// Gets the estimate print time in seconds /// </summary> - float PrintTime { get; } + float PrintTime { get; set; } /// <summary> /// Gets the estimate print time in seconds, if print doesn't support it it will be calculated @@ -231,22 +236,22 @@ namespace UVtools.Core.FileFormats /// <summary> /// Gets the estimate used material in ml /// </summary> - float UsedMaterial { get; } + float UsedMaterial { get; set; } /// <summary> /// Gets the estimate material cost /// </summary> - float MaterialCost { get; } + float MaterialCost { get; set; } /// <summary> /// Gets the material name /// </summary> - string MaterialName { get; } + string MaterialName { get; set; } /// <summary> /// Gets the machine name /// </summary> - string MachineName { get; } + string MachineName { get; set; } /// <summary> /// Gets the GCode, returns null if not supported diff --git a/UVtools.Core/FileFormats/ImageFile.cs b/UVtools.Core/FileFormats/ImageFile.cs index 9517be0..3331d44 100644 --- a/UVtools.Core/FileFormats/ImageFile.cs +++ b/UVtools.Core/FileFormats/ImageFile.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using Emgu.CV; using Emgu.CV.CvEnum; using UVtools.Core.Operations; @@ -59,11 +58,11 @@ namespace UVtools.Core.FileFormats public override byte AntiAliasing { get; } = 1; public override float LayerHeight { get; set; } = 0; - public override float PrintTime { get; } = 0; + /*public override float PrintTime { get; } = 0; public override float UsedMaterial { get; } = 0; public override float MaterialCost { get; } = 0; public override string MaterialName { get; } = null; - public override string MachineName { get; } = null; + public override string MachineName { get; } = null;*/ public override object[] Configs { get; } = null; private Mat ImageMat { get; set; } diff --git a/UVtools.Core/FileFormats/LGSFile.cs b/UVtools.Core/FileFormats/LGSFile.cs index 82ec363..cf98191 100644 --- a/UVtools.Core/FileFormats/LGSFile.cs +++ b/UVtools.Core/FileFormats/LGSFile.cs @@ -207,8 +207,8 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Binary; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("lgs", "Longer Orange 10 Files"), - new FileExtension("lgs30", "Longer Orange 30 Files"), + new FileExtension("lgs", "Longer Orange 10"), + new FileExtension("lgs30", "Longer Orange 30"), }; public override Type[] ConvertToFormats { get; } = @@ -378,14 +378,14 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => 0; + /*public override float PrintTime => 0; public override float UsedMaterial => 0; public override float MaterialCost => 0; public override string MaterialName => "Unknown"; - public override string MachineName => null; + public override string MachineName => null;*/ public override object[] Configs => new object[] { HeaderSettings }; diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs index bb28b78..4f72f58 100644 --- a/UVtools.Core/FileFormats/PHZFile.cs +++ b/UVtools.Core/FileFormats/PHZFile.cs @@ -679,7 +679,7 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Binary; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("phz", "Chitubox PHZ Files"), + new FileExtension("phz", "Chitubox PHZ"), }; public override Type[] ConvertToFormats { get; } = @@ -903,15 +903,48 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => HeaderSettings.PrintTime; + public override float PrintTime + { + get => HeaderSettings.PrintTime; + set + { + HeaderSettings.PrintTime = (uint) value; + RaisePropertyChanged(); + } + } - public override float UsedMaterial => (float) Math.Round(HeaderSettings.VolumeMl, 2); + public override float UsedMaterial + { + get => (float) Math.Round(HeaderSettings.VolumeMl, 2); + set + { + HeaderSettings.VolumeMl = (float)Math.Round(value, 2); + RaisePropertyChanged(); + } + } - public override float MaterialCost => (float) Math.Round(HeaderSettings.CostDollars, 2); + public override float MaterialCost + { + get => (float) Math.Round(HeaderSettings.CostDollars, 2); + set + { + HeaderSettings.CostDollars = (float)Math.Round(value, 2); + RaisePropertyChanged(); + } + } + + public override string MachineName + { + get => HeaderSettings.MachineName; + set + { + HeaderSettings.MachineName = value; + HeaderSettings.MachineNameSize = (uint)HeaderSettings.MachineName.Length; + RequireFullEncode = true; + RaisePropertyChanged(); + } + } - public override string MaterialName => "Unknown"; - public override string MachineName => HeaderSettings.MachineName; - public override object[] Configs => new object[] { HeaderSettings }; #endregion diff --git a/UVtools.Core/FileFormats/PWSFile.cs b/UVtools.Core/FileFormats/PWSFile.cs index c055f63..7f27959 100644 --- a/UVtools.Core/FileFormats/PWSFile.cs +++ b/UVtools.Core/FileFormats/PWSFile.cs @@ -769,9 +769,9 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Binary; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("pws", "Photon Workshop PWS Files"), - new FileExtension("pw0", "Photon Workshop PW0 Files"), - new FileExtension("pwmx", "Photon Workshop PWMX Files") + new FileExtension("pws", "Photon Workshop PWS"), + new FileExtension("pw0", "Photon Workshop PW0"), + new FileExtension("pwmx", "Photon Workshop PWMX") }; public override Type[] ConvertToFormats { get; } = @@ -931,13 +931,26 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => 0; - - public override float UsedMaterial => (float) Math.Round(HeaderSettings.Volume, 2); + public override float UsedMaterial + { + get => (float) Math.Round(HeaderSettings.Volume, 2); + set + { + HeaderSettings.Volume = (float)Math.Round(value, 2); + RaisePropertyChanged(); + } + } - public override float MaterialCost => (float) Math.Round(HeaderSettings.Price, 2); + public override float MaterialCost + { + get => (float) Math.Round(HeaderSettings.Price, 2); + set + { + HeaderSettings.Price = (float)Math.Round(value, 2); + RaisePropertyChanged(); + } + } - public override string MaterialName => null; public override string MachineName => LayerFormat == LayerRleFormat.PWS ? "AnyCubic Photon S" : "AnyCubic Photon Zero"; public override object[] Configs => new object[] { FileMarkSettings, HeaderSettings, PreviewSettings, LayersDefinition }; diff --git a/UVtools.Core/FileFormats/PhotonSFile.cs b/UVtools.Core/FileFormats/PhotonSFile.cs index 1d092ef..f4a671a 100644 --- a/UVtools.Core/FileFormats/PhotonSFile.cs +++ b/UVtools.Core/FileFormats/PhotonSFile.cs @@ -162,7 +162,7 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Binary; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("photons", "Chitubox PhotonS Files"), + new FileExtension("photons", "Chitubox PhotonS"), }; public override Type[] ConvertToFormats { get; } = @@ -340,13 +340,17 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => 0; - public override float UsedMaterial => (float) HeaderSettings.Volume; - - public override float MaterialCost => 0; + public override float UsedMaterial + { + get => (float) HeaderSettings.Volume; + set + { + HeaderSettings.Volume = Math.Round(value, 2); + RaisePropertyChanged(); + } + } - public override string MaterialName => "Unknown"; public override string MachineName => "Anycubic Photon S"; public override object[] Configs => new object[] { HeaderSettings }; diff --git a/UVtools.Core/FileFormats/SL1File.cs b/UVtools.Core/FileFormats/SL1File.cs index 9584766..0034082 100644 --- a/UVtools.Core/FileFormats/SL1File.cs +++ b/UVtools.Core/FileFormats/SL1File.cs @@ -255,7 +255,7 @@ namespace UVtools.Core.FileFormats public string MaterialName { get; set; } public ushort NumFade { get; set; } public ushort NumFast { get; set; } - public byte NumSlow { get; set; } + public ushort NumSlow { get; set; } public string PrintProfile { get; set; } public float PrintTime { get; set; } public string PrinterModel { get; set; } @@ -289,15 +289,15 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Archive; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("sl1", "PrusaSlicer SL1 Files") + new FileExtension("sl1", "PrusaSlicer SL1") }; public override Type[] ConvertToFormats { get; } = { typeof(ChituboxFile), typeof(ChituboxZipFile), - typeof(PWSFile), typeof(PHZFile), + typeof(PWSFile), typeof(ZCodexFile), typeof(CWSFile), typeof(LGSFile), @@ -407,15 +407,47 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => OutputConfigSettings.PrintTime; + public override float PrintTime + { + get => OutputConfigSettings.PrintTime; + set + { + OutputConfigSettings.PrintTime = value; + RaisePropertyChanged(); + } + } - public override float UsedMaterial => OutputConfigSettings.UsedMaterial; + public override float UsedMaterial + { + get => OutputConfigSettings.UsedMaterial; + set + { + OutputConfigSettings.UsedMaterial = value; + RaisePropertyChanged(); + } + } public override float MaterialCost => (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.BottleCost / MaterialSettings.BottleVolume, 2); - public override string MaterialName => OutputConfigSettings.MaterialName; + public override string MaterialName + { + get => OutputConfigSettings.MaterialName; + set + { + OutputConfigSettings.MaterialName = value; + RaisePropertyChanged(); + } + } - public override string MachineName => PrinterSettings.PrinterSettingsId; + public override string MachineName + { + get => PrinterSettings.PrinterSettingsId; + set + { + PrinterSettings.PrinterSettingsId = value; + RaisePropertyChanged(); + } + } public override object[] Configs => new object[] { PrinterSettings, MaterialSettings, PrintSettings, OutputConfigSettings }; #endregion @@ -569,14 +601,18 @@ namespace UVtools.Core.FileFormats var fieldName = IniKeyToMemberName(keyValue[0]); bool foundMember = false; + foreach (var obj in Configs) { var attribute = obj.GetType().GetProperty(fieldName); if (ReferenceEquals(attribute, null)) continue; Helpers.SetPropertyValue(attribute, obj, keyValue[1]); + Statistics.ImplementedKeys.Add(keyValue[0]); foundMember = true; } + + if (!foundMember) { diff --git a/UVtools.Core/FileFormats/UVJFile.cs b/UVtools.Core/FileFormats/UVJFile.cs index 7baa21a..c9235c0 100644 --- a/UVtools.Core/FileFormats/UVJFile.cs +++ b/UVtools.Core/FileFormats/UVJFile.cs @@ -130,10 +130,19 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Archive; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("uvj", "UVJ Files") + new FileExtension("uvj", "UVJ") }; - public override Type[] ConvertToFormats { get; } = null; + public override Type[] ConvertToFormats { get; } = + { + typeof(ChituboxFile), + typeof(ChituboxZipFile), + typeof(PHZFile), + typeof(PWSFile), + typeof(ZCodexFile), + typeof(CWSFile), + //typeof(LGSFile) + }; public override PrintParameterModifier[] PrintParameterModifiers { get; } = { PrintParameterModifier.BottomLayerCount, @@ -340,16 +349,6 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => 0; - - public override float UsedMaterial => 0; - - public override float MaterialCost => 0; - - public override string MaterialName => null; - - public override string MachineName => null; - public override object[] Configs => new[] {(object) JsonSettings.Properties.Size, JsonSettings.Properties.Size.Millimeter, JsonSettings.Properties.Bottom, JsonSettings.Properties.Exposure}; #endregion @@ -456,7 +455,9 @@ namespace UVtools.Core.FileFormats { using (Stream stream = entry.Open()) { - CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, Thumbnails[0]); + Mat image = new Mat(); + CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); + Thumbnails[0] = image; stream.Close(); } } @@ -466,7 +467,9 @@ namespace UVtools.Core.FileFormats { using (Stream stream = entry.Open()) { - CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, Thumbnails[1]); + Mat image = new Mat(); + CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); + Thumbnails[1] = image; stream.Close(); } } @@ -523,7 +526,315 @@ namespace UVtools.Core.FileFormats public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) { - throw new NotImplementedException(); + if (to == typeof(ChituboxFile)) + { + ChituboxFile file = new ChituboxFile + { + + LayerManager = LayerManager, + HeaderSettings + = + { + BedSizeX = DisplayWidth, + BedSizeY = DisplayHeight, + BedSizeZ = TotalHeight, + OverallHeightMilimeter = TotalHeight, + BottomExposureSeconds = BottomExposureTime, + BottomLayersCount = BottomLayerCount, + BottomLightPWM = BottomLightPWM, + LayerCount = LayerCount, + LayerExposureSeconds = ExposureTime, + LayerHeightMilimeter = LayerHeight, + LayerOffTime = LayerOffTime, + LightPWM = LightPWM, + PrintTime = (uint) PrintTimeOrComputed, + ProjectorType = 0, + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + AntiAliasLevel = ValidateAntiAliasingLevel() + }, + PrintParametersSettings = + { + BottomLayerCount = BottomLayerCount, + BottomLiftHeight = BottomLiftHeight, + BottomLiftSpeed = BottomLiftSpeed, + BottomLightOffDelay = BottomLayerOffTime, + CostDollars = MaterialCost, + LiftHeight = LiftHeight, + LiftSpeed = LiftSpeed, + LightOffDelay = LayerOffTime, + RetractSpeed = RetractSpeed, + VolumeMl = UsedMaterial, + WeightG = 0 + }, + SlicerInfoSettings = { MachineName = MachineName, MachineNameSize = (uint)MachineName.Length } + }; + + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ChituboxZipFile)) + { + ChituboxZipFile file = new ChituboxZipFile + { + LayerManager = LayerManager, + HeaderSettings = + { + Filename = Path.GetFileName(FileFullPath), + + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + MachineX = DisplayWidth, + MachineY = DisplayHeight, + MachineZ = TotalHeight, + MachineType = MachineName, + ProjectType = "Normal", + + Resin = MaterialName, + Price = MaterialCost, + Weight = 0, + Volume = UsedMaterial, + Mirror = 0, + + + BottomLiftHeight = BottomLiftHeight, + LiftHeight = LiftHeight, + BottomLiftSpeed = BottomLiftSpeed, + LiftSpeed = LiftSpeed, + RetractSpeed = RetractSpeed, + BottomLayCount = BottomLayerCount, + BottomLayerCount = BottomLayerCount, + BottomLightOffTime = BottomLayerOffTime, + LightOffTime = LayerOffTime, + BottomLayExposureTime = BottomExposureTime, + BottomLayerExposureTime = BottomExposureTime, + LayerExposureTime = ExposureTime, + LayerHeight = LayerHeight, + LayerCount = LayerCount, + AntiAliasing = ValidateAntiAliasingLevel(), + BottomLightPWM = BottomLightPWM, + LightPWM = LightPWM, + + EstimatedPrintTime = PrintTime + }, + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(PHZFile)) + { + PHZFile file = new PHZFile + { + LayerManager = LayerManager, + HeaderSettings = + { + Version = 2, + BedSizeX = DisplayWidth, + BedSizeY = DisplayHeight, + BedSizeZ = TotalHeight, + OverallHeightMilimeter = TotalHeight, + BottomExposureSeconds = BottomExposureTime, + BottomLayersCount = BottomLayerCount, + BottomLightPWM = BottomLightPWM, + LayerCount = LayerCount, + LayerExposureSeconds = ExposureTime, + LayerHeightMilimeter = LayerHeight, + LayerOffTime = LayerOffTime, + LightPWM = LightPWM, + PrintTime = (uint) PrintTimeOrComputed, + ProjectorType = 0, + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + BottomLayerCount = BottomLayerCount, + BottomLiftHeight = BottomLiftHeight, + BottomLiftSpeed = BottomLiftSpeed, + BottomLightOffDelay = BottomLayerOffTime, + CostDollars = MaterialCost, + LiftHeight = LiftHeight, + LiftSpeed = LiftSpeed, + RetractSpeed = RetractSpeed, + VolumeMl = UsedMaterial, + AntiAliasLevelInfo = ValidateAntiAliasingLevel(), + WeightG = 0, + MachineName = MachineName, + MachineNameSize = (uint)MachineName.Length + } + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(PWSFile)) + { + PWSFile file = new PWSFile + { + LayerManager = LayerManager, + HeaderSettings = + { + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + LayerHeight = LayerHeight, + LayerExposureTime = ExposureTime, + LiftHeight = LiftHeight, + LiftSpeed = LiftSpeed / 60, + RetractSpeed = RetractSpeed / 60, + LayerOffTime = LayerOffTime, + BottomLayersCount = BottomLayerCount, + BottomExposureSeconds = BottomExposureTime, + Price = MaterialCost, + Volume = UsedMaterial, + Weight = 0, + AntiAliasing = ValidateAntiAliasingLevel() + } + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ZCodexFile)) + { + TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); + ZCodexFile file = new ZCodexFile + { + ResinMetadataSettings = new ZCodexFile.ResinMetadata + { + MaterialId = 2, + Material = MaterialName, + AdditionalSupportLayerTime = 0, + BottomLayersNumber = BottomLayerCount, + BottomLayersTime = (uint)(BottomExposureTime * 1000), + LayerTime = (uint)(ExposureTime * 1000), + DisableSettingsChanges = false, + LayerThickness = LayerHeight, + PrintTime = (uint)PrintTime, + TotalLayersCount = LayerCount, + TotalMaterialVolumeUsed = UsedMaterial, + TotalMaterialWeightUsed = UsedMaterial, + }, + UserSettings = new ZCodexFile.UserSettingsdata + { + Printer = MachineName, + BottomLayersCount = BottomLayerCount, + PrintTime = $"{ts.Hours}h {ts.Minutes}m", + LayerExposureTime = (uint)(ExposureTime * 1000), + BottomLayerExposureTime = (uint)(BottomExposureTime * 1000), + MaterialId = 2, + LayerThickness = $"{LayerHeight} mm", + AntiAliasing = (byte)(AntiAliasing > 1 ? 1 : 0), + CrossSupportEnabled = 1, + ExposureOffTime = (uint)LayerOffTime, + HollowEnabled = 0, + HollowThickness = 0, + InfillDensity = 0, + IsAdvanced = 0, + MaterialType = MaterialName, + MaterialVolume = UsedMaterial, + MaxLayer = LayerCount - 1, + ModelLiftEnabled = 0, + ModelLiftHeight = 0, + RaftEnabled = 0, + RaftHeight = 0, + RaftOffset = 0, + SupportAdditionalExposureEnabled = 0, + SupportAdditionalExposureTime = 0, + XCorrection = 0, + YCorrection = 0, + ZLiftDistance = LiftHeight, + ZLiftFeedRate = LiftSpeed, + ZLiftRetractRate = RetractSpeed, + }, + ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata + { + PrintTime = (uint)PrintTime, + PrinterName = MachineName, + Materials = new List<ZCodexFile.ZCodeMetadata.MaterialsData> + { + new ZCodexFile.ZCodeMetadata.MaterialsData + { + Name = MaterialName, + ExtruderType = "MAIN", + Id = 0, + Usage = 0, + Temperature = 0 + } + }, + }, + LayerManager = LayerManager + }; + + float usedMaterial = UsedMaterial / LayerCount; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData + { + Layer = layerIndex, + UsedMaterialVolume = usedMaterial + }); + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + return true; + } + + if (to == typeof(CWSFile)) + { + CWSFile defaultFormat = (CWSFile)FindByType(typeof(CWSFile)); + CWSFile file = new CWSFile { LayerManager = LayerManager }; + + file.SliceSettings.Xppm = file.OutputSettings.PixPermmX = (float)Math.Round(ResolutionX / DisplayWidth, 3); + file.SliceSettings.Yppm = file.OutputSettings.PixPermmY = (float)Math.Round(ResolutionY / DisplayHeight, 3); + file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort)ResolutionX; + file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort)ResolutionY; + file.SliceSettings.Thickness = file.OutputSettings.LayerThickness = LayerHeight; + file.SliceSettings.LayersNum = file.OutputSettings.LayersNum = LayerCount; + file.SliceSettings.HeadLayersNum = file.OutputSettings.NumberBottomLayers = BottomLayerCount; + file.SliceSettings.LayersExpoMs = file.OutputSettings.LayerTime = (uint)ExposureTime * 1000; + file.SliceSettings.HeadLayersExpoMs = file.OutputSettings.BottomLayersTime = (uint)BottomExposureTime * 1000; + file.SliceSettings.WaitBeforeExpoMs = (uint)(LayerOffTime * 1000); + file.SliceSettings.LiftDistance = file.OutputSettings.LiftDistance = LiftHeight; + file.SliceSettings.LiftUpSpeed = file.OutputSettings.ZLiftFeedRate = LiftSpeed; + file.SliceSettings.LiftDownSpeed = file.OutputSettings.ZLiftRetractRate = RetractSpeed; + file.SliceSettings.LiftWhenFinished = defaultFormat.SliceSettings.LiftWhenFinished; + + file.OutputSettings.BlankingLayerTime = (uint)(LayerOffTime * 1000); + //file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.OutlineWidthInset = 0; + //file.OutputSettings.OutlineWidthOutset = 0; + file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.TiltValue = 0; + //file.OutputSettings.UseMainliftGCodeTab = false; + //file.OutputSettings.AntiAliasing = 0; + //file.OutputSettings.AntiAliasingValue = 0; + file.OutputSettings.FlipX = false; + file.OutputSettings.FlipY = file.OutputSettings.FlipX; + file.OutputSettings.AntiAliasingValue = ValidateAntiAliasingLevel(); + file.OutputSettings.AntiAliasing = file.OutputSettings.AntiAliasingValue > 1; + + file.Printer = MachineName.Contains("Bene4 Mono") || + FileFullPath.Contains("bene4_mono") + ? CWSFile.PrinterType.BeneMono : CWSFile.PrinterType.Elfin; + + file.Encode(fileFullPath, progress); + + return true; + } + + return false; } #endregion diff --git a/UVtools.Core/FileFormats/ZCodexFile.cs b/UVtools.Core/FileFormats/ZCodexFile.cs index 682d3f0..944a694 100644 --- a/UVtools.Core/FileFormats/ZCodexFile.cs +++ b/UVtools.Core/FileFormats/ZCodexFile.cs @@ -148,7 +148,7 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Archive; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("zcodex", "Z-Suite ZCodex Files") + new FileExtension("zcodex", "Z-Suite ZCodex") }; public override Type[] ConvertToFormats { get; } = null; @@ -281,15 +281,45 @@ namespace UVtools.Core.FileFormats } } - public override float PrintTime => ResinMetadataSettings.PrintTime; - - public override float UsedMaterial => ResinMetadataSettings.TotalMaterialVolumeUsed; + public override float PrintTime + { + get => ResinMetadataSettings.PrintTime; + set + { + ResinMetadataSettings.PrintTime = (uint) value; + RaisePropertyChanged(); + } + } - public override float MaterialCost => 0; + public override float UsedMaterial + { + get => ResinMetadataSettings.TotalMaterialVolumeUsed; + set + { + ResinMetadataSettings.TotalMaterialVolumeUsed = (float) Math.Round(value, 2); + RaisePropertyChanged(); + } + } - public override string MaterialName => ResinMetadataSettings.Material; + public override string MaterialName + { + get => ResinMetadataSettings.Material; + set + { + ResinMetadataSettings.Material = value; + RaisePropertyChanged(); + } + } - public override string MachineName => ZCodeMetadataSettings.PrinterName; + public override string MachineName + { + get => ZCodeMetadataSettings.PrinterName; + set + { + ZCodeMetadataSettings.PrinterName = value; + RaisePropertyChanged(); + } + } public override object[] Configs => new[] {(object) ResinMetadataSettings, UserSettings, ZCodeMetadataSettings}; #endregion diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs index cb5bf2b..d75b2bd 100644 --- a/UVtools.Core/Layer/Layer.cs +++ b/UVtools.Core/Layer/Layer.cs @@ -730,6 +730,20 @@ namespace UVtools.Core for (int i = 0; i < contours.Size; i++) { if ((int)arr.GetValue(0, i, 2) != -1 || (int)arr.GetValue(0, i, 3) == -1) continue; + if (operation.MinimumArea > 1) + { + var rectangle = CvInvoke.BoundingRectangle(contours[i]); + if (operation.AreaCheckType == OperationSolidify.AreaCheckTypes.More) + { + if (rectangle.GetArea() < operation.MinimumArea) continue; + } + else + { + if (rectangle.GetArea() > operation.MinimumArea) continue; + } + + } + CvInvoke.DrawContours(target, contours, i, new MCvScalar(255), -1); } } @@ -751,7 +765,7 @@ namespace UVtools.Core } } - public void MutatePixelDimming(Matrix<byte> evenPattern = null, Matrix<byte> oddPattern = null, ushort borderSize = 5) + /*public void MutatePixelDimming(Matrix<byte> evenPattern = null, Matrix<byte> oddPattern = null, ushort borderSize = 5) { var anchor = new Point(-1, -1); var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); @@ -808,16 +822,16 @@ namespace UVtools.Core } } } - } + }*/ - public void PixelDimming(OperationPixelDimming operation, Mat evenPatternMask, Mat oddPatternMask = null) + public void PixelDimming(OperationPixelDimming operation, Mat patternMask, Mat alternatePatternMask = null) { var anchor = new Point(-1, -1); var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - if (ReferenceEquals(oddPatternMask, null)) + if (ReferenceEquals(alternatePatternMask, null)) { - oddPatternMask = evenPatternMask; + alternatePatternMask = patternMask; } using (Mat dst = LayerMat) @@ -826,16 +840,18 @@ namespace UVtools.Core { Mat target = operation.GetRoiOrDefault(dst); - CvInvoke.Erode(target, erode, kernel, anchor, (int) operation.BorderSize, BorderType.Reflect101, default); + CvInvoke.Erode(target, erode, kernel, anchor, (int) operation.WallThickness, BorderType.Reflect101, default); CvInvoke.Subtract(target, erode, diff); - if (operation.BordersOnly) + + + if (operation.WallsOnly) { - CvInvoke.BitwiseAnd(diff, Index % 2 == 0 ? evenPatternMask : oddPatternMask, target); + CvInvoke.BitwiseAnd(diff, operation.IsNormalPattern(Index) ? patternMask : alternatePatternMask, target); CvInvoke.Add(erode, target, target); } else { - CvInvoke.BitwiseAnd(erode, Index % 2 == 0 ? evenPatternMask : oddPatternMask, target); + CvInvoke.BitwiseAnd(erode, operation.IsNormalPattern(Index) ? patternMask : alternatePatternMask, target); CvInvoke.Add(target, diff, target); } @@ -843,6 +859,210 @@ namespace UVtools.Core } } + public void Infill(OperationInfill operation) + { + var anchor = new Point(-1, -1); + var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + + uint layerIndex = Index - operation.LayerIndexStart; + var infillColor = new MCvScalar(operation.InfillBrightness); + + Mat patternMask = null; + using (Mat dst = LayerMat) + using (Mat erode = new Mat()) + using (Mat diff = new Mat()) + { + Mat target = operation.GetRoiOrDefault(dst); + + /*if (operation.InfillType == OperationInfill.InfillAlgorithm.Rhombus) + { + const double rotationAngle = 55; + patternMask = target.CloneBlank(); + int offsetLayerPos = (int)(layerIndex % operation.InfillSpacing); + var pivot = new Point( operation.InfillThickness / 2, operation.InfillThickness / 2); + + Point[] points1 = { + new Point(operation.InfillThickness / 4, operation.InfillThickness / 2), // Left + new Point(operation.InfillThickness / 2, 0), // Top + new Point((int) (operation.InfillThickness / 1.25), operation.InfillThickness / 2), // Right + new Point(operation.InfillThickness / 2, operation.InfillThickness) // Bottom + }; + var vec1 = new VectorOfPoint(points1); + + + Point[] points2 = { + points1[0].Rotate(rotationAngle, pivot), + points1[1].Rotate(rotationAngle, pivot), + points1[2].Rotate(rotationAngle, pivot), + points1[3].Rotate(rotationAngle, pivot), + }; + var vec2 = new VectorOfPoint(points2); + + Point[] points3 = { + points1[0].Rotate(-rotationAngle, pivot), + points1[1].Rotate(-rotationAngle, pivot), + points1[2].Rotate(-rotationAngle, pivot), + points1[3].Rotate(-rotationAngle, pivot), + }; + var vec3 = new VectorOfPoint(points3); + + + /*int halfPos = (operation.InfillThickness + offsetPos) / 2; + + Point[] points = { + new Point(0+offsetPos, halfPos), // Left + new Point(halfPos, 0+offsetPos), // Top + new Point(operation.InfillThickness+offsetPos, halfPos), // Right + new Point(halfPos, operation.InfillThickness+offsetPos) // Bottom + }; */ + /* for (int y = 0; y < patternMask.Height; y += operation.InfillSpacing) + { + for (int x = 0; x < patternMask.Width; x+=operation.InfillSpacing) + { + CvInvoke.FillPoly(patternMask, vec1, infillColor, LineType.EightConnected, default, new Point(x, y+offsetLayerPos)); + CvInvoke.FillPoly(patternMask, vec2, infillColor, LineType.EightConnected, default, new Point(x- offsetLayerPos, y+ offsetLayerPos)); + CvInvoke.FillPoly(patternMask, vec3, infillColor, LineType.EightConnected, default, new Point(x+ offsetLayerPos, y+ offsetLayerPos)); + } + } + + patternMask.Save("D:\\mask.png"); + + + } + else*/ if (operation.InfillType == OperationInfill.InfillAlgorithm.CubicSimple || + operation.InfillType == OperationInfill.InfillAlgorithm.CubicCenterLink || + operation.InfillType == OperationInfill.InfillAlgorithm.CubicInterlinked) + { + using (var infillPattern = EmguExtensions.InitMat(new Size(operation.InfillSpacing, operation.InfillSpacing))) + using (Mat matPattern = dst.CloneBlank()) + { + bool firstPattern = true; + uint accumulator = 0; + uint step = 0; + while (accumulator < layerIndex) + { + firstPattern = true; + accumulator += operation.InfillSpacing; + + if (accumulator >= layerIndex) break; + firstPattern = false; + accumulator += operation.InfillThickness; + } + + if (firstPattern) + { + int thickness = operation.InfillThickness / 2; + // Top Left + CvInvoke.Rectangle(infillPattern, + new Rectangle(0, 0, thickness, thickness), + infillColor, -1); + + // Top Right + CvInvoke.Rectangle(infillPattern, + new Rectangle(infillPattern.Width - thickness, 0, thickness, thickness), + infillColor, -1); + + // Bottom Left + CvInvoke.Rectangle(infillPattern, + new Rectangle(0, infillPattern.Height - thickness, thickness, thickness), + infillColor, -1); + + // Bottom Right + CvInvoke.Rectangle(infillPattern, + new Rectangle(infillPattern.Width - thickness, infillPattern.Height - thickness, + thickness, thickness), + infillColor, -1); + + if (operation.InfillType == OperationInfill.InfillAlgorithm.CubicCenterLink || + operation.InfillType == OperationInfill.InfillAlgorithm.CubicInterlinked) + { + // Center cross + int margin = (int) (operation.InfillSpacing - accumulator + layerIndex) - thickness; + int marginInv = (int) (accumulator - layerIndex) - thickness; + CvInvoke.Rectangle(infillPattern, + new Rectangle(margin, margin, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(marginInv, marginInv, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(margin, marginInv, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(marginInv, margin, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + if (operation.InfillType == OperationInfill.InfillAlgorithm.CubicInterlinked) + { + CvInvoke.Rectangle(infillPattern, + new Rectangle(margin, -thickness, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(marginInv, -thickness, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(-thickness, margin, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(-thickness, marginInv, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(operation.InfillSpacing - thickness, margin, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(operation.InfillSpacing - thickness, marginInv, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(margin, operation.InfillSpacing - thickness, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(marginInv, operation.InfillSpacing - thickness, operation.InfillThickness, operation.InfillThickness), + infillColor, -1); + } + } + + + } + else + { + CvInvoke.Rectangle(infillPattern, + new Rectangle(0, 0, operation.InfillSpacing, operation.InfillSpacing), + infillColor, operation.InfillThickness); + } + + + { + CvInvoke.Repeat(infillPattern, target.Rows / infillPattern.Rows + 1, + target.Cols / infillPattern.Cols + 1, matPattern); + patternMask = new Mat(matPattern, new Rectangle(0, 0, target.Width, target.Height)); + } + } + } + + + CvInvoke.Erode(target, erode, kernel, anchor, operation.WallThickness, BorderType.Reflect101, + default); + CvInvoke.Subtract(target, erode, diff); + + + CvInvoke.BitwiseAnd(erode, patternMask, target); + CvInvoke.Add(target, diff, target); + patternMask?.Dispose(); + + LayerMat = dst; + } + } + public void Morph(OperationMorph operation, int iterations = 1, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) { if (iterations == 0) @@ -1033,6 +1253,6 @@ namespace UVtools.Core #endregion - + } } diff --git a/UVtools.Core/Layer/LayerIssue.cs b/UVtools.Core/Layer/LayerIssue.cs index 3c1dfdd..58c086e 100644 --- a/UVtools.Core/Layer/LayerIssue.cs +++ b/UVtools.Core/Layer/LayerIssue.cs @@ -5,16 +5,18 @@ * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ + +using System; using System.Collections; using System.Collections.Generic; using System.Drawing; -using System.Runtime.CompilerServices; +using System.Linq; namespace UVtools.Core { #region LayerIssue Class - public class IssuesDetectionConfiguration + public sealed class IssuesDetectionConfiguration { public IslandDetectionConfiguration IslandConfig { get; } public OverhangDetectionConfiguration OverhangConfig { get; } @@ -32,7 +34,7 @@ namespace UVtools.Core } } - public class IslandDetectionConfiguration + public sealed class IslandDetectionConfiguration { /// <summary> /// Gets or sets if the detection is enabled @@ -93,7 +95,7 @@ namespace UVtools.Core /// <summary> /// Overhang configuration /// </summary> - public class OverhangDetectionConfiguration + public sealed class OverhangDetectionConfiguration { /// <summary> /// Gets or sets if the detection is enabled @@ -128,7 +130,7 @@ namespace UVtools.Core } } - public class ResinTrapDetectionConfiguration + public sealed class ResinTrapDetectionConfiguration { /// <summary> /// Gets or sets if the detection is enabled @@ -163,7 +165,7 @@ namespace UVtools.Core } - public class TouchingBoundDetectionConfiguration + public sealed class TouchingBoundDetectionConfiguration { /// <summary> /// Gets if the detection is enabled @@ -171,9 +173,30 @@ namespace UVtools.Core public bool Enabled { get; set; } = true; /// <summary> - /// Gets the maximum pixel brightness to be a touching bound + /// Gets the minimum pixel brightness to be a touching bound + /// </summary> + public byte MinimumPixelBrightness { get; set; } = 127; + + /// <summary> + /// Gets or sets the margin in pixels from left edge to check for touching white pixels + /// </summary> + public byte MarginLeft { get; set; } = 5; + + /// <summary> + /// Gets or sets the margin in pixels from top to check for touching white pixels + /// </summary> + public byte MarginTop { get; set; } = 5; + + /// <summary> + /// Gets or sets the margin in pixels from right edge to check for touching white pixels + /// </summary> + public byte MarginRight { get; set; } = 5; + + /// <summary> + /// Gets or sets the margin in pixels from bottom edge to check for touching white pixels /// </summary> - public byte MaximumPixelBrightness { get; set; } = 200; + public byte MarginBottom { get; set; } = 5; + public TouchingBoundDetectionConfiguration(bool enabled = true) { @@ -182,7 +205,7 @@ namespace UVtools.Core } - public class LayerIssue : IEnumerable<Point> + public class LayerIssue : IEquatable<LayerIssue>, IEnumerable<Point> { public enum IssueType : byte { @@ -285,6 +308,38 @@ namespace UVtools.Core { return $"{nameof(Type)}: {Type}, Layer: {Layer.Index}, {nameof(X)}: {X}, {nameof(Y)}: {Y}, {nameof(Size)}: {Size}"; } + + public bool Equals(LayerIssue other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Layer.Index == other.Layer.Index + && Type == other.Type + && PixelsCount == other.PixelsCount + && !(Pixels is null) && !(other.Pixels is null) && Pixels.SequenceEqual(other.Pixels) + //&& BoundingRectangle.Equals(other.BoundingRectangle) + ; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((LayerIssue) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Layer != null ? Layer.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (int) Type; + hashCode = (hashCode * 397) ^ (Pixels != null ? Pixels.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ BoundingRectangle.GetHashCode(); + return hashCode; + } + } } #endregion diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs index 06f2a72..02e4988 100644 --- a/UVtools.Core/Layer/LayerManager.cs +++ b/UVtools.Core/Layer/LayerManager.cs @@ -426,17 +426,17 @@ namespace UVtools.Core if (progress is null) progress = new OperationProgress(); progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - if (operation.EvenPattern is null) + if (operation.Pattern is null) { - operation.EvenPattern = new Matrix<byte>(2, 2) + operation.Pattern = new Matrix<byte>(2, 2) { [0, 0] = 127, [0, 1] = 255, [1, 0] = 255, [1, 1] = 127, }; - if (operation.OddPattern is null) + if (operation.AlternatePattern is null) { - operation.OddPattern = new Matrix<byte>(2, 2) + operation.AlternatePattern = new Matrix<byte>(2, 2) { [0, 0] = 255, [0, 1] = 127, [1, 0] = 127, [1, 1] = 255, @@ -444,29 +444,29 @@ namespace UVtools.Core } } - if (operation.OddPattern is null) + if (operation.AlternatePattern is null) { - operation.OddPattern = operation.EvenPattern; + operation.AlternatePattern = operation.Pattern; } using (Mat mat = this[0].LayerMat) - using (Mat matEven = mat.CloneBlank()) - using (Mat matOdd = mat.CloneBlank()) + using (Mat matPattern = mat.CloneBlank()) + using (Mat matAlternatePattern = mat.CloneBlank()) { Mat target = operation.GetRoiOrDefault(mat); - CvInvoke.Repeat(operation.EvenPattern, target.Rows / operation.EvenPattern.Rows + 1, - target.Cols / operation.EvenPattern.Cols + 1, matEven); - CvInvoke.Repeat(operation.OddPattern, target.Rows / operation.OddPattern.Rows + 1, - target.Cols / operation.OddPattern.Cols + 1, matOdd); + CvInvoke.Repeat(operation.Pattern, target.Rows / operation.Pattern.Rows + 1, + target.Cols / operation.Pattern.Cols + 1, matPattern); + CvInvoke.Repeat(operation.AlternatePattern, target.Rows / operation.AlternatePattern.Rows + 1, + target.Cols / operation.AlternatePattern.Cols + 1, matAlternatePattern); - using (var evenPatternMask = new Mat(matEven, new Rectangle(0, 0, target.Width, target.Height))) - using (var oddPatternMask = new Mat(matOdd, new Rectangle(0, 0, target.Width, target.Height))) + using (var patternMask = new Mat(matPattern, new Rectangle(0, 0, target.Width, target.Height))) + using (var alternatePatternMask = new Mat(matAlternatePattern, new Rectangle(0, 0, target.Width, target.Height))) { Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - this[layerIndex].PixelDimming(operation, evenPatternMask, oddPatternMask); + this[layerIndex].PixelDimming(operation, patternMask, alternatePatternMask); lock (progress.Mutex) { progress++; @@ -478,6 +478,22 @@ namespace UVtools.Core progress.Token.ThrowIfCancellationRequested(); } + public void Infill(OperationInfill operation, OperationProgress progress) + { + if (progress is null) progress = new OperationProgress(); + progress.Reset(operation.ProgressAction, operation.LayerRangeCount); + + Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + this[layerIndex].Infill(operation); + lock (progress.Mutex) + { + progress++; + } + }); + } + private void MutateGetVarsIterationFade(uint startLayerIndex, uint endLayerIndex, int iterationsStart, int iterationsEnd, ref bool isFade, out float iterationSteps, out int maxIteration) { iterationSteps = 0; @@ -780,19 +796,30 @@ namespace UVtools.Core ResinTrapDetectionConfiguration resinTrapConfig = null, TouchingBoundDetectionConfiguration touchBoundConfig = null, bool emptyLayersConfig = true, + List<LayerIssue> ignoredIssues = null, OperationProgress progress = null) { - if(islandConfig is null) islandConfig = new IslandDetectionConfiguration(); + + if (islandConfig is null) islandConfig = new IslandDetectionConfiguration(); if(overhangConfig is null) overhangConfig = new OverhangDetectionConfiguration(); if(resinTrapConfig is null) resinTrapConfig = new ResinTrapDetectionConfiguration(); if(touchBoundConfig is null) touchBoundConfig = new TouchingBoundDetectionConfiguration(); if(progress is null) progress = new OperationProgress(); - + var result = new ConcurrentBag<LayerIssue>(); var layerHollowAreas = new ConcurrentDictionary<uint, List<LayerHollowArea>>(); bool islandsFinished = false; + bool IsIgnored(LayerIssue issue) => !(ignoredIssues is null) && ignoredIssues.Count > 0 && ignoredIssues.Contains(issue); + + bool AddIssue(LayerIssue issue) + { + if (IsIgnored(issue)) return false; + result.Add(issue); + return true; + } + progress.Reset(OperationProgress.StatusIslands, Count); Parallel.Invoke(() => @@ -813,7 +840,7 @@ namespace UVtools.Core { if (emptyLayersConfig) { - result.Add(new LayerIssue(layer, LayerIssue.IssueType.EmptyLayer)); + AddIssue(new LayerIssue(layer, LayerIssue.IssueType.EmptyLayer)); } lock (progress.Mutex) @@ -851,41 +878,75 @@ namespace UVtools.Core { // TouchingBounds Checker List<Point> pixels = new List<Point>(); - for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds + bool touchTop = layer.BoundingRectangle.Top <= touchBoundConfig.MarginTop; + bool touchBottom = layer.BoundingRectangle.Bottom >= image.Height - touchBoundConfig.MarginBottom; + bool touchLeft = layer.BoundingRectangle.Left <= touchBoundConfig.MarginLeft; + bool touchRight = layer.BoundingRectangle.Right >= image.Width - touchBoundConfig.MarginRight; + if (touchTop || touchBottom) { - if (span[x] >= touchBoundConfig.MaximumPixelBrightness) // Top + for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds { - pixels.Add(new Point(x, 0)); - } + if (touchTop) + { + for (int y = 0; y < touchBoundConfig.MarginTop; y++) // Top + { + if (span[image.GetPixelPos(x, y)] >= + touchBoundConfig.MinimumPixelBrightness) + { + pixels.Add(new Point(x, y)); + } + } + } + + if (touchBottom) + { + for (int y = image.Height - touchBoundConfig.MarginBottom; y < image.Height; y++) // Bottom + { + if (span[image.GetPixelPos(x, y)] >= + touchBoundConfig.MinimumPixelBrightness) + { + pixels.Add(new Point(x, y)); + } + } + } - if (span[step * image.Height - step + x] >= - touchBoundConfig.MaximumPixelBrightness) // Bottom - { - pixels.Add(new Point(x, image.Height - 1)); } } - for (int y = 0; y < image.Height; y++) // Check Left and Right bounds + if (touchLeft || touchRight) { - if (span[y * step] >= touchBoundConfig.MaximumPixelBrightness) // Left + for (int y = touchBoundConfig.MarginTop; y < image.Height - touchBoundConfig.MarginBottom; y++) // Check Left and Right bounds { - pixels.Add(new Point(0, y)); - } + if (touchLeft) + { + for (int x = 0; x < touchBoundConfig.MarginLeft; x++) // Left + { + if (span[image.GetPixelPos(x, y)] >= + touchBoundConfig.MinimumPixelBrightness) + { + pixels.Add(new Point(x, y)); + } + } + } - if (span[y * step + step - 1] >= touchBoundConfig.MaximumPixelBrightness) // Right - { - pixels.Add(new Point(step - 1, y)); + if (touchRight) + { + for (int x = image.Width - touchBoundConfig.MarginRight; x < image.Width; x++) // Right + { + if (span[image.GetPixelPos(x, y)] >= + touchBoundConfig.MinimumPixelBrightness) + { + pixels.Add(new Point(x, y)); + } + } + } } } if (pixels.Count > 0) { - result.Add(new LayerIssue(layer, LayerIssue.IssueType.TouchingBound, + AddIssue(new LayerIssue(layer, LayerIssue.IssueType.TouchingBound, pixels.ToArray())); - /*result.TryAdd(layer.Index, new List<LayerIssue> - { - new LayerIssue(layer, LayerIssue.IssueType.TouchingBound, pixels.ToArray()) - });*/ } } @@ -998,7 +1059,7 @@ namespace UVtools.Core if (pixelsSupportingIsland < requiredSupportingPixels) { - result.Add(new LayerIssue(layer, LayerIssue.IssueType.Island, + AddIssue(new LayerIssue(layer, LayerIssue.IssueType.Island, points.ToArray(), rect)); } @@ -1036,7 +1097,7 @@ namespace UVtools.Core if (points.Count >= overhangConfig.RequiredPixelsToConsider) { - result.Add(new LayerIssue( + AddIssue(new LayerIssue( layer, LayerIssue.IssueType.Overhang, points.ToArray(), rect )); } @@ -1091,7 +1152,7 @@ namespace UVtools.Core if (vecPoints.Size >= overhangConfig.RequiredPixelsToConsider) { //subtractedImage.Save("D:\\subtracted_image\\subtracted_erroded.png"); - result.Add(new LayerIssue( + AddIssue(new LayerIssue( layer, LayerIssue.IssueType.Overhang, vecPoints.ToArray(), layer.BoundingRectangle )); } @@ -1367,7 +1428,7 @@ namespace UVtools.Core where area.Type == LayerHollowArea.AreaType.Trap select new LayerIssue(this[layerIndex], LayerIssue.IssueType.ResinTrap, area.Contour, area.BoundingRectangle)) { - result.Add(issue); + AddIssue(issue); } } @@ -2077,6 +2138,7 @@ namespace UVtools.Core #endregion + } } diff --git a/UVtools.Core/Operations/OperationInfill.cs b/UVtools.Core/Operations/OperationInfill.cs new file mode 100644 index 0000000..7f0ec58 --- /dev/null +++ b/UVtools.Core/Operations/OperationInfill.cs @@ -0,0 +1,94 @@ +/* + * 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; + +namespace UVtools.Core.Operations +{ + [Serializable] + public sealed class OperationInfill : Operation + { + private InfillAlgorithm _infillType = InfillAlgorithm.CubicCenterLink; + private ushort _wallThickness = 64; + private ushort _infillThickness = 45; + private ushort _infillSpacing = 160; + private ushort _infillBrightness = 255; + + #region Overrides + + public override string Title => "Infill"; + + public override string Description => + $"Generate infill patterns in the model\n\nNOTES:\n1) You must exclude floor and ceil layers from the range.\n2) You must take care of drain holes after the operation."; + + public override string ConfirmationText => + $"infill model with {InfillType} from layers {LayerIndexStart} through {LayerIndexEnd}?"; + + public override string ProgressTitle => + $"Infill model with {InfillType} from layers {LayerIndexStart} through {LayerIndexEnd}"; + + public override string ProgressAction => "Infilled layers"; + + #endregion + + #region Enums + public enum InfillAlgorithm + { + //Rhombus, + CubicSimple, + CubicCenterLink, + CubicInterlinked, + } + #endregion + + #region Properties + public static Array InfillAlgorithmTypes => Enum.GetValues(typeof(InfillAlgorithm)); + public InfillAlgorithm InfillType + { + get => _infillType; + set => RaiseAndSetIfChanged(ref _infillType, value); + } + + public ushort WallThickness + { + get => _wallThickness; + set => RaiseAndSetIfChanged(ref _wallThickness, value); + } + + public ushort InfillBrightness + { + get => _infillBrightness; + set => RaiseAndSetIfChanged(ref _infillBrightness, value); + } + + public ushort InfillThickness + { + get => _infillThickness; + set => RaiseAndSetIfChanged(ref _infillThickness, value); + } + + public ushort InfillSpacing + { + get => _infillSpacing; + set => RaiseAndSetIfChanged(ref _infillSpacing, value); + } + + public override string ToString() + { + var result = $"[{_infillType}] [Wall: {_wallThickness}px] [B: {_infillBrightness}px] [T: {_infillThickness}px] [S: {_infillSpacing}px]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #endregion + + #region Equality + + #endregion + } +} diff --git a/UVtools.Core/Operations/OperationPixelDimming.cs b/UVtools.Core/Operations/OperationPixelDimming.cs index 24bbeaa..c099e15 100644 --- a/UVtools.Core/Operations/OperationPixelDimming.cs +++ b/UVtools.Core/Operations/OperationPixelDimming.cs @@ -17,10 +17,11 @@ namespace UVtools.Core.Operations [Serializable] public class OperationPixelDimming : Operation { - private uint _borderSize = 5; - private bool _bordersOnly; - private Matrix<byte> _evenPattern; - private Matrix<byte> _oddPattern; + private uint _wallThickness = 5; + private bool _wallsOnly; + private Matrix<byte> _pattern; + private Matrix<byte> _alternatePattern; + private ushort _alternatePatternPerLayers = 1; #region Overrides public override string Title => "Pixel dimming"; @@ -43,12 +44,12 @@ namespace UVtools.Core.Operations public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); - if (BorderSize == 0 && BordersOnly) + if (WallThickness == 0 && WallsOnly) { sb.AppendLine("Border size must be positive in order to use \"Dim only borders\" function."); } - if (EvenPattern is null && OddPattern is null) + if (Pattern is null && AlternatePattern is null) { sb.AppendLine("Either even or odd pattern must contain a valid matrix."); } @@ -59,46 +60,66 @@ namespace UVtools.Core.Operations #region Properties - public uint BorderSize + public uint WallThickness { - get => _borderSize; - set => RaiseAndSetIfChanged(ref _borderSize, value); + get => _wallThickness; + set => RaiseAndSetIfChanged(ref _wallThickness, value); } - public bool BordersOnly + public bool WallsOnly { - get => _bordersOnly; - set => RaiseAndSetIfChanged(ref _bordersOnly, value); + get => _wallsOnly; + set => RaiseAndSetIfChanged(ref _wallsOnly, value); + } + + /// <summary> + /// Use the alternate pattern every <see cref="AlternatePatternPerLayers"/> layers + /// </summary> + public ushort AlternatePatternPerLayers + { + get => _alternatePatternPerLayers; + set => RaiseAndSetIfChanged(ref _alternatePatternPerLayers, Math.Max((ushort)1, value)); } [XmlIgnore] - public Matrix<byte> EvenPattern + public Matrix<byte> Pattern { - get => _evenPattern; - set => RaiseAndSetIfChanged(ref _evenPattern, value); + get => _pattern; + set => RaiseAndSetIfChanged(ref _pattern, value); } [XmlIgnore] - public Matrix<byte> OddPattern + public Matrix<byte> AlternatePattern { - get => _oddPattern; - set => RaiseAndSetIfChanged(ref _oddPattern, value); + get => _alternatePattern; + set => RaiseAndSetIfChanged(ref _alternatePattern, value); } #endregion + #region Methods + + public bool IsNormalPattern(uint layerIndex) + { + return layerIndex / AlternatePatternPerLayers % 2 == 0; + } + + public bool IsAlternatePattern(uint layerIndex) => !IsNormalPattern(layerIndex); + public override string ToString() { - var result = $"[Border: {_borderSize}px] [Only borders: {_bordersOnly}]" + LayerRangeString; + var result = $"[Border: {_wallThickness}px] [Only borders: {_wallsOnly}] [Alternate every: {_alternatePatternPerLayers}]" + LayerRangeString; if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; return result; } + #endregion + #region Equality protected bool Equals(OperationPixelDimming other) { - return _borderSize == other._borderSize && _bordersOnly == other._bordersOnly; + return _wallThickness == other._wallThickness && _wallsOnly == other._wallsOnly; } public override bool Equals(object obj) @@ -113,7 +134,7 @@ namespace UVtools.Core.Operations { unchecked { - return ((int) _borderSize * 397) ^ _bordersOnly.GetHashCode(); + return ((int) _wallThickness * 397) ^ _wallsOnly.GetHashCode(); } } diff --git a/UVtools.Core/Operations/OperationProgress.cs b/UVtools.Core/Operations/OperationProgress.cs index 85a9971..077c4ba 100644 --- a/UVtools.Core/Operations/OperationProgress.cs +++ b/UVtools.Core/Operations/OperationProgress.cs @@ -38,7 +38,7 @@ namespace UVtools.Core.Operations private bool _canCancel = true; private string _title = "Operation"; - private string _itemName = StatusDecodeLayers; + private string _itemName = "Initializing"; private uint _processedItems; private uint _itemCount; diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs index af9474c..4dc4088 100644 --- a/UVtools.Core/Operations/OperationSolidify.cs +++ b/UVtools.Core/Operations/OperationSolidify.cs @@ -13,6 +13,14 @@ namespace UVtools.Core.Operations [Serializable] public sealed class OperationSolidify : Operation { + public enum AreaCheckTypes + { + More, + Less + } + private uint _minimumArea = 1; + private AreaCheckTypes _areaCheckType = AreaCheckTypes.More; + #region Overrides public override string Title => "Solidify"; @@ -29,7 +37,29 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Solidified layers"; - public override bool CanHaveProfiles => false; + /// <summary> + /// Gets the minimum required area to solidify it + /// </summary> + public uint MinimumArea + { + get => _minimumArea; + set => RaiseAndSetIfChanged(ref _minimumArea, Math.Max(1, value)); + } + + public AreaCheckTypes AreaCheckType + { + get => _areaCheckType; + set => RaiseAndSetIfChanged(ref _areaCheckType, value); + } + + public static Array AreaCheckTypeItems => Enum.GetValues(typeof(AreaCheckTypes)); + + public override string ToString() + { + var result = $"[Area: ={_areaCheckType} than {_minimumArea}px²]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } #endregion } diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 176522d..9d3c379 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, repair, conversion and manipulation</Description> - <Version>1.2.1</Version> + <Version>1.3.0</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> diff --git a/UVtools.GUI/Controls/Tools/CtrlToolPixelDimming.cs b/UVtools.GUI/Controls/Tools/CtrlToolPixelDimming.cs index 576e3f4..f9cfbf3 100644 --- a/UVtools.GUI/Controls/Tools/CtrlToolPixelDimming.cs +++ b/UVtools.GUI/Controls/Tools/CtrlToolPixelDimming.cs @@ -43,8 +43,8 @@ namespace UVtools.GUI.Controls.Tools public override bool UpdateOperation() { base.UpdateOperation(); - Operation.BorderSize = (uint) nmBorderSize.Value; - Operation.BordersOnly = cbDimsOnlyBorders.Checked; + Operation.WallThickness = (uint) nmBorderSize.Value; + Operation.WallsOnly = cbDimsOnlyBorders.Checked; var matrixTextbox = new[] @@ -95,8 +95,8 @@ namespace UVtools.GUI.Controls.Tools } } - Operation.EvenPattern = matrixTextbox[0].Pattern; - Operation.OddPattern = matrixTextbox[1].Pattern; + Operation.Pattern = matrixTextbox[0].Pattern; + Operation.AlternatePattern = matrixTextbox[1].Pattern; return true; } diff --git a/UVtools.GUI/FrmMain.cs b/UVtools.GUI/FrmMain.cs index f5f0ffb..5ce71fd 100644 --- a/UVtools.GUI/FrmMain.cs +++ b/UVtools.GUI/FrmMain.cs @@ -3625,7 +3625,7 @@ namespace UVtools.GUI try { Issues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapConfig, touchingBoundConfig, - emptyLayersConfig, FrmLoading.RestartProgress()); + emptyLayersConfig, null, FrmLoading.RestartProgress()); } catch (OperationCanceledException) { @@ -4061,7 +4061,7 @@ namespace UVtools.GUI try { var issues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapConfig, - touchingBoundConfig, false, + touchingBoundConfig, false, null, FrmLoading.RestartProgress()); issues.RemoveAll(issue => issue.Type != LayerIssue.IssueType.Island && issue.Type != LayerIssue.IssueType.Overhang); // Remove all non islands diff --git a/UVtools.WPF/Assets/Icons/eye-slash-16x16.png b/UVtools.WPF/Assets/Icons/eye-slash-16x16.png Binary files differnew file mode 100644 index 0000000..de4683b --- /dev/null +++ b/UVtools.WPF/Assets/Icons/eye-slash-16x16.png diff --git a/UVtools.WPF/Assets/Icons/stroopwafel-16x16.png b/UVtools.WPF/Assets/Icons/stroopwafel-16x16.png Binary files differnew file mode 100644 index 0000000..4fc0de6 --- /dev/null +++ b/UVtools.WPF/Assets/Icons/stroopwafel-16x16.png diff --git a/UVtools.WPF/Controls/Helpers.cs b/UVtools.WPF/Controls/Helpers.cs index efcd8cd..9530e78 100644 --- a/UVtools.WPF/Controls/Helpers.cs +++ b/UVtools.WPF/Controls/Helpers.cs @@ -71,5 +71,13 @@ namespace UVtools.WPF.Controls result.AddRange(data.Select(kv => new FileDialogFilter {Name = kv.Key, Extensions = kv.Value})); return result; } + + public static List<FileDialogFilter> ToAvaloniaFilter(string name, string extension) + { + return new List<FileDialogFilter>(1) + { + new FileDialogFilter {Name = name, Extensions = new List<string>(1) {extension}} + }; + } } } diff --git a/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml b/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml new file mode 100644 index 0000000..f09dc61 --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml @@ -0,0 +1,119 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="300" + x:Class="UVtools.WPF.Controls.Tools.ToolInfillControl"> + <Grid + RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto" + ColumnDefinitions="Auto,10,200,5,Auto" + > + + <!--Pattern--> + <TextBlock + VerticalAlignment="Center" + Text="Pattern:" + /> + + <ComboBox + Grid.Column="2" + Items="{Binding Operation.InfillAlgorithmTypes}" + SelectedItem="{Binding Operation.InfillType}" + /> + + <!--Wall thickness--> + <TextBlock + Grid.Row="2" + Grid.Column="0" + VerticalAlignment="Center" + Text="Wall thickness:" + /> + + <NumericUpDown + Grid.Row="2" + Grid.Column="2" + Minimum="0" + Maximum="65535" + Increment="1" + ClipValueToMinMax="True" + Value="{Binding Operation.WallThickness}" + /> + + <TextBlock + Grid.Row="2" + Grid.Column="4" + VerticalAlignment="Center" + Text="px" + /> + + <!--Infill brightness--> + <TextBlock + Grid.Row="4" + Grid.Column="0" + VerticalAlignment="Center" + Text="Infill brightness:" + /> + + <NumericUpDown + Grid.Row="4" + Grid.Column="2" + Minimum="0" + Maximum="255" + Increment="1" + ClipValueToMinMax="True" + Value="{Binding Operation.InfillBrightness}" + /> + + <!--Infill thickness--> + <TextBlock + Grid.Row="6" + Grid.Column="0" + VerticalAlignment="Center" + Text="Infill thickness:" + /> + + <NumericUpDown + Grid.Row="6" + Grid.Column="2" + Name="InfillThickness" + Minimum="0" + Maximum="65535" + Increment="1" + ClipValueToMinMax="True" + Value="{Binding Operation.InfillThickness}" + /> + + <TextBlock + Grid.Row="6" + Grid.Column="4" + VerticalAlignment="Center" + Text="px" + /> + + <!--Infill spacing--> + <TextBlock + Grid.Row="8" + Grid.Column="0" + VerticalAlignment="Center" + Text="Infill spacing:" + /> + + <NumericUpDown + Grid.Row="8" + Grid.Column="2" + Minimum="{Binding #InfillThickness.Value}" + Maximum="65535" + Increment="1" + ClipValueToMinMax="True" + Value="{Binding Operation.InfillSpacing}" + /> + + <TextBlock + Grid.Row="8" + Grid.Column="4" + VerticalAlignment="Center" + Text="px" + /> + + </Grid> +</UserControl> diff --git a/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml.cs new file mode 100644 index 0000000..a710a9e --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml.cs @@ -0,0 +1,21 @@ +using Avalonia.Markup.Xaml; +using UVtools.Core.Operations; + +namespace UVtools.WPF.Controls.Tools +{ + public class ToolInfillControl : ToolControl + { + public OperationInfill Operation => BaseOperation as OperationInfill; + + public ToolInfillControl() + { + InitializeComponent(); + BaseOperation = new OperationInfill(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml b/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml index c9e7ee4..2dd9dbe 100644 --- a/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml @@ -8,13 +8,13 @@ <StackPanel Spacing="10" Orientation="Horizontal"> <TextBlock VerticalAlignment="Center" - Text="Border size:" + Text="Wall thickness:" /> <NumericUpDown Minimum="0" Maximum="1000" Width="80" - Value="{Binding Operation.BorderSize}" + Value="{Binding Operation.WallThickness}" /> <TextBlock VerticalAlignment="Center" @@ -23,8 +23,24 @@ <CheckBox Margin="20,0,0,0" - Content="Dim only borders" - IsChecked="{Binding Operation.BordersOnly}" + Content="Dim only walls" + IsChecked="{Binding Operation.WallsOnly}" + /> + + <TextBlock + Margin="20,0,0,0" + VerticalAlignment="Center" + Text="Alternate the pattern every:" + /> + <NumericUpDown + Minimum="1" + Maximum="{Binding ushort.MaxValue}" + Width="80" + Value="{Binding Operation.AlternatePatternPerLayers}" + /> + <TextBlock + VerticalAlignment="Center" + Text="layers" /> @@ -222,6 +238,13 @@ Command="{Binding GenerateInfill}" CommandParameter="Waves" /> + <Button + Padding="10" + Content="Lattice" + Width="100" + Command="{Binding GenerateInfill}" + CommandParameter="Lattice" + /> </StackPanel> </StackPanel> </Border> diff --git a/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml.cs index 36499a2..e4d35bf 100644 --- a/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolPixelDimmingControl.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Drawing; using Avalonia.Markup.Xaml; using Emgu.CV; using UVtools.Core.Extensions; @@ -116,8 +117,8 @@ namespace UVtools.WPF.Controls.Tools } } - Operation.EvenPattern = stringMatrix[0].Pattern; - Operation.OddPattern = stringMatrix[1].Pattern; + Operation.Pattern = stringMatrix[0].Pattern; + Operation.AlternatePattern = stringMatrix[1].Pattern; return true; } @@ -331,6 +332,64 @@ namespace UVtools.WPF.Controls.Tools AlternatePatternText = null; return; } + + if (pattern == "Lattice") + { + var p1 = string.Empty; + var p2 = string.Empty; + + var zeros = Math.Max(0, _infillGenSpacing - _infillGenThickness * 2); + + // Pillar + for (int i = 0; i < _infillGenThickness; i++) + { + p1 += "255 ".Repeat(_infillGenThickness); + p1 += "0 ".Repeat(zeros); + p1 += "255 ".Repeat(_infillGenThickness); + p1 = p1.Trim() + '\n'; + } + + for (int i = 0; i < zeros; i++) + { + p1 += "0 ".Repeat(_infillGenSpacing); + p1 = p1.Trim() + '\n'; + } + + for (int i = 0; i < _infillGenThickness; i++) + { + p1 += "255 ".Repeat(_infillGenThickness); + p1 += "0 ".Repeat(zeros); + p1 += "255 ".Repeat(_infillGenThickness); + p1 = p1.Trim() + '\n'; + } + + // Square + for (int i = 0; i < _infillGenThickness; i++) + { + p2 += "255 ".Repeat(_infillGenSpacing); + p2 = p2.Trim() + '\n'; + } + + for (int i = 0; i < zeros; i++) + { + p2 += "255 ".Repeat(_infillGenThickness); + p2 += "0 ".Repeat(zeros); + p2 += "255 ".Repeat(_infillGenThickness); + p2 = p2.Trim() + '\n'; + } + + for (int i = 0; i < _infillGenThickness; i++) + { + p2 += "255 ".Repeat(_infillGenSpacing); + p2 = p2.Trim() + '\n'; + } + + + + PatternText = p1.Trim('\n', '\r'); + AlternatePatternText = p2.Trim('\n', '\r'); ; + return; + } } } diff --git a/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml b/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml new file mode 100644 index 0000000..fe7cc24 --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml @@ -0,0 +1,32 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="100" + x:Class="UVtools.WPF.Controls.Tools.ToolSolidifyControl"> + <StackPanel Orientation="Horizontal" Spacing="5"> + <TextBlock + Text="Solidifies areas with or" + VerticalAlignment="Center" /> + + <ComboBox + Items="{Binding Operation.AreaCheckTypeItems}" + SelectedItem="{Binding Operation.AreaCheckType}" + Width="70" + VerticalAlignment="Center" /> + + <TextBlock + Text="than:" + VerticalAlignment="Center" /> + + <NumericUpDown + Width="100" + Minimum="1" + Maximum="4294967295" + Value="{Binding Operation.MinimumArea}" + /> + <TextBlock + Text="px²" + VerticalAlignment="Center" /> + </StackPanel> +</UserControl> diff --git a/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml.cs new file mode 100644 index 0000000..e851ee9 --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia.Markup.Xaml; +using UVtools.Core.Operations; + +namespace UVtools.WPF.Controls.Tools +{ + public class ToolSolidifyControl : ToolControl + { + public OperationSolidify Operation => BaseOperation as OperationSolidify; + public ToolSolidifyControl() + { + InitializeComponent(); + BaseOperation = new OperationSolidify(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index 3785ebb..6542e25 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -52,6 +52,8 @@ namespace UVtools.WPF private set => RaiseAndSetIfChanged(ref _issues, value); } + public readonly List<LayerIssue> IgnoredIssues = new List<LayerIssue>(); + public bool IssueCanGoPrevious => Issues.Count > 0 && _issueSelectedIndex > 0; public bool IssueCanGoNext => Issues.Count > 0 && _issueSelectedIndex < Issues.Count - 1; #endregion @@ -84,7 +86,7 @@ namespace UVtools.WPF if (await this.MessageBoxQuestion($"Are you sure you want to remove all selected {IssuesGrid.SelectedItems.Count} issues?\n\n" + "Warning: Removing an island can cause other issues to appear if there is material present in the layers above it.\n" + - "Always check previous and next layers before performing an island removal.", "Remove Issues?") != ButtonResult.Yes) return; + "Always check previous and next layers before performing an island removal.", $"Remove {IssuesGrid.SelectedItems.Count} Issues?") != ButtonResult.Yes) return; Dictionary<uint, List<LayerIssue>> processIssues = new Dictionary<uint, List<LayerIssue>>(); List<uint> layersRemove = new List<uint>(); @@ -222,6 +224,35 @@ namespace UVtools.WPF CanSave = true; } + public async void OnClickIssueIgnore() + { + if ((_globalModifiers & KeyModifiers.Alt) != 0) + { + if(IgnoredIssues.Count == 0) return; + if (await this.MessageBoxQuestion( + $"Are you sure you want to re-enable {IgnoredIssues.Count} ignored issues?\n" + + "A full re-detect will be required to get the ignored issues.\n", $"Re-enable {IgnoredIssues.Count} Issues?") != + ButtonResult.Yes) return; + + IgnoredIssues.Clear(); + + return; + } + + if (IssuesGrid.SelectedItems.Count == 0) return; + + if (await this.MessageBoxQuestion( + $"Are you sure you want to hide and ignore all selected {IssuesGrid.SelectedItems.Count} issues?\n" + + "The ignored issues won't be re-detected.\n", $"Ignore {IssuesGrid.SelectedItems.Count} Issues?") != + ButtonResult.Yes) return; + + var list = IssuesGrid.SelectedItems.Cast<LayerIssue>().ToArray(); + IgnoredIssues.AddRange(list); + IssuesGrid.SelectedItems.Clear(); + Issues.RemoveMany(list); + ShowLayer(); + } + private async Task UpdateIslandsOverhangs(List<uint> whiteListLayers) { if (whiteListLayers.Count == 0) return; @@ -254,7 +285,7 @@ namespace UVtools.WPF try { var issues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapConfig, - touchingBoundConfig, false, + touchingBoundConfig, false, IgnoredIssues, ProgressWindow.RestartProgress()); issues.RemoveAll(issue => issue.Type != LayerIssue.IssueType.Island && issue.Type != LayerIssue.IssueType.Overhang); // Remove all non islands and overhangs @@ -414,7 +445,7 @@ namespace UVtools.WPF try { var issues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapConfig, touchingBoundConfig, - emptyLayersConfig, ProgressWindow.RestartProgress()); + emptyLayersConfig, IgnoredIssues, ProgressWindow.RestartProgress()); return issues; } catch (OperationCanceledException) @@ -529,7 +560,11 @@ namespace UVtools.WPF return new TouchingBoundDetectionConfiguration { Enabled = Settings.Issues.ComputeTouchingBounds, - //MaximumPixelBrightness = 100 + MinimumPixelBrightness = UserSettings.Instance.Issues.TouchingBoundMinimumPixelBrightness, + MarginLeft = UserSettings.Instance.Issues.TouchingBoundMarginLeft, + MarginTop = UserSettings.Instance.Issues.TouchingBoundMarginTop, + MarginRight = UserSettings.Instance.Issues.TouchingBoundMarginRight, + MarginBottom = UserSettings.Instance.Issues.TouchingBoundMarginBottom, }; } diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs index 0073f3c..984bd60 100644 --- a/UVtools.WPF/MainWindow.LayerPreview.cs +++ b/UVtools.WPF/MainWindow.LayerPreview.cs @@ -309,13 +309,13 @@ namespace UVtools.WPF } } - public string LayerBoundsStr => LayerCache.Layer is null ? "NS" : LayerCache.Layer.BoundingRectangle.ToString(); + public string LayerBoundsStr => LayerCache.Layer is null ? "NS" : $"{LayerCache.Layer.BoundingRectangle} ({LayerCache.Layer.BoundingRectangle.GetArea()}px²)"; public string LayerROIStr { get { var roi = ROI; - return roi.IsEmpty ? "NS" : roi.ToString(); + return roi.IsEmpty ? "NS" : $"{roi} ({roi.GetArea()}px²)"; } } @@ -540,7 +540,7 @@ namespace UVtools.WPF if (issue.Type == LayerIssue.IssueType.ResinTrap) { - color = selectedIssues.Contains(issue) + color = selectedIssues.Count > 0 && selectedIssues.Contains(issue) ? Settings.LayerPreview.ResinTrapHighlightColor : Settings.LayerPreview.ResinTrapColor; @@ -564,7 +564,7 @@ namespace UVtools.WPF switch (issue.Type) { case LayerIssue.IssueType.Island: - color = selectedIssues.Contains(issue) + color = selectedIssues.Count > 0 && selectedIssues.Contains(issue) ? Settings.LayerPreview.IslandHighlightColor : Settings.LayerPreview.IslandColor; if (_showLayerImageCrosshairs && @@ -576,7 +576,7 @@ namespace UVtools.WPF break; case LayerIssue.IssueType.Overhang: - color = selectedIssues.Contains(issue) + color = selectedIssues.Count > 0 && selectedIssues.Contains(issue) ? Settings.LayerPreview.OverhangHighlightColor : Settings.LayerPreview.OverhangColor; if (_showLayerImageCrosshairs && diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 7f2ac69..76e33b2 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -89,6 +89,7 @@ Name="MainMenu.File.Convert" Header="_Convert to" IsEnabled="{Binding IsFileLoaded}" + IsVisible="{Binding MenuFileConvertItems, Converter={x:Static ObjectConverters.IsNotNull}}" Items="{Binding MenuFileConvertItems}"> <MenuItem.Icon> <Image Source="\Assets\Icons\convert-16x16.png"/> @@ -631,6 +632,14 @@ </RepeatButton> <Button + ToolTip.Tip="Hides and ignores the selected issues, they won't be re-detected. +
ALT + Click to re-enable the ignored issues." + IsEnabled="{Binding #IssuesGrid.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}" + Command="{Binding OnClickIssueIgnore}"> + <Image Source="/Assets/Icons/eye-slash-16x16.png" /> + </Button> + + <Button ToolTip.Tip="Remove selected issue when possible. 
Islands: All pixels are removed (turn black). 
ResinTrap: All trap areas are filled with white pixels. @@ -639,7 +648,7 @@ Command="{Binding OnClickIssueRemove}"> <StackPanel Orientation="Horizontal" Spacing="5"> <Image Source="/Assets/Icons/trash-16x16.png" /> - <TextBlock VerticalAlignment="Center" Text="Remove"/> + <!--<TextBlock VerticalAlignment="Center" Text="Remove"/>!--> </StackPanel> </Button> @@ -688,7 +697,7 @@ </Button.ContextMenu> <StackPanel Orientation="Horizontal" Spacing="5"> - <Image Source="/Assets/Icons/save-16x16.png" /> + <Image Source="/Assets/Icons/refresh-16x16.png" /> <TextBlock Text="Detect ⮟"/> </StackPanel> @@ -1716,7 +1725,7 @@ <Grid IsEnabled="{Binding IsFileLoaded}" Grid.Row="2" - ColumnDefinitions="*,*" RowDefinitions="Auto" Margin="5"> + ColumnDefinitions="4*,2*" RowDefinitions="Auto" Margin="5"> <WrapPanel Orientation="Horizontal"> <StackPanel ToolTip.Tip="Number of pixels to cure on this layer image and the percentage of them against total lcd pixels" diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index e91f678..c82ce00 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -152,6 +152,14 @@ namespace UVtools.WPF }, new MenuItem { + Tag = new OperationInfill(), + Icon = new Avalonia.Controls.Image + { + Source = new Bitmap(App.GetAsset("/Assets/Icons/stroopwafel-16x16.png")) + } + }, + new MenuItem + { Tag = new OperationBlur(), Icon = new Avalonia.Controls.Image { @@ -230,7 +238,7 @@ namespace UVtools.WPF #region Members - public Stopwatch LastStopWatch; + public Stopwatch LastStopWatch = new Stopwatch(); private bool _isGUIEnabled = true; private uint _savesCount; @@ -479,7 +487,7 @@ namespace UVtools.WPF || LayerImageBox.SelectionMode == AdvancedImageBox.SelectionModes.Rectangle ) return; - var imageBoxMousePosition = _globalPointerEventArgs.GetPosition(LayerImageBox); + var imageBoxMousePosition = _globalPointerEventArgs?.GetPosition(LayerImageBox) ?? new Point(-1, -1); if (imageBoxMousePosition.X < 0 || imageBoxMousePosition.Y < 0) return; // Pixel Edit is active, Shift is down, and the cursor is over the image region. @@ -621,6 +629,8 @@ namespace UVtools.WPF { if (SlicerFile is null) return; + MenuFileConvertItems = null; + ClipboardManager.Instance.Reset(); SlicerFile?.Dispose(); @@ -628,6 +638,7 @@ namespace UVtools.WPF SlicerProperties.Clear(); Issues.Clear(); + IgnoredIssues.Clear(); _issuesSliderCanvas.Children.Clear(); Drawings.Clear(); @@ -703,10 +714,12 @@ namespace UVtools.WPF { var result = await this.MessageBoxQuestion( - $"Do you like to auto-update {About.Software} v{App.Version} to v{VersionChecker.Version}?\n\n" + + $"Do you like to auto-update {About.Software} v{App.Version} to v{VersionChecker.Version}?\n" + "Yes: Auto update\n" + "No: Manual update\n" + - "Cancel: No action", "Update UVtools?", ButtonEnum.YesNoCancel); + "Cancel: No action\n\n" + + "Changelog:\n" + + $"{VersionChecker.Changelog}", $"Update UVtools to v{VersionChecker.Version}?", ButtonEnum.YesNoCancel); if (result == ButtonResult.Yes) { @@ -749,9 +762,9 @@ namespace UVtools.WPF private void UpdateTitle() { - Title = (SlicerFile is null - ? $"{About.Software} Version: {App.VersionStr}" - : $"{About.Software} File: {Path.GetFileName(SlicerFile.FileFullPath)} ({Math.Round(LastStopWatch.ElapsedMilliseconds / 1000m, 2)}s) Version: {App.VersionStr}") + Title = SlicerFile is null + ? $"{About.Software} Version: {App.VersionStr}" + : $"{About.Software} File: {Path.GetFileName(SlicerFile.FileFullPath)} ({Math.Round(LastStopWatch.ElapsedMilliseconds / 1000m, 2)}s) Version: {App.VersionStr}" ; Title += $" RAM: {SizeExtensions.SizeSuffix(Environment.WorkingSet)}"; @@ -846,8 +859,20 @@ namespace UVtools.WPF foreach (var fileFormatType in SlicerFile.ConvertToFormats) { FileFormat fileFormat = FileFormat.FindByType(fileFormatType); + if(fileFormat.FileExtensions is null) continue; + foreach (var fileExtension in fileFormat.FileExtensions) + { + var menuItem = new MenuItem + { + Header = fileExtension.Description, + Tag = fileExtension + }; - string extensions = fileFormat.FileExtensions.Length > 0 + menuItem.Tapped += ConvertToOnTapped; + + menuItems.Add(menuItem); + } + /*string extensions = fileFormat.FileExtensions.Length > 0 ? $" ({fileFormat.GetFileExtensions()})" : string.Empty; @@ -859,7 +884,7 @@ namespace UVtools.WPF menuItem.Tapped += ConvertToOnTapped; - menuItems.Add(menuItem); + menuItems.Add(menuItem);*/ } MenuFileConvertItems = menuItems.ToArray(); @@ -920,12 +945,12 @@ namespace UVtools.WPF private async void ConvertToOnTapped(object? sender, RoutedEventArgs e) { if (!(sender is MenuItem item)) return; - if (!(item.Tag is FileFormat fileFormat)) return; + if (!(item.Tag is FileExtension fileExtension)) return; SaveFileDialog dialog = new SaveFileDialog { InitialFileName = Path.GetFileNameWithoutExtension(SlicerFile.FileFullPath), - Filters = Helpers.ToAvaloniaFileFilter(fileFormat.FileFilterAvalonia), + Filters = Helpers.ToAvaloniaFilter(fileExtension.Description, fileExtension.Extension), Directory = string.IsNullOrEmpty(Settings.General.DefaultDirectoryConvertFile) ? Path.GetDirectoryName(SlicerFile.FileFullPath) : Settings.General.DefaultDirectoryConvertFile @@ -942,7 +967,7 @@ namespace UVtools.WPF ShowProgressWindow($"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {Path.GetExtension(result)}"); try { - return SlicerFile.Convert(fileFormat, result, ProgressWindow.RestartProgress()); + return SlicerFile.Convert(fileExtension.GetFileFormat(), result, ProgressWindow.RestartProgress()); } catch (OperationCanceledException) { @@ -1249,6 +1274,9 @@ namespace UVtools.WPF case OperationPixelDimming operation: SlicerFile.LayerManager.PixelDimming(operation, ProgressWindow.RestartProgress(operation.CanCancel)); break; + case OperationInfill operation: + SlicerFile.LayerManager.Infill(operation, ProgressWindow.RestartProgress(operation.CanCancel)); + break; case OperationBlur operation: SlicerFile.LayerManager.Blur(operation, ProgressWindow.RestartProgress(operation.CanCancel)); break; diff --git a/UVtools.WPF/Structures/AppVersionChecker.cs b/UVtools.WPF/Structures/AppVersionChecker.cs index 57d9ba0..79e6903 100644 --- a/UVtools.WPF/Structures/AppVersionChecker.cs +++ b/UVtools.WPF/Structures/AppVersionChecker.cs @@ -6,15 +6,12 @@ * of this license document, but changing it is not allowed. */ using System; -using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Net; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; -using ABI.Windows.Data.Json; using Avalonia.Threading; using Newtonsoft.Json; using UVtools.Core; @@ -29,6 +26,7 @@ namespace UVtools.WPF.Structures public const string GitHubReleaseApi = "https://api.github.com/repos/sn4k3/UVtools/releases/latest"; private string _version; private string _url; + private string _changelog; public string Filename { @@ -83,6 +81,12 @@ namespace UVtools.WPF.Structures } } + public string Changelog + { + get => _changelog; + set => RaiseAndSetIfChanged(ref _changelog, value); + } + public string VersionAnnouncementText => $"New version v{_version} is available!"; public string UrlLatestRelease => $"{About.Website}/releases/latest"; @@ -113,12 +117,15 @@ namespace UVtools.WPF.Structures tag_name = tag_name.Trim(' ', 'v', 'V'); Debug.WriteLine($"Version checker: v{App.VersionStr} <=> v{tag_name}"); + Changelog = json.body; + if (string.Compare(tag_name, App.VersionStr, StringComparison.OrdinalIgnoreCase) > 0) { Dispatcher.UIThread.InvokeAsync(() => { Version = tag_name; - Debug.WriteLine($"New version detected: {DownloadLink}"); + Debug.WriteLine($"New version detected: {DownloadLink}\n" + + $"{_changelog}"); }); return true; } diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 62df3bd..ac1ac0e 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>1.2.1</Version> + <Version>1.3.0</Version> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> @@ -42,8 +42,10 @@ </ItemGroup> <ItemGroup> <None Remove="Assets\Icons\clipboard-32x32.png" /> + <None Remove="Assets\Icons\eye-slash-16x16.png" /> <None Remove="Assets\Icons\layers-alt-32x32.png" /> <None Remove="Assets\Icons\redo-16x16.png" /> + <None Remove="Assets\Icons\stroopwafel-16x16.png" /> <None Remove="Assets\Icons\UVtools.ico" /> </ItemGroup> <ItemGroup> diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs index f0f9d4f..6f6e3d3 100644 --- a/UVtools.WPF/UserSettings.cs +++ b/UVtools.WPF/UserSettings.cs @@ -14,12 +14,13 @@ using Avalonia.Media; using JetBrains.Annotations; using ReactiveUI; using UVtools.Core; +using UVtools.Core.Objects; using Color=UVtools.WPF.Structures.Color; namespace UVtools.WPF { [Serializable] - public sealed class UserSettings : ReactiveObject + public sealed class UserSettings : BindableBase { #region Constants public const ushort SETTINGS_VERSION = 1; @@ -29,7 +30,7 @@ namespace UVtools.WPF #region General [Serializable] - public sealed class GeneralUserSettings : ReactiveObject + public sealed class GeneralUserSettings : BindableBase { private bool _startMaximized = true; private bool _checkForUpdatesOnStartup = true; @@ -133,7 +134,7 @@ namespace UVtools.WPF #region Layer Preview [Serializable] - public sealed class LayerPreviewUserSettings : ReactiveObject + public sealed class LayerPreviewUserSettings : BindableBase { private Color _tooltipOverlayBackgroundColor = new Color(210, 255, 255, 192); private bool _tooltipOverlay = true; @@ -541,7 +542,7 @@ namespace UVtools.WPF #region Issues [Serializable] - public sealed class IssuesUserSettings : ReactiveObject + public sealed class IssuesUserSettings : BindableBase { private bool _computeIssuesOnLoad = false; private bool _computeIssuesOnClickTab = true; @@ -563,6 +564,12 @@ namespace UVtools.WPF private byte _resinTrapRequiredAreaToProcessCheck = 17; private byte _resinTrapRequiredBlackPixelsToDrain = 10; private byte _resinTrapMaximumPixelBrightnessToDrain = 30; + private byte _touchingBoundMinimumPixelBrightness = 127; + private byte _touchingBoundMarginLeft = 5; + private byte _touchingBoundMarginTop = 5; + private byte _touchingBoundMarginRight = 5; + private byte _touchingBoundMarginBottom = 5; + private bool _touchingBoundSyncMargins = true; public bool ComputeIssuesOnLoad { @@ -684,6 +691,70 @@ namespace UVtools.WPF set => this.RaiseAndSetIfChanged(ref _resinTrapMaximumPixelBrightnessToDrain, value); } + public byte TouchingBoundMinimumPixelBrightness + { + get => _touchingBoundMinimumPixelBrightness; + set => RaiseAndSetIfChanged(ref _touchingBoundMinimumPixelBrightness, value); + } + + public byte TouchingBoundMarginLeft + { + get => _touchingBoundMarginLeft; + set + { + if(!RaiseAndSetIfChanged(ref _touchingBoundMarginLeft, value)) return; + if (_touchingBoundSyncMargins) + { + TouchingBoundMarginRight = value; + } + } + } + + public byte TouchingBoundMarginTop + { + get => _touchingBoundMarginTop; + set + { + if (!RaiseAndSetIfChanged(ref _touchingBoundMarginTop, value)) return; + if (_touchingBoundSyncMargins) + { + TouchingBoundMarginBottom = value; + } + } + } + + public byte TouchingBoundMarginRight + { + get => _touchingBoundMarginRight; + set + { + if(!RaiseAndSetIfChanged(ref _touchingBoundMarginRight, value)) return; + if (_touchingBoundSyncMargins) + { + TouchingBoundMarginLeft = value; + } + } + } + + public byte TouchingBoundMarginBottom + { + get => _touchingBoundMarginBottom; + set + { + if(!RaiseAndSetIfChanged(ref _touchingBoundMarginBottom, value)) return; + if (_touchingBoundSyncMargins) + { + TouchingBoundMarginTop = value; + } + } + } + + public bool TouchingBoundSyncMargins + { + get => _touchingBoundSyncMargins; + set => RaiseAndSetIfChanged(ref _touchingBoundSyncMargins, value); + } + public IssuesUserSettings Clone() { return MemberwiseClone() as IssuesUserSettings; @@ -694,7 +765,7 @@ namespace UVtools.WPF #region Pixel Editor [Serializable] - public sealed class PixelEditorUserSettings : ReactiveObject + public sealed class PixelEditorUserSettings : BindableBase { private Color _addPixelColor = new Color(255, 144, 238, 144); private Color _addPixelHighlightColor = new Color(255, 0, 255, 0); @@ -882,7 +953,7 @@ namespace UVtools.WPF #region Layer Repair [Serializable] - public sealed class LayerRepairUserSettings : ReactiveObject + public sealed class LayerRepairUserSettings : BindableBase { private bool _repairIslands = true; private bool _repairResinTraps = true; diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index 74595a9..4182b2b 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -202,6 +202,7 @@ <NumericUpDown Grid.Row="2" Grid.Column="2" Margin="10,0,0,0" VerticalAlignment="Center" + ClipValueToMinMax="True" Minimum="1" Maximum="50" Value="{Binding Settings.LayerPreview.VolumeBoundsOutlineThickness}" @@ -235,6 +236,7 @@ <NumericUpDown Grid.Row="4" Grid.Column="2" Margin="10,0,0,0" VerticalAlignment="Center" + ClipValueToMinMax="True" Minimum="1" Maximum="50" Value="{Binding Settings.LayerPreview.LayerBoundsOutlineThickness}" @@ -269,6 +271,7 @@ <NumericUpDown Grid.Row="6" Grid.Column="2" Margin="10,0,0,0" VerticalAlignment="Center" + ClipValueToMinMax="True" Minimum="1" Maximum="50" Value="{Binding Settings.LayerPreview.HollowOutlineLineThickness}" @@ -528,6 +531,7 @@ <NumericUpDown Grid.Row="0" Grid.Column="3" Margin="10,0,0,0" VerticalAlignment="Center" + ClipValueToMinMax="True" Minimum="1" Maximum="1000000" Width="70" @@ -558,6 +562,7 @@ <NumericUpDown Grid.Row="2" Grid.Column="3" Margin="10,0,0,0" VerticalAlignment="Center" + ClipValueToMinMax="True" Minimum="1" Maximum="255" Width="70" @@ -664,6 +669,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="0" Maximum="254" Value="{Binding Settings.Issues.IslandBinaryThreshold}" @@ -679,6 +685,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="1" Maximum="255" Value="{Binding Settings.Issues.IslandRequiredAreaToProcessCheck}" @@ -692,6 +699,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="1" Maximum="255" Value="{Binding Settings.Issues.IslandRequiredPixelBrightnessToProcessCheck}" @@ -705,6 +713,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="0.05" Maximum="0.95" Increment="0.05" @@ -721,6 +730,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="50" Maximum="255" Value="{Binding Settings.Issues.IslandRequiredPixelBrightnessToSupport}" @@ -735,6 +745,7 @@ <!-- <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="1" Maximum="255" Value="{Binding Settings.Issues.IslandRequiredPixelsToSupport}" @@ -750,6 +761,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="50" Maximum="255" Value="{Binding Settings.Issues.IslandRequiredPixelBrightnessToSupport}" @@ -782,6 +794,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="2" Maximum="255" Value="{Binding Settings.Issues.OverhangErodeIterations}" @@ -806,6 +819,7 @@ <StackPanel Orientation="Horizontal" Margin="15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="1" Maximum="254" Value="{Binding Settings.Issues.ResinTrapBinaryThreshold}" @@ -822,6 +836,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="1" Maximum="255" Value="{Binding Settings.Issues.ResinTrapRequiredAreaToProcessCheck}" @@ -835,6 +850,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="1" Maximum="255" Value="{Binding Settings.Issues.ResinTrapRequiredBlackPixelsToDrain}" @@ -848,6 +864,7 @@ <StackPanel Orientation="Horizontal" Margin="15,0,15,15" Spacing="10"> <NumericUpDown Width="60" + ClipValueToMinMax="True" Minimum="1" Maximum="150" Value="{Binding Settings.Issues.ResinTrapMaximumPixelBrightnessToDrain}" @@ -861,6 +878,145 @@ </StackPanel> </Border> + <Border + Margin="5" + BorderBrush="LightBlue" + BorderThickness="4"> + + <StackPanel Orientation="Vertical"> + <TextBlock Padding="10" Background="LightBlue" FontWeight="Bold" Text="Touching boundary"/> + + <StackPanel Orientation="Horizontal" Margin="15" Spacing="10"> + <NumericUpDown Width="60" + ClipValueToMinMax="True" + Minimum="1" + Maximum="255" + Value="{Binding Settings.Issues.TouchingBoundMinimumPixelBrightness}" + /> + <TextBlock + VerticalAlignment="Center" + Text="Minimum pixel intensity for a pixel to be considered a touching boundary" + ToolTip.Tip="Pixels below this values won't flag a issue when inside the dangerous boundary margin. +
Pixels equal or above this values will flag a issue when inside the dangerous boundary margin." + /> + </StackPanel> + + + <Grid + ColumnDefinitions="Auto,Auto,Auto,Auto,Auto" + RowDefinitions="Auto,Auto,Auto,Auto,Auto" + Margin="15,0,15,15" + > + <TextBlock + VerticalAlignment="Center" + HorizontalAlignment="Center" + Grid.Row="1" + Grid.Column="1" + FontWeight="Bold" + Text="Margin" /> + + <TextBlock + VerticalAlignment="Center" + HorizontalAlignment="Center" + Grid.Row="3" + Grid.Column="1" + FontWeight="Bold" + Text="Margin" /> + + <TextBlock + VerticalAlignment="Center" + HorizontalAlignment="Center" + Grid.Row="1" + Grid.Column="3" + FontWeight="Bold" + Text="Margin" /> + + <TextBlock + VerticalAlignment="Center" + HorizontalAlignment="Center" + Grid.Row="3" + Grid.Column="3" + FontWeight="Bold" + Text="Margin" /> + + <TextBlock + Grid.Column="0" + Grid.Row="2" + VerticalAlignment="Center" + Text="Left " /> + <NumericUpDown + Grid.Column="1" + Grid.Row="2" + Width="70" + Minimum="1" + Maximum="255" + Margin="0,0,5,0" + ClipValueToMinMax="True" + Value="{Binding Settings.Issues.TouchingBoundMarginLeft}" /> + + + <TextBlock + Grid.Column="2" + Grid.Row="0" + VerticalAlignment="Center" + HorizontalAlignment="Center" + Margin="0,0,0,2" + Text="Top" /> + <NumericUpDown + Grid.Column="2" + Grid.Row="1" + Width="70" + Minimum="1" + Maximum="255" + Margin="0,0,0,5" + ClipValueToMinMax="True" + Value="{Binding Settings.Issues.TouchingBoundMarginTop}" /> + + <TextBlock + Grid.Column="4" + Grid.Row="2" + VerticalAlignment="Center" + HorizontalAlignment="Center" + Text=" Right" /> + <NumericUpDown + Grid.Column="3" + Grid.Row="2" + Width="70" + Minimum="1" + Maximum="255" + Margin="5,0,0,0" + ClipValueToMinMax="True" + Value="{Binding Settings.Issues.TouchingBoundMarginRight}" /> + + <TextBlock + Grid.Column="2" + Grid.Row="4" + VerticalAlignment="Center" + HorizontalAlignment="Center" + Margin="0,2,0,0" + Text="Bottom" /> + <NumericUpDown + Grid.Column="2" + Grid.Row="3" + Width="70" + Minimum="1" + Maximum="255" + Margin="0,5,0,0" + ClipValueToMinMax="True" + Value="{Binding Settings.Issues.TouchingBoundMarginBottom}" /> + + <CheckBox + Grid.Row="2" + Grid.Column="2" + VerticalAlignment="Center" + HorizontalAlignment="Center" + IsChecked="{Binding Settings.Issues.TouchingBoundSyncMargins}" + Content="Sync"/> + </Grid> + + </StackPanel> + </Border> + </StackPanel> </ScrollViewer> </TabItem> @@ -1024,6 +1180,7 @@ <NumericUpDown Value="{Binding Settings.LayerRepair.RemoveIslandsBelowEqualPixels}" Width="70" + ClipValueToMinMax="True" Minimum="0" Maximum="255"/> <TextBlock VerticalAlignment="Center" Text="Default maximum pixel area for Island removal (0 = disable)"/> @@ -1033,6 +1190,7 @@ <NumericUpDown Value="{Binding Settings.LayerRepair.RemoveIslandsRecursiveIterations}" Width="70" + ClipValueToMinMax="True" Minimum="0" Maximum="65535"/> <TextBlock VerticalAlignment="Center" Text="Default maximum layers for recursive island removal (0 = All)"/> @@ -1042,6 +1200,7 @@ <NumericUpDown Value="{Binding Settings.LayerRepair.ClosingIterations}" Width="70" + ClipValueToMinMax="True" Minimum="0" Maximum="255"/> <TextBlock VerticalAlignment="Center" Text="Default 'Gap Closing' iterations"/> @@ -1051,6 +1210,7 @@ <NumericUpDown Value="{Binding Settings.LayerRepair.OpeningIterations}" Width="70" + ClipValueToMinMax="True" Minimum="0" Maximum="255"/> <TextBlock VerticalAlignment="Center" Text="Default 'Noise Removal' iterations"/> diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml.cs b/UVtools.WPF/Windows/SettingsWindow.axaml.cs index cd767ee..f3522d5 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml.cs +++ b/UVtools.WPF/Windows/SettingsWindow.axaml.cs @@ -61,7 +61,7 @@ namespace UVtools.WPF.Windows //MaxHeight = Screens.Primary.WorkingArea.Height - 50; - ScrollViewerMaxHeight = Screens.Primary.WorkingArea.Height / Screens.Primary.PixelDensity - 200; + ScrollViewerMaxHeight = Screens.Primary.WorkingArea.Height / Screens.Primary.PixelDensity - 300; DataContext = this; |