mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-31 21:02:15 +02:00
* (Upgrade) EmguCV/OpenCV to v4.5.1
* (Upgrade) AvaloniaUI to 1.0
* (Improvement) GUI re-touched
* (Improvement) Make pixel editor tab to disappear when pixel editor is disabled
* (Improvement) Simplify the output filename from PrusaSlicer profiles
* (Improvement) All operations require a slicer file at constructor rather than on execute, this allow exposure the open file to the operation before run it
* (Improvement) Calibrations: Auto set "Mirror Output" if open file have MirrorDisplay set
* (Change) Tool - Redraw model/supports icon
* (Change) photon and cbddlp to use version 3 by default
* (Add) Tool - Dynamic layer height: Analyze and optimize the model with dynamic layer heights, larger angles will slice at lower layer height
while more straight angles will slice larger layer height. (#131)
* (Add) Calibration - Exposure time finder: Generates test models with various strategies and increments to verify the best exposure time for a given layer height
* (Add) File load checks, trigger error when a file have critical errors and attempt to fix non-critical errors
* Layers must have an valid image, otherwise trigger an error
* Layers must have a incremental or equal position Z than it previous, otherwise trigger an error
* If layer 0 starts at 0mm it will auto fix all layers, it will add Layer Height to the current z at every layer
* (Add) Tool - Edit print parameters: Allow set parameters to each x layers and skip n layers inside the given range.
This allow the use of optimizations in a layer pattern, for example, to set 3s for a layer but 2.5s for the next.
* (Add) Layer height property to "Layer Data" table: Shows layer height for the slice
* (Fix) When automations applied and file is saved, it will not warn user about file overwrite for the first time save
* (Fix) Tool - Redraw model/supports: Disable apply button when no file selected
* (Fix) Tool - Infill: Lack of equality member to test if same infill profile already exists
* (Fix) Auto converted files from SL1 where clipping filename at first dot (.), now it only strips known extensions
* (Fix) SL1 encoded files wasn't generating the right information and lead to printer crash
* (Fix) PrusaSlicer printer "Anycubic Photon S" LiftSpeed was missing and contains a typo (#135)
* (Fix) PrusaSlicer profile manager wasnt marking missing profiles to be installed (#135)
* (Fix) PrusaSlicer folder search on linux to also look at %HOME%/.config/PrusaSlicer (#135, #136)
* (Fix) Operations were revised and some bug fixed, most about can't cancel the progress
* (Fix) Some typos on tooltips
* (Fix) Prevent PhotonS from enconding, it will trigger error now as this format is read-only
* **(Fix) Ctrl + Shift + Z to redo the last operation:**
* The layer range is reseted instead of pull the used values
* Tool - Arithmetic always disabled
* Action - Layer import didn't generate info and always disabled
272 lines
9.8 KiB
C#
272 lines
9.8 KiB
C#
/*
|
|
* GNU AFFERO GENERAL PUBLIC LICENSE
|
|
* Version 3, 19 November 2007
|
|
* Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
* Everyone is permitted to copy and distribute verbatim copies
|
|
* of this license document, but changing it is not allowed.
|
|
*/
|
|
|
|
using System;
|
|
using System.Drawing;
|
|
using System.Threading.Tasks;
|
|
using Emgu.CV;
|
|
using Emgu.CV.CvEnum;
|
|
using Emgu.CV.Structure;
|
|
using UVtools.Core.Extensions;
|
|
using UVtools.Core.FileFormats;
|
|
|
|
namespace UVtools.Core.Operations
|
|
{
|
|
[Serializable]
|
|
public class OperationRaftRelief : Operation
|
|
{
|
|
#region Enums
|
|
public enum RaftReliefTypes : byte
|
|
{
|
|
Relief,
|
|
Dimming,
|
|
Decimate
|
|
}
|
|
#endregion
|
|
|
|
#region Members
|
|
private RaftReliefTypes _reliefType = RaftReliefTypes.Relief;
|
|
private byte _ignoreFirstLayers;
|
|
private byte _brightness;
|
|
private byte _dilateIterations = 10;
|
|
private byte _wallMargin = 20;
|
|
private byte _holeDiameter = 50;
|
|
private byte _holeSpacing = 20;
|
|
#endregion
|
|
|
|
#region Overrides
|
|
public override string Title => "Raft relief";
|
|
public override string Description =>
|
|
"Relief raft by adding holes in between to reduce FEP suction, save resin and easier to remove the prints.";
|
|
|
|
public override string ConfirmationText =>
|
|
$"relief the raft";
|
|
|
|
public override string ProgressTitle =>
|
|
$"Relieving raft";
|
|
|
|
public override string ProgressAction => "Relieved layers";
|
|
|
|
public override Enumerations.LayerRangeSelection StartLayerRangeSelection =>
|
|
Enumerations.LayerRangeSelection.None;
|
|
|
|
public override string ToString()
|
|
{
|
|
var result = $"[{_reliefType}] [Ignore: {_ignoreFirstLayers}] [B: {_brightness}] [Dilate: {_dilateIterations}] [Wall margin: {_wallMargin}] [Hole diameter: {_holeDiameter}] [Hole spacing: {_holeSpacing}]";
|
|
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public OperationRaftRelief() { }
|
|
|
|
public OperationRaftRelief(FileFormat slicerFile) : base(slicerFile) { }
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
public static Array RaftReliefItems => Enum.GetValues(typeof(RaftReliefTypes));
|
|
|
|
public RaftReliefTypes ReliefType
|
|
{
|
|
get => _reliefType;
|
|
set
|
|
{
|
|
if(!RaiseAndSetIfChanged(ref _reliefType, value)) return;
|
|
RaisePropertyChanged(nameof(IsRelief));
|
|
RaisePropertyChanged(nameof(IsDimming));
|
|
RaisePropertyChanged(nameof(IsDecimate));
|
|
}
|
|
}
|
|
|
|
public bool IsRelief => _reliefType == RaftReliefTypes.Relief;
|
|
public bool IsDimming => _reliefType == RaftReliefTypes.Dimming;
|
|
public bool IsDecimate => _reliefType == RaftReliefTypes.Decimate;
|
|
|
|
public byte IgnoreFirstLayers
|
|
{
|
|
get => _ignoreFirstLayers;
|
|
set => RaiseAndSetIfChanged(ref _ignoreFirstLayers, value);
|
|
}
|
|
|
|
public byte Brightness
|
|
{
|
|
get => _brightness;
|
|
set
|
|
{
|
|
if (!RaiseAndSetIfChanged(ref _brightness, value)) return;
|
|
RaisePropertyChanged(nameof(BrightnessPercent));
|
|
}
|
|
}
|
|
|
|
public decimal BrightnessPercent => Math.Round(_brightness * 100 / 255M, 2);
|
|
|
|
public byte DilateIterations
|
|
{
|
|
get => _dilateIterations;
|
|
set => RaiseAndSetIfChanged(ref _dilateIterations, value);
|
|
}
|
|
|
|
public byte WallMargin
|
|
{
|
|
get => _wallMargin;
|
|
set => RaiseAndSetIfChanged(ref _wallMargin, value);
|
|
}
|
|
|
|
public byte HoleDiameter
|
|
{
|
|
get => _holeDiameter;
|
|
set => RaiseAndSetIfChanged(ref _holeDiameter, value);
|
|
}
|
|
|
|
public byte HoleSpacing
|
|
{
|
|
get => _holeSpacing;
|
|
set => RaiseAndSetIfChanged(ref _holeSpacing, value);
|
|
}
|
|
#endregion
|
|
|
|
#region Equality
|
|
|
|
protected bool Equals(OperationRaftRelief other)
|
|
{
|
|
return _reliefType == other._reliefType && _ignoreFirstLayers == other._ignoreFirstLayers && _brightness == other._brightness && _dilateIterations == other._dilateIterations && _wallMargin == other._wallMargin && _holeDiameter == other._holeDiameter && _holeSpacing == other._holeSpacing;
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (ReferenceEquals(null, obj)) return false;
|
|
if (ReferenceEquals(this, obj)) return true;
|
|
if (obj.GetType() != this.GetType()) return false;
|
|
return Equals((OperationRaftRelief) obj);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return HashCode.Combine((int) _reliefType, _ignoreFirstLayers, _brightness, _dilateIterations, _wallMargin, _holeDiameter, _holeSpacing);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
protected override bool ExecuteInternally(OperationProgress progress)
|
|
{
|
|
progress.ItemCount = 0;
|
|
const uint minLength = 5;
|
|
|
|
Mat supportsMat = null;
|
|
var anchor = new Point(-1, -1);
|
|
var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor);
|
|
|
|
|
|
uint firstSupportLayerIndex = 0;
|
|
for (; firstSupportLayerIndex < SlicerFile.LayerCount; firstSupportLayerIndex++)
|
|
{
|
|
progress.Reset("Tracing raft", SlicerFile.LayerCount, firstSupportLayerIndex);
|
|
if (progress.Token.IsCancellationRequested) return false;
|
|
supportsMat = GetRoiOrDefault(SlicerFile[firstSupportLayerIndex].LayerMat);
|
|
var circles = CvInvoke.HoughCircles(supportsMat, HoughModes.Gradient, 1, 20, 100, 30, 5, 200);
|
|
if (circles.Length >= minLength) break;
|
|
|
|
supportsMat.Dispose();
|
|
supportsMat = null;
|
|
}
|
|
|
|
if (supportsMat is null) return false;
|
|
Mat patternMat = null;
|
|
|
|
if (DilateIterations > 0)
|
|
{
|
|
CvInvoke.Dilate(supportsMat, supportsMat,
|
|
CvInvoke.GetStructuringElement(ElementShape.Ellipse, new Size(3, 3), new Point(-1, -1)),
|
|
new Point(-1, -1), DilateIterations, BorderType.Reflect101, new MCvScalar());
|
|
}
|
|
|
|
var color = new MCvScalar(255 - Brightness);
|
|
|
|
switch (ReliefType)
|
|
{
|
|
case RaftReliefTypes.Relief:
|
|
patternMat = EmguExtensions.InitMat(supportsMat.Size);
|
|
int shapeSize = HoleDiameter + HoleSpacing;
|
|
using (var shape = EmguExtensions.InitMat(new Size(shapeSize, shapeSize)))
|
|
{
|
|
|
|
int center = HoleDiameter / 2;
|
|
//int centerTwo = operation.HoleDiameter + operation.HoleSpacing + operation.HoleDiameter / 2;
|
|
int radius = center;
|
|
CvInvoke.Circle(shape, new Point(shapeSize / 2, shapeSize / 2), radius, color, -1);
|
|
CvInvoke.Circle(shape, new Point(0, 0), radius / 2, color, -1);
|
|
CvInvoke.Circle(shape, new Point(0, shapeSize), radius / 2, color, -1);
|
|
CvInvoke.Circle(shape, new Point(shapeSize, 0), radius / 2, color, -1);
|
|
CvInvoke.Circle(shape, new Point(shapeSize, shapeSize), radius / 2, color, -1);
|
|
|
|
CvInvoke.Repeat(shape, supportsMat.Height / shape.Height + 1, supportsMat.Width / shape.Width + 1, patternMat);
|
|
|
|
patternMat = new Mat(patternMat, new Rectangle(0, 0, supportsMat.Width, supportsMat.Height));
|
|
}
|
|
|
|
break;
|
|
case RaftReliefTypes.Dimming:
|
|
patternMat = EmguExtensions.InitMat(supportsMat.Size, color);
|
|
break;
|
|
}
|
|
|
|
progress.Reset(ProgressAction, firstSupportLayerIndex);
|
|
Parallel.For(IgnoreFirstLayers, firstSupportLayerIndex, layerIndex =>
|
|
{
|
|
if (progress.Token.IsCancellationRequested) return;
|
|
using (Mat dst = SlicerFile[layerIndex].LayerMat)
|
|
{
|
|
var target = GetRoiOrDefault(dst);
|
|
|
|
switch (ReliefType)
|
|
{
|
|
case RaftReliefTypes.Relief:
|
|
case RaftReliefTypes.Dimming:
|
|
using (Mat mask = new Mat())
|
|
{
|
|
/*CvInvoke.Subtract(target, supportsMat, mask);
|
|
CvInvoke.Erode(mask, mask, kernel, anchor, operation.WallMargin, BorderType.Reflect101, new MCvScalar());
|
|
CvInvoke.Subtract(target, patternMat, target, mask);*/
|
|
|
|
CvInvoke.Erode(target, mask, kernel, anchor, WallMargin, BorderType.Reflect101, default);
|
|
CvInvoke.Subtract(mask, supportsMat, mask);
|
|
CvInvoke.Subtract(target, patternMat, target, mask);
|
|
}
|
|
|
|
break;
|
|
case RaftReliefTypes.Decimate:
|
|
supportsMat.CopyTo(target);
|
|
break;
|
|
}
|
|
|
|
|
|
SlicerFile[layerIndex].LayerMat = dst;
|
|
}
|
|
|
|
lock (progress.Mutex)
|
|
{
|
|
progress++;
|
|
}
|
|
});
|
|
|
|
|
|
supportsMat.Dispose();
|
|
patternMat?.Dispose();
|
|
|
|
return !progress.Token.IsCancellationRequested;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|