From aaa9c68a66866b658619ffcb1c78b15a339552bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 6 May 2023 16:12:08 +0100 Subject: [PATCH] v3.13.2 - **UVJ:** - (Add) Properties: BottomWaitTimeBeforeCure, WaitTimeBeforeCure, BottomWaitTimeAfterCure, WaitTimeAfterCure, BottomWaitTimeAfterLift, WaitTimeAfterLift - (Add) Vendor key to the configuration to be able to save custom key-values from other softwares (#687) - (Add) Layers properties: `CompletionTime`, `CompletionTimeStr`, `StartTime`, `StartTimeStr`, `EndTime`, `EndTimeStr` (#698) - (Add) PrusaSlicer printer: Creality Halot Mage and Mage Pro - (Add) Tool - Blur: Stack blur - (Change) Tool - Timelapse: Increase wait time from 100s to 1000s maximum - (Change) Settings: Allow to combine start maximized with restore windows position and size (#695) - (Fix) File formats: Parse transition layer count from layers now only take into account decreasing times related to the previous layer --- CHANGELOG.md | 12 ++ CREDITS.md | 3 +- .../printer/Creality Halot Mage CL-103L.ini | 42 +++++ .../Creality Halot Mage Pro CL-103.ini | 42 +++++ RELEASE_NOTES.md | 15 +- Scripts/ImportPrusaSlicerData.ps1 | 104 +----------- UVtools.Core/Extensions/EmguExtensions.cs | 26 ++- UVtools.Core/FileFormats/FileFormat.cs | 20 +-- UVtools.Core/FileFormats/UVJFile.cs | 157 +++++++++++++----- UVtools.Core/Layers/Layer.cs | 86 +++++++++- .../NullTerminatedUintStringBigEndian.cs | 2 + UVtools.Core/Objects/UInt24BigEndian.cs | 2 + UVtools.Core/Operations/OperationBlur.cs | 13 +- UVtools.Core/Printer/Machine.cs | 2 + UVtools.Core/UVtools.Core.csproj | 6 +- .../Controls/Tools/ToolTimelapseControl.axaml | 2 +- UVtools.WPF/MainWindow.axaml.cs | 29 ++-- UVtools.WPF/Program.cs | 43 ++--- UVtools.WPF/UVtools.WPF.csproj | 2 +- UVtools.WPF/Windows/SettingsWindow.axaml | 2 - documentation/UVtools.Core.xml | 57 +++++++ wiki/UVtools_Compression.xlsx | Bin 0 -> 19336 bytes 22 files changed, 451 insertions(+), 216 deletions(-) create mode 100644 PrusaSlicer/printer/Creality Halot Mage CL-103L.ini create mode 100644 PrusaSlicer/printer/Creality Halot Mage Pro CL-103.ini create mode 100644 wiki/UVtools_Compression.xlsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 075759b3..ee402a45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 06/05/2023 - v3.13.2 + +- **UVJ:** + - (Add) Properties: BottomWaitTimeBeforeCure, WaitTimeBeforeCure, BottomWaitTimeAfterCure, WaitTimeAfterCure, BottomWaitTimeAfterLift, WaitTimeAfterLift + - (Add) Vendor key to the configuration to be able to save custom key-values from other softwares (#687) +- (Add) Layers properties: `CompletionTime`, `CompletionTimeStr`, `StartTime`, `StartTimeStr`, `EndTime`, `EndTimeStr` (#698) +- (Add) PrusaSlicer printer: Creality Halot Mage and Mage Pro +- (Add) Tool - Blur: Stack blur +- (Change) Tool - Timelapse: Increase wait time from 100s to 1000s maximum +- (Change) Settings: Allow to combine start maximized with restore windows position and size (#695) +- (Fix) File formats: Parse transition layer count from layers now only take into account decreasing times related to the previous layer + ## 27/04/2023 - v3.13.1 - (Change) `Layer.IsBottomLayer` no longer calculate the value using the position of the layer, a new property `IsBottomLayerByHeight` is now used to get that result diff --git a/CREDITS.md b/CREDITS.md index 644259aa..8a2b59ab 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -75,4 +75,5 @@ - James F Hammond - Steven Woodward - Piotr Czerkasow -- Kevin Bullmann \ No newline at end of file +- Kevin Bullmann +- Robert Redden \ No newline at end of file diff --git a/PrusaSlicer/printer/Creality Halot Mage CL-103L.ini b/PrusaSlicer/printer/Creality Halot Mage CL-103L.ini new file mode 100644 index 00000000..6c9e04cf --- /dev/null +++ b/PrusaSlicer/printer/Creality Halot Mage CL-103L.ini @@ -0,0 +1,42 @@ +# generated by PrusaSlicer 2.5.2+win64 on 2023-05-05 at 21:41:15 UTC +absolute_correction = 0 +area_fill = 50 +bed_custom_model = +bed_custom_texture = +bed_shape = 0x0,228.1x0,228.1x128.3,0x128.3 +default_sla_material_profile = Prusa Orange Tough 0.05 +default_sla_print_profile = 0.05 Normal +display_height = 128.304 +display_mirror_x = 0 +display_mirror_y = 0 +display_orientation = landscape +display_pixels_x = 7680 +display_pixels_y = 4320 +display_width = 228.096 +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 = 230 +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_CREALITY\nPRINTER_MODEL_HALOT-MAGE\nFILEFORMAT_ENCRYPTED.CTB\n\nSTART_CUSTOM_VALUES\nWaitTimeBeforeCure_2\nBottomLiftHeight_7\nLiftHeight_6\nBottomLiftSpeed_80\nLiftSpeed_80\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 = 116x116,90x290,90x290 diff --git a/PrusaSlicer/printer/Creality Halot Mage Pro CL-103.ini b/PrusaSlicer/printer/Creality Halot Mage Pro CL-103.ini new file mode 100644 index 00000000..a6b5ca30 --- /dev/null +++ b/PrusaSlicer/printer/Creality Halot Mage Pro CL-103.ini @@ -0,0 +1,42 @@ +# generated by PrusaSlicer 2.5.2+win64 on 2023-05-05 at 21:41:31 UTC +absolute_correction = 0 +area_fill = 50 +bed_custom_model = +bed_custom_texture = +bed_shape = 0x0,228.1x0,228.1x128.3,0x128.3 +default_sla_material_profile = Prusa Orange Tough 0.05 +default_sla_print_profile = 0.05 Normal +display_height = 128.304 +display_mirror_x = 0 +display_mirror_y = 0 +display_orientation = landscape +display_pixels_x = 7680 +display_pixels_y = 4320 +display_width = 228.096 +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 = 230 +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_CREALITY\nPRINTER_MODEL_HALOT-MAGE-PRO\nFILEFORMAT_ENCRYPTED.CTB\n\nSTART_CUSTOM_VALUES\nWaitTimeBeforeCure_2\nBottomLiftHeight_7\nLiftHeight_6\nBottomLiftSpeed_80\nLiftSpeed_80\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 = 116x116,90x290,90x290 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b3f48783..b73277d6 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,7 +1,10 @@ -- (Change) `Layer.IsBottomLayer` no longer calculate the value using the position of the layer, a new property `IsBottomLayerByHeight` is now used to get that result -- (Improvement) Tool - Double exposure: Increase the bottom layer count per cloned bottom layer -- (Improvement) Calibration - Exposure time finder: Set the absolute bottom layer count accordingly when also testing for bottom time -- (Improvement) Goo: Enforce Wait times or Light-off-delay flag based on property set -- (Fix) AnyCubic and Goo: `PerLayerSetting` flag was set inverted causing printer not to follow layer settings when it should and also the otherwise (#689) -- (Fix) Tool - Scripting: Prevent from reload UI multiple times when using profiles (#694) +- **UVJ:** + - (Add) Properties: BottomWaitTimeBeforeCure, WaitTimeBeforeCure, BottomWaitTimeAfterCure, WaitTimeAfterCure, BottomWaitTimeAfterLift, WaitTimeAfterLift + - (Add) Vendor key to the configuration to be able to save custom key-values from other softwares (#687) +- (Add) Layers properties: `CompletionTime`, `CompletionTimeStr`, `StartTime`, `StartTimeStr`, `EndTime`, `EndTimeStr` (#698) +- (Add) PrusaSlicer printer: Creality Halot Mage and Mage Pro +- (Add) Tool - Blur: Stack blur +- (Change) Tool - Timelapse: Increase wait time from 100s to 1000s maximum +- (Change) Settings: Allow to combine start maximized with restore windows position and size (#695) +- (Fix) File formats: Parse transition layer count from layers now only take into account decreasing times related to the previous layer diff --git a/Scripts/ImportPrusaSlicerData.ps1 b/Scripts/ImportPrusaSlicerData.ps1 index 3ef558e5..c8ba3781 100644 --- a/Scripts/ImportPrusaSlicerData.ps1 +++ b/Scripts/ImportPrusaSlicerData.ps1 @@ -1,109 +1,11 @@ -Set-Location $PSScriptRoot\.. +Write-Output 'PrusaSlicer Printers Instalation' +Write-Output 'This will replace printers, all changes will be discarded' $input_dir = "$Env:AppData\PrusaSlicer" -$output_dir = 'PrusaSlicer' +$output_dir = Resolve-Path "$PSScriptRoot\..\PrusaSlicer" | select -ExpandProperty Path $print_dir = 'sla_print' $printer_dir = 'printer' -#$printers = -# 'UVtools Prusa SL1.ini', -# 'UVtools Prusa SL1S Speed.ini', -# 'EPAX E6 Mono.ini', -# 'EPAX E10 Mono.ini', -# 'EPAX E10 5K.ini', -# 'EPAX E10 8K.ini', -# 'EPAX X10 8K.ini', -# 'EPAX X1.ini', -# 'EPAX X1 4KS.ini', -# 'EPAX X1K 2K Mono.ini', -# 'EPAX X10.ini', -# 'EPAX X10 4K Mono.ini', -# 'EPAX X10 5K.ini', -# 'EPAX X133 4K Mono.ini', -# 'EPAX X133 6K.ini', -# 'EPAX X156 4K Color.ini', -# 'EPAX DX1 Pro.ini', -# 'EPAX DX10 Pro 5K.ini', -# 'EPAX DX10 Pro 8K.ini', -# 'Zortrax Inkspire.ini', -# 'Nova3D Elfin.ini', -# 'Nova3D Elfin2.ini', -# 'Nova3D Elfin2 Mono SE.ini', -# 'Nova3D Elfin3 Mini.ini', -# 'Nova3D Bene4.ini', -# 'Nova3D Bene4 Mono.ini', -# 'Nova3D Bene5.ini', -# 'Nova3D Whale.ini', -# 'Nova3D Whale2.ini', -# 'AnyCubic Photon.ini', -# 'AnyCubic Photon S.ini', -# 'AnyCubic Photon Zero.ini', -# 'AnyCubic Photon X.ini', -# 'AnyCubic Photon Ultra.ini', -# 'AnyCubic Photon Mono.ini', -# 'AnyCubic Photon Mono 4K.ini', -# 'AnyCubic Photon Mono SE.ini', -# 'AnyCubic Photon Mono X.ini', -# 'AnyCubic Photon Mono X 6K.ini', -# 'AnyCubic Photon Mono SQ.ini', -# 'Elegoo Mars.ini', -# 'Elegoo Mars 2 Pro.ini', -# 'Elegoo Mars 3.ini', -# 'Elegoo Mars C.ini', -# 'Elegoo Saturn.ini', -# 'Elegoo Jupiter.ini', -# 'Peopoly Phenom.ini', -# 'Peopoly Phenom L.ini', -# 'Peopoly Phenom Noir.ini', -# 'Peopoly Phenom XXL.ini', -# 'QIDI Shadow5.5.ini', -# 'QIDI Shadow6.0 Pro.ini', -# 'QIDI S-Box.ini', -# 'QIDI I-Box Mono.ini', -# 'Phrozen Shuffle.ini', -# 'Phrozen Shuffle Lite.ini', -# 'Phrozen Shuffle XL.ini', -# 'Phrozen Shuffle XL Lite.ini', -# 'Phrozen Shuffle 16.ini', -# 'Phrozen Shuffle 4K.ini', -# 'Phrozen Sonic.ini', -# 'Phrozen Sonic 4K.ini', -# 'Phrozen Sonic Mighty 4K.ini', -# 'Phrozen Sonic Mini.ini', -# 'Phrozen Sonic Mini 4K.ini', -# 'Phrozen Sonic Mini 8K.ini', -# 'Phrozen Transform.ini', -# 'Phrozen Sonic Mega 8K.ini', -# 'Kelant S400.ini', -# 'Wanhao D7.ini', -# 'Wanhao D8.ini', -# 'Wanhao CGR Mini Mono.ini', -# 'Wanhao CGR Mono.ini', -# 'Creality LD-002R.ini', -# 'Creality LD-002H.ini', -# 'Creality LD-006.ini', -# 'Creality HALOT-ONE CL-60.ini', -# 'Creality HALOT-SKY CL-89.ini', -# 'Creality HALOT-MAX CL-133.ini', -# 'Voxelab Polaris 5.5.ini', -# 'Voxelab Proxima 6.ini', -# 'Voxelab Ceres 8.9.ini', -# 'Longer Orange 10.ini', -# 'Longer Orange 30.ini', -# 'Longer Orange 120.ini', -# 'Longer Orange 4K.ini', -# 'Uniz IBEE.ini', -# 'FlashForge Explorer MAX.ini', -# 'FlashForge Focus 8.9.ini', -# 'FlashForge Focus 13.3.ini', -# 'FlashForge Foto 6.0.ini', -# 'FlashForge Foto 8.9.ini', -# 'FlashForge Foto 13.3.ini', -# 'FlashForge Hunter.ini' -#; - -Write-Output 'PrusaSlicer Printers Instalation' -Write-Output 'This will replace printers, all changes will be discarded' Write-Output $input_dir Write-Output $output_dir diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index ff742116..1e383622 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -201,7 +201,7 @@ public static unsafe Span2D GetDataSpan2D(this Mat mat) { var step = mat.GetRealStep(); if (!mat.IsSubmatrix) return new(mat.DataPointer.ToPointer(), mat.Height, step, 0); - return new(mat.DataPointer.ToPointer(), mat.Height, step, mat.Step / mat.ElementSize - step); + return new(mat.DataPointer.ToPointer(), mat.Height, step, mat.Step / mat.DepthToByteCount() - step); } /// @@ -238,7 +238,7 @@ public static unsafe Span GetDataSpan(this Mat mat, int length = 0, int of { if (mat.IsSubmatrix) { - length = mat.Step * (mat.Height - 1) + mat.GetRealStep(); + length = mat.Step / mat.DepthToByteCount() * (mat.Height - 1) + mat.GetRealStep(); } else { @@ -364,6 +364,28 @@ public static void FillSpan(this Mat mat, Point position, int length, byte color #region Get/Set methods + /// + /// Gets the number of bytes this uses per data type (Depth) + /// + /// + /// + /// + public static byte DepthToByteCount(this Mat mat) + { + return mat.Depth switch + { + DepthType.Default => 1, + DepthType.Cv8U => 1, + DepthType.Cv8S => 1, + DepthType.Cv16U => 2, + DepthType.Cv16S => 2, + DepthType.Cv32S => 4, + DepthType.Cv32F => 4, + DepthType.Cv64F => 8, + _ => throw new ArgumentOutOfRangeException() + }; + } + /// /// Step return the original Mat step, if ROI step still from original matrix which lead to errors. /// Use this to get the real step size diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 0092ff72..2924ffc0 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -2843,24 +2843,7 @@ public float PrintTimeComputed break; } - var motorTime = layer.CalculateMotorMovementTime(); - time += layer.WaitTimeBeforeCure + layer.ExposureTime + layer.WaitTimeAfterCure + layer.WaitTimeAfterLift; - if (SupportsGCode) - { - time += motorTime; - if (layer.WaitTimeBeforeCure <= 0) - { - time += layer.LightOffDelay; - } - } - else - { - time += motorTime > layer.LightOffDelay ? motorTime : layer.LightOffDelay; - } - /*if (lightOffDelay >= layer.LightOffDelay) - time += lightOffDelay; - else - time += layer.LightOffDelay;*/ + time += layer.CalculateCompletionTime(); } } @@ -4053,6 +4036,7 @@ public ushort ParseTransitionLayerCountFromLayers() ushort count = 0; for (uint layerIndex = BottomLayerCount; layerIndex < LastLayerIndex; layerIndex++) { + if (this[layerIndex].ExposureTime < this[layerIndex + 1].ExposureTime) break; // Increasing time related to previous layer, we want decreasing time only if (Math.Abs(this[layerIndex].ExposureTime - this[layerIndex + 1].ExposureTime) < 0.009f) break; // First equal layer, transition ended count++; } diff --git a/UVtools.Core/FileFormats/UVJFile.cs b/UVtools.Core/FileFormats/UVJFile.cs index 8f2172ce..aeb5a8a2 100644 --- a/UVtools.Core/FileFormats/UVJFile.cs +++ b/UVtools.Core/FileFormats/UVJFile.cs @@ -56,15 +56,18 @@ public override string ToString() } } - public sealed class Exposure + public class Exposure { - public float LightOnTime { get; set; } + public float WaitTimeBeforeCure { get; set; } public float LightOffTime { get; set; } + public float LightOnTime { get; set; } public byte LightPWM { get; set; } = DefaultLightPWM; + public float WaitTimeAfterCure { get; set; } public float LiftHeight { get; set; } = DefaultLiftHeight; public float LiftSpeed { get; set; } = DefaultLiftSpeed; public float LiftHeight2 { get; set; } = DefaultLiftHeight2; public float LiftSpeed2 { get; set; } = DefaultLiftSpeed2; + public float WaitTimeAfterLift { get; set; } public float RetractHeight { get; set; } public float RetractSpeed { get; set; } = DefaultRetractSpeed; public float RetractHeight2 { get; set; } @@ -72,28 +75,17 @@ public sealed class Exposure public override string ToString() { - return $"{nameof(LightOnTime)}: {LightOnTime}, {nameof(LightOffTime)}: {LightOffTime}, {nameof(LightPWM)}: {LightPWM}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(RetractHeight)}: {RetractHeight}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}"; + return $"{nameof(WaitTimeBeforeCure)}: {WaitTimeBeforeCure}, {nameof(LightOffTime)}: {LightOffTime}, {nameof(LightOnTime)}: {LightOnTime}, {nameof(LightPWM)}: {LightPWM}, {nameof(WaitTimeAfterCure)}: {WaitTimeAfterCure}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(WaitTimeAfterLift)}: {WaitTimeAfterLift}, {nameof(RetractHeight)}: {RetractHeight}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}"; } } - public sealed class Bottom + public sealed class Bottom : Exposure { - public float LightOffTime { get; set; } - public float LightOnTime { get; set; } - public byte LightPWM { get; set; } = DefaultBottomLightPWM; - public float LiftHeight { get; set; } = DefaultBottomLiftHeight; - public float LiftSpeed { get; set; } = DefaultBottomLiftSpeed; - public float LiftHeight2 { get; set; } = DefaultBottomLiftHeight2; - public float LiftSpeed2 { get; set; } = DefaultBottomLiftSpeed2; - public float RetractHeight { get; set; } - public float RetractSpeed { get; set; } = DefaultBottomRetractSpeed; - public float RetractHeight2 { get; set; } - public float RetractSpeed2 { get; set; } = DefaultBottomRetractSpeed2; public ushort Count { get; set; } public override string ToString() { - return $"{nameof(LightOffTime)}: {LightOffTime}, {nameof(LightOnTime)}: {LightOnTime}, {nameof(LightPWM)}: {LightPWM}, {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(Count)}: {Count}"; + return $"{base.ToString()}, {nameof(Count)}: {Count}"; } } @@ -129,12 +121,15 @@ public override string ToString() public void SetFrom(Layer layer) { Z = layer.PositionZ; + Exposure.WaitTimeBeforeCure = layer.WaitTimeBeforeCure; Exposure.LightOffTime = layer.LightOffDelay; Exposure.LightOnTime = layer.ExposureTime; + Exposure.WaitTimeAfterCure = layer.WaitTimeAfterCure; Exposure.LiftHeight = layer.LiftHeight; Exposure.LiftSpeed = layer.LiftSpeed; Exposure.LiftHeight2 = layer.LiftHeight2; Exposure.LiftSpeed2 = layer.LiftSpeed2; + Exposure.WaitTimeAfterLift = layer.WaitTimeAfterLift; Exposure.RetractHeight = layer.RetractHeight; Exposure.RetractSpeed = layer.RetractSpeed; Exposure.RetractHeight2 = layer.RetractHeight2; @@ -145,12 +140,15 @@ public void SetFrom(Layer layer) public void CopyTo(Layer layer) { layer.PositionZ = Z; + layer.WaitTimeBeforeCure = Exposure.WaitTimeBeforeCure; layer.LightOffDelay = Exposure.LightOffTime; layer.ExposureTime = Exposure.LightOnTime; + layer.WaitTimeAfterCure = Exposure.WaitTimeAfterCure; layer.LiftHeight = Exposure.LiftHeight; layer.LiftSpeed = Exposure.LiftSpeed; layer.LiftHeight2 = Exposure.LiftHeight2; layer.LiftSpeed2 = Exposure.LiftSpeed2; + layer.WaitTimeAfterLift = Exposure.WaitTimeAfterLift; layer.RetractSpeed = Exposure.RetractSpeed; layer.RetractHeight2 = Exposure.RetractHeight2; layer.RetractSpeed2 = Exposure.RetractSpeed2; @@ -163,10 +161,10 @@ public sealed class Properties public Size Size { get; set; } = new (); public Exposure Exposure { get; set; } = new (); public Bottom Bottom { get; set; } = new (); - /*public Dictionary Vendor { get; set; } = new() + public Dictionary Vendor { get; set; } = new() { - {About.Software, new UVtoolsVendor()} - };*/ + //{About.Software, new UVtoolsVendor()} + }; public byte AntiAliasLevel { get; set; } = 1; @@ -205,50 +203,53 @@ public override string ToString() 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.RetractHeight2, - PrintParameterModifier.BottomRetractSpeed2, + PrintParameterModifier.RetractHeight2, PrintParameterModifier.RetractSpeed2, PrintParameterModifier.BottomLightPWM, - PrintParameterModifier.LightPWM, + 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.BottomLightPWM, - PrintParameterModifier.LightPWM, + PrintParameterModifier.LightPWM }; public override System.Drawing.Size[]? ThumbnailsOriginalSize { get; } = @@ -314,13 +315,31 @@ public override ushort BottomLayerCount public override float BottomLightOffDelay { get => JsonSettings.Properties.Bottom.LightOffTime; - set => base.BottomLightOffDelay = JsonSettings.Properties.Bottom.LightOffTime = (float)Math.Round(value, 2); + set + { + base.BottomLightOffDelay = JsonSettings.Properties.Bottom.LightOffTime = (float)Math.Round(value, 2); + if (value > 0) + { + BottomWaitTimeBeforeCure = 0; + BottomWaitTimeAfterCure = 0; + BottomWaitTimeAfterLift = 0; + } + } } public override float LightOffDelay { get => JsonSettings.Properties.Exposure.LightOffTime; - set => base.LightOffDelay = JsonSettings.Properties.Exposure.LightOffTime = (float)Math.Round(value, 2); + set + { + base.LightOffDelay = JsonSettings.Properties.Exposure.LightOffTime = (float)Math.Round(value, 2); + if (value > 0) + { + WaitTimeBeforeCure = 0; + WaitTimeAfterCure = 0; + WaitTimeAfterLift = 0; + } + } } public override float BottomWaitTimeBeforeCure @@ -328,8 +347,12 @@ public override float BottomWaitTimeBeforeCure get => base.BottomWaitTimeBeforeCure; set { - SetBottomLightOffDelay(value); - base.BottomWaitTimeBeforeCure = value; + base.BottomWaitTimeBeforeCure = JsonSettings.Properties.Bottom.WaitTimeBeforeCure = (float)Math.Round(value, 2); + if (value > 0) + { + BottomLightOffDelay = 0; + LightOffDelay = 0; + } } } @@ -338,8 +361,12 @@ public override float WaitTimeBeforeCure get => base.WaitTimeBeforeCure; set { - SetNormalLightOffDelay(value); - base.WaitTimeBeforeCure = value; + base.WaitTimeBeforeCure = JsonSettings.Properties.Exposure.WaitTimeBeforeCure = (float)Math.Round(value, 2); + if (value > 0) + { + BottomLightOffDelay = 0; + LightOffDelay = 0; + } } } @@ -355,6 +382,34 @@ public override float ExposureTime set => base.ExposureTime = JsonSettings.Properties.Exposure.LightOnTime = (float)Math.Round(value, 2); } + public override float BottomWaitTimeAfterCure + { + get => base.BottomWaitTimeAfterCure; + set + { + base.BottomWaitTimeAfterCure = JsonSettings.Properties.Bottom.WaitTimeAfterCure = (float)Math.Round(value, 2); + if (value > 0) + { + BottomLightOffDelay = 0; + LightOffDelay = 0; + } + } + } + + public override float WaitTimeAfterCure + { + get => base.WaitTimeAfterCure; + set + { + base.WaitTimeAfterCure = JsonSettings.Properties.Exposure.WaitTimeAfterCure = (float)Math.Round(value, 2); + if (value > 0) + { + BottomLightOffDelay = 0; + LightOffDelay = 0; + } + } + } + public override float BottomLiftHeight { get => JsonSettings.Properties.Bottom.LiftHeight; @@ -391,6 +446,34 @@ public override float BottomLiftSpeed2 set => base.BottomLiftSpeed2 = JsonSettings.Properties.Bottom.LiftSpeed2 = (float)Math.Round(value, 2); } + public override float BottomWaitTimeAfterLift + { + get => base.BottomWaitTimeAfterLift; + set + { + base.BottomWaitTimeAfterLift = JsonSettings.Properties.Bottom.WaitTimeAfterLift = (float)Math.Round(value, 2); + if (value > 0) + { + BottomLightOffDelay = 0; + LightOffDelay = 0; + } + } + } + + public override float WaitTimeAfterLift + { + get => base.WaitTimeAfterLift; + set + { + base.WaitTimeAfterLift = JsonSettings.Properties.Exposure.WaitTimeAfterLift = (float)Math.Round(value, 2); + if (value > 0) + { + BottomLightOffDelay = 0; + LightOffDelay = 0; + } + } + } + public override float LiftHeight2 { get => JsonSettings.Properties.Exposure.LiftHeight2; diff --git a/UVtools.Core/Layers/Layer.cs b/UVtools.Core/Layers/Layer.cs index a7215e11..edd9266f 100644 --- a/UVtools.Core/Layers/Layer.cs +++ b/UVtools.Core/Layers/Layer.cs @@ -30,16 +30,17 @@ namespace UVtools.Core.Layers; public enum LayerCompressionCodec : byte { - [Description("PNG: Compression=High Speed=Slow (Use with low RAM)")] + [Description("PNG: Compression=High | Speed=Slow (Use with low RAM)")] Png, - [Description("GZip: Compression=Medium Speed=Medium (Optimal)")] + [Description("GZip: Compression=Medium | Speed=Medium (Optimal)")] GZip, - [Description("Deflate: Compression=Medium Speed=Medium (Optimal)")] + [Description("Deflate: Compression=Medium | Speed=Medium (Optimal)")] Deflate, - [Description("LZ4: Compression=Low Speed=Fast (Use with high RAM)")] + [Description("LZ4: Compression=Low | Speed=Fast (Use with high RAM)")] Lz4, - //[Description("None: Compression=None Speed=Fastest (Your soul belongs to RAM)")] + //[Description("None: Compression=None | Speed=Fastest (Your soul belongs to RAM)")] //None + } #endregion @@ -802,6 +803,36 @@ public float MaterialMilliliters /// public float MaterialMillilitersPercent => SlicerFile.MaterialMilliliters > 0 ? _materialMilliliters * 100 / SlicerFile.MaterialMilliliters : float.NaN; + /// + /// Gets the time estimate in seconds it takes for this layer to be completed + /// + public float CompletionTime => (float)Math.Round(CalculateCompletionTime(), 2); + + /// + /// Gets the time estimate in minutes and seconds it takes for this layer to be completed + /// + public string CompletionTimeStr => TimeSpan.FromSeconds(CalculateCompletionTime()).ToString(@"mm\m\:ss\s"); + + /// + /// Get the start time estimate in seconds when this layer should start at + /// + public float StartTime => (float)Math.Round(CalculateStartTime(30), 2); + + /// + /// Get the start time estimate in hours, minutes and seconds when this layer should start at + /// + public string StartTimeStr => TimeSpan.FromSeconds(CalculateStartTime(30)).ToString(@"hh\h\:mm\m\:ss\s"); + + /// + /// Get the end time estimate in seconds when this layer should end at + /// + public float EndTime => (float)Math.Round(CalculateStartTime(30) + CalculateCompletionTime(), 2); + + /// + /// Get the end time estimate in hours, minutes and seconds when this layer should end at + /// + public string EndTimeStr => TimeSpan.FromSeconds(CalculateStartTime(30) + CalculateCompletionTime()).ToString(@"hh\h\:mm\m\:ss\s"); + /// /// Gets or sets the compression method used to cache the image /// @@ -1317,6 +1348,51 @@ public void ResetParameters() /// public float GetVolume(byte roundToDigits) => (float)Math.Round(GetArea() * LayerHeight, roundToDigits); + /// + /// Calculates the time estimate in seconds it takes for this layer to be completed + /// + /// + public float CalculateCompletionTime(float extraTime = 0) + { + float time = extraTime; + var motorTime = CalculateMotorMovementTime(); + time += WaitTimeBeforeCure + ExposureTime + WaitTimeAfterCure + WaitTimeAfterLift; + if (SlicerFile.SupportsGCode) + { + time += motorTime; + if (WaitTimeBeforeCure <= 0) + { + time += LightOffDelay; + } + } + else + { + time += motorTime > LightOffDelay ? motorTime : LightOffDelay; + } + + return time; + } + + /// + /// Calculates the start time estimate in seconds when this layer should start at + /// + /// + public float CalculateStartTime(float extraTime = 0) + { + float time = extraTime; + for (int i = 0; i < Index; i++) + { + time += SlicerFile[i].CompletionTime; + } + + return time; + } + + /// + /// Calculates the time the motor movements take to complete + /// + /// + /// public float CalculateMotorMovementTime(float extraTime = 0) { return OperationCalculator.LightOffDelayC.CalculateSeconds(this, extraTime); diff --git a/UVtools.Core/Objects/NullTerminatedUintStringBigEndian.cs b/UVtools.Core/Objects/NullTerminatedUintStringBigEndian.cs index 6f932769..2139e2c7 100644 --- a/UVtools.Core/Objects/NullTerminatedUintStringBigEndian.cs +++ b/UVtools.Core/Objects/NullTerminatedUintStringBigEndian.cs @@ -57,4 +57,6 @@ public override int GetHashCode() { return (SerializedValue != null ? SerializedValue.GetHashCode() : 0); } + + public static implicit operator NullTerminatedUintStringBigEndian(string value) => new(value); } \ No newline at end of file diff --git a/UVtools.Core/Objects/UInt24BigEndian.cs b/UVtools.Core/Objects/UInt24BigEndian.cs index c1bf3a6c..f6f8c78b 100644 --- a/UVtools.Core/Objects/UInt24BigEndian.cs +++ b/UVtools.Core/Objects/UInt24BigEndian.cs @@ -65,4 +65,6 @@ public override int GetHashCode() public static uint operator *(UInt24BigEndian a, UInt24BigEndian b) => a.Value * b.Value; public static uint operator /(UInt24BigEndian a, UInt24BigEndian b) => a.Value / b.Value; + + public static implicit operator UInt24BigEndian(uint value) => new(value); } \ No newline at end of file diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs index 806b4f17..7347029b 100644 --- a/UVtools.Core/Operations/OperationBlur.cs +++ b/UVtools.Core/Operations/OperationBlur.cs @@ -22,7 +22,7 @@ namespace UVtools.Core.Operations; public sealed class OperationBlur : Operation { #region Members - private BlurAlgorithm _blurOperation = BlurAlgorithm.Blur; + private BlurAlgorithm _blurOperation; private uint _size = 1; #endregion @@ -71,8 +71,10 @@ public sealed class OperationBlur : Operation #region Enums public enum BlurAlgorithm { - [Description("Blur: Normalized box filter")] - Blur, + [Description("Stack Blur: Normalized stack blur")] + StackBlur, + [Description("Box Blur: Normalized box filter")] + BoxBlur, [Description("Pyramid: Down/up-sampling step of Gaussian pyramid decomposition")] Pyramid, [Description("Median Blur: Each pixel becomes the median of its surrounding pixels")] @@ -156,7 +158,10 @@ public override bool Execute(Mat mat, params object[]? arguments) using var original = mat.Clone(); switch (BlurOperation) { - case BlurAlgorithm.Blur: + case BlurAlgorithm.StackBlur: + CvInvoke.StackBlur(target, target, size); + break; + case BlurAlgorithm.BoxBlur: CvInvoke.Blur(target, target, size, Kernel.Anchor); break; case BlurAlgorithm.Pyramid: diff --git a/UVtools.Core/Printer/Machine.cs b/UVtools.Core/Printer/Machine.cs index fc6bed89..50ffb19c 100644 --- a/UVtools.Core/Printer/Machine.cs +++ b/UVtools.Core/Printer/Machine.cs @@ -243,6 +243,8 @@ public Machine Clone() new(PrinterBrand.Creality, "Creality Halot Sky CL-89", "CL-89", 3840, 2400, 192f, 120f, 200f, FlipDirection.None), new(PrinterBrand.Creality, "Creality Halot Sky Plus CL-92", "CL-92", 5760, 3600, 198.14f, 123.84f, 210f, FlipDirection.None), new(PrinterBrand.Creality, "Creality Halot Ray CL925", "CL925", 5760, 3600, 198.14f, 123.84f, 210f, FlipDirection.None), + new(PrinterBrand.Creality, "Creality Halot Mage CL-103L", "CL-103L", 7680, 4320, 228.096f, 128.304f, 230f, FlipDirection.None), + new(PrinterBrand.Creality, "Creality Halot Mage Pro CL-103", "CL-103", 7680, 4320, 228.096f, 128.304f, 230f, FlipDirection.None), new(PrinterBrand.Creality, "Creality LD-002H", "LD-002H", 1620, 2560, 82.62f, 130.56f, 160f, FlipDirection.Horizontally), new(PrinterBrand.Creality, "Creality LD-002R", "LD-002R", 1440, 2560, 68.04f, 120.96f, 160f, FlipDirection.Horizontally), new(PrinterBrand.Creality, "Creality LD-006", "LD-006", 3840, 2400, 192f, 120f, 245f, FlipDirection.Horizontally), diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 3f1fc144..ba17d2a6 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.13.1 + 3.13.2 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 @@ -78,8 +78,8 @@ - - + + diff --git a/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml b/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml index 4989987b..c7b89bf1 100644 --- a/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml @@ -79,7 +79,7 @@ Classes="ValueLabel ValueLabel_s" HorizontalAlignment="Left" Minimum="0" - Maximum="100" + Maximum="1000" Increment="0.5" Width="100" Value="{Binding Operation.WaitTimeAfterLift}"> diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 73885969..36eed095 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -252,25 +252,26 @@ public IEnumerable MenuFileConvertItems public MainWindow() { + if (Settings.General.RestoreWindowLastPosition) + { + Position = new PixelPoint(Settings.General.LastWindowBounds.Location.X, Settings.General.LastWindowBounds.Location.Y); + } + + if (Settings.General.RestoreWindowLastSize) + { + Width = Settings.General.LastWindowBounds.Width; + Height = Settings.General.LastWindowBounds.Height; + } + if (Settings.General.StartMaximized) { WindowState = WindowState.Maximized; } - else + else { - if (Settings.General.RestoreWindowLastPosition) - { - Position = new PixelPoint(Settings.General.LastWindowBounds.Location.X, Settings.General.LastWindowBounds.Location.Y); - } - - if (Settings.General.RestoreWindowLastSize) - { - Width = Settings.General.LastWindowBounds.Width; - Height = Settings.General.LastWindowBounds.Height; - } - - var windowSize = this.GetScreenWorkingArea(); - if (Width >= windowSize.Width || Height >= windowSize.Height) + var screenSize = this.GetScreenWorkingArea(); + // Use a 20px margin + if (Width + 20 >= screenSize.Width || Height + 20 >= screenSize.Height) { WindowState = WindowState.Maximized; } diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs index 957e87b0..c8344884 100644 --- a/UVtools.WPF/Program.cs +++ b/UVtools.WPF/Program.cs @@ -68,6 +68,7 @@ public static void Main(string[] args) } } + /*using var mat = CvInvoke.Imread(@"D:\layer0.png", ImreadModes.Grayscale); var contours = mat.FindContours(out var hierarchy, RetrType.Tree, ChainApproxMethod.ChainApproxTc89Kcos); @@ -124,30 +125,30 @@ public static void Main(string[] args) - //z++; - /* } + //z++; + /* } - - //break; - } - var mesh = poly.Triangulate(new ConstraintOptions()); - for (int i = 0; i < 50; i++) - { - foreach (var meshTriangle in mesh.Triangles) - { - stl.WriteTriangle( - new Vector3((float)meshTriangle.GetVertex(0).X, (float)meshTriangle.GetVertex(0).Y, i), - new Vector3((float)meshTriangle.GetVertex(1).X, (float)meshTriangle.GetVertex(1).Y, i), - new Vector3((float)meshTriangle.GetVertex(2).X, (float)meshTriangle.GetVertex(2).Y, i), - new Vector3(1f, 1f, i) - ); - } - } - + //break; + } + + var mesh = poly.Triangulate(new ConstraintOptions()); + for (int i = 0; i < 50; i++) + { + foreach (var meshTriangle in mesh.Triangles) + { + stl.WriteTriangle( + new Vector3((float)meshTriangle.GetVertex(0).X, (float)meshTriangle.GetVertex(0).Y, i), + new Vector3((float)meshTriangle.GetVertex(1).X, (float)meshTriangle.GetVertex(1).Y, i), + new Vector3((float)meshTriangle.GetVertex(2).X, (float)meshTriangle.GetVertex(2).Y, i), + new Vector3(1f, 1f, i) + ); + } + } + - stl.EndWrite(); - return;*/ + stl.EndWrite(); + return;*/ /*Slicer slicer = new(Size.Empty, SizeF.Empty, "D:\\Cube100x100x100.stl"); var slices = slicer.SliceModel(0.05f); diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 82877381..c8379119 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.13.1 + 3.13.2 AnyCPU;x64 UVtools.png README.md diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index cf92aa2e..82722c76 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -41,12 +41,10 @@ Content="Start maximized"/> diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml index 8d637848..cd34a50e 100644 --- a/documentation/UVtools.Core.xml +++ b/documentation/UVtools.Core.xml @@ -689,6 +689,14 @@ Color to fill with Ignore and fill to if is less than the threshold value + + + Gets the number of bytes this uses per data type (Depth) + + + + + Step return the original Mat step, if ROI step still from original matrix which lead to errors. @@ -5252,6 +5260,36 @@ Gets the computed material milliliters percentage compared to the rest of the model + + + Gets the time estimate in seconds it takes for this layer to be completed + + + + + Gets the time estimate in minutes and seconds it takes for this layer to be completed + + + + + Get the start time estimate in seconds when this layer should start at + + + + + Get the start time estimate in hours, minutes and seconds when this layer should start at + + + + + Get the end time estimate in seconds when this layer should end at + + + + + Get the end time estimate in hours, minutes and seconds when this layer should end at + + Gets or sets the compression method used to cache the image @@ -5384,6 +5422,25 @@ Pixel size * number of pixels * layer height + + + Calculates the time estimate in seconds it takes for this layer to be completed + + + + + + Calculates the start time estimate in seconds when this layer should start at + + + + + + Calculates the time the motor movements take to complete + + + + Gets the wait time before cure, if not available calculate it from light off delay diff --git a/wiki/UVtools_Compression.xlsx b/wiki/UVtools_Compression.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..75893e56e640c5e7df87cd35769f31470d30ba80 GIT binary patch literal 19336 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 literal 0 HcmV?d00001