diff --git a/CHANGELOG.md b/CHANGELOG.md
index c2d3ba6..c655792 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## 04/11/2020 - v1.4.0
+
+* (Add) Tool - Raft relief: Relief raft by adding holes in between to reduce FEP suction, save resin and easier to remove the prints.
+
## 04/11/2020 - v1.3.5
* (Add) Pixel Dimming: Chamfer - Allow the number of walls pixels to be gradually varied as the operation progresses from the starting layer to the ending layer (#106)
diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs
index 95f41a4..38dd145 100644
--- a/UVtools.Core/Layer/LayerManager.cs
+++ b/UVtools.Core/Layer/LayerManager.cs
@@ -540,8 +540,6 @@ namespace UVtools.Core
out var maxIteration
);
- Debug.WriteLine($"Steps: {iterationSteps}, Max iteration: {maxIteration}");
-
Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1,
//new ParallelOptions {MaxDegreeOfParallelism = 1},
layerIndex =>
@@ -709,6 +707,106 @@ namespace UVtools.Core
progress.Token.ThrowIfCancellationRequested();
}*/
+ public void RaftRelief(OperationRaftRelief operation, OperationProgress progress)
+ {
+ const uint minLength = 5;
+ if (progress is null) progress = new OperationProgress();
+ //progress.Reset(operation.ProgressAction);
+
+ Mat supportsMat = null;
+
+ uint firstSupportLayerIndex = 0;
+ for (; firstSupportLayerIndex < Count; firstSupportLayerIndex++)
+ {
+ progress.Reset("Tracing raft", Count, firstSupportLayerIndex);
+ if (progress.Token.IsCancellationRequested) return;
+ supportsMat = operation.GetRoiOrDefault(this[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;
+ Mat patternMat = null;
+
+ switch (operation.ReliefType)
+ {
+ case OperationRaftRelief.RaftReliefTypes.Relief:
+ patternMat = EmguExtensions.InitMat(supportsMat.Size);
+ int shapeSize = operation.HoleDiameter + operation.HoleSpacing;
+ using (var shape = EmguExtensions.InitMat(new Size(shapeSize, shapeSize)))
+ {
+
+ int center = operation.HoleDiameter / 2;
+ int centerTwo = operation.HoleDiameter + operation.HoleSpacing + operation.HoleDiameter / 2;
+ int radius = center;
+ CvInvoke.Circle(shape, new Point(shapeSize / 2, shapeSize / 2), radius, EmguExtensions.WhiteByte, -1);
+ CvInvoke.Circle(shape, new Point(0, 0), radius / 2, EmguExtensions.WhiteByte, -1);
+ CvInvoke.Circle(shape, new Point(0, shapeSize), radius / 2, EmguExtensions.WhiteByte, -1);
+ CvInvoke.Circle(shape, new Point(shapeSize, 0), radius / 2, EmguExtensions.WhiteByte, -1);
+ CvInvoke.Circle(shape, new Point(shapeSize, shapeSize), radius / 2, EmguExtensions.WhiteByte, -1);
+
+ //shape.Save("D:\\shape.png");
+
+ 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 OperationRaftRelief.RaftReliefTypes.Decimate:
+ if (operation.DilateIterations <= 0) break;
+ CvInvoke.Dilate(supportsMat, supportsMat,
+ CvInvoke.GetStructuringElement(ElementShape.Ellipse, new Size(3, 3), new Point(-1, -1)),
+ new Point(-1, -1), operation.DilateIterations, BorderType.Reflect101, new MCvScalar());
+ break;
+ }
+
+ progress.Reset(operation.ProgressAction, firstSupportLayerIndex);
+ Parallel.For(0, firstSupportLayerIndex, layerIndex =>
+ {
+ using (Mat dst = this[layerIndex].LayerMat)
+ {
+ var target = operation.GetRoiOrDefault(dst);
+
+ switch (operation.ReliefType)
+ {
+ case OperationRaftRelief.RaftReliefTypes.Relief:
+ using (Mat mask = new Mat())
+ {
+ CvInvoke.Subtract(target, supportsMat, mask);
+ //target.CopyTo(mask);
+ CvInvoke.Erode(mask, mask,
+ CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3),
+ new Point(-1, -1)),
+ new Point(-1, -1), operation.WallMargin, BorderType.Reflect101, new MCvScalar());
+ CvInvoke.Subtract(target, patternMat, target, mask);
+ }
+
+ break;
+ case OperationRaftRelief.RaftReliefTypes.Decimate:
+ supportsMat.CopyTo(target);
+ break;
+ }
+
+
+ this[layerIndex].LayerMat = dst;
+ }
+
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+
+
+ supportsMat.Dispose();
+ patternMat?.Dispose();
+ }
+
public void Arithmetic(OperationArithmetic operation, OperationProgress progress = null)
{
if (!operation.IsValid) return;
diff --git a/UVtools.Core/Operations/OperationRaftRelief.cs b/UVtools.Core/Operations/OperationRaftRelief.cs
new file mode 100644
index 0000000..55ce521
--- /dev/null
+++ b/UVtools.Core/Operations/OperationRaftRelief.cs
@@ -0,0 +1,116 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc.
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+using System;
+
+namespace UVtools.Core.Operations
+{
+ [Serializable]
+ public class OperationRaftRelief : Operation
+ {
+ private RaftReliefTypes _reliefType = RaftReliefTypes.Relief;
+ private byte _dilateIterations = 10;
+ private byte _wallMargin = 10;
+ private byte _holeDiameter = 50;
+ private byte _holeSpacing = 20;
+ 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 OperationRaftRelief()
+ {
+ }
+
+ public override string ToString()
+ {
+ var result = $"[{_reliefType}] [Dilate: {_dilateIterations}] [Wall margin: {_wallMargin}] [Hole diameter: {_holeDiameter}] [Hole spacing: {_holeSpacing}]";
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
+
+ public enum RaftReliefTypes : byte
+ {
+ Relief,
+ Decimate
+ }
+
+ public static Array RaftReliefItems => Enum.GetValues(typeof(RaftReliefTypes));
+
+ public RaftReliefTypes ReliefType
+ {
+ get => _reliefType;
+ set => RaiseAndSetIfChanged(ref _reliefType, value);
+ }
+
+ 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);
+ }
+
+ #region Equality
+
+ protected bool Equals(OperationRaftRelief other)
+ {
+ return _reliefType == other._reliefType && _dilateIterations == other._dilateIterations && _holeDiameter == other._holeDiameter && _holeSpacing == other._holeSpacing && _wallMargin == other._wallMargin;
+ }
+
+ 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()
+ {
+ unchecked
+ {
+ var hashCode = (int) _reliefType;
+ hashCode = (hashCode * 397) ^ _dilateIterations.GetHashCode();
+ hashCode = (hashCode * 397) ^ _holeDiameter.GetHashCode();
+ hashCode = (hashCode * 397) ^ _holeSpacing.GetHashCode();
+ hashCode = (hashCode * 397) ^ _wallMargin.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 566debb..2d8dfab 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, repair, conversion and manipulation
- 1.3.5
+ 1.4.0
Copyright © 2020 PTRTECH
UVtools.png
AnyCPU;x64
diff --git a/UVtools.WPF/Assets/Icons/dot-circle-16x16.png b/UVtools.WPF/Assets/Icons/dot-circle-16x16.png
new file mode 100644
index 0000000..d86c0b5
Binary files /dev/null and b/UVtools.WPF/Assets/Icons/dot-circle-16x16.png differ
diff --git a/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml
new file mode 100644
index 0000000..3acf2cc
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml.cs
new file mode 100644
index 0000000..aa333eb
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml.cs
@@ -0,0 +1,21 @@
+using Avalonia.Markup.Xaml;
+using UVtools.Core.Operations;
+
+namespace UVtools.WPF.Controls.Tools
+{
+ public class ToolRaftReliefControl : ToolControl
+ {
+ public OperationRaftRelief Operation => BaseOperation as OperationRaftRelief;
+
+ public ToolRaftReliefControl()
+ {
+ this.InitializeComponent();
+ BaseOperation = new OperationRaftRelief();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index 6cde099..9123232 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -119,6 +119,14 @@ namespace UVtools.WPF
}
},
new MenuItem
+ {
+ Tag = new OperationRaftRelief(),
+ Icon = new Avalonia.Controls.Image
+ {
+ Source = new Bitmap(App.GetAsset("/Assets/Icons/dot-circle-16x16.png"))
+ }
+ },
+ new MenuItem
{
Tag = new OperationThreshold(),
Icon = new Avalonia.Controls.Image
@@ -1268,6 +1276,9 @@ namespace UVtools.WPF
case OperationMorph operation:
SlicerFile.LayerManager.Morph(operation, BorderType.Default, new MCvScalar(), ProgressWindow.RestartProgress(operation.CanCancel));
break;
+ case OperationRaftRelief operation:
+ SlicerFile.LayerManager.RaftRelief(operation, ProgressWindow.RestartProgress(operation.CanCancel));
+ break;
case OperationThreshold operation:
SlicerFile.LayerManager.ThresholdPixels(operation, ProgressWindow.RestartProgress(operation.CanCancel));
break;
diff --git a/UVtools.WPF/Structures/OperationProfiles.cs b/UVtools.WPF/Structures/OperationProfiles.cs
index 8dd604e..1ce9503 100644
--- a/UVtools.WPF/Structures/OperationProfiles.cs
+++ b/UVtools.WPF/Structures/OperationProfiles.cs
@@ -30,6 +30,7 @@ namespace UVtools.WPF.Structures
//[XmlElement(typeof(OperationLayerRemove))]
//[XmlElement(typeof(OperationMask))]
[XmlElement(typeof(OperationMorph))]
+ [XmlElement(typeof(OperationRaftRelief))]
//[XmlElement(typeof(OperationMove))]
//[XmlElement(typeof(OperationPattern))]
[XmlElement(typeof(OperationPixelDimming))]
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index 70be849..22d8777 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -12,7 +12,7 @@
LICENSE
https://github.com/sn4k3/UVtools
Git
- 1.3.5
+ 1.4.0