diff --git a/CHANGELOG.md b/CHANGELOG.md
index f621776..8e8f5c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 27/01/2022 - v2.27.7
+
+- **Pixel Arithmetic:**
+ - (Add) Corrode: Number of passes
+ - (Change) Corrode: Noise area default from 3px² to 1px²
+ - (Change) Fuzzy skin preset: Wall thickness from 6px to 4px
+ - (Change) Fuzzy skin preset: Max noise offset: 64
+- (Add) Core: More helper functions: Area, Volume, LastBottomLayer, BottomLayersHeight, SetNoDelays, SetWaitTimeBeforeCureOrLightOffDelay
+- (Change) Display the current layer volume instead of area
+- (Fix) Having a first empty layer will miscalculate the model rectangle bounds
+- (Fix) Tool - Calculator - Model tilt: Change formula to use arctan instead of tanh
+- (Upgrade) OpenCV from 4.5.4 to 4.5.5
+- (Upgrade) AvaloniaUI from 0.10.11 to 0.10.12 (#378)
+
## 07/01/2022 - v2.27.6
- **PrusaSlicer:**
@@ -11,7 +25,7 @@
## 05/01/2022 - v2.27.5
- **Pixel Arithmetic:**
- - (Add) Corode: Noise pixel area, defaulting to 3px2
+ - (Add) Corode: Noise pixel area, defaulting to 3px²
- (Change) Corode: Cryptonumeric random to normal random to speed up calculation
- (Change) Fuzzy skin preset: Set a ignore threshold area of 5000px2
- (Improvement) Masking performance and auto crop the layer to speed up the processing when using an "Apply to" other than "All"
diff --git a/README.md b/README.md
index 5c80b1c..0f0382a 100644
--- a/README.md
+++ b/README.md
@@ -98,6 +98,7 @@ But also, i need victims for test subject. Proceed at your own risk!
- LGS30 (Longer Orange 30)
- LGS120 (Longer Orange 120)
- LGS4K (Longer Orange 4K & mono)
+- Flashforge SVGX
- VDA.ZIP (Voxeldance Additive)
- VDT (Voxeldance Tango)
- UVJ (Zip file format for manual manipulation)
diff --git a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
index 68508f1..7ed8446 100644
--- a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
+++ b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
@@ -34,7 +34,7 @@
-
+
diff --git a/UVtools.Core/Extensions/RectangleExtensions.cs b/UVtools.Core/Extensions/RectangleExtensions.cs
index 877b200..b122051 100644
--- a/UVtools.Core/Extensions/RectangleExtensions.cs
+++ b/UVtools.Core/Extensions/RectangleExtensions.cs
@@ -14,8 +14,7 @@ namespace UVtools.Core.Extensions
{
public static Point Center(this Rectangle src)
{
- return new Point(src.X + src.Width / 2,
- src.Y + src.Height / 2);
+ return new Point(src.Right / 2, src.Bottom / 2);
}
}
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index 4bd1b01..e04dda0 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -991,6 +991,11 @@ namespace UVtools.Core.FileFormats
///
public Layer FirstLayer => LayerManager.FirstLayer;
+ ///
+ /// Gets the last bottom layer
+ ///
+ public Layer LastBottomLayer => LayerManager.LastOrDefault(layer => layer.IsBottomLayer);
+
///
/// Gets the first layer normal layer
///
@@ -1283,6 +1288,15 @@ namespace UVtools.Core.FileFormats
}
}
+ ///
+ /// Gets or sets the total height for the bottom layers in millimeters
+ ///
+ public float BottomLayersHeight
+ {
+ get => LastBottomLayer?.PositionZ ?? 0;
+ set => BottomLayerCount = (ushort)Math.Ceiling(value / LayerHeight);
+ }
+
#region Universal Properties
///
@@ -3346,6 +3360,18 @@ namespace UVtools.Core.FileFormats
return changed;
}
+ public void SetNoDelays()
+ {
+ BottomLightOffDelay = 0;
+ LightOffDelay = 0;
+ BottomWaitTimeBeforeCure = 0;
+ WaitTimeBeforeCure = 0;
+ BottomWaitTimeAfterCure = 0;
+ WaitTimeAfterCure = 0;
+ BottomWaitTimeAfterLift = 0;
+ WaitTimeAfterLift = 0;
+ }
+
public float CalculateBottomLightOffDelay(float extraTime = 0) => CalculateLightOffDelay(true, extraTime);
public bool SetBottomLightOffDelay(float extraTime = 0) => SetLightOffDelay(true, extraTime);
@@ -3391,6 +3417,61 @@ namespace UVtools.Core.FileFormats
return false;
}
+ ///
+ /// Attempt to set wait time before cure if supported, otherwise fallback to light-off delay
+ ///
+ /// The time to set
+ /// When true and time is zero, it will calculate light-off delay without extra time, otherwise false to set light-off delay to 0 when time is 0
+ public void SetWaitTimeBeforeCureOrLightOffDelay(bool isBottomLayer, float time = 0, bool zeroLightOffDelayCalculateBase = false)
+ {
+ if (isBottomLayer)
+ {
+ SetBottomWaitTimeBeforeCureOrLightOffDelay(time, zeroLightOffDelayCalculateBase);
+ }
+ else
+ {
+ SetNormalWaitTimeBeforeCureOrLightOffDelay(time, zeroLightOffDelayCalculateBase);
+ }
+ }
+
+ public void SetBottomWaitTimeBeforeCureOrLightOffDelay(float time = 0, bool zeroLightOffDelayCalculateBase = false)
+ {
+ if (CanUseBottomWaitTimeBeforeCure)
+ {
+ BottomLightOffDelay = 0;
+ BottomWaitTimeBeforeCure = time;
+ }
+ else if (CanUseBottomLightOffDelay)
+ {
+ if (time == 0 && !zeroLightOffDelayCalculateBase)
+ {
+ BottomLightOffDelay = 0;
+ return;
+ }
+
+ SetBottomLightOffDelay(time);
+ }
+ }
+
+ public void SetNormalWaitTimeBeforeCureOrLightOffDelay(float time = 0, bool zeroLightOffDelayCalculateBase = false)
+ {
+ if (CanUseWaitTimeBeforeCure)
+ {
+ LightOffDelay = 0;
+ WaitTimeBeforeCure = time;
+ }
+ else if (CanUseLightOffDelay)
+ {
+ if (time == 0 && !zeroLightOffDelayCalculateBase)
+ {
+ LightOffDelay = 0;
+ return;
+ }
+
+ SetNormalLightOffDelay(time);
+ }
+ }
+
///
/// Rebuilds GCode based on current settings
///
diff --git a/UVtools.Core/Layers/Layer.cs b/UVtools.Core/Layers/Layer.cs
index d355b0e..ab57fb5 100644
--- a/UVtools.Core/Layers/Layer.cs
+++ b/UVtools.Core/Layers/Layer.cs
@@ -75,7 +75,8 @@ namespace UVtools.Core.Layers
internal set
{
if (!RaiseAndSetIfChanged(ref _nonZeroPixelCount, value)) return;
- RaisePropertyChanged(nameof(ExposureMillimeters));
+ RaisePropertyChanged(nameof(Area));
+ RaisePropertyChanged(nameof(Volume));
MaterialMilliliters = -1; // Recalculate
}
}
@@ -85,14 +86,17 @@ namespace UVtools.Core.Layers
///
public bool IsEmpty => _nonZeroPixelCount == 0;
- public float ExposureMillimeters
- {
- get
- {
- if (SlicerFile is null) return 0;
- return (float) Math.Round(SlicerFile.PixelSizeMax * _nonZeroPixelCount, 2);
- }
- }
+ ///
+ /// Gets the layer area (XY)
+ /// Pixel size * number of pixels
+ ///
+ public float Area => GetArea(3);
+
+ ///
+ /// Gets the layer volume (XYZ)
+ /// Pixel size * number of pixels * layer height
+ ///
+ public float Volume => GetVolume(3);
///
/// Gets the bounding rectangle for the image area
@@ -465,7 +469,7 @@ namespace UVtools.Core.Layers
return layerHeight;
}
- return SlicerFile.LayerHeight;
+ return SlicerFile?.LayerHeight ?? 0;
}
}
@@ -481,7 +485,7 @@ namespace UVtools.Core.Layers
//var globalMilliliters = SlicerFile.MaterialMilliliters - _materialMilliliters;
if (value < 0)
{
- value = (float) Math.Round(SlicerFile.PixelArea * LayerHeight * NonZeroPixelCount / 1000f, 4);
+ value = (float) Math.Round(GetVolume() / 1000f, 4);
}
if(!RaiseAndSetIfChanged(ref _materialMilliliters, value)) return;
@@ -787,6 +791,31 @@ namespace UVtools.Core.Layers
#region Methods
+ ///
+ /// Gets the layer area (XY)
+ /// Pixel size * number of pixels
+ ///
+ public float GetArea() => (SlicerFile?.PixelArea ?? 0) * _nonZeroPixelCount;
+
+
+ ///
+ /// Gets the layer area (XY)
+ /// Pixel size * number of pixels
+ ///
+ public float GetArea(byte roundToDigits) => (float)Math.Round(GetArea(), roundToDigits);
+
+ ///
+ /// Gets the layer volume (XYZ)
+ /// Pixel size * number of pixels * layer height
+ ///
+ public float GetVolume() => GetArea() * LayerHeight;
+
+ ///
+ /// Gets the layer volume (XYZ)
+ /// Pixel size * number of pixels * layer height
+ ///
+ public float GetVolume(byte roundToDigits) => (float)Math.Round(GetArea() * LayerHeight, roundToDigits);
+
public float CalculateMotorMovementTime(float extraTime = 0)
{
return OperationCalculator.LightOffDelayC.CalculateSeconds(this, extraTime);
diff --git a/UVtools.Core/Layers/LayerManager.cs b/UVtools.Core/Layers/LayerManager.cs
index 020e4a0..207a8f7 100644
--- a/UVtools.Core/Layers/LayerManager.cs
+++ b/UVtools.Core/Layers/LayerManager.cs
@@ -689,8 +689,24 @@ namespace UVtools.Core
var firstLayer = FirstLayer;
if (!_boundingRectangle.IsEmpty || LayerCount == 0 || firstLayer is null || !firstLayer.HaveImage) return _boundingRectangle;
progress ??= new OperationProgress(OperationProgress.StatusOptimizingBounds, LayerCount - 1);
- _boundingRectangle = firstLayer.BoundingRectangle;
- if (_boundingRectangle.IsEmpty) // Safe checking
+ _boundingRectangle = Rectangle.Empty;
+ uint firstValidLayerBounds = 0;
+
+ void FindFirstBoundingRectangle()
+ {
+ for (uint layerIndex = 0; layerIndex < Count; layerIndex++)
+ {
+ firstValidLayerBounds = layerIndex;
+ if (this[layerIndex] is null || this[layerIndex].BoundingRectangle == Rectangle.Empty) continue;
+ _boundingRectangle = this[layerIndex].BoundingRectangle;
+ break;
+ }
+ }
+
+ FindFirstBoundingRectangle();
+ //_boundingRectangle = firstLayer.BoundingRectangle;
+
+ if (_boundingRectangle.IsEmpty) // Safe checking, all layers haven't a bounding rectangle
{
progress.Reset(OperationProgress.StatusOptimizingBounds, LayerCount-1);
Parallel.For(0, LayerCount, CoreSettings.ParallelOptions, layerIndex =>
@@ -699,25 +715,29 @@ namespace UVtools.Core
this[layerIndex].GetBoundingRectangle();
- if (progress is null) return;
progress.LockAndIncrement();
});
- _boundingRectangle = firstLayer.BoundingRectangle;
if (progress.Token.IsCancellationRequested)
{
_boundingRectangle = Rectangle.Empty;
progress.Token.ThrowIfCancellationRequested();
}
+
+ FindFirstBoundingRectangle();
}
- progress.Reset(OperationProgress.StatusCalculatingBounds, LayerCount-1);
- for (int i = 1; i < LayerCount; i++)
+ if (firstValidLayerBounds+1 < LayerCount)
{
- if(this[i] is null || this[i].BoundingRectangle.IsEmpty) continue;
- _boundingRectangle = Rectangle.Union(_boundingRectangle, this[i].BoundingRectangle);
- progress++;
+ progress.Reset(OperationProgress.StatusCalculatingBounds, LayerCount - firstValidLayerBounds - 1);
+ for (var i = firstValidLayerBounds+1; i < LayerCount; i++)
+ {
+ if (this[i] is null || this[i].BoundingRectangle.IsEmpty) continue;
+ _boundingRectangle = Rectangle.Union(_boundingRectangle, this[i].BoundingRectangle);
+ progress++;
+ }
}
+
RaisePropertyChanged(nameof(BoundingRectangle));
return _boundingRectangle;
}
@@ -770,6 +790,16 @@ namespace UVtools.Core
return _layers?.Where((layer, index) => !layer.IsEmpty && index >= layerStartIndex && index <= layerEndIndex).MaxBy(layer => layer.NonZeroPixelCount).FirstOrDefault();
}
+ public IEnumerable GetLayersFromHeightRange(float startPositionZ, float endPositionZ)
+ {
+ return this.Where(layer => layer.PositionZ >= startPositionZ && layer.PositionZ <= endPositionZ);
+ }
+
+ public IEnumerable GetLayersFromHeightRange(float endPositionZ)
+ {
+ return this.Where(layer => layer.PositionZ <= endPositionZ);
+ }
+
public static void MutateGetVarsIterationChamfer(uint startLayerIndex, uint endLayerIndex, int iterationsStart, int iterationsEnd, ref bool isFade, out float iterationSteps, out int maxIteration)
{
iterationSteps = 0;
diff --git a/UVtools.Core/Operations/OperationCalculator.cs b/UVtools.Core/Operations/OperationCalculator.cs
index 779272e..8c63c06 100644
--- a/UVtools.Core/Operations/OperationCalculator.cs
+++ b/UVtools.Core/Operations/OperationCalculator.cs
@@ -354,7 +354,7 @@ namespace UVtools.Core.Operations
private decimal _layerHeight = 0.05m;
public override string Description => "Calculates the optimal model tilt angle for printing and to minimize the visual layer effect.";
- public override string Formula => "Angleº = tanh(Layer height / XYResolution) * (180 / PI)";
+ public override string Formula => "Angleº = arctan(Layer height / XYResolution) * (180 / PI)";
public OptimalModelTilt(Size resolution, SizeF display, decimal layerHeight = 0.05m)
{
@@ -437,7 +437,7 @@ namespace UVtools.Core.Operations
public decimal XYResolutionUm => Math.Round(XYResolution * 1000, 2);
public decimal TiltAngleDegrees =>
- XYResolution > 0 ? (decimal) Math.Round(Math.Tanh((double) (_layerHeight / XYResolution)) * (180 / Math.PI), 3) : 0;
+ XYResolution > 0 ? (decimal) Math.Round(Math.Atan((double) (_layerHeight / XYResolution)) * (180 / Math.PI), 3) : 0;
}
}
diff --git a/UVtools.Core/Operations/OperationPixelArithmetic.cs b/UVtools.Core/Operations/OperationPixelArithmetic.cs
index a351d01..d89befb 100644
--- a/UVtools.Core/Operations/OperationPixelArithmetic.cs
+++ b/UVtools.Core/Operations/OperationPixelArithmetic.cs
@@ -75,7 +75,8 @@ namespace UVtools.Core.Operations
private short _noiseMinOffset = -128;
private short _noiseMaxOffset = 128;
private byte _noiseThreshold;
- private ushort _noisePixelArea = 3;
+ private ushort _noisePixelArea = 1;
+ private byte _noisePasses = 1;
#endregion
@@ -366,7 +367,13 @@ namespace UVtools.Core.Operations
public ushort NoisePixelArea
{
get => _noisePixelArea;
- set => RaiseAndSetIfChanged(ref _noisePixelArea, value);
+ set => RaiseAndSetIfChanged(ref _noisePixelArea, Math.Max((byte)1, value));
+ }
+
+ public byte NoisePasses
+ {
+ get => _noisePasses;
+ set => RaiseAndSetIfChanged(ref _noisePasses, Math.Max((byte)1, value));
}
public byte Value
@@ -781,7 +788,13 @@ namespace UVtools.Core.Operations
}
if (zoneBrightness <= _noiseThreshold) continue;
- byte brightness = (byte)Math.Clamp(random.Next(_noiseMinOffset, _noiseMaxOffset + 1) + zoneBrightness, byte.MinValue, byte.MaxValue);
+ byte brightness = zoneBrightness;
+
+ for (ushort i = 0; i < _noisePasses; i++)
+ {
+ brightness = (byte)Math.Clamp(random.Next(_noiseMinOffset, _noiseMaxOffset + 1) + brightness, byte.MinValue, byte.MaxValue);
+ }
+
//byte brightness = (byte)Math.Clamp(RandomNumberGenerator.GetInt32(_noiseMinOffset, _noiseMaxOffset + 1) + zoneBrightness, byte.MinValue, byte.MaxValue);
for (var y1 = y; y1 < y + _noisePixelArea && y1 < bounds.Bottom; y1++)
{
@@ -904,8 +917,8 @@ namespace UVtools.Core.Operations
Operator = PixelArithmeticOperators.Corrode;
ApplyMethod = PixelArithmeticApplyMethod.ModelSurfaceAndInset;
NoiseMinOffset = -200;
- NoiseMaxOffset = 127;
- WallThickness = 6;
+ NoiseMaxOffset = 64;
+ WallThickness = 4;
IgnoreAreaOperator = PixelArithmeticIgnoreAreaOperator.SmallerThan;
IgnoreAreaThreshold = 5000;
}
@@ -1257,7 +1270,7 @@ namespace UVtools.Core.Operations
protected bool Equals(OperationPixelArithmetic other)
{
- return _operator == other._operator && _applyMethod == other._applyMethod && _wallThicknessStart == other._wallThicknessStart && _wallThicknessEnd == other._wallThicknessEnd && _wallChamfer == other._wallChamfer && _ignoreAreaOperator == other._ignoreAreaOperator && _ignoreAreaThreshold == other._ignoreAreaThreshold && _value == other._value && _usePattern == other._usePattern && _thresholdType == other._thresholdType && _thresholdMaxValue == other._thresholdMaxValue && _patternAlternatePerLayersNumber == other._patternAlternatePerLayersNumber && _patternInvert == other._patternInvert && _patternText == other._patternText && _patternTextAlternate == other._patternTextAlternate && _patternGenMinBrightness == other._patternGenMinBrightness && _patternGenBrightness == other._patternGenBrightness && _patternGenInfillThickness == other._patternGenInfillThickness && _patternGenInfillSpacing == other._patternGenInfillSpacing && _noiseMinOffset == other._noiseMinOffset && _noiseMaxOffset == other._noiseMaxOffset && _noiseThreshold == other._noiseThreshold && _noisePixelArea == other._noisePixelArea;
+ return _operator == other._operator && _applyMethod == other._applyMethod && _wallThicknessStart == other._wallThicknessStart && _wallThicknessEnd == other._wallThicknessEnd && _wallChamfer == other._wallChamfer && _ignoreAreaOperator == other._ignoreAreaOperator && _ignoreAreaThreshold == other._ignoreAreaThreshold && _value == other._value && _usePattern == other._usePattern && _thresholdType == other._thresholdType && _thresholdMaxValue == other._thresholdMaxValue && _patternAlternatePerLayersNumber == other._patternAlternatePerLayersNumber && _patternInvert == other._patternInvert && _patternText == other._patternText && _patternTextAlternate == other._patternTextAlternate && _patternGenMinBrightness == other._patternGenMinBrightness && _patternGenBrightness == other._patternGenBrightness && _patternGenInfillThickness == other._patternGenInfillThickness && _patternGenInfillSpacing == other._patternGenInfillSpacing && _noiseMinOffset == other._noiseMinOffset && _noiseMaxOffset == other._noiseMaxOffset && _noiseThreshold == other._noiseThreshold && _noisePixelArea == other._noisePixelArea && _noisePasses == other._noisePasses;
}
public override bool Equals(object obj)
@@ -1294,6 +1307,7 @@ namespace UVtools.Core.Operations
hashCode.Add(_noiseMaxOffset);
hashCode.Add(_noiseThreshold);
hashCode.Add(_noisePixelArea);
+ hashCode.Add(_noisePasses);
return hashCode.ToHashCode();
}
diff --git a/UVtools.Core/Suggestions/Suggestion.cs b/UVtools.Core/Suggestions/Suggestion.cs
new file mode 100644
index 0000000..e9623bd
--- /dev/null
+++ b/UVtools.Core/Suggestions/Suggestion.cs
@@ -0,0 +1,159 @@
+/*
+ * 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;
+using System.Xml.Serialization;
+using UVtools.Core.FileFormats;
+using UVtools.Core.Objects;
+
+namespace UVtools.Core.Suggestions
+{
+ public abstract class Suggestion : BindableBase
+ {
+ #region Members
+
+ private bool _enabled = true;
+ private bool _autoApply;
+ private FileFormat _slicerFile;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets if this suggestion is enabled
+ ///
+ public bool Enabled
+ {
+ get => _enabled;
+ set => RaiseAndSetIfChanged(ref _enabled, value);
+ }
+
+ ///
+ /// Gets or sets if this suggestion can be auto applied once file load
+ ///
+ public bool AutoApply
+ {
+ get => _autoApply;
+ set => RaiseAndSetIfChanged(ref _autoApply, value);
+ }
+
+ ///
+ /// Gets or sets the
+ ///
+ [XmlIgnore]
+ public FileFormat SlicerFile
+ {
+ get => _slicerFile;
+ set
+ {
+ if (ReferenceEquals(_slicerFile, value)) return;
+ _slicerFile = value;
+ RaisePropertyChanged();
+ RaisePropertyChanged(nameof(IsAvailable));
+ RaisePropertyChanged(nameof(IsApplied));
+ }
+ }
+
+ ///
+ /// Gets if this suggestion is informative only and contain no actions to execute
+ ///
+ public virtual bool IsInformativeOnly => false;
+
+ ///
+ /// Gets if this suggestion is available given the
+ ///
+ public virtual bool IsAvailable => true;
+
+ ///
+ /// Gets if this suggestion is already applied given the
+ ///
+ public abstract bool IsApplied { get; }
+
+ ///
+ /// Gets the title for this suggestion
+ ///
+ public abstract string Title { get; }
+
+ ///
+ /// Gets the message for this suggestion
+ ///
+ public abstract string Message { get; }
+
+ ///
+ /// Gets the tooltip message
+ ///
+ public virtual string ToolTip => null;
+
+ public virtual string InformationUrl => null;
+
+ ///
+ /// Gets the confirmation message before apply the suggestion
+ ///
+ public virtual string ConfirmationMessage => null;
+
+
+ public string GlobalAppliedMessage => $"✓ {Title}";
+ public string GlobalNotAppliedMessage => $"âš {Title}";
+
+ #endregion
+
+ #region Methods
+
+ public void RefreshNotifyAll()
+ {
+ RaisePropertyChanged(nameof(IsAvailable));
+ RaisePropertyChanged(nameof(IsApplied));
+ RefreshNotifyMessage();
+ }
+
+ public void RefreshNotifyMessage()
+ {
+ RaisePropertyChanged(nameof(Message));
+ RaisePropertyChanged(nameof(ToolTip));
+ }
+
+ ///
+ /// Executes and applies the suggestion
+ ///
+ ///
+ ///
+ protected virtual bool ExecuteInternally()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Executes and applies the suggestion
+ ///
+ ///
+ ///
+ public bool Execute()
+ {
+ if (_slicerFile is null) throw new InvalidOperationException($"The suggestion '{Title}' can't execute due the lacking of a file parent.");
+ if (!Enabled || !IsAvailable || IsApplied || IsInformativeOnly) return false;
+
+ var result = ExecuteInternally();
+
+ RaisePropertyChanged(nameof(IsApplied));
+ RefreshNotifyMessage();
+
+ return result;
+ }
+
+ ///
+ /// Executes only if this suggestion is marked with as true
+ ///
+ ///
+ public bool ExecuteIfAutoApply()
+ {
+ return _autoApply && Execute();
+ }
+ #endregion
+ }
+}
diff --git a/UVtools.Core/Suggestions/SuggestionBottomLayerCount.cs b/UVtools.Core/Suggestions/SuggestionBottomLayerCount.cs
new file mode 100644
index 0000000..5c5ddf7
--- /dev/null
+++ b/UVtools.Core/Suggestions/SuggestionBottomLayerCount.cs
@@ -0,0 +1,96 @@
+/*
+ * 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.Suggestions
+{
+ public sealed class SuggestionBottomLayerCount : Suggestion
+ {
+ #region Members
+
+ private decimal _targetBottomHeight = 0.25m;
+ private decimal _minimumBottomHeight = 0.07m;
+ private decimal _maximumBottomHeight = 0.4m;
+ private byte _minimumBottomLayerCount = 3;
+ private byte _maximumBottomLayerCount = 7;
+
+ #endregion
+
+ #region Properties
+
+ public override bool IsAvailable => SlicerFile?.CanUseBottomLayerCount ?? false;
+
+ public override bool IsApplied
+ {
+ get
+ {
+ if (SlicerFile is null) return false;
+ var bottomHeight = (decimal)SlicerFile.BottomLayersHeight;
+ return bottomHeight >= _minimumBottomHeight && bottomHeight <= _maximumBottomHeight
+ && SlicerFile.BottomLayerCount >= _minimumBottomLayerCount && SlicerFile.BottomLayerCount <= _maximumBottomLayerCount;
+ }
+ }
+
+ public override string Title => "Bottom layer count";
+ public override string Message => IsApplied
+ ? $"{GlobalAppliedMessage}: {SlicerFile.BottomLayerCount} / {SlicerFile.BottomLayersHeight}mm"
+ : $"{GlobalNotAppliedMessage} ({SlicerFile.BottomLayerCount}) is out of the recommended {BottomLayerCountValue} layers";
+
+ public override string ToolTip => $"The recommended total height for the bottom layers must be between {_minimumBottomHeight}mm and {_maximumBottomHeight}mm constrained from {_minimumBottomLayerCount} to {_maximumBottomLayerCount} layers.\n" +
+ "Explanation: Bottom layers should be kept to a minimum, usually from 2 or 3, it function is to provide a good adhesion to the build plate, and that happens on the first layer, using a high count have disadvantages.";
+
+ public override string InformationUrl => "https://ameralabs.com/blog/default-3d-printing-raft-settings";
+
+ public override string ConfirmationMessage => $"{Title}: {SlicerFile.BottomLayerCount} » {BottomLayerCountValue}";
+
+ public decimal TargetBottomHeight
+ {
+ get => _targetBottomHeight;
+ set => RaiseAndSetIfChanged(ref _targetBottomHeight, value);
+ }
+
+ public decimal MinimumBottomHeight
+ {
+ get => _minimumBottomHeight;
+ set => RaiseAndSetIfChanged(ref _minimumBottomHeight, value);
+ }
+
+ public decimal MaximumBottomHeight
+ {
+ get => _maximumBottomHeight;
+ set => RaiseAndSetIfChanged(ref _maximumBottomHeight, value);
+ }
+
+ public byte MinimumBottomLayerCount
+ {
+ get => _minimumBottomLayerCount;
+ set => RaiseAndSetIfChanged(ref _minimumBottomLayerCount, value);
+ }
+
+ public byte MaximumBottomLayerCount
+ {
+ get => _maximumBottomLayerCount;
+ set => RaiseAndSetIfChanged(ref _maximumBottomLayerCount, value);
+ }
+
+ public ushort BottomLayerCountValue => Math.Clamp((ushort)Math.Ceiling((float)_targetBottomHeight / SlicerFile.LayerHeight), _minimumBottomLayerCount, _maximumBottomLayerCount);
+
+ #endregion
+
+ #region Methods
+
+ protected override bool ExecuteInternally()
+ {
+ SlicerFile.BottomLayerCount = BottomLayerCountValue;
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/Suggestions/SuggestionLayerHeight.cs b/UVtools.Core/Suggestions/SuggestionLayerHeight.cs
new file mode 100644
index 0000000..2603292
--- /dev/null
+++ b/UVtools.Core/Suggestions/SuggestionLayerHeight.cs
@@ -0,0 +1,84 @@
+/*
+ * 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 UVtools.Core.Extensions;
+using Layer = UVtools.Core.Layers.Layer;
+
+namespace UVtools.Core.Suggestions
+{
+ public sealed class SuggestionLayerHeight : Suggestion
+ {
+ #region Members
+
+ private decimal _minimumLayerHeight = 0.03m;
+ private decimal _maximumLayerHeight = 0.10m;
+ private byte _maximumLayerHeightDecimalDigits = 2;
+
+ #endregion
+
+ #region Properties
+
+ public override bool IsApplied
+ {
+ get
+ {
+ if (SlicerFile is null) return false;
+ if ((decimal)SlicerFile.LayerHeight < _minimumLayerHeight || (decimal)SlicerFile.LayerHeight > _maximumLayerHeight) return false;
+ if (SlicerFile.LayerHeight.DecimalDigits() > _maximumLayerHeightDecimalDigits) return false;
+
+ foreach (var layer in SlicerFile)
+ {
+ if ((decimal)layer.LayerHeight < _minimumLayerHeight || (decimal)layer.LayerHeight > _maximumLayerHeight) return false;
+ if (layer.LayerHeight.DecimalDigits() > _maximumLayerHeightDecimalDigits) return false;
+ }
+
+ return true;
+ }
+ }
+
+ public override bool IsInformativeOnly => true;
+
+ public override string Title => "Layer height";
+ public override string Message => IsApplied
+ ? $"{GlobalAppliedMessage}: {SlicerFile.LayerHeight}mm"
+ : $"{GlobalNotAppliedMessage} is out of the recommended {_minimumLayerHeight}mm » {_maximumLayerHeight}mm, up to {_maximumLayerHeightDecimalDigits} decimal digit(s)";
+
+ public override string ToolTip => $"The recommended layer height is between {_minimumLayerHeight}mm and {_maximumLayerHeight}mm up to {_maximumLayerHeightDecimalDigits} digit(s) precision.\n" +
+ "Explanation: Using the right layer height is important to get successful prints:\n" +
+ "Thin layers may cause problems on adhesion, delamination, will print much slower and have no real visual benefits.\n" +
+ "Thick layers may not fully cure no matter the exposure time you use, causing delamination and other hazards. Read your resin datasheet to know the limits.\n" +
+ "Using layer height with too many decimal digits may produce a wrong positioning due stepper step loss and/or Z axis quality.\n" +
+ "Solution: Re-slice the model with proper layer height.";
+
+ public override string ConfirmationMessage => $"{Title}: Re-slice the model with proper layer height";
+
+ public decimal MinimumLayerHeight
+ {
+ get => _minimumLayerHeight;
+ set => RaiseAndSetIfChanged(ref _minimumLayerHeight, value.Clamp(Layer.MinimumHeight, Layer.MaximumHeight));
+ }
+
+ public decimal MaximumLayerHeight
+ {
+ get => _maximumLayerHeight;
+ set => RaiseAndSetIfChanged(ref _maximumLayerHeight, value.Clamp(Layer.MinimumHeight, Layer.MaximumHeight));
+ }
+
+ public byte MaximumLayerHeightDecimalPlates
+ {
+ get => _maximumLayerHeightDecimalDigits;
+ set => RaiseAndSetIfChanged(ref _maximumLayerHeightDecimalDigits, value.Clamp(2, 4));
+ }
+
+ #endregion
+
+ #region Methods
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/Suggestions/SuggestionWaitTimeAfterCure.cs b/UVtools.Core/Suggestions/SuggestionWaitTimeAfterCure.cs
new file mode 100644
index 0000000..f5b0da5
--- /dev/null
+++ b/UVtools.Core/Suggestions/SuggestionWaitTimeAfterCure.cs
@@ -0,0 +1,182 @@
+/*
+ * 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;
+using UVtools.Core.Extensions;
+
+namespace UVtools.Core.Suggestions
+{
+ public sealed class SuggestionWaitTimeAfterCure : Suggestion
+ {
+ #region Enums
+ public enum SuggestionWaitTimeAfterCureApplyType
+ {
+ Fixed,
+ ExposureProportional
+ }
+ #endregion
+
+ #region Members
+ private SuggestionWaitTimeAfterCureApplyType _applyType = SuggestionWaitTimeAfterCureApplyType.Fixed;
+ private decimal _fixedBottomWaitTimeAfterCure = 7;
+ private decimal _fixedWaitTimeAfterCure = 1;
+ private decimal _proportionalExposureTime = 2;
+ private decimal _proportionalWaitTimeAfterCure = 1;
+ private decimal _minimumBottomWaitTimeAfterCure = 3;
+ private decimal _minimumWaitTimeAfterCure = 1;
+ private decimal _maximumBottomWaitTimeAfterCure = 20;
+ private decimal _maximumWaitTimeAfterCure = 12;
+ #endregion
+
+ #region Properties
+
+ public override bool IsAvailable => SlicerFile?.CanUseAnyWaitTimeAfterCure ?? false;
+
+ public override bool IsApplied
+ {
+ get
+ {
+ if (SlicerFile is null) return false;
+ if (SlicerFile.CanUseBottomWaitTimeAfterCure)
+ {
+ if ((decimal) SlicerFile.BottomWaitTimeAfterCure < _minimumWaitTimeAfterCure ||
+ (decimal) SlicerFile.BottomWaitTimeAfterCure > _maximumWaitTimeAfterCure ||
+ Math.Abs(SlicerFile.BottomWaitTimeAfterCure - CalculateWaitTime(true, (decimal)SlicerFile.BottomExposureTime)) > 0.1) return false;
+ }
+ if (SlicerFile.CanUseWaitTimeAfterCure)
+ {
+ if ((decimal)SlicerFile.WaitTimeAfterCure < _minimumWaitTimeAfterCure ||
+ (decimal)SlicerFile.WaitTimeAfterCure > _maximumWaitTimeAfterCure ||
+ Math.Abs(SlicerFile.WaitTimeAfterCure - CalculateWaitTime(false, (decimal)SlicerFile.ExposureTime)) > 0.1) return false;
+ }
+
+ if (SlicerFile.CanUseLayerWaitTimeAfterCure)
+ {
+ foreach (var layer in SlicerFile)
+ {
+ if ((decimal)layer.WaitTimeAfterCure < _minimumWaitTimeAfterCure ||
+ (decimal)layer.WaitTimeAfterCure > _maximumWaitTimeAfterCure ||
+ Math.Abs(layer.WaitTimeAfterCure - CalculateWaitTime(layer.IsBottomLayer, (decimal)layer.ExposureTime)) > 0.1) return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ public override string Title => "Wait time after cure";
+ public override string Message => IsApplied
+ ? $"{GlobalAppliedMessage}: {SlicerFile.BottomWaitTimeAfterCure}/{SlicerFile.WaitTimeAfterCure}s"
+ : $"{GlobalNotAppliedMessage} of {SlicerFile.BottomWaitTimeAfterCure}/{SlicerFile.WaitTimeAfterCure}s " +
+ $"is out of the recommended {CalculateWaitTime(true, (decimal) SlicerFile.BottomWaitTimeAfterCure)}/{CalculateWaitTime(false, (decimal)SlicerFile.WaitTimeAfterCure)}s";
+
+ //public override string ToolTip => $"The recommended total height for the bottom layers must be between {_minimumBottomHeight}mm and {_maximumBottomHeight}mm constrained from {_minimumBottomLayerCount} to {_maximumBottomLayerCount} layers.\n" +
+ // "Explanation: Bottom layers should be kept to a minimum, usually from 2 or 3, it function is to provide a good adhesion to the build plate, and that happens on the first layer, using a high count have disadvantages.";
+
+ public override string InformationUrl => "https://ameralabs.com/blog/default-3d-printing-raft-settings";
+
+ public override string ConfirmationMessage => $"{Title}: {SlicerFile.BottomWaitTimeAfterCure}/{SlicerFile.WaitTimeAfterCure}s » {CalculateWaitTime(true, (decimal)SlicerFile.BottomWaitTimeAfterCure)}/{CalculateWaitTime(false, (decimal)SlicerFile.WaitTimeAfterCure)}s";
+
+ private SuggestionWaitTimeAfterCureApplyType ApplyType
+ {
+ get => _applyType;
+ set => RaiseAndSetIfChanged(ref _applyType, value);
+ }
+
+ public decimal FixedBottomWaitTimeAfterCure
+ {
+ get => _fixedBottomWaitTimeAfterCure;
+ set => RaiseAndSetIfChanged(ref _fixedBottomWaitTimeAfterCure, Math.Round(value, 2));
+ }
+
+ public decimal FixedWaitTimeAfterCure
+ {
+ get => _fixedWaitTimeAfterCure;
+ set => RaiseAndSetIfChanged(ref _fixedWaitTimeAfterCure, Math.Round(value, 2));
+ }
+
+ public decimal ProportionalExposureTime
+ {
+ get => _proportionalExposureTime;
+ set => RaiseAndSetIfChanged(ref _proportionalExposureTime, Math.Round(value, 2));
+ }
+
+ public decimal ProportionalWaitTimeAfterCure
+ {
+ get => _proportionalWaitTimeAfterCure;
+ set => RaiseAndSetIfChanged(ref _proportionalWaitTimeAfterCure, Math.Round(value, 2));
+ }
+
+ public decimal MinimumBottomWaitTimeAfterCure
+ {
+ get => _minimumBottomWaitTimeAfterCure;
+ set => RaiseAndSetIfChanged(ref _minimumBottomWaitTimeAfterCure, Math.Round(value, 2));
+ }
+
+ public decimal MinimumWaitTimeAfterCure
+ {
+ get => _minimumWaitTimeAfterCure;
+ set => RaiseAndSetIfChanged(ref _minimumWaitTimeAfterCure, Math.Round(value, 2));
+ }
+
+ public decimal MaximumBottomWaitTimeAfterCure
+ {
+ get => _maximumBottomWaitTimeAfterCure;
+ set => RaiseAndSetIfChanged(ref _maximumBottomWaitTimeAfterCure, Math.Round(value, 2));
+ }
+
+ public decimal MaximumWaitTimeAfterCure
+ {
+ get => _maximumWaitTimeAfterCure;
+ set => RaiseAndSetIfChanged(ref _maximumWaitTimeAfterCure, Math.Round(value, 2));
+ }
+
+ #endregion
+
+ #region Methods
+
+ protected override bool ExecuteInternally()
+ {
+ if (SlicerFile.CanUseBottomWaitTimeAfterCure)
+ {
+ SlicerFile.BottomWaitTimeAfterCure = CalculateWaitTime(true, (decimal)SlicerFile.BottomExposureTime);
+ }
+ if (SlicerFile.CanUseWaitTimeAfterCure)
+ {
+ SlicerFile.WaitTimeAfterCure = CalculateWaitTime(false, (decimal)SlicerFile.ExposureTime);
+ }
+
+ if (SlicerFile.CanUseLayerWaitTimeAfterCure)
+ {
+ foreach (var layer in SlicerFile)
+ {
+ layer.WaitTimeAfterCure = CalculateWaitTime(layer.IsBottomLayer, (decimal) layer.ExposureTime);
+ }
+ }
+
+ return true;
+ }
+
+
+ public float CalculateWaitTime(bool isBottomLayer, decimal exposureTime)
+ {
+ return _applyType switch
+ {
+ SuggestionWaitTimeAfterCureApplyType.Fixed => (float) (isBottomLayer
+ ? _fixedBottomWaitTimeAfterCure
+ : _fixedWaitTimeAfterCure),
+ SuggestionWaitTimeAfterCureApplyType.ExposureProportional => (float) Math.Round(
+ (exposureTime * _proportionalWaitTimeAfterCure / _proportionalExposureTime).Clamp(
+ isBottomLayer ? _minimumBottomWaitTimeAfterCure : _minimumWaitTimeAfterCure,
+ isBottomLayer ? _maximumBottomWaitTimeAfterCure : _maximumWaitTimeAfterCure), 2),
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index e8fce27..d197d21 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.27.6
+ 2.27.7
Copyright © 2020 PTRTECH
UVtools.png
AnyCPU;x64
@@ -60,7 +60,7 @@
-
+
diff --git a/UVtools.Platforms/linux-arm64/libcvextern.so b/UVtools.Platforms/linux-arm64/libcvextern.so
index 1a5062d..d57c955 100644
Binary files a/UVtools.Platforms/linux-arm64/libcvextern.so and b/UVtools.Platforms/linux-arm64/libcvextern.so differ
diff --git a/UVtools.Platforms/osx-x64/libcvextern.dylib b/UVtools.Platforms/osx-x64/libcvextern.dylib
index 7b8afcf..eb8bdfc 100644
Binary files a/UVtools.Platforms/osx-x64/libcvextern.dylib and b/UVtools.Platforms/osx-x64/libcvextern.dylib differ
diff --git a/UVtools.Platforms/rhel-x64/libcvextern.so b/UVtools.Platforms/rhel-x64/libcvextern.so
index 57e78cc..eb198ab 100644
Binary files a/UVtools.Platforms/rhel-x64/libcvextern.so and b/UVtools.Platforms/rhel-x64/libcvextern.so differ
diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs
index 5ab32f6..c302212 100644
--- a/UVtools.WPF/App.axaml.cs
+++ b/UVtools.WPF/App.axaml.cs
@@ -142,7 +142,7 @@ namespace UVtools.WPF
uri = new Uri($"avares://{assemblyName}{url}");
}
- var res = AvaloniaLocator.Current.GetService().Open(uri);
+ var res = AvaloniaLocator.Current.GetService()?.Open(uri);
return res;
}
diff --git a/UVtools.WPF/Assets/Icons/info-circle-16x16.png b/UVtools.WPF/Assets/Icons/info-circle-16x16.png
new file mode 100644
index 0000000..c1fe64d
Binary files /dev/null and b/UVtools.WPF/Assets/Icons/info-circle-16x16.png differ
diff --git a/UVtools.WPF/Assets/Icons/shield-virus-32x32.png b/UVtools.WPF/Assets/Icons/shield-virus-32x32.png
new file mode 100644
index 0000000..e984a64
Binary files /dev/null and b/UVtools.WPF/Assets/Icons/shield-virus-32x32.png differ
diff --git a/UVtools.WPF/Assets/Styles/StylesLight.xaml b/UVtools.WPF/Assets/Styles/StylesLight.xaml
index d030ff4..2a966ef 100644
--- a/UVtools.WPF/Assets/Styles/StylesLight.xaml
+++ b/UVtools.WPF/Assets/Styles/StylesLight.xaml
@@ -16,5 +16,5 @@
-
+
\ No newline at end of file
diff --git a/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml
index 193a74b..cf81890 100644
--- a/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolPixelArithmeticControl.axaml
@@ -156,6 +156,17 @@
Maximum="65535"
Width="80"
Value="{Binding Operation.NoisePixelArea}"/>
+
+
+
+
diff --git a/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
index 3ce50ad..e86c387 100644
--- a/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
@@ -43,6 +43,7 @@ namespace UVtools.WPF.Controls.Tools
case ToolWindow.Callbacks.Loaded:
if(ParentWindow is not null) ParentWindow.ButtonOkEnabled = Operation.CanExecute;
ReloadGUI();
+ ReloadScript();
Operation.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == nameof(Operation.CanExecute))
diff --git a/UVtools.WPF/Controls/WindowEx.cs b/UVtools.WPF/Controls/WindowEx.cs
index 7901b47..8b529e6 100644
--- a/UVtools.WPF/Controls/WindowEx.cs
+++ b/UVtools.WPF/Controls/WindowEx.cs
@@ -9,7 +9,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Diagnostics;
using System.Runtime.CompilerServices;
using Avalonia;
using Avalonia.Controls;
diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs
index 278cdba..3618687 100644
--- a/UVtools.WPF/MainWindow.LayerPreview.cs
+++ b/UVtools.WPF/MainWindow.LayerPreview.cs
@@ -489,11 +489,11 @@ namespace UVtools.WPF
{
if (!LayerCache.IsCached) return "Pixels: 0";
var pixelPercent = Math.Round(LayerCache.Layer.NonZeroPixelCount * 100.0 / (SlicerFile.ResolutionX * SlicerFile.ResolutionY), 2);
- string text = $"Pixels: {LayerCache.Layer.NonZeroPixelCount} ({pixelPercent}%)";
- var exposedMillimeters = LayerCache.Layer.ExposureMillimeters;
- if (exposedMillimeters > 0)
+ var text = $"Pixels: {LayerCache.Layer.NonZeroPixelCount} ({pixelPercent}%)";
+ var volume = LayerCache.Layer.Volume;
+ if (volume > 0)
{
- text += $"\nMillimeters: {exposedMillimeters}";
+ text += $"\nVolume: {volume:F2}mm³";
}
return text;
}
diff --git a/UVtools.WPF/MainWindow.Suggestions.cs b/UVtools.WPF/MainWindow.Suggestions.cs
new file mode 100644
index 0000000..db7afc7
--- /dev/null
+++ b/UVtools.WPF/MainWindow.Suggestions.cs
@@ -0,0 +1,98 @@
+/*
+ * 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.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using Avalonia.Controls;
+using MessageBox.Avalonia.Enums;
+using UVtools.Core.Suggestions;
+using UVtools.WPF.Extensions;
+
+namespace UVtools.WPF
+{
+ public partial class MainWindow
+ {
+ #region Members
+
+ private ListBox _suggestionsAvailableListBox;
+
+ #endregion
+
+ #region Properties
+ public Suggestion[] Suggestions { get; } =
+ {
+ //new SuggestionBottomLayerCount(),
+ //new SuggestionWaitTimeAfterCure(),
+ //new SuggestionLayerHeight()
+ };
+
+ public RangeObservableCollection SuggestionsAvailable { get; } = new();
+ public RangeObservableCollection SuggestionsApplied { get; } = new();
+
+ #endregion
+
+ #region Methods
+ public void InitSuggestions()
+ {
+ _suggestionsAvailableListBox = this.FindControl("SuggestionsAvailableListBox");
+ }
+
+ public void PopulateSuggestions(bool tryToAutoApply = true)
+ {
+ var suggestionsAvailable = new List();
+ var suggestionsApplied = new List();
+ foreach (var suggestion in Suggestions)
+ {
+ suggestion.SlicerFile = SlicerFile;
+ if(!suggestion.Enabled || !suggestion.IsAvailable) continue;
+ if(tryToAutoApply) suggestion.ExecuteIfAutoApply();
+
+ if(suggestion.IsApplied) suggestionsApplied.Add(suggestion);
+ else suggestionsAvailable.Add(suggestion);
+ }
+
+ SuggestionsAvailable.ReplaceCollection(suggestionsAvailable);
+ SuggestionsApplied.ReplaceCollection(suggestionsApplied);
+ }
+
+ public async void ApplySuggestionsClicked()
+ {
+ if (!IsFileLoaded || _suggestionsAvailableListBox.SelectedItems.Count == 0) return;
+ var suggestions = _suggestionsAvailableListBox.SelectedItems.Cast().Where(suggestion => !suggestion.IsInformativeOnly).ToArray();
+ if (suggestions.Length == 0) return;
+ var sb = new StringBuilder($"Are you sure you want to apply the following {suggestions.Length} suggestions?:\n\n");
+
+ foreach (var suggestion in suggestions)
+ {
+ sb.AppendLine(suggestion.ConfirmationMessage);
+ }
+ if (await this.MessageBoxQuestion(sb.ToString(), "Apply suggestions?") != ButtonResult.Yes) return;
+
+ foreach (var suggestion in suggestions)
+ {
+ suggestion.Execute();
+ }
+
+ PopulateSuggestions(false);
+ }
+
+ public async void ApplySuggestionClicked(Suggestion suggestion)
+ {
+ if (!IsFileLoaded || suggestion is null || suggestion.IsInformativeOnly) return;
+
+ if (await this.MessageBoxQuestion($"Are you sure you want to apply the following suggestion?:\n\n{suggestion.ConfirmationMessage}", "Apply the suggestion?") != ButtonResult.Yes) return;
+
+ suggestion.Execute();
+ PopulateSuggestions(false);
+ }
+
+ #endregion
+ }
+}
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index e30fd9c..d1c757e 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -883,6 +883,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
LICENSE
https://github.com/sn4k3/UVtools
Git
- 2.27.6
+ 2.27.7
AnyCPU;x64
UVtools.png
README.md
@@ -38,12 +38,12 @@
1701;1702;
-
+
-
-
-
-
+
+
+
+