From 08a5797f36867c9b4c9b58da94e61bbea6258ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Mon, 6 Sep 2021 01:34:58 +0100 Subject: [PATCH] v2.21.1 - **(Add) Layer outline:** - Blob outline: Outline all separate blobs - Centroids: Draw a dot at the gemoetric center of a blob - (Add) Adjust layer height: Allow to change exposure time on the dialog and inform that different layer thickness require different exposure times - (Add) Resin trap detection: Allow to choose the starting layer index for resin trap detection which will also be considered a drain layer. Use this setting to bypass complicated rafts by selected the model first real layer (#221) - (Improvement) Disable mirroed preview when loading a file that is not mirroed --- CHANGELOG.md | 10 ++ UVtools.Core/EmguCV/Contour.cs | 14 ++- UVtools.Core/Layer/LayerIssue.cs | 6 ++ UVtools.Core/Layer/LayerManager.cs | 4 +- .../Operations/OperationDoubleExposure.cs | 2 +- .../Operations/OperationLayerReHeight.cs | 34 ++++++- UVtools.Core/UVtools.Core.csproj | 2 +- .../Tools/ToolDoubleExposureControl.axaml | 2 +- .../Tools/ToolLayerReHeightControl.axaml | 31 ++++-- UVtools.WPF/MainWindow.Issues.cs | 25 ++++- UVtools.WPF/MainWindow.LayerPreview.cs | 68 +++++++++++++ UVtools.WPF/MainWindow.axaml | 45 ++++++++- UVtools.WPF/MainWindow.axaml.cs | 9 +- UVtools.WPF/UVtools.WPF.csproj | 2 +- UVtools.WPF/UserSettings.cs | 73 +++++++++++++- UVtools.WPF/Windows/SettingsWindow.axaml | 97 +++++++++++++++---- 16 files changed, 381 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c921acea..40444c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 06/09/2021 - v2.21.1 + +- **(Add) Layer outline:** + - Blob outline: Outline all separate blobs + - Centroids: Draw a dot at the gemoetric center of a blob +- (Add) Adjust layer height: Allow to change exposure time on the dialog and inform that different layer thickness require different exposure times +- (Add) Resin trap detection: Allow to choose the starting layer index for resin trap detection which will also be considered a drain layer. + Use this setting to bypass complicated rafts by selected the model first real layer (#221) +- (Improvement) Disable mirroed preview when loading a file that is not mirroed + ## 03/09/2021 - v2.21.0 - **UI:** diff --git a/UVtools.Core/EmguCV/Contour.cs b/UVtools.Core/EmguCV/Contour.cs index 20d216e1..291cad87 100644 --- a/UVtools.Core/EmguCV/Contour.cs +++ b/UVtools.Core/EmguCV/Contour.cs @@ -90,7 +90,7 @@ public double Perimeter /// /// Gets the centroid of the contour /// - public Point Centroid => _centroid ??= Moments.M00 == 0 ? Point.Empty : + public Point Centroid => _centroid ??= Moments.M00 == 0 ? new Point(-1,-1) : new Point( (int)Math.Round(_moments.M10 / _moments.M00), (int)Math.Round(_moments.M01 / _moments.M00)); @@ -211,6 +211,18 @@ public void FitCircle(Mat src, MCvScalar color, int thickness = 1, LineType line }*/ #endregion + #region Static methods + public static Point GetCentroid(VectorOfPoint points) + { + if (points is null || points.Length == 0) return new Point(-1, -1); + using var moments = CvInvoke.Moments(points); + return moments.M00 == 0 ? new Point(-1, -1) : + new Point( + (int)Math.Round(moments.M10 / moments.M00), + (int)Math.Round(moments.M01 / moments.M00)); + } + #endregion + #region Implementations public IEnumerator GetEnumerator() diff --git a/UVtools.Core/Layer/LayerIssue.cs b/UVtools.Core/Layer/LayerIssue.cs index 8b46e51d..598231ff 100644 --- a/UVtools.Core/Layer/LayerIssue.cs +++ b/UVtools.Core/Layer/LayerIssue.cs @@ -158,6 +158,12 @@ public sealed class ResinTrapDetectionConfiguration /// public bool Enabled { get; set; } = true; + /// + /// Gets or sets the starting layer index for the detection which will also be considered a drain layer. + /// Use this setting to bypass complicated rafts by selected the model first real layer. + /// + public uint StartLayerIndex { get; set; } + /// /// Gets or sets the binary threshold, all pixels below this value will turn in black, otherwise white /// Set to 0 to disable this operation diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs index 678c2989..ba1ed48d 100644 --- a/UVtools.Core/Layer/LayerManager.cs +++ b/UVtools.Core/Layer/LayerManager.cs @@ -1389,7 +1389,7 @@ island is null listHollowArea.Add(new LayerHollowArea(contours[i].ToArray(), rect, - layer.Index == 0 || + layer.Index <= resinTrapConfig.StartLayerIndex || layer.Index == LayerCount - 1 // First and Last layers, always drains ? LayerHollowArea.AreaType.Drain : LayerHollowArea.AreaType.Unknown)); @@ -1410,7 +1410,7 @@ island is null ResinTrapTree resinTrapTree = new(); - for (uint layerIndex = 1; layerIndex < LayerCount - 1; layerIndex++) // First and Last layers, always drains + for (uint layerIndex = resinTrapConfig.StartLayerIndex+1; layerIndex < LayerCount - 1; layerIndex++) // First and Last layers, always drains { if (progress.Token.IsCancellationRequested) break; if (!layerHollowAreas.TryGetValue(layerIndex, out var areas)) diff --git a/UVtools.Core/Operations/OperationDoubleExposure.cs b/UVtools.Core/Operations/OperationDoubleExposure.cs index 5de3e0e4..96068d9c 100644 --- a/UVtools.Core/Operations/OperationDoubleExposure.cs +++ b/UVtools.Core/Operations/OperationDoubleExposure.cs @@ -45,7 +45,7 @@ public class OperationDoubleExposure : Operation "The double exposure method clones the selected layer range and print the same layer twice with different exposure times and strategies.\n" + "Can be used to eliminate the elephant foot effect or to harden a layer in two steps.\n" + "After this, do not apply any modification which reconstruct the z positions of the layers.\n" + - "Note: To eliminate the elephant foot effect, the use of wall dimming method recommended."; + "Note: To eliminate the elephant foot effect, the use of wall dimming method is recommended."; public override string ConfirmationText => $"double exposure model layers {LayerIndexStart} through {LayerIndexEnd}"; diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs index 44f153d2..f9a86a65 100644 --- a/UVtools.Core/Operations/OperationLayerReHeight.cs +++ b/UVtools.Core/Operations/OperationLayerReHeight.cs @@ -38,6 +38,8 @@ public enum OperationLayerReHeightAntiAliasingType : byte #region Members private OperationLayerReHeightItem _selectedItem; private OperationLayerReHeightAntiAliasingType _antiAliasingType; + private decimal _bottomExposure; + private decimal _normalExposure; #endregion @@ -49,7 +51,8 @@ public enum OperationLayerReHeightAntiAliasingType : byte public override string Description => "Adjust the layer height of the model.\n\n" + "Adjusting to values lower than current height will reduce layer lines, adjusting to values higher" + - " than current height will reduce model detail.\n\n" + + " than current height will reduce model detail.\n" + + "Different layer thickness will require different exposure times, adjust accordingly.\n\n" + "Note: Using dedicated slicer software to re-slice will usually yeild better results."; public override string ConfirmationText => $"adjust layer height to {SelectedItem.LayerHeight}mm?"; @@ -87,7 +90,9 @@ public override string ValidateInternally() public override string ToString() { - var result = $"[Layer Count: {SelectedItem.LayerCount}] [Layer Height: {SelectedItem.LayerHeight}]" + LayerRangeString; + var result = $"[Layer Count: {SelectedItem.LayerCount}] " + + $"[Layer Height: {SelectedItem.LayerHeight}] " + + $"[Exposure: {_bottomExposure}/{_normalExposure}s]" + LayerRangeString; if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; return result; } @@ -115,6 +120,18 @@ public OperationLayerReHeightAntiAliasingType AntiAliasingType set => RaiseAndSetIfChanged(ref _antiAliasingType, value); } + public decimal BottomExposure + { + get => _bottomExposure; + set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2)); + } + + public decimal NormalExposure + { + get => _normalExposure; + set => RaiseAndSetIfChanged(ref _normalExposure, Math.Round(value, 2)); + } + public static OperationLayerReHeightItem[] GetItems(uint layerCount, decimal layerHeight) { @@ -158,6 +175,9 @@ public override void InitWithSlicerFile() { _selectedItem = Presets[0]; } + + if (_bottomExposure <= 0) _bottomExposure = (decimal)SlicerFile.BottomExposureTime; + if (_normalExposure <= 0) _normalExposure = (decimal)SlicerFile.ExposureTime; } #endregion @@ -297,8 +317,14 @@ protected override bool ExecuteInternally(OperationProgress progress) progress.Token.ThrowIfCancellationRequested(); - SlicerFile.LayerHeight = (float)SelectedItem.LayerHeight; - SlicerFile.LayerManager.Layers = layers; + SlicerFile.SuppressRebuildPropertiesWork(() => + { + SlicerFile.LayerHeight = (float)SelectedItem.LayerHeight; + SlicerFile.BottomExposureTime = (float)_bottomExposure; + SlicerFile.ExposureTime = (float)_normalExposure; + SlicerFile.LayerManager.Layers = layers; + }, true); + return !progress.Token.IsCancellationRequested; } diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index fc4b6367..268f8502 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ https://github.com/sn4k3/UVtools https://github.com/sn4k3/UVtools MSLA/DLP, file analysis, calibration, repair, conversion and manipulation - 2.21.0 + 2.21.1 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 diff --git a/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml b/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml index 56dd8eff..063ff3cc 100644 --- a/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml @@ -153,7 +153,7 @@ IsEnabled="{Binding Operation.SecondLayerDifference}" ToolTip.Tip="When the 'Exposure the difference between first and second layer for the second layer' is active, this setting will further erode the layer producing a overlap of n pixel perimeters over the previous layer" - Text="Difference overlap margin:"/> + Text="Difference overlap by:"/> - + + Items="{Binding Operation.Presets}"/> - + + + + + + + + diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index 1308b0e2..8acfc620 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -44,9 +44,17 @@ public RangeObservableCollection Issues } public readonly List IgnoredIssues = new(); + private uint _resinTrapDetectionStartLayer; public bool IssueCanGoPrevious => Issues.Count > 0 && _issueSelectedIndex > 0; public bool IssueCanGoNext => Issues.Count > 0 && _issueSelectedIndex < Issues.Count - 1; + + public uint ResinTrapDetectionStartLayer + { + get => _resinTrapDetectionStartLayer; + set => RaiseAndSetIfChanged(ref _resinTrapDetectionStartLayer, value); + } + #endregion #region Methods @@ -526,7 +534,21 @@ public void IssuesClear(bool clearIgnored = true) if(clearIgnored) IgnoredIssues.Clear(); } - + public void SetResinTrapDetectionStartLayer(char which) + { + switch (which) + { + case 'N': + ResinTrapDetectionStartLayer = SlicerFile.FirstNormalLayer.Index; + break; + case 'C': + ResinTrapDetectionStartLayer = ActualLayer; + break; + } + } + + + public IslandDetectionConfiguration GetIslandDetectionConfiguration(bool enable) { return new() @@ -561,6 +583,7 @@ public ResinTrapDetectionConfiguration GetResinTrapDetectionConfiguration(bool e return new() { Enabled = enable, + StartLayerIndex = _resinTrapDetectionStartLayer, BinaryThreshold = Settings.Issues.ResinTrapBinaryThreshold, RequiredAreaToProcessCheck = Settings.Issues.ResinTrapRequiredAreaToProcessCheck, RequiredBlackPixelsToDrain = Settings.Issues.ResinTrapRequiredBlackPixelsToDrain, diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs index fd53e1db..06097c17 100644 --- a/UVtools.WPF/MainWindow.LayerPreview.cs +++ b/UVtools.WPF/MainWindow.LayerPreview.cs @@ -26,6 +26,7 @@ using Emgu.CV.Util; using UVtools.AvaloniaControls; using UVtools.Core; +using UVtools.Core.EmguCV; using UVtools.Core.Extensions; using UVtools.Core.PixelEditor; using UVtools.WPF.Controls; @@ -74,7 +75,9 @@ public enum ZoomToFitType : byte private bool _isPixelEditorActive; private bool _showLayerOutlinePrintVolumeBoundary; private bool _showLayerOutlineLayerBoundary; + private bool _showLayerOutlineContourBoundary; private bool _showLayerOutlineHollowAreas; + private bool _showLayerOutlineCentroids; private bool _showLayerOutlineEdgeDetection; private bool _showLayerOutlineDistanceDetection; private bool _showLayerOutlineSkeletonize; @@ -107,7 +110,9 @@ public void InitLayerPreview() _showLayerImageDifference = Settings.LayerPreview.ShowLayerDifference; _showLayerOutlinePrintVolumeBoundary = Settings.LayerPreview.VolumeBoundsOutline; _showLayerOutlineLayerBoundary = Settings.LayerPreview.LayerBoundsOutline; + _showLayerOutlineContourBoundary = Settings.LayerPreview.ContourBoundsOutline; _showLayerOutlineHollowAreas = Settings.LayerPreview.HollowOutline; + _showLayerOutlineCentroids = Settings.LayerPreview.CentroidOutline; LayerImageBox.ZoomLevels = new AdvancedImageBox.ZoomLevelCollection(AppSettings.ZoomLevels); @@ -370,6 +375,16 @@ public bool ShowLayerOutlineLayerBoundary } } + public bool ShowLayerOutlineContourBoundary + { + get => _showLayerOutlineContourBoundary; + set + { + if (!RaiseAndSetIfChanged(ref _showLayerOutlineContourBoundary, value)) return; + ShowLayer(); + } + } + public bool ShowLayerOutlineHollowAreas { get => _showLayerOutlineHollowAreas; @@ -380,6 +395,16 @@ public bool ShowLayerOutlineHollowAreas } } + public bool ShowLayerOutlineCentroids + { + get => _showLayerOutlineCentroids; + set + { + if (!RaiseAndSetIfChanged(ref _showLayerOutlineCentroids, value)) return; + ShowLayer(); + } + } + public bool ShowLayerOutlineEdgeDetection { get => _showLayerOutlineEdgeDetection; @@ -983,6 +1008,21 @@ unsafe void ShowLayer() Settings.LayerPreview.LayerBoundsOutlineThickness); } + if (_showLayerOutlineContourBoundary) + { + for (int i = 0; i < LayerCache.LayerContours.Size; i++) + { + if ((int)LayerCache.LayerHierarchyJagged.GetValue(0, i, 2) == -1 && + (int)LayerCache.LayerHierarchyJagged.GetValue(0, i, 3) != -1) continue; + CvInvoke.Rectangle(LayerCache.ImageBgr, CvInvoke.BoundingRectangle(LayerCache.LayerContours[i]), + new MCvScalar( + Settings.LayerPreview.ContourBoundsOutlineColor.B, + Settings.LayerPreview.ContourBoundsOutlineColor.G, + Settings.LayerPreview.ContourBoundsOutlineColor.R), + Settings.LayerPreview.ContourBoundsOutlineThickness); + } + } + if (_showLayerOutlineHollowAreas) { //CvInvoke.Threshold(ActualLayerImage, grayscale, 1, 255, ThresholdType.Binary); @@ -1009,6 +1049,34 @@ unsafe void ShowLayer() } } + if (_showLayerOutlineCentroids) + { + for (int i = 0; i < LayerCache.LayerContours.Size; i++) + { + if ((int)LayerCache.LayerHierarchyJagged.GetValue(0, i, 2) == -1 && + (int)LayerCache.LayerHierarchyJagged.GetValue(0, i, 3) != -1) + { + if (Settings.LayerPreview.CentroidOutlineHollow) + { + CvInvoke.Circle(LayerCache.ImageBgr, Contour.GetCentroid(LayerCache.LayerContours[i]), + Settings.LayerPreview.CentroidOutlineDiameter / 2, new MCvScalar( + Settings.LayerPreview.HollowOutlineColor.B, + Settings.LayerPreview.HollowOutlineColor.G, + Settings.LayerPreview.HollowOutlineColor.R), 1, LineType.AntiAlias); + } + } + else + { + CvInvoke.Circle(LayerCache.ImageBgr, Contour.GetCentroid(LayerCache.LayerContours[i]), + Settings.LayerPreview.CentroidOutlineDiameter / 2, new MCvScalar( + Settings.LayerPreview.CentroidOutlineColor.B, + Settings.LayerPreview.CentroidOutlineColor.G, + Settings.LayerPreview.CentroidOutlineColor.R), -1, LineType.AntiAlias); + } + + } + } + if (_maskPoints is not null && _maskPoints.Count > 0) { using var vec = new VectorOfVectorOfPoint(_maskPoints.ToArray()); diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 6c98575b..35fe48c0 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -786,9 +786,39 @@ - + + + + + +