/* * GNU AFFERO GENERAL PUBLIC LICENSE * Version 3, 19 November 2007 * Copyright (C) 2007 Free Software Foundation, Inc. * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Input; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Threading; using Emgu.CV; using Emgu.CV.Util; using MessageBox.Avalonia.Enums; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using UVtools.Core; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; using UVtools.Core.Layers; using UVtools.Core.Objects; using UVtools.Core.Operations; using UVtools.WPF.Extensions; using Brushes = Avalonia.Media.Brushes; namespace UVtools.WPF; public partial class MainWindow { #region Members private bool _firstTimeOnIssues = true; public DataGrid IssuesGrid; private int _issueSelectedIndex = -1; public IEnumerable IssuesGridItems { get { if (!IsFileLoaded || DataContext is null) return null; if (Settings.Issues.DataGridGroupByType || Settings.Issues.DataGridGroupByLayerIndex) { var groupView = new DataGridCollectionView(SlicerFile.IssueManager); if (Settings.Issues.DataGridGroupByType) groupView.GroupDescriptions.Add(new DataGridPathGroupDescription("Type")); if (Settings.Issues.DataGridGroupByLayerIndex) groupView.GroupDescriptions.Add(new DataGridPathGroupDescription("StartLayerIndex")); return groupView; } return SlicerFile.IssueManager; } } #endregion #region Properties private uint _resinTrapDetectionStartLayer; public bool IssueCanGoPrevious => IsFileLoaded && SlicerFile.IssueManager.Count > 0 && _issueSelectedIndex > 0; public bool IssueCanGoNext => IsFileLoaded && SlicerFile.IssueManager.Count > 0 && _issueSelectedIndex < SlicerFile.IssueManager.Count - 1; public uint ResinTrapDetectionStartLayer { get => _resinTrapDetectionStartLayer; set => RaiseAndSetIfChanged(ref _resinTrapDetectionStartLayer, value); } public bool SuppressIssueGridSelectionEvent { get; set; } #endregion #region Methods public void InitIssues() { IssuesGrid = this.FindControl("IssuesGrid"); IssuesGrid.CellPointerPressed += IssuesGridOnCellPointerPressed; IssuesGrid.SelectionChanged += IssuesGridOnSelectionChanged; IssuesGrid.KeyUp += IssuesGridOnKeyUp; } public void IssueGoPrevious() { if (!IssueCanGoPrevious) return; IssueSelectedIndex--; } public void IssueGoNext() { if (!IssueCanGoNext) return; IssueSelectedIndex++; } /*public List GetOverlappingIssues(IssueOfContours targetIssue, int indexOffset) { var retValue = new List(); int targetLayerIndex = (int)targetIssue.LayerIndex + indexOffset; if (targetLayerIndex > SlicerFile.LayerCount - 1 || targetLayerIndex < 0) return retValue; foreach (IssueOfContours candidate in SlicerFile.IssueManager.GetIssuesBy(MainIssue.IssueType.SuctionCup, (uint)targetLayerIndex)) { using var vec1 = new VectorOfVectorOfPoint(targetIssue.Contours); using var vec2 = new VectorOfVectorOfPoint(candidate.Contours); if (!EmguContours.ContoursIntersect(vec1, vec2)) continue; retValue.Add(candidate); break; } return retValue; }*/ public async void RemoveRepairIssues(IEnumerable issues, bool promptConfirmation = true, bool suctionCupDrill = true) { if (!issues.Any()) return; uint emptyLayers = 0; uint islands = 0; uint resinTraps = 0; uint suctionCups = 0; foreach (MainIssue mainIssue in issues) { switch (mainIssue.Type) { case MainIssue.IssueType.Island: islands++; break; case MainIssue.IssueType.Overhang: // No action break; case MainIssue.IssueType.ResinTrap: resinTraps++; break; case MainIssue.IssueType.SuctionCup: suctionCups++; break; case MainIssue.IssueType.TouchingBound: // No action break; case MainIssue.IssueType.PrintHeight: // No action break; case MainIssue.IssueType.EmptyLayer: emptyLayers++; break; case MainIssue.IssueType.Debug: // No action break; default: throw new ArgumentOutOfRangeException(); } } if (emptyLayers == 0 && islands == 0 && resinTraps == 0 && suctionCups == 0) return; if (promptConfirmation && await this.MessageBoxQuestion( $"Are you sure you want to remove and/or repair all selected {issues.Count()} issues?\n\n" + "If any, this option will:\n" + (emptyLayers > 0 ? $"- Remove {emptyLayers} empty layer(s)\n" : string.Empty) + (islands > 0 ? $"- Remove {emptyLayers} island(s)\n" : string.Empty) + (resinTraps > 0 ? $"- Fill/solidify {resinTraps} resin trap(s)\n" : string.Empty) + (suctionCups > 0 ? $"- Drill {suctionCups} suction cup(s) at it's center\n" : string.Empty) + "\nWarning: 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.Count()} Issues?") != ButtonResult.Yes) return; var processParallelIssues = new Dictionary>(); var processSuctionCups = new List(); var layersToRemove = new List(); foreach (MainIssue mainIssue in issues) { switch (mainIssue.Type) { case MainIssue.IssueType.Island: case MainIssue.IssueType.ResinTrap: foreach (var issue in mainIssue) { // Islands and resin traps if (!processParallelIssues.TryGetValue(issue.LayerIndex, out var issueList)) { issueList = new List(); processParallelIssues.Add(issue.LayerIndex, issueList); } issueList.Add(issue); } continue; case MainIssue.IssueType.SuctionCup: if (!suctionCupDrill) { foreach (var issue in mainIssue) { // Islands and resin traps if (!processParallelIssues.TryGetValue(issue.LayerIndex, out var issueList)) { issueList = new List(); processParallelIssues.Add(issue.LayerIndex, issueList); } issueList.Add(issue); } continue; } if(mainIssue.StartLayerIndex == 0) continue; processSuctionCups.Add(mainIssue); continue; case MainIssue.IssueType.EmptyLayer: layersToRemove.AddRange(mainIssue.Select(issue => issue.LayerIndex)); continue; } } var totalIssues = processParallelIssues.Count + processSuctionCups.Count + layersToRemove.Count; if (totalIssues == 0) return; var issueRemoveList = new List(); IsGUIEnabled = false; ShowProgressWindow("Removing selected issues", false); Clipboard.Snapshot(); var task = await Task.Run(() => { Progress.Reset("Removing selected issues", (uint)processParallelIssues.Count); try { Parallel.ForEach(processParallelIssues, CoreSettings.GetParallelOptions(Progress), layerIssues => { using (var image = SlicerFile[layerIssues.Key].LayerMat) { var bytes = image.GetDataByteSpan(); bool edited = false; foreach (var issue in layerIssues.Value) { if (issue.Type == MainIssue.IssueType.Island) { var issueOfPoints = (IssueOfPoints)issue; foreach (var pixel in issueOfPoints.Points) { bytes[image.GetPixelPos(pixel.X, pixel.Y)] = 0; } edited = true; } else if (issue.Type == MainIssue.IssueType.ResinTrap || (issue.Type == MainIssue.IssueType.SuctionCup && !suctionCupDrill)) { var issueOfContours = (IssueOfContours)issue; using var contours = new VectorOfVectorOfPoint(issueOfContours.Contours); CvInvoke.DrawContours(image, contours, -1, EmguExtensions.WhiteColor, -1); if (Settings.LayerRepair.ResinTrapsOverlapBy > 0) { CvInvoke.DrawContours(image, contours, -1, EmguExtensions.WhiteColor, Settings.LayerRepair.ResinTrapsOverlapBy * 2 + 1); } edited = true; } } if (edited) { SlicerFile[layerIssues.Key].LayerMat = image; } } Progress.LockAndIncrement(); }); if (layersToRemove.Count > 0) { OperationLayerRemove.RemoveLayers(SlicerFile, layersToRemove); } if(suctionCupDrill) issueRemoveList.AddRange(SlicerFile.IssueManager.DrillSuctionCupsForIssues(processSuctionCups, UserSettings.Instance.LayerRepair.SuctionCupsVentHole, Progress)); } catch (Exception ex) { Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(ex.ToString(), "Removal/repair failed")); return false; } return true; }, Progress.Token); IsGUIEnabled = true; if (!task) { Clipboard.RestoreSnapshot(); return; } var whiteListLayers = new List(); // Update GUI foreach (MainIssue issue in issues) { if (issue.IsSuctionCup && !suctionCupDrill) { issueRemoveList.Add(issue); continue; } if (issue.Type is not MainIssue.IssueType.Island and not MainIssue.IssueType.ResinTrap and not MainIssue.IssueType.EmptyLayer) continue; issueRemoveList.Add(issue); if (issue.IsIsland) { var nextLayer = issue.StartLayerIndex + 1; if (nextLayer >= SlicerFile.LayerCount) continue; if (whiteListLayers.Contains(nextLayer)) continue; whiteListLayers.Add(nextLayer); } //Issues.Remove(issue); } if (issueRemoveList.Count == 0) return; Clipboard.Clip($"Manually removed {issueRemoveList.Count} issues"); IssuesGrid.SelectedIndex = -1; SlicerFile.IssueManager.RemoveRange(issueRemoveList); if (layersToRemove.Count > 0) { ResetDataContext(); } if (Settings.PixelEditor.PartialUpdateIslandsOnEditing) { await UpdateIslandsOverhangs(whiteListLayers); } ShowLayer(); // It will call latter so its a extra call CanSave = true; } public void OnClickIssueRemove() { RemoveRepairIssues(IssuesGrid.SelectedItems.OfType(), true); } public void SelectedIssuesIslandRemove() { if (IssuesGrid.SelectedItem is null) return; RemoveRepairIssues(IssuesGrid.SelectedItems.OfType().Where(mainIssue => mainIssue.IsIsland), false); } public void SelectedIssuesResinTrapSolidify() { if (IssuesGrid.SelectedItem is null) return; RemoveRepairIssues(IssuesGrid.SelectedItems.OfType().Where(mainIssue => mainIssue.IsResinTrap), false); } public void SelectedIssuesSuctionCupDrill() { if (IssuesGrid.SelectedItem is null) return; RemoveRepairIssues(IssuesGrid.SelectedItems.OfType().Where(mainIssue => mainIssue.IsSuctionCup), false); } public void SelectedIssuesSuctionCupSolidify() { if (IssuesGrid.SelectedItem is null) return; RemoveRepairIssues(IssuesGrid.SelectedItems.OfType().Where(mainIssue => mainIssue.IsSuctionCup), false, false); } public void SelectedIssuesEmptyLayerRemove() { if (IssuesGrid.SelectedItem is null) return; RemoveRepairIssues(IssuesGrid.SelectedItems.OfType().Where(mainIssue => mainIssue.IsEmptyLayer), false); } public async void OnClickIssueIgnore() { if ((_globalModifiers & KeyModifiers.Alt) != 0) { if(SlicerFile.IssueManager.IgnoredIssues.Count == 0) return; if (await this.MessageBoxQuestion( $"Are you sure you want to re-enable {SlicerFile.IssueManager.IgnoredIssues.Count} ignored issues?\n" + "A full re-detect will be required to get the ignored issues.\n", $"Re-enable {SlicerFile.IssueManager.IgnoredIssues.Count} Issues?") != ButtonResult.Yes) return; SlicerFile.IssueManager.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().ToArray(); SlicerFile.IssueManager.IgnoredIssues.AddRange(list); IssuesGrid.SelectedItems.Clear(); SlicerFile.IssueManager.RemoveRange(list); ShowLayer(); } private async Task UpdateIslandsOverhangs(List whiteListLayers) { if (whiteListLayers.Count == 0) return; var config = GetIssuesDetectionConfiguration(false); config.IslandConfig.Enable(); config.IslandConfig.WhiteListLayers = whiteListLayers; config.OverhangConfig.Enable(); config.OverhangConfig.WhiteListLayers = whiteListLayers; IsGUIEnabled = false; ShowProgressWindow("Updating Issues"); var issueList = SlicerFile.IssueManager.ToList(); issueList.RemoveAll(issue => config.IslandConfig.WhiteListLayers.Contains(issue.StartLayerIndex) && issue.Type is MainIssue.IssueType.Island or MainIssue.IssueType.Overhang); /*foreach (var layerIndex in islandConfig.WhiteListLayers) { issueList.RemoveAll(issue => issue.LayerIndex == layerIndex && (issue.Type == LayerIssue.IssueType.Island || issue.Type == LayerIssue.IssueType.Overhang)); }*/ var resultIssues = await Task.Run(() => { try { var issues = SlicerFile.IssueManager.DetectIssues(config, Progress); issues.RemoveAll(issue => issue.Type is not MainIssue.IssueType.Island and not MainIssue.IssueType.Overhang); // Remove all non islands and overhangs return issues; } catch (OperationCanceledException) { } catch (Exception ex) { Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(ex.ToString(), "Error while trying to compute issues")); } return null; }, Progress.Token); IsGUIEnabled = true; if (resultIssues is not null && resultIssues.Count > 0) issueList.AddRange(resultIssues); switch (Settings.Issues.DataGridOrderBy) { case IssuesOrderBy.TypeAscLayerAscAreaDesc: issueList = issueList.OrderBy(issue => issue.Type) .ThenBy(issue => issue.StartLayerIndex) .ThenByDescending(issue => issue.Area).ToList(); break; case IssuesOrderBy.TypeAscAreaDescLayerAsc: issueList = issueList.OrderBy(issue => issue.Type) .ThenByDescending(issue => issue.Area) .ThenBy(issue => issue.StartLayerIndex).ToList(); break; case IssuesOrderBy.AreaDescLayerIndexAscTypeAsc: issueList = issueList.OrderByDescending(issue => issue.Area) .ThenBy(issue => issue.StartLayerIndex) .ThenBy(issue => issue.Type).ToList(); break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Issues.DataGridOrderBy)); } SlicerFile.IssueManager.ReplaceCollection(issueList); } public int IssueSelectedIndex { get => _issueSelectedIndex; set { if (!RaiseAndSetIfChanged(ref _issueSelectedIndex, value)) return; if(_issueSelectedIndex > 0) IssuesGrid.ScrollIntoView(SlicerFile.IssueManager.FirstOrDefault(issue => ReferenceEquals(issue, IssuesGrid.SelectedItem)), null); RaisePropertyChanged(nameof(IssueSelectedIndexStr)); RaisePropertyChanged(nameof(IssueCanGoPrevious)); RaisePropertyChanged(nameof(IssueCanGoNext)); } } public string IssueSelectedIndexStr => IsFileLoaded ? (_issueSelectedIndex + 1).ToString().PadLeft(SlicerFile.IssueManager.Count.ToString().Length, '0') : "0"; private void IssuesGridOnSelectionChanged(object? sender, SelectionChangedEventArgs e) { if (DataContext is null || SuppressIssueGridSelectionEvent) return; if (IssuesGrid.SelectedItem is not MainIssue mainIssue) { ShowLayer(); return; } var issue = mainIssue.FirstOrDefault(); ZoomToIssue(issue, true); } private void IssuesGridOnCellPointerPressed(object? sender, DataGridCellPointerPressedEventArgs e) { if (e.PointerPressedEventArgs.ClickCount == 2) return; if (IssuesGrid.SelectedItem is not MainIssue) return; // Double clicking an issue will center and zoom into the // selected issue. Left click on an issue will zoom to fit. var pointer = e.PointerPressedEventArgs.GetCurrentPoint(IssuesGrid); if (pointer.Properties.IsRightButtonPressed) { ZoomToFit(); return; } //ForceUpdateActualLayer(issue.LayerIndex); } private void IssuesGridOnKeyUp(object? sender, KeyEventArgs e) { switch (e.Key) { case Key.Escape: IssuesGrid.SelectedItems.Clear(); break; case Key.Multiply: var selectedItems = IssuesGrid.SelectedItems.OfType().ToList(); IssuesGrid.SelectedItems.Clear(); foreach (var item in SlicerFile.IssueManager) { if (!selectedItems.Contains(item)) IssuesGrid.SelectedItems.Add(item); } break; case Key.Delete: OnClickIssueRemove(); break; } } public async void OnClickRepairIssues() { await ShowRunOperation(typeof(OperationRepairLayers)); } public async void OnClickExportIssues() { if (!SlicerFile.IssueManager.HaveIssues) return; var dialog = new SaveFileDialog { DefaultExtension = ".uvtissues", Filters = new List { new() { Name = "UVtools issues files", Extensions = new List {"uvtissues"} } }, InitialFileName = SlicerFile.FilenameNoExt, Directory = SlicerFile.DirectoryPath }; var path = await dialog.ShowAsync(this); if (string.IsNullOrWhiteSpace(path)) return; IsGUIEnabled = false; try { var exportIssues = new SerializableIssuesDocument(SlicerFile); exportIssues.SerializeAsync(path); } catch (Exception e) { await this.MessageBoxError(e.ToString()); Debug.WriteLine(e); if(File.Exists(path)) File.Delete(path); } IsGUIEnabled = true; } public async Task OnClickDetectIssues() { if (!IsFileLoaded) return; if (SlicerFile.DecodeType == FileFormat.FileDecodeType.Partial) { await this.MessageBoxError("The file was open in partial mode and the detect issues is unable to run in this mode.\n" + "Please reload the file in full mode in order to use detect issues.", "Unable to run in partial mode"); return; } await ComputeIssues(GetIssuesDetectionConfiguration()); } private async Task ComputeIssues(IssuesDetectionConfiguration config) { SlicerFile.IssueManager.Clear(); IsGUIEnabled = false; ShowProgressWindow("Computing Issues"); var resultIssues = await Task.Run(() => { try { var issues = SlicerFile.IssueManager.DetectIssues(config, Progress); switch (Settings.Issues.DataGridOrderBy) { case IssuesOrderBy.TypeAscLayerAscAreaDesc: // This order is already made on the detection break; case IssuesOrderBy.TypeAscAreaDescLayerAsc: issues = issues.OrderBy(issue => issue.Type) .ThenByDescending(issue => issue.Area) .ThenBy(issue => issue.StartLayerIndex).ToList(); break; case IssuesOrderBy.AreaDescLayerIndexAscTypeAsc: issues = issues.OrderByDescending(issue => issue.Area) .ThenBy(issue => issue.StartLayerIndex) .ThenBy(issue => issue.Type).ToList(); break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Issues.DataGridOrderBy)); } return issues; } catch (OperationCanceledException) { } catch (Exception ex) { Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(ex.ToString(), "Error while trying compute issues")); } return null; }, Progress.Token); IsGUIEnabled = true; if (resultIssues is null) { return; } SlicerFile.IssueManager.AddRange(resultIssues); ShowLayer(); RaisePropertyChanged(nameof(IssueSelectedIndexStr)); RaisePropertyChanged(nameof(IssueCanGoPrevious)); RaisePropertyChanged(nameof(IssueCanGoNext)); } /*public Dictionary GetIssuesCountPerLayer() { if (SlicerFile.IssueManager.Count == 0) return null; Dictionary layerIndexIssueCount = new(); foreach (var issue in Issues) { if (!layerIndexIssueCount.ContainsKey(issue.StartLayerIndex)) { layerIndexIssueCount.Add(issue.LayerIndex, 1); } else { layerIndexIssueCount[issue.LayerIndex]++; } } return layerIndexIssueCount; }*/ public Dictionary GetIssueColors(bool highlightColors = false) { return new Dictionary { {MainIssue.IssueType.Island, highlightColors ? Settings.LayerPreview.IslandHighlightBrush : Settings.LayerPreview.IslandBrush}, {MainIssue.IssueType.Overhang, highlightColors ? Settings.LayerPreview.OverhangHighlightBrush : Settings.LayerPreview.OverhangBrush}, {MainIssue.IssueType.ResinTrap, highlightColors ? Settings.LayerPreview.ResinTrapHighlightBrush : Settings.LayerPreview.ResinTrapBrush}, {MainIssue.IssueType.SuctionCup, highlightColors ? Settings.LayerPreview.SuctionCupHighlightBrush : Settings.LayerPreview.SuctionCupBrush}, {MainIssue.IssueType.TouchingBound, Settings.LayerPreview.TouchingBoundsBrush}, {MainIssue.IssueType.EmptyLayer, Brushes.Red}, {MainIssue.IssueType.PrintHeight, Brushes.Red}, {MainIssue.IssueType.Debug, new ImmutableSolidColorBrush(new Color(255, 15, 112, 16))}, }; } private void UpdateLayerTrackerHighlightIssues() { _issuesSliderCanvas.Children.Clear(); if (!IsFileLoaded || SlicerFile.IssueManager.Count == 0) return; var tickFrequencySize = _issuesSliderCanvas.Bounds.Height * LayerSlider.TickFrequency / LayerSlider.Maximum; var stroke = (int)Math.Ceiling(tickFrequencySize); var colorDictionary = GetIssueColors(true); var issues = SlicerFile.IssueManager.GetIssues().OrderBy(issue => issue.Parent.Type).DistinctBy(mainIssue => mainIssue.LayerIndex); foreach (var issue in issues) { var color = Brushes.Red; if (Settings.LayerPreview.UseIssueColorOnTracker) colorDictionary.TryGetValue(issue.Parent.Type, out color); var yPos = tickFrequencySize * issue.LayerIndex; if (issue.LayerIndex == 0 && stroke > 3) { yPos += tickFrequencySize / 2; } else if (issue.LayerIndex == SlicerFile.LastLayerIndex && stroke > 3) { yPos -= tickFrequencySize / 2; } var line = new Line { StrokeThickness = stroke, Stroke = color, EndPoint = new Avalonia.Point(_issuesSliderCanvas.Width, 0) }; _issuesSliderCanvas.Children.Add(line); Canvas.SetBottom(line, yPos); } /*for (int layerIndex = 0; layerIndex < SlicerFile.LayerCount; layerIndex++) { var color = Brushes.Red; if(Settings.LayerPreview.UseIssueColorOnTracker) colorDictionary.TryGetValue(issue.Type, out color); var yPos = tickFrequencySize * layerIndex; if (layerIndex == 0 && stroke > 3) { yPos += tickFrequencySize / 2; } else if (layerIndex == SlicerFile.LastLayerIndex && stroke > 3) { yPos -= tickFrequencySize / 2; } var line = new Line { StrokeThickness = stroke, Stroke = color, EndPoint = new Avalonia.Point(_issuesSliderCanvas.Width, 0) }; _issuesSliderCanvas.Children.Add(line); Canvas.SetBottom(line, yPos); }*/ /*var issuesCountPerLayer = GetIssuesCountPerLayer(); if (issuesCountPerLayer is null) { return; } //var tickFrequencySize = LayerSlider.Track.Bounds.Height * LayerSlider.TickFrequency / (LayerSlider.Maximum - LayerSlider.Minimum); var tickFrequencySize = _issuesSliderCanvas.Bounds.Height * LayerSlider.TickFrequency / (LayerSlider.Maximum - LayerSlider.Minimum); var stroke = (int)Math.Ceiling(tickFrequencySize); foreach (var value in issuesCountPerLayer) { var yPos = tickFrequencySize * value.Key; if (value.Key == 0 && stroke > 3) { yPos += tickFrequencySize / 2; } else if(value.Key == SlicerFile.LastLayerIndex && stroke > 3) { yPos -= tickFrequencySize / 2; } var line = new Line{StrokeThickness = stroke, Stroke = Brushes.Red, EndPoint = new Avalonia.Point(_issuesSliderCanvas.Width, 0)}; _issuesSliderCanvas.Children.Add(line); Canvas.SetBottom(line, yPos); }*/ } public void IssuesClear(bool clearIgnored = true) { if (!IsFileLoaded) return; SlicerFile.IssueManager.Clear(); if(clearIgnored) SlicerFile.IssueManager.IgnoredIssues.Clear(); } public void SetResinTrapDetectionStartLayer(char which) { switch (which) { case 'N': ResinTrapDetectionStartLayer = SlicerFile.FirstNormalLayer.Index; break; case 'C': ResinTrapDetectionStartLayer = ActualLayer; break; } } public IssuesDetectionConfiguration GetIssuesDetectionConfiguration() { return new IssuesDetectionConfiguration( GetIslandDetectionConfiguration(), GetOverhangDetectionConfiguration(), GetResinTrapDetectionConfiguration(), GetTouchingBoundsDetectionConfiguration(), GetPrintHeightDetectionConfiguration(), GetEmptyLayerDetectionConfiguration() ); } public IssuesDetectionConfiguration GetIssuesDetectionConfiguration(bool enable) { return new IssuesDetectionConfiguration( GetIslandDetectionConfiguration(enable), GetOverhangDetectionConfiguration(enable), GetResinTrapDetectionConfiguration(enable), GetTouchingBoundsDetectionConfiguration(enable), GetPrintHeightDetectionConfiguration(enable), GetEmptyLayerDetectionConfiguration(enable) ); } public IslandDetectionConfiguration GetIslandDetectionConfiguration(bool enable) { return new() { Enabled = enable, EnhancedDetection = Settings.Issues.IslandEnhancedDetection, AllowDiagonalBonds = Settings.Issues.IslandAllowDiagonalBonds, BinaryThreshold = Settings.Issues.IslandBinaryThreshold, RequiredAreaToProcessCheck = Settings.Issues.IslandRequiredAreaToProcessCheck, RequiredPixelBrightnessToProcessCheck = Settings.Issues.IslandRequiredPixelBrightnessToProcessCheck, RequiredPixelsToSupportMultiplier = Settings.Issues.IslandRequiredPixelsToSupportMultiplier, RequiredPixelsToSupport = Settings.Issues.IslandRequiredPixelsToSupport, RequiredPixelBrightnessToSupport = Settings.Issues.IslandRequiredPixelBrightnessToSupport }; } public IslandDetectionConfiguration GetIslandDetectionConfiguration() => GetIslandDetectionConfiguration(Settings.Issues.ComputeIslands); public OverhangDetectionConfiguration GetOverhangDetectionConfiguration(bool enable) { return new() { Enabled = enable, IndependentFromIslands = Settings.Issues.OverhangIndependentFromIslands, ErodeIterations = Settings.Issues.OverhangErodeIterations, }; } public OverhangDetectionConfiguration GetOverhangDetectionConfiguration() => GetOverhangDetectionConfiguration(Settings.Issues.ComputeOverhangs); public ResinTrapDetectionConfiguration GetResinTrapDetectionConfiguration(bool enable) { return new() { Enabled = enable, StartLayerIndex = _resinTrapDetectionStartLayer, BinaryThreshold = Settings.Issues.ResinTrapBinaryThreshold, RequiredAreaToProcessCheck = Settings.Issues.ResinTrapRequiredAreaToProcessCheck, RequiredBlackPixelsToDrain = Settings.Issues.ResinTrapRequiredBlackPixelsToDrain, MaximumPixelBrightnessToDrain = Settings.Issues.ResinTrapMaximumPixelBrightnessToDrain, DetectSuctionCups = Settings.Issues.ComputeSuctionCups, RequiredAreaToConsiderSuctionCup = Settings.Issues.SuctionCupRequiredAreaToConsider, RequiredHeightToConsiderSuctionCup = Settings.Issues.SuctionCupRequiredHeightToConsider }; } public ResinTrapDetectionConfiguration GetResinTrapDetectionConfiguration() => GetResinTrapDetectionConfiguration(Settings.Issues.ComputeResinTraps); public TouchingBoundDetectionConfiguration GetTouchingBoundsDetectionConfiguration(bool enable) { return new() { Enabled = enable, 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, }; } public TouchingBoundDetectionConfiguration GetTouchingBoundsDetectionConfiguration() => GetTouchingBoundsDetectionConfiguration(Settings.Issues.ComputeTouchingBounds); public PrintHeightDetectionConfiguration GetPrintHeightDetectionConfiguration(bool enable) { return new () { Enabled = enable, Offset = (float) Settings.Issues.PrintHeightOffset }; } public PrintHeightDetectionConfiguration GetPrintHeightDetectionConfiguration() => GetPrintHeightDetectionConfiguration(Settings.Issues.ComputePrintHeight); public EmptyLayerDetectionConfiguration GetEmptyLayerDetectionConfiguration(bool enable) { return new() { Enabled = enable, }; } public EmptyLayerDetectionConfiguration GetEmptyLayerDetectionConfiguration() => GetEmptyLayerDetectionConfiguration(Settings.Issues.ComputeEmptyLayers); #endregion }