diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9deea85e..2c621e24 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 27/02/2023 - v3.12.0
+
+- (Add) Allow to pause and resume operations (#654)
+- (Add) `Layer.FirstTransitionLayer`
+- (Add) `Layer.LastTransitionLayer`
+- (Add) File format: Elegoo GOO
+- (Add) PrusaSlicer Printer: Elegoo Mars 4
+- (Improvement) Allocate maximum GPU memory for Skia up to 256 MB
+- (Improvement) Set and sanitize transition layers exposure time from last bottom layer and first normal layer instead of global times (#659)
+- (Change) CXDLP: Default version from 2 to 3
+- (Fix) UI was not rendering with GPU (ANGLE)
+- (Fix) `Layer.IsTransitionLayer` was returning the wrong value
+- (Upgrade) .NET from 6.0.13 to 6.0.14
+
## 01/01/2023 - v3.11.2
- (Fix) Converting to lgs sets Bottom Layer Count to 0 (#655)
diff --git a/PrusaSlicer/printer/Elegoo Mars 4.ini b/PrusaSlicer/printer/Elegoo Mars 4.ini
new file mode 100644
index 00000000..e59d3770
--- /dev/null
+++ b/PrusaSlicer/printer/Elegoo Mars 4.ini
@@ -0,0 +1,42 @@
+# generated by PrusaSlicer 2.5.0+win64 on 2023-02-27 at 01:25:19 UTC
+absolute_correction = 0
+area_fill = 50
+bed_custom_model =
+bed_custom_texture =
+bed_shape = 0x0,132.8x0,132.8x74.7,0x74.7
+default_sla_material_profile = Prusa Orange Tough 0.05
+default_sla_print_profile = 0.05 Normal
+display_height = 74.7
+display_mirror_x = 0
+display_mirror_y = 1
+display_orientation = landscape
+display_pixels_x = 2560
+display_pixels_y = 1440
+display_width = 132.8
+elefant_foot_compensation = 0.2
+elefant_foot_min_width = 0.2
+fast_tilt_time = 5
+gamma_correction = 1
+high_viscosity_tilt_time = 10
+host_type = octoprint
+inherits = Original Prusa SL1
+max_exposure_time = 120
+max_initial_exposure_time = 300
+max_print_height = 150
+min_exposure_time = 1
+min_initial_exposure_time = 1
+print_host =
+printer_model = SL1
+printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\nPRINTER_VENDOR_ELEGOO\nPRINTER_MODEL_MARS4\nFILEFORMAT_GOO\n\nSTART_CUSTOM_VALUES\nBottomWaitTimeBeforeCure_3\nWaitTimeBeforeCure_2.5\nBottomWaitTimeAfterCure_3\nWaitTimeAfterCure_1\nBottomLiftHeight_7\nLiftHeight_5\nBottomLiftSpeed_80\nLiftSpeed_100\nBottomWaitTimeAfterLift_0\nWaitTimeAfterLift_0\nRetractSpeed_150\nBottomLightPWM_255\nLightPWM_255\nEND_CUSTOM_VALUES
+printer_settings_id =
+printer_technology = SLA
+printer_variant = default
+printer_vendor =
+printhost_apikey =
+printhost_cafile =
+relative_correction = 1,1
+relative_correction_x = 1
+relative_correction_y = 1
+relative_correction_z = 1
+slow_tilt_time = 8
+thumbnails = 290x290,116x116
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 2b9e5457..a4b8684f 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,2 +1,12 @@
-- (Fix) Converting to lgs sets Bottom Layer Count to 0 (#655)
+- (Add) Allow to pause and resume operations (#654)
+- (Add) `Layer.FirstTransitionLayer`
+- (Add) `Layer.LastTransitionLayer`
+- (Add) File format: Elegoo GOO
+- (Add) PrusaSlicer Printer: Elegoo Mars 4
+- (Improvement) Allocate maximum GPU memory for Skia up to 256 MB
+- (Improvement) Set and sanitize transition layers exposure time from last bottom layer and first normal layer instead of global times (#659)
+- (Change) CXDLP: Default version from 2 to 3
+- (Fix) UI was not rendering with GPU (ANGLE)
+- (Fix) `Layer.IsTransitionLayer` was returning the wrong value
+- (Upgrade) .NET from 6.0.13 to 6.0.14
diff --git a/Scripts/010 Editor/FileFormats.1pj b/Scripts/010 Editor/FileFormats.1pj
index 4dbf2bf8..a6c26eec 100644
--- a/Scripts/010 Editor/FileFormats.1pj
+++ b/Scripts/010 Editor/FileFormats.1pj
@@ -15,6 +15,7 @@
mdlp.bt
osf.bt
osla.bt
+ goo.bt
photons.bt
PhotonWorkshop.bt
phz.bt
diff --git a/Scripts/010 Editor/PhotonWorkshop.bt b/Scripts/010 Editor/PhotonWorkshop.bt
index b1640e5c..b92b5dc2 100644
--- a/Scripts/010 Editor/PhotonWorkshop.bt
+++ b/Scripts/010 Editor/PhotonWorkshop.bt
@@ -59,7 +59,7 @@ struct HEADER {
uint ResolutionY ;
float WeightG ;
float Price ;
- wstring PriceCurrencyDec ;
+ wstring PriceCurrency ;
uint PerLayerOverride ;
uint PrintTime ;
uint TransitionLayerCount ;
diff --git a/Scripts/010 Editor/goo.bt b/Scripts/010 Editor/goo.bt
new file mode 100644
index 00000000..dcb7952e
--- /dev/null
+++ b/Scripts/010 Editor/goo.bt
@@ -0,0 +1,123 @@
+//------------------------------------------------
+//--- 010 Editor v8.0.1 Binary Template
+//
+// File: goo
+// Authors: Tiago Conceição
+//------------------------------------------------
+
+BigEndian();
+
+enum BOOL { False, True };
+
+struct HEADER {
+ char Version[4] ; // format version: V3.0
+ char Magic[8] ; // Fix contant:0x07 0x00 0x00 0x00 0x44 0x4C 0x50 0x00
+ char SoftwareName[32] ;
+ char SoftwareVersion[24] ;
+ char FileCreateTime[24] ; // yyyy-mm-dd hh:mm:ss
+ char MachineName[32] ;
+ char MachineType[32] ;
+ char ProfileName[32] ;
+ ushort AntiAliasingLevel ;
+ ushort GreyLevel ;
+ ushort BlurLevel ;
+ ubyte SmallPreview[116*116*2] ; // RGB_565 16Bit
+ ubyte SmallPreviewDelimiter[2] ; // \n 0x0D 0x0A
+ ubyte BigPreview[290*290*2] ; // RGB_565 16Bit
+ ubyte BigPreviewDelimiter[2] ; // \n 0x0D 0x0A
+
+ uint LayerCount ;
+ ushort ResolutionX ;
+ ushort ResolutionY ;
+ BOOL MirrorX ;
+ BOOL MirrorY ;
+ float DisplayWidth ;
+ float DisplayHeight ;
+ float MachineZ ;
+ float LayerHeight ;
+ float ExposureTime ;
+ enum { WaitTime, LightOffDelay } DelayMode ; // 1: wait time mode , 0: light off delay mode
+ float LightOffDelay ;
+ float BottomWaitTimeAfterCure ;
+ float BottomWaitTimeAfterLift ;
+ float BottomWaitTimeBeforeCure ;
+ float WaitTimeAfterCure ;
+ float WaitTimeAfterLift ;
+ float WaitTimeBeforeCure ;
+ float BottomExposureTime ;
+ uint BottomLayerCount ;
+ float BottomLiftHeight ;
+ float BottomLiftSpeed ; // mm/min
+ float LiftHeight ;
+ float LiftSpeed ; // mm/min
+ float BottomRetractHeight ;
+ float BottomRetractSpeed ; // mm/min
+ float RetractHeight ;
+ float RetractSpeed ; // mm/min
+
+ float BottomLiftHeight2 ;
+ float BottomLiftSpeed2 ; // mm/min
+ float LiftHeight2 ;
+ float LiftSpeed2 ; // mm/min
+ float BottomRetractHeight2 ;
+ float BottomRetractSpeed2 ; // mm/min
+ float RetractHeight2 ;
+ float RetractSpeed2 ; // mm/min
+
+ ushort BottomLightPWM ;
+ ushort LightPWM ;
+
+ BOOL PerLayerSettings ; // 0: normal mode; 1:advance mode, printing use the value of "Layer Definition Content"
+
+ uint PrintTime ; // seconds
+ float Volume ; // The volume of all parts. unit:mm3
+ float MaterialGrams ; // The weight of all parts. unit:g
+ float MaterialCost ;
+ char PriceCurrency[8] ; // $, €
+
+ uint LayerDefAddress ;
+ ubyte GrayScaleLevel ; // 0: The range of pixel's gray value is from 0x0 ~ 0xf; 1:The range of pixel's gray value is from 0x0 ~ 0xff;
+ ushort TransitionLayerCount ;
+} header;
+
+struct LAYER_DEF {
+ // 0: reserve; 1: current layer pause printing
+ ushort Pause ;
+ // The lift distance of Z axis when "Pause flag" equal 1. unit:mm
+ float PausePositionZ ;
+ float PositionZ ;
+ float ExposureTime ;
+ float LightOffDelay ;
+ float WaitTimeAfterCure ;
+ float WaitTimeAfterLift ;
+ float WaitTimeBeforeCure ;
+ float LiftHeight ;
+ float LiftSpeed ; // mm/min
+ float LiftHeight2 ;
+ float LiftSpeed2 ; // mm/min
+ float RetractHeight ;
+ float RetractSpeed ; // mm/min
+ float RetractHeight2 ;
+ float RetractSpeed2 ; // mm/min
+ ushort LightPWM ;
+ ubyte DelimiterData[2] ; // \n 0x0D 0x0A
+ uint DataSize ;
+ // First RLE byte is Magic (0x55)
+ ubyte RLE[DataSize] ;
+ // Last RLE byte is Checksum XOR
+ ubyte DelimiterRLE[2] ; // \n 0x0D 0x0A
+};
+
+struct LAYERS{
+ local uint i;
+ for(i = 0; i < header.LayerCount; i++){
+ LAYER_DEF layerDef;
+ }
+} layers;
+
+
+struct FOOTER {
+ ubyte Padding[3] ; // format version
+ char Magic[8] ; // Fix contant:0x07 0x00 0x00 0x00 0x44 0x4C 0x50 0x00
+
+} footer;
\ No newline at end of file
diff --git a/Scripts/010 Editor/osf.bt b/Scripts/010 Editor/osf.bt
index 0c8ebf45..e4239876 100644
--- a/Scripts/010 Editor/osf.bt
+++ b/Scripts/010 Editor/osf.bt
@@ -107,12 +107,12 @@ struct HEADER {
ubyte ProtocolType ; // 0
local int leftover = HeaderLength - 350001;
- if(HeaderLength == 350013) // VLR
+ /*if(HeaderLength == 350013) // VLR
{
uint Unknown ; // 350054
ubyte Unknown1[8] ;
leftover -= 12;
- }
+ }*/
if(leftover > 0)
diff --git a/Scripts/UVtools.ScriptSample/ScriptAdvancedDialogSample.cs b/Scripts/UVtools.ScriptSample/ScriptAdvancedDialogSample.cs
index 36e482af..37c25012 100644
--- a/Scripts/UVtools.ScriptSample/ScriptAdvancedDialogSample.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptAdvancedDialogSample.cs
@@ -75,7 +75,7 @@ public bool ScriptExecute()
for (int i = 0; i < Iterations.Value; i++)
{
- Progress.ThrowIfCancellationRequested();
+ Progress.PauseOrCancelIfRequested();
Thread.Sleep(1000);
Progress.Log = $"Task {i}: Completed!\n{Progress.Log}";
diff --git a/Scripts/UVtools.ScriptSample/ScriptAutomateWorkflowSample.cs b/Scripts/UVtools.ScriptSample/ScriptAutomateWorkflowSample.cs
index 5bfc8766..320e33ee 100644
--- a/Scripts/UVtools.ScriptSample/ScriptAutomateWorkflowSample.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptAutomateWorkflowSample.cs
@@ -99,7 +99,7 @@ public bool ScriptExecute()
foreach (var operation in operations) // Loop all my created operations to execute them
{
- Progress.ThrowIfCancellationRequested(); // Abort operation, user requested cancellation
+ Progress.PauseOrCancelIfRequested(); // Abort operation, user requested cancellation
operation.ROI = Operation.ROI; // Copy user selected ROI to my operation
operation.MaskPoints = Operation.MaskPoints; // Copy user selected Masks to my operation
if (!operation.CanValidate()) continue; // If cant validate don't execute the operation
diff --git a/Scripts/UVtools.ScriptSample/ScriptChangeLayerPropertiesSample.cs b/Scripts/UVtools.ScriptSample/ScriptChangeLayerPropertiesSample.cs
index 71fc9a8f..a4257342 100644
--- a/Scripts/UVtools.ScriptSample/ScriptChangeLayerPropertiesSample.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptChangeLayerPropertiesSample.cs
@@ -48,7 +48,7 @@ public bool ScriptExecute()
for (uint layerIndex = Operation.LayerIndexStart; layerIndex <= Operation.LayerIndexEnd; layerIndex++)
{
- Progress.ThrowIfCancellationRequested(); // Abort operation, user requested cancellation
+ Progress.PauseOrCancelIfRequested(); // Abort operation, user requested cancellation
var layer = SlicerFile[layerIndex]; // Unpack and expose layer variable for easier use
layer.LiftHeight = random.Next(3, 10); // Random value from 3 to 10
diff --git a/Scripts/UVtools.ScriptSample/ScriptCloneSettings.cs b/Scripts/UVtools.ScriptSample/ScriptCloneSettings.cs
index 8b6edd02..39b7d784 100644
--- a/Scripts/UVtools.ScriptSample/ScriptCloneSettings.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptCloneSettings.cs
@@ -120,7 +120,7 @@ public bool ScriptExecute()
var results = new List?>>();
foreach (var filePath in filePaths)
{
- Progress.ThrowIfCancellationRequested();
+ Progress.PauseOrCancelIfRequested();
Tuple?> result;
try
diff --git a/Scripts/UVtools.ScriptSample/ScriptCompensateCrossBleeding.cs b/Scripts/UVtools.ScriptSample/ScriptCompensateCrossBleeding.cs
index a2178feb..efb8e818 100644
--- a/Scripts/UVtools.ScriptSample/ScriptCompensateCrossBleeding.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptCompensateCrossBleeding.cs
@@ -1,5 +1,6 @@
using Emgu.CV;
using System;
+using System.Diagnostics;
using System.Drawing;
using System.Threading.Tasks;
using UVtools.Core;
@@ -45,6 +46,7 @@ public bool ScriptExecute()
CoreSettings.GetParallelOptions(Progress),
layerIndex =>
{
+ Progress.PauseIfRequested();
var layersBelowCount = layerIndex > LayerBleed.Value ? LayerBleed.Value : layerIndex;
using var sourceMat = originalLayers[layerIndex].LayerMat;
diff --git a/Scripts/UVtools.ScriptSample/ScriptInsetSample.cs b/Scripts/UVtools.ScriptSample/ScriptInsetSample.cs
index 2a1cc577..3f903b84 100644
--- a/Scripts/UVtools.ScriptSample/ScriptInsetSample.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptInsetSample.cs
@@ -95,6 +95,7 @@ public bool ScriptExecute()
// Loop user selected layers in parallel, this will put each core of CPU working here on parallel
Parallel.For(Operation.LayerIndexStart, Operation.LayerIndexEnd+1, CoreSettings.GetParallelOptions(Progress), layerIndex =>
{
+ Progress.PauseIfRequested();
var layer = SlicerFile[layerIndex]; // Unpack and expose layer variable for easier use
using var mat = layer.LayerMat; // Gets this layer mat/image
var original = mat.Clone(); // Keep a original mat copy
diff --git a/Scripts/UVtools.ScriptSample/ScriptLightBleedCompensationSample.cs b/Scripts/UVtools.ScriptSample/ScriptLightBleedCompensationSample.cs
index 88ca4639..3160e6b1 100644
--- a/Scripts/UVtools.ScriptSample/ScriptLightBleedCompensationSample.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptLightBleedCompensationSample.cs
@@ -89,6 +89,7 @@ public bool ScriptExecute()
// Loop user selected layers in parallel, this will put each core of CPU working here on parallel
Parallel.For(Operation.LayerIndexStart, Operation.LayerIndexEnd+1, CoreSettings.GetParallelOptions(Progress), layerIndex =>
{
+ Progress.PauseIfRequested();
var layer = SlicerFile[layerIndex]; // Unpack and expose layer variable for easier use
using var mat = layer.LayerMat; // Gets this layer mat/image
var original = mat.Clone(); // Keep a original mat copy
diff --git a/Scripts/UVtools.ScriptSample/ScriptPreventResinShrinkage.cs b/Scripts/UVtools.ScriptSample/ScriptPreventResinShrinkage.cs
index 333d5f31..c3988bae 100644
--- a/Scripts/UVtools.ScriptSample/ScriptPreventResinShrinkage.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptPreventResinShrinkage.cs
@@ -131,6 +131,7 @@ public bool ScriptExecute()
CoreSettings.GetParallelOptions(Progress),
layerIndex =>
{
+ Progress.PauseIfRequested();
var fullLayer = SlicerFile[layerIndex];
if (fullLayer.IsEmpty)
{
diff --git a/Scripts/UVtools.ScriptSample/ScriptTester.cs b/Scripts/UVtools.ScriptSample/ScriptTester.cs
index 0807cd6a..712b1a3d 100644
--- a/Scripts/UVtools.ScriptSample/ScriptTester.cs
+++ b/Scripts/UVtools.ScriptSample/ScriptTester.cs
@@ -54,6 +54,7 @@ public bool ScriptExecute()
var dict = new Dictionary>();
Parallel.For(Operation.LayerIndexStart, Operation.LayerIndexEnd + 1, CoreSettings.GetParallelOptions(Progress), layerIndex =>
{
+ Progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
using var contours = mat.FindContours(out var hierarchy, RetrType.Tree);
diff --git a/UVtools.Core/FileFormats/AnetFile.cs b/UVtools.Core/FileFormats/AnetFile.cs
index 80addf34..c741fbb3 100644
--- a/UVtools.Core/FileFormats/AnetFile.cs
+++ b/UVtools.Core/FileFormats/AnetFile.cs
@@ -469,6 +469,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = this[layerIndex];
using var mat = layer.LayerMat;
layerData[layerIndex] = new LayerDef();
@@ -479,7 +480,7 @@ protected override void EncodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
outputFile.WriteSerialize(layerData[layerIndex]);
outputFile.WriteBytes(layerData[layerIndex].EncodedRle);
@@ -521,7 +522,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = Helpers.Deserialize(inputFile);
layersDefinitions[layerIndex] = layerDef;
@@ -543,6 +544,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = layersDefinitions[layerIndex].Decode(out var resolutionX, out var resolutionY);
if (layerIndex == 0) // Set file resolution from first layer RLE. Figure out other properties after that
{
diff --git a/UVtools.Core/FileFormats/CTBEncryptedFile.cs b/UVtools.Core/FileFormats/CTBEncryptedFile.cs
index c70dc5d1..ba006a48 100644
--- a/UVtools.Core/FileFormats/CTBEncryptedFile.cs
+++ b/UVtools.Core/FileFormats/CTBEncryptedFile.cs
@@ -1157,7 +1157,7 @@ protected override void DecodeInternally(OperationProgress progress)
LayersPointer = new LayerPointer[Settings.LayerCount];
for (uint layerIndex = 0; layerIndex < Settings.LayerCount; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
LayersPointer[layerIndex] = Helpers.Deserialize(inputFile);
Debug.WriteLine($"pointer[{layerIndex}]: {LayersPointer[layerIndex]}");
progress++;
@@ -1172,7 +1172,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
inputFile.Seek(LayersPointer[layerIndex].PageNumber * ChituboxFile.PageSize + LayersPointer[layerIndex].LayerOffset, SeekOrigin.Begin);
LayersDefinition[layerIndex] = Helpers.Deserialize(inputFile);
@@ -1185,6 +1185,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layerDef = LayersDefinition[layerIndex];
@@ -1349,6 +1350,7 @@ protected override void EncodeInternally(OperationProgress progress)
progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
Parallel.For(0, LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layerDef = new LayerDef(this, this[layerIndex]);
using (var mat = this[layerIndex].LayerMat)
{
@@ -1362,7 +1364,7 @@ protected override void EncodeInternally(OperationProgress progress)
progress.Reset(OperationProgress.StatusWritingFile, LayerCount);
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = LayersDefinition[layerIndex];
LayersPointer[layerIndex] = new LayerPointer(outputFile.Position);
diff --git a/UVtools.Core/FileFormats/CXDLPFile.cs b/UVtools.Core/FileFormats/CXDLPFile.cs
index 5a14221e..eee0d1c4 100644
--- a/UVtools.Core/FileFormats/CXDLPFile.cs
+++ b/UVtools.Core/FileFormats/CXDLPFile.cs
@@ -33,9 +33,12 @@ namespace UVtools.Core.FileFormats;
public sealed class CXDLPFile : FileFormat
{
#region Constants
+
private const byte HEADER_SIZE = 9; // CXSW3DV2
private const string HEADER_VALUE = "CXSW3DV2";
private const string HEADER_VALUE_GENERIC = "CXSW3D";
+ private const byte DEFAULT_VERSION = 3;
+
#endregion
#region Sub Classes
@@ -62,7 +65,7 @@ public sealed class Header
[FieldOrder(2)]
[FieldEndianness(Endianness.Big)]
- public ushort Version { get; set; } = 2;
+ public ushort Version { get; set; } = DEFAULT_VERSION;
///
/// Gets the size of the printer model
@@ -458,7 +461,7 @@ public override string ToString()
public override uint[] AvailableVersions { get; } = { 2, 3 };
- public override uint DefaultVersion => 2;
+ public override uint DefaultVersion => DEFAULT_VERSION;
public override uint Version
{
@@ -675,6 +678,7 @@ protected override void EncodeInternally(OperationProgress progress)
// Previews
Parallel.For(0, previews.Length, CoreSettings.GetParallelOptions(progress), previewIndex =>
{
+ progress.PauseIfRequested();
var encodeLength = ThumbnailsOriginalSize[previewIndex].Area() * 2;
if (Thumbnails[previewIndex] is null)
{
@@ -720,6 +724,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = this[layerIndex];
using (var mat = layer.LayerMat)
{
@@ -841,6 +846,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.For(0, previews.Length, CoreSettings.GetParallelOptions(progress), previewIndex =>
{
+ progress.PauseIfRequested();
Thumbnails[previewIndex] = DecodeImage(DATATYPE_RGB565_BE, previews[previewIndex], ThumbnailsOriginalSize[previewIndex]);
previews[previewIndex] = null!;
});
@@ -868,7 +874,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
inputFile.Seek(4, SeekOrigin.Current);
var lineCount = BitExtensions.ToUIntBigEndian(inputFile.ReadBytes(4));
@@ -880,6 +886,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = EmguExtensions.InitMat(Resolution))
{
diff --git a/UVtools.Core/FileFormats/CXDLPv1File.cs b/UVtools.Core/FileFormats/CXDLPv1File.cs
index e7c11e46..bfdb781f 100644
--- a/UVtools.Core/FileFormats/CXDLPv1File.cs
+++ b/UVtools.Core/FileFormats/CXDLPv1File.cs
@@ -492,6 +492,7 @@ protected override void EncodeInternally(OperationProgress progress)
// Previews
Parallel.For(0, previews.Length, CoreSettings.GetParallelOptions(progress), previewIndex =>
{
+ progress.PauseIfRequested();
var encodeLength = ThumbnailsOriginalSize[previewIndex].Area() * 2;
if (Thumbnails[previewIndex] is null)
{
@@ -529,6 +530,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = this[layerIndex];
using (var mat = layer.LayerMat)
{
@@ -611,6 +613,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.For(0, previews.Length, CoreSettings.GetParallelOptions(progress), previewIndex =>
{
+ progress.PauseIfRequested();
Thumbnails[previewIndex] = DecodeImage(DATATYPE_RGB565_BE, previews[previewIndex], ThumbnailsOriginalSize[previewIndex]);
previews[previewIndex] = null!;
});
@@ -631,7 +634,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
inputFile.Seek(4, SeekOrigin.Current);
var lineCount = BitExtensions.ToUIntBigEndian(inputFile.ReadBytes(4));
@@ -644,6 +647,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = EmguExtensions.InitMat(Resolution))
{
for (int i = 0; i < linesBytes[layerIndex].Length; i++)
diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs
index 4b908d08..d6c1a6de 100644
--- a/UVtools.Core/FileFormats/ChituboxFile.cs
+++ b/UVtools.Core/FileFormats/ChituboxFile.cs
@@ -1906,6 +1906,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = this[layerIndex].LayerMat)
{
for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++)
@@ -1923,7 +1924,7 @@ protected override void EncodeInternally(OperationProgress progress)
if (layerIndex == 0) layerDefSize = Helpers.Serializer.SizeOf(LayerDefinitions[0, layerIndex]);
for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = LayerDefinitions[aaIndex, layerIndex];
LayerDef? layerDefHash = null;
@@ -2087,7 +2088,7 @@ protected override void DecodeInternally(OperationProgress progress)
Debug.WriteLine($"-Image GROUP {aaIndex}-");
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
inputFile.Seek(layerOffset, SeekOrigin.Begin);
var layerDef = Helpers.Deserialize(inputFile);
layerDef.Parent = this;
@@ -2125,7 +2126,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
inputFile.Seek(LayerDefinitions[aaIndex, layerIndex].PageNumber * PageSize + LayerDefinitions[aaIndex, layerIndex].DataAddress, SeekOrigin.Begin);
LayerDefinitions[aaIndex, layerIndex].EncodedRle = inputFile.ReadBytes(LayerDefinitions[aaIndex, layerIndex].DataSize);
@@ -2134,6 +2135,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = LayerDefinitions[0, layerIndex].Decode((uint)layerIndex);
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
diff --git a/UVtools.Core/FileFormats/FDGFile.cs b/UVtools.Core/FileFormats/FDGFile.cs
index 776d6f60..8ac32f31 100644
--- a/UVtools.Core/FileFormats/FDGFile.cs
+++ b/UVtools.Core/FileFormats/FDGFile.cs
@@ -959,6 +959,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = this[layerIndex].LayerMat)
{
LayersDefinitions[layerIndex] = new LayerDef(this, this[layerIndex]);
@@ -969,7 +970,7 @@ protected override void EncodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = LayersDefinitions[layerIndex];
LayerDef? layerDefHash = null;
@@ -1070,7 +1071,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = Helpers.Deserialize(inputFile);
layerDef.Parent = this;
@@ -1090,6 +1091,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
if (DecodeType == FileDecodeType.Full)
{
using var mat = LayersDefinitions[layerIndex].Decode((uint)layerIndex);
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index a31f0cfd..0ce4f747 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -386,6 +386,7 @@ public override string ToString()
//new CXDLPv1File(), // Creality Box v1
new CXDLPFile(), // Creality Box
new FDGFile(), // fdg
+ new GooFile(), // goo
new ZCodeFile(), // zcode
new JXSFile(), // jxs
new ZCodexFile(), // zcodex
@@ -1348,6 +1349,16 @@ public Layer[] Layers
///
public Layer? LastBottomLayer => this.LastOrDefault(layer => layer.IsBottomLayer);
+ ///
+ /// Gets the first transition layer
+ ///
+ public Layer? FirstTransitionLayer => TransitionLayerCount == 0 ? null : this[BottomLayerCount];
+
+ ///
+ /// Gets the last transition layer
+ ///
+ public Layer? LastTransitionLayer => TransitionLayerCount == 0 ? null : this[BottomLayerCount + TransitionLayerCount - 1];
+
///
/// Gets the first normal layer
///
@@ -3604,7 +3615,7 @@ public void Decode(string? fileFullPath, FileDecodeType fileDecodeType, Operatio
progress.Reset(OperationProgress.StatusGatherLayers, LayerCount);
DecodeInternally(progress);
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerHeightDigits = LayerHeight.DecimalDigits();
if (layerHeightDigits > Layer.HeightPrecision)
@@ -3624,7 +3635,14 @@ public void Decode(string? fileFullPath, FileDecodeType fileDecodeType, Operatio
if (CanUseTransitionLayerCount && TransitionLayerType == TransitionLayerTypes.Software)
{
- SuppressRebuildPropertiesWork(() => TransitionLayerCount = ParseTransitionLayerCountFromLayers());
+ SuppressRebuildPropertiesWork(() =>
+ {
+ var transitionLayers = ParseTransitionLayerCountFromLayers();
+ if (transitionLayers > 0)
+ {
+ TransitionLayerCount = transitionLayers;
+ }
+ });
}
bool reSaveFile = Sanitize();
@@ -3684,6 +3702,7 @@ public void EncodeLayersInZip(ZipArchive zipArchive, string prepend, byte padDig
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
if (matGenFunc is null)
{
switch (layerImageType)
@@ -3767,6 +3786,7 @@ public void DecodeLayersFromZip(ZipArchiveEntry[] layerEntries, OperationProgres
Parallel.For(0, LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
byte[] pngBytes;
lock (Mutex)
{
@@ -4007,6 +4027,7 @@ public virtual void Extract(string path, bool genericConfigExtract = true, bool
{
Parallel.ForEach(this, CoreSettings.GetParallelOptions(progress), layer =>
{
+ progress.PauseIfRequested();
var byteArr = layer.CompressedPngBytes;
if (byteArr is null) return;
using var stream = new FileStream(Path.Combine(path, layer.Filename), FileMode.Create, FileAccess.Write);
@@ -4024,13 +4045,13 @@ public virtual void Extract(string path, bool genericConfigExtract = true, bool
public ushort ParseTransitionLayerCountFromLayers()
{
ushort count = 0;
- for (uint layerIndex = BottomLayerCount + 1u; layerIndex < LayerCount; layerIndex++)
+ for (uint layerIndex = BottomLayerCount; layerIndex < LastLayerIndex; layerIndex++)
{
- if (Math.Abs(this[layerIndex - 1].ExposureTime - this[layerIndex].ExposureTime) < 0.009f) break; // First equal layer, transition ended
+ if (Math.Abs(this[layerIndex].ExposureTime - this[layerIndex + 1].ExposureTime) < 0.009f) break; // First equal layer, transition ended
count++;
}
- return count;
+ return count > 0 ? count : TransitionLayerCount;
}
///
@@ -4042,7 +4063,7 @@ public float ParseTransitionStepTimeFromLayers()
var transitionLayerCount = ParseTransitionLayerCountFromLayers();
return transitionLayerCount == 0
? 0
- : (float)Math.Round(this[BottomLayerCount].ExposureTime - this[BottomLayerCount + 1].ExposureTime, 2);
+ : (float)Math.Round(this[BottomLayerCount].ExposureTime - this[BottomLayerCount + 1].ExposureTime, 2, MidpointRounding.AwayFromZero);
}
///
@@ -4054,7 +4075,9 @@ public float ParseTransitionStepTimeFromLayers()
/// Seconds
public static float GetTransitionStepTime(float longExposureTime, float shortExposureTime, ushort transitionLayerCount)
{
- return transitionLayerCount == 0 ? 0 : (float)Math.Round((longExposureTime - shortExposureTime) / (transitionLayerCount + 1), 2, MidpointRounding.AwayFromZero);
+ return transitionLayerCount == 0
+ ? 0
+ : (float)Math.Round((longExposureTime - shortExposureTime) / (transitionLayerCount + 1), 2, MidpointRounding.AwayFromZero);
}
///
@@ -4067,6 +4090,21 @@ public float GetTransitionStepTime(ushort transitionLayerCount)
return GetTransitionStepTime(BottomExposureTime, ExposureTime, transitionLayerCount);
}
+ ///
+ /// Gets the transition step time from and first normal layer after the last transition layer, value is returned as positive from normal perspective and logic (Longer - shorter)
+ ///
+ /// Number of transition layers
+ /// Seconds
+ public float GetTransitionStepTimeFromLayers(ushort transitionLayerCount)
+ {
+ var bottomExposureTime = LastBottomLayer?.ExposureTime ?? BottomExposureTime;
+ var exposureTime = TransitionLayerCount > 0
+ ? this[BottomLayerCount + TransitionLayerCount - 1].ExposureTime
+ : this[BottomLayerCount].ExposureTime;
+
+ return GetTransitionStepTime(bottomExposureTime, exposureTime, transitionLayerCount);
+ }
+
///
/// Gets the transition step time from and , value is returned as positive from normal perspective and logic (Longer - shorter)
///
@@ -4100,6 +4138,25 @@ public ushort GetTransitionLayerCount(float stepDecrementTime, bool constrainToL
return count;
}
+ ///
+ /// Gets the transition layer count based on and first normal layer after the last transition layer
+ ///
+ /// Step decrement time in seconds
+ /// True if transition layer count can't be higher than supported by the file, otherwise set to false to not look at possible file layers
+ /// Midpoint rounding method
+ /// Transition layer count
+ public ushort GetTransitionLayerCountFromLayers(float stepDecrementTime, bool constrainToLayerCount = true, MidpointRounding rounding = MidpointRounding.AwayFromZero)
+ {
+ var bottomExposureTime = LastBottomLayer?.ExposureTime ?? BottomExposureTime;
+ var exposureTime = TransitionLayerCount > 0
+ ? this[BottomLayerCount + TransitionLayerCount - 1].ExposureTime
+ : this[BottomLayerCount].ExposureTime;
+
+ var count = GetTransitionLayerCount(bottomExposureTime, exposureTime, stepDecrementTime, rounding);
+ if (constrainToLayerCount) count = (ushort)Math.Min(count, MaximumPossibleTransitionLayerCount);
+ return count;
+ }
+
///
/// Re-set exposure time to the transition layers
@@ -4118,27 +4175,37 @@ public void ResetCurrentTransitionLayers(bool resetExposureTimes = true)
/// True to default all the previous transition layers exposure time, otherwise false
public void SetTransitionLayers(ushort transitionLayerCount, bool resetExposureTimes = true)
{
- if (resetExposureTimes)
+ var bottomExposureTime = LastBottomLayer?.ExposureTime ?? BottomExposureTime;
+ var exposureTime = ExposureTime;
+ var layersToReset = new List();
+ for (uint layerIndex = BottomLayerCount; layerIndex < LastLayerIndex; layerIndex++)
{
- for (uint layerIndex = BottomLayerCount; layerIndex < LayerCount; layerIndex++)
+ if (Math.Abs(this[layerIndex].ExposureTime - this[layerIndex + 1].ExposureTime) < 0.009)
{
- var layer = this[layerIndex];
- if (Math.Abs(layer.ExposureTime - ExposureTime) < 0.009) break; // First equal layer, transition ended
+ exposureTime = this[layerIndex].ExposureTime;
+ break; // First equal layer, transition ended
+ }
+ layersToReset.Add(this[layerIndex]);
+ }
- layer.ExposureTime = ExposureTime;
+ if (resetExposureTimes)
+ {
+ foreach (var layer in layersToReset)
+ {
+ layer.ExposureTime = exposureTime;
}
}
if (transitionLayerCount == 0) return;
- float decrement = GetTransitionStepTime(transitionLayerCount);
+ float decrement = GetTransitionStepTime(bottomExposureTime, exposureTime, transitionLayerCount);
if (decrement <= 0) return;
uint appliedLayers = 0;
for (uint layerIndex = BottomLayerCount; appliedLayers < transitionLayerCount && layerIndex < LayerCount; layerIndex++)
{
appliedLayers++;
- this[layerIndex].ExposureTime = Math.Clamp(BottomExposureTime - (decrement * appliedLayers), ExposureTime, BottomExposureTime);
+ this[layerIndex].ExposureTime = Math.Clamp(bottomExposureTime - (decrement * appliedLayers), exposureTime, bottomExposureTime);
}
}
@@ -5097,6 +5164,7 @@ public void ChangeLayersCompressionMethod(LayerCompressionCodec newCodec, Operat
Parallel.ForEach(this, CoreSettings.GetParallelOptions(progress), layer =>
{
+ progress.PauseIfRequested();
layer.CompressionCodec = newCodec;
progress.LockAndIncrement();
});
@@ -5355,6 +5423,7 @@ void FindFirstBoundingRectangle()
progress.Reset(OperationProgress.StatusOptimizingBounds, LayerCount - 1);
Parallel.For(0, LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
this[layerIndex].GetBoundingRectangle();
progress.LockAndIncrement();
});
@@ -5725,6 +5794,7 @@ public Layer[] AllocateFromMat(Mat[] mats, OperationProgress? progress = null)
var layers = new Layer[mats.Length];
Parallel.For(0, mats.Length, CoreSettings.GetParallelOptions(progress), i =>
{
+ progress.PauseIfRequested();
layers[i] = new Layer((uint)i, mats[i], this);
});
@@ -6100,6 +6170,7 @@ or PixelOperation.PixelOperationType.Text
Parallel.ForEach(group1, CoreSettings.GetParallelOptions(progress), layerOperationGroup =>
{
+ progress.PauseIfRequested();
var layer = this[layerOperationGroup.Key];
using var mat = layer.LayerMat;
@@ -6176,7 +6247,7 @@ is PixelOperation.PixelOperationType.Supports
var drawnDrainHoleLayers = 0;
for (int operationLayer = (int)layerOperationGroup.Key - 1; operationLayer >= 0 && toProcess.Count > 0; operationLayer--)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layer = this[operationLayer];
var mat = matCache.Get1((uint) operationLayer);
var isMatModified = false;
@@ -6426,6 +6497,7 @@ public Mat GenerateHeatmap(uint layerIndexStart = 0, uint layerIndexEnd = uint.M
Parallel.ForEach(layerRange, CoreSettings.GetParallelOptions(progress), layer =>
{
+ progress.PauseIfRequested();
using var mat = GetMergedMatForSequentialPositionedLayers(layer.Index);
using var mat32Roi = mat.Roi(roi);
diff --git a/UVtools.Core/FileFormats/FlashForgeSVGXFile.cs b/UVtools.Core/FileFormats/FlashForgeSVGXFile.cs
index a1f5770f..c8513857 100644
--- a/UVtools.Core/FileFormats/FlashForgeSVGXFile.cs
+++ b/UVtools.Core/FileFormats/FlashForgeSVGXFile.cs
@@ -490,6 +490,7 @@ protected override void EncodeInternally(OperationProgress progress)
Parallel.For(0, LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
groups[layerIndex] = new FlashForgeSVGXSvgGroup($"layer-{layerIndex}");
using var mat = this[layerIndex].LayerMat;
@@ -636,6 +637,7 @@ protected override void DecodeInternally(OperationProgress progress)
progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
Parallel.For(0, LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var mat = EmguExtensions.InitMat(Resolution);
var group = SVGDocument.Groups.FirstOrDefault(g => g.Id == $"layer-{layerIndex}");
@@ -646,7 +648,7 @@ protected override void DecodeInternally(OperationProgress progress)
var points = new List();
foreach (var path in @group.Paths)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var spaceSplit = path.Value.Split(' ',
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
diff --git a/UVtools.Core/FileFormats/GR1File.cs b/UVtools.Core/FileFormats/GR1File.cs
index 5cf49b49..7982c46a 100644
--- a/UVtools.Core/FileFormats/GR1File.cs
+++ b/UVtools.Core/FileFormats/GR1File.cs
@@ -338,6 +338,7 @@ protected override void EncodeInternally(OperationProgress progress)
// Previews
Parallel.For(0, previews.Length, CoreSettings.GetParallelOptions(progress), previewIndex =>
{
+ progress.PauseIfRequested();
var encodeLength = ThumbnailsOriginalSize[previewIndex].Area() * 2;
if (Thumbnails[previewIndex] is null)
{
@@ -369,6 +370,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = this[layerIndex];
using (var mat = layer.LayerMat)
{
@@ -450,6 +452,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.For(0, previews.Length, CoreSettings.GetParallelOptions(progress), previewIndex =>
{
+ progress.PauseIfRequested();
Thumbnails[previewIndex] = DecodeImage(DATATYPE_RGB565_BE, previews[previewIndex], ThumbnailsOriginalSize[previewIndex]);
previews[previewIndex] = null!;
});
@@ -468,7 +471,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var lineCount = BitExtensions.ToUIntBigEndian(inputFile.ReadBytes(4));
linesBytes[layerIndex] = new byte[lineCount * 6];
@@ -478,6 +481,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = EmguExtensions.InitMat(Resolution);
for (int i = 0; i < linesBytes[layerIndex].Length; i++)
diff --git a/UVtools.Core/FileFormats/GooFile.cs b/UVtools.Core/FileFormats/GooFile.cs
new file mode 100644
index 00000000..6c0ea740
--- /dev/null
+++ b/UVtools.Core/FileFormats/GooFile.cs
@@ -0,0 +1,1044 @@
+using BinarySerialization;
+using Emgu.CV;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using UVtools.Core.Exceptions;
+using UVtools.Core.Extensions;
+using UVtools.Core.Layers;
+using UVtools.Core.Operations;
+
+namespace UVtools.Core.FileFormats;
+
+public sealed class GooFile : FileFormat
+{
+
+ #region Constants
+
+ private const string FileVersion = "V3.0";
+
+ private static readonly byte[] FileMagic = {
+ 0x07, 0x00, 0x00, 0x00,
+ 0x44, 0x4C, 0x50, 0x00
+ };
+
+ private static byte[] Delimiter => new byte[]{ 0x0D, 0x0A };
+
+ private static byte LayerMagic => 0x55;
+
+ #endregion
+
+ #region Sub Classes
+
+ public class FileHeader
+ {
+ [FieldEndianness(Endianness.Big)] [FieldOrder(0)] [FieldLength(4)] public string Version { get; set; } = FileVersion;
+ [FieldOrder(1)] [FieldCount(8)] public byte[] Magic { get; set; } = FileMagic;
+ [FieldOrder(2)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string SoftwareName { get; set; } = About.Software;
+ [FieldOrder(3)] [FieldLength(24)] [SerializeAs(SerializedType.TerminatedString)] public string SoftwareVersion { get; set; } = About.VersionStr;
+ [FieldOrder(4)] [FieldLength(24)] [SerializeAs(SerializedType.TerminatedString)] public string FileCreateTime { get; set; } = DateTime.UtcNow.ToString("yyyy-mm-dd hh:mm:ss");
+ [FieldOrder(5)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string MachineName { get; set; } = DefaultMachineName;
+ [FieldOrder(6)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string MachineType { get; set; } = "DLP";
+ [FieldOrder(7)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string ProfileName { get; set; } = About.Software;
+ [FieldEndianness(Endianness.Big)] [FieldOrder(8)] public ushort AntiAliasingLevel { get; set; } = 8;
+ [FieldEndianness(Endianness.Big)] [FieldOrder(9)] public ushort GreyLevel { get; set; } = 1;
+ [FieldEndianness(Endianness.Big)] [FieldOrder(10)] public ushort BlurLevel { get; set; } = 0;
+ [FieldOrder(11)] [FieldCount(116 * 116 * 2)] public byte[] SmallPreview565 { get; set; } = Array.Empty();
+ [FieldOrder(12)] [FieldCount(2)] public byte[] SmallPreviewDelimiter { get; set; } = Delimiter;
+ [FieldOrder(13)] [FieldCount(290 * 290 * 2)] public byte[] BigPreview565 { get; set; } = Array.Empty();
+ [FieldOrder(14)] [FieldCount(2)] public byte[] BigPreviewDelimiter { get; set; } = Delimiter;
+ [FieldEndianness(Endianness.Big)] [FieldOrder(15)] public uint LayerCount { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(16)] public ushort ResolutionX { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(17)] public ushort ResolutionY { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(18)] public bool MirrorX { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(19)] public bool MirrorY { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(20)] public float DisplayWidth { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(21)] public float DisplayHeight { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(22)] public float MachineZ { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(23)] public float LayerHeight { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(24)] public float ExposureTime { get; set; }
+ ///
+ /// 0: Light off delay mode | 1:Wait time mode
+ ///
+ [FieldEndianness(Endianness.Big)] [FieldOrder(25)] public byte DelayMode { get; set; } = 1;
+ [FieldEndianness(Endianness.Big)] [FieldOrder(26)] public float LightOffDelay { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(27)] public float BottomWaitTimeAfterCure { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(28)] public float BottomWaitTimeAfterLift { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(29)] public float BottomWaitTimeBeforeCure { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(30)] public float WaitTimeAfterCure { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(31)] public float WaitTimeAfterLift { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(32)] public float WaitTimeBeforeCure { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(33)] public float BottomExposureTime { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(34)] public uint BottomLayerCount { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(35)] public float BottomLiftHeight { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(36)] public float BottomLiftSpeed { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(37)] public float LiftHeight { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(38)] public float LiftSpeed { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(39)] public float BottomRetractHeight { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(40)] public float BottomRetractSpeed { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(41)] public float RetractHeight { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(42)] public float RetractSpeed { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(43)] public float BottomLiftHeight2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(44)] public float BottomLiftSpeed2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(45)] public float LiftHeight2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(46)] public float LiftSpeed2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(47)] public float BottomRetractHeight2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(48)] public float BottomRetractSpeed2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(49)] public float RetractHeight2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(50)] public float RetractSpeed2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(51)] public ushort BottomLightPWM { get; set; } = DefaultBottomLightPWM;
+ [FieldEndianness(Endianness.Big)] [FieldOrder(52)] public ushort LightPWM { get; set; } = DefaultLightPWM;
+ ///
+ /// 0: Normal mode
+ /// 1: Advance mode, printing use the value of "Layer Definition Content"
+ ///
+ [FieldEndianness(Endianness.Big)] [FieldOrder(53)] public bool PerLayerSettings { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(54)] public uint PrintTime { get; set; }
+ ///
+ /// // The volume of all parts. unit: mm3
+ ///
+ [FieldEndianness(Endianness.Big)] [FieldOrder(55)] public float Volume { get; set; }
+ ///
+ /// The weight of all parts. unit: g
+ ///
+ [FieldEndianness(Endianness.Big)] [FieldOrder(56)] public float MaterialGrams { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(57)] public float MaterialCost { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(58)] [FieldLength(8)] [SerializeAs(SerializedType.TerminatedString)] public string PriceCurrencySymbol { get; set; } = "$";
+ [FieldEndianness(Endianness.Big)] [FieldOrder(59)] public uint LayerDefAddress { get; set; } // 195477
+ ///
+ /// 0:The range of pixel's gray value is from 0x0 ~ 0xf
+ /// 1:The range of pixel's gray value is from 0x0 ~ 0xff
+ ///
+ [FieldEndianness(Endianness.Big)] [FieldOrder(60)] public byte GrayScaleLevel { get; set; } = 1;
+ [FieldEndianness(Endianness.Big)] [FieldOrder(61)] public ushort TransitionLayerCount { get; set; }
+
+ public override string ToString()
+ {
+ return $"{nameof(Version)}: {Version}, {nameof(Magic)}: {Magic}, {nameof(SoftwareName)}: {SoftwareName}, {nameof(SoftwareVersion)}: {SoftwareVersion}, {nameof(FileCreateTime)}: {FileCreateTime}, {nameof(MachineName)}: {MachineName}, {nameof(MachineType)}: {MachineType}, {nameof(ProfileName)}: {ProfileName}, {nameof(AntiAliasingLevel)}: {AntiAliasingLevel}, {nameof(GreyLevel)}: {GreyLevel}, {nameof(BlurLevel)}: {BlurLevel}, {nameof(SmallPreview565)}: {SmallPreview565}, {nameof(SmallPreviewDelimiter)}: {SmallPreviewDelimiter}, {nameof(BigPreview565)}: {BigPreview565}, {nameof(BigPreviewDelimiter)}: {BigPreviewDelimiter}, {nameof(LayerCount)}: {LayerCount}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(MirrorX)}: {MirrorX}, {nameof(MirrorY)}: {MirrorY}, {nameof(DisplayWidth)}: {DisplayWidth}, {nameof(DisplayHeight)}: {DisplayHeight}, {nameof(MachineZ)}: {MachineZ}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(DelayMode)}: {DelayMode}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(BottomWaitTimeAfterCure)}: {BottomWaitTimeAfterCure}, {nameof(BottomWaitTimeAfterLift)}: {BottomWaitTimeAfterLift}, {nameof(BottomWaitTimeBeforeCure)}: {BottomWaitTimeBeforeCure}, {nameof(WaitTimeAfterCure)}: {WaitTimeAfterCure}, {nameof(WaitTimeAfterLift)}: {WaitTimeAfterLift}, {nameof(WaitTimeBeforeCure)}: {WaitTimeBeforeCure}, {nameof(BottomExposureTime)}: {BottomExposureTime}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(BottomRetractHeight)}: {BottomRetractHeight}, {nameof(BottomRetractSpeed)}: {BottomRetractSpeed}, {nameof(RetractHeight)}: {RetractHeight}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(BottomLiftHeight2)}: {BottomLiftHeight2}, {nameof(BottomLiftSpeed2)}: {BottomLiftSpeed2}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(BottomRetractHeight2)}: {BottomRetractHeight2}, {nameof(BottomRetractSpeed2)}: {BottomRetractSpeed2}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(LightPWM)}: {LightPWM}, {nameof(PerLayerSettings)}: {PerLayerSettings}, {nameof(PrintTime)}: {PrintTime}, {nameof(Volume)}: {Volume}, {nameof(MaterialGrams)}: {MaterialGrams}, {nameof(MaterialCost)}: {MaterialCost}, {nameof(PriceCurrencySymbol)}: {PriceCurrencySymbol}, {nameof(LayerDefAddress)}: {LayerDefAddress}, {nameof(GrayScaleLevel)}: {GrayScaleLevel}, {nameof(TransitionLayerCount)}: {TransitionLayerCount}";
+ }
+ }
+
+ public class LayerDef
+ {
+ ///
+ /// 0: reserve
+ /// 1: current layer pause printing
+ ///
+ [FieldEndianness(Endianness.Big)] [FieldOrder(0)] public ushort Pause { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(1)] public float PausePositionZ { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(2)] public float PositionZ { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(3)] public float ExposureTime { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(4)] public float LightOffDelay { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(5)] public float WaitTimeAfterCure { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(6)] public float WaitTimeAfterLift { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(7)] public float WaitTimeBeforeCure { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(8)] public float LiftHeight { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(9)] public float LiftSpeed { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(10)] public float LiftHeight2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(11)] public float LiftSpeed2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(12)] public float RetractHeight { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(13)] public float RetractSpeed { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(14)] public float RetractHeight2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(15)] public float RetractSpeed2 { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(16)] public ushort LightPWM { get; set; }
+ [FieldEndianness(Endianness.Big)] [FieldOrder(17)] [FieldCount(2)] public byte[] DelimiterData { get; set; } = Delimiter;
+ [FieldEndianness(Endianness.Big)] [FieldOrder(18)] public uint DataLength { get; set; }
+
+
+ [Ignore] public GooFile? Parent { get; set; }
+
+ [Ignore] public byte[] EncodedRle { get; set; } = Array.Empty();
+
+ // DelimiterRLE
+
+ public LayerDef() { }
+
+ public LayerDef(GooFile parent, Layer layer)
+ {
+ Parent = parent;
+ PausePositionZ = parent.MachineZ;
+ SetFrom(layer);
+ }
+
+ public void SetFrom(Layer layer)
+ {
+ PositionZ = layer.PositionZ;
+ ExposureTime = layer.ExposureTime;
+ LightOffDelay = layer.LightOffDelay;
+ LiftHeight = layer.LiftHeight;
+ LiftSpeed = layer.LiftSpeed;
+ LiftHeight2 = layer.LiftHeight2;
+ LiftSpeed2 = layer.LiftSpeed2;
+ RetractSpeed = layer.RetractSpeed;
+ RetractHeight2 = layer.RetractHeight2;
+ RetractSpeed2 = layer.RetractSpeed2;
+ WaitTimeAfterCure = layer.WaitTimeAfterCure;
+ WaitTimeAfterLift = layer.WaitTimeAfterLift;
+ WaitTimeBeforeCure = layer.WaitTimeBeforeCure;
+ LightPWM = layer.LightPWM;
+ }
+
+ public void CopyTo(Layer layer)
+ {
+ layer.PositionZ = PositionZ;
+ layer.ExposureTime = ExposureTime;
+ layer.LightOffDelay = LightOffDelay;
+ layer.LiftHeight = LiftHeight;
+ layer.LiftSpeed = LiftSpeed;
+ layer.LiftHeight2 = LiftHeight2;
+ layer.LiftSpeed2 = LiftSpeed2;
+ layer.RetractSpeed = RetractSpeed;
+ layer.RetractHeight2 = RetractHeight2;
+ layer.RetractSpeed2 = RetractSpeed2;
+ layer.WaitTimeAfterCure = WaitTimeAfterCure;
+ layer.WaitTimeAfterLift = WaitTimeAfterLift;
+ layer.WaitTimeBeforeCure = WaitTimeBeforeCure;
+ layer.LightPWM = (byte)LightPWM;
+ }
+
+
+ public Mat DecodeImage(uint layerIndex, bool consumeRle = true)
+ {
+ var mat = EmguExtensions.InitMat(Parent!.Resolution);
+
+ if (DataLength <= 3) return mat;
+
+ if (EncodedRle![0] != LayerMagic) throw new MessageException($"RLE for layer {layerIndex} is corrupted, should start with {LayerMagic} but got {EncodedRle[0]}");
+
+ int pixel = 0;
+ var lastByteIndex = DataLength - 1;
+ byte color = 0;
+ byte checkSum = 0;
+
+ for (var i = 1; i < lastByteIndex; i++)
+ {
+ // Calculate checksum
+ unchecked
+ {
+ checkSum += EncodedRle[i];
+ }
+ }
+ checkSum = (byte)~checkSum;
+ if (EncodedRle[^1] != checkSum) throw new MessageException($"Decoded RLE for layer {layerIndex} is corrupted, expected checksum <{EncodedRle[^1]}>, got <{checkSum}>");
+
+ for (var i = 1; i < lastByteIndex; i++)
+ {
+ /* Byte0[7:6]: The type of chunk
+ * (0x0) 0 0 This chunk contain all 0x0 pixels
+ * (0x1) 0 1 This chunk contain the value of gray between 0x1 to 0xfe. The gray value is after byte0.
+ * (0x2) 1 0 This chunk contain the diff value from the previous pixel
+ * (0x3) 1 1 This chunk contain all 0xff pixels
+ */
+ byte chunkType = (byte)(EncodedRle[i] >> 6);
+ int stride = 0;
+
+ int strideIndex0 = i;
+ int strideIndex1 = i + 1;
+ int strideIndex2 = i + 2;
+ int strideIndex3 = i + 3;
+
+ if (chunkType == 0x0) // 0 0
+ {
+ color = byte.MinValue;
+ }
+ else if (chunkType == 0x1) // 0 1
+ {
+ color = EncodedRle[++i];
+ strideIndex1++;
+ strideIndex2++;
+ strideIndex3++;
+ }
+ else if (chunkType == 0x2) // 1 0
+ {
+ /* When byte0[7:6] is [1:0], the meaning of byte0[5:4] follow below definition:
+ * 0 0 byte0[3:0] is the positive diff value. that's mean
+ current value subtract previous value is bigger
+ than 0. The range is from 0 to 15. 0x0 map to 0.
+ 0xf map to 15.
+ * 0 1 byte0[3:0] is the positive diff value. And this
+ value's run-length represent by byte1[7:0]
+ * 1 0 byte0[3:0] is the negative diff value.that's mean
+ current value subtract previous value is smaller
+ than 0. The range is from 0 to 15. 0x0 map to 0.
+ 0xf map to 15.
+ * 1 1 byte0[3:0] is the negative diff value. And this
+ value's run-length represent by byte1[7:0]
+ */
+ byte diffType = (byte)(EncodedRle[i] >> 4 & 0x3);
+ byte diffValue = (byte)(EncodedRle[i] & 0xf);
+ if (diffType == 0x0)
+ {
+ color += diffValue;
+ stride = 1;
+ }
+ else if (diffType == 0x1)
+ {
+ color += diffValue;
+ stride = EncodedRle[++i];
+ }
+ else if (diffType == 0x2)
+ {
+ color -= diffValue;
+ stride = 1;
+ }
+ else if (diffType == 0x3)
+ {
+ color -= diffValue;
+ stride = EncodedRle[++i];
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(diffType), $"Diff type {diffType:X} is out of range, can only go up to 0x3.");
+ }
+ }
+ else if (chunkType == 0x3) // 1 1
+ {
+ color = byte.MaxValue;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(chunkType), $"Chunk type {chunkType:X} is out of range, can only go up to 0x3.");
+ }
+
+ if (chunkType != 0x2)
+ {
+ /* Byte0[5:4]: The length of chunk except when byte0[7:6] is [1 0]
+ * (0x0) 0 0 4-bit run-length use byte0[3:0]
+ * (0x1) 0 1 The run-length consist by byte1[7:0] and byte0[3:0]
+ * (0x2) 1 0 The run-length consist by byte1[7:0], byte2[7:0] and byte0[3:0]
+ * (0x3) 1 1 The run-length consist by byte1[7:0], byte2[7:0], byte3[7:0] and byte0[3:0]
+ */
+ byte chunkLength = (byte) (EncodedRle[strideIndex0] >> 4 & 0x3);
+ switch (chunkLength)
+ {
+ case 0x0:
+ stride = EncodedRle[strideIndex0] & 0xF;
+ break;
+ case 0x1:
+ stride = (EncodedRle[strideIndex1] << 4) + (EncodedRle[strideIndex0] & 0xF);
+ i += 1;
+ break;
+ case 0x2:
+ stride = (EncodedRle[strideIndex1] << 12) + (EncodedRle[strideIndex2] << 4) + (EncodedRle[strideIndex0] & 0xF);
+ i += 2;
+ break;
+ case 0x3:
+ stride = (EncodedRle[strideIndex1] << 20) + (EncodedRle[strideIndex2] << 12) + (EncodedRle[strideIndex3] << 4) + (EncodedRle[strideIndex0] & 0xF);
+ i += 3;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(chunkLength), $"Chunk length {chunkLength:X} is out of range, can only go up to 0x3.");
+ }
+ }
+
+ mat.FillSpan(ref pixel, stride, color);
+ }
+
+ if (consumeRle) EncodedRle = null;
+
+ return mat;
+ }
+
+ public byte[] EncodeImage(Mat image, uint layerIndex)
+ {
+ List rle = new(){ LayerMagic };
+ byte previousColor = 0;
+ byte currentColor = 0;
+ uint stride = 0;
+ byte checkSum = 0;
+ var span = image.GetDataByteSpan();
+
+ void AddRep()
+ {
+ if (stride == 0)
+ {
+ return;
+ }
+
+ int firstByteIndex = rle.Count;
+ rle.Add(0);
+
+ // Difference mode
+ var differenceColor = (byte) Math.Abs(currentColor - previousColor);
+ if (stride <= byte.MaxValue && differenceColor <= 0xF)
+ {
+ rle[firstByteIndex] = (byte) (0x2 << 6 | (differenceColor & 0xF));
+ if (stride > 1)
+ {
+ rle[firstByteIndex] |= 0x1 << 4;
+ }
+
+ if (previousColor > currentColor)
+ {
+ rle[firstByteIndex] |= 0x1 << 5;
+ }
+
+ if (stride > 1)
+ {
+ rle.Add((byte) stride);
+ }
+ }
+ else
+ {
+ /*if (currentColor == byte.MinValue)
+ {
+ 0 0 This chunk contain all 0x0 pixels
+ firstByte |= 0x00 << 6;
+ }*/
+ if (currentColor == byte.MaxValue)
+ {
+ // 1 1 This chunk contain all 0xff pixels
+ rle[firstByteIndex] |= 0x3 << 6;
+ }
+ else if (currentColor > byte.MinValue)
+ {
+ // 0 1 This chunk contain the value of gray between 0x1 to 0xfe. The gray value is after byte0.
+ rle[firstByteIndex] |= 0x1 << 6;
+ rle.Add(currentColor);
+ }
+
+ rle[firstByteIndex] |= (byte) (stride & 0xF);
+ if (stride <= 0xF)
+ {
+ return;
+ }
+
+ if (stride <= 0xFFF)
+ {
+ rle[firstByteIndex] |= 0x1 << 4;
+ rle.Add((byte) (stride >> 4));
+ return;
+ }
+
+ if (stride <= 0xFFFFF)
+ {
+ rle[firstByteIndex] |= 0x2 << 4;
+ rle.Add((byte) (stride >> 12));
+ rle.Add((byte) (stride >> 4));
+ return;
+ }
+
+ if (stride <= 0xFFFFFFF)
+ {
+ rle[firstByteIndex] |= 0x3 << 4;
+ rle.Add((byte) (stride >> 20));
+ rle.Add((byte) (stride >> 12));
+ rle.Add((byte) (stride >> 4));
+ return;
+ }
+ }
+ }
+
+ for (int i = 0; i < span.Length; i++)
+ {
+ if (currentColor == span[i])
+ {
+ stride++;
+ }
+ else
+ {
+ AddRep();
+ stride = 1;
+ previousColor = currentColor;
+ currentColor = span[i];
+ }
+ }
+
+ AddRep();
+
+ // Calculate checksum
+ for (int i = 1; i < rle.Count; i++)
+ {
+ unchecked
+ {
+ checkSum += rle[i];
+ }
+ }
+
+ rle.Add((byte) ~checkSum);
+
+ EncodedRle = rle.ToArray();
+ DataLength = (uint)EncodedRle.Length;
+
+ return EncodedRle;
+ }
+
+ public override string ToString()
+ {
+ return $"{nameof(Pause)}: {Pause}, {nameof(PausePositionZ)}: {PausePositionZ}, {nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(WaitTimeAfterCure)}: {WaitTimeAfterCure}, {nameof(WaitTimeAfterLift)}: {WaitTimeAfterLift}, {nameof(WaitTimeBeforeCure)}: {WaitTimeBeforeCure}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(RetractHeight)}: {RetractHeight}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}, {nameof(LightPWM)}: {LightPWM}, {nameof(DelimiterData)}: {DelimiterData}, {nameof(DataLength)}: {DataLength}, {nameof(Parent)}: {Parent}, {nameof(EncodedRle)}: {EncodedRle}";
+ }
+ }
+
+ public class FileFooter
+ {
+ [FieldOrder(0)] public byte Padding1 { get; set; }
+ [FieldOrder(1)] public byte Padding2 { get; set; }
+ [FieldOrder(2)] public byte Padding3 { get; set; }
+ [FieldOrder(3)] [FieldCount(8)] public byte[] Magic { get; set; } = FileMagic;
+
+ public override string ToString()
+ {
+ return $"{nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(Padding3)}: {Padding3}, {nameof(Magic)}: {Magic}";
+ }
+ }
+
+ #endregion
+
+ #region Properties
+ public override FileFormatType FileType => FileFormatType.Binary;
+
+ public override FileExtension[] FileExtensions { get; } = {
+ new(typeof(GooFile), "goo", "Elegoo GOO"),
+ };
+
+ public override Size[]? ThumbnailsOriginalSize { get; } =
+ {
+ new(116, 116),
+ new(290, 290)
+ };
+
+ public FileHeader Header { get; private set; } = new();
+
+ public LayerDef[]? LayersDefinition { get; private set; }
+
+ public FileFooter Footer { get; private set; } = new();
+
+ public override PrintParameterModifier[]? PrintParameterModifiers { get; } = {
+ PrintParameterModifier.BottomLayerCount,
+ PrintParameterModifier.TransitionLayerCount,
+
+ //PrintParameterModifier.BottomLightOffDelay,
+ PrintParameterModifier.LightOffDelay,
+
+ PrintParameterModifier.BottomWaitTimeBeforeCure,
+ PrintParameterModifier.WaitTimeBeforeCure,
+
+ PrintParameterModifier.BottomExposureTime,
+ PrintParameterModifier.ExposureTime,
+
+ PrintParameterModifier.BottomWaitTimeAfterCure,
+ PrintParameterModifier.WaitTimeAfterCure,
+
+ PrintParameterModifier.BottomLiftHeight,
+ PrintParameterModifier.BottomLiftSpeed,
+ PrintParameterModifier.LiftHeight,
+ PrintParameterModifier.LiftSpeed,
+ PrintParameterModifier.BottomLiftHeight2,
+ PrintParameterModifier.BottomLiftSpeed2,
+ PrintParameterModifier.LiftHeight2,
+ PrintParameterModifier.LiftSpeed2,
+
+ PrintParameterModifier.BottomWaitTimeAfterLift,
+ PrintParameterModifier.WaitTimeAfterLift,
+
+ PrintParameterModifier.BottomRetractSpeed,
+ PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.BottomRetractHeight2,
+ PrintParameterModifier.BottomRetractSpeed2,
+ PrintParameterModifier.RetractHeight2,
+ PrintParameterModifier.RetractSpeed2,
+
+ PrintParameterModifier.BottomLightPWM,
+ PrintParameterModifier.LightPWM
+ };
+
+ public override PrintParameterModifier[]? PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
+ PrintParameterModifier.LightOffDelay,
+ PrintParameterModifier.WaitTimeBeforeCure,
+ PrintParameterModifier.ExposureTime,
+ PrintParameterModifier.WaitTimeAfterCure,
+ PrintParameterModifier.LiftHeight,
+ PrintParameterModifier.LiftSpeed,
+ PrintParameterModifier.LiftHeight2,
+ PrintParameterModifier.LiftSpeed2,
+ PrintParameterModifier.WaitTimeAfterLift,
+ PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.RetractHeight2,
+ PrintParameterModifier.RetractSpeed2,
+ PrintParameterModifier.LightPWM
+ };
+
+ public override uint ResolutionX
+ {
+ get => Header.ResolutionX;
+ set => base.ResolutionX = Header.ResolutionX = (ushort)value;
+ }
+
+ public override uint ResolutionY
+ {
+ get => Header.ResolutionY;
+ set => base.ResolutionY = Header.ResolutionY = (ushort)value;
+ }
+
+ public override float LayerHeight
+ {
+ get => Header.LayerHeight;
+ set
+ {
+ Header.LayerHeight = value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float DisplayWidth
+ {
+ get => Header.DisplayWidth;
+ set => base.DisplayWidth = Header.DisplayWidth = RoundDisplaySize(value);
+ }
+
+ public override float DisplayHeight
+ {
+ get => Header.DisplayHeight;
+ set => base.DisplayHeight = Header.DisplayHeight = RoundDisplaySize(value);
+ }
+
+ public override float MachineZ
+ {
+ get => Header.MachineZ > 0 ? Header.MachineZ : base.MachineZ;
+ set => base.MachineZ = Header.MachineZ = (float)Math.Round(value, 2);
+ }
+
+ public override FlipDirection DisplayMirror
+ {
+ get
+ {
+ if (Header is {MirrorX: true, MirrorY: true}) return FlipDirection.Both;
+ if (Header.MirrorX) return FlipDirection.Horizontally;
+ if (Header.MirrorY) return FlipDirection.Vertically;
+ return FlipDirection.None;
+ }
+ set
+ {
+ switch (value)
+ {
+ case FlipDirection.None:
+ Header.MirrorX = false;
+ Header.MirrorY = false;
+ break;
+ case FlipDirection.Horizontally:
+ Header.MirrorX = true;
+ Header.MirrorY = false;
+ break;
+ case FlipDirection.Vertically:
+ Header.MirrorX = false;
+ Header.MirrorY = true;
+ break;
+ case FlipDirection.Both:
+ Header.MirrorX = true;
+ Header.MirrorY = true;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(value), value, null);
+ }
+ RaisePropertyChanged();
+ }
+ }
+
+ public override byte AntiAliasing
+ {
+ get => (byte)(Header.AntiAliasingLevel);
+ set => base.AntiAliasing = (byte)(Header.AntiAliasingLevel = value);
+ }
+
+ public override uint LayerCount
+ {
+ get => base.LayerCount;
+ set => base.LayerCount = Header.LayerCount = base.LayerCount;
+ }
+
+ public override ushort BottomLayerCount
+ {
+ get => (ushort) Header.BottomLayerCount;
+ set => base.BottomLayerCount = (ushort) (Header.BottomLayerCount = value);
+ }
+
+ public override TransitionLayerTypes TransitionLayerType => TransitionLayerTypes.Software;
+
+ public override ushort TransitionLayerCount
+ {
+ get => Header.TransitionLayerCount;
+ set => base.TransitionLayerCount = Header.TransitionLayerCount = (ushort) Math.Min(value, MaximumPossibleTransitionLayerCount);
+ }
+
+ public override float BottomLightOffDelay => Header.LightOffDelay;
+
+ public override float LightOffDelay
+ {
+ get => Header.LightOffDelay;
+ set => base.LightOffDelay = Header.LightOffDelay = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomWaitTimeBeforeCure
+ {
+ get => base.BottomWaitTimeBeforeCure > 0 ? base.BottomWaitTimeBeforeCure : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeBeforeCure ?? 0;
+ set => base.BottomWaitTimeBeforeCure = value;
+ }
+
+
+ public override float WaitTimeBeforeCure
+ {
+ get => Header.WaitTimeBeforeCure;
+ set
+ {
+ base.WaitTimeBeforeCure = Header.WaitTimeBeforeCure = Header.WaitTimeBeforeCure = (float)Math.Round(value, 2);
+ if (value > 0)
+ {
+ BottomLightOffDelay = 0;
+ LightOffDelay = 0;
+ }
+ }
+ }
+
+ public override float BottomExposureTime
+ {
+ get => Header.BottomExposureTime;
+ set => base.BottomExposureTime = Header.BottomExposureTime = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomWaitTimeAfterCure
+ {
+ get => base.BottomWaitTimeAfterCure > 0 ? base.BottomWaitTimeAfterCure : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeAfterCure ?? 0;
+ set => base.BottomWaitTimeAfterCure = value;
+ }
+
+ public override float WaitTimeAfterCure
+ {
+ get => Header.WaitTimeAfterCure;
+ set
+ {
+ base.WaitTimeAfterCure = Header.WaitTimeAfterCure = (float)Math.Round(value, 2);
+ if (value > 0)
+ {
+ BottomLightOffDelay = 0;
+ LightOffDelay = 0;
+ }
+ }
+ }
+
+ public override float ExposureTime
+ {
+ get => Header.ExposureTime;
+ set => base.ExposureTime = Header.ExposureTime = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomLiftHeight
+ {
+ get => Header.BottomLiftHeight;
+ set => base.BottomLiftHeight = Header.BottomLiftHeight = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomLiftSpeed
+ {
+ get => Header.BottomLiftSpeed;
+ set => base.BottomLiftSpeed = Header.BottomLiftSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float LiftHeight
+ {
+ get => Header.LiftHeight;
+ set => base.LiftHeight = Header.LiftHeight = (float)Math.Round(value, 2);
+ }
+
+ public override float LiftSpeed
+ {
+ get => Header.LiftSpeed;
+ set => base.LiftSpeed = Header.LiftSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomLiftHeight2
+ {
+ get => Header.BottomLiftHeight2;
+ set => base.BottomLiftHeight2 = Header.BottomLiftHeight2 = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomLiftSpeed2
+ {
+ get => Header.BottomLiftSpeed2;
+ set => base.BottomLiftSpeed2 = Header.BottomLiftSpeed2 = (float)Math.Round(value, 2);
+ }
+
+ public override float LiftHeight2
+ {
+ get => Header.LiftHeight2;
+ set => base.LiftHeight2 = Header.LiftHeight2 = (float)Math.Round(value, 2);
+ }
+
+ public override float LiftSpeed2
+ {
+ get => Header.LiftSpeed2;
+ set => base.LiftSpeed2 = Header.LiftSpeed2 = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomWaitTimeAfterLift
+ {
+ get => base.BottomWaitTimeAfterLift > 0 ? base.BottomWaitTimeAfterLift : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeAfterLift ?? 0;
+ set => base.BottomWaitTimeAfterLift = value;
+ }
+
+ public override float WaitTimeAfterLift
+ {
+ get => Header.WaitTimeAfterLift;
+ set
+ {
+ base.WaitTimeAfterLift = Header.WaitTimeAfterLift = Header.WaitTimeAfterLift = (float)Math.Round(value, 2);
+ if (value > 0)
+ {
+ BottomLightOffDelay = 0;
+ LightOffDelay = 0;
+ }
+ }
+ }
+
+ public override float BottomRetractSpeed
+ {
+ get => Header.BottomRetractSpeed;
+ set => base.BottomRetractSpeed = Header.BottomRetractSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float RetractSpeed
+ {
+ get => Header.RetractSpeed;
+ set => base.RetractSpeed = Header.RetractSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomRetractHeight2
+ {
+ get => Header.BottomRetractHeight2;
+ set
+ {
+ value = Math.Clamp((float)Math.Round(value, 2), 0, BottomRetractHeightTotal);
+ base.BottomRetractHeight2 = Header.BottomRetractHeight2 = value;
+ }
+ }
+
+ public override float BottomRetractSpeed2
+ {
+ get => Header.BottomRetractSpeed2;
+ set => base.BottomRetractSpeed2 = Header.BottomRetractSpeed2 = (float)Math.Round(value, 2);
+ }
+
+ public override float RetractHeight2
+ {
+ get => Header.RetractHeight2;
+ set
+ {
+ value = Math.Clamp((float)Math.Round(value, 2), 0, RetractHeightTotal);
+ base.RetractHeight2 = Header.RetractHeight2 = value;
+ }
+ }
+
+ public override float RetractSpeed2
+ {
+ get => Header.RetractSpeed2;
+ set => base.RetractSpeed2 = Header.RetractSpeed2 = (float)Math.Round(value, 2);
+ }
+
+ public override byte BottomLightPWM
+ {
+ get => (byte)Header.BottomLightPWM;
+ set => base.BottomLightPWM = (byte)(Header.BottomLightPWM = value);
+ }
+
+ public override byte LightPWM
+ {
+ get => (byte)Header.LightPWM;
+ set => base.LightPWM = (byte)(Header.LightPWM = value);
+ }
+
+ public override float PrintTime
+ {
+ get => base.PrintTime;
+ set
+ {
+ base.PrintTime = value;
+ Header.PrintTime = (uint)base.PrintTime;
+ }
+ }
+
+ public override string MachineName
+ {
+ get => Header.MachineName;
+ set => base.MachineName = Header.MachineName = value;
+ }
+
+ public override float MaterialGrams
+ {
+ get => Header.MaterialGrams;
+ set => base.MaterialGrams = Header.MaterialGrams = (float)Math.Round(value, 3);
+ }
+
+ public override float MaterialCost
+ {
+ get => (float)Math.Round(Header.MaterialCost, 3);
+ set => base.MaterialCost = Header.MaterialCost = (float)Math.Round(value, 3);
+ }
+
+ public override object[] Configs => new object[] { Header, Footer };
+
+ #endregion
+
+ #region Constructors
+ public GooFile()
+ {
+ }
+ #endregion
+
+ #region Methods
+
+ protected override void DecodeInternally(OperationProgress progress)
+ {
+ using var inputFile = new FileStream(FileFullPath!, FileMode.Open, FileAccess.Read);
+ Header = Helpers.Deserialize(inputFile);
+ Debug.WriteLine($"Header: {Header}");
+
+ var expectedMagic = FileMagic;
+
+ if (!Header.Magic.SequenceEqual(expectedMagic))
+ {
+ throw new FileLoadException("Not a valid GOO file! Magic value mismatch", FileFullPath);
+ }
+
+ progress.Reset(OperationProgress.StatusDecodePreviews, ThumbnailsCount);
+
+ Thumbnails[0] = DecodeImage(DATATYPE_RGB565_BE, Header.SmallPreview565, ThumbnailsOriginalSize![0]);
+ progress++;
+ Thumbnails[1] = DecodeImage(DATATYPE_RGB565_BE, Header.BigPreview565, ThumbnailsOriginalSize[1]);
+ progress++;
+
+
+ progress.Reset(OperationProgress.StatusDecodeLayers, Header.LayerCount);
+ Init(Header.LayerCount, DecodeType == FileDecodeType.Partial);
+ LayersDefinition = new LayerDef[LayerCount];
+ inputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
+
+ foreach (var batch in BatchLayersIndexes())
+ {
+ foreach (var layerIndex in batch)
+ {
+ progress.PauseOrCancelIfRequested();
+
+ LayersDefinition[layerIndex] = Helpers.Deserialize(inputFile);
+ LayersDefinition[layerIndex].Parent = this;
+ if (DecodeType == FileDecodeType.Full)
+ {
+ LayersDefinition[layerIndex].EncodedRle = inputFile.ReadBytes(LayersDefinition[layerIndex].DataLength);
+ }
+ else
+ {
+ inputFile.Seek(LayersDefinition[layerIndex].DataLength, SeekOrigin.Current);
+ }
+
+ inputFile.Seek(2, SeekOrigin.Current); // \n
+ Debug.WriteLine($"layer[{layerIndex}]: {LayersDefinition[layerIndex]}");
+ }
+
+ if (DecodeType == FileDecodeType.Full)
+ {
+ Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
+ {
+ progress.PauseIfRequested();
+ var layerDef = LayersDefinition[layerIndex];
+
+ _layers[layerIndex] = new Layer((uint) layerIndex, layerDef.DecodeImage((uint) layerIndex), this);
+ layerDef.CopyTo(_layers[layerIndex]);
+
+ progress.LockAndIncrement();
+ });
+ }
+ }
+
+
+ Footer = Helpers.Deserialize(inputFile);
+ Debug.WriteLine($"Footer: {Footer}");
+ if (!Footer.Magic.SequenceEqual(expectedMagic))
+ {
+ throw new FileLoadException("Not a valid GOO file! Footer magic value mismatch", FileFullPath);
+ }
+ }
+
+ protected override void OnBeforeEncode(bool isPartialEncode)
+ {
+ Header.PerLayerSettings = AllLayersAreUsingGlobalParameters;
+ Header.Volume = Volume;
+ Header.MaterialGrams = MaterialMilliliters;
+ }
+
+ protected override void EncodeInternally(OperationProgress progress)
+ {
+ using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Create, FileAccess.Write);
+
+ progress.Reset(OperationProgress.StatusEncodePreviews, 2);
+
+ Mat?[] thumbnails = { GetThumbnail(true), GetThumbnail(false) };
+ Header.BigPreview565 = EncodeImage(DATATYPE_RGB565_BE, thumbnails[0]!);
+ progress++;
+ Header.SmallPreview565 = EncodeImage(DATATYPE_RGB565_BE, thumbnails[1]!);
+ progress++;
+
+ Header.LayerDefAddress = (uint)Helpers.Serializer.SizeOf(Header);
+ outputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
+
+ progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
+ var layerData = new LayerDef[LayerCount];
+
+ var delimiter = Delimiter;
+
+ foreach (var batch in BatchLayersIndexes())
+ {
+ Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
+ {
+ progress.PauseIfRequested();
+ using (var mat = this[layerIndex].LayerMat)
+ {
+ layerData[layerIndex] = new LayerDef(this, this[layerIndex]);
+ layerData[layerIndex].EncodeImage(mat, (uint) layerIndex);
+ }
+ progress.LockAndIncrement();
+ });
+
+ foreach (var layerIndex in batch)
+ {
+ progress.PauseOrCancelIfRequested();
+
+ outputFile.WriteSerialize(layerData[layerIndex]);
+ outputFile.WriteBytes(layerData[layerIndex].EncodedRle);
+ outputFile.WriteBytes(delimiter);
+
+ layerData[layerIndex].EncodedRle = null!; // Free
+ }
+ }
+
+ // Footer
+ outputFile.WriteSerialize(Footer);
+
+ // Header
+ outputFile.Seek(0, SeekOrigin.Begin);
+ outputFile.WriteSerialize(Header);
+
+ Debug.WriteLine("Encode Results:");
+ Debug.WriteLine(Header);
+ Debug.WriteLine(Footer);
+ Debug.WriteLine("-End-");
+ }
+
+ protected override void PartialSaveInternally(OperationProgress progress)
+ {
+ using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Open, FileAccess.Write);
+ outputFile.Seek(0, SeekOrigin.Begin);
+
+ outputFile.WriteSerialize(Header);
+ outputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
+ for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ {
+ LayersDefinition![layerIndex].SetFrom(this[layerIndex]);
+ outputFile.WriteSerialize(LayersDefinition[layerIndex]);
+ outputFile.Seek(LayersDefinition[layerIndex].DataLength + 2, SeekOrigin.Current);
+ }
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/UVtools.Core/FileFormats/LGSFile.cs b/UVtools.Core/FileFormats/LGSFile.cs
index 2c8add26..59f37b16 100644
--- a/UVtools.Core/FileFormats/LGSFile.cs
+++ b/UVtools.Core/FileFormats/LGSFile.cs
@@ -494,6 +494,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = this[layerIndex].LayerMat)
{
layerData[layerIndex] = new LayerDef(this);
@@ -504,7 +505,7 @@ protected override void EncodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
outputFile.WriteSerialize(layerData[layerIndex]);
layerData[layerIndex].EncodedRle = null!; // Free this
}
@@ -516,7 +517,7 @@ protected override void EncodeInternally(OperationProgress progress)
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
outputFile.WriteSerialize(layerData[layerIndex]);
progress++;
}
@@ -565,7 +566,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
layerData[layerIndex] = Helpers.Deserialize(inputFile);
layerData[layerIndex].Parent = this;
@@ -573,6 +574,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = layerData[layerIndex].Decode();
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
diff --git a/UVtools.Core/FileFormats/MDLPFile.cs b/UVtools.Core/FileFormats/MDLPFile.cs
index 3dfbfb34..f3891b59 100644
--- a/UVtools.Core/FileFormats/MDLPFile.cs
+++ b/UVtools.Core/FileFormats/MDLPFile.cs
@@ -299,6 +299,7 @@ protected override void EncodeInternally(OperationProgress progress)
// Previews
Parallel.For(0, previews.Length, CoreSettings.GetParallelOptions(progress), previewIndex =>
{
+ progress.PauseIfRequested();
var encodeLength = ThumbnailsOriginalSize[previewIndex].Area() * 2;
if (Thumbnails[previewIndex] is null)
{
@@ -330,6 +331,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = this[layerIndex];
using (var mat = layer.LayerMat)
{
@@ -409,6 +411,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.For(0, previews.Length, CoreSettings.GetParallelOptions(progress), previewIndex =>
{
+ progress.PauseIfRequested();
Thumbnails[previewIndex] = DecodeImage(DATATYPE_RGB565_BE, previews[previewIndex], ThumbnailsOriginalSize[previewIndex]);
previews[previewIndex] = null!;
});
@@ -426,7 +429,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var lineCount = BitExtensions.ToUIntBigEndian(inputFile.ReadBytes(4));
@@ -437,6 +440,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = EmguExtensions.InitMat(Resolution))
{
diff --git a/UVtools.Core/FileFormats/OSFFile.cs b/UVtools.Core/FileFormats/OSFFile.cs
index cf42e397..2933a5df 100644
--- a/UVtools.Core/FileFormats/OSFFile.cs
+++ b/UVtools.Core/FileFormats/OSFFile.cs
@@ -714,6 +714,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = this[layerIndex];
using (var mat = layer.LayerMat)
@@ -730,7 +731,7 @@ protected override void EncodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
outputFile.WriteSerialize(layerDef[layerIndex]);
outputFile.WriteBytes(layerDef[layerIndex].EncodedRle);
layerDef[layerIndex].EncodedRle = null!; // Free this
@@ -781,7 +782,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
//Debug.WriteLine($"{layerIndex}: {inputFile.Position}");
layerDef[layerIndex] = Helpers.Deserialize(inputFile);
@@ -835,6 +836,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = layerDef[layerIndex].DecodeImage(this);
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
layerDef[layerIndex].EncodedRle = null!;
diff --git a/UVtools.Core/FileFormats/OSLAFile.cs b/UVtools.Core/FileFormats/OSLAFile.cs
index 17f3369f..d7cf08c8 100644
--- a/UVtools.Core/FileFormats/OSLAFile.cs
+++ b/UVtools.Core/FileFormats/OSLAFile.cs
@@ -461,7 +461,7 @@ protected override void EncodeInternally(OperationProgress progress)
var image = Thumbnails[i];
if(image is null) continue;
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var bytes = EncodeImage(HeaderSettings.PreviewDataType, image);
if (bytes.Length == 0) continue;
@@ -504,6 +504,7 @@ protected override void EncodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = this[layerIndex].LayerMat)
{
layerBytes[layerIndex] = EncodeImage(HeaderSettings.LayerDataType, mat);
@@ -514,7 +515,7 @@ protected override void EncodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
// Try to reuse layers
var hash = CryptExtensions.ComputeSHA1Hash(layerBytes[layerIndex]);
@@ -540,7 +541,7 @@ protected override void EncodeInternally(OperationProgress progress)
outputFile.Seek(HeaderSettings.LayerDefinitionsAddress, SeekOrigin.Begin);
for (int layerIndex = 0; layerIndex < layerDataAddresses.Length; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layer = this[layerIndex];
var layerdef = new LayerDef(layer);
@@ -599,7 +600,7 @@ protected override void DecodeInternally(OperationProgress progress)
for (byte i = 0; i < HeaderSettings.PreviewCount; i++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var preview = Helpers.Deserialize(inputFile);
Debug.Write($"Preview {i} -> ");
@@ -628,7 +629,7 @@ protected override void DecodeInternally(OperationProgress progress)
uint layerTableSize = 0;
for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
layerDataAddresses[layerIndex] = inputFile.ReadUIntLittleEndian();
layerDef[layerIndex] = Helpers.Deserialize(inputFile);
@@ -654,7 +655,7 @@ protected override void DecodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
inputFile.Seek(layerDataAddresses[layerIndex], SeekOrigin.Begin);
layerBytes[layerIndex] = inputFile.ReadBytes(inputFile.ReadUIntLittleEndian());
@@ -662,6 +663,7 @@ protected override void DecodeInternally(OperationProgress progress)
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = DecodeImage(HeaderSettings.LayerDataType, layerBytes[layerIndex], Resolution);
layerBytes[layerIndex] = null!; // Clean
diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs
index bfc1d7b7..d9b55587 100644
--- a/UVtools.Core/FileFormats/PHZFile.cs
+++ b/UVtools.Core/FileFormats/PHZFile.cs
@@ -981,6 +981,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = this[layerIndex].LayerMat)
{
LayersDefinitions[layerIndex] = new LayerDef(this, this[layerIndex]);
@@ -991,7 +992,7 @@ protected override void EncodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = LayersDefinitions[layerIndex];
LayerDef? layerDefHash = null;
@@ -1093,7 +1094,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = Helpers.Deserialize(inputFile);
layerDef.Parent = this;
@@ -1113,6 +1114,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = LayersDefinitions[layerIndex].Decode((uint)layerIndex);
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
progress.LockAndIncrement();
diff --git a/UVtools.Core/FileFormats/PhotonSFile.cs b/UVtools.Core/FileFormats/PhotonSFile.cs
index 8fe30354..19fbc371 100644
--- a/UVtools.Core/FileFormats/PhotonSFile.cs
+++ b/UVtools.Core/FileFormats/PhotonSFile.cs
@@ -415,6 +415,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = this[layerIndex].LayerMat)
{
layerData[layerIndex] = new LayerDef(mat);
@@ -425,7 +426,7 @@ protected override void EncodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
outputFile.WriteSerialize(layerData[layerIndex]);
outputFile.WriteBytes(layerData[layerIndex].EncodedRle);
@@ -468,7 +469,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = Helpers.Deserialize(inputFile);
layersDefinitions[layerIndex] = layerDef;
@@ -491,6 +492,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = layersDefinitions[layerIndex].Decode();
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
progress.LockAndIncrement();
diff --git a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
index d14ee70a..f46f02aa 100644
--- a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
+++ b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
@@ -1760,6 +1760,7 @@ protected override void EncodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = this[layerIndex].LayerMat)
{
LayersDefinition.Layers[layerIndex] = new LayerDef(this, this[layerIndex]);
@@ -1770,7 +1771,7 @@ protected override void EncodeInternally(OperationProgress progress)
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layerDef = LayersDefinition.Layers[layerIndex];
@@ -1924,7 +1925,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
foreach (var layerIndex in batch)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
LayersDefinition[layerIndex] = Helpers.Deserialize(inputFile);
LayersDefinition[layerIndex].Parent = this;
@@ -1944,6 +1945,7 @@ protected override void DecodeInternally(OperationProgress progress)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = LayersDefinition[layerIndex].Decode();
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this)
{
diff --git a/UVtools.Core/FileFormats/ZCodexFile.cs b/UVtools.Core/FileFormats/ZCodexFile.cs
index 24996bfd..509673eb 100644
--- a/UVtools.Core/FileFormats/ZCodexFile.cs
+++ b/UVtools.Core/FileFormats/ZCodexFile.cs
@@ -390,7 +390,7 @@ protected override void EncodeInternally(OperationProgress progress)
float lastZPosition = 0;
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var layer = this[layerIndex];
GCode.AppendLine($"{GCodeKeywordSlice} {layerIndex}");
diff --git a/UVtools.Core/Layers/Layer.cs b/UVtools.Core/Layers/Layer.cs
index 3c8cd1b9..08fc7266 100644
--- a/UVtools.Core/Layers/Layer.cs
+++ b/UVtools.Core/Layers/Layer.cs
@@ -292,7 +292,8 @@ public bool IsBottomLayer
///
/// Gets if this layer is also an transition layer
///
- public bool IsTransitionLayer => SlicerFile.TransitionLayerCount <= Number;
+ public bool IsTransitionLayer => SlicerFile.TransitionLayerCount > 0 &&
+ Index >= SlicerFile.BottomLayerCount && Index < SlicerFile.BottomLayerCount + SlicerFile.TransitionLayerCount;
///
/// Gets the previous layer, returns null if no previous layer
diff --git a/UVtools.Core/Managers/IssueManager.cs b/UVtools.Core/Managers/IssueManager.cs
index 6b8e7f7a..183f9bc5 100644
--- a/UVtools.Core/Managers/IssueManager.cs
+++ b/UVtools.Core/Managers/IssueManager.cs
@@ -236,6 +236,7 @@ void GenerateAirMap(IInputArray input, IInputOutputArray output, VectorOfVectorO
// Detect contours
Parallel.For(0, SlicerFile.LayerCount, CoreSettings.ParallelOptions, layerIndexInt =>
{
+ progress.PauseIfRequested();
if (progress.Token.IsCancellationRequested) return;
uint layerIndex = (uint)layerIndexInt;
var layer = SlicerFile[layerIndex];
@@ -706,6 +707,7 @@ void GenerateAirMap(IInputArray input, IInputOutputArray output, VectorOfVectorO
airContours[layerIndex] = new();
Parallel.For(0, hollows[layerIndex].Count, CoreSettings.ParallelOptions, i =>
{
+ progress.PauseIfRequested();
//for (var i = 0; i < hollows[layerIndex].Count; i++)
//{
if (progress.Token.IsCancellationRequested) return;
@@ -793,7 +795,10 @@ void GenerateAirMap(IInputArray input, IInputOutputArray output, VectorOfVectorO
if (airContours[layerIndex] is not null)
{
Parallel.ForEach(airContours[layerIndex], CoreSettings.ParallelOptions, vec =>
- CvInvoke.DrawContours(layerAirMap, vec, -1, EmguExtensions.WhiteColor, -1)
+ {
+ progress.PauseIfRequested();
+ CvInvoke.DrawContours(layerAirMap, vec, -1, EmguExtensions.WhiteColor, -1);
+ }
);
}
@@ -810,6 +815,7 @@ void GenerateAirMap(IInputArray input, IInputOutputArray output, VectorOfVectorO
/* all we care about is contours the first pass thought were resin traps, since there was no access to air from the bottom */
Parallel.For(0, resinTraps[layerIndex].Count, CoreSettings.ParallelOptions, x =>
{
+ progress.PauseIfRequested();
if (progress.Token.IsCancellationRequested) return;
/* check if each contour overlaps known air */
@@ -949,6 +955,7 @@ void GenerateAirMap(IInputArray input, IInputOutputArray output, VectorOfVectorO
{
Parallel.ForEach(listOfLayers.Where(list => list is not null), contoursGroups =>
{
+ progress.PauseIfRequested();
for (var groupIndex = 0; groupIndex < contoursGroups.Count; groupIndex++)
{
var contours = contoursGroups[groupIndex];
@@ -985,6 +992,7 @@ void GenerateAirMap(IInputArray input, IInputOutputArray output, VectorOfVectorO
/* select new LayerIssue(this[layerIndex], LayerIssue.IssueType.ResinTrap, area.Contour, area.BoundingRectangle)) */
foreach (var trap in resinTraps[layerIndex])
{
+ progress.PauseIfRequested();
if (progress.Token.IsCancellationRequested) return;
var area = EmguContours.GetContourArea(trap);
@@ -1053,6 +1061,7 @@ void GenerateAirMap(IInputArray input, IInputOutputArray output, VectorOfVectorO
foreach (var trap in suctionCups[layerIndex])
{
+ progress.PauseIfRequested();
if (progress.Token.IsCancellationRequested) return;
var area = EmguContours.GetContourArea(trap);
diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs
index 06230f61..76029d91 100644
--- a/UVtools.Core/Operations/Operation.cs
+++ b/UVtools.Core/Operations/Operation.cs
@@ -741,7 +741,7 @@ public bool Execute(OperationProgress? progress = null)
var result = ExecuteInternally(progress);
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
return result;
}
diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs
index 39d8c7da..806b4f17 100644
--- a/UVtools.Core/Operations/OperationBlur.cs
+++ b/UVtools.Core/Operations/OperationBlur.cs
@@ -133,6 +133,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = SlicerFile[layerIndex].LayerMat)
{
Execute(mat);
diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
index c5d4f946..e8050619 100644
--- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
+++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
@@ -1893,6 +1893,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.ItemCount = (uint) (SlicerFile.LayerCount * table.Count);
Parallel.For(0, SlicerFile.LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = SlicerFile[layerIndex];
using var mat = layer.LayerMat;
var matRoi = new Mat(mat, boundingRectangle);
@@ -1970,7 +1971,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Layer currentLayer = layers[0];
for (var layerIndex = 1; layerIndex < layers.Count; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
progress++;
var layer = layers[layerIndex];
if (currentLayer.PositionZ != layer.PositionZ ||
@@ -2203,7 +2204,7 @@ lastExposureItem is not null &&
currentHeight = Layer.RoundHeight(currentHeight);
for (decimal layerHeight = _layerHeight; layerHeight <= endLayerHeight; layerHeight += _multipleLayerHeightStep)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
layerHeight = Layer.RoundHeight(layerHeight);
if (_multipleExposures)
diff --git a/UVtools.Core/Operations/OperationCalibrateStressTower.cs b/UVtools.Core/Operations/OperationCalibrateStressTower.cs
index c6f17c67..02822ee9 100644
--- a/UVtools.Core/Operations/OperationCalibrateStressTower.cs
+++ b/UVtools.Core/Operations/OperationCalibrateStressTower.cs
@@ -419,6 +419,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(0, LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], SlicerFile) {IsModified = true};
layers[layerIndex].Dispose();
progress.LockAndIncrement();
diff --git a/UVtools.Core/Operations/OperationCalibrateTolerance.cs b/UVtools.Core/Operations/OperationCalibrateTolerance.cs
index bf76c8ef..ad3dbb72 100644
--- a/UVtools.Core/Operations/OperationCalibrateTolerance.cs
+++ b/UVtools.Core/Operations/OperationCalibrateTolerance.cs
@@ -730,6 +730,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(0, LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], SlicerFile) {IsModified = true};
layers[layerIndex].Dispose();
progress.LockAndIncrement();
diff --git a/UVtools.Core/Operations/OperationChangeResolution.cs b/UVtools.Core/Operations/OperationChangeResolution.cs
index 409032e9..cecd6531 100644
--- a/UVtools.Core/Operations/OperationChangeResolution.cs
+++ b/UVtools.Core/Operations/OperationChangeResolution.cs
@@ -254,6 +254,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(0, SlicerFile.LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
if (mat.Size != newSize)
diff --git a/UVtools.Core/Operations/OperationDoubleExposure.cs b/UVtools.Core/Operations/OperationDoubleExposure.cs
index 957e4520..21f93773 100644
--- a/UVtools.Core/Operations/OperationDoubleExposure.cs
+++ b/UVtools.Core/Operations/OperationDoubleExposure.cs
@@ -283,6 +283,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var firstLayer = SlicerFile[layerIndex];
var secondLayer = firstLayer.Clone();
var isBottomLayer = firstLayer.IsBottomLayer;
diff --git a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
index 1f60dc5e..df0abc76 100644
--- a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
+++ b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
@@ -637,7 +637,7 @@ void ReUseLayer(uint layerIndex)
while (true) // In a stack
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
progress.ProcessedItems = layerIndex - LayerIndexStart;
if (currentLayerHeight >= (float)_maximumLayerHeight || layerIndex == LayerIndexEnd)
diff --git a/UVtools.Core/Operations/OperationDynamicLifts.cs b/UVtools.Core/Operations/OperationDynamicLifts.cs
index c444a735..286c7cbd 100644
--- a/UVtools.Core/Operations/OperationDynamicLifts.cs
+++ b/UVtools.Core/Operations/OperationDynamicLifts.cs
@@ -289,7 +289,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var calculateLayer = SlicerFile[layerIndex == 0 ? 0 : layerIndex - 1];
var setLayer = SlicerFile[layerIndex];
diff --git a/UVtools.Core/Operations/OperationFadeExposureTime.cs b/UVtools.Core/Operations/OperationFadeExposureTime.cs
index cc925772..9b74f375 100644
--- a/UVtools.Core/Operations/OperationFadeExposureTime.cs
+++ b/UVtools.Core/Operations/OperationFadeExposureTime.cs
@@ -198,7 +198,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
var exposure = _fromExposureTime;
for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
exposure += increment;
SlicerFile[layerIndex].ExposureTime = (float)exposure;
}
diff --git a/UVtools.Core/Operations/OperationFlip.cs b/UVtools.Core/Operations/OperationFlip.cs
index d4a8a069..ef565d9d 100644
--- a/UVtools.Core/Operations/OperationFlip.cs
+++ b/UVtools.Core/Operations/OperationFlip.cs
@@ -102,6 +102,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
Execute(mat);
SlicerFile[layerIndex].LayerMat = mat;
diff --git a/UVtools.Core/Operations/OperationInfill.cs b/UVtools.Core/Operations/OperationInfill.cs
index 1d578c03..e28a9d7d 100644
--- a/UVtools.Core/Operations/OperationInfill.cs
+++ b/UVtools.Core/Operations/OperationInfill.cs
@@ -171,6 +171,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
Execute(mat, layerIndex, mask!);
SlicerFile[layerIndex].LayerMat = mat;
diff --git a/UVtools.Core/Operations/OperationLayerArithmetic.cs b/UVtools.Core/Operations/OperationLayerArithmetic.cs
index ffdbfe99..d4442a32 100644
--- a/UVtools.Core/Operations/OperationLayerArithmetic.cs
+++ b/UVtools.Core/Operations/OperationLayerArithmetic.cs
@@ -247,7 +247,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
if(!operation.IsValid) continue;
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
using var result = SlicerFile[operation.Operations[0].LayerIndex].LayerMat;
using var resultRoi = GetRoiOrDefault(result);
using var imageMask = GetMask(resultRoi);
@@ -255,7 +255,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.ItemCount = (uint)operation.Operations.Count;
for (int i = 1; i < operation.Operations.Count; i++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
using var image = SlicerFile[operation.Operations[i].LayerIndex].LayerMat;
var imageRoi = GetRoiOrDefault(image);
@@ -293,6 +293,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.Reset("Applied layers", (uint)operation.SetLayers.Count);
Parallel.ForEach(operation.SetLayers, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
progress.LockAndIncrement();
if (operation.Operations.Count == 1 || HaveROIorMask)
{
diff --git a/UVtools.Core/Operations/OperationLayerExportGif.cs b/UVtools.Core/Operations/OperationLayerExportGif.cs
index e2fb4c55..e27ddbb4 100644
--- a/UVtools.Core/Operations/OperationLayerExportGif.cs
+++ b/UVtools.Core/Operations/OperationLayerExportGif.cs
@@ -226,6 +226,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(0, TotalLayers, CoreSettings.GetParallelOptions(progress), i =>
{
+ progress.PauseIfRequested();
uint layerIndex = (uint) (LayerIndexStart + i * (_skip + 1));
var layer = SlicerFile[layerIndex];
using var mat = layer.LayerMat;
@@ -269,7 +270,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.ResetNameAndProcessed("Packed layers");
foreach (var buffer in layerBuffer)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
using var stream = new MemoryStream(buffer);
using var img = Image.FromStream(stream);
gif.AddFrame(img, -1, GifQuality.Bit8);
diff --git a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
index 3f176de5..72322b59 100644
--- a/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
+++ b/UVtools.Core/Operations/OperationLayerExportHeatMap.cs
@@ -138,6 +138,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.ForEach(layerRange, CoreSettings.GetParallelOptions(progress), layer =>
{
+ progress.PauseIfRequested();
using var mat = _mergeSamePositionedLayers
? SlicerFile.GetMergedMatForSequentialPositionedLayers(layer.Index)
: layer.LayerMat;
diff --git a/UVtools.Core/Operations/OperationLayerExportHtml.cs b/UVtools.Core/Operations/OperationLayerExportHtml.cs
index 9686ece7..9ed8806b 100644
--- a/UVtools.Core/Operations/OperationLayerExportHtml.cs
+++ b/UVtools.Core/Operations/OperationLayerExportHtml.cs
@@ -582,6 +582,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
var layerSvgPath = new string[SlicerFile.LayerCount];
Parallel.For(0, SlicerFile.LayerCount, CoreSettings.ParallelOptions, layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
CvInvoke.Threshold(mat, mat, 127, byte.MaxValue, ThresholdType.Binary); // Remove AA
diff --git a/UVtools.Core/Operations/OperationLayerExportImage.cs b/UVtools.Core/Operations/OperationLayerExportImage.cs
index 3685873c..83441f9e 100644
--- a/UVtools.Core/Operations/OperationLayerExportImage.cs
+++ b/UVtools.Core/Operations/OperationLayerExportImage.cs
@@ -181,6 +181,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(LayerIndexStart, LayerIndexEnd+1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
var matRoi = mat;
if (_cropByRoi && HaveROI)
diff --git a/UVtools.Core/Operations/OperationLayerExportMesh.cs b/UVtools.Core/Operations/OperationLayerExportMesh.cs
index b3382f4e..bc019657 100644
--- a/UVtools.Core/Operations/OperationLayerExportMesh.cs
+++ b/UVtools.Core/Operations/OperationLayerExportMesh.cs
@@ -342,6 +342,7 @@ void ExitCleanup()
/* Seems to be faster to parallel on the Y and not the X */
Parallel.For(0, curLayer!.Height, CoreSettings.GetParallelOptions(progress), y =>
{
+ progress.PauseIfRequested();
/* Collects all the faces found for this thread, will be combined into the main dictionary later */
var threadDict = new Dictionary>();
for (var x = 0; x < curLayer.Width; x++)
@@ -545,6 +546,7 @@ or Voxelizer.FaceOrientation.Top
/* We build out a 3 dimensional KD tree for each layer, having 1 big KD tree is prohibitive when you get to millions and millions of faces. */
Parallel.For(0, distinctLayers.Length, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
/* Create the KD tree for the layer, in practice there should never be dups, but just in case, set to skip */
layerTrees[layerIndex] = new KdTree(3, new FloatMath(), AddDuplicateBehavior.Skip);
@@ -577,6 +579,7 @@ or Voxelizer.FaceOrientation.Top
*/
Parallel.For(0, distinctLayers.Length, CoreSettings.GetParallelOptions(progress), i =>
{
+ progress.PauseIfRequested();
/* if no faces on this layer... skip.... needed for empty layers */
if (layerTrees[i] is null) return;
diff --git a/UVtools.Core/Operations/OperationLayerExportSkeleton.cs b/UVtools.Core/Operations/OperationLayerExportSkeleton.cs
index efc677f3..40c290ed 100644
--- a/UVtools.Core/Operations/OperationLayerExportSkeleton.cs
+++ b/UVtools.Core/Operations/OperationLayerExportSkeleton.cs
@@ -93,6 +93,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(LayerIndexStart, LayerIndexEnd+1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
var matRoi = GetRoiOrDefault(mat);
using var skeletonRoi = matRoi.Skeletonize();
diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs
index dc5a65cf..46690fa4 100644
--- a/UVtools.Core/Operations/OperationLayerImport.cs
+++ b/UVtools.Core/Operations/OperationLayerImport.cs
@@ -268,6 +268,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.ForEach(keyImage, CoreSettings.GetParallelOptions(progress), pair =>
{
+ progress.PauseIfRequested();
using var mat = CvInvoke.Imread(pair.Value, ImreadModes.Grayscale);
if (pair.Key == 0) format.Resolution = mat.Size;
format[pair.Key] = new Layer(pair.Key, mat, format);
@@ -289,7 +290,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
fileFormats.Add(fileFormat);
}
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
if (fileFormats.Count == 0) return false;
@@ -385,6 +386,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.Reset(ProgressAction, fileFormat.LayerCount);
Parallel.For(0, fileFormat.LayerCount, CoreSettings.GetParallelOptions(progress), i =>
{
+ progress.PauseIfRequested();
uint layerIndex = (uint)(_startLayerIndex + i);
switch (_importType)
diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs
index cb655503..7bacb193 100644
--- a/UVtools.Core/Operations/OperationLayerReHeight.cs
+++ b/UVtools.Core/Operations/OperationLayerReHeight.cs
@@ -296,7 +296,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
uint newLayerIndex = 0;
for (uint layerIndex = 0; layerIndex < SlicerFile.LayerCount; layerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
var oldLayer = SlicerFile[layerIndex];
for (byte i = 0; i < _selectedItem.Modifier; i++)
@@ -320,6 +320,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.ForEach(layerIndexes, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var oldLayer = SlicerFile[layerIndex];
using var matSum = oldLayer.LayerMat;
Mat? matXorSum = null;
diff --git a/UVtools.Core/Operations/OperationLightBleedCompensation.cs b/UVtools.Core/Operations/OperationLightBleedCompensation.cs
index c4c153d2..915c9981 100644
--- a/UVtools.Core/Operations/OperationLightBleedCompensation.cs
+++ b/UVtools.Core/Operations/OperationLightBleedCompensation.cs
@@ -205,6 +205,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
if (dimMats.Length == 0) return false;
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = SlicerFile[layerIndex];
using var mat = layer.LayerMat;
using var original = mat.Clone();
diff --git a/UVtools.Core/Operations/OperationLithophane.cs b/UVtools.Core/Operations/OperationLithophane.cs
index dd11dda4..c0836b3c 100644
--- a/UVtools.Core/Operations/OperationLithophane.cs
+++ b/UVtools.Core/Operations/OperationLithophane.cs
@@ -394,6 +394,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.Reset("Threshold levels", byte.MaxValue);
Parallel.For(_startThresholdRange, _endThresholdRange, CoreSettings.GetParallelOptions(progress), threshold =>
{
+ progress.PauseIfRequested();
using var thresholdMat = new Mat();
CvInvoke.Threshold(mat, thresholdMat, threshold, byte.MaxValue, ThresholdType.Binary);
if (CvInvoke.CountNonZero(thresholdMat) == 0) return;
@@ -436,6 +437,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.ResetNameAndProcessed("Packed layers");
Parallel.ForEach(indexes, CoreSettings.GetParallelOptions(progress), i =>
{
+ progress.PauseIfRequested();
progress.LockAndIncrement();
using var mat = thresholdLayers[i].LayerMat;
for (int index = i+1; index < i + layerIncrement && index < thresholdLayers.Length; index++)
diff --git a/UVtools.Core/Operations/OperationMask.cs b/UVtools.Core/Operations/OperationMask.cs
index 6991af5d..3d514813 100644
--- a/UVtools.Core/Operations/OperationMask.cs
+++ b/UVtools.Core/Operations/OperationMask.cs
@@ -97,6 +97,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
Execute(mat);
SlicerFile[layerIndex].LayerMat = mat;
diff --git a/UVtools.Core/Operations/OperationMorph.cs b/UVtools.Core/Operations/OperationMorph.cs
index 5462b2e4..f270384e 100644
--- a/UVtools.Core/Operations/OperationMorph.cs
+++ b/UVtools.Core/Operations/OperationMorph.cs
@@ -178,6 +178,7 @@ out var maxIteration
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
int iterations = FileFormat.MutateGetIterationVar(isFade, (int)IterationsStart, (int)IterationsEnd, iterationSteps, maxIteration, LayerIndexStart, (uint)layerIndex);
using var mat = SlicerFile[layerIndex].LayerMat;
diff --git a/UVtools.Core/Operations/OperationMove.cs b/UVtools.Core/Operations/OperationMove.cs
index 4948705f..5c3c7da7 100644
--- a/UVtools.Core/Operations/OperationMove.cs
+++ b/UVtools.Core/Operations/OperationMove.cs
@@ -266,6 +266,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = SlicerFile[layerIndex].LayerMat)
{
Execute(mat);
diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs
index 028aa5cd..157b21a9 100644
--- a/UVtools.Core/Operations/OperationPattern.cs
+++ b/UVtools.Core/Operations/OperationPattern.cs
@@ -301,6 +301,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
using var layerRoi = new Mat(mat, ROI);
using var dstLayer = mat.NewBlank();
diff --git a/UVtools.Core/Operations/OperationPixelArithmetic.cs b/UVtools.Core/Operations/OperationPixelArithmetic.cs
index f5605609..a3a44854 100644
--- a/UVtools.Core/Operations/OperationPixelArithmetic.cs
+++ b/UVtools.Core/Operations/OperationPixelArithmetic.cs
@@ -583,6 +583,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = SlicerFile[layerIndex];
using (var mat = layer.LayerMat)
{
diff --git a/UVtools.Core/Operations/OperationPixelDimming.cs b/UVtools.Core/Operations/OperationPixelDimming.cs
index 3728b30a..74909447 100644
--- a/UVtools.Core/Operations/OperationPixelDimming.cs
+++ b/UVtools.Core/Operations/OperationPixelDimming.cs
@@ -652,6 +652,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
Execute(mat, layerIndex, patternMask, alternatePatternMask);
SlicerFile[layerIndex].LayerMat = mat;
diff --git a/UVtools.Core/Operations/OperationProgress.cs b/UVtools.Core/Operations/OperationProgress.cs
index 94493ff3..d2cb5e9e 100644
--- a/UVtools.Core/Operations/OperationProgress.cs
+++ b/UVtools.Core/Operations/OperationProgress.cs
@@ -12,7 +12,7 @@
namespace UVtools.Core.Operations;
-public sealed class OperationProgress : BindableBase
+public sealed class OperationProgress : BindableBase, IDisposable
{
public const string StatusDecodePreviews = "Decoded Previews";
public const string StatusGatherLayers = "Gathered Layers";
@@ -39,7 +39,25 @@ public sealed class OperationProgress : BindableBase
public CancellationToken Token => TokenSource.Token;
public void ThrowIfCancellationRequested() => TokenSource.Token.ThrowIfCancellationRequested();
+ public ManualResetEvent ManualReset { get; } = new (true);
+
+ ///
+ /// Blocks the current thread until the current WaitHandle receives a signal.
+ ///
+ /// true if the current instance receives a signal. If the current instance is never signaled, WaitOne() never returns.
+ public bool PauseIfRequested() => ManualReset.WaitOne();
+
+ ///
+ /// Blocks or cancels the current thread until the current WaitHandle receives a signal.
+ ///
+ public void PauseOrCancelIfRequested()
+ {
+ ManualReset.WaitOne();
+ TokenSource.Token.ThrowIfCancellationRequested();
+ }
+
private bool _canCancel = true;
+ private bool _isPaused;
private string _title = "Operation";
private string _itemName = "Initializing";
private uint _processedItems;
@@ -77,6 +95,25 @@ public bool CanCancel
set => RaiseAndSetIfChanged(ref _canCancel, value);
}
+ public bool IsPaused
+ {
+ get => _isPaused;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _isPaused, value)) return;
+ if (value)
+ {
+ ManualReset.Reset(); // pause
+ StopWatch.Stop();
+ }
+ else
+ {
+ ManualReset.Set(); // resume
+ StopWatch.Start();
+ }
+ }
+ }
+
///
/// Gets or sets the item name for the operation
///
@@ -183,6 +220,7 @@ public string Log
public void Init(bool canCancel = true)
{
CanCancel = canCancel;
+ IsPaused = false;
Title = "Operation";
ItemName = "Initializing";
ItemCount = 0;
@@ -250,4 +288,10 @@ public void LockAndIncrement()
RaisePropertyChanged(nameof(ProgressPercent));
RaisePropertyChanged(nameof(Description));
}
+
+ public void Dispose()
+ {
+ TokenSource.Dispose();
+ ManualReset.Dispose();
+ }
}
\ No newline at end of file
diff --git a/UVtools.Core/Operations/OperationRaftRelief.cs b/UVtools.Core/Operations/OperationRaftRelief.cs
index 443dae4a..8ca1b9fb 100644
--- a/UVtools.Core/Operations/OperationRaftRelief.cs
+++ b/UVtools.Core/Operations/OperationRaftRelief.cs
@@ -283,7 +283,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.Reset("Tracing raft", layerCount, firstSupportLayerIndex);
for (; firstSupportLayerIndex < layerCount; firstSupportLayerIndex++)
{
- progress.ThrowIfCancellationRequested();
+ progress.PauseOrCancelIfRequested();
supportsMat = GetRoiOrDefault(SlicerFile[firstSupportLayerIndex].LayerMat);
//var circles = CvInvoke.HoughCircles(supportsMat, HoughModes.Gradient, 1, 5, 80, 35, 5, 255); // OLD
var circles = CvInvoke.HoughCircles(supportsMat, HoughModes.GradientAlt, 1.5, 25, 300, 0.80, 5, 255);
@@ -397,6 +397,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
progress.Reset(ProgressAction, firstSupportLayerIndex - _ignoreFirstLayers);
Parallel.For(_ignoreFirstLayers, firstSupportLayerIndex, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
using var original = mat.Clone();
var target = GetRoiOrDefault(mat);
diff --git a/UVtools.Core/Operations/OperationRedrawModel.cs b/UVtools.Core/Operations/OperationRedrawModel.cs
index a769a953..c14eed6a 100644
--- a/UVtools.Core/Operations/OperationRedrawModel.cs
+++ b/UVtools.Core/Operations/OperationRedrawModel.cs
@@ -172,6 +172,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
if (startLayerIndex < 0) return false;
Parallel.For(0, otherFile.LayerCount, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var fullMatLayerIndex = startLayerIndex + layerIndex;
using var fullMat = SlicerFile[fullMatLayerIndex].LayerMat;
using var original = fullMat.Clone();
diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs
index e0806b84..15f0a99b 100644
--- a/UVtools.Core/Operations/OperationRepairLayers.cs
+++ b/UVtools.Core/Operations/OperationRepairLayers.cs
@@ -280,6 +280,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
islandsToRecompute.Clear();
Parallel.ForEach(issuesGroup, CoreSettings.GetParallelOptions(progress), group =>
{
+ progress.PauseIfRequested();
var layer = SlicerFile[group.Key];
var image = layer.LayerMat;
var span = image.GetDataByteSpan();
@@ -327,6 +328,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
var sync = new object();
Parallel.ForEach(issuesGroup, CoreSettings.GetParallelOptions(progress), group =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[group.Key].LayerMat;
var matSpan = mat.GetDataByteSpan();
var matCache = new Dictionary();
@@ -417,6 +419,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
Parallel.For(LayerIndexStart, LayerIndexEnd, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var layer = SlicerFile[layerIndex];
Mat? image = null;
diff --git a/UVtools.Core/Operations/OperationResize.cs b/UVtools.Core/Operations/OperationResize.cs
index 4b57c593..e2b623e1 100644
--- a/UVtools.Core/Operations/OperationResize.cs
+++ b/UVtools.Core/Operations/OperationResize.cs
@@ -159,6 +159,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
var newX = _x;
var newY = _y;
if (IsFade)
diff --git a/UVtools.Core/Operations/OperationRotate.cs b/UVtools.Core/Operations/OperationRotate.cs
index 970652b6..2f8f41c6 100644
--- a/UVtools.Core/Operations/OperationRotate.cs
+++ b/UVtools.Core/Operations/OperationRotate.cs
@@ -97,6 +97,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
Execute(mat);
SlicerFile[layerIndex].LayerMat = mat;
diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs
index 71498f1c..34457b26 100644
--- a/UVtools.Core/Operations/OperationSolidify.cs
+++ b/UVtools.Core/Operations/OperationSolidify.cs
@@ -89,6 +89,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using var mat = SlicerFile[layerIndex].LayerMat;
Execute(mat);
SlicerFile[layerIndex].LayerMat = mat;
diff --git a/UVtools.Core/Operations/OperationThreshold.cs b/UVtools.Core/Operations/OperationThreshold.cs
index bb513fe9..4190bfc6 100644
--- a/UVtools.Core/Operations/OperationThreshold.cs
+++ b/UVtools.Core/Operations/OperationThreshold.cs
@@ -84,6 +84,7 @@ protected override bool ExecuteInternally(OperationProgress progress)
{
Parallel.For(LayerIndexStart, LayerIndexEnd + 1, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
+ progress.PauseIfRequested();
using (var mat = SlicerFile[layerIndex].LayerMat)
{
Execute(mat);
diff --git a/UVtools.Core/Suggestions/SuggestionTransitionLayerCount.cs b/UVtools.Core/Suggestions/SuggestionTransitionLayerCount.cs
index 433daf06..08b10bd8 100644
--- a/UVtools.Core/Suggestions/SuggestionTransitionLayerCount.cs
+++ b/UVtools.Core/Suggestions/SuggestionTransitionLayerCount.cs
@@ -83,11 +83,16 @@ public override string Message
: SlicerFile.ParseTransitionLayerCountFromLayers();
var suggestedTransitionLayerCount = TransitionLayerCount;
- var suggestedTransitionDecrementTime = SlicerFile.GetTransitionStepTime(suggestedTransitionLayerCount);
+ var suggestedTransitionDecrementTime = SlicerFile.GetTransitionStepTimeFromLayers(suggestedTransitionLayerCount);
+
+ var bottomExposureTime = SlicerFile.LastBottomLayer?.ExposureTime ?? SlicerFile.BottomExposureTime;
+ var exposureTime = SlicerFile.TransitionLayerCount > 0
+ ? SlicerFile[SlicerFile.BottomLayerCount + SlicerFile.TransitionLayerCount - 1].ExposureTime
+ : SlicerFile[SlicerFile.BottomLayerCount].ExposureTime;
return IsApplied
- ? $"{GlobalAppliedMessage}: {SlicerFile.BottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s"
- : $"{GlobalNotAppliedMessage} {SlicerFile.BottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s is out of the recommended {SlicerFile.BottomExposureTime}s » {(suggestedTransitionDecrementTime <= 0 || suggestedTransitionLayerCount == 0 ? string.Empty : $"[-{suggestedTransitionDecrementTime}s/{suggestedTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s";
+ ? $"{GlobalAppliedMessage}: {bottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{exposureTime}s"
+ : $"{GlobalNotAppliedMessage} {bottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{exposureTime}s is out of the recommended {bottomExposureTime}s » {(suggestedTransitionDecrementTime <= 0 || suggestedTransitionLayerCount == 0 ? string.Empty : $"[-{suggestedTransitionDecrementTime}s/{suggestedTransitionLayerCount} layers] » ")}{exposureTime}s";
}
}
@@ -108,17 +113,22 @@ public override string? ConfirmationMessage
: SlicerFile.ParseTransitionLayerCountFromLayers();
var suggestedTransitionLayerCount = TransitionLayerCount;
- var suggestedTransitionDecrementTime = SlicerFile.GetTransitionStepTime(suggestedTransitionLayerCount);
+ var suggestedTransitionDecrementTime = SlicerFile.GetTransitionStepTimeFromLayers(suggestedTransitionLayerCount);
+
+ var bottomExposureTime = SlicerFile.LastBottomLayer?.ExposureTime ?? SlicerFile.BottomExposureTime;
+ var exposureTime = SlicerFile.TransitionLayerCount > 0
+ ? SlicerFile[SlicerFile.BottomLayerCount + SlicerFile.TransitionLayerCount - 1].ExposureTime
+ : SlicerFile[SlicerFile.BottomLayerCount].ExposureTime;
return
- $"{Title}: ({SlicerFile.BottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s) » ({SlicerFile.BottomExposureTime}s » {(suggestedTransitionDecrementTime <= 0 || suggestedTransitionLayerCount == 0 ? string.Empty : $"[-{suggestedTransitionDecrementTime}s/{suggestedTransitionLayerCount} layers] » ")}{SlicerFile.ExposureTime}s)";
+ $"{Title}: ({bottomExposureTime}s » {(actualTransitionDecrementTime <= 0 || actualTransitionLayerCount == 0 ? string.Empty : $"[-{actualTransitionDecrementTime}s/{actualTransitionLayerCount} layers] » ")}{exposureTime}s) » ({bottomExposureTime}s » {(suggestedTransitionDecrementTime <= 0 || suggestedTransitionLayerCount == 0 ? string.Empty : $"[-{suggestedTransitionDecrementTime}s/{suggestedTransitionLayerCount} layers] » ")}{exposureTime}s)";
}
}
public ushort TransitionLayerCount =>
(ushort)Math.Min(
Math.Clamp(
- SlicerFile.GetTransitionLayerCount((float)_transitionStepTime, false),
+ SlicerFile.GetTransitionLayerCountFromLayers((float)_transitionStepTime, false),
_minimumTransitionLayerCount,
_maximumTransitionLayerCount)
, SlicerFile.MaximumPossibleTransitionLayerCount);
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 9808d0b7..8501c6e9 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -10,7 +10,7 @@
https://github.com/sn4k3/UVtools
https://github.com/sn4k3/UVtools
MSLA/DLP, file analysis, calibration, repair, conversion and manipulation
- 3.11.2
+ 3.12.0
Copyright © 2020 PTRTECH
UVtools.png
AnyCPU;x64
@@ -78,18 +78,19 @@
+
-
+
-
+
diff --git a/UVtools.Installer/Code/HeatGeneratedFileList.wxs b/UVtools.Installer/Code/HeatGeneratedFileList.wxs
index cdda20b9..2a0428bc 100644
--- a/UVtools.Installer/Code/HeatGeneratedFileList.wxs
+++ b/UVtools.Installer/Code/HeatGeneratedFileList.wxs
@@ -227,6 +227,9 @@
+
+
+
@@ -320,8 +323,8 @@
-
-
+
+
@@ -1031,6 +1034,9 @@
+
+
+
@@ -1662,6 +1668,7 @@
+
@@ -1693,7 +1700,7 @@
-
+
@@ -1929,6 +1936,7 @@
+
diff --git a/UVtools.Installer/Code/Product.wxs b/UVtools.Installer/Code/Product.wxs
index 7117eaed..0b0f183d 100644
--- a/UVtools.Installer/Code/Product.wxs
+++ b/UVtools.Installer/Code/Product.wxs
@@ -153,7 +153,7 @@
-
+
diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs
index 2df8907f..55be5b63 100644
--- a/UVtools.WPF/MainWindow.Issues.cs
+++ b/UVtools.WPF/MainWindow.Issues.cs
@@ -239,6 +239,7 @@ public async void RemoveRepairIssues(IEnumerable issues, bool promptC
{
Parallel.ForEach(processParallelIssues, CoreSettings.GetParallelOptions(Progress), layerIssues =>
{
+ Progress.PauseIfRequested();
using (var image = SlicerFile[layerIssues.Key].LayerMat)
{
var bytes = image.GetDataByteSpan();
diff --git a/UVtools.WPF/MainWindow.Progress.cs b/UVtools.WPF/MainWindow.Progress.cs
index 2d81e31e..c7fedc5d 100644
--- a/UVtools.WPF/MainWindow.Progress.cs
+++ b/UVtools.WPF/MainWindow.Progress.cs
@@ -52,12 +52,21 @@ public void InitProgress()
};
}
+ public void ProgressOnClickPauseResume()
+ {
+ if (!Progress.CanCancel) return;
+ DialogResult = DialogResults.Cancel;
+ Progress.CanCancel = false;
+ Progress.TokenSource.Cancel();
+ }
+
public void ProgressOnClickCancel()
{
if (!Progress.CanCancel) return;
DialogResult = DialogResults.Cancel;
Progress.CanCancel = false;
Progress.TokenSource.Cancel();
+ Progress.IsPaused = false;
}
public void ProgressShow(string title, bool canCancel = true)
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index aec02024..888442f6 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -2245,18 +2245,26 @@
-
-
+
+
+
+
+
+
+
+
+ RowDefinitions="30" ColumnDefinitions="*,40,100">
-
+
+ Text="Cancel"
+ Icon="fa-regular fa-circle-stop"/>
- await this.MessageBoxError(exception.Message, $"Unable to eject the drive {removableDrive.Name}"));
+ HandleException(ex, $"Unable to eject the drive { removableDrive.Name}");
}
return false;
@@ -1058,13 +1056,9 @@ await Task.Run(() =>
SlicerFile.ChangeLayersCompressionMethod(Settings.General.LayerCompressionCodec, Progress);
return true;
}
- catch (OperationCanceledException)
+ catch (Exception ex)
{
- }
- catch (Exception exception)
- {
- Dispatcher.UIThread.InvokeAsync(async () =>
- await this.MessageBoxError(exception.ToString(), "Error while converting layers"));
+ HandleException(ex, "Error while converting layers");
}
return false;
@@ -1348,13 +1342,12 @@ async void ProcessFile(string fileName, FileFormat.FileDecodeType fileDecodeType
SlicerFile.Decode(fileName, fileDecodeType, Progress);
return true;
}
- catch (OperationCanceledException) {}
- catch (Exception exception)
+ catch (Exception ex)
{
- Dispatcher.UIThread.InvokeAsync(async () =>
- await this.MessageBoxError(exception.ToString(), "Error opening the file"));
+ HandleException(ex, "Error opening the file");
}
+
return false;
}, Progress.Token);
@@ -1474,14 +1467,9 @@ await this.MessageBoxError("It seems this file has no layers. Possible causes c
convertedFile = SlicerFile.Convert(convertToFormat, outputFile, 0, Progress);
return true;
}
- catch (OperationCanceledException)
+ catch (Exception ex)
{
- }
- catch (Exception exception)
- {
- Dispatcher.UIThread.InvokeAsync(async () =>
- await this.MessageBoxError(exception.ToString(),
- "Error while converting the file"));
+ HandleException(ex, "Error while converting the file");
}
return false;
@@ -1857,7 +1845,7 @@ private async void ConvertToOnTapped(object? sender, RoutedEventArgs e)
}
Dispatcher.UIThread.InvokeAsync(async () =>
- await this.MessageBoxError($"{extraMessage}{ex}", "Convertion unsuccessful"));
+ await this.MessageBoxError($"{extraMessage}{ex}", "Conversion unsuccessful"));
}
return false;
@@ -1940,13 +1928,9 @@ public async Task SaveFile(string filepath = null, bool ignoreOverwriteWar
SlicerFile.SaveAs(filepath, Progress);
return true;
}
- catch (OperationCanceledException)
- {
- }
catch (Exception ex)
{
- Dispatcher.UIThread.InvokeAsync(async () =>
- await this.MessageBoxError(ex.ToString(), "Error while saving the file"));
+ HandleException(ex, "Error while saving the file");
}
return false;
@@ -1997,15 +1981,10 @@ await Task.Factory.StartNew(() =>
try
{
SlicerFile.Extract(finalPath, true, true, Progress);
- }
- catch (OperationCanceledException)
- {
-
}
catch (Exception ex)
{
- Dispatcher.UIThread.InvokeAsync(async () =>
- await this.MessageBoxError(ex.ToString(), "Error while try extracting the file"));
+ HandleException(ex, "Error while try extracting the file");
}
});
@@ -2141,55 +2120,9 @@ public async Task RunOperation(Operation baseOperation)
{
return baseOperation.Execute(Progress);
}
- catch (OperationCanceledException)
- {
- }
- catch (MessageException ex)
- {
- Dispatcher.UIThread.InvokeAsync(async ()
- => await this.MessageBoxError(ex.Message, $"{baseOperation.Title} Error"));
- }
- catch (AggregateException ex)
- {
- var sb = new StringBuilder();
-
- var title = $"{baseOperation.Title} Error";
-
- if (ex.InnerExceptions.Count > 0)
- {
- if (ex.InnerExceptions.Count == 1)
- {
- if (ex.InnerExceptions[0] is MessageException messageException)
- {
- if (messageException.Title is not null) title = messageException.Title;
- sb.AppendLine(ex.InnerExceptions[0].Message);
- }
- else
- {
- sb.AppendLine(ex.InnerExceptions[0].ToString());
- }
- }
- else
- {
- for (var i = 0; i < ex.InnerExceptions.Count; i++)
- {
- if (ex.InnerExceptions[i] is MessageException {Title: { }} messageException)
- {
- title = messageException.Title;
- }
-
- if (i > 0) sb.AppendLine("---------------");
- sb.AppendLine($"({i + 1}) {(ex.InnerExceptions[i] is MessageException ? ex.InnerExceptions[i].Message : ex.InnerExceptions[i].ToString())}");
- }
- }
- }
-
- Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(sb.ToString(), title));
- }
catch (Exception ex)
{
- Dispatcher.UIThread.InvokeAsync(async ()
- => await this.MessageBoxError(ex.ToString(), $"{baseOperation.Title} Error"));
+ HandleException(ex, $"{baseOperation.Title} Error");
}
return false;
@@ -2346,7 +2279,58 @@ private async void MenuFileOpenRecentItemOnClick(object? sender, RoutedEventArgs
#endregion
-
+ #region Error Handling
+ public void HandleException(Exception ex, string? title = null)
+ {
+ switch (ex)
+ {
+ case OperationCanceledException:
+ return;
+ case MessageException msgEx:
+ Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(msgEx.Message, title));
+ return;
+ case AggregateException aggEx:
+ {
+ var sb = new StringBuilder();
+
+ if (aggEx.InnerExceptions.Count > 0)
+ {
+ if (aggEx.InnerExceptions.Count == 1)
+ {
+ if (aggEx.InnerExceptions[0] is MessageException messageException)
+ {
+ if (messageException.Title is not null) title = messageException.Title;
+ sb.AppendLine(aggEx.InnerExceptions[0].Message);
+ }
+ else
+ {
+ sb.AppendLine(aggEx.InnerExceptions[0].ToString());
+ }
+ }
+ else
+ {
+ for (var i = 0; i < aggEx.InnerExceptions.Count; i++)
+ {
+ if (aggEx.InnerExceptions[i] is MessageException { Title: { } } messageException)
+ {
+ title = messageException.Title;
+ }
+
+ if (i > 0) sb.AppendLine("---------------");
+ sb.AppendLine($"({i + 1}) {(aggEx.InnerExceptions[i] is MessageException ? aggEx.InnerExceptions[i].Message : aggEx.InnerExceptions[i].ToString())}");
+ }
+ }
+ }
+
+ Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(sb.ToString(), title));
+ return;
+ }
+ default:
+ Dispatcher.UIThread.InvokeAsync(async () => await this.MessageBoxError(ex.ToString(), title));
+ return;
+ }
+ }
+ #endregion
#endregion
}
\ No newline at end of file
diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs
index 47dd8119..d3ad780a 100644
--- a/UVtools.WPF/Program.cs
+++ b/UVtools.WPF/Program.cs
@@ -222,7 +222,8 @@ private static void HandleUnhandledException(string category, Exception ex)
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
- .With(new Win32PlatformOptions { AllowEglInitialization = false/*, UseWgl = true*/})
+ .With(new SkiaOptions {MaxGpuResourceSizeBytes = 256_000_000 })
+ .With(new Win32PlatformOptions { AllowEglInitialization = true/*, UseWgl = true*/})
.With(new X11PlatformOptions { UseGpu = true/*, UseEGL = true*/ })
.With(new MacOSPlatformOptions { ShowInDock = true })
.With(new AvaloniaNativePlatformOptions { UseGpu = true })
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index 0f103cf4..6a2f013c 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -12,7 +12,7 @@
LICENSE
https://github.com/sn4k3/UVtools
Git
- 3.11.2
+ 3.12.0
AnyCPU;x64
UVtools.png
README.md
@@ -48,9 +48,9 @@
-
-
-
+
+
+
diff --git a/documentation/Goo-Format-Spec-V1.2.pdf b/documentation/Goo-Format-Spec-V1.2.pdf
new file mode 100644
index 00000000..da0aab7b
Binary files /dev/null and b/documentation/Goo-Format-Spec-V1.2.pdf differ
diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml
index 1770e966..251151b3 100644
--- a/documentation/UVtools.Core.xml
+++ b/documentation/UVtools.Core.xml
@@ -2581,6 +2581,16 @@
Gets the last bottom layer
+
+
+ Gets the first transition layer
+
+
+
+
+ Gets the last transition layer
+
+
Gets the first normal layer
@@ -3436,6 +3446,13 @@
Number of transition layers
Seconds
+
+
+ Gets the transition step time from and first normal layer after the last transition layer, value is returned as positive from normal perspective and logic (Longer - shorter)
+
+ Number of transition layers
+ Seconds
+
Gets the transition step time from and , value is returned as positive from normal perspective and logic (Longer - shorter)
@@ -3461,6 +3478,15 @@
Midpoint rounding method
Transition layer count
+
+
+ Gets the transition layer count based on and first normal layer after the last transition layer
+
+ Step decrement time in seconds
+ True if transition layer count can't be higher than supported by the file, otherwise set to false to not look at possible file layers
+ Midpoint rounding method
+ Transition layer count
+
Re-set exposure time to the transition layers
@@ -3941,6 +3967,39 @@
Gets the Y dimension of the preview image, in pixels.
+
+
+ 0: Light off delay mode | 1:Wait time mode
+
+
+
+
+ 0: Normal mode
+ 1: Advance mode, printing use the value of "Layer Definition Content"
+
+
+
+
+ // The volume of all parts. unit: mm3
+
+
+
+
+ The weight of all parts. unit: g
+
+
+
+
+ 0:The range of pixel's gray value is from 0x0 ~ 0xf
+ 1:The range of pixel's gray value is from 0x0 ~ 0xff
+
+
+
+
+ 0: reserve
+ 1: current layer pause printing
+
+
Gets the file tag = MKSDLP
@@ -6697,6 +6756,17 @@
Use the alternate pattern every layers
+
+
+ Blocks the current thread until the current WaitHandle receives a signal.
+
+ true if the current instance receives a signal. If the current instance is never signaled, WaitOne() never returns.
+
+
+
+ Blocks or cancels the current thread until the current WaitHandle receives a signal.
+
+
Gets or sets if operation can be cancelled