Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2021-09-04 00:21:41 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2021-09-04 00:21:41 +0300
commit6a2a52ebcb3bcd98476d13f24d562383fc8756b4 (patch)
tree2cac7b6c41b1e8f597da980cd5ebf91e33751021
parent774fe9d5d096aa2922f2219ddfeeb18c925530de (diff)
v2.21.0v2.21.0
- **UI:** - **Menu:** - (Add) File - Open recent: Open any recent open file from a list Shift + Click: Open file in a new window Shift + Ctrl + Click: Remove file from recent list Ctrl + Click: Purge non-existing files - (Add) File - Send to: Copy the file directly to a removable drive (Windows only) - **(Add) Layer navigation buttons:** - SB: Navigate to the smallest bottom layer in mass - LB: Navigate to the largest bottom layer in mass - SN: Navigate to the smallest normal layer in mass - LN: Navigate to the largest normal layer in mass - (Add) Layer outline - Distance detection: Calculates the distance to the closest zero pixel for each pixel - **Tools:** - **Dynamic Lifts:** - (Improvement) Select normal layers by default - (Improvement) Hide light-off delay fields when the file format don't support them - (Fix) Light-off delay fields was not hidding when set a mode that dont require the extra time fields - **Exposure time finder:** - (Fix) Fix the 'light-off delay' field not being show on files that support wait time before cure - (Change) Field name 'Light-off delay' to 'Wait time before cure' - (Add) Fade exposure time: The double exposure method clones the selected layer range and print the same layer twice with different exposure times and strategies - (Add) Double exposure: The double exposure method clones the selected layer range and print the same layer twice with different exposure times and strategies - (Add) Clone layers: Option to keep the same z position for the cloned layers instead of rebuild model height - (Improvement) The layer range selector for normal and bottom layers now selects the correct range based on IsBottom property rather than layer index - (Fix) The layer range selector was setting a very high last layer index when bottom layer count is 0 - (Fix) Pixel arithmetic: Threshold types "Otsu" and "Triangle" are flags to combine with other types, it will auto append the "Binnary" type - (Add) Support for Encrypted CTB (read-only)
-rw-r--r--CHANGELOG.md31
-rw-r--r--UVtools.Core/FileFormats/CTBEncryptedFile.cs30
-rw-r--r--UVtools.Core/FileFormats/FileFormat.cs9
-rw-r--r--UVtools.Core/Layer/Layer.cs23
-rw-r--r--UVtools.Core/Operations/Operation.cs24
-rw-r--r--UVtools.Core/Operations/OperationCalibrateElephantFoot.cs2
-rw-r--r--UVtools.Core/Operations/OperationCalibrateExposureFinder.cs30
-rw-r--r--UVtools.Core/Operations/OperationDoubleExposure.cs389
-rw-r--r--UVtools.Core/Operations/OperationDynamicLayerHeight.cs2
-rw-r--r--UVtools.Core/Operations/OperationDynamicLifts.cs21
-rw-r--r--UVtools.Core/Operations/OperationFadeExposureTime.cs190
-rw-r--r--UVtools.Core/Operations/OperationLayerClone.cs25
-rw-r--r--UVtools.Core/Operations/OperationPixelArithmetic.cs4
-rw-r--r--UVtools.Core/UVtools.Core.csproj2
-rw-r--r--UVtools.WPF/Assets/Icons/equals-16x16.pngbin0 -> 87 bytes
-rw-r--r--UVtools.WPF/Assets/Icons/history-16x16.pngbin0 -> 233 bytes
-rw-r--r--UVtools.WPF/Assets/Icons/share-square-16x16.pngbin0 -> 951 bytes
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml36
-rw-r--r--UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml240
-rw-r--r--UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml.cs22
-rw-r--r--UVtools.WPF/Controls/Tools/ToolDynamicLiftsControl.axaml134
-rw-r--r--UVtools.WPF/Controls/Tools/ToolFadeExposureTimeControl.axaml56
-rw-r--r--UVtools.WPF/Controls/Tools/ToolFadeExposureTimeControl.axaml.cs48
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml7
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs7
-rw-r--r--UVtools.WPF/Extensions/WindowExtensions.cs2
-rw-r--r--UVtools.WPF/MainWindow.LayerPreview.cs42
-rw-r--r--UVtools.WPF/MainWindow.axaml85
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs237
-rw-r--r--UVtools.WPF/Structures/OperationProfiles.cs2
-rw-r--r--UVtools.WPF/Structures/RecentFiles.cs201
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj2
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml12
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml.cs25
-rw-r--r--build/CreateRelease.WPF.ps12
35 files changed, 1803 insertions, 139 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d34147..c921ace 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## 03/09/2021 - v2.21.0
+
+- **UI:**
+ - **Menu:**
+ - (Add) File - Open recent: Open any recent open file from a list
+ Shift + Click: Open file in a new window
+ Shift + Ctrl + Click: Remove file from recent list
+ Ctrl + Click: Purge non-existing files
+ - (Add) File - Send to: Copy the file directly to a removable drive (Windows only)
+ - **(Add) Layer navigation buttons:**
+ - SB: Navigate to the smallest bottom layer in mass
+ - LB: Navigate to the largest bottom layer in mass
+ - SN: Navigate to the smallest normal layer in mass
+ - LN: Navigate to the largest normal layer in mass
+ - (Add) Layer outline - Distance detection: Calculates the distance to the closest zero pixel for each pixel
+- **Tools:**
+ - **Dynamic Lifts:**
+ - (Improvement) Select normal layers by default
+ - (Improvement) Hide light-off delay fields when the file format don't support them
+ - (Fix) Light-off delay fields was not hidding when set a mode that dont require the extra time fields
+ - **Exposure time finder:**
+ - (Fix) Fix the 'light-off delay' field not being show on files that support wait time before cure
+ - (Change) Field name 'Light-off delay' to 'Wait time before cure'
+ - (Add) Fade exposure time: The double exposure method clones the selected layer range and print the same layer twice with different exposure times and strategies
+ - (Add) Double exposure: The double exposure method clones the selected layer range and print the same layer twice with different exposure times and strategies
+ - (Add) Clone layers: Option to keep the same z position for the cloned layers instead of rebuild model height
+ - (Improvement) The layer range selector for normal and bottom layers now selects the correct range based on IsBottom property rather than layer index
+ - (Fix) The layer range selector was setting a very high last layer index when bottom layer count is 0
+ - (Fix) Pixel arithmetic: Threshold types "Otsu" and "Triangle" are flags to combine with other types, it will auto append the "Binnary" type
+- (Add) Support for Encrypted CTB (read-only)
+
## 31/08/2021 - v2.20.5
- (Add) Setting - Max degree of parallelism: Sets the maximum number of concurrent tasks/threads/operations enabled to run by parallel method calls.
diff --git a/UVtools.Core/FileFormats/CTBEncryptedFile.cs b/UVtools.Core/FileFormats/CTBEncryptedFile.cs
index e37bf8c..39321da 100644
--- a/UVtools.Core/FileFormats/CTBEncryptedFile.cs
+++ b/UVtools.Core/FileFormats/CTBEncryptedFile.cs
@@ -35,11 +35,14 @@ namespace UVtools.Core.FileFormats
public const byte HASH_LENGTH = 32;
public const uint LAYER_XOR_KEY = 0xEFBEADDE;
+ public const string Secret0 = "XxUBHR0JHSE6DU8YCVMxORpIG0wSOTobGE8KGjkzVBwOGhZ6MxoMAAgWeiQRDB0JEiE/GwFPAx11JgEdHwMAMHYbAU8YGzwlVAoBDwEsJgAKC0wVPDoRTwkDATg3AEFlOxZ1NwYKTw0UND8aHBtMHToiVB8KHh48IgAKC0wGJjMGTwsNBzR2EQEMHgolIh0AAUBTOTkXBBxAUzY5GhwbHhI8OAdDTx4WJiIGBgwYGjo4B0NPARw7OQQAAwUJNCIdAAFMEjsyVAEAAl4mMxocCkwDOjodDAYJAHUiHA4bTAMnMwIKARgAdTkABwoeAHUwBgACTBAnMxUbCkwSOzJUAwoNF3gwGx0YDQExdgcAAxkHPDkaHE8NATojGgtPGBY2PhoAAwMULHh+KRoAH3UlAR8fAwEhPxoITxgbPCVUCQYAFnUwGx0CDQd1IRsaAwhTNzNUHBsJA3g0FQwETBU6JFRcK0wHMDUcAQAAHDIvVA4BCFMhPhFPDAMeOCMaBhsVUzogER0OAB97dicbBgAfeXYDCk8NHzk5A08bA1MnMxULTxgbMHYSBgMJUzM5Bk8dCQU8MwNDTx4WNjkCCh1MFzQiFU8OAhd1MhEbCg8HdSYGAA0AFjglVBsATB40PRFPFgMGdTdUDQYYUzg5BgpPDxwjMwYKC0wVJzkZTwIFACE3HwocQnkFOhEOHAlTODcfCk8VHCAkVBwHBRUhdhIdAAFTIT4dHE8cAToyAQwbH1M0OBBPBwkfJXYABwpMQBF2AAoMBB06OhsIFkwUOnYSAB0bEicyVA4BCFM6JhEBTmY=";
public const string Secret1 = "hQ36XB6yTk+zO02ysyiowt8yC1buK+nbLWyfY40EXoU=";
public const string Secret2 = "Wld+ampndVJecmVjYH5cWQ==";
+
- public static readonly byte[] Bigfoot = new byte[32];
- public static readonly byte[] CookieMonster = new byte[16];
+ public static readonly string Preamble = CryptExtensions.XORCipherString(System.Convert.FromBase64String(Secret0), About.Software);
+ public static byte[] Bigfoot = CryptExtensions.XORCipher(System.Convert.FromBase64String(Secret1), About.Software);
+ public static byte[] CookieMonster = CryptExtensions.XORCipher(System.Convert.FromBase64String(Secret2), About.Software);
#endregion
@@ -576,7 +579,11 @@ namespace UVtools.Core.FileFormats
public override FileFormatType FileType => FileFormatType.Binary;
public override FileExtension[] FileExtensions { get; } = {
- new(typeof(CTBEncryptedFile), "ctb", "Chitubox CTB (Encrypted)"),
+ new(typeof(CTBEncryptedFile), "ctb", "Chitubox CTB (Encrypted)", true
+#if !DEBUG
+ , false
+#endif
+ ),
new(typeof(CTBEncryptedFile), "encrypted.ctb", "Chitubox CTB (Encrypted)", false, false),
};
@@ -1042,12 +1049,12 @@ namespace UVtools.Core.FileFormats
{
Previews = new Preview[ThumbnailsCount];
- if (Bigfoot is not null && Bigfoot[0] == 0 && File.Exists("MAGIC.ectb"))
+ /*if (Bigfoot is not null && Bigfoot[0] == 0 && File.Exists("MAGIC.ectb"))
{
using var fs = new FileStream("MAGIC.ectb", FileMode.Open);
fs.ReadBytes(Bigfoot);
fs.ReadBytes(CookieMonster);
- }
+ }*/
}
#endregion
@@ -1253,7 +1260,7 @@ namespace UVtools.Core.FileFormats
{
throw new FileLoadException(
"Unable to load this file due to an Chitubox bug and the impossibility to auto correct some of these layers.\n" +
- "Please increase the portion of the plate in use and reslice the file.");
+ "Please increase the portion of the plate in use and re-slice the file.");
}
}
//inputFile.ReadBytes(HashLength);
@@ -1261,6 +1268,10 @@ namespace UVtools.Core.FileFormats
protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
{
+#if !DEBUG
+ throw new NotSupportedException(Preamble);
+#endif
+
using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write);
//uint currentOffset = 0;
@@ -1293,7 +1304,9 @@ namespace UVtools.Core.FileFormats
};
var previewBytes = preview.Encode(image);
-
+#if !DEBUG
+ Bigfoot = new byte[32]; CookieMonster = new byte[16];
+#endif
if (previewBytes.Length == 0) continue;
if (i == 0)
@@ -1345,6 +1358,9 @@ namespace UVtools.Core.FileFormats
progress.LockAndIncrement();
});
+#if !DEBUG
+ Bigfoot = new byte[32]; CookieMonster = new byte[16];
+#endif
progress.Reset(OperationProgress.StatusWritingFile, LayerCount);
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index 6163492..4dd3ea8 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -278,9 +278,7 @@ namespace UVtools.Core.FileFormats
new SL1File(), // Prusa SL1
new ChituboxZipFile(), // Zip
new ChituboxFile(), // cbddlp, cbt, photon
-#if DEBUG
new CTBEncryptedFile(), // encrypted ctb
-#endif
new PhotonSFile(), // photons
new PHZFile(), // phz
new FDGFile(), // fdg
@@ -2497,6 +2495,13 @@ namespace UVtools.Core.FileFormats
progress ??= new OperationProgress();
progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
+#if !DEBUG
+ if (this is CTBEncryptedFile)
+ {
+ throw new NotSupportedException(CTBEncryptedFile.Preamble);
+ }
+#endif
+
_layerManager.Sanitize();
FileFullPath = fileFullPath;
diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs
index 77bd7ec..f2cec59 100644
--- a/UVtools.Core/Layer/Layer.cs
+++ b/UVtools.Core/Layer/Layer.cs
@@ -740,6 +740,29 @@ namespace UVtools.Core
}
/// <summary>
+ /// Attempt to set wait time before cure if supported, otherwise fallback to light-off delay
+ /// </summary>
+ /// <param name="time">The time to set</param>
+ /// <param name="zeroLightOffDelayCalculateBase">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</param>
+ public void SetWaitTimeBeforeCureOrLightOffDelay(float time = 0, bool zeroLightOffDelayCalculateBase = false)
+ {
+ if (SlicerFile.CanUseLayerWaitTimeBeforeCure)
+ {
+ LightOffDelay = 0;
+ WaitTimeBeforeCure = time;
+ }
+ else
+ {
+ if (time == 0 && !zeroLightOffDelayCalculateBase)
+ {
+ LightOffDelay = 0;
+ return;
+ }
+ SetLightOffDelay(time);
+ }
+ }
+
+ /// <summary>
/// Zero all 'wait times / delays' for this layer
/// </summary>
public void SetNoDelays()
diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs
index 9f52000..6560de9 100644
--- a/UVtools.Core/Operations/Operation.cs
+++ b/UVtools.Core/Operations/Operation.cs
@@ -106,6 +106,11 @@ namespace UVtools.Core.Operations
}
/// <summary>
+ /// Gets if the LayerIndexEnd selector is enabled
+ /// </summary>
+ public virtual bool LayerIndexEndEnabled => true;
+
+ /// <summary>
/// Gets if this operation should set layer range to the actual layer index on layer preview
/// </summary>
public virtual bool PassActualLayerIndex => false;
@@ -173,8 +178,13 @@ namespace UVtools.Core.Operations
get => _layerIndexStart;
set
{
- if(!RaiseAndSetIfChanged(ref _layerIndexStart, value)) return;
+ if (SlicerFile is not null)
+ {
+ value = Math.Min(value, SlicerFile.LastLayerIndex);
+ }
+ if (!RaiseAndSetIfChanged(ref _layerIndexStart, value)) return;
RaisePropertyChanged(nameof(LayerRangeCount));
+ RaisePropertyChanged(nameof(LayerRangeHaveBottoms));
}
}
@@ -186,11 +196,19 @@ namespace UVtools.Core.Operations
get => _layerIndexEnd;
set
{
+ if (SlicerFile is not null)
+ {
+ value = Math.Min(value, SlicerFile.LastLayerIndex);
+ }
if(!RaiseAndSetIfChanged(ref _layerIndexEnd, value)) return;
RaisePropertyChanged(nameof(LayerRangeCount));
+ RaisePropertyChanged(nameof(LayerRangeHaveNormals));
}
}
+ public bool LayerRangeHaveBottoms => LayerIndexStart < (SlicerFile.FirstNormalLayer?.Index ?? 0);
+ public bool LayerRangeHaveNormals => LayerIndexEnd >= (SlicerFile.FirstNormalLayer?.Index ?? 0);
+
public uint LayerRangeCount => LayerIndexEnd - LayerIndexStart + 1;
/// <summary>
@@ -318,13 +336,13 @@ namespace UVtools.Core.Operations
public void SelectBottomLayers()
{
LayerIndexStart = 0;
- LayerIndexEnd = SlicerFile.BottomLayerCount - 1u;
+ LayerIndexEnd = Math.Max(1, SlicerFile.FirstNormalLayer?.Index ?? 1) - 1u;
LayerRangeSelection = Enumerations.LayerRangeSelection.Bottom;
}
public void SelectNormalLayers()
{
- LayerIndexStart = SlicerFile.BottomLayerCount;
+ LayerIndexStart = SlicerFile.FirstNormalLayer?.Index ?? 0;
LayerIndexEnd = SlicerFile.LastLayerIndex;
LayerRangeSelection = Enumerations.LayerRangeSelection.Normal;
}
diff --git a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
index b400c6a..59f2450 100644
--- a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
+++ b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
@@ -425,7 +425,7 @@ namespace UVtools.Core.Operations
/// <returns></returns>
public Mat[] GetLayers()
{
- Mat[] layers = new Mat[3];
+ var layers = new Mat[3];
var anchor = new Point(-1, -1);
var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor);
diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
index b69a64d..d199840 100644
--- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
+++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
@@ -168,8 +168,8 @@ namespace UVtools.Core.Operations
private bool _differentSettingsForSamePositionedLayers;
private bool _samePositionedLayersLiftHeightEnabled = true;
private decimal _samePositionedLayersLiftHeight;
- private bool _samePositionedLayersLightOffDelayEnabled = true;
- private decimal _samePositionedLayersLightOffDelay;
+ private bool _samePositionedLayersWaitTimeBeforeCureEnabled = true;
+ private decimal _samePositionedLayersWaitTimeBeforeCure;
private bool _multipleExposures;
private CalibrateExposureFinderExposureGenTypes _exposureGenType = CalibrateExposureFinderExposureGenTypes.Linear;
private bool _exposureGenIgnoreBaseExposure;
@@ -907,16 +907,16 @@ namespace UVtools.Core.Operations
set => RaiseAndSetIfChanged(ref _samePositionedLayersLiftHeight, Math.Round(value, 2));
}
- public bool SamePositionedLayersLightOffDelayEnabled
+ public bool SamePositionedLayersWaitTimeBeforeCureEnabled
{
- get => _samePositionedLayersLightOffDelayEnabled;
- set => RaiseAndSetIfChanged(ref _samePositionedLayersLightOffDelayEnabled, value);
+ get => _samePositionedLayersWaitTimeBeforeCureEnabled;
+ set => RaiseAndSetIfChanged(ref _samePositionedLayersWaitTimeBeforeCureEnabled, value);
}
- public decimal SamePositionedLayersLightOffDelay
+ public decimal SamePositionedLayersWaitTimeBeforeCure
{
- get => _samePositionedLayersLightOffDelay;
- set => RaiseAndSetIfChanged(ref _samePositionedLayersLightOffDelay, Math.Round(value, 2));
+ get => _samePositionedLayersWaitTimeBeforeCure;
+ set => RaiseAndSetIfChanged(ref _samePositionedLayersWaitTimeBeforeCure, Math.Round(value, 2));
}
public bool MultipleExposures
@@ -1140,12 +1140,12 @@ namespace UVtools.Core.Operations
if (SlicerFile.SupportsGCode)
{
_samePositionedLayersLiftHeight = 0;
- _samePositionedLayersLightOffDelay = 2;
+ _samePositionedLayersWaitTimeBeforeCure = 2;
}
else
{
_samePositionedLayersLiftHeight = 0.1m;
- _samePositionedLayersLightOffDelay = 0;
+ _samePositionedLayersWaitTimeBeforeCure = 0;
}
}
@@ -1176,7 +1176,7 @@ namespace UVtools.Core.Operations
if (_multipleExposuresBaseLayersCustomExposure <= 0) _multipleExposuresBaseLayersCustomExposure = (decimal)SlicerFile.ExposureTime;
- if (!SlicerFile.HaveLayerParameterModifier(FileFormat.PrintParameterModifier.ExposureTime))
+ if (!SlicerFile.CanUseLayerExposureTime)
{
_multipleLayerHeight = false;
_multipleExposures = false;
@@ -1197,7 +1197,7 @@ namespace UVtools.Core.Operations
private bool Equals(OperationCalibrateExposureFinder other)
{
- return _displayWidth == other._displayWidth && _displayHeight == other._displayHeight && _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _topBottomMargin == other._topBottomMargin && _leftRightMargin == other._leftRightMargin && _chamferLayers == other._chamferLayers && _erodeBottomIterations == other._erodeBottomIterations && _partMargin == other._partMargin && _enableAntiAliasing == other._enableAntiAliasing && _mirrorOutput == other._mirrorOutput && _baseHeight == other._baseHeight && _featuresHeight == other._featuresHeight && _featuresMargin == other._featuresMargin && _staircaseThicknessPx == other._staircaseThicknessPx && _holesEnabled == other._holesEnabled && _holeShape == other._holeShape && _unitOfMeasure == other._unitOfMeasure && _holeDiametersPx == other._holeDiametersPx && _holeDiametersMm == other._holeDiametersMm && _barsEnabled == other._barsEnabled && _barSpacing == other._barSpacing && _barLength == other._barLength && _barVerticalSplitter == other._barVerticalSplitter && _barFenceThickness == other._barFenceThickness && _barFenceOffset == other._barFenceOffset && _barThicknessesPx == other._barThicknessesPx && _barThicknessesMm == other._barThicknessesMm && _textEnabled == other._textEnabled && _textFont == other._textFont && _textScale.Equals(other._textScale) && _textThickness == other._textThickness && _text == other._text && _multipleBrightness == other._multipleBrightness && _multipleBrightnessExcludeFrom == other._multipleBrightnessExcludeFrom && _multipleBrightnessValues == other._multipleBrightnessValues && _multipleBrightnessGenExposureTime == other._multipleBrightnessGenExposureTime && _multipleBrightnessGenEmulatedAALevel == other._multipleBrightnessGenEmulatedAALevel && _multipleBrightnessGenExposureFractions == other._multipleBrightnessGenExposureFractions && _multipleLayerHeight == other._multipleLayerHeight && _multipleLayerHeightMaximum == other._multipleLayerHeightMaximum && _multipleLayerHeightStep == other._multipleLayerHeightStep && _multipleExposuresBaseLayersPrintMode == other._multipleExposuresBaseLayersPrintMode && _multipleExposuresBaseLayersCustomExposure == other._multipleExposuresBaseLayersCustomExposure && _differentSettingsForSamePositionedLayers == other._differentSettingsForSamePositionedLayers && _samePositionedLayersLiftHeightEnabled == other._samePositionedLayersLiftHeightEnabled && _samePositionedLayersLiftHeight == other._samePositionedLayersLiftHeight && _samePositionedLayersLightOffDelayEnabled == other._samePositionedLayersLightOffDelayEnabled && _samePositionedLayersLightOffDelay == other._samePositionedLayersLightOffDelay && _multipleExposures == other._multipleExposures && _exposureGenType == other._exposureGenType && _exposureGenIgnoreBaseExposure == other._exposureGenIgnoreBaseExposure && _exposureGenBottomStep == other._exposureGenBottomStep && _exposureGenNormalStep == other._exposureGenNormalStep && _exposureGenTests == other._exposureGenTests && _exposureGenManualLayerHeight == other._exposureGenManualLayerHeight && _exposureGenManualBottom == other._exposureGenManualBottom && _exposureGenManualNormal == other._exposureGenManualNormal && Equals(_exposureTable, other._exposureTable) && _bullsEyeEnabled == other._bullsEyeEnabled && _bullsEyeConfigurationPx == other._bullsEyeConfigurationPx && _bullsEyeConfigurationMm == other._bullsEyeConfigurationMm && _bullsEyeInvertQuadrants == other._bullsEyeInvertQuadrants && _counterTrianglesEnabled == other._counterTrianglesEnabled && _counterTrianglesTipOffset == other._counterTrianglesTipOffset && _counterTrianglesFence == other._counterTrianglesFence && _patternModel == other._patternModel && _bullsEyeFenceThickness == other._bullsEyeFenceThickness && _bullsEyeFenceOffset == other._bullsEyeFenceOffset && _patternModelGlueBottomLayers == other._patternModelGlueBottomLayers;
+ return _displayWidth == other._displayWidth && _displayHeight == other._displayHeight && _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _topBottomMargin == other._topBottomMargin && _leftRightMargin == other._leftRightMargin && _chamferLayers == other._chamferLayers && _erodeBottomIterations == other._erodeBottomIterations && _partMargin == other._partMargin && _enableAntiAliasing == other._enableAntiAliasing && _mirrorOutput == other._mirrorOutput && _baseHeight == other._baseHeight && _featuresHeight == other._featuresHeight && _featuresMargin == other._featuresMargin && _staircaseThicknessPx == other._staircaseThicknessPx && _holesEnabled == other._holesEnabled && _holeShape == other._holeShape && _unitOfMeasure == other._unitOfMeasure && _holeDiametersPx == other._holeDiametersPx && _holeDiametersMm == other._holeDiametersMm && _barsEnabled == other._barsEnabled && _barSpacing == other._barSpacing && _barLength == other._barLength && _barVerticalSplitter == other._barVerticalSplitter && _barFenceThickness == other._barFenceThickness && _barFenceOffset == other._barFenceOffset && _barThicknessesPx == other._barThicknessesPx && _barThicknessesMm == other._barThicknessesMm && _textEnabled == other._textEnabled && _textFont == other._textFont && _textScale.Equals(other._textScale) && _textThickness == other._textThickness && _text == other._text && _multipleBrightness == other._multipleBrightness && _multipleBrightnessExcludeFrom == other._multipleBrightnessExcludeFrom && _multipleBrightnessValues == other._multipleBrightnessValues && _multipleBrightnessGenExposureTime == other._multipleBrightnessGenExposureTime && _multipleBrightnessGenEmulatedAALevel == other._multipleBrightnessGenEmulatedAALevel && _multipleBrightnessGenExposureFractions == other._multipleBrightnessGenExposureFractions && _multipleLayerHeight == other._multipleLayerHeight && _multipleLayerHeightMaximum == other._multipleLayerHeightMaximum && _multipleLayerHeightStep == other._multipleLayerHeightStep && _multipleExposuresBaseLayersPrintMode == other._multipleExposuresBaseLayersPrintMode && _multipleExposuresBaseLayersCustomExposure == other._multipleExposuresBaseLayersCustomExposure && _differentSettingsForSamePositionedLayers == other._differentSettingsForSamePositionedLayers && _samePositionedLayersLiftHeightEnabled == other._samePositionedLayersLiftHeightEnabled && _samePositionedLayersLiftHeight == other._samePositionedLayersLiftHeight && _samePositionedLayersWaitTimeBeforeCureEnabled == other._samePositionedLayersWaitTimeBeforeCureEnabled && _samePositionedLayersWaitTimeBeforeCure == other._samePositionedLayersWaitTimeBeforeCure && _multipleExposures == other._multipleExposures && _exposureGenType == other._exposureGenType && _exposureGenIgnoreBaseExposure == other._exposureGenIgnoreBaseExposure && _exposureGenBottomStep == other._exposureGenBottomStep && _exposureGenNormalStep == other._exposureGenNormalStep && _exposureGenTests == other._exposureGenTests && _exposureGenManualLayerHeight == other._exposureGenManualLayerHeight && _exposureGenManualBottom == other._exposureGenManualBottom && _exposureGenManualNormal == other._exposureGenManualNormal && Equals(_exposureTable, other._exposureTable) && _bullsEyeEnabled == other._bullsEyeEnabled && _bullsEyeConfigurationPx == other._bullsEyeConfigurationPx && _bullsEyeConfigurationMm == other._bullsEyeConfigurationMm && _bullsEyeInvertQuadrants == other._bullsEyeInvertQuadrants && _counterTrianglesEnabled == other._counterTrianglesEnabled && _counterTrianglesTipOffset == other._counterTrianglesTipOffset && _counterTrianglesFence == other._counterTrianglesFence && _patternModel == other._patternModel && _bullsEyeFenceThickness == other._bullsEyeFenceThickness && _bullsEyeFenceOffset == other._bullsEyeFenceOffset && _patternModelGlueBottomLayers == other._patternModelGlueBottomLayers;
}
public override bool Equals(object obj)
@@ -1257,8 +1257,8 @@ namespace UVtools.Core.Operations
hashCode.Add(_differentSettingsForSamePositionedLayers);
hashCode.Add(_samePositionedLayersLiftHeightEnabled);
hashCode.Add(_samePositionedLayersLiftHeight);
- hashCode.Add(_samePositionedLayersLightOffDelayEnabled);
- hashCode.Add(_samePositionedLayersLightOffDelay);
+ hashCode.Add(_samePositionedLayersWaitTimeBeforeCureEnabled);
+ hashCode.Add(_samePositionedLayersWaitTimeBeforeCure);
hashCode.Add(_multipleExposures);
hashCode.Add((int) _exposureGenType);
hashCode.Add(_exposureGenIgnoreBaseExposure);
@@ -2260,7 +2260,7 @@ namespace UVtools.Core.Operations
foreach (var layer in layers)
{
if(_samePositionedLayersLiftHeightEnabled) layer.LiftHeightTotal = (float) _samePositionedLayersLiftHeight;
- if(_samePositionedLayersLightOffDelayEnabled) layer.LightOffDelay = (float) _samePositionedLayersLightOffDelay;
+ if(_samePositionedLayersWaitTimeBeforeCureEnabled) layer.SetWaitTimeBeforeCureOrLightOffDelay((float) _samePositionedLayersWaitTimeBeforeCure);
}
}
diff --git a/UVtools.Core/Operations/OperationDoubleExposure.cs b/UVtools.Core/Operations/OperationDoubleExposure.cs
new file mode 100644
index 0000000..5de3e0e
--- /dev/null
+++ b/UVtools.Core/Operations/OperationDoubleExposure.cs
@@ -0,0 +1,389 @@
+/*
+ * 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.Text;
+using System.Threading.Tasks;
+using Emgu.CV;
+using Emgu.CV.CvEnum;
+using UVtools.Core.FileFormats;
+
+namespace UVtools.Core.Operations
+{
+ [Serializable]
+ public class OperationDoubleExposure : Operation
+ {
+ #region Members
+ private decimal _firstBottomExposure;
+ private decimal _firstNormalExposure;
+ private decimal _secondBottomExposure;
+ private decimal _secondNormalExposure;
+ private byte _firstBottomErodeIterations = 4;
+ private byte _secondBottomErodeIterations;
+ private byte _firstNormalErodeIterations = 1;
+ private byte _secondNormalErodeIterations;
+ private bool _secondLayerDifference = true;
+ private byte _secondLayerDifferenceOverlapErodeIterations = 10;
+ private bool _differentSettingsForSecondLayer;
+ private bool _secondLayerLiftHeightEnabled = true;
+ private decimal _secondLayerLiftHeight;
+ private bool _secondLayerWaitTimeBeforeCureEnabled = true;
+ private decimal _secondLayerWaitTimeBeforeCure;
+
+ #endregion
+
+ #region Overrides
+ public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.Bottom;
+ public override string Title => "Double exposure";
+ public override string Description =>
+ "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.";
+
+ public override string ConfirmationText =>
+ $"double exposure model layers {LayerIndexStart} through {LayerIndexEnd}";
+
+ public override string ProgressTitle =>
+ $"Double exposure from layers {LayerIndexStart} to {LayerIndexEnd}";
+
+ public override string ProgressAction => "Cloned layers";
+
+ public override string ValidateSpawn()
+ {
+ if (!SlicerFile.CanUseLayerLiftHeight || !SlicerFile.CanUseLayerExposureTime)
+ {
+ return NotSupportedMessage;
+ }
+
+ return null;
+ }
+
+ public override string ValidateInternally()
+ {
+ var sb = new StringBuilder();
+
+ //if (LayerRangeHaveBottoms && _firstBottomExposure == _secondBottomExposure && _firstBottomErodeIterations == _secondBottomErodeIterations)
+ // sb.AppendLine("The settings for bottoms layers will produce exactly to equal layers");
+
+
+ float lastPositionZ = SlicerFile[LayerIndexStart].PositionZ;
+ for (uint layerIndex = LayerIndexStart + 1; layerIndex <= LayerIndexEnd; layerIndex++)
+ {
+ if (lastPositionZ == SlicerFile[layerIndex].PositionZ)
+ {
+ sb.AppendLine($"The selected layer range already have modified layers with same z position, starting at layer {layerIndex}. Not safe to continue.");
+ break;
+ }
+ lastPositionZ = SlicerFile[layerIndex].PositionZ;
+ }
+
+
+ return sb.ToString();
+ }
+
+ public override string ToString()
+ {
+ var result = $"[1º exp: {_firstBottomExposure}/{_firstNormalExposure}s erode: {_firstBottomErodeIterations}/{_firstNormalErodeIterations}px] " +
+ $"[2º exp: {_secondBottomExposure}/{_secondNormalExposure}s erode: {_secondBottomErodeIterations}/{_secondNormalErodeIterations}px] " +
+ $"[Diff: {_secondLayerDifference} Overlap: {_secondLayerDifferenceOverlapErodeIterations}px]" + LayerRangeString;
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
+ #endregion
+
+ #region Properties
+
+ public decimal FirstBottomExposure
+ {
+ get => _firstBottomExposure;
+ set => RaiseAndSetIfChanged(ref _firstBottomExposure, Math.Round(value, 2));
+ }
+
+ public decimal FirstNormalExposure
+ {
+ get => _firstNormalExposure;
+ set => RaiseAndSetIfChanged(ref _firstNormalExposure, Math.Round(value, 2));
+ }
+
+ public decimal SecondBottomExposure
+ {
+ get => _secondBottomExposure;
+ set => RaiseAndSetIfChanged(ref _secondBottomExposure, Math.Round(value, 2));
+ }
+
+ public decimal SecondNormalExposure
+ {
+ get => _secondNormalExposure;
+ set => RaiseAndSetIfChanged(ref _secondNormalExposure, Math.Round(value, 2));
+ }
+
+ public byte FirstBottomErodeIterations
+ {
+ get => _firstBottomErodeIterations;
+ set => RaiseAndSetIfChanged(ref _firstBottomErodeIterations, value);
+ }
+
+ public byte SecondBottomErodeIterations
+ {
+ get => _secondBottomErodeIterations;
+ set => RaiseAndSetIfChanged(ref _secondBottomErodeIterations, value);
+ }
+
+ public byte FirstNormalErodeIterations
+ {
+ get => _firstNormalErodeIterations;
+ set => RaiseAndSetIfChanged(ref _firstNormalErodeIterations, value);
+ }
+
+ public byte SecondNormalErodeIterations
+ {
+ get => _secondNormalErodeIterations;
+ set => RaiseAndSetIfChanged(ref _secondNormalErodeIterations, value);
+ }
+
+ public bool SecondLayerDifference
+ {
+ get => _secondLayerDifference;
+ set => RaiseAndSetIfChanged(ref _secondLayerDifference, value);
+ }
+
+ public byte SecondLayerDifferenceOverlapErodeIterations
+ {
+ get => _secondLayerDifferenceOverlapErodeIterations;
+ set => RaiseAndSetIfChanged(ref _secondLayerDifferenceOverlapErodeIterations, value);
+ }
+
+ public bool DifferentSettingsForSecondLayer
+ {
+ get => _differentSettingsForSecondLayer;
+ set => RaiseAndSetIfChanged(ref _differentSettingsForSecondLayer, value);
+ }
+
+ public bool SecondLayerLiftHeightEnabled
+ {
+ get => _secondLayerLiftHeightEnabled;
+ set => RaiseAndSetIfChanged(ref _secondLayerLiftHeightEnabled, value);
+ }
+
+ public decimal SecondLayerLiftHeight
+ {
+ get => _secondLayerLiftHeight;
+ set => RaiseAndSetIfChanged(ref _secondLayerLiftHeight, value);
+ }
+
+ public bool SecondLayerWaitTimeBeforeCureEnabled
+ {
+ get => _secondLayerWaitTimeBeforeCureEnabled;
+ set => RaiseAndSetIfChanged(ref _secondLayerWaitTimeBeforeCureEnabled, value);
+ }
+
+ public decimal SecondLayerWaitTimeBeforeCure
+ {
+ get => _secondLayerWaitTimeBeforeCure;
+ set => RaiseAndSetIfChanged(ref _secondLayerWaitTimeBeforeCure, value);
+ }
+
+ #endregion
+
+ #region Constructor
+
+ public OperationDoubleExposure() { }
+
+ public OperationDoubleExposure(FileFormat slicerFile) : base(slicerFile)
+ {
+ if (SlicerFile.SupportPerLayerSettings)
+ {
+ _differentSettingsForSecondLayer = true;
+ if (SlicerFile.SupportsGCode)
+ {
+ _secondLayerLiftHeight = 0;
+ _secondLayerWaitTimeBeforeCure = 2;
+ }
+ else
+ {
+ _secondLayerLiftHeight = 0.1m;
+ _secondLayerWaitTimeBeforeCure = 0;
+ }
+ }
+ }
+
+ public override void InitWithSlicerFile()
+ {
+ base.InitWithSlicerFile();
+ if (_firstBottomExposure <= 0) _firstBottomExposure = (decimal)SlicerFile.BottomExposureTime;
+ if (_firstNormalExposure <= 0) _firstNormalExposure = (decimal)SlicerFile.ExposureTime;
+ if (_secondBottomExposure <= 0) _secondBottomExposure = (decimal)SlicerFile.ExposureTime;
+ if (_secondNormalExposure <= 0) _secondNormalExposure = (decimal)SlicerFile.ExposureTime;
+ }
+
+ #endregion
+
+ #region Equality
+
+ protected bool Equals(OperationDoubleExposure other)
+ {
+ return _firstBottomExposure == other._firstBottomExposure && _firstNormalExposure == other._firstNormalExposure && _secondBottomExposure == other._secondBottomExposure && _secondNormalExposure == other._secondNormalExposure && _firstBottomErodeIterations == other._firstBottomErodeIterations && _secondBottomErodeIterations == other._secondBottomErodeIterations && _firstNormalErodeIterations == other._firstNormalErodeIterations && _secondNormalErodeIterations == other._secondNormalErodeIterations && _secondLayerDifference == other._secondLayerDifference && _secondLayerDifferenceOverlapErodeIterations == other._secondLayerDifferenceOverlapErodeIterations && _differentSettingsForSecondLayer == other._differentSettingsForSecondLayer && _secondLayerLiftHeightEnabled == other._secondLayerLiftHeightEnabled && _secondLayerLiftHeight == other._secondLayerLiftHeight && _secondLayerWaitTimeBeforeCureEnabled == other._secondLayerWaitTimeBeforeCureEnabled && _secondLayerWaitTimeBeforeCure == other._secondLayerWaitTimeBeforeCure;
+ }
+
+ 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((OperationDoubleExposure)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+ hashCode.Add(_firstBottomExposure);
+ hashCode.Add(_firstNormalExposure);
+ hashCode.Add(_secondBottomExposure);
+ hashCode.Add(_secondNormalExposure);
+ hashCode.Add(_firstBottomErodeIterations);
+ hashCode.Add(_secondBottomErodeIterations);
+ hashCode.Add(_firstNormalErodeIterations);
+ hashCode.Add(_secondNormalErodeIterations);
+ hashCode.Add(_secondLayerDifference);
+ hashCode.Add(_secondLayerDifferenceOverlapErodeIterations);
+ hashCode.Add(_differentSettingsForSecondLayer);
+ hashCode.Add(_secondLayerLiftHeightEnabled);
+ hashCode.Add(_secondLayerLiftHeight);
+ hashCode.Add(_secondLayerWaitTimeBeforeCureEnabled);
+ hashCode.Add(_secondLayerWaitTimeBeforeCure);
+ return hashCode.ToHashCode();
+ }
+
+ #endregion
+
+ #region Methods
+
+ protected override bool ExecuteInternally(OperationProgress progress)
+ {
+ var anchor = new Point(-1, -1);
+ var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor);
+
+ var layers = new Layer[SlicerFile.LayerCount+LayerRangeCount];
+
+ // Untouched
+ for (uint i = 0; i < LayerIndexStart; i++)
+ {
+ layers[i] = SlicerFile[i];
+ }
+
+ Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.ParallelOptions, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+
+ var firstLayer = SlicerFile[layerIndex];
+ var secondLayer = firstLayer.Clone();
+ var isBottomLayer = firstLayer.IsBottomLayer;
+
+ firstLayer.ExposureTime = (float)( isBottomLayer ? _firstBottomExposure : _firstNormalExposure);
+ secondLayer.ExposureTime = (float)(isBottomLayer ? _secondBottomExposure : _secondNormalExposure);
+
+ if (_differentSettingsForSecondLayer)
+ {
+ if (_secondLayerLiftHeightEnabled) secondLayer.LiftHeightTotal = (float)_secondLayerLiftHeight;
+ if (_secondLayerWaitTimeBeforeCureEnabled) secondLayer.SetWaitTimeBeforeCureOrLightOffDelay((float)_secondLayerWaitTimeBeforeCure);
+ }
+
+ byte firstErodeIterations = isBottomLayer ? _firstBottomErodeIterations : _firstNormalErodeIterations;
+ byte secondErodeIterations = isBottomLayer ? _secondBottomErodeIterations : _secondNormalErodeIterations;
+
+ using (var mat = firstLayer.LayerMat)
+ {
+ //using Mat matOriginal = _secondExposureLayerDifference ? mat.Clone() : null;
+ if (firstErodeIterations > 0 && firstErodeIterations == secondErodeIterations)
+ {
+ CvInvoke.Erode(mat, mat, kernel, anchor, firstErodeIterations, BorderType.Reflect101, default);
+ firstLayer.LayerMat = mat;
+ firstLayer.CopyImageTo(secondLayer);
+
+ if (_secondLayerDifference && _secondLayerDifferenceOverlapErodeIterations > 0)
+ {
+ using var matErode = new Mat();
+ CvInvoke.Erode(mat, matErode, kernel, anchor, _secondLayerDifferenceOverlapErodeIterations, BorderType.Reflect101, default);
+ //CvInvoke.Threshold(matErode, matErode, 127, 255, ThresholdType.Binary);
+ CvInvoke.Subtract(mat, matErode, mat);
+ secondLayer.LayerMat = mat;
+ }
+ else
+ {
+ firstLayer.CopyImageTo(secondLayer);
+ }
+ }
+ else
+ {
+ Mat firstMat = null;
+ Mat secondMat = null;
+ if (firstErodeIterations > 0)
+ {
+ firstMat = new Mat();
+ CvInvoke.Erode(mat, firstMat, kernel, anchor, firstErodeIterations, BorderType.Reflect101, default);
+ firstLayer.LayerMat = firstMat;
+ }
+
+ if (secondErodeIterations > 0)
+ {
+ secondMat = new Mat();
+ CvInvoke.Erode(mat, secondMat, kernel, anchor, secondErodeIterations, BorderType.Reflect101, default);
+ }
+
+ if(firstMat is not null && _secondLayerDifference)
+ {
+ if (firstErodeIterations + _secondLayerDifferenceOverlapErodeIterations != secondErodeIterations)
+ {
+ if (_secondLayerDifferenceOverlapErodeIterations > 0 &&
+ firstErodeIterations + _secondLayerDifferenceOverlapErodeIterations != secondErodeIterations)
+ {
+ CvInvoke.Erode(firstMat, firstMat, kernel, anchor, _secondLayerDifferenceOverlapErodeIterations, BorderType.Reflect101, default);
+ //CvInvoke.Threshold(firstMat, firstMat, 127, 255, ThresholdType.Binary);
+ }
+
+ CvInvoke.AbsDiff(firstMat, secondMat ?? mat, mat);
+ secondLayer.LayerMat = mat;
+ }
+ }
+ else if (secondMat is not null)
+ {
+ secondLayer.LayerMat = secondMat;
+ }
+
+ firstMat?.Dispose();
+ secondMat?.Dispose();
+ }
+ }
+
+ uint index = LayerIndexStart + (uint)(layerIndex - LayerIndexStart) * 2;
+
+ layers[index] = firstLayer;
+ layers[index + 1] = secondLayer;
+
+ progress.LockAndIncrement();
+ });
+
+ // Untouched
+ for (uint i = LayerIndexEnd+1; i < SlicerFile.LayerCount; i++)
+ {
+ layers[i + LayerRangeCount] = SlicerFile[i];
+ }
+
+ SlicerFile.SuppressRebuildPropertiesWork(() =>
+ {
+ SlicerFile.LayerManager.Layers = layers;
+ });
+
+ return !progress.Token.IsCancellationRequested;
+ }
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
index 321a815..2fe3e42 100644
--- a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
+++ b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
@@ -148,7 +148,7 @@ namespace UVtools.Core.Operations
for (uint layerIndex = 1; layerIndex < SlicerFile.LayerCount; layerIndex++)
{
- if ((decimal)Math.Round(SlicerFile[layerIndex].PositionZ - SlicerFile[layerIndex - 1].PositionZ, Layer.HeightPrecision) ==
+ if ((decimal)Layer.RoundHeight(SlicerFile[layerIndex].PositionZ - SlicerFile[layerIndex - 1].PositionZ) ==
(decimal)SlicerFile.LayerHeight) continue;
return $"This file contain layer(s) with modified positions, starting at layer {layerIndex}.\n" +
$"This tool requires sequential layers with equal height.\n" +
diff --git a/UVtools.Core/Operations/OperationDynamicLifts.cs b/UVtools.Core/Operations/OperationDynamicLifts.cs
index ea82e3d..ff533a4 100644
--- a/UVtools.Core/Operations/OperationDynamicLifts.cs
+++ b/UVtools.Core/Operations/OperationDynamicLifts.cs
@@ -51,6 +51,8 @@ namespace UVtools.Core.Operations
#region Overrides
+ public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.Normal;
+
public override string Title => "Dynamic lifts";
public override string Description =>
@@ -69,8 +71,7 @@ namespace UVtools.Core.Operations
public override string ValidateSpawn()
{
- if (!SlicerFile.HaveLayerParameterModifier(FileFormat.PrintParameterModifier.LiftHeight) ||
- !SlicerFile.HaveLayerParameterModifier(FileFormat.PrintParameterModifier.LiftSpeed))
+ if (!SlicerFile.CanUseLayerLiftHeight || !SlicerFile.CanUseLayerLiftSpeed)
{
return NotSupportedMessage;
}
@@ -124,23 +125,10 @@ namespace UVtools.Core.Operations
return result;
}
- protected override void OnPropertyChanged(PropertyChangedEventArgs e)
- {
- base.OnPropertyChanged(e);
- if (e.PropertyName is nameof(LayerRangeCount))
- {
- RaisePropertyChanged(nameof(IsBottomLayersEnabled));
- RaisePropertyChanged(nameof(IsNormalLayersEnabled));
- }
- }
-
#endregion
#region Properties
- public bool IsBottomLayersEnabled => LayerIndexStart < SlicerFile.BottomLayerCount;
- public bool IsNormalLayersEnabled => LayerIndexEnd >= SlicerFile.BottomLayerCount;
-
public float MinBottomLiftHeight
{
get => _minBottomLiftHeight;
@@ -263,9 +251,6 @@ namespace UVtools.Core.Operations
if(_minLiftSpeed <= 0) _minLiftSpeed = SlicerFile.LiftSpeed;
if (_maxLiftSpeed <= 0 || _maxLiftSpeed < _minLiftSpeed) _maxLiftSpeed = _minLiftSpeed;
-
- RaisePropertyChanged(nameof(IsBottomLayersEnabled));
- RaisePropertyChanged(nameof(IsNormalLayersEnabled));
}
#endregion
diff --git a/UVtools.Core/Operations/OperationFadeExposureTime.cs b/UVtools.Core/Operations/OperationFadeExposureTime.cs
new file mode 100644
index 0000000..6a9ffd9
--- /dev/null
+++ b/UVtools.Core/Operations/OperationFadeExposureTime.cs
@@ -0,0 +1,190 @@
+/*
+ * 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.ComponentModel;
+using System.Text;
+using UVtools.Core.FileFormats;
+
+namespace UVtools.Core.Operations
+{
+ [Serializable]
+ public class OperationFadeExposureTime : Operation
+ {
+ #region Members
+
+ private uint _layerCount = 10;
+ private decimal _fromExposureTime;
+ private decimal _toExposureTime;
+
+ #endregion
+
+ #region Overrides
+ public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.Normal;
+ public override bool LayerIndexEndEnabled => false;
+ public override string Title => "Fade exposure time";
+
+ public override string Description =>
+ "Fade the exposure time in increments from a start to a end value on the selected layer range.";
+
+ public override string ConfirmationText =>
+ $"fade exposure time model layers {LayerIndexStart} through {LayerIndexEnd} with increments of {IncrementValue}s";
+
+ public override string ProgressTitle =>
+ $"Fading exposure time from layers {LayerIndexStart} to {LayerIndexEnd} with increments of {IncrementValue}s";
+
+ public override string ProgressAction => "Faded layers";
+
+ public override string ValidateSpawn()
+ {
+ if (!SlicerFile.CanUseLayerExposureTime)
+ {
+ return NotSupportedMessage;
+ }
+
+ return null;
+ }
+
+ public override string ValidateInternally()
+ {
+ var sb = new StringBuilder();
+
+ if (_layerCount == 0) sb.AppendLine("The layer count must be higher than 0.");
+ if(_fromExposureTime == _toExposureTime) sb.AppendLine("The starting exposure time can't be the same as the ending exposure time.");
+
+ return sb.ToString();
+ }
+
+ public override string ToString()
+ {
+ var result = $"[Layers: {LayerRangeCount} From: {_fromExposureTime}s To: {_toExposureTime}s @ {IncrementValue}s] " + LayerRangeString;
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
+
+ protected override void OnPropertyChanged(PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(LayerIndexStart))
+ {
+ LayerCount = _layerCount; // Sanitize
+ LayerIndexEnd = LayerIndexStart + _layerCount - 1 ; // Sync
+ RaisePropertyChanged(nameof(MaximumLayerCount));
+ RaisePropertyChanged(nameof(IncrementValue));
+ }
+ /*else if (e.PropertyName == nameof(LayerIndexEnd))
+ {
+ LayerCount = LayerRangeCount;
+ RaisePropertyChanged(nameof(IncrementValue));
+ }*/
+
+ base.OnPropertyChanged(e);
+ }
+
+ #endregion
+
+ #region Properties
+
+ public uint LayerCount
+ {
+ get => _layerCount;
+ set
+ {
+ if (!RaiseAndSetIfChanged(ref _layerCount, Math.Min(value, SlicerFile.LayerCount - LayerIndexStart))) return;
+ LayerIndexEnd = LayerIndexStart + _layerCount - 1;
+ RaisePropertyChanged(nameof(MaximumLayerCount));
+ RaisePropertyChanged(nameof(IncrementValue));
+ }
+ }
+
+ public uint MaximumLayerCount => Math.Max(LayerCount, SlicerFile.LayerCount - LayerIndexStart);
+
+ public decimal FromExposureTime
+ {
+ get => _fromExposureTime;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _fromExposureTime, Math.Round(value, 2))) return;
+ RaisePropertyChanged(nameof(IncrementValue));
+ }
+ }
+
+ public decimal ToExposureTime
+ {
+ get => _toExposureTime;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _toExposureTime, Math.Round(value, 2))) return;
+ RaisePropertyChanged(nameof(IncrementValue));
+ }
+ }
+
+ public decimal IncrementValue => Math.Round(IncrementValueRaw, 2);
+ public decimal IncrementValueRaw => (_toExposureTime - _fromExposureTime) / (LayerRangeCount + 1);
+
+ #endregion
+
+ #region Constructor
+
+ public OperationFadeExposureTime() { }
+
+ public OperationFadeExposureTime(FileFormat slicerFile) : base(slicerFile) { }
+
+ public override void InitWithSlicerFile()
+ {
+ base.InitWithSlicerFile();
+ if (_fromExposureTime <= 0) _fromExposureTime = (decimal)SlicerFile.BottomExposureTime;
+ if (_toExposureTime <= 0) _toExposureTime = (decimal)SlicerFile.ExposureTime;
+
+ LayerIndexEnd = LayerIndexStart + _layerCount - 1; // Sync
+ }
+
+ #endregion
+
+ #region Equality
+
+ protected bool Equals(OperationFadeExposureTime other)
+ {
+ return _fromExposureTime == other._fromExposureTime && _toExposureTime == other._toExposureTime && _layerCount == other._layerCount;
+ }
+
+ 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((OperationFadeExposureTime)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(_fromExposureTime, _toExposureTime, _layerCount);
+ }
+
+ #endregion
+
+ #region Methods
+
+ protected override bool ExecuteInternally(OperationProgress progress)
+ {
+ LayerIndexEnd = LayerIndexStart + _layerCount - 1; // Sanitize
+
+ var increment = IncrementValueRaw;
+ var exposure = _fromExposureTime;
+ for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+ exposure += increment;
+ SlicerFile[layerIndex].ExposureTime = (float)exposure;
+ }
+
+ return !progress.Token.IsCancellationRequested;
+ }
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/Operations/OperationLayerClone.cs b/UVtools.Core/Operations/OperationLayerClone.cs
index 3b6fd8a..7c873d9 100644
--- a/UVtools.Core/Operations/OperationLayerClone.cs
+++ b/UVtools.Core/Operations/OperationLayerClone.cs
@@ -22,6 +22,8 @@ namespace UVtools.Core.Operations
{
#region Members
private uint _clones = 1;
+ private bool _keepSamePositionZ;
+
#endregion
#region Overrides
@@ -69,14 +71,29 @@ namespace UVtools.Core.Operations
#region Properties
/// <summary>
+ /// Gets or sets if cloned layers will keep same position z or get the height rebuilt
+ /// </summary>
+ public bool KeepSamePositionZ
+ {
+ get => _keepSamePositionZ;
+ set => RaiseAndSetIfChanged(ref _keepSamePositionZ, value);
+ }
+
+ /// <summary>
/// Gets or sets the number of clones
/// </summary>
public uint Clones
{
get => _clones;
- set => RaiseAndSetIfChanged(ref _clones, value);
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _clones, value)) return;
+ RaisePropertyChanged(nameof(ExtraLayers));
+ }
}
+ public uint ExtraLayers => (uint)Math.Max(0, ((int)LayerIndexEnd - LayerIndexStart + 1) * _clones);
+
#endregion
#region Constructor
@@ -107,7 +124,11 @@ namespace UVtools.Core.Operations
}
}
- SlicerFile.LayerManager.Layers = newLayers;
+ SlicerFile.SuppressRebuildPropertiesWork(() =>
+ {
+ SlicerFile.LayerManager.Layers = newLayers;
+ }, !_keepSamePositionZ);
+
/*var oldLayers = SlicerFile.LayerManager.Layers;
diff --git a/UVtools.Core/Operations/OperationPixelArithmetic.cs b/UVtools.Core/Operations/OperationPixelArithmetic.cs
index 12fab9f..426acc6 100644
--- a/UVtools.Core/Operations/OperationPixelArithmetic.cs
+++ b/UVtools.Core/Operations/OperationPixelArithmetic.cs
@@ -626,7 +626,9 @@ namespace UVtools.Core.Operations
CvInvoke.BitwiseXor(target, tempMat, target, applyMask);
break;
case PixelArithmeticOperators.Threshold:
- CvInvoke.Threshold(target, target, _value, _thresholdMaxValue, _thresholdType);
+ var tempThreshold = _thresholdType;
+ if (_thresholdType is ThresholdType.Otsu or ThresholdType.Triangle) tempThreshold |= ThresholdType.Binary;
+ CvInvoke.Threshold(target, target, _value, _thresholdMaxValue, tempThreshold);
if (_applyMethod != PixelArithmeticApplyMethod.All) ApplyMask(originalRoi, target, applyMask);
break;
case PixelArithmeticOperators.AbsDiff:
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 5de153d..fc4b636 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
- <Version>2.20.5</Version>
+ <Version>2.21.0</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
diff --git a/UVtools.WPF/Assets/Icons/equals-16x16.png b/UVtools.WPF/Assets/Icons/equals-16x16.png
new file mode 100644
index 0000000..8b7528a
--- /dev/null
+++ b/UVtools.WPF/Assets/Icons/equals-16x16.png
Binary files differ
diff --git a/UVtools.WPF/Assets/Icons/history-16x16.png b/UVtools.WPF/Assets/Icons/history-16x16.png
new file mode 100644
index 0000000..17fe4c3
--- /dev/null
+++ b/UVtools.WPF/Assets/Icons/history-16x16.png
Binary files differ
diff --git a/UVtools.WPF/Assets/Icons/share-square-16x16.png b/UVtools.WPF/Assets/Icons/share-square-16x16.png
new file mode 100644
index 0000000..73b9e2f
--- /dev/null
+++ b/UVtools.WPF/Assets/Icons/share-square-16x16.png
Binary files differ
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
index dda24b6..21e0d2f 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
@@ -1149,22 +1149,40 @@
<CheckBox Grid.Row="2" Grid.Column="0"
ToolTip.Tip="Use a low value to speed up layers with same Z position, a delay is not really required here.
&#x0a;Set no delay (0s) is not recommended for gcode printers, as most need some time to render the image before move to the next command, 2s is recommended as a safe-guard."
- Content="Light-off delay:"
- IsVisible="{Binding SlicerFile.CanUseLayerLightOffDelay}"
- IsChecked="{Binding Operation.SamePositionedLayersLightOffDelayEnabled}"
- VerticalAlignment="Center"/>
+ Content="Wait time before cure:"
+ IsChecked="{Binding Operation.SamePositionedLayersWaitTimeBeforeCureEnabled}"
+ VerticalAlignment="Center">
+ <CheckBox.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.Or}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="SlicerFile.CanUseLayerWaitTimeBeforeCure"/>
+ </MultiBinding>
+ </CheckBox.IsVisible>
+ </CheckBox>
<NumericUpDown Grid.Row="2" Grid.Column="2"
Increment="0.5"
Minimum="0"
Maximum="1000"
FormatString="F2"
- IsVisible="{Binding SlicerFile.CanUseLayerLightOffDelay}"
- IsEnabled="{Binding Operation.SamePositionedLayersLightOffDelayEnabled}"
- Value="{Binding Operation.SamePositionedLayersLightOffDelay}"/>
+ IsEnabled="{Binding Operation.SamePositionedLayersWaitTimeBeforeCureEnabled}"
+ Value="{Binding Operation.SamePositionedLayersWaitTimeBeforeCure}">
+ <NumericUpDown.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.Or}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="SlicerFile.CanUseLayerWaitTimeBeforeCure"/>
+ </MultiBinding>
+ </NumericUpDown.IsVisible>
+ </NumericUpDown>
<TextBlock Grid.Row="2" Grid.Column="4"
Text="s"
- IsVisible="{Binding SlicerFile.CanUseLayerLightOffDelay}"
- VerticalAlignment="Center"/>
+ VerticalAlignment="Center">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.Or}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="SlicerFile.CanUseLayerWaitTimeBeforeCure"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
</Grid>
diff --git a/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml b/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml
new file mode 100644
index 0000000..56dd8ef
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml
@@ -0,0 +1,240 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="UVtools.WPF.Controls.Tools.ToolDoubleExposureControl">
+ <StackPanel Spacing="10">
+ <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,10,Auto,5,Auto,40,Auto,5,Auto">
+ <TextBlock Grid.Row="0" Grid.Column="2"
+ VerticalAlignment="Center"
+ HorizontalAlignment="Center"
+ FontWeight="Bold"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ Text="Bottom layers"/>
+ <TextBlock Grid.Row="0" Grid.Column="6"
+ VerticalAlignment="Center"
+ HorizontalAlignment="Center"
+ FontWeight="Bold"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ Text="Normal layers"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="1º exposure:"/>
+
+ <NumericUpDown
+ Grid.Row="2" Grid.Column="2"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ VerticalAlignment="Center"
+ Minimum="0.01"
+ Maximum="1000"
+ Increment="0.5"
+ Value="{Binding Operation.FirstBottomExposure}"/>
+ <TextBlock Grid.Row="2" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ Text="s"/>
+
+ <NumericUpDown
+ Grid.Row="2" Grid.Column="6"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ VerticalAlignment="Center"
+ Minimum="0.01"
+ Maximum="1000"
+ Increment="0.5"
+ Value="{Binding Operation.FirstNormalExposure}"/>
+ <TextBlock Grid.Row="2" Grid.Column="8"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ Text="s"/>
+
+ <TextBlock Grid.Row="4" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="1º erode iterations:"/>
+
+ <NumericUpDown
+ Grid.Row="4" Grid.Column="2"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ VerticalAlignment="Center"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.FirstBottomErodeIterations}"/>
+ <TextBlock Grid.Row="4" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ Text="px"/>
+
+ <NumericUpDown
+ Grid.Row="4" Grid.Column="6"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ VerticalAlignment="Center"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.FirstNormalErodeIterations}"/>
+ <TextBlock Grid.Row="4" Grid.Column="8"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ Text="px"/>
+
+ <TextBlock Grid.Row="6" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="2º exposure:"/>
+
+ <NumericUpDown
+ Grid.Row="6" Grid.Column="2"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ VerticalAlignment="Center"
+ Minimum="0.01"
+ Maximum="1000"
+ Increment="0.5"
+ Value="{Binding Operation.SecondBottomExposure}"/>
+ <TextBlock Grid.Row="6" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ Text="s"/>
+
+ <NumericUpDown
+ Grid.Row="6" Grid.Column="6"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ Minimum="0.01"
+ Maximum="1000"
+ Increment="0.5"
+ Value="{Binding Operation.SecondNormalExposure}"/>
+ <TextBlock Grid.Row="6" Grid.Column="8"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ Text="s"/>
+
+ <TextBlock Grid.Row="8" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="2º erode iterations:"/>
+
+ <NumericUpDown
+ Grid.Row="8" Grid.Column="2"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ VerticalAlignment="Center"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.SecondBottomErodeIterations}"/>
+ <TextBlock Grid.Row="8" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
+ Text="px"/>
+
+ <NumericUpDown
+ Grid.Row="8" Grid.Column="6"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.SecondNormalErodeIterations}"/>
+ <TextBlock Grid.Row="8" Grid.Column="8"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
+ Text="px"/>
+ </Grid>
+
+ <ToggleSwitch
+ OffContent="Exposure the whole image for the second layer"
+ OnContent="Exposure the difference between first and second layer for the second layer"
+ IsChecked="{Binding Operation.SecondLayerDifference}"/>
+
+ <Grid RowDefinitions="Auto"
+ ColumnDefinitions="Auto,10,Auto,5,Auto">
+ <TextBlock Grid.Row="0" Grid.Column="0"
+ VerticalAlignment="Center"
+ 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:"/>
+ <NumericUpDown
+ Grid.Row="0" Grid.Column="2"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.SecondLayerDifference}"
+ Minimum="0"
+ Maximum="255"
+ Increment="1"
+ Value="{Binding Operation.SecondLayerDifferenceOverlapErodeIterations}"/>
+ <TextBlock Grid.Row="0" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.SecondLayerDifference}"
+ Text="px"/>
+ </Grid>
+
+
+ <CheckBox
+ ToolTip.Tip="Change some defined settings for the second layers"
+ Content="Use different settings for the second layer:"
+ IsChecked="{Binding Operation.DifferentSettingsForSecondLayer}"/>
+
+ <Grid RowDefinitions="Auto,10,Auto" ColumnDefinitions="Auto,10,Auto,5,Auto" IsEnabled="{Binding Operation.DifferentSettingsForSecondLayer}">
+ <CheckBox Grid.Row="0" Grid.Column="0"
+ ToolTip.Tip="Use a low value to speed up layers with same Z position, lift is not really required here.
+&#x0a;Set no lift height (0mm) will not work on most of the printers, so far, only gcode printers are known/able to use no lifts.
+&#x0a;However set 0mm on a not compatible printer will cause no harm, value will be contained inside a min-max inside firmware."
+ Content="Lift height:"
+ IsVisible="{Binding SlicerFile.CanUseLayerLiftHeight}"
+ IsChecked="{Binding Operation.SecondLayerLiftHeightEnabled}"
+ VerticalAlignment="Center"/>
+ <NumericUpDown Grid.Row="0" Grid.Column="2"
+ Increment="0.5"
+ Minimum="0"
+ Maximum="1000"
+ FormatString="F2"
+ IsVisible="{Binding SlicerFile.CanUseLayerLiftHeight}"
+ IsEnabled="{Binding Operation.SecondLayerLiftHeightEnabled}"
+ Value="{Binding Operation.SecondLayerLiftHeight}"/>
+ <TextBlock Grid.Row="0" Grid.Column="4"
+ Text="mm"
+ IsVisible="{Binding SlicerFile.CanUseLayerLiftHeight}"
+ VerticalAlignment="Center"/>
+
+ <CheckBox Grid.Row="2" Grid.Column="0"
+ ToolTip.Tip="Use a low value to speed up layers with same Z position, a delay is not really required here.
+&#x0a;Set no delay (0s) is not recommended for gcode printers, as most need some time to render the image before move to the next command, 2s is recommended as a safe-guard."
+ Content="Wait time before cure:"
+ IsChecked="{Binding Operation.SecondLayerWaitTimeBeforeCureEnabled}"
+ VerticalAlignment="Center">
+ <CheckBox.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.Or}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="SlicerFile.CanUseLayerWaitTimeBeforeCure"/>
+ </MultiBinding>
+ </CheckBox.IsVisible>
+ </CheckBox>
+ <NumericUpDown Grid.Row="2" Grid.Column="2"
+ Increment="0.5"
+ Minimum="0"
+ Maximum="1000"
+ FormatString="F2"
+ IsEnabled="{Binding Operation.SecondLayerWaitTimeBeforeCureEnabled}"
+ Value="{Binding Operation.SecondLayerWaitTimeBeforeCure}">
+ <NumericUpDown.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.Or}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="SlicerFile.CanUseLayerWaitTimeBeforeCure"/>
+ </MultiBinding>
+ </NumericUpDown.IsVisible>
+ </NumericUpDown>
+ <TextBlock Grid.Row="2" Grid.Column="4"
+ Text="s"
+ VerticalAlignment="Center">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.Or}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="SlicerFile.CanUseLayerWaitTimeBeforeCure"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
+
+ </Grid>
+
+ </StackPanel>
+</UserControl>
diff --git a/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml.cs
new file mode 100644
index 0000000..db2b324
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolDoubleExposureControl.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia.Markup.Xaml;
+using UVtools.Core.Operations;
+
+namespace UVtools.WPF.Controls.Tools
+{
+ public partial class ToolDoubleExposureControl : ToolControl
+ {
+ public OperationDoubleExposure Operation => BaseOperation as OperationDoubleExposure;
+
+ public ToolDoubleExposureControl()
+ {
+ BaseOperation = new OperationDoubleExposure(SlicerFile);
+ if (!ValidateSpawn()) return;
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/UVtools.WPF/Controls/Tools/ToolDynamicLiftsControl.axaml b/UVtools.WPF/Controls/Tools/ToolDynamicLiftsControl.axaml
index 91a4219..8a53f62 100644
--- a/UVtools.WPF/Controls/Tools/ToolDynamicLiftsControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolDynamicLiftsControl.axaml
@@ -81,12 +81,12 @@
Text="View"/>
<TextBlock Grid.Row="2" Grid.Column="0"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
Text="Bottom lift height:"/>
<NumericUpDown Grid.Row="2" Grid.Column="2"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
Minimum="1"
Maximum="100"
@@ -95,13 +95,13 @@
Value="{Binding Operation.MinBottomLiftHeight}"/>
<TextBlock Grid.Row="2" Grid.Column="3"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="/"/>
<NumericUpDown Grid.Row="2" Grid.Column="4"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
Minimum="1"
Maximum="100"
@@ -110,12 +110,12 @@
Value="{Binding Operation.MaxBottomLiftHeight}"/>
<TextBlock Grid.Row="2" Grid.Column="6"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
Text="mm"/>
<Button Grid.Row="2" Grid.Column="8"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
@@ -125,12 +125,12 @@
Content="Smallest"/>
<TextBlock Grid.Row="4" Grid.Column="0"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
Text="Bottom lift speed:"/>
<NumericUpDown Grid.Row="4" Grid.Column="2"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
Minimum="5"
Maximum="1000"
@@ -139,13 +139,13 @@
Value="{Binding Operation.MinBottomLiftSpeed}"/>
<TextBlock Grid.Row="4" Grid.Column="3"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="/"/>
<NumericUpDown Grid.Row="4" Grid.Column="4"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
Minimum="5"
Maximum="1000"
@@ -154,12 +154,12 @@
Value="{Binding Operation.MaxBottomLiftSpeed}"/>
<TextBlock Grid.Row="4" Grid.Column="6"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Center"
Text="mm/min"/>
<Button Grid.Row="4" Grid.Column="8"
- IsEnabled="{Binding Operation.IsBottomLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveBottoms}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
@@ -170,12 +170,12 @@
<TextBlock Grid.Row="6" Grid.Column="0"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
Text="Lift height:"/>
<NumericUpDown Grid.Row="6" Grid.Column="2"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
Minimum="1"
Maximum="100"
@@ -184,13 +184,13 @@
Value="{Binding Operation.MinLiftHeight}"/>
<TextBlock Grid.Row="6" Grid.Column="3"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="/"/>
<NumericUpDown Grid.Row="6" Grid.Column="4"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
Minimum="1"
Maximum="100"
@@ -199,12 +199,12 @@
Value="{Binding Operation.MaxLiftHeight}"/>
<TextBlock Grid.Row="6" Grid.Column="6"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
Text="mm"/>
<Button Grid.Row="6" Grid.Column="8"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
@@ -214,12 +214,12 @@
Content="Smallest"/>
<TextBlock Grid.Row="8" Grid.Column="0"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
Text="Lift speed:"/>
<NumericUpDown Grid.Row="8" Grid.Column="2"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
Minimum="5"
Maximum="1000"
@@ -228,13 +228,13 @@
Value="{Binding Operation.MinLiftSpeed}"/>
<TextBlock Grid.Row="8" Grid.Column="3"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="/"/>
<NumericUpDown Grid.Row="8" Grid.Column="4"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
Minimum="5"
Maximum="1000"
@@ -243,12 +243,12 @@
Value="{Binding Operation.MaxLiftSpeed}"/>
<TextBlock Grid.Row="8" Grid.Column="6"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Center"
Text="mm/min"/>
<Button Grid.Row="8" Grid.Column="8"
- IsEnabled="{Binding Operation.IsNormalLayersEnabled}"
+ IsEnabled="{Binding Operation.LayerRangeHaveNormals}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
@@ -258,70 +258,114 @@
Content="Largest"/>
<TextBlock Grid.Row="10" Grid.Column="0"
+ IsVisible="{Binding SlicerFile.CanUseLayerLightOffDelay}"
VerticalAlignment="Center"
Text="Light-off mode:"/>
<ComboBox Grid.Row="10" Grid.Column="2"
Grid.ColumnSpan="3"
+ IsVisible="{Binding SlicerFile.CanUseLayerLightOffDelay}"
HorizontalAlignment="Stretch"
Items="{Binding Operation.LightOffDelaySetMode, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
SelectedItem="{Binding Operation.LightOffDelaySetMode, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
<TextBlock Grid.Row="12" Grid.Column="2"
- IsEnabled="{Binding !Operation.LightOffSetMode}"
- IsVisible="{Binding !Operation.LightOffSetMode}"
+ IsEnabled="{Binding !Operation.LightOffDelaySetMode}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
- Text="Bottom extra time"/>
+ Text="Bottom extra time">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="!Operation.LightOffDelaySetMode"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
<TextBlock Grid.Row="12" Grid.Column="4"
- IsEnabled="{Binding !Operation.LightOffSetMode}"
- IsVisible="{Binding !Operation.LightOffSetMode}"
+ IsEnabled="{Binding !Operation.LightOffDelaySetMode}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
- Text="Normal extra time"/>
+ Text="Normal extra time">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="!Operation.LightOffDelaySetMode"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
<TextBlock Grid.Row="14" Grid.Column="0"
- IsEnabled="{Binding !Operation.LightOffSetMode}"
- IsVisible="{Binding !Operation.LightOffSetMode}"
+ IsEnabled="{Binding !Operation.LightOffDelaySetMode}"
VerticalAlignment="Center"
- Text="Light-off delay:"/>
+ Text="Light-off delay:">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="!Operation.LightOffDelaySetMode"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
<NumericUpDown Grid.Row="14" Grid.Column="2"
- IsEnabled="{Binding !Operation.LightOffSetMode}"
- IsVisible="{Binding !Operation.LightOffSetMode}"
+ IsEnabled="{Binding !Operation.LightOffDelaySetMode}"
VerticalAlignment="Center"
Minimum="0"
Maximum="100"
Increment="1"
FormatString="F2"
- Value="{Binding Operation.LightOffDelayBottomExtraTime}"/>
+ Value="{Binding Operation.LightOffDelayBottomExtraTime}">
+ <NumericUpDown.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="!Operation.LightOffDelaySetMode"/>
+ </MultiBinding>
+ </NumericUpDown.IsVisible>
+ </NumericUpDown>
<TextBlock Grid.Row="14" Grid.Column="3"
- IsEnabled="{Binding !Operation.LightOffSetMode}"
- IsVisible="{Binding !Operation.LightOffSetMode}"
+ IsEnabled="{Binding !Operation.LightOffDelaySetMode}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
- Text="/"/>
+ Text="/">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="!Operation.LightOffDelaySetMode"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
<NumericUpDown Grid.Row="14" Grid.Column="4"
- IsEnabled="{Binding !Operation.LightOffSetMode}"
- IsVisible="{Binding !Operation.LightOffSetMode}"
+ IsEnabled="{Binding !Operation.LightOffDelaySetMode}"
VerticalAlignment="Center"
Minimum="0"
Maximum="100"
Increment="1"
FormatString="F2"
- Value="{Binding Operation.LightOffDelayExtraTime}"/>
+ Value="{Binding Operation.LightOffDelayExtraTime}">
+ <NumericUpDown.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="!Operation.LightOffDelaySetMode"/>
+ </MultiBinding>
+ </NumericUpDown.IsVisible>
+ </NumericUpDown>
<TextBlock Grid.Row="14" Grid.Column="6"
- IsEnabled="{Binding !Operation.LightOffSetMode}"
- IsVisible="{Binding !Operation.LightOffSetMode}"
+ IsEnabled="{Binding !Operation.LightOffDelaySetMode}"
VerticalAlignment="Center"
- Text="s"/>
+ Text="s">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="SlicerFile.CanUseLayerLightOffDelay"/>
+ <Binding Path="!Operation.LightOffDelaySetMode"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
</Grid>
</StackPanel>
diff --git a/UVtools.WPF/Controls/Tools/ToolFadeExposureTimeControl.axaml b/UVtools.WPF/Controls/Tools/ToolFadeExposureTimeControl.axaml
new file mode 100644
index 0000000..53939bd
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolFadeExposureTimeControl.axaml
@@ -0,0 +1,56 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="UVtools.WPF.Controls.Tools.ToolFadeExposureTimeControl">
+ <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,10,Auto,5,Auto,5,Auto,5,Auto">
+
+ <TextBlock Grid.Row="0" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Layer count:"/>
+ <NumericUpDown
+ Grid.Row="0" Grid.Column="2"
+ Minimum="1"
+ Maximum="{Binding Operation.MaximumLayerCount}"
+ Increment="1"
+ Value="{Binding Operation.LayerCount}"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Exposure time:"/>
+ <NumericUpDown
+ Grid.Row="2" Grid.Column="2"
+ Minimum="0.1"
+ Maximum="1000"
+ Increment="0.5"
+ Value="{Binding Operation.FromExposureTime}"/>
+ <TextBlock Grid.Row="2" Grid.Column="4"
+ VerticalAlignment="Center"
+ Text="->"/>
+ <NumericUpDown
+ Grid.Row="2" Grid.Column="6"
+ Minimum="0.1"
+ Maximum="1000"
+ Increment="0.5"
+ Value="{Binding Operation.ToExposureTime}"/>
+ <TextBlock Grid.Row="2" Grid.Column="8"
+ VerticalAlignment="Center"
+ Text="s"/>
+
+ <TextBlock Grid.Row="4" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Time increment:"/>
+ <NumericUpDown
+ Grid.Row="4" Grid.Column="2"
+ IsReadOnly="True"
+ ShowButtonSpinner="False"
+ AllowSpin="False"
+ Value="{Binding Operation.IncrementValue}"/>
+ <TextBlock Grid.Row="4" Grid.Column="4" Grid.ColumnSpan="5"
+ VerticalAlignment="Center"
+ Text="s / per layer"/>
+
+ </Grid>
+</UserControl>
diff --git a/UVtools.WPF/Controls/Tools/ToolFadeExposureTimeControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolFadeExposureTimeControl.axaml.cs
new file mode 100644
index 0000000..a75fe28
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolFadeExposureTimeControl.axaml.cs
@@ -0,0 +1,48 @@
+using System;
+using Avalonia.Markup.Xaml;
+using UVtools.Core.Operations;
+using UVtools.WPF.Windows;
+
+namespace UVtools.WPF.Controls.Tools
+{
+ public partial class ToolFadeExposureTimeControl : ToolControl
+ {
+ public OperationFadeExposureTime Operation => BaseOperation as OperationFadeExposureTime;
+
+ public ToolFadeExposureTimeControl()
+ {
+ BaseOperation = new OperationFadeExposureTime(SlicerFile);
+ if (!ValidateSpawn()) return;
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void Callback(ToolWindow.Callbacks callback)
+ {
+ switch (callback)
+ {
+ case ToolWindow.Callbacks.Init:
+ case ToolWindow.Callbacks.Loaded:
+ ParentWindow.LayerIndexEnd = Operation.LayerIndexStart + Operation.LayerCount - 1;
+ Operation.PropertyChanged += (sender, e) =>
+ {
+ if (e.PropertyName != nameof(Operation.LayerCount)) return;
+ ParentWindow.LayerIndexEnd = Operation.LayerIndexStart + Operation.LayerCount - 1;
+ };
+ ParentWindow.PropertyChanged += (sender, e) =>
+ {
+ if (e.PropertyName is nameof(ParentWindow.LayerIndexStart) or nameof(ParentWindow.LayerIndexEnd))
+ {
+ ParentWindow.LayerIndexEnd = Operation.LayerIndexStart + Operation.LayerCount - 1;
+ }
+ };
+ break;
+ }
+
+ }
+ }
+}
diff --git a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml
index 98c3084..38f3b06 100644
--- a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml
@@ -5,7 +5,12 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UVtools.WPF.Controls.Tools.ToolLayerCloneControl">
<StackPanel Spacing="10">
- <StackPanel Spacing="10" Orientation="Horizontal">
+ <ToggleSwitch
+ OffContent="Rebuild whole model height with the new layers"
+ OnContent="Keep the same z position for the cloned layers"
+ IsChecked="{Binding Operation.KeepSamePositionZ}"
+ />
+ <StackPanel Spacing="10" Orientation="Horizontal">
<TextBlock
VerticalAlignment="Center"
Text="Clones:"/>
diff --git a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs
index 382c5c8..93a9e62 100644
--- a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs
@@ -10,13 +10,12 @@ namespace UVtools.WPF.Controls.Tools
{
public OperationLayerClone Operation => BaseOperation as OperationLayerClone;
- public uint ExtraLayers => (uint)Math.Max(0, ((int)Operation.LayerIndexEnd - Operation.LayerIndexStart + 1) * Operation.Clones);
-
+
public string InfoLayersStr
{
get
{
- uint extraLayers = ExtraLayers;
+ uint extraLayers = Operation.ExtraLayers;
return $"Layers: {App.SlicerFile.LayerCount} → {SlicerFile.LayerCount + extraLayers} (+ {extraLayers})";
}
}
@@ -25,7 +24,7 @@ namespace UVtools.WPF.Controls.Tools
{
get
{
- float extraHeight = Layer.RoundHeight(ExtraLayers * SlicerFile.LayerHeight);
+ float extraHeight = Operation.KeepSamePositionZ ? 0 : Layer.RoundHeight(Operation.ExtraLayers * SlicerFile.LayerHeight);
return $"Height: {App.SlicerFile.PrintHeight}mm → {Layer.RoundHeight(SlicerFile.PrintHeight + extraHeight)}mm (+ {extraHeight}mm)";
}
}
diff --git a/UVtools.WPF/Extensions/WindowExtensions.cs b/UVtools.WPF/Extensions/WindowExtensions.cs
index b72784d..c6bb5c0 100644
--- a/UVtools.WPF/Extensions/WindowExtensions.cs
+++ b/UVtools.WPF/Extensions/WindowExtensions.cs
@@ -32,7 +32,7 @@ namespace UVtools.WPF.Extensions
Style = style,
WindowIcon = new WindowIcon(App.GetAsset("/Assets/Icons/UVtools.ico")),
WindowStartupLocation = location,
- CanResize = false,
+ CanResize = UserSettings.Instance.General.WindowsCanResize,
MaxWidth = window.GetScreenWorkingArea().Width - UserSettings.Instance.General.WindowsHorizontalMargin,
MaxHeight = window.GetScreenWorkingArea().Height - UserSettings.Instance.General.WindowsVerticalMargin,
SizeToContent = SizeToContent.WidthAndHeight,
diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs
index 4e94c57..fd53e1d 100644
--- a/UVtools.WPF/MainWindow.LayerPreview.cs
+++ b/UVtools.WPF/MainWindow.LayerPreview.cs
@@ -76,6 +76,7 @@ namespace UVtools.WPF
private bool _showLayerOutlineLayerBoundary;
private bool _showLayerOutlineHollowAreas;
private bool _showLayerOutlineEdgeDetection;
+ private bool _showLayerOutlineDistanceDetection;
private bool _showLayerOutlineSkeletonize;
@@ -389,6 +390,16 @@ namespace UVtools.WPF
}
}
+ public bool ShowLayerOutlineDistanceDetection
+ {
+ get => _showLayerOutlineDistanceDetection;
+ set
+ {
+ if (!RaiseAndSetIfChanged(ref _showLayerOutlineDistanceDetection, value)) return;
+ ShowLayer();
+ }
+ }
+
public bool ShowLayerOutlineSkeletonize
{
get => _showLayerOutlineSkeletonize;
@@ -661,32 +672,47 @@ namespace UVtools.WPF
public void GoFirstLayer()
{
- if (SlicerFile is null) return;
+ if (!IsFileLoaded) return;
if (!CanGoDown) return;
ActualLayer = 0;
}
public void GoPreviousLayer()
{
- if (SlicerFile is null) return;
+ if (!IsFileLoaded) return;
if (!CanGoDown) return;
ActualLayer--;
}
public void GoNextLayer()
{
- if (SlicerFile is null) return;
+ if (!IsFileLoaded) return;
if (!CanGoUp) return;
ActualLayer++;
}
public void GoLastLayer()
{
- if (SlicerFile is null) return;
+ if (!IsFileLoaded) return;
if (!CanGoUp) return;
ActualLayer = SliderMaximumValue;
}
+ public void GoMassLayer(string which)
+ {
+ if (!IsFileLoaded) return;
+ var layer = which switch
+ {
+ "SB" => SlicerFile.LayerManager.SmallestBottomLayer,
+ "LB" => SlicerFile.LayerManager.LargestBottomLayer,
+ "SN" => SlicerFile.LayerManager.SmallestNormalLayer,
+ "LN" => SlicerFile.LayerManager.LargestNormalLayer,
+ _ => null
+ };
+ if (layer is null) return;
+ ActualLayer = layer.Index;
+ }
+
public void RefreshLayerImage()
{
LayerImageBox.Image = LayerCache.ImageBgr.ToBitmap();
@@ -723,6 +749,14 @@ namespace UVtools.WPF
CvInvoke.Canny(LayerCache.Image, canny, 80, 40, 3, true);
CvInvoke.CvtColor(canny, LayerCache.ImageBgr, ColorConversion.Gray2Bgr);
}
+ else if (_showLayerOutlineDistanceDetection)
+ {
+ using var distance = new Mat();
+ CvInvoke.DistanceTransform(LayerCache.Image, distance, null, DistType.C, 3);
+ //distance.ConvertTo(distance, DepthType.Cv8U);
+ CvInvoke.Normalize(distance, distance, byte.MinValue, byte.MaxValue, NormType.MinMax, DepthType.Cv8U);
+ CvInvoke.CvtColor(distance, LayerCache.ImageBgr, ColorConversion.Gray2Bgr);
+ }
else if (_showLayerOutlineSkeletonize)
{
using var skeletonize = LayerCache.Image.Skeletonize();
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index 3db3bc7..6c98575 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -30,6 +30,19 @@
<Image Source="\Assets\Icons\file-import-16x16.png"/>
</MenuItem.Icon>
</MenuItem>
+ <MenuItem
+ Name="MainMenu.File.OpenRecent"
+ Header="Open recent"
+ ToolTip.ShowDelay="2000"
+ ToolTip.Tip="On a file:
+&#x0a;Shift + Click: Open file in a new window
+&#x0a;Shift + Ctrl + Click: Remove file from list
+&#x0a;Ctrl + Click: Purge non-existing files"
+ Items="{Binding MenuFileOpenRecentItems}">
+ <MenuItem.Icon>
+ <Image Source="\Assets\Icons\file-import-16x16.png"/>
+ </MenuItem.Icon>
+ </MenuItem>
<MenuItem
Name="MainMenu.File.Reload"
Header="_Reload"
@@ -61,6 +74,15 @@
<Image Source="\Assets\Icons\save-as-16x16.png"/>
</MenuItem.Icon>
</MenuItem>
+ <MenuItem
+ Name="MainMenu.File.SendTo"
+ Header="Send to"
+ IsEnabled="{Binding IsFileLoaded}"
+ Items="{Binding MenuFileSendToItems}">
+ <MenuItem.Icon>
+ <Image Source="\Assets\Icons\share-square-16x16.png"/>
+ </MenuItem.Icon>
+ </MenuItem>
<MenuItem
Name="MainMenu.File.Close"
Header="_Close"
@@ -1664,7 +1686,7 @@
IsEnabled="{Binding IsFileLoaded}"
DockPanel.Dock="Right"
ColumnDefinitions="160"
- RowDefinitions="Auto,Auto,*,Auto,Auto,Auto,Auto" Margin="5">
+ RowDefinitions="Auto,Auto,*,Auto,Auto,Auto,Auto,Auto" Margin="5">
<TextBlock
Text="{Binding MaximumLayerString}"
Name="Layer.Navigation.Up"
@@ -1786,6 +1808,37 @@
<Image Width="16" Height="16" Source="/Assets/Icons/arrow-top-16x16.png"/>
</Button>
</Grid>
+
+ <StackPanel Grid.Row="6"
+ Margin="0,1,0,0"
+ HorizontalAlignment="Center"
+ VerticalAlignment="Center"
+ Orientation="Horizontal" Spacing="1">
+ <Button
+ ToolTip.Tip="Navigate to the smallest bottom layer in mass"
+ Command="{Binding GoMassLayer}"
+ CommandParameter="SB"
+ Content="SB"/>
+
+ <Button
+ ToolTip.Tip="Navigate to the largest bottom layer in mass"
+ Command="{Binding GoMassLayer}"
+ CommandParameter="LB"
+ Content="LB"/>
+
+ <Button
+ ToolTip.Tip="Navigate to the smallest normal layer in mass"
+ Command="{Binding GoMassLayer}"
+ CommandParameter="SN"
+ Content="SN"/>
+
+ <Button
+ ToolTip.Tip="Navigate to the largest normal layer in mass"
+ Command="{Binding GoMassLayer}"
+ CommandParameter="LN"
+ Content="LN"/>
+ </StackPanel>
+
</Grid>
@@ -1910,11 +1963,35 @@
Content="Hollow areas"/>
<CheckBox
IsChecked="{Binding ShowLayerOutlineEdgeDetection}"
- Content="Edge detection"/>
+ Content="Edge detection">
+ <CheckBox.IsEnabled>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="!ShowLayerOutlineDistanceDetection"/>
+ <Binding Path="!ShowLayerOutlineSkeletonize"/>
+ </MultiBinding>
+ </CheckBox.IsEnabled>
+ </CheckBox>
+ <CheckBox
+ IsChecked="{Binding ShowLayerOutlineDistanceDetection}"
+ ToolTip.Tip="Calculates the distance to the closest zero pixel for each pixel"
+ Content="Distance detection">
+ <CheckBox.IsEnabled>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="!ShowLayerOutlineEdgeDetection"/>
+ <Binding Path="!ShowLayerOutlineSkeletonize"/>
+ </MultiBinding>
+ </CheckBox.IsEnabled>
+ </CheckBox>
<CheckBox
IsChecked="{Binding ShowLayerOutlineSkeletonize}"
- IsEnabled="{Binding !ShowLayerOutlineEdgeDetection}"
- Content="Skeletonize"/>
+ Content="Skeletonize">
+ <CheckBox.IsEnabled>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="!ShowLayerOutlineEdgeDetection"/>
+ <Binding Path="!ShowLayerOutlineDistanceDetection"/>
+ </MultiBinding>
+ </CheckBox.IsEnabled>
+ </CheckBox>
</ContextMenu>
</Button.ContextMenu>
<StackPanel Orientation="Horizontal">
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index d9ac0c4..72ef210 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -208,10 +208,18 @@ namespace UVtools.WPF
},
new()
{
- Tag = new OperationDynamicLayerHeight(),
+ Tag = new OperationFadeExposureTime(),
Icon = new Avalonia.Controls.Image
{
- Source = new Bitmap(App.GetAsset("/Assets/Icons/dynamic-layers-16x16.png"))
+ Source = new Bitmap(App.GetAsset("/Assets/Icons/history-16x16.png"))
+ }
+ },
+ new()
+ {
+ Tag = new OperationDoubleExposure(),
+ Icon = new Avalonia.Controls.Image
+ {
+ Source = new Bitmap(App.GetAsset("/Assets/Icons/equals-16x16.png"))
}
},
new()
@@ -224,6 +232,14 @@ namespace UVtools.WPF
},
new()
{
+ Tag = new OperationDynamicLayerHeight(),
+ Icon = new Avalonia.Controls.Image
+ {
+ Source = new Bitmap(App.GetAsset("/Assets/Icons/dynamic-layers-16x16.png"))
+ }
+ },
+ new()
+ {
Tag = new OperationLayerReHeight(),
Icon = new Avalonia.Controls.Image
{
@@ -407,6 +423,9 @@ namespace UVtools.WPF
private bool _isGUIEnabled = true;
private uint _savesCount;
private bool _canSave;
+ private MenuItem _menuFileSendTo;
+ private MenuItem[] _menuFileOpenRecentItems;
+ private MenuItem[] _menuFileSendToItems;
private MenuItem[] _menuFileConvertItems;
private PointerEventArgs _globalPointerEventArgs;
@@ -501,6 +520,18 @@ namespace UVtools.WPF
set => RaiseAndSetIfChanged(ref _canSave, value);
}
+ public MenuItem[] MenuFileOpenRecentItems
+ {
+ get => _menuFileOpenRecentItems;
+ set => RaiseAndSetIfChanged(ref _menuFileOpenRecentItems, value);
+ }
+
+ public MenuItem[] MenuFileSendToItems
+ {
+ get => _menuFileSendToItems;
+ set => RaiseAndSetIfChanged(ref _menuFileSendToItems, value);
+ }
+
public MenuItem[] MenuFileConvertItems
{
get => _menuFileConvertItems;
@@ -522,7 +553,8 @@ namespace UVtools.WPF
InitClipboardLayers();
InitLayerPreview();
-
+ RefreshRecentFiles(true);
+
TabInformation = this.FindControl<TabItem>("TabInformation");
TabGCode = this.FindControl<TabItem>("TabGCode");
TabIssues = this.FindControl<TabItem>("TabIssues");
@@ -566,6 +598,94 @@ namespace UVtools.WPF
{
ProcessFiles(e.Data.GetFileNames().ToArray());
});
+
+ _menuFileSendTo = this.FindControl<MenuItem>("MainMenu.File.SendTo");
+ this.FindControl<MenuItem>("MainMenu.File").SubmenuOpened += (sender, e) =>
+ {
+ if (!IsFileLoaded) return;
+
+ var menuItems = new List<MenuItem>();
+
+ var drives = DriveInfo.GetDrives();
+
+ foreach (var drive in drives)
+ {
+ if(drive.DriveType != DriveType.Removable || !drive.IsReady) continue; // Not our target, skip
+ if (SlicerFile.FileFullPath.StartsWith(drive.Name)) continue; // File already on this device, skip
+
+ var header = drive.Name;
+ if (!string.IsNullOrWhiteSpace(drive.VolumeLabel))
+ {
+ header += $" {drive.VolumeLabel}";
+ }
+
+ header += $" ({SizeExtensions.SizeSuffix(drive.AvailableFreeSpace)}) [{drive.DriveFormat}]";
+
+ var menuItem = new MenuItem
+ {
+ Header = header,
+ Tag = drive,
+ };
+ menuItem.Click += FileSendToItemClick;
+
+ menuItems.Add(menuItem);
+ }
+
+ MenuFileSendToItems = menuItems.ToArray();
+ _menuFileSendTo.IsVisible = _menuFileSendTo.IsEnabled = menuItems.Count > 0;
+ };
+ }
+
+ private async void FileSendToItemClick(object? sender, RoutedEventArgs e)
+ {
+ if (sender is not MenuItem menuItem) return;
+ if (menuItem.Tag is not DriveInfo drive) return;
+
+ if (!drive.IsReady)
+ {
+ await this.MessageBoxError($"The device {drive.Name} is not ready/available at this time.", "Unable to copy the file");
+ return;
+ }
+
+ if (CanSave)
+ {
+ switch (await this.MessageBoxQuestion("There are unsaved changes. Do you want to save the current file before copy it over?\n\n" +
+ "Yes: Save the current file and copy it over.\n" +
+ "No: Copy the file without current modifications.\n" +
+ "Cancel: Abort the operation.", "Send to - Unsaved changes", ButtonEnum.YesNoCancel))
+ {
+ case ButtonResult.Yes:
+ await SaveFile(true);
+ break;
+ case ButtonResult.No:
+ break;
+ default:
+ return;
+ }
+ }
+
+ ShowProgressWindow($"Copying: {SlicerFile.Filename} to {drive.Name}", false);
+ Progress.ItemName = "Copying";
+ await Task.Factory.StartNew(() =>
+ {
+ try
+ {
+ File.Copy(SlicerFile.FileFullPath, $"{drive.Name}{SlicerFile.Filename}", true);
+ return true;
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (Exception exception)
+ {
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ await this.MessageBoxError(exception.ToString(), "Unable to copy the file"));
+ }
+
+ return false;
+ });
+
+ IsGUIEnabled = true;
}
protected override void OnOpened(EventArgs e)
@@ -1083,6 +1203,8 @@ namespace UVtools.WPF
return;
}
+ AddRecentFile(fileName);
+
if (SlicerFile.LayerCount == 0)
{
await this.MessageBoxError("It seems this file has no layers. Possible causes could be:\n" +
@@ -1144,6 +1266,7 @@ namespace UVtools.WPF
if (task && convertedFile is not null)
{
SlicerFile = convertedFile;
+ AddRecentFile(SlicerFile.FileFullPath);
}
}
}
@@ -1325,6 +1448,12 @@ namespace UVtools.WPF
}
SlicerFile.PropertyChanged += SlicerFileOnPropertyChanged;
+#if !DEBUG
+ if (SlicerFile is CTBEncryptedFile)
+ {
+ await this.MessageBoxInfo(CTBEncryptedFile.Preamble, "Information");
+ }
+#endif
}
private void SlicerFileOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
@@ -1448,7 +1577,7 @@ namespace UVtools.WPF
}
-
+ public async Task<bool> SaveFile(bool ignoreOverwriteWarning) => await SaveFile(null, ignoreOverwriteWarning);
public async Task<bool> SaveFile(string filepath = null, bool ignoreOverwriteWarning = false)
{
@@ -1470,7 +1599,7 @@ namespace UVtools.WPF
IsGUIEnabled = false;
ShowProgressWindow($"Saving {Path.GetFileName(filepath)}");
-
+
var task = await Task.Factory.StartNew( () =>
{
try
@@ -1575,7 +1704,7 @@ namespace UVtools.WPF
}
- #region Operations
+#region Operations
public async Task<Operation> ShowRunOperation(Type type, Operation loadOperation = null)
{
var operation = await ShowOperation(type, loadOperation);
@@ -1735,10 +1864,102 @@ namespace UVtools.WPF
return result;
}
- #endregion
+ private void RefreshRecentFiles(bool reloadFiles = false)
+ {
+ if(reloadFiles) RecentFiles.Load();
+
+ var items = new List<MenuItem>();
+
+ foreach (var file in RecentFiles.Instance)
+ {
+ var item = new MenuItem
+ {
+ Header = Path.GetFileName(file),
+ Tag = file,
+ IsEnabled = SlicerFile?.FileFullPath != file
+ };
+ ToolTip.SetTip(item, file);
+ ToolTip.SetPlacement(item, PlacementMode.Right);
+ ToolTip.SetShowDelay(item, 100);
+ items.Add(item);
+
+ item.Click += MenuFileOpenRecentItemOnClick;
+ }
+
+ MenuFileOpenRecentItems = items.ToArray();
+ }
+
+ private void AddRecentFile(string file)
+ {
+ if (file == Path.Combine(App.ApplicationPath, About.DemoFile)) return;
+ RecentFiles.Load();
+ RecentFiles.Instance.Insert(0, file);
+ RecentFiles.Save();
+ RefreshRecentFiles();
+ }
+
+ private async void MenuFileOpenRecentItemOnClick(object? sender, RoutedEventArgs e)
+ {
+ if (sender is not MenuItem { Tag: string file }) return;
+ if (IsFileLoaded && SlicerFile.FileFullPath == file) return;
+
+ if (_globalModifiers == KeyModifiers.Control)
+ {
+ if (await this.MessageBoxQuestion("Are you sure you want to purge the non-existing files from the recent list?",
+ "Purge the non-existing files?") == ButtonResult.Yes)
+ {
+ /*if (_globalModifiers == KeyModifiers.Shift)
+ {
+ RecentFiles.ClearFiles(true);
+ RefreshRecentFiles();
+ return;
+ }*/
+ if (RecentFiles.PurgenNonExistingFiles(true) > 0) RefreshRecentFiles();
+ }
+
+ return;
+ }
+
+ if ((_globalModifiers & KeyModifiers.Control) != 0 &&
+ (_globalModifiers & KeyModifiers.Shift) != 0)
+ {
+ if (await this.MessageBoxQuestion($"Are you sure you want to remove the selected file from the recent list?\n{file}",
+ "Remove the file from recent list?") == ButtonResult.Yes)
+ {
+ RecentFiles.Load();
+ RecentFiles.Instance.Remove(file);
+ RecentFiles.Save();
+
+ RefreshRecentFiles();
+ }
+
+ return;
+ }
+
+ if (!File.Exists(file))
+ {
+ if (await this.MessageBoxQuestion($"The file: {file} does not exists anymore.\n" +
+ "Do you want to remove this file from recent list?",
+ "The file does not exists") == ButtonResult.Yes)
+ {
+ RecentFiles.Load();
+ RecentFiles.Instance.Remove(file);
+ RecentFiles.Save();
+
+ RefreshRecentFiles();
+ }
+
+ return;
+ }
+
+ if (_globalModifiers == KeyModifiers.Shift) App.NewInstance(file);
+ else ProcessFile(file);
+ }
+
+#endregion
- #endregion
+#endregion
}
}
diff --git a/UVtools.WPF/Structures/OperationProfiles.cs b/UVtools.WPF/Structures/OperationProfiles.cs
index 479e196..982fd16 100644
--- a/UVtools.WPF/Structures/OperationProfiles.cs
+++ b/UVtools.WPF/Structures/OperationProfiles.cs
@@ -37,6 +37,8 @@ namespace UVtools.WPF.Structures
[XmlElement(typeof(OperationPixelDimming))]
[XmlElement(typeof(OperationInfill))]
[XmlElement(typeof(OperationBlur))]
+ [XmlElement(typeof(OperationFadeExposureTime))]
+ [XmlElement(typeof(OperationDoubleExposure))]
[XmlElement(typeof(OperationDynamicLayerHeight))]
[XmlElement(typeof(OperationDynamicLifts))]
[XmlElement(typeof(OperationRaiseOnPrintFinish))]
diff --git a/UVtools.WPF/Structures/RecentFiles.cs b/UVtools.WPF/Structures/RecentFiles.cs
new file mode 100644
index 0000000..35d32dd
--- /dev/null
+++ b/UVtools.WPF/Structures/RecentFiles.cs
@@ -0,0 +1,201 @@
+/*
+ * 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.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+
+namespace UVtools.WPF.Structures
+{
+ public class RecentFiles : IList<string>
+ {
+ #region Properties
+ /// <summary>
+ /// Default filepath for store
+ /// </summary>
+ private static string FilePath => Path.Combine(UserSettings.SettingsFolder, "recentfiles.dat");
+
+ private readonly List<string> _files = new();
+
+ public byte MaxEntries { get; set; } = 40;
+
+ #endregion
+
+ #region Singleton
+
+ private static Lazy<RecentFiles> _instanceHolder =
+ new(() => new RecentFiles());
+
+ /// <summary>
+ /// Instance of <see cref="UserSettings"/> (singleton)
+ /// </summary>
+ public static RecentFiles Instance => _instanceHolder.Value;
+
+ //public static List<Operation> Operations => _instance.Operations;
+ #endregion
+
+ #region Constructor
+
+ private RecentFiles()
+ { }
+
+ #endregion
+
+ #region Static Methods
+ /// <summary>
+ /// Clear all files
+ /// </summary>
+ /// <param name="save">True to save settings on file, otherwise false</param>
+ public static void ClearFiles(bool save = true)
+ {
+ Instance.Clear();
+ if (save) Save();
+ }
+
+ /// <summary>
+ /// Load settings from file
+ /// </summary>
+ public static void Load()
+ {
+ if (!File.Exists(FilePath))
+ {
+ return;
+ }
+
+ Instance.Clear();
+
+ try
+ {
+ using var tr = new StreamReader(FilePath);
+
+ string path;
+ while ((path = tr.ReadLine()) is not null)
+ {
+ if(string.IsNullOrWhiteSpace(path)) continue;
+
+ try
+ {
+ Path.GetFullPath(path);
+ }
+ catch (Exception e)
+ {
+ continue;
+ }
+
+ Instance.Add(path);
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e.ToString());
+ }
+ }
+
+ /// <summary>
+ /// Save settings to file
+ /// </summary>
+ public static void Save()
+ {
+ try
+ {
+ using var tw = new StreamWriter(FilePath);
+
+ foreach (var file in Instance)
+ {
+ tw.WriteLine(file);
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e.ToString());
+ }
+ }
+
+ public static int PurgenNonExistingFiles(bool save = true)
+ {
+ Load();
+ var count = Instance.RemoveAll(s => !File.Exists(s));
+ if(save && count > 0) Save();
+ return count;
+ }
+
+ #endregion
+
+ #region List Implementation
+ public IEnumerator<string> GetEnumerator()
+ {
+ return _files.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)_files).GetEnumerator();
+ }
+
+ public void Add(string item)
+ {
+ _files.RemoveAll(s => s == item);
+ if (Count >= MaxEntries) return;
+ _files.Add(item);
+ }
+
+ public void Clear()
+ {
+ _files.Clear();
+ }
+
+ public bool Contains(string item)
+ {
+ return _files.Contains(item);
+ }
+
+ public void CopyTo(string[] array, int arrayIndex)
+ {
+ _files.CopyTo(array, arrayIndex);
+ }
+
+ public bool Remove(string item)
+ {
+ return _files.Remove(item);
+ }
+
+ public int Count => _files.Count;
+
+ public bool IsReadOnly => ((ICollection<string>)_files).IsReadOnly;
+
+ public int IndexOf(string item)
+ {
+ return _files.IndexOf(item);
+ }
+
+ public void Insert(int index, string item)
+ {
+ _files.RemoveAll(s => s == item);
+ _files.Insert(index, item);
+ while (Count > MaxEntries)
+ {
+ RemoveAt(Count-1);
+ }
+ }
+
+ public void RemoveAt(int index)
+ {
+ _files.RemoveAt(index);
+ }
+
+ public int RemoveAll(Predicate<string> match) => _files.RemoveAll(match);
+
+ public string this[int index]
+ {
+ get => _files[index];
+ set => _files[index] = value;
+ }
+ #endregion
+ }
+}
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index 3f1d96f..fb90318 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -12,7 +12,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
- <Version>2.20.5</Version>
+ <Version>2.21.0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
diff --git a/UVtools.WPF/Windows/ToolWindow.axaml b/UVtools.WPF/Windows/ToolWindow.axaml
index e8959f5..299b4a8 100644
--- a/UVtools.WPF/Windows/ToolWindow.axaml
+++ b/UVtools.WPF/Windows/ToolWindow.axaml
@@ -53,6 +53,7 @@
VerticalAlignment="Center"
HorizontalAlignment="Right"
IsChecked="{Binding LayerRangeSync}"
+ IsVisible="{Binding LayerIndexEndEnabled}"
ToolTip.Tip="Synchronize and lock the layer range for single layer navigation"
Content="Synchronize"/>
</Grid>
@@ -93,9 +94,14 @@
VerticalAlignment="Center"
Minimum="0"
Maximum="{Binding MaximumLayerIndex}"
- IsEnabled="{Binding !LayerRangeSync}"
- Value="{Binding LayerIndexEnd}"
- />
+ Value="{Binding LayerIndexEnd}">
+ <NumericUpDown.IsEnabled>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="LayerIndexEndEnabled"/>
+ <Binding Path="!LayerRangeSync"/>
+ </MultiBinding>
+ </NumericUpDown.IsEnabled>
+ </NumericUpDown>
<Button Name="LayerSelectPresetButton"
Grid.Row="0"
diff --git a/UVtools.WPF/Windows/ToolWindow.axaml.cs b/UVtools.WPF/Windows/ToolWindow.axaml.cs
index cd7a6fe..c43aa92 100644
--- a/UVtools.WPF/Windows/ToolWindow.axaml.cs
+++ b/UVtools.WPF/Windows/ToolWindow.axaml.cs
@@ -38,6 +38,7 @@ namespace UVtools.WPF.Windows
private bool _layerRangeSync;
private uint _layerIndexStart;
private uint _layerIndexEnd;
+ private bool _layerIndexEndEnabled = true;
private bool _isROIVisible;
private bool _isMasksVisible;
@@ -110,13 +111,14 @@ namespace UVtools.WPF.Windows
get => _layerIndexStart;
set
{
+ value = Math.Min(value, SlicerFile.LastLayerIndex);
+
if (ToolControl?.BaseOperation is not null)
{
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None;
ToolControl.BaseOperation.LayerIndexStart = value;
}
- value = value.Clamp(0, SlicerFile.LastLayerIndex);
if (!RaiseAndSetIfChanged(ref _layerIndexStart, value)) return;
RaisePropertyChanged(nameof(LayerStartMM));
RaisePropertyChanged(nameof(LayerRangeCountStr));
@@ -137,13 +139,14 @@ namespace UVtools.WPF.Windows
get => _layerIndexEnd;
set
{
+ value = Math.Min(value, SlicerFile.LastLayerIndex);
+
if (ToolControl?.BaseOperation is not null)
{
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None;
ToolControl.BaseOperation.LayerIndexEnd = value;
}
- value = value.Clamp(0, SlicerFile.LastLayerIndex);
if (!RaiseAndSetIfChanged(ref _layerIndexEnd, value)) return;
RaisePropertyChanged(nameof(LayerEndMM));
RaisePropertyChanged(nameof(LayerRangeCountStr));
@@ -151,7 +154,14 @@ namespace UVtools.WPF.Windows
}
public float LayerEndMM => SlicerFile[_layerIndexEnd].PositionZ;
-
+
+ public bool LayerIndexEndEnabled
+ {
+ get => _layerIndexEndEnabled;
+ set => RaiseAndSetIfChanged(ref _layerIndexEndEnabled, value);
+ }
+
+
public string LayerRangeCountStr
{
get
@@ -198,14 +208,14 @@ namespace UVtools.WPF.Windows
public void SelectBottomLayers()
{
LayerIndexStart = 0;
- LayerIndexEnd = SlicerFile.BottomLayerCount-1u;
+ LayerIndexEnd = Math.Max(1, SlicerFile.FirstNormalLayer?.Index ?? 1) - 1u;
if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Bottom;
}
public void SelectNormalLayers()
{
- LayerIndexStart = SlicerFile.BottomLayerCount;
+ LayerIndexStart = SlicerFile.FirstNormalLayer?.Index ?? 0;
LayerIndexEnd = MaximumLayerIndex;
if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Normal;
@@ -571,13 +581,14 @@ namespace UVtools.WPF.Windows
}
}
- public ToolWindow(string description = null, bool layerRangeVisible = true) : this()
+ public ToolWindow(string description = null, bool layerRangeVisible = true, bool layerEndIndexEnabled = true) : this()
{
_description = description;
_layerRangeVisible = layerRangeVisible;
+ _layerIndexEndEnabled = layerEndIndexEnabled;
}
- public ToolWindow(ToolControl toolControl) : this(toolControl.BaseOperation.Description, toolControl.BaseOperation.StartLayerRangeSelection != Enumerations.LayerRangeSelection.None)
+ public ToolWindow(ToolControl toolControl) : this(toolControl.BaseOperation.Description, toolControl.BaseOperation.StartLayerRangeSelection != Enumerations.LayerRangeSelection.None, toolControl.BaseOperation.LayerIndexEndEnabled)
{
ToolControl = toolControl;
toolControl.ParentWindow = this;
diff --git a/build/CreateRelease.WPF.ps1 b/build/CreateRelease.WPF.ps1
index 4985e72..f1fc18f 100644
--- a/build/CreateRelease.WPF.ps1
+++ b/build/CreateRelease.WPF.ps1
@@ -34,7 +34,7 @@ Set-Location $PSScriptRoot\..
####################################
$enableMSI = $true
#$buildOnly = 'win-x64'
-#$buildOnly = 'linux-x64'
+#$buildOnly = 'osx-x64'
$enableNugetPublish = $true
# Profilling
$stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch