From 0ba61780b7ba662814b040d43bcf60847955ba18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 6 Feb 2021 22:09:55 +0000 Subject: v2.4.0 * (Upgrade) EmguCV/OpenCV to v4.5.1 * (Upgrade) AvaloniaUI to 1.0 * (Improvement) GUI re-touched * (Improvement) Make pixel editor tab to disappear when pixel editor is disabled * (Improvement) Simplify the output filename from PrusaSlicer profiles * (Improvement) All operations require a slicer file at constructor rather than on execute, this allow exposure the open file to the operation before run it * (Improvement) Calibrations: Auto set "Mirror Output" if open file have MirrorDisplay set * (Change) Tool - Redraw model/supports icon * (Change) photon and cbddlp to use version 3 by default * (Add) Tool - Dynamic layer height: Analyze and optimize the model with dynamic layer heights, larger angles will slice at lower layer height while more straight angles will slice larger layer height. (#131) * (Add) Calibration - Exposure time finder: Generates test models with various strategies and increments to verify the best exposure time for a given layer height * (Add) File load checks, trigger error when a file have critical errors and attempt to fix non-critical errors * Layers must have an valid image, otherwise trigger an error * Layers must have a incremental or equal position Z than it previous, otherwise trigger an error * If layer 0 starts at 0mm it will auto fix all layers, it will add Layer Height to the current z at every layer * (Add) Tool - Edit print parameters: Allow set parameters to each x layers and skip n layers inside the given range. This allow the use of optimizations in a layer pattern, for example, to set 3s for a layer but 2.5s for the next. * (Add) Layer height property to "Layer Data" table: Shows layer height for the slice * (Fix) When automations applied and file is saved, it will not warn user about file overwrite for the first time save * (Fix) Tool - Redraw model/supports: Disable apply button when no file selected * (Fix) Tool - Infill: Lack of equality member to test if same infill profile already exists * (Fix) Auto converted files from SL1 where clipping filename at first dot (.), now it only strips known extensions * (Fix) SL1 encoded files wasn't generating the right information and lead to printer crash * (Fix) PrusaSlicer printer "Anycubic Photon S" LiftSpeed was missing and contains a typo (#135) * (Fix) PrusaSlicer profile manager wasnt marking missing profiles to be installed (#135) * (Fix) PrusaSlicer folder search on linux to also look at %HOME%/.config/PrusaSlicer (#135, #136) * (Fix) Operations were revised and some bug fixed, most about can't cancel the progress * (Fix) Some typos on tooltips * (Fix) Prevent PhotonS from enconding, it will trigger error now as this format is read-only * **(Fix) Ctrl + Shift + Z to redo the last operation:** * The layer range is reseted instead of pull the used values * Tool - Arithmetic always disabled * Action - Layer import didn't generate info and always disabled --- .github/ISSUE_TEMPLATE/feature_request.md | 2 + .github/ISSUE_TEMPLATE/question.md | 8 +- .github/ISSUE_TEMPLATE/script.md | 11 +- CHANGELOG.md | 37 + CreateRelease.WPF.ps1 | 13 +- PrusaSlicer/printer/AnyCubic Photon S.ini | 2 +- PrusaSlicer/printer/QIDI I-Box Mono.ini | 4 +- .../sla_print/Universal 0.01 - Heavy Supports.ini | 2 +- .../sla_print/Universal 0.01 - Light Supports.ini | 2 +- .../sla_print/Universal 0.01 - Medium Supports.ini | 2 +- .../sla_print/Universal 0.02 - Heavy Supports.ini | 2 +- .../sla_print/Universal 0.02 - Light Supports.ini | 2 +- .../sla_print/Universal 0.02 - Medium Supports.ini | 2 +- .../sla_print/Universal 0.03 - Heavy Supports.ini | 2 +- .../sla_print/Universal 0.03 - Light Supports.ini | 2 +- .../sla_print/Universal 0.03 - Medium Supports.ini | 2 +- .../sla_print/Universal 0.04 - Heavy Supports.ini | 2 +- .../sla_print/Universal 0.04 - Light Supports.ini | 2 +- .../sla_print/Universal 0.04 - Medium Supports.ini | 2 +- .../sla_print/Universal 0.05 - Heavy Supports.ini | 2 +- .../sla_print/Universal 0.05 - Light Supports.ini | 2 +- .../sla_print/Universal 0.05 - Medium Supports.ini | 2 +- .../sla_print/Universal 0.10 - Heavy Supports.ini | 2 +- .../sla_print/Universal 0.10 - Light Supports.ini | 2 +- .../sla_print/Universal 0.10 - Medium Supports.ini | 2 +- .../sla_print/Universal 0.15 - Heavy Supports.ini | 2 +- .../sla_print/Universal 0.15 - Light Supports.ini | 2 +- .../sla_print/Universal 0.15 - Medium Supports.ini | 2 +- .../sla_print/Universal 0.20 - Heavy Supports.ini | 2 +- .../sla_print/Universal 0.20 - Light Supports.ini | 2 +- .../sla_print/Universal 0.20 - Medium Supports.ini | 2 +- UVtools.CAD/UVtools_demo_file.sl1 | Bin 550654 -> 550645 bytes UVtools.Core/Extensions/EmguExtensions.cs | 19 +- UVtools.Core/Extensions/MathExtensions.cs | 6 +- UVtools.Core/Extensions/PathExtensions.cs | 14 + UVtools.Core/Extensions/SizeExtensions.cs | 4 + UVtools.Core/FileFormats/CWSFile.cs | 18 +- UVtools.Core/FileFormats/ChituboxFile.cs | 59 +- UVtools.Core/FileFormats/ChituboxZipFile.cs | 16 +- UVtools.Core/FileFormats/FDGFile.cs | 14 +- UVtools.Core/FileFormats/FileFormat.cs | 489 +++++++++- UVtools.Core/FileFormats/IFileFormat.cs | 510 ---------- UVtools.Core/FileFormats/ImageFile.cs | 9 +- UVtools.Core/FileFormats/LGSFile.cs | 31 +- UVtools.Core/FileFormats/MakerbaseFile.cs | 18 +- UVtools.Core/FileFormats/PHZFile.cs | 17 +- UVtools.Core/FileFormats/PhotonSFile.cs | 37 +- UVtools.Core/FileFormats/PhotonWorkshopFile.cs | 13 +- UVtools.Core/FileFormats/SL1File.cs | 63 +- UVtools.Core/FileFormats/UVJFile.cs | 33 +- UVtools.Core/FileFormats/ZCodexFile.cs | 33 +- UVtools.Core/Layer/Layer.cs | 71 +- UVtools.Core/Layer/LayerManager.cs | 17 +- UVtools.Core/Objects/StringTag.cs | 2 +- UVtools.Core/Operations/Operation.cs | 172 +++- UVtools.Core/Operations/OperationArithmetic.cs | 26 +- UVtools.Core/Operations/OperationBlur.cs | 21 +- UVtools.Core/Operations/OperationCalculator.cs | 20 +- .../Operations/OperationCalibrateElephantFoot.cs | 65 +- .../Operations/OperationCalibrateExposureFinder.cs | 1015 ++++++++++++++++++++ .../Operations/OperationCalibrateExternalTests.cs | 14 +- .../Operations/OperationCalibrateGrayscale.cs | 63 +- .../Operations/OperationCalibrateStressTower.cs | 73 +- .../Operations/OperationCalibrateTolerance.cs | 81 +- .../Operations/OperationCalibrateXYZAccuracy.cs | 78 +- .../Operations/OperationChangeResolution.cs | 56 +- .../Operations/OperationDynamicLayerHeight.cs | 760 +++++++++++++++ UVtools.Core/Operations/OperationEditParameters.cs | 67 +- UVtools.Core/Operations/OperationFlip.cs | 20 +- UVtools.Core/Operations/OperationInfill.cs | 34 +- UVtools.Core/Operations/OperationLayerClone.cs | 18 +- UVtools.Core/Operations/OperationLayerImport.cs | 126 ++- UVtools.Core/Operations/OperationLayerReHeight.cs | 21 +- UVtools.Core/Operations/OperationLayerRemove.cs | 18 +- UVtools.Core/Operations/OperationMask.cs | 20 +- UVtools.Core/Operations/OperationMorph.cs | 21 +- UVtools.Core/Operations/OperationMove.cs | 81 +- UVtools.Core/Operations/OperationPattern.cs | 33 +- UVtools.Core/Operations/OperationPixelDimming.cs | 22 +- UVtools.Core/Operations/OperationProgress.cs | 22 +- UVtools.Core/Operations/OperationRaftRelief.cs | 30 +- UVtools.Core/Operations/OperationRedrawModel.cs | 21 +- UVtools.Core/Operations/OperationRepairLayers.cs | 31 +- UVtools.Core/Operations/OperationResize.cs | 22 +- UVtools.Core/Operations/OperationRotate.cs | 19 +- UVtools.Core/Operations/OperationSolidify.cs | 25 +- UVtools.Core/Operations/OperationThreshold.cs | 19 +- UVtools.Core/UVtools.Core.csproj | 4 +- UVtools.InstallerMM/UVtools.InstallerMM.wxs | 251 ++++- UVtools.Platforms/arch-x64/libcvextern.so | Bin 99097232 -> 99098608 bytes UVtools.Platforms/osx-x64/libcvextern.dylib | Bin 48698852 -> 43667988 bytes UVtools.Scripts/Erode-Bottom.ps1 | 10 +- UVtools.Scripts/README.md | 21 +- UVtools.WPF/App.axaml | 9 +- UVtools.WPF/App.axaml.cs | 21 +- UVtools.WPF/Assets/Icons/dynamic-layers-16x16.png | Bin 0 -> 113 bytes UVtools.WPF/Assets/Styles/Styles.xaml | 53 + UVtools.WPF/Assets/Styles/StylesLight.xaml | 20 + .../Calibrators/CalibrateElephantFootControl.axaml | 13 +- .../CalibrateElephantFootControl.axaml.cs | 13 +- .../CalibrateExposureFinderControl.axaml | 592 ++++++++++++ .../CalibrateExposureFinderControl.axaml.cs | 136 +++ .../CalibrateExternalTestsControl.axaml | 6 + .../CalibrateExternalTestsControl.axaml.cs | 5 +- .../Calibrators/CalibrateGrayscaleControl.axaml | 13 +- .../Calibrators/CalibrateGrayscaleControl.axaml.cs | 13 +- .../Calibrators/CalibrateStressTowerControl.axaml | 29 +- .../CalibrateStressTowerControl.axaml.cs | 19 +- .../Calibrators/CalibrateToleranceControl.axaml | 37 +- .../Calibrators/CalibrateToleranceControl.axaml.cs | 15 +- .../Calibrators/CalibrateXYZAccuracyControl.axaml | 41 +- .../CalibrateXYZAccuracyControl.axaml.cs | 15 +- UVtools.WPF/Controls/KernelControl.axaml | 10 +- UVtools.WPF/Controls/SliderEx.cs | 113 ++- .../Controls/Tools/ToolArithmeticControl.axaml.cs | 19 +- UVtools.WPF/Controls/Tools/ToolBlurControl.axaml | 3 +- .../Controls/Tools/ToolBlurControl.axaml.cs | 2 +- .../Controls/Tools/ToolCalculatorControl.axaml | 75 +- .../Controls/Tools/ToolCalculatorControl.axaml.cs | 11 +- .../Tools/ToolChangeResolutionControl.axaml | 9 +- .../Tools/ToolChangeResolutionControl.axaml.cs | 2 +- UVtools.WPF/Controls/Tools/ToolControl.axaml | 3 +- UVtools.WPF/Controls/Tools/ToolControl.axaml.cs | 3 + .../Tools/ToolDynamicLayerHeightControl.axaml | 199 ++++ .../Tools/ToolDynamicLayerHeightControl.axaml.cs | 86 ++ .../Controls/Tools/ToolEditParametersControl.axaml | 35 +- .../Tools/ToolEditParametersControl.axaml.cs | 12 +- .../Controls/Tools/ToolFlipControl.axaml.cs | 2 +- UVtools.WPF/Controls/Tools/ToolInfillControl.axaml | 1 + .../Controls/Tools/ToolInfillControl.axaml.cs | 2 +- .../Controls/Tools/ToolLayerCloneControl.axaml | 2 +- .../Controls/Tools/ToolLayerCloneControl.axaml.cs | 2 +- .../Controls/Tools/ToolLayerImportControl.axaml | 3 +- .../Controls/Tools/ToolLayerImportControl.axaml.cs | 7 +- .../Controls/Tools/ToolLayerReHeightControl.axaml | 4 +- .../Tools/ToolLayerReHeightControl.axaml.cs | 10 +- .../Controls/Tools/ToolLayerRemoveControl.axaml.cs | 2 +- UVtools.WPF/Controls/Tools/ToolMaskControl.axaml | 6 +- .../Controls/Tools/ToolMaskControl.axaml.cs | 2 +- UVtools.WPF/Controls/Tools/ToolMorphControl.axaml | 13 +- .../Controls/Tools/ToolMorphControl.axaml.cs | 2 +- UVtools.WPF/Controls/Tools/ToolMoveControl.axaml | 293 +++--- .../Controls/Tools/ToolMoveControl.axaml.cs | 3 +- .../Controls/Tools/ToolPatternControl.axaml | 9 +- .../Controls/Tools/ToolPatternControl.axaml.cs | 2 +- .../Controls/Tools/ToolPixelDimmingControl.axaml | 95 +- .../Tools/ToolPixelDimmingControl.axaml.cs | 2 +- .../Controls/Tools/ToolRaftReliefControl.axaml | 10 +- .../Controls/Tools/ToolRaftReliefControl.axaml.cs | 2 +- .../Controls/Tools/ToolRedrawModelControl.axaml | 72 +- .../Controls/Tools/ToolRedrawModelControl.axaml.cs | 21 +- .../Controls/Tools/ToolRepairLayersControl.axaml | 167 ++-- .../Tools/ToolRepairLayersControl.axaml.cs | 2 +- UVtools.WPF/Controls/Tools/ToolResizeControl.axaml | 16 +- .../Controls/Tools/ToolResizeControl.axaml.cs | 2 +- UVtools.WPF/Controls/Tools/ToolRotateControl.axaml | 5 +- .../Controls/Tools/ToolRotateControl.axaml.cs | 2 +- .../Controls/Tools/ToolSolidifyControl.axaml | 4 +- .../Controls/Tools/ToolSolidifyControl.axaml.cs | 2 +- .../Controls/Tools/ToolThresholdControl.axaml | 6 +- .../Controls/Tools/ToolThresholdControl.axaml.cs | 2 +- UVtools.WPF/Controls/WindowEx.cs | 17 +- UVtools.WPF/Extensions/WindowExtensions.cs | 5 +- UVtools.WPF/MainWindow.Information.cs | 5 +- UVtools.WPF/MainWindow.axaml | 249 +++-- UVtools.WPF/MainWindow.axaml.cs | 90 +- UVtools.WPF/Program.cs | 1 - UVtools.WPF/Structures/OperationProfiles.cs | 14 +- UVtools.WPF/Structures/PEProfileFolder.cs | 4 + UVtools.WPF/UVtools.WPF.csproj | 77 +- UVtools.WPF/Windows/AboutWindow.axaml | 5 +- UVtools.WPF/Windows/BenchmarkWindow.axaml | 6 +- UVtools.WPF/Windows/ColorPickerWindow.axaml | 5 +- UVtools.WPF/Windows/ProgressWindow.axaml | 15 +- UVtools.WPF/Windows/ProgressWindow.axaml.cs | 20 +- UVtools.WPF/Windows/PrusaSlicerManager.axaml | 130 +-- UVtools.WPF/Windows/SettingsWindow.axaml | 294 +++--- UVtools.WPF/Windows/ToolWindow.axaml | 81 +- UVtools.WPF/Windows/ToolWindow.axaml.cs | 30 +- 179 files changed, 5810 insertions(+), 2663 deletions(-) delete mode 100644 UVtools.Core/FileFormats/IFileFormat.cs create mode 100644 UVtools.Core/Operations/OperationCalibrateExposureFinder.cs create mode 100644 UVtools.Core/Operations/OperationDynamicLayerHeight.cs create mode 100644 UVtools.WPF/Assets/Icons/dynamic-layers-16x16.png create mode 100644 UVtools.WPF/Assets/Styles/Styles.xaml create mode 100644 UVtools.WPF/Assets/Styles/StylesLight.xaml create mode 100644 UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml create mode 100644 UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs create mode 100644 UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml create mode 100644 UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index df77f81..4999274 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,6 +6,8 @@ labels: enhancement assignees: sn4k3 --- +Also look at: https://github.com/sn4k3/UVtools/discussions/categories/ideas + **! Before continue, please look/search on closed topics if your case was already been discussed, if yes and related, comment there instead.** ## Is your feature request related to a problem? Please describe diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 8c574bd..9c1fc42 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -6,7 +6,7 @@ labels: question assignees: sn4k3 --- -**! Before continue, please look/search on closed topics if your case was already been discussed, if yes and related, comment there instead.** - -## Describe the question -A clear and concise description of your question. \ No newline at end of file +## READ THIS +Do not use issues to post questions! Use discussions instead!: +https://github.com/sn4k3/UVtools/discussions/categories/q-a +## READ THIS \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/script.md b/.github/ISSUE_TEMPLATE/script.md index 3975cd6..73965f7 100644 --- a/.github/ISSUE_TEMPLATE/script.md +++ b/.github/ISSUE_TEMPLATE/script.md @@ -6,10 +6,7 @@ labels: script assignees: sn4k3 --- -## Describe what your script does -A clear and concise description of your script, and all it steps. - - -```Powershell -#Paste your code here or attach the .ps1 file -``` \ No newline at end of file +## READ THIS +Do not use issues to post scripts! Use discussions instead!: +https://github.com/sn4k3/UVtools/discussions/categories/scripts +## READ THIS \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ef747..42d7f3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## 06/02/2021 - v2.4.0 + +* (Upgrade) EmguCV/OpenCV to v4.5.1 +* (Upgrade) AvaloniaUI to 1.0 +* (Improvement) GUI re-touched +* (Improvement) Make pixel editor tab to disappear when pixel editor is disabled +* (Improvement) Simplify the output filename from PrusaSlicer profiles +* (Improvement) All operations require a slicer file at constructor rather than on execute, this allow exposure the open file to the operation before run it +* (Improvement) Calibrations: Auto set "Mirror Output" if open file have MirrorDisplay set +* (Change) Tool - Redraw model/supports icon +* (Change) photon and cbddlp to use version 3 by default +* (Add) Tool - Dynamic layer height: Analyze and optimize the model with dynamic layer heights, larger angles will slice at lower layer height + while more straight angles will slice larger layer height. (#131) +* (Add) Calibration - Exposure time finder: Generates test models with various strategies and increments to verify the best exposure time for a given layer height +* (Add) File load checks, trigger error when a file have critical errors and attempt to fix non-critical errors + * Layers must have an valid image, otherwise trigger an error + * Layers must have a incremental or equal position Z than it previous, otherwise trigger an error + * If layer 0 starts at 0mm it will auto fix all layers, it will add Layer Height to the current z at every layer +* (Add) Tool - Edit print parameters: Allow set parameters to each x layers and skip n layers inside the given range. + This allow the use of optimizations in a layer pattern, for example, to set 3s for a layer but 2.5s for the next. +* (Add) Layer height property to "Layer Data" table: Shows layer height for the slice +* (Fix) When automations applied and file is saved, it will not warn user about file overwrite for the first time save +* (Fix) Tool - Redraw model/supports: Disable apply button when no file selected +* (Fix) Tool - Infill: Lack of equality member to test if same infill profile already exists +* (Fix) Auto converted files from SL1 where clipping filename at first dot (.), now it only strips known extensions +* (Fix) SL1 encoded files wasn't generating the right information and lead to printer crash +* (Fix) PrusaSlicer printer "Anycubic Photon S" LiftSpeed was missing and contains a typo (#135) +* (Fix) PrusaSlicer profile manager wasnt marking missing profiles to be installed (#135) +* (Fix) PrusaSlicer folder search on linux to also look at %HOME%/.config/PrusaSlicer (#135, #136) +* (Fix) Operations were revised and some bug fixed, most about can't cancel the progress +* (Fix) Some typos on tooltips +* (Fix) Prevent PhotonS from enconding, it will trigger error now as this format is read-only +* **(Fix) Ctrl + Shift + Z to redo the last operation:** + * The layer range is reseted instead of pull the used values + * Tool - Arithmetic always disabled + * Action - Layer import didn't generate info and always disabled + ## 22/01/2021 - v2.3.2 * (Add) Settings - Automations: Change only light-off delay if value is zero (Enabled by default) diff --git a/CreateRelease.WPF.ps1 b/CreateRelease.WPF.ps1 index fc0a1d4..607d0f4 100644 --- a/CreateRelease.WPF.ps1 +++ b/CreateRelease.WPF.ps1 @@ -30,6 +30,8 @@ class FixedEncoder : System.Text.UTF8Encoding { ### Configuration ### #################################### $enableMSI = $true +$buildOnly = $null +#$buildOnly = ""#"win-x64" # Profilling $stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch $deployStopWatch = New-Object -TypeName System.Diagnostics.Stopwatch @@ -83,22 +85,22 @@ $runtimes = @{ "win-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" - "exclude" = @("libcvextern.so", "libcvextern.dylib", "UVtools.sh") + "exclude" = @("UVtools.sh") "include" = @() } "linux-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" - "exclude" = @("x86", "x64", "libcvextern.dylib") + "exclude" = @() "include" = @("libcvextern.so") } "arch-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" - "exclude" = @("x86", "x64", "libcvextern.dylib", "libcvextern.so") + "exclude" = @() "include" = @("libcvextern.so") } "rhel-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" - "exclude" = @("x86", "x64", "libcvextern.dylib") + "exclude" = @() "include" = @("libcvextern.so") } #"unix-x64" = @{ @@ -107,12 +109,13 @@ $runtimes = #} "osx-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" - "exclude" = @("x86", "x64", "libcvextern.so") + "exclude" = @() "include" = @("libcvextern.dylib") } } foreach ($obj in $runtimes.GetEnumerator()) { + if(![string]::IsNullOrWhiteSpace($buildOnly) -and !$buildOnly.Equals($obj.Name)) {continue} # Configuration $deployStopWatch.Restart() $runtime = $obj.Name; # runtime name diff --git a/PrusaSlicer/printer/AnyCubic Photon S.ini b/PrusaSlicer/printer/AnyCubic Photon S.ini index c598345..6af11a5 100644 --- a/PrusaSlicer/printer/AnyCubic Photon S.ini +++ b/PrusaSlicer/printer/AnyCubic Photon S.ini @@ -26,7 +26,7 @@ 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_ANYCUBIC\nPRINTER_MODEL_PHOTON_S\nFILEFORMAT_PWS\n\nSTART_CUSTOM_VALUES\nLightOffDelay_1\nLiftHeight_6\nLiftingSpeed_60\nRetractSpeed_150\nEND_CUSTOM_VALUES +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_ANYCUBIC\nPRINTER_MODEL_PHOTON_S\nFILEFORMAT_PWS\n\nSTART_CUSTOM_VALUES\nLightOffDelay_1\nLiftHeight_6\nLiftSpeed_60\nRetractSpeed_150\nEND_CUSTOM_VALUES printer_settings_id = printer_technology = SLA printer_variant = default diff --git a/PrusaSlicer/printer/QIDI I-Box Mono.ini b/PrusaSlicer/printer/QIDI I-Box Mono.ini index 9075d01..bd20e34 100644 --- a/PrusaSlicer/printer/QIDI I-Box Mono.ini +++ b/PrusaSlicer/printer/QIDI I-Box Mono.ini @@ -1,4 +1,4 @@ -# generated by PrusaSlicer 2.3.0+win64 on 2021-01-13 at 02:30:16 UTC +# generated by PrusaSlicer 2.3.0+win64 on 2021-02-06 at 03:35:53 UTC absolute_correction = 0 area_fill = 50 bed_custom_model = @@ -26,7 +26,7 @@ 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_QIDI\nPRINTER_MODEL_I-BOX_MONO\nFILEFORMAT_CTB\n\nSTART_CUSTOM_VALUES\nLightOffDelay_0\nBottomLightOffDelay_0\nBottomLiftHeight_6\nLiftHeight_6\nBottomLiftSpeed_65\nLiftSpeed_65\nRetractSpeed_100\nBottomLightPWM_255\nLightPWM_255\nEND_CUSTOM_VALUES +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_QIDI\nPRINTER_MODEL_I-BOX_MONO\nFILEFORMAT_CTB\n\nSTART_CUSTOM_VALUES\nLightOffDelay_0\nBottomLightOffDelay_0\nBottomLiftHeight_7\nLiftHeight_7\nBottomLiftSpeed_80\nLiftSpeed_80\nRetractSpeed_120\nBottomLightPWM_255\nLightPWM_255\nEND_CUSTOM_VALUES printer_settings_id = printer_technology = SLA printer_variant = default diff --git a/PrusaSlicer/sla_print/Universal 0.01 - Heavy Supports.ini b/PrusaSlicer/sla_print/Universal 0.01 - Heavy Supports.ini index 54a964b..330e5b4 100644 --- a/PrusaSlicer/sla_print/Universal 0.01 - Heavy Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.01 - Heavy Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.025 UltraDetail layer_height = 0.01 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.01 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.01 - Light Supports.ini index 60e45f2..639cc29 100644 --- a/PrusaSlicer/sla_print/Universal 0.01 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.01 - Light Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.025 UltraDetail layer_height = 0.01 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.01 - Medium Supports.ini b/PrusaSlicer/sla_print/Universal 0.01 - Medium Supports.ini index 031f5e2..9ad3b32 100644 --- a/PrusaSlicer/sla_print/Universal 0.01 - Medium Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.01 - Medium Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.025 UltraDetail layer_height = 0.01 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.02 - Heavy Supports.ini b/PrusaSlicer/sla_print/Universal 0.02 - Heavy Supports.ini index a05af1b..c77c787 100644 --- a/PrusaSlicer/sla_print/Universal 0.02 - Heavy Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.02 - Heavy Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.025 UltraDetail layer_height = 0.02 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.02 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.02 - Light Supports.ini index 4b31a2f..dd7fad1 100644 --- a/PrusaSlicer/sla_print/Universal 0.02 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.02 - Light Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.025 UltraDetail layer_height = 0.02 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.02 - Medium Supports.ini b/PrusaSlicer/sla_print/Universal 0.02 - Medium Supports.ini index 4abdf3e..ec58cf6 100644 --- a/PrusaSlicer/sla_print/Universal 0.02 - Medium Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.02 - Medium Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.025 UltraDetail layer_height = 0.02 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.03 - Heavy Supports.ini b/PrusaSlicer/sla_print/Universal 0.03 - Heavy Supports.ini index a742d6e..5f059ee 100644 --- a/PrusaSlicer/sla_print/Universal 0.03 - Heavy Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.03 - Heavy Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.035 Detail layer_height = 0.03 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.03 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.03 - Light Supports.ini index d4ecc3c..f6cf26b 100644 --- a/PrusaSlicer/sla_print/Universal 0.03 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.03 - Light Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.035 Detail layer_height = 0.03 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.03 - Medium Supports.ini b/PrusaSlicer/sla_print/Universal 0.03 - Medium Supports.ini index f76de22..fa95d0d 100644 --- a/PrusaSlicer/sla_print/Universal 0.03 - Medium Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.03 - Medium Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.035 Detail layer_height = 0.03 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.04 - Heavy Supports.ini b/PrusaSlicer/sla_print/Universal 0.04 - Heavy Supports.ini index 0b95e6a..596f540 100644 --- a/PrusaSlicer/sla_print/Universal 0.04 - Heavy Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.04 - Heavy Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.05 Normal layer_height = 0.04 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.04 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.04 - Light Supports.ini index 8cb802c..f384ae1 100644 --- a/PrusaSlicer/sla_print/Universal 0.04 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.04 - Light Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.05 Normal layer_height = 0.04 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.04 - Medium Supports.ini b/PrusaSlicer/sla_print/Universal 0.04 - Medium Supports.ini index f85f2bb..49cc824 100644 --- a/PrusaSlicer/sla_print/Universal 0.04 - Medium Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.04 - Medium Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.05 Normal layer_height = 0.04 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.05 - Heavy Supports.ini b/PrusaSlicer/sla_print/Universal 0.05 - Heavy Supports.ini index 51f9874..7ae04f5 100644 --- a/PrusaSlicer/sla_print/Universal 0.05 - Heavy Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.05 - Heavy Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.05 Normal layer_height = 0.05 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.05 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.05 - Light Supports.ini index 42c4012..fd821fc 100644 --- a/PrusaSlicer/sla_print/Universal 0.05 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.05 - Light Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.05 Normal layer_height = 0.05 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.05 - Medium Supports.ini b/PrusaSlicer/sla_print/Universal 0.05 - Medium Supports.ini index e73f429..34c70d1 100644 --- a/PrusaSlicer/sla_print/Universal 0.05 - Medium Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.05 - Medium Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.05 Normal layer_height = 0.05 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.10 - Heavy Supports.ini b/PrusaSlicer/sla_print/Universal 0.10 - Heavy Supports.ini index ce007b3..993832f 100644 --- a/PrusaSlicer/sla_print/Universal 0.10 - Heavy Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.10 - Heavy Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.1 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.10 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.10 - Light Supports.ini index 3d85901..60c151b 100644 --- a/PrusaSlicer/sla_print/Universal 0.10 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.10 - Light Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.1 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.10 - Medium Supports.ini b/PrusaSlicer/sla_print/Universal 0.10 - Medium Supports.ini index 86cc2f4..65dc102 100644 --- a/PrusaSlicer/sla_print/Universal 0.10 - Medium Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.10 - Medium Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.1 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.15 - Heavy Supports.ini b/PrusaSlicer/sla_print/Universal 0.15 - Heavy Supports.ini index 87c1089..8beb433 100644 --- a/PrusaSlicer/sla_print/Universal 0.15 - Heavy Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.15 - Heavy Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.15 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.15 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.15 - Light Supports.ini index befb278..3c34639 100644 --- a/PrusaSlicer/sla_print/Universal 0.15 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.15 - Light Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.15 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.15 - Medium Supports.ini b/PrusaSlicer/sla_print/Universal 0.15 - Medium Supports.ini index 40e5b19..3b0061a 100644 --- a/PrusaSlicer/sla_print/Universal 0.15 - Medium Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.15 - Medium Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.15 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.20 - Heavy Supports.ini b/PrusaSlicer/sla_print/Universal 0.20 - Heavy Supports.ini index f0600c1..abce98c 100644 --- a/PrusaSlicer/sla_print/Universal 0.20 - Heavy Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.20 - Heavy Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.2 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.20 - Light Supports.ini b/PrusaSlicer/sla_print/Universal 0.20 - Light Supports.ini index f9d51d3..8e81454 100644 --- a/PrusaSlicer/sla_print/Universal 0.20 - Light Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.20 - Light Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.2 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/PrusaSlicer/sla_print/Universal 0.20 - Medium Supports.ini b/PrusaSlicer/sla_print/Universal 0.20 - Medium Supports.ini index 92593d0..00cced8 100644 --- a/PrusaSlicer/sla_print/Universal 0.20 - Medium Supports.ini +++ b/PrusaSlicer/sla_print/Universal 0.20 - Medium Supports.ini @@ -9,7 +9,7 @@ hollowing_min_thickness = 3 hollowing_quality = 0.5 inherits = 0.1 Fast layer_height = 0.2 -output_filename_format = {input_filename_base}_{material_type}{layer_height}mm_{printer_model}_{print_time}.sl1 +output_filename_format = {input_filename_base}_{layer_height}mm_{print_time}.sl1 pad_around_object = 0 pad_around_object_everywhere = 0 pad_brim_size = 1.6 diff --git a/UVtools.CAD/UVtools_demo_file.sl1 b/UVtools.CAD/UVtools_demo_file.sl1 index 233a187..5d00568 100644 Binary files a/UVtools.CAD/UVtools_demo_file.sl1 and b/UVtools.CAD/UVtools_demo_file.sl1 differ diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index 64b2dc5..b5ed369 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -63,10 +63,27 @@ namespace UVtools.Core.Extensions public static unsafe Span GetPixelRowSpan(this Mat mat, int y, int length = 0, int offset = 0) { - return new Span(IntPtr.Add(mat.DataPointer, y * mat.Step + offset).ToPointer(), length == 0 ? mat.Step : length); + return new(IntPtr.Add(mat.DataPointer, y * mat.Step + offset).ToPointer(), length == 0 ? mat.Step : length); //return mat.GetPixelSpan().Slice(offset, mat.Step); } + /// + /// Gets if a is all zeroed + /// + /// + /// Pixel brightness threshold + /// + public static unsafe bool IsZeroed(this Mat mat, byte threshold = 0) + { + var ptr = mat.GetBytePointer(); + for (int i = 0; i < mat.GetLength(); i++) + { + if (ptr[i] > threshold) return false; + } + return true; + } + + public static void Transform(this Mat src, double xScale, double yScale, double xTrans = 0, double yTrans = 0, Size dstSize = default, Inter interpolation = Inter.Linear) { //var dst = new Mat(src.Size, src.Depth, src.NumberOfChannels); diff --git a/UVtools.Core/Extensions/MathExtensions.cs b/UVtools.Core/Extensions/MathExtensions.cs index 2020744..4bb6c92 100644 --- a/UVtools.Core/Extensions/MathExtensions.cs +++ b/UVtools.Core/Extensions/MathExtensions.cs @@ -103,12 +103,16 @@ namespace UVtools.Core.Extensions public static uint DecimalDigits(this decimal val) { var valStr = val.ToString(CultureInfo.InvariantCulture).TrimEnd('0'); - if (string.IsNullOrEmpty(valStr) || valStr[valStr.Length-1] == '.') return 0; + if (string.IsNullOrEmpty(valStr) || valStr[^1] == '.') return 0; var index = valStr.IndexOf('.'); if (index < 0) return 0; return (uint)(valStr.Substring(index).Length - 1); } + + public static bool IsInteger(this float val, float tolerance = 0.0001f) => Math.Abs(val - Math.Floor(val)) < tolerance; + public static bool IsInteger(this double val, double tolerance = 0.0001) => Math.Abs(val - Math.Floor(val)) < tolerance; + public static bool IsInteger(this decimal val) => val == Math.Floor(val); } } diff --git a/UVtools.Core/Extensions/PathExtensions.cs b/UVtools.Core/Extensions/PathExtensions.cs index 846c4e1..73937bb 100644 --- a/UVtools.Core/Extensions/PathExtensions.cs +++ b/UVtools.Core/Extensions/PathExtensions.cs @@ -6,6 +6,7 @@ * of this license document, but changing it is not allowed. */ using System; +using System.Collections.Generic; using System.IO; namespace UVtools.Core.Extensions @@ -19,5 +20,18 @@ namespace UVtools.Core.Extensions var splitPath = path.Split('.', 2, StringSplitOptions.TrimEntries); return splitPath.Length == 0 ? string.Empty : splitPath[0]; } + + public static string GetFileNameStripExtensions(string path, List extensions) + { + path = Path.GetFileName(path); + if (string.IsNullOrEmpty(path)) return string.Empty; + foreach (var extension in extensions) + { + var dotExtension = $".{extension}"; + if (path.EndsWith(dotExtension)) return path.Remove(path.Length - dotExtension.Length); + } + + return path; + } } } diff --git a/UVtools.Core/Extensions/SizeExtensions.cs b/UVtools.Core/Extensions/SizeExtensions.cs index 8e164ec..455dfee 100644 --- a/UVtools.Core/Extensions/SizeExtensions.cs +++ b/UVtools.Core/Extensions/SizeExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Text; namespace UVtools.Core.Extensions @@ -34,5 +35,8 @@ namespace UVtools.Core.Extensions adjustedSize, SizeSuffixes[mag]); } + + public static Size Inflate(this Size size, int pixels) => new (size.Width + pixels, size.Height + pixels); + public static Size Inflate(this Size size, int width, int height) => new (size.Width + width, size.Height + height); } } diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs index 0578202..2312168 100644 --- a/UVtools.Core/FileFormats/CWSFile.cs +++ b/UVtools.Core/FileFormats/CWSFile.cs @@ -540,12 +540,8 @@ namespace UVtools.Core.FileFormats GCode = null; } - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); - //var filename = fileFullPath.EndsWith(TemporaryFileAppend) ? Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(fileFullPath)) : Path.GetFileNameWithoutExtension(fileFullPath); if (Printer == PrinterType.Unknown) @@ -662,20 +658,14 @@ namespace UVtools.Core.FileFormats RebuildGCode(); outputFile.PutFileContent($"{filename}.gcode", GCode.ToString(), ZipArchiveMode.Create); } - - AfterEncode(); } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - if(progress is null) progress = new OperationProgress(OperationProgress.StatusGatherLayers, LayerCount); - - FileFullPath = fileFullPath; - using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) + using (var inputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Read)) { var entry = inputFile.GetEntry("manifest.xml"); - if (!ReferenceEquals(entry, null)) // Wanhao + if (entry is not null) // Wanhao { //DecodeXML(fileFullPath, inputFile, progress); Printer = PrinterType.Wanhao; diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs index 242bc3d..3bb7648 100644 --- a/UVtools.Core/FileFormats/ChituboxFile.cs +++ b/UVtools.Core/FileFormats/ChituboxFile.cs @@ -1336,17 +1336,13 @@ namespace UVtools.Core.FileFormats LayerDefinitions = null; } - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); LayersHash.Clear(); HeaderSettings.Magic = fileFullPath.EndsWith(".ctb") || fileFullPath.EndsWith($".ctb{TemporaryFileAppend}") ? MAGIC_CBT : MAGIC_CBDDLP; HeaderSettings.PrintParametersSize = (uint)Helpers.Serializer.SizeOf(PrintParametersSettings); - if (IsCbtFile) { if (SlicerInfoSettings.AntiAliasLevel <= 1) @@ -1380,8 +1376,8 @@ namespace UVtools.Core.FileFormats } else { - HeaderSettings.Version = 2; - HeaderSettings.EncryptionKey = 0; + //HeaderSettings.Version = 2; + HeaderSettings.EncryptionKey = 0; // Force disable encryption SlicerInfoSettings.EncryptionMode = ENCRYPTYION_MODE_CBDDLP; } @@ -1511,8 +1507,6 @@ namespace UVtools.Core.FileFormats outputFile.Seek(0, SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - AfterEncode(); - Debug.WriteLine("Encode Results:"); Debug.WriteLine(HeaderSettings); Debug.WriteLine(Previews[0]); @@ -1523,14 +1517,9 @@ namespace UVtools.Core.FileFormats } } - - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - if(progress is null) progress = new OperationProgress(); - progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) { //HeaderSettings = Helpers.ByteToType(InputFile); @@ -1659,38 +1648,34 @@ namespace UVtools.Core.FileFormats return; } - using (var image = LayerDefinitions[0, layerIndex].Decode((uint) layerIndex)) + using var image = LayerDefinitions[0, layerIndex].Decode((uint) layerIndex); + var layer = new Layer((uint) layerIndex, image, LayerManager) { - var layer = new Layer((uint) layerIndex, image, LayerManager) - { - PositionZ = LayerDefinitions[0, layerIndex].LayerPositionZ, - ExposureTime = LayerDefinitions[0, layerIndex].LayerExposure, - LightOffDelay = LayerDefinitions[0, layerIndex].LightOffSeconds, - }; + PositionZ = LayerDefinitions[0, layerIndex].LayerPositionZ, + ExposureTime = LayerDefinitions[0, layerIndex].LayerExposure, + LightOffDelay = LayerDefinitions[0, layerIndex].LightOffSeconds, + }; - if (LayerDefinitionsEx is not null) + if (LayerDefinitionsEx is not null) + { + if (layerIndex == 0) { - if (layerIndex == 0) - { - } - layer.LiftHeight = LayerDefinitionsEx[layerIndex].LiftHeight; - layer.LiftSpeed = LayerDefinitionsEx[layerIndex].LiftSpeed; - layer.RetractSpeed = LayerDefinitionsEx[layerIndex].RetractSpeed; - layer.LightPWM = (byte) LayerDefinitionsEx[layerIndex].LightPWM; } + layer.LiftHeight = LayerDefinitionsEx[layerIndex].LiftHeight; + layer.LiftSpeed = LayerDefinitionsEx[layerIndex].LiftSpeed; + layer.RetractSpeed = LayerDefinitionsEx[layerIndex].RetractSpeed; + layer.LightPWM = (byte) LayerDefinitionsEx[layerIndex].LightPWM; + } - this[layerIndex] = layer; + this[layerIndex] = layer; - lock (progress.Mutex) - { - progress++; - } + lock (progress.Mutex) + { + progress++; } }); } - - progress.Token.ThrowIfCancellationRequested(); } public override void SaveAs(string filePath = null, OperationProgress progress = null) diff --git a/UVtools.Core/FileFormats/ChituboxZipFile.cs b/UVtools.Core/FileFormats/ChituboxZipFile.cs index d9cf337..fc011a0 100644 --- a/UVtools.Core/FileFormats/ChituboxZipFile.cs +++ b/UVtools.Core/FileFormats/ChituboxZipFile.cs @@ -400,11 +400,8 @@ namespace UVtools.Core.FileFormats #region Methods - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) { if (Thumbnails.Length > 0 && !ReferenceEquals(Thumbnails[0], null)) @@ -448,21 +445,14 @@ namespace UVtools.Core.FileFormats progress++; } } - - AfterEncode(); } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - if(progress is null) progress = new OperationProgress(); - progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); - - FileFullPath = fileFullPath; using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) { var entry = inputFile.GetEntry("run.gcode"); - if (!ReferenceEquals(entry, null)) + if (entry is not null) { //Clear(); //throw new FileLoadException("run.gcode not found", fileFullPath); diff --git a/UVtools.Core/FileFormats/FDGFile.cs b/UVtools.Core/FileFormats/FDGFile.cs index d2ed39d..53d5bd1 100644 --- a/UVtools.Core/FileFormats/FDGFile.cs +++ b/UVtools.Core/FileFormats/FDGFile.cs @@ -999,11 +999,8 @@ namespace UVtools.Core.FileFormats LayersDefinitions = null; } - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); LayersHash.Clear(); /*if (HeaderSettings.EncryptionKey == 0) @@ -1118,8 +1115,6 @@ namespace UVtools.Core.FileFormats outputFile.Seek(0, SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - AfterEncode(); - Debug.WriteLine("Encode Results:"); Debug.WriteLine(HeaderSettings); Debug.WriteLine(Previews[0]); @@ -1128,11 +1123,8 @@ namespace UVtools.Core.FileFormats } } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - if(progress is null) progress = new OperationProgress(OperationProgress.StatusGatherLayers, LayerCount); - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) { @@ -1235,8 +1227,6 @@ namespace UVtools.Core.FileFormats } }); } - - progress.Token.ThrowIfCancellationRequested(); } public override void SaveAs(string filePath = null, OperationProgress progress = null) diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 462ffd1..3bd1f15 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -26,7 +26,7 @@ namespace UVtools.Core.FileFormats /// /// Slicer representation /// - public abstract class FileFormat : BindableBase, IFileFormat, IDisposable, IEquatable, IEnumerable + public abstract class FileFormat : BindableBase, IDisposable, IEquatable, IEnumerable { #region Constants public const string TemporaryFileAppend = ".tmp"; @@ -44,6 +44,9 @@ namespace UVtools.Core.FileFormats public const float DefaultLightOffDelay = 0; public const byte DefaultBottomLightPWM = 255; public const byte DefaultLightPWM = 255; + + public const float MinimumLayerHeight = 0.01f; + public const float MaximumLayerHeight = 0.20f; #endregion #region Enums @@ -190,11 +193,11 @@ namespace UVtools.Core.FileFormats public static FileFormat[] AvailableFormats { get; } = { new SL1File(), // Prusa SL1 - new ChituboxZipFile(), // Zip + new ChituboxZipFile(), // Zip new ChituboxFile(), // cbddlp, cbt, photon + new PhotonSFile(), // photons new PHZFile(), // phz new FDGFile(), // fdg - new PhotonSFile(), // photons new PhotonWorkshopFile(), // PSW new ZCodexFile(), // zcodex new CWSFile(), // CWS @@ -243,19 +246,28 @@ namespace UVtools.Core.FileFormats } } - - /// - /// Gets the count of available file extensions - /// - public static byte FileExtensionsCount + public static List AllFileExtensions { get { - return AvailableFormats.Aggregate(0, (current, fileFormat) => (byte) (current + fileFormat.FileExtensions.Length)); + List extensions = new(); + foreach (var slicerFile in AvailableFormats) + { + extensions.AddRange(slicerFile.FileExtensions); + } + return extensions; } } + public static List AllFileExtensionsString => (from slicerFile in AvailableFormats from extension in slicerFile.FileExtensions select extension.Extension).ToList(); + + + /// + /// Gets the count of available file extensions + /// + public static byte FileExtensionsCount => AvailableFormats.Aggregate(0, (current, fileFormat) => (byte) (current + fileFormat.FileExtensions.Length)); + /// /// Find by an extension /// @@ -285,14 +297,54 @@ namespace UVtools.Core.FileFormats } #endregion + #region Members + private bool _haveModifiedLayers; + private LayerManager _layerManager; + private float _printTime; + private float _maxPrintHeight; + #endregion + #region Properties + /// + /// Gets the file format type + /// public abstract FileFormatType FileType { get; } + /// + /// Gets the valid file extensions for this + /// public abstract FileExtension[] FileExtensions { get; } + + /// + /// Gets the available + /// public abstract PrintParameterModifier[] PrintParameterModifiers { get; } + + /// + /// Gets the available per layer + /// public virtual PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = null; + /// + /// Checks if a exists on print parameters + /// + /// + /// True if exists, otherwise false + public bool HavePrintParameterModifier(PrintParameterModifier modifier) => + PrintParameterModifiers is not null && PrintParameterModifiers.Contains(modifier); + + /// + /// Checks if a exists on print parameters + /// + /// + /// True if exists, otherwise false + public bool HavePrintParameterPerLayerModifier(PrintParameterModifier modifier) => + PrintParameterPerLayerModifiers is not null && PrintParameterPerLayerModifiers.Contains(modifier); + + /// + /// Gets the file filter for open and save dialogs + /// public string FileFilter { get { @@ -311,9 +363,15 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets all valid file extensions for Avalonia file dialog + /// public List>> FileFilterAvalonia => FileExtensions.Select(fileExt => new KeyValuePair>(fileExt.Description, new List {fileExt.Extension})).ToList(); + /// + /// Gets all valid file extensions in "*.extension1;*.extension2" format + /// public string FileFilterExtensionsOnly { get @@ -333,12 +391,24 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets or sets if change a global property should rebuild every layer data based on them + /// public bool SuppressRebuildProperties { get; set; } + /// + /// Gets the input file path loaded into this + /// public string FileFullPath { get; set; } + /// + /// Gets the thumbnails count present in this file format + /// public abstract byte ThumbnailsCount { get; } + /// + /// Gets the number of created thumbnails + /// public byte CreatedThumbnailsCount { get { @@ -355,10 +425,19 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets the original thumbnail sizes + /// public abstract Size[] ThumbnailsOriginalSize { get; } + /// + /// Gets the thumbnails for this + /// public Mat[] Thumbnails { get; set; } + /// + /// Gets the cached layers into compressed bytes + /// public LayerManager LayerManager { get => _layerManager; @@ -373,10 +452,10 @@ namespace UVtools.Core.FileFormats } } - private bool _haveModifiedLayers; - private LayerManager _layerManager; - private float _printTime; - private float _maxPrintHeight; + /// + /// Gets the bounding rectangle of the object + /// + public Rectangle BoundingRectangle => _layerManager?.BoundingRectangle ?? Rectangle.Empty; /// /// Gets or sets if modifications require a full encode to save @@ -387,6 +466,9 @@ namespace UVtools.Core.FileFormats set => _haveModifiedLayers = value; } // => LayerManager.IsModified; + /// + /// Gets the image width resolution + /// public Size Resolution { get => new((int)ResolutionX, (int)ResolutionY); @@ -396,12 +478,21 @@ namespace UVtools.Core.FileFormats ResolutionY = (uint) value.Height; RaisePropertyChanged(); } - } - + } + + /// + /// Gets the image width resolution + /// public abstract uint ResolutionX { get; set; } + /// + /// Gets the image height resolution + /// public abstract uint ResolutionY { get; set; } + /// + /// Gets the size of display in millimeters + /// public SizeF Display { get => new(DisplayWidth, DisplayHeight); @@ -413,16 +504,33 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets or sets the display width in millimeters + /// public abstract float DisplayWidth { get; set; } + + /// + /// Gets or sets the display height in millimeters + /// public abstract float DisplayHeight { get; set; } + + /// + /// Gets or sets if images need to be mirrored on lcd to print on the correct orientation + /// public abstract bool MirrorDisplay { get; set; } + /// + /// Gets or sets the maximum printer build Z volume + /// public virtual float MaxPrintHeight { get => _maxPrintHeight > 0 ? _maxPrintHeight : PrintHeight; set => RaiseAndSetIfChanged(ref _maxPrintHeight, value); } + /// + /// Gets or sets the pixels per mm on X direction + /// public virtual float Xppmm { get => DisplayWidth > 0 ? ResolutionX / DisplayWidth : 0; @@ -433,6 +541,9 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets or sets the pixels per mm on Y direction + /// public virtual float Yppmm { get => DisplayHeight > 0 ? ResolutionY / DisplayHeight : 0; @@ -443,6 +554,9 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets or sets the pixels per mm + /// public SizeF Ppmm { get => new(Xppmm, Yppmm); @@ -454,6 +568,9 @@ namespace UVtools.Core.FileFormats } + /// + /// Gets the printer XY pixel resolution + /// public decimal XYResolution => DisplayWidth > 0 || DisplayHeight > 0 ? (decimal) Math.Round(Math.Max( DisplayWidth / ResolutionX, @@ -461,6 +578,9 @@ namespace UVtools.Core.FileFormats ), 3) : 0; + /// + /// Gets the printer XY pixel resolution in microns + /// public decimal XYResolutionUm => DisplayWidth > 0 || DisplayHeight > 0 ? (decimal)Math.Round(Math.Max( DisplayWidth / ResolutionX, @@ -468,11 +588,30 @@ namespace UVtools.Core.FileFormats ), 3) * 1000 : 0; + /// + /// Checks if this file have AntiAliasing + /// public bool HaveAntiAliasing => AntiAliasing > 1; + + /// + /// Gets or sets the AntiAliasing level + /// public abstract byte AntiAliasing { get; set; } + /// + /// Gets Layer Height in mm + /// public abstract float LayerHeight { get; set; } + /// + /// Gets Layer Height in um + /// + public ushort LayerHeightUm => (ushort) (LayerHeight * 1000); + + + /// + /// Gets or sets the print height in mm + /// public virtual float PrintHeight { get => LayerCount == 0 ? 0 : this[LayerCount - 1]?.PositionZ ?? 0; @@ -482,30 +621,97 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets the last layer index + /// public uint LastLayerIndex => LayerCount - 1; + + /// + /// Checks if this file format supports per layer settings + /// public virtual bool SupportPerLayerSettings => !(PrintParameterPerLayerModifiers is null || PrintParameterPerLayerModifiers.Length == 0); + /// + /// Gets or sets the layer count + /// public virtual uint LayerCount { get => LayerManager?.Count ?? 0; set { } } + #region Universal Properties + + /// + /// Gets or sets the number of initial layer count + /// public virtual ushort BottomLayerCount { get; set; } + + /// + /// Gets the number of normal layer count + /// public uint NormalLayerCount => LayerCount - BottomLayerCount; + + /// + /// Gets or sets the initial exposure time for in seconds + /// public virtual float BottomExposureTime { get; set; } + + /// + /// Gets or sets the normal layer exposure time in seconds + /// public virtual float ExposureTime { get; set; } + + /// + /// Gets or sets the bottom layer off time in seconds + /// public virtual float BottomLightOffDelay { get; set; } = DefaultBottomLightOffDelay; + + /// + /// Gets or sets the layer off time in seconds + /// public virtual float LightOffDelay { get; set; } = DefaultLightOffDelay; + + /// + /// Gets or sets the bottom lift height in mm + /// public virtual float BottomLiftHeight { get; set; } = DefaultBottomLiftHeight; + + /// + /// Gets or sets the lift height in mm + /// public virtual float LiftHeight { get; set; } = DefaultLiftHeight; + + /// + /// Gets or sets the bottom lift speed in mm/min + /// public virtual float BottomLiftSpeed { get; set; } = DefaultBottomLiftSpeed; + + /// + /// Gets or sets the speed in mm/min + /// public virtual float LiftSpeed { get; set; } = DefaultLiftSpeed; + + /// + /// Gets the speed in mm/min for the retracts + /// public virtual float RetractSpeed { get; set; } = DefaultRetractSpeed; + + /// + /// Gets or sets the bottom pwm value from 0 to 255 + /// public virtual byte BottomLightPWM { get; set; } = DefaultBottomLightPWM; + + /// + /// Gets or sets the pwm value from 0 to 255 + /// public virtual byte LightPWM { get; set; } = DefaultLightPWM; + #endregion + /// + /// Gets the estimate print time in seconds + /// public virtual float PrintTime { get => _printTime; @@ -520,9 +726,14 @@ namespace UVtools.Core.FileFormats } } - //(header.numberOfLayers - header.bottomLayers) * (double) header.exposureTimeSeconds + (double) header.bottomLayers * (double) header.exposureBottomTimeSeconds + (double) header.offTimeSeconds * (double) header.numberOfLayers); + /// + /// Gets the estimate print time in seconds, if print doesn't support it it will be calculated + /// public float PrintTimeOrComputed => PrintTime > 0 ? PrintTime : PrintTimeComputed; + /// + /// Gets the calculated estimate print time in seconds + /// public float PrintTimeComputed { get @@ -562,27 +773,65 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets the estimate print time in hours + /// public float PrintTimeHours => (float) Math.Round(PrintTimeOrComputed / 3600, 2); + /// + /// Gets the estimate print time in hours and minutes formatted + /// public string PrintTimeString => TimeSpan.FromSeconds(PrintTimeOrComputed).ToString("hh\\hmm\\m"); + /// + /// Gets the estimate used material in ml + /// public virtual float MaterialMilliliters { get; set; } + + /// + /// Gets the estimate material in grams + /// public virtual float MaterialGrams { get; set; } + /// + /// Gets the estimate material cost + /// public virtual float MaterialCost { get; set; } + /// + /// Gets the material name + /// public virtual string MaterialName { get; set; } + /// + /// Gets the machine name + /// public virtual string MachineName { get; set; } = "Unknown"; + /// + /// Gets the GCode, returns null if not supported + /// public StringBuilder GCode { get; set; } + /// + /// Gets the GCode, returns null if not supported + /// public string GCodeStr => GCode?.ToString(); - public bool HaveGCode => !(GCode is null); + /// + /// Gets if this file have available gcode + /// + public bool HaveGCode => GCode is not null; + + /// + /// Get all configuration objects with properties and values + /// public abstract object[] Configs { get; } - public bool IsValid => !ReferenceEquals(FileFullPath, null); + /// + /// Gets if this file is valid to read + /// + public bool IsValid => FileFullPath is not null; #endregion #region Constructor @@ -675,7 +924,7 @@ namespace UVtools.Core.FileFormats { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return FileFullPath.Equals(other.FileFullPath); + return FileFullPath == other.FileFullPath; } public override int GetHashCode() @@ -691,26 +940,31 @@ namespace UVtools.Core.FileFormats #endregion #region Methods + /// + /// Clears all definitions and properties, it also dispose valid candidates + /// public virtual void Clear() { FileFullPath = null; LayerManager = null; GCode = null; - if (!ReferenceEquals(Thumbnails, null)) + if (Thumbnails is not null) { for (int i = 0; i < ThumbnailsCount; i++) { Thumbnails[i]?.Dispose(); } } - - } + /// + /// Validate if a file is a valid + /// + /// Full file path public void FileValidation(string fileFullPath) { - if (ReferenceEquals(fileFullPath, null)) throw new ArgumentNullException(nameof(FileFullPath), "fullFilePath can't be null."); + if (fileFullPath is null) throw new ArgumentNullException(nameof(FileFullPath), "fullFilePath can't be null."); if (!File.Exists(fileFullPath)) throw new FileNotFoundException("The specified file does not exists.", fileFullPath); if (IsExtensionValid(fileFullPath, true)) @@ -721,12 +975,21 @@ namespace UVtools.Core.FileFormats throw new FileLoadException($"The specified file is not valid.", fileFullPath); } + /// + /// Checks if a extension is valid under the + /// + /// Extension to check + /// True if is a full file path, otherwise false for extension only + /// True if valid, otherwise false public bool IsExtensionValid(string extension, bool isFilePath = false) { extension = isFilePath ? Path.GetExtension(extension)?.Remove(0, 1) : extension; return FileExtensions.Any(fileExtension => fileExtension.Equals(extension)); } + /// + /// Gets all valid file extensions in a specified format + /// public string GetFileExtensions(string prepend = ".", string separator = ", ") { var result = string.Empty; @@ -743,6 +1006,11 @@ namespace UVtools.Core.FileFormats return result; } + /// + /// Gets a thumbnail by it height or lower + /// + /// Max height allowed + /// public Mat GetThumbnail(uint maxHeight = 400) { for (int i = 0; i < ThumbnailsCount; i++) @@ -754,6 +1022,11 @@ namespace UVtools.Core.FileFormats return null; } + /// + /// Gets a thumbnail by the largest or smallest + /// + /// True to get the largest, otherwise false + /// public Mat GetThumbnail(bool largest) { switch (CreatedThumbnailsCount) @@ -774,6 +1047,10 @@ namespace UVtools.Core.FileFormats } } + /// + /// Sets thumbnails from a list of thumbnails and clone them + /// + /// public void SetThumbnails(Mat[] images) { for (var i = 0; i < ThumbnailsCount; i++) @@ -786,6 +1063,10 @@ namespace UVtools.Core.FileFormats } } + /// + /// Sets all thumbnails the same image + /// + /// Image to set public void SetThumbnails(Mat image) { for (var i = 0; i < ThumbnailsCount; i++) @@ -798,6 +1079,11 @@ namespace UVtools.Core.FileFormats } } + /// + /// Sets a thumbnail from a disk file + /// + /// Thumbnail index + /// public void SetThumbnail(int index, string filePath) { Thumbnails[index] = CvInvoke.Imread(filePath, ImreadModes.AnyColor); @@ -807,8 +1093,23 @@ namespace UVtools.Core.FileFormats } } - public virtual void Encode(string fileFullPath, OperationProgress progress = null) + /// + /// Encode to an output file + /// + /// Output file + /// + protected abstract void EncodeInternally(string fileFullPath, OperationProgress progress); + + /// + /// Encode to an output file + /// + /// Output file + /// + public void Encode(string fileFullPath, OperationProgress progress = null) { + progress ??= new OperationProgress(); + progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); + FileFullPath = fileFullPath; if (File.Exists(fileFullPath)) @@ -822,25 +1123,69 @@ namespace UVtools.Core.FileFormats if(Thumbnails[i].Size == ThumbnailsOriginalSize[i]) continue; CvInvoke.Resize(Thumbnails[i], Thumbnails[i], new Size(ThumbnailsOriginalSize[i].Width, ThumbnailsOriginalSize[i].Height)); } - } - public void AfterEncode() - { + EncodeInternally(fileFullPath, progress); + LayerManager.Desmodify(); RequireFullEncode = false; } - public virtual void Decode(string fileFullPath, OperationProgress progress = null) + /// + /// Decode a slicer file + /// + /// + /// + protected abstract void DecodeInternally(string fileFullPath, OperationProgress progress); + + /// + /// Decode a slicer file + /// + /// + /// + public void Decode(string fileFullPath, OperationProgress progress = null) { Clear(); FileValidation(fileFullPath); FileFullPath = fileFullPath; + progress ??= new OperationProgress(); + progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); + + DecodeInternally(fileFullPath, progress); + + progress.Token.ThrowIfCancellationRequested(); + + // Sanitize + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + // Check for null layers + if(this[layerIndex] is null) throw new FileLoadException($"Layer {layerIndex} was defined but doesn't contain a valid image.", fileFullPath); + if(layerIndex <= 0) continue; + // Check for bigger position z than it successor + if(this[layerIndex-1].PositionZ > this[layerIndex].PositionZ) throw new FileLoadException($"Layer {layerIndex-1} ({this[layerIndex - 1].PositionZ}mm) have a higher Z position than the successor layer {layerIndex} ({this[layerIndex].PositionZ}mm).\n", fileFullPath); + } + + // Fix 0mm positions at layer 0 + if(this[0].PositionZ == 0) + { + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + this[layerIndex].PositionZ += LayerHeight; + } + Save(progress); + } } + /// + /// Extract contents to a folder + /// + /// Path to folder where content will be extracted + /// + /// + /// public virtual void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true, OperationProgress progress = null) { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress ??= new OperationProgress(); progress.ItemName = OperationProgress.StatusExtracting; /*if (emptyFirst) { @@ -955,16 +1300,33 @@ namespace UVtools.Core.FileFormats } } + /// + /// Get height in mm from layer height + /// + /// + /// + /// The height in mm public float GetHeightFromLayer(uint layerIndex, bool realHeight = true) { return (float)Math.Round((layerIndex+(realHeight ? 1 : 0)) * LayerHeight, 2); } + /// + /// Gets the value for initial layer or normal layers based on layer index + /// + /// Type of value + /// Layer index + /// Initial value + /// Normal value + /// public T GetInitialLayerValueOrNormal(uint layerIndex, T initialLayerValue, T normalLayerValue) { return layerIndex < BottomLayerCount ? initialLayerValue : normalLayerValue; } + /// + /// Refresh print parameters globals with this file settings + /// public void RefreshPrintParametersModifiersValues() { if (PrintParameterModifiers is null) return; @@ -1029,6 +1391,9 @@ namespace UVtools.Core.FileFormats } } + /// + /// Refresh print parameters per layer globals with this file settings + /// public void RefreshPrintParametersPerLayerModifiersValues(uint layerIndex) { if (PrintParameterPerLayerModifiers is null) return; @@ -1065,6 +1430,11 @@ namespace UVtools.Core.FileFormats } } + /// + /// Gets the value attributed to + /// + /// Modifier to use + /// A value public object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) { if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerCount)) @@ -1098,6 +1468,12 @@ namespace UVtools.Core.FileFormats return null; } + /// + /// Sets a property value attributed to + /// + /// Modifier to use + /// Value to set + /// True if set, otherwise false = not found public bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, decimal value) { if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerCount)) @@ -1167,6 +1543,10 @@ namespace UVtools.Core.FileFormats return false; } + /// + /// Sets properties from print parameters + /// + /// Number of affected parameters public byte SetValuesFromPrintParametersModifiers() { if (PrintParameterModifiers is null) return 0; @@ -1182,36 +1562,34 @@ namespace UVtools.Core.FileFormats return changed; } - public void EditPrintParameters(OperationEditParameters operation) - { - if (operation.PerLayerOverride) - { - for (uint layerIndex = operation.LayerIndexStart; layerIndex <= operation.LayerIndexEnd; layerIndex++) - { - this[layerIndex].SetValuesFromPrintParametersModifiers(operation.Modifiers); - } - - foreach (var modifier in operation.Modifiers) - { - modifier.OldValue = modifier.NewValue; - } - RebuildGCode(); - } - else - { - SetValuesFromPrintParametersModifiers(); - } - } - + /// + /// Rebuilds GCode based on current settings + /// public virtual void RebuildGCode() { } + /// + /// Saves current configuration on input file + /// + /// public void Save(OperationProgress progress = null) { SaveAs(null, progress); } + /// + /// Saves current configuration on a copy + /// + /// File path to save copy as, use null to overwrite active file (Same as ) + /// public abstract void SaveAs(string filePath = null, OperationProgress progress = null); + /// + /// Converts this file type to another file type + /// + /// Target file format + /// Output path file + /// + /// The converted file if successful, otherwise null public virtual FileFormat Convert(Type to, string fileFullPath, OperationProgress progress = null) { if (!IsValid) return null; @@ -1270,9 +1648,20 @@ namespace UVtools.Core.FileFormats return slicerFile; } + + /// + /// Converts this file type to another file type + /// + /// Target file format + /// Output path file + /// + /// TThe converted file if successful, otherwise null public FileFormat Convert(FileFormat to, string fileFullPath, OperationProgress progress = null) => Convert(to.GetType(), fileFullPath, progress); + /// + /// Validate AntiAlias Level + /// public byte ValidateAntiAliasingLevel() { if (AntiAliasing < 2) return 1; diff --git a/UVtools.Core/FileFormats/IFileFormat.cs b/UVtools.Core/FileFormats/IFileFormat.cs deleted file mode 100644 index e96bb0a..0000000 --- a/UVtools.Core/FileFormats/IFileFormat.cs +++ /dev/null @@ -1,510 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.Drawing; -using System.Text; -using Emgu.CV; -using UVtools.Core.Operations; - -namespace UVtools.Core.FileFormats -{ - /// - /// Slicer file format representation interface - /// - public interface IFileFormat - { - #region Properties - /// - /// Gets the file format type - /// - FileFormat.FileFormatType FileType { get; } - - /// - /// Gets the valid file extensions for this - /// - FileExtension[] FileExtensions { get; } - - /// - /// Gets the available - /// - FileFormat.PrintParameterModifier[] PrintParameterModifiers { get; } - - /// - /// Gets the available per layer - /// - FileFormat.PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } - - /// - /// Gets the file filter for open and save dialogs - /// - string FileFilter { get; } - - /// - /// Gets all valid file extensions in "*.extension1;*.extension2" format - /// - - string FileFilterExtensionsOnly { get; } - - /// - /// Gets or sets if change a global property should rebuild every layer data based on them - /// - bool SuppressRebuildProperties { get; set; } - - /// - /// Gets the input file path loaded into this - /// - string FileFullPath { get; set; } - - /// - /// Gets the thumbnails count present in this file format - /// - byte ThumbnailsCount { get; } - - /// - /// Gets the number of created thumbnails - /// - byte CreatedThumbnailsCount { get; } - - /// - /// Gets the original thumbnail sizes - /// - System.Drawing.Size[] ThumbnailsOriginalSize { get; } - - /// - /// Gets the thumbnails for this - /// - Mat[] Thumbnails { get; set; } - - /// - /// Gets the cached layers into compressed bytes - /// - LayerManager LayerManager { get; set; } - - Size Resolution { get; set; } - - /// - /// Gets the image width resolution - /// - uint ResolutionX { get; set; } - - /// - /// Gets the image height resolution - /// - uint ResolutionY { get; set; } - - /// - /// Gets the size of display in millimeters - /// - SizeF Display { get; set; } - - /// - /// Gets or sets the display width in millimeters - /// - float DisplayWidth { get; set; } - - /// - /// Gets or sets the display height in millimeters - /// - float DisplayHeight { get; set; } - - /// - /// Gets or sets if images need to be mirrored on lcd to print on the correct orientation - /// - bool MirrorDisplay { get; set; } - - /// - /// Gets or sets the maximum printer build Z volume - /// - float MaxPrintHeight { get; set; } - - /// - /// Gets or sets the pixels per mm on X direction - /// - float Xppmm { get; set; } - - /// - /// Gets or sets the pixels per mm on Y direction - /// - float Yppmm { get; set; } - - /// - /// Gets or sets the pixels per mm - /// - SizeF Ppmm { get; set; } - - /// - /// Gets the printer XY pixel resolution - /// - decimal XYResolution { get; } - - /// - /// Gets the printer XY pixel resolution in microns - /// - decimal XYResolutionUm { get; } - - bool HaveAntiAliasing { get; } - - /// - /// Gets or sets the AntiAliasing level - /// - byte AntiAliasing { get; set; } - - /// - /// Gets Layer Height in mm - /// - float LayerHeight { get; set; } - - /// - /// Gets or sets the print height in mm - /// - float PrintHeight { get; set; } - - /// - /// Gets the last layer index - /// - uint LastLayerIndex { get; } - - #region Universal Properties - - /// - /// Gets if this format support per layer override settings - /// - bool SupportPerLayerSettings { get; } - - /// - /// Gets the number of layers present in this file - /// - uint LayerCount { get; set; } - - /// - /// Gets or sets the number of initial layer count - /// - ushort BottomLayerCount { get; set; } - - /// - /// Gets the number of normal layer count - /// - uint NormalLayerCount { get; } - - /// - /// Gets or sets the initial exposure time for in seconds - /// - float BottomExposureTime { get; set; } - - /// - /// Gets or sets the normal layer exposure time in seconds - /// - float ExposureTime { get; set; } - - /// - /// Gets or sets the bottom layer off time in seconds - /// - float BottomLightOffDelay { get; set; } - - /// - /// Gets or sets the layer off time in seconds - /// - float LightOffDelay { get; set; } - - /// - /// Gets or sets the bottom lift height in mm - /// - float BottomLiftHeight { get; set; } - - /// - /// Gets or sets the lift height in mm - /// - float LiftHeight { get; set; } - - /// - /// Gets or sets the bottom lift speed in mm/min - /// - float BottomLiftSpeed { get; set; } - - /// - /// Gets or sets the speed in mm/min - /// - float LiftSpeed { get; set; } - - /// - /// Gets the speed in mm/min for the retracts - /// - float RetractSpeed { get; set; } - - /// - /// Gets or sets the bottom pwm value from 0 to 255 - /// - byte BottomLightPWM { get; set; } - - /// - /// Gets or sets the pwm value from 0 to 255 - /// - byte LightPWM { get; set; } - #endregion - - /// - /// Gets the estimate print time in seconds - /// - float PrintTime { get; set; } - - /// - /// Gets the estimate print time in seconds, if print doesn't support it it will be calculated - /// - float PrintTimeOrComputed { get; } - - /// - /// Gets the calculated estimate print time in seconds - /// - float PrintTimeComputed { get; } - - /// - /// Gets the estimate print time in hours - /// - float PrintTimeHours { get; } - - /// - /// Gets the estimate print time in hours and minutes formatted - /// - string PrintTimeString { get; } - - /// - /// Gets the estimate used material in ml - /// - float MaterialMilliliters { get; set; } - - /// - /// Gets the estimate material in grams - /// - float MaterialGrams { get; set; } - - /// - /// Gets the estimate material cost - /// - float MaterialCost { get; set; } - - /// - /// Gets the material name - /// - string MaterialName { get; set; } - - /// - /// Gets the machine name - /// - string MachineName { get; set; } - - /// - /// Gets the GCode, returns null if not supported - /// - StringBuilder GCode { get; set; } - string GCodeStr { get; } - - /// - /// Gets if this file have available gcode - /// - bool HaveGCode { get; } - - /// - /// Get all configuration objects with properties and values - /// - object[] Configs { get; } - - /// - /// Gets if this file is valid to read - /// - bool IsValid { get; } - - #endregion - - #region Methods - /// - /// Clears all definitions and properties, it also dispose valid candidates - /// - void Clear(); - - /// - /// Validate if a file is a valid - /// - /// Full file path - void FileValidation(string fileFullPath); - - /// - /// Checks if a extension is valid under the - /// - /// Extension to check - /// True if is a full file path, otherwise false for extension only - /// True if valid, otherwise false - bool IsExtensionValid(string extension, bool isFilePath = false); - - /// - /// Gets all valid file extensions in a specified format - /// - - string GetFileExtensions(string prepend = ".", string separator = ", "); - - /// - /// Gets a thumbnail by it height or lower - /// - /// Max height allowed - /// - Mat GetThumbnail(uint maxHeight = 400); - - /// - /// Gets a thumbnail by the largest or smallest - /// - /// True to get the largest, otherwise false - /// - Mat GetThumbnail(bool largest); - - /// - /// Sets thumbnails from a list of thumbnails and clone them - /// - /// - void SetThumbnails(Mat[] images); - - /// - /// Sets all thumbnails the same image - /// - /// Image to set - void SetThumbnails(Mat images); - - /// - /// Sets a thumbnail from a disk file - /// - /// Thumbnail index - /// - void SetThumbnail(int index, string filePath); - - /// - /// Encode to an output file - /// - /// Output file - /// - void Encode(string fileFullPath, OperationProgress progress = null); - void AfterEncode(); - - /* - /// - /// Begin encode to an output file - /// - /// Output file - //void BeginEncode(string fileFullPath); - - /// - /// Insert a layer image to be encoded - /// - /// - /// - //void InsertLayerImageEncode(Image image, uint layerIndex); - - /// - /// Finish the encoding procedure - /// - //void EndEncode();*/ - - /// - /// Decode a slicer file - /// - /// - /// - void Decode(string fileFullPath, OperationProgress progress = null); - - /// - /// Extract contents to a folder - /// - /// Path to folder where content will be extracted - /// - /// - /// - void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true, - OperationProgress progress = null); - - /// - /// Get height in mm from layer height - /// - /// - /// - /// The height in mm - float GetHeightFromLayer(uint layerIndex, bool realHeight = true); - - /// - /// Gets the value for initial layer or normal layers based on layer index - /// - /// Type of value - /// Layer index - /// Initial value - /// Normal value - /// - T GetInitialLayerValueOrNormal(uint layerIndex, T initialLayerValue, T normalLayerValue); - - void RefreshPrintParametersModifiersValues(); - void RefreshPrintParametersPerLayerModifiersValues(uint layerIndex); - - /// - /// Gets the value attributed to - /// - /// Modifier to use - /// A value - object GetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier); - - /// - /// Sets a property value attributed to - /// - /// Modifier to use - /// Value to set - /// True if set, otherwise false = not found - bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, decimal value); - - byte SetValuesFromPrintParametersModifiers(); - - void EditPrintParameters(OperationEditParameters operation); - - /// - /// Rebuilds GCode based on current settings - /// - void RebuildGCode(); - - /// - /// Saves current configuration on input file - /// - /// - void Save(OperationProgress progress = null); - - /// - /// Saves current configuration on a copy - /// - /// File path to save copy as, use null to overwrite active file (Same as ) - /// - void SaveAs(string filePath = null, OperationProgress progress = null); - - /// - /// Converts this file type to another file type - /// - /// Target file format - /// Output path file - /// - /// The converted file if successful, otherwise null - FileFormat Convert(Type to, string fileFullPath, OperationProgress progress = null); - - /// - /// Converts this file type to another file type - /// - /// Target file format - /// Output path file - /// - /// TThe converted file if successful, otherwise null - FileFormat Convert(FileFormat to, string fileFullPath, OperationProgress progress = null); - - /// - /// Validate AntiAlias Level - /// - byte ValidateAntiAliasingLevel(); - - #endregion - } -} diff --git a/UVtools.Core/FileFormats/ImageFile.cs b/UVtools.Core/FileFormats/ImageFile.cs index 490b286..c97ff1d 100644 --- a/UVtools.Core/FileFormats/ImageFile.cs +++ b/UVtools.Core/FileFormats/ImageFile.cs @@ -76,10 +76,13 @@ namespace UVtools.Core.FileFormats private Mat ImageMat { get; set; } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); + throw new NotSupportedException(); + } + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) + { ImageMat = CvInvoke.Imread(fileFullPath, ImreadModes.Grayscale); const byte startDivisor = 2; for (int i = 0; i < ThumbnailsCount; i++) @@ -106,7 +109,7 @@ namespace UVtools.Core.FileFormats public override FileFormat Convert(Type to, string fileFullPath, OperationProgress progress = null) { - throw new NotImplementedException(); + throw new NotSupportedException(); } diff --git a/UVtools.Core/FileFormats/LGSFile.cs b/UVtools.Core/FileFormats/LGSFile.cs index 84806fc..52f56a1 100644 --- a/UVtools.Core/FileFormats/LGSFile.cs +++ b/UVtools.Core/FileFormats/LGSFile.cs @@ -450,12 +450,9 @@ namespace UVtools.Core.FileFormats return bytes; } - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) + { if (ResolutionY >= 2560) // Longer Orange 30 { HeaderSettings.Float_94 = 170; @@ -495,8 +492,6 @@ namespace UVtools.Core.FileFormats } } - AfterEncode(); - Debug.WriteLine("Encode Results:"); Debug.WriteLine(HeaderSettings); Debug.WriteLine("-End-"); @@ -522,10 +517,8 @@ namespace UVtools.Core.FileFormats return mat; } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) { HeaderSettings = Helpers.Deserialize
(inputFile); @@ -570,25 +563,17 @@ namespace UVtools.Core.FileFormats { if (progress.Token.IsCancellationRequested) return; - using (var image = layerData[layerIndex].Decode()) - { - this[layerIndex] = new Layer((uint) layerIndex, image, LayerManager); + using var image = layerData[layerIndex].Decode(); + this[layerIndex] = new Layer((uint) layerIndex, image, LayerManager); - lock (progress.Mutex) - { - progress++; - } + lock (progress.Mutex) + { + progress++; } }); LayerManager.RebuildLayersProperties(); - - - FileFullPath = fileFullPath; - } - - progress.Token.ThrowIfCancellationRequested(); } public override void SaveAs(string filePath = null, OperationProgress progress = null) diff --git a/UVtools.Core/FileFormats/MakerbaseFile.cs b/UVtools.Core/FileFormats/MakerbaseFile.cs index 218e54e..78f678e 100644 --- a/UVtools.Core/FileFormats/MakerbaseFile.cs +++ b/UVtools.Core/FileFormats/MakerbaseFile.cs @@ -150,10 +150,8 @@ namespace UVtools.Core.FileFormats #endregion #region Methods - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - base.Encode(fileFullPath, progress); - uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) { @@ -164,19 +162,15 @@ namespace UVtools.Core.FileFormats } - AfterEncode(); - Debug.WriteLine("Encode Results:"); Debug.WriteLine(HeaderSettings); Debug.WriteLine("-End-"); } - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) + { using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) { //HeaderSettings = Helpers.ByteToType(InputFile); @@ -186,13 +180,7 @@ namespace UVtools.Core.FileFormats { throw new FileLoadException("Not a valid Makerfile file!", fileFullPath); } - - - FileFullPath = fileFullPath; - } - - progress.Token.ThrowIfCancellationRequested(); } public override void SaveAs(string filePath = null, OperationProgress progress = null) diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs index 4f9a6a3..76b45ea 100644 --- a/UVtools.Core/FileFormats/PHZFile.cs +++ b/UVtools.Core/FileFormats/PHZFile.cs @@ -1017,11 +1017,8 @@ namespace UVtools.Core.FileFormats LayersDefinitions = null; } - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); LayersHash.Clear(); /*if (HeaderSettings.EncryptionKey == 0) @@ -1139,8 +1136,6 @@ namespace UVtools.Core.FileFormats outputFile.Seek(0, SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - AfterEncode(); - Debug.WriteLine("Encode Results:"); Debug.WriteLine(HeaderSettings); Debug.WriteLine(Previews[0]); @@ -1149,11 +1144,8 @@ namespace UVtools.Core.FileFormats } } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - if(progress is null) progress = new OperationProgress(OperationProgress.StatusGatherLayers, LayerCount); - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) { @@ -1167,9 +1159,6 @@ namespace UVtools.Core.FileFormats HeaderSettings.AntiAliasLevel = 1; - FileFullPath = fileFullPath; - - progress.Reset(OperationProgress.StatusDecodeThumbnails, ThumbnailsCount); Debug.Write("Header -> "); Debug.WriteLine(HeaderSettings); @@ -1256,8 +1245,6 @@ namespace UVtools.Core.FileFormats } }); } - - progress.Token.ThrowIfCancellationRequested(); } public override void SaveAs(string filePath = null, OperationProgress progress = null) diff --git a/UVtools.Core/FileFormats/PhotonSFile.cs b/UVtools.Core/FileFormats/PhotonSFile.cs index 4b14faf..c36ec5f 100644 --- a/UVtools.Core/FileFormats/PhotonSFile.cs +++ b/UVtools.Core/FileFormats/PhotonSFile.cs @@ -125,11 +125,12 @@ namespace UVtools.Core.FileFormats for (int i = 0; i < imageLength; i++) { - color = color <= 127 ? 0 : 255; // Sanitize no AA - if (spanMat[i] != color) + //color = color <= 127 ? 0 : 255; // Sanitize no AA + byte thisColor = spanMat[i] <= 127 ? 0 : 255; // Sanitize no AA + if (thisColor != color) { AddRep(); - color = spanMat[i]; + color = thisColor; // Sanitize no AA rep = 1; } else @@ -420,12 +421,10 @@ namespace UVtools.Core.FileFormats return bytes; } - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) + { + throw new NotSupportedException("PhotonS is read-only format, please use pws instead!"); //uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) { @@ -463,8 +462,6 @@ namespace UVtools.Core.FileFormats } } - AfterEncode(); - Debug.WriteLine("Encode Results:"); Debug.WriteLine(HeaderSettings); Debug.WriteLine("-End-"); @@ -495,10 +492,8 @@ namespace UVtools.Core.FileFormats return mat; } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) { HeaderSettings = Helpers.Deserialize
(inputFile); @@ -546,24 +541,16 @@ namespace UVtools.Core.FileFormats { if (progress.Token.IsCancellationRequested) return; - using (var image = layerData[layerIndex].Decode()) + using var image = layerData[layerIndex].Decode(); + this[layerIndex] = new Layer((uint) layerIndex, image, LayerManager); + lock (progress.Mutex) { - this[layerIndex] = new Layer((uint) layerIndex, image, LayerManager); - lock (progress.Mutex) - { - progress++; - } + progress++; } }); LayerManager.RebuildLayersProperties(); - - - FileFullPath = fileFullPath; - } - - progress.Token.ThrowIfCancellationRequested(); } public override void SaveAs(string filePath = null, OperationProgress progress = null) diff --git a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs index c4b9edc..eafecf9 100644 --- a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs +++ b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs @@ -1048,11 +1048,8 @@ namespace UVtools.Core.FileFormats LayersDefinition = null; } - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); LayersHash.Clear(); LayersDefinition = new LayerDefinition(LayerCount); @@ -1124,14 +1121,10 @@ namespace UVtools.Core.FileFormats outputFile.Seek(0, SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, FileMarkSettings); } - - AfterEncode(); } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) { FileMarkSettings = Helpers.Deserialize(inputFile); @@ -1235,8 +1228,6 @@ namespace UVtools.Core.FileFormats } }); } - - progress.Token.ThrowIfCancellationRequested(); } public override void SaveAs(string filePath = null, OperationProgress progress = null) diff --git a/UVtools.Core/FileFormats/SL1File.cs b/UVtools.Core/FileFormats/SL1File.cs index 005b708..c5fd792 100644 --- a/UVtools.Core/FileFormats/SL1File.cs +++ b/UVtools.Core/FileFormats/SL1File.cs @@ -252,22 +252,30 @@ namespace UVtools.Core.FileFormats public class OutputConfig { - public string Action { get; set; } + public string Action { get; set; } = "print"; public string JobDir { get; set; } public float ExpTime { get; set; } public float ExpTimeFirst { get; set; } - public string FileCreationTimestamp { get; set; } + //public string FileCreationTimestamp { get; set; } + public string FileCreationTimestamp { + get + { + //2021-01-23 at 04:07:36 UTC + var now = DateTime.Now; + return $"{now.Year}-{now.Month:D2}-{now.Day:D2} at {now.Hour:D2}:{now.Minute:D2}:{now.Second:D2} {now.Kind}"; + } + } public float LayerHeight { get; set; } - public string MaterialName { get; set; } + public string MaterialName { get; set; } = About.Software; public ushort NumFade { get; set; } public ushort NumFast { get; set; } public ushort NumSlow { get; set; } - public string PrintProfile { get; set; } + public string PrintProfile { get; set; } = About.Software; public float PrintTime { get; set; } - public string PrinterModel { get; set; } - public string PrinterProfile { get; set; } - public string PrinterVariant { get; set; } - public string PrusaSlicerVersion { get; set; } + public string PrinterModel { get; set; } = "SL1"; + public string PrinterProfile { get; set; } = About.Software; + public string PrinterVariant { get; set; } = "default"; + public string PrusaSlicerVersion { get; set; } = "PrusaSlicer-2.3.0+win64-202101111315"; public float UsedMaterial { get; set; } public override string ToString() @@ -519,10 +527,12 @@ namespace UVtools.Core.FileFormats Statistics.Clear(); } - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - base.Encode(fileFullPath, progress); - + var filename = fileFullPath; + if (filename.EndsWith(TemporaryFileAppend)) filename = Path.GetFileNameWithoutExtension(filename); // tmp + filename = Path.GetFileNameWithoutExtension(filename); // sl1 + OutputConfigSettings.JobDir = filename; using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) { var entry = outputFile.CreateEntry("config.ini"); @@ -562,39 +572,28 @@ namespace UVtools.Core.FileFormats foreach (var thumbnail in Thumbnails) { if (ReferenceEquals(thumbnail, null)) continue; - using (var stream = outputFile.CreateEntry($"thumbnail/thumbnail{thumbnail.Width}x{thumbnail.Height}.png").Open()) - { - var vec = new VectorOfByte(); - CvInvoke.Imencode(".png", thumbnail, vec); - stream.WriteBytes(vec.ToArray()); - stream.Close(); - } + using var stream = outputFile.CreateEntry($"thumbnail/thumbnail{thumbnail.Width}x{thumbnail.Height}.png").Open(); + var vec = new VectorOfByte(); + CvInvoke.Imencode(".png", thumbnail, vec); + stream.WriteBytes(vec.ToArray()); + stream.Close(); } for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { progress.Token.ThrowIfCancellationRequested(); Layer layer = this[layerIndex]; - var layerImagePath = $"{Path.GetFileNameWithoutExtension(fileFullPath)}{layerIndex:D5}.png"; + var layerImagePath = $"{filename}{layerIndex:D5}.png"; //layer.Filename = layerImagePath; outputFile.PutFileContent(layerImagePath, layer.CompressedBytes, ZipArchiveMode.Create); progress++; } } - - AfterEncode(); } - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); - - FileFullPath = fileFullPath; + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) + { PrinterSettings = new Printer(); MaterialSettings = new Material(); PrintSettings = new Print(); @@ -602,7 +601,7 @@ namespace UVtools.Core.FileFormats Statistics.ExecutionTime.Restart(); - using (var inputFile = ZipFile.OpenRead(FileFullPath)) + using (var inputFile = ZipFile.OpenRead(fileFullPath)) { List iniFiles = new(); foreach (ZipArchiveEntry entity in inputFile.Entries) @@ -625,7 +624,7 @@ namespace UVtools.Core.FileFormats foreach (var obj in Configs) { var attribute = obj.GetType().GetProperty(fieldName); - if (ReferenceEquals(attribute, null)) continue; + if (attribute is null || !attribute.CanWrite) continue; //Debug.WriteLine(attribute.Name); Helpers.SetPropertyValue(attribute, obj, keyValue[1]); diff --git a/UVtools.Core/FileFormats/UVJFile.cs b/UVtools.Core/FileFormats/UVJFile.cs index c74f438..f638aea 100644 --- a/UVtools.Core/FileFormats/UVJFile.cs +++ b/UVtools.Core/FileFormats/UVJFile.cs @@ -360,13 +360,8 @@ namespace UVtools.Core.FileFormats JsonSettings.Layers = new List(); } - public override void Encode(string fileFullPath, OperationProgress progress = null) + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - - base.Encode(fileFullPath, progress); - // Redo layer data JsonSettings.Layers.Clear(); for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) @@ -430,17 +425,11 @@ namespace UVtools.Core.FileFormats progress++; } } - AfterEncode(); } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - if(progress is null) progress = new OperationProgress(); - progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); - - FileFullPath = fileFullPath; - using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) + using (var inputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Read)) { var entry = inputFile.GetEntry(FileConfigName); if (ReferenceEquals(entry, null)) @@ -466,21 +455,19 @@ namespace UVtools.Core.FileFormats } entry = inputFile.GetEntry(FilePreviewHugeName); - if (!ReferenceEquals(entry, null)) + if (entry is not null) { - using (Stream stream = entry.Open()) - { - Mat image = new Mat(); - CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); - Thumbnails[1] = image; - stream.Close(); - } + using Stream stream = entry.Open(); + Mat image = new Mat(); + CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); + Thumbnails[1] = image; + stream.Close(); } for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { entry = inputFile.GetEntry($"{FolderImageName}/{layerIndex:D8}.png"); - if (ReferenceEquals(entry, null)) continue; + if (entry is null) continue; this[layerIndex] = new Layer(layerIndex, entry.Open(), LayerManager) { diff --git a/UVtools.Core/FileFormats/ZCodexFile.cs b/UVtools.Core/FileFormats/ZCodexFile.cs index 208cd29..f29b907 100644 --- a/UVtools.Core/FileFormats/ZCodexFile.cs +++ b/UVtools.Core/FileFormats/ZCodexFile.cs @@ -382,12 +382,8 @@ namespace UVtools.Core.FileFormats LayersSettings.Clear(); } - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - progress ??= new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - base.Encode(fileFullPath, progress); - + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) + { float usedMaterial = MaterialMilliliters / LayerCount; ResinMetadataSettings.Layers.Clear(); for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) @@ -471,18 +467,14 @@ namespace UVtools.Core.FileFormats outputFile.PutFileContent("ResinGCodeData", GCode.ToString(), ZipArchiveMode.Create); } - AfterEncode(); } - public override void Decode(string fileFullPath, OperationProgress progress = null) + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) { - base.Decode(fileFullPath, progress); - - FileFullPath = fileFullPath; - using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) + using (var inputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Read)) { var entry = inputFile.GetEntry("ResinMetadata"); - if (ReferenceEquals(entry, null)) + if (entry is null) { Clear(); throw new FileLoadException("ResinMetadata not found", fileFullPath); @@ -491,7 +483,7 @@ namespace UVtools.Core.FileFormats ResinMetadataSettings = Helpers.JsonDeserializeObject(entry.Open()); entry = inputFile.GetEntry("UserSettingsData"); - if (ReferenceEquals(entry, null)) + if (entry is null) { Clear(); throw new FileLoadException("UserSettingsData not found", fileFullPath); @@ -500,7 +492,7 @@ namespace UVtools.Core.FileFormats UserSettings = Helpers.JsonDeserializeObject(entry.Open()); entry = inputFile.GetEntry("ZCodeMetadata"); - if (ReferenceEquals(entry, null)) + if (entry is null) { Clear(); throw new FileLoadException("ZCodeMetadata not found", fileFullPath); @@ -509,7 +501,7 @@ namespace UVtools.Core.FileFormats ZCodeMetadataSettings = Helpers.JsonDeserializeObject(entry.Open()); entry = inputFile.GetEntry("ResinGCodeData"); - if (ReferenceEquals(entry, null)) + if (entry is null) { Clear(); throw new FileLoadException("ResinGCodeData not found", fileFullPath); @@ -615,12 +607,9 @@ M106 S0 entry = inputFile.GetEntry("Preview.png"); if (!ReferenceEquals(entry, null)) { - using (Stream stream = entry.Open()) - { - - CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, Thumbnails[0]); - stream.Close(); - } + using Stream stream = entry.Open(); + CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, Thumbnails[0]); + stream.Close(); } } diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs index ebec600..74ca871 100644 --- a/UVtools.Core/Layer/Layer.cs +++ b/UVtools.Core/Layer/Layer.cs @@ -7,17 +7,15 @@ */ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; -using System.IO.Compression; using System.Linq; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; using Emgu.CV.Util; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; using UVtools.Core.Objects; -using UVtools.Core.Operations; using Stream = System.IO.Stream; namespace UVtools.Core @@ -27,9 +25,24 @@ namespace UVtools.Core ///
public class Layer : BindableBase, IEquatable, IEquatable { + #region Members + private byte[] _compressedBytes; + private uint _nonZeroPixelCount; + private Rectangle _boundingRectangle = Rectangle.Empty; + private bool _isModified; + private uint _index; + private float _positionZ; + private float _exposureTime; + private float _lightOffDelay = FileFormat.DefaultLightOffDelay; + private float _liftHeight = FileFormat.DefaultLiftHeight; + private float _liftSpeed = FileFormat.DefaultLiftSpeed; + private float _retractSpeed = FileFormat.DefaultRetractSpeed; + private byte _lightPwm = FileFormat.DefaultLightPWM; + #endregion + #region Properties - public object Mutex = new object(); + public object Mutex = new(); /// /// Gets the parent layer manager @@ -152,21 +165,35 @@ namespace UVtools.Core public float PositionZ { get => _positionZ; - set => RaiseAndSetIfChanged(ref _positionZ, value); + set + { + if(!RaiseAndSetIfChanged(ref _positionZ, value)) return; + RaisePropertyChanged(nameof(LayerHeight)); + } } - private byte[] _compressedBytes; - private uint _nonZeroPixelCount; - private Rectangle _boundingRectangle = Rectangle.Empty; - private bool _isModified; - private uint _index; - private float _positionZ; - private float _exposureTime; - private float _lightOffDelay = FileFormat.DefaultLightOffDelay; - private float _liftHeight = FileFormat.DefaultLiftHeight; - private float _liftSpeed = FileFormat.DefaultLiftSpeed; - private float _retractSpeed = FileFormat.DefaultRetractSpeed; - private byte _lightPwm = FileFormat.DefaultLightPWM; + /// + /// Gets the layer height in millimeters of this layer + /// + public float LayerHeight + { + get + { + if (_index == 0) return _positionZ; + Layer previousLayer = this; + + while ((previousLayer = previousLayer.PreviousLayer()) is not null) // This cycle returns the correct layer height if two or more layers have the same position z + { + var layerHeight = (float)Math.Round(_positionZ - previousLayer.PositionZ, 2); + //Debug.WriteLine($"Layer {_index}-{previousLayer.Index}: {_positionZ} - {previousLayer.PositionZ}: {layerHeight}"); + if (layerHeight == 0f) continue; + if (layerHeight < 0f) break; + return layerHeight; + } + + return ParentLayerManager.SlicerFile.LayerHeight; + } + } /// /// Gets or sets layer image compressed data @@ -348,7 +375,7 @@ namespace UVtools.Core public override string ToString() { - return $"{nameof(Index)}: {Index}, {nameof(Filename)}: {Filename}, {nameof(NonZeroPixelCount)}: {NonZeroPixelCount}, {nameof(BoundingRectangle)}: {BoundingRectangle}, {nameof(IsBottomLayer)}: {IsBottomLayer}, {nameof(IsNormalLayer)}: {IsNormalLayer}, {nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(LightPWM)}: {LightPWM}, {nameof(IsModified)}: {IsModified}"; + return $"{nameof(Index)}: {Index}, {nameof(Filename)}: {Filename}, {nameof(NonZeroPixelCount)}: {NonZeroPixelCount}, {nameof(BoundingRectangle)}: {BoundingRectangle}, {nameof(IsBottomLayer)}: {IsBottomLayer}, {nameof(IsNormalLayer)}: {IsNormalLayer}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(LightPWM)}: {LightPWM}, {nameof(IsModified)}: {IsModified}"; } #endregion @@ -388,18 +415,18 @@ namespace UVtools.Core public Layer PreviousLayer() { - if (ReferenceEquals(ParentLayerManager, null) || Index == 0) + if (ParentLayerManager is null || _index == 0) return null; - return ParentLayerManager[Index - 1]; + return ParentLayerManager[_index - 1]; } public Layer NextLayer() { - if (ReferenceEquals(ParentLayerManager, null) || Index >= ParentLayerManager.Count - 1) + if (ParentLayerManager is null || _index >= ParentLayerManager.Count - 1) return null; - return ParentLayerManager[Index + 1]; + return ParentLayerManager[_index + 1]; } public bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, decimal value) diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs index a5ec45d..65127c4 100644 --- a/UVtools.Core/Layer/LayerManager.cs +++ b/UVtools.Core/Layer/LayerManager.cs @@ -49,8 +49,14 @@ namespace UVtools.Core } } + /// + /// Gets the bounding rectangle of the object + /// private Rectangle _boundingRectangle = Rectangle.Empty; + /// + /// Gets the bounding rectangle of the object + /// public Rectangle BoundingRectangle { get => GetBoundingRectangle(); @@ -79,7 +85,7 @@ namespace UVtools.Core } } - public float LayerHeight => Layers[0].PositionZ; + //public float LayerHeight => Layers[0].PositionZ; #endregion @@ -1293,6 +1299,15 @@ namespace UVtools.Core } + #endregion + + #region Formater + + public override string ToString() + { + return $"{nameof(BoundingRectangle)}: {BoundingRectangle}, {nameof(Count)}: {Count}, {nameof(IsModified)}: {IsModified}"; + } + #endregion } } diff --git a/UVtools.Core/Objects/StringTag.cs b/UVtools.Core/Objects/StringTag.cs index d19d686..edaea6f 100644 --- a/UVtools.Core/Objects/StringTag.cs +++ b/UVtools.Core/Objects/StringTag.cs @@ -30,7 +30,7 @@ namespace UVtools.Core.Objects public string TagString { - get => Tag.ToString(); + get => Tag?.ToString(); set => Tag = value; } diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index fabeb31..f0d896d 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -8,7 +8,6 @@ using System; using System.Drawing; -using System.Reflection.Metadata.Ecma335; using System.Xml.Serialization; using Emgu.CV; using UVtools.Core.FileFormats; @@ -19,13 +18,31 @@ namespace UVtools.Core.Operations [Serializable] public abstract class Operation : BindableBase, IDisposable { + #region Members private Rectangle _roi = Rectangle.Empty; private uint _layerIndexEnd; private uint _layerIndexStart; private string _profileName; private bool _profileIsDefault; private Enumerations.LayerRangeSelection _layerRangeSelection = Enumerations.LayerRangeSelection.All; + private FileFormat _slicerFile; public const byte ClassNameLength = 9; + #endregion + + #region Properties + [XmlIgnore] + public FileFormat SlicerFile + { + get => _slicerFile; + set + { + if(!RaiseAndSetIfChanged(ref _slicerFile, value)) return; + InitWithSlicerFile(); + } + } + + [XmlIgnore] + public object Tag { get; set; } /// /// Gets the ID name of this operation, this comes from class name with "Operation" removed @@ -43,6 +60,19 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _layerRangeSelection, value); } + public virtual string LayerRangeString + { + get + { + if (LayerRangeSelection == Enumerations.LayerRangeSelection.None) + { + return $" [Layers: {LayerIndexStart}-{LayerIndexEnd}]"; + } + + return $" [Layers: {LayerRangeSelection}]"; + } + } + /// /// Gets if this operation should set layer range to the actual layer index on layer preview /// @@ -95,18 +125,6 @@ namespace UVtools.Core.Operations public bool HaveAction => !string.IsNullOrEmpty(ProgressAction); - /// - /// Validates the operation - /// - /// null or empty if validates, or else, return a string with error message - public virtual StringTag Validate(params object[] parameters) => null; - - public bool CanValidate(params object[] parameters) - { - var result = Validate(parameters); - return result is null || string.IsNullOrEmpty(result.Content); - } - /// /// Gets the start layer index where operation will starts in /// @@ -153,6 +171,105 @@ namespace UVtools.Core.Operations } public bool HaveROI => !ROI.IsEmpty; + #endregion + + #region Constructor + protected Operation() { } + + protected Operation(FileFormat slicerFile) + { + _slicerFile = slicerFile; + SelectAllLayers(); + InitWithSlicerFile(); + } + #endregion + + #region Methods + /// + /// Validates the operation + /// + /// null or empty if validates, or else, return a string with error message + public virtual StringTag Validate(params object[] parameters) => null; + + public bool CanValidate(params object[] parameters) + { + var result = Validate(parameters); + return result is null || string.IsNullOrEmpty(result.Content); + } + + public void SelectAllLayers() + { + LayerIndexStart = 0; + LayerIndexEnd = SlicerFile.LastLayerIndex; + LayerRangeSelection = Enumerations.LayerRangeSelection.All; + } + + public void SelectCurrentLayer(uint layerIndex) + { + LayerIndexStart = LayerIndexEnd = layerIndex; + LayerRangeSelection = Enumerations.LayerRangeSelection.Current; + } + + public void SelectBottomLayers() + { + LayerIndexStart = 0; + LayerIndexEnd = SlicerFile.BottomLayerCount - 1u; + LayerRangeSelection = Enumerations.LayerRangeSelection.Bottom; + } + + public void SelectNormalLayers() + { + LayerIndexStart = SlicerFile.BottomLayerCount; + LayerIndexEnd = SlicerFile.LastLayerIndex; + LayerRangeSelection = Enumerations.LayerRangeSelection.Normal; + } + + public void SelectFirstLayer() + { + LayerIndexStart = LayerIndexEnd = 0; + LayerRangeSelection = Enumerations.LayerRangeSelection.First; + } + + public void SelectLastLayer() + { + LayerIndexStart = LayerIndexEnd = SlicerFile.LastLayerIndex; + LayerRangeSelection = Enumerations.LayerRangeSelection.Last; + } + + public void SelectLayers(Enumerations.LayerRangeSelection range) + { + switch (range) + { + case Enumerations.LayerRangeSelection.None: + break; + case Enumerations.LayerRangeSelection.All: + SelectAllLayers(); + break; + case Enumerations.LayerRangeSelection.Current: + //SelectCurrentLayer(); + break; + case Enumerations.LayerRangeSelection.Bottom: + SelectBottomLayers(); + break; + case Enumerations.LayerRangeSelection.Normal: + SelectNormalLayers(); + break; + case Enumerations.LayerRangeSelection.First: + SelectFirstLayer(); + break; + case Enumerations.LayerRangeSelection.Last: + SelectLastLayer(); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + + /// + /// Called to init the object when changes + /// + public virtual void InitWithSlicerFile() { } public Mat GetRoiOrDefault(Mat defaultMat) { @@ -161,7 +278,9 @@ namespace UVtools.Core.Operations public virtual Operation Clone() { - return MemberwiseClone() as Operation; + var operation = MemberwiseClone() as Operation; + operation.SlicerFile = _slicerFile; + return operation; } public override string ToString() @@ -171,23 +290,23 @@ namespace UVtools.Core.Operations var result = $"{Title}: {LayerRangeString}"; return result; } + - public virtual string LayerRangeString + protected virtual bool ExecuteInternally(OperationProgress progress) { - get - { - if (LayerRangeSelection == Enumerations.LayerRangeSelection.None) - { - return $" [Layers: {LayerIndexStart}-{LayerIndexEnd}]"; - } - - return $" [Layers: {LayerRangeSelection}]"; - } + throw new NotImplementedException(); } - public virtual bool Execute(FileFormat slicerFile, OperationProgress progress = null) + public bool Execute(OperationProgress progress = null) { - throw new NotImplementedException(); + if (_slicerFile is null) throw new InvalidOperationException($"{Title} can't execute due the lacking of file parent."); + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + + var result = ExecuteInternally(progress); + + progress.Token.ThrowIfCancellationRequested(); + return result; } public virtual bool Execute(Mat mat, params object[] arguments) @@ -196,5 +315,6 @@ namespace UVtools.Core.Operations } public virtual void Dispose() { } + #endregion } } diff --git a/UVtools.Core/Operations/OperationArithmetic.cs b/UVtools.Core/Operations/OperationArithmetic.cs index fc6fe69..c4ef8dd 100644 --- a/UVtools.Core/Operations/OperationArithmetic.cs +++ b/UVtools.Core/Operations/OperationArithmetic.cs @@ -113,6 +113,14 @@ namespace UVtools.Core.Operations public bool IsValid => SetLayers.Count > 0 & Operations.Count > 0; #endregion + #region Constructor + + public OperationArithmetic() { } + + public OperationArithmetic(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Methods public bool Parse() @@ -205,17 +213,16 @@ namespace UVtools.Core.Operations return true; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { if (!IsValid) return false; - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, (uint)Operations.Count); - using Mat result = slicerFile[Operations[0].LayerIndex].LayerMat; + using Mat result = SlicerFile[Operations[0].LayerIndex].LayerMat; Mat resultRoi = GetRoiOrDefault(result); for (int i = 1; i < Operations.Count; i++) { - using var image = slicerFile[Operations[i].LayerIndex].LayerMat; + progress.Token.ThrowIfCancellationRequested(); + using var image = SlicerFile[Operations[i].LayerIndex].LayerMat; Mat imageRoi = GetRoiOrDefault(image); switch (Operations[i - 1].Operator) { @@ -245,18 +252,19 @@ namespace UVtools.Core.Operations Parallel.ForEach(SetLayers, layerIndex => { + if (progress.Token.IsCancellationRequested) return; if (Operations.Count == 1 && HaveROI) { - var mat = slicerFile[layerIndex].LayerMat; + var mat = SlicerFile[layerIndex].LayerMat; var matRoi = GetRoiOrDefault(mat); resultRoi.CopyTo(matRoi); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; return; } - slicerFile[layerIndex].LayerMat = result; + SlicerFile[layerIndex].LayerMat = result; }); - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs index a5237e6..bc9b456 100644 --- a/UVtools.Core/Operations/OperationBlur.cs +++ b/UVtools.Core/Operations/OperationBlur.cs @@ -124,28 +124,33 @@ namespace UVtools.Core.Operations #endregion + #region Constructor + + public OperationBlur() { } + + public OperationBlur(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) - { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); + protected override bool ExecuteInternally(OperationProgress progress) + { Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using (var mat = slicerFile[layerIndex].LayerMat) + using (var mat = SlicerFile[layerIndex].LayerMat) { Execute(mat); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; } lock (progress.Mutex) { progress++; } }); - progress.Token.ThrowIfCancellationRequested(); - return true; + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationCalculator.cs b/UVtools.Core/Operations/OperationCalculator.cs index 0070d17..68aa251 100644 --- a/UVtools.Core/Operations/OperationCalculator.cs +++ b/UVtools.Core/Operations/OperationCalculator.cs @@ -8,6 +8,7 @@ using System; using System.Drawing; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -15,6 +16,7 @@ namespace UVtools.Core.Operations [Serializable] public class OperationCalculator : Operation { + #region Overrides public override string Title => "Calculator"; public override string Description => null; @@ -28,15 +30,31 @@ namespace UVtools.Core.Operations public override bool CanROI => false; public override bool CanHaveProfiles => false; + #endregion + #region Properties public MillimetersToPixels CalcMillimetersToPixels { get; set; } public LightOffDelayC CalcLightOffDelay { get; set; } public OptimalModelTilt CalcOptimalModelTilt { get; set; } + #endregion - public OperationCalculator() + #region Constructor + + public OperationCalculator() { } + + public OperationCalculator(FileFormat slicerFile) : base(slicerFile) { + CalcMillimetersToPixels = new MillimetersToPixels(slicerFile.Resolution, slicerFile.Display); + CalcLightOffDelay = new LightOffDelayC( + (decimal) SlicerFile.LiftHeight, (decimal) slicerFile.BottomLiftHeight, + (decimal) SlicerFile.LiftSpeed, (decimal) slicerFile.BottomLiftSpeed, + (decimal) SlicerFile.RetractSpeed, (decimal) slicerFile.RetractSpeed); + CalcOptimalModelTilt = new OptimalModelTilt(slicerFile.Resolution, slicerFile.Display, + (decimal) slicerFile.LayerHeight); } + #endregion + public abstract class Calculation : BindableBase { public abstract string Description { get; } diff --git a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs index b4e93e9..0b3f789 100644 --- a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs +++ b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs @@ -26,7 +26,6 @@ namespace UVtools.Core.Operations public sealed class OperationCalibrateElephantFoot : Operation { #region Members - private Size _resolution = Size.Empty; private decimal _layerHeight = 0.05M; private bool _syncLayers; private ushort _bottomLayers = 10; @@ -112,14 +111,6 @@ namespace UVtools.Core.Operations #region Properties - [XmlIgnore] - public Size Resolution - { - get => _resolution; - set => RaiseAndSetIfChanged(ref _resolution, value); - } - - public decimal LayerHeight { get => _layerHeight; @@ -347,6 +338,21 @@ namespace UVtools.Core.Operations #endregion + #region Constructor + + public OperationCalibrateElephantFoot() { } + + public OperationCalibrateElephantFoot(FileFormat slicerFile) : base(slicerFile) + { + _layerHeight = (decimal)slicerFile.LayerHeight; + //_bottomLayers = slicerFile.BottomLayerCount; + _bottomExposure = (decimal)slicerFile.BottomExposureTime; + _normalExposure = (decimal)slicerFile.ExposureTime; + _mirrorOutput = slicerFile.MirrorDisplay; + } + + #endregion + #region Equality private bool Equals(OperationCalibrateElephantFoot other) @@ -400,7 +406,7 @@ namespace UVtools.Core.Operations var anchor = new Point(-1, -1); var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - layers[0] = EmguExtensions.InitMat(Resolution); + layers[0] = EmguExtensions.InitMat(SlicerFile.Resolution); LineType lineType = _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected; int length = (int) (250 * _partScale); int triangleLength = (int) (50 * _partScale); @@ -633,32 +639,25 @@ namespace UVtools.Core.Operations return thumbnail; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, 3); - slicerFile.SuppressRebuildProperties = true; - + progress.ItemCount = 3; + var newLayers = new Layer[LayerCount]; - slicerFile.LayerHeight = (float)LayerHeight; - slicerFile.BottomExposureTime = (float)BottomExposure; - slicerFile.ExposureTime = (float)NormalExposure; - slicerFile.BottomLayerCount = BottomLayers; - var layers = GetLayers(); progress++; - var bottomLayer = new Layer(0, layers[0], slicerFile.LayerManager) + var bottomLayer = new Layer(0, layers[0], SlicerFile.LayerManager) { IsModified = true }; - var layer = new Layer(0, layers[1], slicerFile.LayerManager) + var layer = new Layer(0, layers[1], SlicerFile.LayerManager) { IsModified = true }; - var moveOp = new OperationMove(bottomLayer.BoundingRectangle, layers[0].Size); + var moveOp = new OperationMove(SlicerFile, bottomLayer.BoundingRectangle); moveOp.Execute(layers[0]); moveOp.Execute(layers[1]); @@ -671,7 +670,7 @@ namespace UVtools.Core.Operations layerIndex < LayerCount; layerIndex++) { - newLayers[layerIndex] = slicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomLayer.Clone(), layer.Clone()); + newLayers[layerIndex] = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomLayer.Clone(), layer.Clone()); } foreach (var mat in layers) @@ -680,16 +679,22 @@ namespace UVtools.Core.Operations } - if (slicerFile.ThumbnailsCount > 0) - slicerFile.SetThumbnails(GetThumbnail()); + if (SlicerFile.ThumbnailsCount > 0) + SlicerFile.SetThumbnails(GetThumbnail()); progress++; - slicerFile.LayerManager.Layers = newLayers; - slicerFile.SuppressRebuildProperties = false; - slicerFile.LayerManager.RebuildLayersProperties(); + SlicerFile.SuppressRebuildProperties = true; + SlicerFile.LayerHeight = (float)LayerHeight; + SlicerFile.BottomExposureTime = (float)BottomExposure; + SlicerFile.ExposureTime = (float)NormalExposure; + SlicerFile.BottomLayerCount = BottomLayers; + + SlicerFile.LayerManager.Layers = newLayers; + SlicerFile.LayerManager.RebuildLayersProperties(); + SlicerFile.SuppressRebuildProperties = false; - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs new file mode 100644 index 0000000..183d20e --- /dev/null +++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs @@ -0,0 +1,1015 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; +using UVtools.Core.Objects; + +namespace UVtools.Core.Operations +{ + [Serializable] + public sealed class OperationCalibrateExposureFinder : Operation + { + #region Sub classes + + [Serializable] + public sealed class ExposureItem : BindableBase, IComparable + { + private decimal _layerHeight; + private decimal _bottomExposure; + private decimal _exposure; + + + /// + /// Gets or sets the layer height in millimeters + /// + public decimal LayerHeight + { + get => _layerHeight; + set => RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2)); + } + + + /// + /// Gets or sets the bottom exposure in seconds + /// + public decimal BottomExposure + { + get => _bottomExposure; + set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2)); + } + + /// + /// Gets or sets the bottom exposure in seconds + /// + public decimal Exposure + { + get => _exposure; + set => RaiseAndSetIfChanged(ref _exposure, Math.Round(value, 2)); + } + + public bool IsValid => _layerHeight > 0 && _bottomExposure > 0 && _exposure > 0; + + public ExposureItem() { } + + public ExposureItem(decimal layerHeight, decimal bottomExposure = 0, decimal exposure = 0) + { + _layerHeight = Math.Round(layerHeight, 2); + _bottomExposure = Math.Round(bottomExposure, 2); + _exposure = Math.Round(exposure, 2); + } + + public override string ToString() + { + return $"{nameof(LayerHeight)}: {LayerHeight}mm, {nameof(BottomExposure)}: {BottomExposure}s, {nameof(Exposure)}: {Exposure}s"; + } + + private bool Equals(ExposureItem other) + { + return _layerHeight == other._layerHeight && _bottomExposure == other._bottomExposure && _exposure == other._exposure; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is ExposureItem other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_layerHeight, _bottomExposure, _exposure); + } + + public int CompareTo(ExposureItem other) + { + if (ReferenceEquals(this, other)) return 0; + if (ReferenceEquals(null, other)) return 1; + var layerHeightComparison = _layerHeight.CompareTo(other._layerHeight); + if (layerHeightComparison != 0) return layerHeightComparison; + var bottomExposureComparison = _bottomExposure.CompareTo(other._bottomExposure); + if (bottomExposureComparison != 0) return bottomExposureComparison; + return _exposure.CompareTo(other._exposure); + } + } + + #endregion + + #region Constants + + const byte TextSpacing = 60; + const byte TextLineBreak = 30; + const FontFace FontFace = Emgu.CV.CvEnum.FontFace.HersheyDuplex; + const byte TextStartX = 10; + //const byte TextStartY = 50; + const double TextScale = 0.8; + const byte TextThickness = 2; + + #endregion + + #region Members + private decimal _displayWidth; + private decimal _displayHeight; + private decimal _layerHeight = 0.05M; + private ushort _bottomLayers = 3; + private decimal _bottomExposure = 60; + private decimal _normalExposure = 12; + private decimal _topBottomMargin = 5; + private decimal _leftRightMargin = 5; + private byte _chamferLayers = 0; + private byte _erodeBottomIterations = 0; + private decimal _partMargin = 0; + private bool _enableAntiAliasing = true; + private bool _mirrorOutput; + private decimal _baseHeight = 1; + private decimal _cylinderHeight = 1; + private decimal _cylinderMargin = 1.5m; + private Measures _unitOfMeasure = Measures.Millimeters; + private string _cylinderDiametersMm = "0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0"; + private string _cylinderDiametersPx = "1, 2, 3, 4, 5, 7, 10 ,15, 20"; + private bool _multipleLayerHeight; + private decimal _multipleLayerHeightMaximum = 0.1m; + private decimal _multipleLayerHeightStep = 0.01m; + private bool _multipleExposures; + private Shapes _shape = Shapes.Circle; + private ExposureGenTypes _exposureGenType = ExposureGenTypes.Linear; + private bool _exposureGenIgnoreBaseExposure; + private decimal _exposureGenBottomStep = 0.5m; + private decimal _exposureGenNormalStep = 0.2m; + private byte _exposureGenTests = 4; + private decimal _exposureGenManualLayerHeight; + private decimal _exposureGenManualBottom; + private decimal _exposureGenManualNormal; + private ObservableCollection _exposureTable = new(); + + #endregion + + #region Overrides + + public override bool CanROI => false; + + //public override bool CanCancel => false; + + public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; + + public override string Title => "Exposure time finder"; + public override string Description => + "Generates test models with various strategies and increments to verify the best exposure time for a given layer height.\n" + + "You must repeat this test when change any of the following: printer, LEDs, resin and exposure times.\n" + + "Note: The current opened file will be overwritten with this test, use a dummy or a not needed file."; + + public override string ConfirmationText => + $"generate the exposure time finder test?"; + + public override string ProgressTitle => + $"Generating the exposure time finder test"; + + public override string ProgressAction => "Generated layers"; + + public override StringTag Validate(params object[] parameters) + { + var sb = new StringBuilder(); + + if (_displayWidth <= 0) + { + sb.AppendLine("Display width must be a positive value."); + } + + if (_displayHeight <= 0) + { + sb.AppendLine("Display height must be a positive value."); + } + + if (Cylinders.Length <= 0) + { + sb.AppendLine("No objects to output."); + } + + if (_multipleExposures) + { + var endLayerHeight = _multipleLayerHeight ? _multipleLayerHeightMaximum : _layerHeight; + for (decimal layerHeight = _layerHeight; + layerHeight <= endLayerHeight; + layerHeight += _multipleLayerHeightStep) + { + bool found = false; + foreach (var exposureItem in _exposureTable) + { + if (exposureItem.LayerHeight == layerHeight && exposureItem.IsValid) + { + found = true; + break; + } + } + if(!found) + sb.AppendLine($"[ME]: {layerHeight:F2}mm layer height have no exposure(s)."); + } + } + + return new StringTag(sb.ToString()); + } + + public override string ToString() + { + var result = $"[Layer Height: {_layerHeight}] " + + $"[Bottom layers: {_bottomLayers}] " + + $"[Exposure: {_bottomExposure}/{_normalExposure}] " + + $"[TB:{_topBottomMargin} LR:{_leftRightMargin} PM:{_partMargin} CM:{_cylinderMargin}] " + + $"[Chamfer: {_chamferLayers}] [Erode: {_erodeBottomIterations}] " + + $"[Obj height: {_cylinderHeight}] " + + $"[Cylinders: {Cylinders.Length}] " + + $"[AA: {_enableAntiAliasing}] [Mirror: {_mirrorOutput}]"; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + #endregion + + #region Properties + + public decimal DisplayWidth + { + get => _displayWidth; + set + { + if(!RaiseAndSetIfChanged(ref _displayWidth, Math.Round(value, 2))) return; + RaisePropertyChanged(nameof(Xppmm)); + } + } + + public decimal DisplayHeight + { + get => _displayHeight; + set + { + if(!RaiseAndSetIfChanged(ref _displayHeight, Math.Round(value, 2))) return; + RaisePropertyChanged(nameof(Yppmm)); + } + } + + public decimal Xppmm => DisplayWidth > 0 ? Math.Round(SlicerFile.Resolution.Width / DisplayWidth, 2) : 0; + public decimal Yppmm => DisplayHeight > 0 ? Math.Round(SlicerFile.Resolution.Height / DisplayHeight, 2) : 0; + public decimal Ppmm => Math.Max(Xppmm, Yppmm); + + public decimal LayerHeight + { + get => _layerHeight; + set + { + if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return; + RaisePropertyChanged(nameof(BottomLayersMM)); + RaisePropertyChanged(nameof(AvailableLayerHeights)); + } + } + + public ushort Microns => (ushort)(LayerHeight * 1000); + + public ushort BottomLayers + { + get => _bottomLayers; + set + { + if(!RaiseAndSetIfChanged(ref _bottomLayers, value)) return; + RaisePropertyChanged(nameof(BottomLayersMM)); + } + } + + public decimal BottomLayersMM => Math.Round(LayerHeight * BottomLayers, 2); + + public decimal BottomExposure + { + get => _bottomExposure; + set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2)); + } + + public decimal NormalExposure + { + get => _normalExposure; + set => RaiseAndSetIfChanged(ref _normalExposure, Math.Round(value, 2)); + } + + public decimal TopBottomMargin + { + get => _topBottomMargin; + set => RaiseAndSetIfChanged(ref _topBottomMargin, Math.Round(value, 2)); + } + + public decimal LeftRightMargin + { + get => _leftRightMargin; + set => RaiseAndSetIfChanged(ref _leftRightMargin, Math.Round(value, 2)); + } + + public byte ChamferLayers + { + get => _chamferLayers; + set => RaiseAndSetIfChanged(ref _chamferLayers, value); + } + + public byte ErodeBottomIterations + { + get => _erodeBottomIterations; + set => RaiseAndSetIfChanged(ref _erodeBottomIterations, value); + } + + public decimal PartMargin + { + get => _partMargin; + set => RaiseAndSetIfChanged(ref _partMargin, Math.Round(value, 2)); + } + + public bool EnableAntiAliasing + { + get => _enableAntiAliasing; + set => RaiseAndSetIfChanged(ref _enableAntiAliasing, value); + } + + public bool MirrorOutput + { + get => _mirrorOutput; + set => RaiseAndSetIfChanged(ref _mirrorOutput, value); + } + + public decimal BaseHeight + { + get => _baseHeight; + set => RaiseAndSetIfChanged(ref _baseHeight, Math.Round(value, 2)); + } + + public decimal CylinderHeight + { + get => _cylinderHeight; + set => RaiseAndSetIfChanged(ref _cylinderHeight, Math.Round(value, 2)); + } + + public decimal CylinderMargin + { + get => _cylinderMargin; + set => RaiseAndSetIfChanged(ref _cylinderMargin, Math.Round(value, 2)); + } + + public decimal TotalHeight => BaseHeight + CylinderHeight; + + public Measures UnitOfMeasure + { + get => _unitOfMeasure; + set + { + if (!RaiseAndSetIfChanged(ref _unitOfMeasure, value)) return; + RaisePropertyChanged(nameof(IsUnitOfMeasureMm)); + } + } + + public bool IsUnitOfMeasureMm => _unitOfMeasure == Measures.Millimeters; + + public string CylinderDiametersMm + { + get => _cylinderDiametersMm; + set => RaiseAndSetIfChanged(ref _cylinderDiametersMm, value); + } + + public string CylinderDiametersPx + { + get => _cylinderDiametersPx; + set => RaiseAndSetIfChanged(ref _cylinderDiametersPx, value); + } + + /// + /// Gets all cylinders in pixels and ordered + /// + public int[] Cylinders + { + get + { + List cylinders = new(); + + if (_unitOfMeasure == Measures.Millimeters) + { + var split = _cylinderDiametersMm.Split(',', StringSplitOptions.TrimEntries); + foreach (var mmStr in split) + { + if (string.IsNullOrWhiteSpace(mmStr)) continue; + if (!decimal.TryParse(mmStr, out var mm)) continue; + if(mm <= 0) continue; + var mmPx = (int) Math.Floor(mm * Ppmm); + if(cylinders.Contains(mmPx)) continue; + cylinders.Add((int)Math.Floor(mm * Ppmm)); + } + } + else + { + var split = _cylinderDiametersPx.Split(',', StringSplitOptions.TrimEntries); + foreach (var pxStr in split) + { + if (string.IsNullOrWhiteSpace(pxStr)) continue; + if (!int.TryParse(pxStr, out var px)) continue; + if (px <= 0) continue; + if (cylinders.Contains(px)) continue; + cylinders.Add(px); + } + } + + return cylinders.OrderBy(pixels => pixels).ToArray(); + } + } + + public Shapes Shape + { + get => _shape; + set => RaiseAndSetIfChanged(ref _shape, value); + } + + public int GetCylindersLength(int[] cylinders) + { + return (int) (cylinders.Sum() + (cylinders.Length-1) * _cylinderMargin * Yppmm); + } + + public bool MultipleLayerHeight + { + get => _multipleLayerHeight; + set + { + if(!RaiseAndSetIfChanged(ref _multipleLayerHeight, value)) return; + RaisePropertyChanged(nameof(AvailableLayerHeights)); + } + } + + public decimal MultipleLayerHeightMaximum + { + get => _multipleLayerHeightMaximum; + set + { + if(!RaiseAndSetIfChanged(ref _multipleLayerHeightMaximum, value)) return; + RaisePropertyChanged(nameof(AvailableLayerHeights)); + } + } + + public decimal MultipleLayerHeightStep + { + get => _multipleLayerHeightStep; + set + { + if(!RaiseAndSetIfChanged(ref _multipleLayerHeightStep, value)) return; + RaisePropertyChanged(nameof(AvailableLayerHeights)); + } + } + + public bool MultipleExposures + { + get => _multipleExposures; + set => RaiseAndSetIfChanged(ref _multipleExposures, value); + } + + public ExposureGenTypes ExposureGenType + { + get => _exposureGenType; + set => RaiseAndSetIfChanged(ref _exposureGenType, value); + } + + public bool ExposureGenIgnoreBaseExposure + { + get => _exposureGenIgnoreBaseExposure; + set => RaiseAndSetIfChanged(ref _exposureGenIgnoreBaseExposure, value); + } + + public decimal ExposureGenBottomStep + { + get => _exposureGenBottomStep; + set => RaiseAndSetIfChanged(ref _exposureGenBottomStep, Math.Round(value, 2)); + } + + public decimal ExposureGenNormalStep + { + get => _exposureGenNormalStep; + set => RaiseAndSetIfChanged(ref _exposureGenNormalStep, Math.Round(value, 2)); + } + + public byte ExposureGenTests + { + get => _exposureGenTests; + set => RaiseAndSetIfChanged(ref _exposureGenTests, value); + } + + public decimal ExposureGenManualLayerHeight + { + get => _exposureGenManualLayerHeight; + set => RaiseAndSetIfChanged(ref _exposureGenManualLayerHeight, value); + } + + public decimal[] AvailableLayerHeights + { + get + { + List layerHeights = new List(); + var endLayerHeight = _multipleLayerHeight ? _multipleLayerHeightMaximum : _layerHeight; + List list = new(); + for (decimal layerHeight = _layerHeight; layerHeight <= endLayerHeight; layerHeight += _multipleLayerHeightStep) + { + layerHeights.Add(Math.Round(layerHeight, 2)); + } + + return layerHeights.ToArray(); + } + } + + public decimal ExposureGenManualBottom + { + get => _exposureGenManualBottom; + set => RaiseAndSetIfChanged(ref _exposureGenManualBottom, value); + } + + public decimal ExposureGenManualNormal + { + get => _exposureGenManualNormal; + set => RaiseAndSetIfChanged(ref _exposureGenManualNormal, value); + } + + public ExposureItem ExposureManualEntry => new (_exposureGenManualLayerHeight, _exposureGenManualBottom, _exposureGenManualNormal); + + + public ObservableCollection ExposureTable + { + get => _exposureTable; + set => RaiseAndSetIfChanged(ref _exposureTable, value); + } + + #endregion + + #region Constructor + + public OperationCalibrateExposureFinder() { } + + public OperationCalibrateExposureFinder(FileFormat slicerFile) : base(slicerFile) + { + _layerHeight = (decimal)slicerFile.LayerHeight; + _bottomLayers = slicerFile.BottomLayerCount; + _bottomExposure = (decimal)slicerFile.BottomExposureTime; + _normalExposure = (decimal)slicerFile.ExposureTime; + _mirrorOutput = slicerFile.MirrorDisplay; + } + + public override void InitWithSlicerFile() + { + base.InitWithSlicerFile(); + if (SlicerFile.DisplayWidth > 0) + DisplayWidth = (decimal)SlicerFile.DisplayWidth; + if (SlicerFile.DisplayHeight > 0) + DisplayHeight = (decimal)SlicerFile.DisplayHeight; + + if (_exposureGenManualBottom == 0) + _exposureGenManualBottom = (decimal) SlicerFile.BottomExposureTime; + if (_exposureGenManualNormal == 0) + _exposureGenManualNormal = (decimal)SlicerFile.ExposureTime; + + if (!SlicerFile.HavePrintParameterPerLayerModifier(FileFormat.PrintParameterModifier.ExposureSeconds)) + { + _multipleLayerHeight = false; + _multipleExposures = false; + } + } + + #endregion + + #region Enums + + public enum Measures : byte + { + Millimeters, + Pixels, + } + + public static Array MeasuresItems => Enum.GetValues(typeof(Measures)); + + public enum Shapes : byte + { + Circle, + Square + } + + public static Array ShapesItems => Enum.GetValues(typeof(Shapes)); + + public enum ExposureGenTypes : byte + { + Linear, + Multiplier + } + + public static Array ExposureGenTypeItems => Enum.GetValues(typeof(ExposureGenTypes)); + #endregion + + #region Equality + + private bool Equals(OperationCalibrateExposureFinder other) + { + return _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _topBottomMargin == other._topBottomMargin && _leftRightMargin == other._leftRightMargin && _chamferLayers == other._chamferLayers && _erodeBottomIterations == other._erodeBottomIterations && _partMargin == other._partMargin && _enableAntiAliasing == other._enableAntiAliasing && _mirrorOutput == other._mirrorOutput && _baseHeight == other._baseHeight && _cylinderHeight == other._cylinderHeight && _cylinderMargin == other._cylinderMargin && _unitOfMeasure == other._unitOfMeasure && _cylinderDiametersMm == other._cylinderDiametersMm && _cylinderDiametersPx == other._cylinderDiametersPx && _multipleLayerHeight == other._multipleLayerHeight && _multipleLayerHeightMaximum == other._multipleLayerHeightMaximum && _multipleLayerHeightStep == other._multipleLayerHeightStep && _multipleExposures == other._multipleExposures && _shape == other._shape && _exposureGenType == other._exposureGenType && _exposureGenIgnoreBaseExposure == other._exposureGenIgnoreBaseExposure && _exposureGenBottomStep == other._exposureGenBottomStep && _exposureGenNormalStep == other._exposureGenNormalStep && _exposureGenTests == other._exposureGenTests && Equals(_exposureTable, other._exposureTable); + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationCalibrateExposureFinder other && Equals(other); + } + + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(_layerHeight); + hashCode.Add(_bottomLayers); + hashCode.Add(_bottomExposure); + hashCode.Add(_normalExposure); + hashCode.Add(_topBottomMargin); + hashCode.Add(_leftRightMargin); + hashCode.Add(_chamferLayers); + hashCode.Add(_erodeBottomIterations); + hashCode.Add(_partMargin); + hashCode.Add(_enableAntiAliasing); + hashCode.Add(_mirrorOutput); + hashCode.Add(_baseHeight); + hashCode.Add(_cylinderHeight); + hashCode.Add(_cylinderMargin); + hashCode.Add((int) _unitOfMeasure); + hashCode.Add(_cylinderDiametersMm); + hashCode.Add(_cylinderDiametersPx); + hashCode.Add(_multipleLayerHeight); + hashCode.Add(_multipleLayerHeightMaximum); + hashCode.Add(_multipleLayerHeightStep); + hashCode.Add(_multipleExposures); + hashCode.Add((int) _shape); + hashCode.Add((int) _exposureGenType); + hashCode.Add(_exposureGenIgnoreBaseExposure); + hashCode.Add(_exposureGenBottomStep); + hashCode.Add(_exposureGenNormalStep); + hashCode.Add(_exposureGenTests); + hashCode.Add(_exposureTable); + return hashCode.ToHashCode(); + } + + #endregion + + #region Methods + + public void SortExposureTable() + { + var list = _exposureTable.ToList(); + list.Sort(); + ExposureTable = new(list); + } + + public void SanitizeExposureTable() + { + List list = _exposureTable.ToList().Distinct().ToList(); + list.Sort(); + ExposureTable = new(list); + } + + public void GenerateExposure() + { + var endLayerHeight = _multipleLayerHeight ? _multipleLayerHeightMaximum : _layerHeight; + List list = new(); + for (decimal layerHeight = _layerHeight; + layerHeight <= endLayerHeight; + layerHeight += _multipleLayerHeightStep) + { + if(!_exposureGenIgnoreBaseExposure) + list.Add(new ExposureItem(layerHeight, (decimal) SlicerFile.BottomExposureTime, (decimal) SlicerFile.ExposureTime)); + for (ushort testN = 1; testN <= _exposureGenTests; testN++) + { + decimal bottomExposureTime = 0; + decimal exposureTime = 0; + + switch (_exposureGenType) + { + case ExposureGenTypes.Linear: + bottomExposureTime = (decimal) SlicerFile.BottomExposureTime + _exposureGenBottomStep * testN; + exposureTime = (decimal) SlicerFile.ExposureTime + _exposureGenNormalStep * testN; + break; + case ExposureGenTypes.Multiplier: + bottomExposureTime = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.BottomExposureTime * layerHeight * _exposureGenBottomStep * testN; + exposureTime = (decimal)SlicerFile.ExposureTime + (decimal)SlicerFile.ExposureTime * layerHeight * _exposureGenNormalStep * testN; + break; + } + + ExposureItem item = new(layerHeight, bottomExposureTime, exposureTime); + if(list.Contains(item)) continue; // Already on list, skip + list.Add(item); + } + } + + ExposureTable = new(list); + } + + public Mat[] GetLayers() + { + var cylinders = Cylinders; + int cylinderMarginX = (int) (Xppmm * _cylinderMargin); + int cylinderMarginY = (int) (Yppmm * _cylinderMargin); + Rectangle rect = new Rectangle(new Point(1, 1), + new Size(cylinderMarginX * 4 + cylinders[^1] * 2, + cylinderMarginY * 3 + GetCylindersLength(cylinders) + TextSpacing)); + var layers = new Mat[2]; + layers[0] = EmguExtensions.InitMat(rect.Size.Inflate(2)); + + CvInvoke.Rectangle(layers[0], rect, EmguExtensions.WhiteByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + layers[1] = layers[0].Clone(); + CvInvoke.Rectangle(layers[1], new Rectangle(0, 0, rect.Size.Width / 2, layers[0].Height), EmguExtensions.BlackByte, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + + //int holeXPos = (int) Math.Round(Xppmm * (CylinderMargin + maxCylinder / 2)); + // Print holes + int holeXPos = 0; + int holeYPos = 0; + for (var layerIndex = 0; layerIndex < layers.Length; layerIndex++) + { + var layer = layers[layerIndex]; + holeYPos = cylinderMarginY; + for (int i = 0; i < cylinders.Length; i++) + { + var diameter = cylinders[i]; + var radius = diameter / 2; + + switch (_shape) + { + case Shapes.Circle: + holeXPos = rect.X + cylinderMarginX + cylinders[^1] - radius; + holeYPos += radius; + break; + case Shapes.Square: + holeXPos = rect.X + cylinderMarginX + cylinders[^1] - diameter; + break; + } + + + // Left side + if (layerIndex == 1) + { + if (diameter == 1) + { + layer.SetByte(holeXPos, holeYPos, 255); + } + else + { + switch (_shape) + { + case Shapes.Circle: + CvInvoke.Circle(layers[layerIndex], + new Point(holeXPos, holeYPos), + radius, EmguExtensions.WhiteByte, -1, + _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + break; + case Shapes.Square: + CvInvoke.Rectangle(layers[layerIndex], + new Rectangle(new Point(holeXPos, holeYPos), new Size(diameter, diameter)), + EmguExtensions.WhiteByte, -1, + _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + break; + } + + } + } + + //holeXPos = layers[0].Width - holeXPos; + switch (_shape) + { + case Shapes.Circle: + holeXPos = layers[0].Width - rect.X - cylinderMarginX - cylinders[^1] + radius; + break; + case Shapes.Square: + holeXPos = layers[0].Width - rect.X - cylinderMarginX - cylinders[^1]; + break; + } + + // Right side + if (diameter == 1) + { + layer.SetByte(holeXPos, holeYPos, 0); + } + else + { + switch (_shape) + { + case Shapes.Circle: + CvInvoke.Circle(layers[layerIndex], + new Point(holeXPos, holeYPos), + radius, EmguExtensions.BlackByte, -1, + _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + break; + case Shapes.Square: + CvInvoke.Rectangle(layers[layerIndex], + new Rectangle(new Point(holeXPos, holeYPos), new Size(diameter, diameter)), + EmguExtensions.BlackByte, -1, + _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + break; + } + } + + + holeYPos += cylinderMarginY; + + switch (_shape) + { + case Shapes.Circle: + holeYPos += radius; + break; + case Shapes.Square: + holeYPos += diameter; + break; + } + + + } + } + + + /*if (_mirrorOutput) + { + Parallel.ForEach(layers, mat => CvInvoke.Flip(mat, mat, FlipType.Horizontal)); + }*/ + + //layers[^1].Save("E:\\layer.png"); + + return layers; + } + + public Mat GetThumbnail() + { + Mat thumbnail = EmguExtensions.InitMat(new Size(400, 200), 3); + var fontFace = FontFace.HersheyDuplex; + var fontScale = 1; + var fontThickness = 2; + const byte xSpacing = 45; + const byte ySpacing = 45; + CvInvoke.PutText(thumbnail, "UVtools", new Point(140, 35), fontFace, fontScale, new MCvScalar(255, 27, 245), fontThickness + 1); + CvInvoke.Line(thumbnail, new Point(xSpacing, 0), new Point(xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.Line(thumbnail, new Point(xSpacing, ySpacing + 5), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.Line(thumbnail, new Point(thumbnail.Width - xSpacing, 0), new Point(thumbnail.Width - xSpacing, ySpacing + 5), new MCvScalar(255, 27, 245), 3); + CvInvoke.PutText(thumbnail, "Exposure Time Cal.", new Point(xSpacing, ySpacing * 2), fontFace, fontScale, new MCvScalar(0, 255, 255), fontThickness); + CvInvoke.PutText(thumbnail, $"{Microns}um @ {BottomExposure}s/{NormalExposure}s", new Point(xSpacing, ySpacing * 3), fontFace, fontScale, EmguExtensions.White3Byte, fontThickness); + CvInvoke.PutText(thumbnail, $"Objects: {Cylinders.Length}", new Point(xSpacing, ySpacing * 4), fontFace, fontScale, EmguExtensions.White3Byte, fontThickness); + + return thumbnail; + } + + protected override bool ExecuteInternally(OperationProgress progress) + { + progress.ItemCount = 0; + SanitizeExposureTable(); + var layers = GetLayers(); + if (layers[0].Width > SlicerFile.ResolutionX || layers[0].Height > SlicerFile.ResolutionY) + { + return false; + } + + List newLayers = new(); + + Dictionary table = new(); + var endLayerHeight = _multipleLayerHeight ? _multipleLayerHeightMaximum : _layerHeight; + var totalHeight = TotalHeight; + int sideMarginPx = (int)Math.Floor(_leftRightMargin * Xppmm); + int topBottomMarginPx = (int)Math.Floor(_topBottomMargin * Yppmm); + uint layerIndex = 0; + int currentX = sideMarginPx; + int currentY = topBottomMarginPx; + int cylinderMarginY = (int)(Yppmm * _cylinderMargin); + + var anchor = new Point(-1, -1); + using var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + + void AddLayer(decimal currentHeight, decimal layerHeight, decimal bottomExposure, decimal normalExposure) + { + var layerDifference = currentHeight / layerHeight; + + if (!layerDifference.IsInteger()) return; // Not at right height to process with layer height + //Debug.WriteLine($"{currentHeight} / {layerHeight} = {layerDifference}, Floor={Math.Floor(layerDifference)}"); + + Point position; + ExposureItem key = new(layerHeight, bottomExposure, normalExposure); + + if (table.TryGetValue(key, out var pos)) + { + position = pos; + } + else + { + if (currentX + layers[0].Width + sideMarginPx > SlicerFile.ResolutionX) + { + currentX = sideMarginPx; + currentY += layers[0].Height + (int)Math.Floor(_partMargin * Yppmm); + } + + if (currentY + layers[0].Height + topBottomMarginPx > SlicerFile.ResolutionY) + { + return; // Reach the end + } + + position = new Point(currentX, currentY); + table.Add(key, new Point(currentX, currentY)); + + currentX += layers[0].Width + (int)Math.Floor(_partMargin * Xppmm); + } + + ushort microns = (ushort)Math.Floor(layerHeight * 1000); + + Mat mat = EmguExtensions.InitMat(SlicerFile.Resolution); + Mat matRoi = new(mat, new Rectangle(position, layers[0].Size)); + + int layerCountOnHeight = (int)Math.Floor(currentHeight / layerHeight); + bool isBottomLayer = layerCountOnHeight <= _bottomLayers; + bool isBaseLayer = currentHeight <= _baseHeight; + layers[isBaseLayer ? 0 : 1].CopyTo(matRoi); + + if (isBottomLayer && _erodeBottomIterations > 0) + { + CvInvoke.Erode(matRoi, matRoi, kernel, anchor, _erodeBottomIterations, BorderType.Reflect101, default); + } + + if (layerCountOnHeight < _chamferLayers) + { + CvInvoke.Erode(matRoi, matRoi, kernel, anchor, _chamferLayers - layerCountOnHeight, BorderType.Reflect101, default); + } + + var textHeightStart = matRoi.Height - cylinderMarginY - TextSpacing; + CvInvoke.PutText(matRoi, $"{microns}u", new Point(TextStartX, textHeightStart), FontFace, TextScale, EmguExtensions.WhiteByte, TextThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + CvInvoke.PutText(matRoi, $"{bottomExposure}s", new Point(TextStartX, textHeightStart + TextLineBreak), FontFace, TextScale, EmguExtensions.WhiteByte, TextThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + CvInvoke.PutText(matRoi, $"{normalExposure}s", new Point(TextStartX, textHeightStart + TextLineBreak * 2), FontFace, TextScale, EmguExtensions.WhiteByte, TextThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + CvInvoke.PutText(matRoi, $"{microns}u", new Point(matRoi.Width / 2 + TextStartX, textHeightStart), FontFace, TextScale, EmguExtensions.BlackByte, TextThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + CvInvoke.PutText(matRoi, $"{bottomExposure}s", new Point(matRoi.Width / 2 + TextStartX, textHeightStart + TextLineBreak), FontFace, TextScale, EmguExtensions.BlackByte, TextThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + CvInvoke.PutText(matRoi, $"{normalExposure}s", new Point(matRoi.Width / 2 + TextStartX, textHeightStart + TextLineBreak * 2), FontFace, TextScale, EmguExtensions.BlackByte, TextThickness, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected); + + Layer layer = new(layerIndex++, mat, SlicerFile) + { + PositionZ = (float)currentHeight, + ExposureTime = isBottomLayer ? (float)bottomExposure : (float)normalExposure, + LiftHeight = isBottomLayer ? SlicerFile.BottomLiftHeight : SlicerFile.LiftHeight, + LiftSpeed = isBottomLayer ? SlicerFile.BottomLiftSpeed : SlicerFile.LiftSpeed, + RetractSpeed = SlicerFile.RetractSpeed, + LightOffDelay = isBottomLayer ? SlicerFile.BottomLightOffDelay : SlicerFile.LightOffDelay, + LightPWM = isBottomLayer ? SlicerFile.BottomLightPWM : SlicerFile.LightPWM, + IsModified = true + }; + newLayers.Add(layer); + mat.Dispose(); + + progress++; + } + + for (decimal currentHeight = _layerHeight; currentHeight <= totalHeight; currentHeight += 0.01m) + { + currentHeight = Math.Round(currentHeight, 2); + for (decimal layerHeight = _layerHeight; layerHeight <= endLayerHeight; layerHeight += _multipleLayerHeightStep) + { + progress.Token.ThrowIfCancellationRequested(); + layerHeight = Math.Round(layerHeight, 2); + + if (_multipleExposures) + { + foreach (var exposureItem in _exposureTable) + { + if (exposureItem.IsValid && exposureItem.LayerHeight == layerHeight) + { + AddLayer(currentHeight, layerHeight, exposureItem.BottomExposure, exposureItem.Exposure); + } + } + } + else + { + AddLayer(currentHeight, layerHeight, _bottomExposure, _normalExposure); + } + } + } + + if (SlicerFile.ThumbnailsCount > 0) + SlicerFile.SetThumbnails(GetThumbnail()); + + SlicerFile.SuppressRebuildProperties = true; + SlicerFile.LayerHeight = (float)LayerHeight; + SlicerFile.BottomExposureTime = (float)BottomExposure; + SlicerFile.ExposureTime = (float)NormalExposure; + SlicerFile.BottomLayerCount = BottomLayers; + SlicerFile.LayerManager.Layers = newLayers.ToArray(); + SlicerFile.SuppressRebuildProperties = false; + + if (_mirrorOutput) + { + new OperationFlip(SlicerFile){FlipDirection = Enumerations.FlipDirection.Horizontally}.Execute(progress); + } + + new OperationMove(SlicerFile).Execute(progress); + + return !progress.Token.IsCancellationRequested; + } + + #endregion + } +} diff --git a/UVtools.Core/Operations/OperationCalibrateExternalTests.cs b/UVtools.Core/Operations/OperationCalibrateExternalTests.cs index 64a5bb6..99fa3fd 100644 --- a/UVtools.Core/Operations/OperationCalibrateExternalTests.cs +++ b/UVtools.Core/Operations/OperationCalibrateExternalTests.cs @@ -7,6 +7,8 @@ */ +using UVtools.Core.FileFormats; + namespace UVtools.Core.Operations { public class OperationCalibrateExternalTests : Operation @@ -29,7 +31,15 @@ namespace UVtools.Core.Operations public override string ProgressTitle => null; public override string ProgressAction => null; - + + #endregion + + #region Constructor + + public OperationCalibrateExternalTests() { } + + public OperationCalibrateExternalTests(FileFormat slicerFile) : base(slicerFile) { } + #endregion #region Properties @@ -37,7 +47,7 @@ namespace UVtools.Core.Operations #endregion #region Equality - + #endregion #region Methods diff --git a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs index 7d78d6f..844d7fd 100644 --- a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs +++ b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs @@ -10,7 +10,6 @@ using System; using System.Drawing; using System.Text; using System.Threading.Tasks; -using System.Xml.Serialization; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; @@ -25,7 +24,6 @@ namespace UVtools.Core.Operations public sealed class OperationCalibrateGrayscale : Operation { #region Members - private Size _resolution = Size.Empty; private decimal _layerHeight = 0.05M; private ushort _bottomLayers = 10; private ushort _interfaceLayers = 5; @@ -100,15 +98,21 @@ namespace UVtools.Core.Operations #endregion - #region Properties + #region Constructor + + public OperationCalibrateGrayscale() { } - [XmlIgnore] - public Size Resolution + public OperationCalibrateGrayscale(FileFormat slicerFile) : base(slicerFile) { - get => _resolution; - set => RaiseAndSetIfChanged(ref _resolution, value); + _layerHeight = (decimal)slicerFile.LayerHeight; + _bottomLayers = slicerFile.BottomLayerCount; + _bottomExposure = (decimal)slicerFile.BottomExposureTime; + _normalExposure = (decimal)slicerFile.ExposureTime; + _mirrorOutput = slicerFile.MirrorDisplay; } + #endregion + #region Properties public decimal LayerHeight { @@ -340,10 +344,10 @@ namespace UVtools.Core.Operations { Mat[] layers = new Mat[3]; - layers[0] = EmguExtensions.InitMat(Resolution); + layers[0] = EmguExtensions.InitMat(SlicerFile.Resolution); - int radius = Math.Max(100, Math.Min(Resolution.Width, Resolution.Height) - _outerMargin * 2) / 2 ; - Point center = new Point(Resolution.Width / 2, Resolution.Height / 2); + int radius = Math.Max(100, Math.Min(SlicerFile.Resolution.Width, SlicerFile.Resolution.Height) - _outerMargin * 2) / 2 ; + Point center = new Point(SlicerFile.Resolution.Width / 2, SlicerFile.Resolution.Height / 2); int innerRadius = Math.Max(100, radius - _innerMargin); double topLineLength = 0; @@ -452,32 +456,22 @@ namespace UVtools.Core.Operations return thumbnail; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerCount); - slicerFile.SuppressRebuildProperties = true; - + progress.ItemCount = LayerCount; var newLayers = new Layer[LayerCount]; - slicerFile.LayerHeight = (float)LayerHeight; - slicerFile.BottomExposureTime = (float)BottomExposure; - slicerFile.ExposureTime = (float)NormalExposure; - slicerFile.BottomLayerCount = BottomLayers; - var layers = GetLayers(); - progress++; - - var bottomLayer = new Layer(0, layers[0], slicerFile.LayerManager) + var bottomLayer = new Layer(0, layers[0], SlicerFile.LayerManager) { IsModified = true }; - var interfaceLayer = InterfaceLayers > 0 && layers[1] is not null ? new Layer(0, layers[1], slicerFile.LayerManager) + var interfaceLayer = InterfaceLayers > 0 && layers[1] is not null ? new Layer(0, layers[1], SlicerFile.LayerManager) { IsModified = true } : null; - var layer = new Layer(0, layers[2], slicerFile.LayerManager) + var layer = new Layer(0, layers[2], SlicerFile.LayerManager) { IsModified = true }; @@ -511,17 +505,20 @@ namespace UVtools.Core.Operations } - if (slicerFile.ThumbnailsCount > 0) - slicerFile.SetThumbnails(GetThumbnail()); + if (SlicerFile.ThumbnailsCount > 0) + SlicerFile.SetThumbnails(GetThumbnail()); - progress++; + SlicerFile.SuppressRebuildProperties = true; + SlicerFile.LayerHeight = (float)LayerHeight; + SlicerFile.BottomExposureTime = (float)BottomExposure; + SlicerFile.ExposureTime = (float)NormalExposure; + SlicerFile.BottomLayerCount = BottomLayers; + SlicerFile.LayerManager.Layers = newLayers; + SlicerFile.LayerManager.RebuildLayersProperties(); + SlicerFile.SuppressRebuildProperties = false; - slicerFile.LayerManager.Layers = newLayers; - slicerFile.SuppressRebuildProperties = false; - slicerFile.LayerManager.RebuildLayersProperties(); - - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationCalibrateStressTower.cs b/UVtools.Core/Operations/OperationCalibrateStressTower.cs index 4d95608..1318f09 100644 --- a/UVtools.Core/Operations/OperationCalibrateStressTower.cs +++ b/UVtools.Core/Operations/OperationCalibrateStressTower.cs @@ -10,7 +10,6 @@ using System; using System.Drawing; using System.Text; using System.Threading.Tasks; -using System.Xml.Serialization; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; @@ -98,10 +97,31 @@ namespace UVtools.Core.Operations #endregion - #region Properties + #region Constructor + + public OperationCalibrateStressTower() { } + + public OperationCalibrateStressTower(FileFormat slicerFile) : base(slicerFile) + { + _layerHeight = (decimal)slicerFile.LayerHeight; + _bottomLayers = slicerFile.BottomLayerCount; + _bottomExposure = (decimal)slicerFile.BottomExposureTime; + _normalExposure = (decimal)slicerFile.ExposureTime; + _mirrorOutput = slicerFile.MirrorDisplay; + } + + public override void InitWithSlicerFile() + { + base.InitWithSlicerFile(); + if (SlicerFile.DisplayWidth > 0) + DisplayWidth = (decimal)SlicerFile.DisplayWidth; + if (SlicerFile.DisplayHeight > 0) + DisplayHeight = (decimal)SlicerFile.DisplayHeight; + } - [XmlIgnore] - public Size Resolution { get; set; } = Size.Empty; + #endregion + + #region Properties public decimal DisplayWidth { @@ -294,8 +314,8 @@ namespace UVtools.Core.Operations { var layers = new Mat[LayerCount]; - Slicer slicer = new(Resolution, new SizeF((float) DisplayWidth, (float) DisplayHeight)); - Point center = new Point(Resolution.Width / 2, Resolution.Height / 2); + Slicer slicer = new(SlicerFile.Resolution, new SizeF((float) DisplayWidth, (float) DisplayHeight)); + Point center = new Point(SlicerFile.Resolution.Width / 2, SlicerFile.Resolution.Height / 2); uint baseRadius = slicer.PixelsFromMillimeters(_baseDiameter) / 2; uint baseLayers = (ushort) Math.Floor(_baseHeight / _layerHeight); uint bodyLayers = (ushort) Math.Floor(_bodyHeight / _layerHeight); @@ -315,7 +335,7 @@ namespace UVtools.Core.Operations var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor);*/ Parallel.For(0, LayerCount, layerIndex => { - layers[layerIndex] = EmguExtensions.InitMat(Resolution); + layers[layerIndex] = EmguExtensions.InitMat(SlicerFile.Resolution); }); Parallel.For(0, baseLayers, layerIndex => @@ -389,24 +409,16 @@ namespace UVtools.Core.Operations return thumbnail; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerCount); - slicerFile.SuppressRebuildProperties = true; - + progress.ItemCount = LayerCount; var newLayers = new Layer[LayerCount]; - slicerFile.LayerHeight = (float)LayerHeight; - slicerFile.BottomExposureTime = (float)BottomExposure; - slicerFile.ExposureTime = (float)NormalExposure; - slicerFile.BottomLayerCount = BottomLayers; - var layers = GetLayers(); Parallel.For(0, LayerCount, layerIndex => { - newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], slicerFile.LayerManager); + newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], SlicerFile.LayerManager); layers[layerIndex].Dispose(); lock (progress) { @@ -414,22 +426,21 @@ namespace UVtools.Core.Operations } }); - slicerFile.LayerManager.Layers = newLayers; - slicerFile.LayerManager.RebuildLayersProperties(); - - /*var moveOp = new OperationMove(slicerFile.LayerManager.BoundingRectangle, slicerFile.Resolution) - { - IsCutMove = true, - LayerIndexEnd = slicerFile.LayerCount - 1 - }; - moveOp.Execute(slicerFile, progress);*/ + + if (SlicerFile.ThumbnailsCount > 0) + SlicerFile.SetThumbnails(GetThumbnail()); - if (slicerFile.ThumbnailsCount > 0) - slicerFile.SetThumbnails(GetThumbnail()); + SlicerFile.SuppressRebuildProperties = true; + SlicerFile.LayerHeight = (float)LayerHeight; + SlicerFile.BottomExposureTime = (float)BottomExposure; + SlicerFile.ExposureTime = (float)NormalExposure; + SlicerFile.BottomLayerCount = BottomLayers; + SlicerFile.LayerManager.Layers = newLayers; + SlicerFile.LayerManager.RebuildLayersProperties(); + SlicerFile.SuppressRebuildProperties = false; - slicerFile.SuppressRebuildProperties = false; - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationCalibrateTolerance.cs b/UVtools.Core/Operations/OperationCalibrateTolerance.cs index aa9bf81..190f111 100644 --- a/UVtools.Core/Operations/OperationCalibrateTolerance.cs +++ b/UVtools.Core/Operations/OperationCalibrateTolerance.cs @@ -128,9 +128,6 @@ namespace UVtools.Core.Operations #region Properties - [XmlIgnore] - public Size Resolution { get; set; } = Size.Empty; - public decimal DisplayWidth { get => _displayWidth; @@ -151,8 +148,8 @@ namespace UVtools.Core.Operations } } - public decimal Xppmm => DisplayWidth > 0 ? Math.Round(Resolution.Width / DisplayWidth, 2) : 0; - public decimal Yppmm => DisplayHeight > 0 ? Math.Round(Resolution.Height / DisplayHeight, 2) : 0; + public decimal Xppmm => DisplayWidth > 0 ? Math.Round(SlicerFile.Resolution.Width / DisplayWidth, 2) : 0; + public decimal Yppmm => DisplayHeight > 0 ? Math.Round(SlicerFile.Resolution.Height / DisplayHeight, 2) : 0; public decimal LayerHeight { @@ -425,8 +422,32 @@ namespace UVtools.Core.Operations */ #endregion + #region Constructor + + public OperationCalibrateTolerance() { } + + public OperationCalibrateTolerance(FileFormat slicerFile) : base(slicerFile) + { + _layerHeight = (decimal)slicerFile.LayerHeight; + _bottomLayers = slicerFile.BottomLayerCount; + _bottomExposure = (decimal)slicerFile.BottomExposureTime; + _normalExposure = (decimal)slicerFile.ExposureTime; + _mirrorOutput = slicerFile.MirrorDisplay; + } + + public override void InitWithSlicerFile() + { + base.InitWithSlicerFile(); + if (SlicerFile.DisplayWidth > 0) + DisplayWidth = (decimal)SlicerFile.DisplayWidth; + if (SlicerFile.DisplayHeight > 0) + DisplayHeight = (decimal)SlicerFile.DisplayHeight; + } + + #endregion + #region Enums - + public enum Shapes : byte { Circle, @@ -483,7 +504,7 @@ namespace UVtools.Core.Operations public Mat[] GetLayers() { var layers = new Mat[LayerCount]; - var layer = EmguExtensions.InitMat(Resolution); + var layer = EmguExtensions.InitMat(SlicerFile.Resolution); ushort startX = Math.Max((ushort)2, _leftRightMargin); ushort startY = Math.Max((ushort)2, _topBottomMargin); @@ -534,13 +555,13 @@ namespace UVtools.Core.Operations if (_fuseParts) { if (xPixels >= FemaleHoleDiameterXPixels || yPixels >= FemaleHoleDiameterYPixels) return false; - if (currentX + FemaleDiameterXPixels + _leftRightMargin >= Resolution.Width) + if (currentX + FemaleDiameterXPixels + _leftRightMargin >= SlicerFile.Resolution.Width) { currentX = startX; currentY += (int)FemaleDiameterYPixels + PartMargin; } - if (currentY + FemaleDiameterYPixels + _topBottomMargin >= Resolution.Height) + if (currentY + FemaleDiameterYPixels + _topBottomMargin >= SlicerFile.Resolution.Height) { return false; // Insufficient size } @@ -571,13 +592,13 @@ namespace UVtools.Core.Operations } else { - if (currentX + xPixels + _leftRightMargin >= Resolution.Width) + if (currentX + xPixels + _leftRightMargin >= SlicerFile.Resolution.Width) { currentX = startX; currentY += yPixels + PartMargin; } - if (currentY + yPixels + _topBottomMargin >= Resolution.Height) + if (currentY + yPixels + _topBottomMargin >= SlicerFile.Resolution.Height) { return false; // Insufficient size } @@ -704,24 +725,17 @@ namespace UVtools.Core.Operations return thumbnail; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerCount); - slicerFile.SuppressRebuildProperties = true; + progress.ItemCount = LayerCount; var newLayers = new Layer[LayerCount]; - slicerFile.LayerHeight = (float)LayerHeight; - slicerFile.BottomExposureTime = (float)BottomExposure; - slicerFile.ExposureTime = (float)NormalExposure; - slicerFile.BottomLayerCount = BottomLayers; - var layers = GetLayers(); Parallel.For(0, LayerCount, layerIndex => { - newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], slicerFile.LayerManager); + newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], SlicerFile.LayerManager); layers[layerIndex].Dispose(); lock (progress) { @@ -729,22 +743,23 @@ namespace UVtools.Core.Operations } }); - slicerFile.LayerManager.Layers = newLayers; - slicerFile.LayerManager.RebuildLayersProperties(); + if (SlicerFile.ThumbnailsCount > 0) + SlicerFile.SetThumbnails(GetThumbnail()); - var moveOp = new OperationMove(slicerFile.LayerManager.BoundingRectangle, slicerFile.Resolution) - { - IsCutMove = true, - LayerIndexEnd = slicerFile.LayerCount - 1 - }; - moveOp.Execute(slicerFile, progress); + SlicerFile.SuppressRebuildProperties = true; + SlicerFile.LayerHeight = (float)LayerHeight; + SlicerFile.BottomExposureTime = (float)BottomExposure; + SlicerFile.ExposureTime = (float)NormalExposure; + SlicerFile.BottomLayerCount = BottomLayers; + SlicerFile.LayerManager.Layers = newLayers; + SlicerFile.LayerManager.RebuildLayersProperties(); + SlicerFile.SuppressRebuildProperties = false; - if (slicerFile.ThumbnailsCount > 0) - slicerFile.SetThumbnails(GetThumbnail()); + var moveOp = new OperationMove(SlicerFile); + moveOp.Execute(progress); - slicerFile.SuppressRebuildProperties = false; - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs index 3c5f4bd..a36d4a9 100644 --- a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs +++ b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs @@ -118,9 +118,6 @@ namespace UVtools.Core.Operations #region Properties - [XmlIgnore] - public Size Resolution { get; set; } = Size.Empty; - public decimal DisplayWidth { get => _displayWidth; @@ -141,8 +138,8 @@ namespace UVtools.Core.Operations } } - public decimal Xppmm => DisplayWidth > 0 ? Math.Round(Resolution.Width / DisplayWidth, 2) : 0; - public decimal Yppmm => DisplayHeight > 0 ? Math.Round(Resolution.Height / DisplayHeight, 2) : 0; + public decimal Xppmm => DisplayWidth > 0 ? Math.Round(SlicerFile.Resolution.Width / DisplayWidth, 2) : 0; + public decimal Yppmm => DisplayHeight > 0 ? Math.Round(SlicerFile.Resolution.Height / DisplayHeight, 2) : 0; public decimal LayerHeight { @@ -447,6 +444,30 @@ namespace UVtools.Core.Operations #endregion + #region Constructor + + public OperationCalibrateXYZAccuracy() { } + + public OperationCalibrateXYZAccuracy(FileFormat slicerFile) : base(slicerFile) + { + _layerHeight = (decimal)slicerFile.LayerHeight; + _bottomLayers = slicerFile.BottomLayerCount; + _bottomExposure = (decimal)slicerFile.BottomExposureTime; + _normalExposure = (decimal)slicerFile.ExposureTime; + _mirrorOutput = slicerFile.MirrorDisplay; + } + + public override void InitWithSlicerFile() + { + base.InitWithSlicerFile(); + if (SlicerFile.DisplayWidth > 0) + DisplayWidth = (decimal)SlicerFile.DisplayWidth; + if (SlicerFile.DisplayHeight > 0) + DisplayHeight = (decimal)SlicerFile.DisplayHeight; + } + + #endregion + #region Enums #endregion @@ -465,7 +486,7 @@ namespace UVtools.Core.Operations #endregion #region Equality - + private bool Equals(OperationCalibrateXYZAccuracy other) { return _layerHeight == other._layerHeight && _bottomLayers == other._bottomLayers && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _topBottomMargin == other._topBottomMargin && _leftRightMargin == other._leftRightMargin && _xSize == other._xSize && _ySize == other._ySize && _zSize == other._zSize && _centerHoleRelief == other._centerHoleRelief && _hollowModel == other._hollowModel && _wallThickness == other._wallThickness && _observedXSize == other._observedXSize && _observedYSize == other._observedYSize && _observedZSize == other._observedZSize && _outputTLObject == other._outputTLObject && _outputTCObject == other._outputTCObject && _outputTRObject == other._outputTRObject && _outputMLObject == other._outputMLObject && _outputMCObject == other._outputMCObject && _outputMRObject == other._outputMRObject && _outputBLObject == other._outputBLObject && _outputBCObject == other._outputBCObject && _outputBRObject == other._outputBRObject && _mirrorOutput == other._mirrorOutput; @@ -586,7 +607,7 @@ namespace UVtools.Core.Operations var layers = new Mat[2]; for (byte i = 0; i < layers.Length; i++) { - layers[i] = EmguExtensions.InitMat(Resolution); + layers[i] = EmguExtensions.InitMat(SlicerFile.Resolution); } @@ -610,11 +631,11 @@ namespace UVtools.Core.Operations positionYStr = "T"; break; case 1: - currentY = (int)(Resolution.Height / 2 - YPixels / 2); + currentY = (int)(SlicerFile.Resolution.Height / 2 - YPixels / 2); positionYStr = "M"; break; case 2: - currentY = (int)(Resolution.Height - YPixels - _topBottomMargin); + currentY = (int)(SlicerFile.Resolution.Height - YPixels - _topBottomMargin); positionYStr = "B"; break; } @@ -627,11 +648,11 @@ namespace UVtools.Core.Operations positionStr = $"{positionYStr}L"; break; case 1: - currentX = (int)(Resolution.Width / 2 - XPixels / 2); + currentX = (int)(SlicerFile.Resolution.Width / 2 - XPixels / 2); positionStr = $"{positionYStr}C"; break; case 2: - currentX = (int)(Resolution.Width - XPixels - _leftRightMargin); + currentX = (int)(SlicerFile.Resolution.Width - XPixels - _leftRightMargin); positionStr = $"{positionYStr}R"; break; } @@ -716,34 +737,27 @@ namespace UVtools.Core.Operations return thumbnail; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerCount); + progress.ItemCount = LayerCount; - slicerFile.SuppressRebuildProperties = true; var newLayers = new Layer[LayerCount]; - slicerFile.LayerHeight = (float)LayerHeight; - slicerFile.BottomExposureTime = (float)BottomExposure; - slicerFile.ExposureTime = (float)NormalExposure; - slicerFile.BottomLayerCount = BottomLayers; - var layers = GetLayers(); - var bottomLayer = new Layer(0, layers[0], slicerFile.LayerManager) + var bottomLayer = new Layer(0, layers[0], SlicerFile.LayerManager) { IsModified = true }; - var layer = new Layer(0, layers[1], slicerFile.LayerManager) + var layer = new Layer(0, layers[1], SlicerFile.LayerManager) { IsModified = true }; for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { - newLayers[layerIndex] = slicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomLayer.Clone(), layer.Clone()); + newLayers[layerIndex] = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomLayer.Clone(), layer.Clone()); progress++; } @@ -752,13 +766,19 @@ namespace UVtools.Core.Operations mat.Dispose(); } - if (slicerFile.ThumbnailsCount > 0) - slicerFile.SetThumbnails(GetThumbnail()); + if (SlicerFile.ThumbnailsCount > 0) + SlicerFile.SetThumbnails(GetThumbnail()); + + SlicerFile.SuppressRebuildProperties = true; + SlicerFile.LayerHeight = (float)LayerHeight; + SlicerFile.BottomExposureTime = (float)BottomExposure; + SlicerFile.ExposureTime = (float)NormalExposure; + SlicerFile.BottomLayerCount = BottomLayers; + SlicerFile.LayerManager.Layers = newLayers; + SlicerFile.LayerManager.RebuildLayersProperties(); + SlicerFile.SuppressRebuildProperties = false; - slicerFile.LayerManager.Layers = newLayers; - slicerFile.LayerManager.RebuildLayersProperties(); - slicerFile.SuppressRebuildProperties = false; - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationChangeResolution.cs b/UVtools.Core/Operations/OperationChangeResolution.cs index fe4d3cf..4a020c9 100644 --- a/UVtools.Core/Operations/OperationChangeResolution.cs +++ b/UVtools.Core/Operations/OperationChangeResolution.cs @@ -64,25 +64,25 @@ namespace UVtools.Core.Operations public override string ConfirmationText => "change print resolution " + - $"from {OldResolution.Width}x{OldResolution.Height} " + + $"from {SlicerFile.ResolutionX}x{SlicerFile.ResolutionY} " + $"to {NewResolutionX}x{NewResolutionY}?"; public override string ProgressTitle => - $"Changing print resolution from ({OldResolution.Width}x{OldResolution.Height}) to ({NewResolutionX}x{NewResolutionY})"; + $"Changing print resolution from ({SlicerFile.ResolutionX}x{SlicerFile.ResolutionY}) to ({NewResolutionX}x{NewResolutionY})"; public override string ProgressAction => "Changed layers"; public override StringTag Validate(params object[] parameters) { var sb = new StringBuilder(); - if (OldResolution.Width == NewResolutionX && OldResolution.Height == NewResolutionY) + if (SlicerFile.ResolutionX == NewResolutionX && SlicerFile.ResolutionY == NewResolutionY) { - sb.AppendLine($"The new resolution must be different from current resolution ({OldResolution.Width} x {OldResolution.Height})."); + sb.AppendLine($"The new resolution must be different from current resolution ({SlicerFile.ResolutionX} x {SlicerFile.ResolutionY})."); } - if (NewResolutionX < VolumeBonds.Width || NewResolutionY < VolumeBonds.Height) + if (NewResolutionX < SlicerFile.BoundingRectangle.Width || NewResolutionY < SlicerFile.BoundingRectangle.Height) { - sb.AppendLine($"The new resolution ({NewResolutionX} x {NewResolutionY}) is not large enough to hold the model volume ({VolumeBonds.Width} x {VolumeBonds.Height}), continuing operation would clip the model"); + sb.AppendLine($"The new resolution ({NewResolutionX} x {NewResolutionY}) is not large enough to hold the model volume ({SlicerFile.BoundingRectangle.Width} x {SlicerFile.BoundingRectangle.Height}), continuing operation would clip the model"); sb.AppendLine("To fix this, try to rotate the object and/or resize to fit on this new resolution."); } @@ -99,8 +99,6 @@ namespace UVtools.Core.Operations #endregion #region Properties - public Size OldResolution { get; } - public uint NewResolutionX { get => _newResolutionX; @@ -113,25 +111,20 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _newResolutionY, value); } - public Rectangle VolumeBonds { get; } - - public Size VolumeBondsSize => VolumeBonds.Size; + public Size VolumeBondsSize => SlicerFile.BoundingRectangle.Size; #endregion #region Constructor - public OperationChangeResolution() - { - } + public OperationChangeResolution() { } - public OperationChangeResolution(Size oldResolution, Rectangle volumeBonds) + public OperationChangeResolution(FileFormat slicerFile) : base(slicerFile) { - OldResolution = oldResolution; - VolumeBonds = volumeBonds; - NewResolutionX = (uint) oldResolution.Width; - NewResolutionY = (uint) oldResolution.Height; + NewResolutionX = SlicerFile.ResolutionX; + NewResolutionY = SlicerFile.ResolutionY; } + #endregion #region Methods @@ -159,25 +152,24 @@ namespace UVtools.Core.Operations public static Resolution[] Presets => GetResolutions(); - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, slicerFile.LayerCount); + progress.ItemCount = SlicerFile.LayerCount; - Parallel.For(0, slicerFile.LayerCount, layerIndex => + Parallel.For(0, SlicerFile.LayerCount, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var mat = slicerFile[layerIndex].LayerMat; - using var matRoi = new Mat(mat, VolumeBonds); + using var mat = SlicerFile[layerIndex].LayerMat; + using var matRoi = new Mat(mat, SlicerFile.BoundingRectangle); using var matDst = new Mat(new Size((int)NewResolutionX, (int)NewResolutionY), mat.Depth, mat.NumberOfChannels); using var matDstRoi = new Mat(matDst, - new Rectangle((int)(NewResolutionX / 2 - VolumeBonds.Width / 2), - (int)NewResolutionY / 2 - VolumeBonds.Height / 2, - VolumeBonds.Width, VolumeBonds.Height)); + new Rectangle((int)(NewResolutionX / 2 - SlicerFile.BoundingRectangle.Width / 2), + (int)NewResolutionY / 2 - SlicerFile.BoundingRectangle.Height / 2, + SlicerFile.BoundingRectangle.Width, SlicerFile.BoundingRectangle.Height)); matRoi.CopyTo(matDstRoi); //Execute(mat); - slicerFile[layerIndex].LayerMat = matDst; + SlicerFile[layerIndex].LayerMat = matDst; lock (progress.Mutex) { @@ -187,10 +179,10 @@ namespace UVtools.Core.Operations progress.Token.ThrowIfCancellationRequested(); - slicerFile.ResolutionX = NewResolutionX; - slicerFile.ResolutionY = NewResolutionY; + SlicerFile.ResolutionX = NewResolutionX; + SlicerFile.ResolutionY = NewResolutionY; - return true; + return !progress.Token.IsCancellationRequested; } /*public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs new file mode 100644 index 0000000..3615714 --- /dev/null +++ b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs @@ -0,0 +1,760 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Drawing; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; +using UVtools.Core.Objects; + +namespace UVtools.Core.Operations +{ + [Serializable] + public sealed class OperationDynamicLayerHeight : Operation + { + #region Sub Classes + public sealed class Report + { + public uint OldLayerCount { get; set; } + public uint NewLayerCount { get; set; } + public uint StackedLayers { get; set; } + public uint ReusedLayers => OldLayerCount - StackedLayers; + public float MaximumLayerHeight { get; set; } + public float OldPrintTime { get; set; } + public float NewPrintTime { get; set; } + public double SparedPrintTime => Math.Round(OldPrintTime - NewPrintTime, 2); + + public double CompressionRatio => Math.Round((double)OldLayerCount / NewLayerCount * 100.0, 2); + + public override string ToString() + { + var oldTime = TimeSpan.FromSeconds(OldPrintTime); + var newTime = TimeSpan.FromSeconds(NewPrintTime); + var sparedTime = TimeSpan.FromSeconds(SparedPrintTime); + return + $"From {OldLayerCount} layers, {ReusedLayers} got reused, {StackedLayers} got stacked and optimized with dynamic layer height's\n" + + $"Resultant layers: {NewLayerCount}\n" + + $"Compression ratio: {CompressionRatio}%\n" + + $"Maximum layer height reached: {MaximumLayerHeight}mm\n" + + $"Print time: {oldTime.Hours}h{oldTime.Minutes}m{oldTime.Seconds}s -> {newTime.Hours}h{newTime.Minutes}m{newTime.Seconds}s (- {sparedTime.Hours}h{sparedTime.Minutes}m{sparedTime.Seconds}s)"; + } + } + + [Serializable] + public sealed class ExposureItem : BindableBase + { + private decimal _layerHeight; + private decimal _bottomExposure; + private decimal _exposure; + + + /// + /// Gets or sets the layer height in millimeters + /// + public decimal LayerHeight + { + get => _layerHeight; + set => RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2)); + } + + + /// + /// Gets or sets the bottom exposure in seconds + /// + public decimal BottomExposure + { + get => _bottomExposure; + set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2)); + } + + /// + /// Gets or sets the bottom exposure in seconds + /// + public decimal Exposure + { + get => _exposure; + set => RaiseAndSetIfChanged(ref _exposure, Math.Round(value, 2)); + } + + public ExposureItem() { } + + public ExposureItem(decimal layerHeight, decimal bottomExposure = 0, decimal exposure = 0) + { + _layerHeight = Math.Round(layerHeight, 2); + _bottomExposure = Math.Round(bottomExposure, 2); + _exposure = Math.Round(exposure, 2); + } + + public override string ToString() + { + return $"{nameof(LayerHeight)}: {LayerHeight}mm, {nameof(BottomExposure)}: {BottomExposure}s, {nameof(Exposure)}: {Exposure}s"; + } + } + #endregion + + #region Constants + public const byte ObjectsPerCache = 2; + #endregion + + #region Members + + //private decimal _displayWidth; + //private decimal _displayHeight; + private decimal _minimumLayerHeight = 0.03m; + private decimal _maximumLayerHeight = 0.10m; + private decimal _cacheRamSize = 1.5m; + private ExposureSetTypes _exposureSetType = ExposureSetTypes.Linear; + private decimal _bottomExposureStep = 0.5m; + private decimal _exposureStep = 0.2m; + private ObservableCollection _automaticExposureTable = new(); + private ObservableCollection _manualExposureTable = new(); + + #endregion + + #region Overrides + + public override bool CanROI => false; + + public override string Title => "Dynamic layer height"; + + public override string Description => + "Analyze and optimize the model with dynamic layer heights, larger angles will slice at lower layer height" + + " while more straight angles will slice larger layer height.\n" + + "Note: The model should be sliced at the lowest layer height possible (0.01mm).\n" + + "After this, do not apply any modification which reconstruct the z positions of the layers. " + + "Only few printers support this, make sure your is supported or else it will print a malformed model."; + + public override string ConfirmationText => + $"dynamic layers from layers {LayerIndexStart} through {LayerIndexEnd}?"; + + public override string ProgressTitle => + $"Analyzing and optimizing layers height from layers {LayerIndexStart} through {LayerIndexEnd}"; + + public override string ProgressAction => "Processed layers"; + + public override StringTag Validate(params object[] parameters) + { + var sb = new StringBuilder(); + + /*if (XYResolutionUm <= 0) + { + sb.AppendLine($"Display width and height must be a positive value."); + }*/ + + decimal layerHeight = (decimal) SlicerFile.LayerHeight; + if (_minimumLayerHeight < layerHeight) + { + sb.AppendLine( + $"Minimum layer height ({_minimumLayerHeight}mm) must be equal or higher than file layer height ({layerHeight}mm)"); + } + if (_minimumLayerHeight > _maximumLayerHeight) + { + sb.AppendLine( + $"Minimum layer height ({_minimumLayerHeight}mm) can't be higher than maximum layer height ({_maximumLayerHeight}mm)"); + } + if (layerHeight >= _maximumLayerHeight) + { + sb.AppendLine( + $"Maximum layer height ({_maximumLayerHeight}mm) can't be the same or less than current file layer height ({SlicerFile.LayerHeight}mm)"); + } + + var exposureTable = ExposureTableDictionary; + + for (layerHeight = (decimal) SlicerFile.LayerHeight; + layerHeight <= _maximumLayerHeight; + layerHeight = layerHeight + (decimal) SlicerFile.LayerHeight) + { + layerHeight = Math.Round(layerHeight, 2); + if (exposureTable.TryGetValue(layerHeight, out var exposure)) + { + if (exposure.BottomExposure <= 0 || exposure.Exposure <= 0) + { + sb.AppendLine($"Layer height {layerHeight}mm exposures must be a positive value, current: {exposure.BottomExposure}s/{exposure.Exposure}s"); + } + } + else + { + sb.AppendLine($"Layer height {layerHeight}mm exposures are missing."); + } + } + + return new StringTag(sb.ToString()); + } + + public override string ToString() + { + var result = $"[Layer Height: Min: {_minimumLayerHeight}mm Max: {_maximumLayerHeight}mm] [RAM: {_cacheRamSize}Gb] [Exposure type: {_exposureSetType}, Steps: {_bottomExposureStep}s/{_exposureStep}s]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + + #endregion + + #region Enums + + public enum ExposureSetTypes: byte + { + Linear, + Multiplier, + Manual + } + + public static Array ExposureSetTypeItems => Enum.GetValues(typeof(ExposureSetTypes)); + + #endregion + + #region Properties + + /*public decimal DisplayWidth + { + get => _displayWidth; + set + { + if (!RaiseAndSetIfChanged(ref _displayWidth, Math.Round(value, 2))) return; + //RaisePropertyChanged(nameof(XYResolutionUm)); + } + } + + public decimal DisplayHeight + { + get => _displayHeight; + set + { + if (!RaiseAndSetIfChanged(ref _displayHeight, Math.Round(value, 2))) return; + //RaisePropertyChanged(nameof(XYResolutionUm)); + } + } + + public decimal XYResolutionUm => DisplayWidth > 0 || DisplayHeight > 0 ? + Math.Round(Math.Max( + DisplayWidth / SlicerFile.ResolutionX, + DisplayHeight / SlicerFile.ResolutionY + ), 3) * 1000 + : 0; + */ + + public decimal MinimumLayerHeight + { + get => _minimumLayerHeight; + set + { + if (!RaiseAndSetIfChanged(ref _minimumLayerHeight, Math.Round(value, 2))) return; + //RaisePropertyChanged(nameof(ExposureData)); + //if (!IsExposureSetTypeManual) RebuildAutoExposureTable(); + } + } + + public decimal MaximumLayerHeight + { + get => _maximumLayerHeight; + set + { + if(!RaiseAndSetIfChanged(ref _maximumLayerHeight, Math.Round(value, 2))) return; + //RaisePropertyChanged(nameof(ExposureData)); + if(!IsExposureSetTypeManual) RebuildAutoExposureTable(); + } + } + + public uint CacheObjectCount => (uint) (_cacheRamSize * 1000000000L / SlicerFile.Resolution.GetArea() / ObjectsPerCache); + + public decimal CacheRAMSize + { + get => _cacheRamSize; + set + { + if(!RaiseAndSetIfChanged(ref _cacheRamSize, Math.Round(value, 2))) return; + RaisePropertyChanged(nameof(CacheObjectCount)); + } + } + + public ExposureSetTypes ExposureSetType + { + get => _exposureSetType; + set + { + if(!RaiseAndSetIfChanged(ref _exposureSetType, value)) return; + RaisePropertyChanged(nameof(IsExposureSetTypeManual)); + RaisePropertyChanged(nameof(ExposureTable)); + RaisePropertyChanged(nameof(ExposureTableDictionary)); + //RaisePropertyChanged(nameof(ExposureData)); + if (!IsExposureSetTypeManual) RebuildAutoExposureTable(); + } + } + + public bool IsExposureSetTypeManual => _exposureSetType == ExposureSetTypes.Manual; + + public decimal BottomExposureStep + { + get => _bottomExposureStep; + set + { + if(!RaiseAndSetIfChanged(ref _bottomExposureStep, value)) return; + //RaisePropertyChanged(nameof(ExposureData)); + if (!IsExposureSetTypeManual) RebuildAutoExposureTable(); + } + } + + public decimal ExposureStep + { + get => _exposureStep; + set + { + if(!RaiseAndSetIfChanged(ref _exposureStep, value)) return; + //RaisePropertyChanged(nameof(ExposureData)); + if (!IsExposureSetTypeManual) RebuildAutoExposureTable(); + } + } + + [XmlIgnore] + public ObservableCollection AutomaticExposureTable + { + get + { + if(_automaticExposureTable.Count == 0) RebuildAutoExposureTable(); + return _automaticExposureTable; + } + set => RaiseAndSetIfChanged(ref _automaticExposureTable, value); + } + + public ObservableCollection ManualExposureTable + { + get => _manualExposureTable; + set => RaiseAndSetIfChanged(ref _manualExposureTable, value); + } + + [XmlIgnore] + public ObservableCollection ExposureTable => IsExposureSetTypeManual ? _manualExposureTable : AutomaticExposureTable; + + /// + /// Gets the exposure table into a dictionary where key is the layer height + /// + [XmlIgnore] + public Dictionary ExposureTableDictionary + { + get + { + Dictionary dictionary = new(); + foreach (var exposure in ExposureTable) + { + dictionary.TryAdd(exposure.LayerHeight, exposure); + } + + return dictionary; + } + } + + public string ExposureData + { + get + { + StringBuilder sb = new(); + byte count = 0; + for (decimal layerHeight = (decimal) SlicerFile.LayerHeight; layerHeight <= _maximumLayerHeight; layerHeight+= (decimal)SlicerFile.LayerHeight) + { + decimal bottomExposure = 0; + decimal exposure = 0; + switch (_exposureSetType) + { + case ExposureSetTypes.Linear: + bottomExposure = (decimal)SlicerFile.BottomExposureTime + count * _bottomExposureStep; + exposure = (decimal)SlicerFile.ExposureTime + count * _exposureStep; + break; + case ExposureSetTypes.Multiplier: + bottomExposure = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.BottomExposureTime * count * layerHeight * _bottomExposureStep; + exposure = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.ExposureTime * count * layerHeight * _exposureStep; + break; + case ExposureSetTypes.Manual: + break; + default: + throw new ArgumentOutOfRangeException(); + } + sb.AppendLine($"{layerHeight:F2}mm: {bottomExposure:F2}s / {exposure:F2}s"); + count++; + } + return sb.ToString(); + } + } + + #endregion + + #region Constructor + + public OperationDynamicLayerHeight() + { + //InitManualTable(); + } + + public OperationDynamicLayerHeight(FileFormat slicerFile) : base(slicerFile) + { + InitManualTable(); + } + + public override void InitWithSlicerFile() + { + base.InitWithSlicerFile(); + + /*if (SlicerFile.DisplayWidth > 0) + _displayWidth = (decimal)SlicerFile.DisplayWidth; + if (SlicerFile.DisplayHeight > 0) + _displayHeight = (decimal)SlicerFile.DisplayHeight;*/ + + var layerHeight = (decimal)SlicerFile.LayerHeight; + if (_minimumLayerHeight < layerHeight) + { + _minimumLayerHeight = layerHeight; + } + if (layerHeight * 2 > _maximumLayerHeight) + { + _maximumLayerHeight = Math.Min((decimal) FileFormat.MaximumLayerHeight, _maximumLayerHeight*2); + } + } + + public void InitManualTable() + { + for (decimal layerHeight = (decimal)FileFormat.MinimumLayerHeight; + layerHeight <= (decimal) FileFormat.MaximumLayerHeight; + layerHeight += (decimal)FileFormat.MinimumLayerHeight) + { + var item = new ExposureItem(layerHeight); + if (layerHeight == (decimal) SlicerFile.LayerHeight) + { + item.BottomExposure = (decimal) SlicerFile.BottomExposureTime; + item.Exposure = (decimal) SlicerFile.ExposureTime; + } + _manualExposureTable.Add(item); + } + } + + #endregion + + #region Equality + + private bool Equals(OperationDynamicLayerHeight other) + { + return _minimumLayerHeight == other._minimumLayerHeight && _maximumLayerHeight == other._maximumLayerHeight && _cacheRamSize == other._cacheRamSize && _exposureSetType == other._exposureSetType && _bottomExposureStep == other._bottomExposureStep && _exposureStep == other._exposureStep && Equals(_manualExposureTable, other._manualExposureTable); + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationDynamicLayerHeight other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_minimumLayerHeight, _maximumLayerHeight, _cacheRamSize, (int) _exposureSetType, _bottomExposureStep, _exposureStep, _manualExposureTable); + } + + #endregion + + #region Methods + + public void RebuildAutoExposureTable() + { + if (SlicerFile is null) return; + _automaticExposureTable.Clear(); + byte count = 0; + for (decimal layerHeight = (decimal)SlicerFile.LayerHeight; layerHeight <= _maximumLayerHeight; layerHeight += (decimal)SlicerFile.LayerHeight) + { + decimal bottomExposure = 0; + decimal exposure = 0; + switch (_exposureSetType) + { + case ExposureSetTypes.Linear: + bottomExposure = (decimal)SlicerFile.BottomExposureTime + count * _bottomExposureStep; + exposure = (decimal)SlicerFile.ExposureTime + count * _exposureStep; + break; + case ExposureSetTypes.Multiplier: + bottomExposure = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.BottomExposureTime * count * layerHeight * _bottomExposureStep; + exposure = (decimal)SlicerFile.BottomExposureTime + (decimal)SlicerFile.ExposureTime * count * layerHeight * _exposureStep; + break; + case ExposureSetTypes.Manual: + break; + default: + throw new ArgumentOutOfRangeException(); + } + _automaticExposureTable.Add(new ExposureItem(layerHeight, Math.Round(bottomExposure, 2), Math.Round(exposure, 2))); + count++; + } + } + + protected override bool ExecuteInternally(OperationProgress progress) + { + Report report = new() + { + OldLayerCount = SlicerFile.LayerCount, + OldPrintTime = SlicerFile.PrintTimeOrComputed + }; + + var anchor = new Point(-1, -1); + using var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + + var matCache = new Mat[SlicerFile.LayerCount]; + var matThresholdCache = new Mat[SlicerFile.LayerCount]; + + List layers = new(); + + using Mat matXor = new(); + Mat matXorSum = null; + Mat matSum = null; + + const byte _maximumErodes = 10; + //decimal xyResolutionUm = XYResolutionUm; + + //const double xyRes = 35; + //var stepAngle = Math.Atan(SlicerFile.LayerHeight*1000 / xyRes) * (180 / Math.PI); + //byte maximumErodes = (byte) (_maximumLayerHeight * 100 - (decimal) (slicerFile.LayerHeight * 100f)); + + void CacheLayers(uint fromLayerIndex) + { + for (int layerIndex = (int) fromLayerIndex-2; layerIndex >= 0; layerIndex--) // clean up only the required layers + { + if (matCache[layerIndex] is null) break; + matCache[layerIndex].Dispose(); + matCache[layerIndex] = null; + matThresholdCache[layerIndex].Dispose(); + matThresholdCache[layerIndex] = null; + } + Parallel.For(fromLayerIndex, Math.Min(fromLayerIndex + CacheObjectCount, SlicerFile.LayerCount), layerIndex => + { + if (matCache[layerIndex] is not null) return; // Already cached + matCache[layerIndex] = SlicerFile[layerIndex].LayerMat; + matThresholdCache[layerIndex] = new Mat(); + + // Clean AA + CvInvoke.Threshold(matCache[layerIndex], matThresholdCache[layerIndex], 128, 255, ThresholdType.Binary); + }); + } + + float GetLastPositionZ(float layerHeight) => layers.Count > 0 ? (float)Math.Round(layers[^1].PositionZ + layerHeight, 2) : layerHeight; + + (Mat, Mat) GetLayer(uint layerIndex) + { + if (matCache[layerIndex] is null) + { + CacheLayers(layerIndex); + } + + return (matCache[layerIndex], matThresholdCache[layerIndex]); + } + + void AddNewLayer(Mat mat, float layerHeight) + { + report.MaximumLayerHeight = Math.Max(report.MaximumLayerHeight, layerHeight); + var positionZ = GetLastPositionZ(layerHeight); + var layer = new Layer((uint) layers.Count, mat, SlicerFile) + { + IsModified = true, + PositionZ = positionZ + + }; + layers.Add(layer); + } + + void ReUseLayer(uint layerIndex) + { + SlicerFile[layerIndex].PositionZ = GetLastPositionZ(SlicerFile.LayerHeight); + SlicerFile[layerIndex].Index = (uint) layers.Count; + SlicerFile[layerIndex].IsModified = true; + layers.Add(SlicerFile[layerIndex]); + } + + for (uint layerIndex = 0; layerIndex < LayerIndexStart; layerIndex++) // Skip layers and re-use layers + { + ReUseLayer(layerIndex); + } + + for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; ) + { + Debug.WriteLine($"Head Layer: {layerIndex} ({SlicerFile.LayerHeight}mm)"); + + if (layerIndex == LayerIndexEnd) + { + ReUseLayer(layerIndex); + break; + } + + var currentLayerHeight = SlicerFile.LayerHeight; + byte layerSum = 1; + byte erodeCount = 0; + //byte maxErodeCount = 0; + //double maxSlop = 90; + matSum?.Dispose(); + matSum = null; + matXorSum?.Dispose(); + matXorSum = null; + + while (true) + { + progress.Token.ThrowIfCancellationRequested(); + progress.ProcessedItems = layerIndex; + + if (currentLayerHeight >= (float)_maximumLayerHeight || layerIndex == LayerIndexEnd) + { + // Cant perform any additional stack. Maximum layer height reached! + // Break this cycle and restart from same layer + matSum ??= GetLayer(layerIndex).Item1.Clone(); // This only happen when layer height is already the maximum supported layer height + layerIndex++; + break; + } + + var previousLayerHeight = currentLayerHeight; + currentLayerHeight = (float)Math.Round(currentLayerHeight + SlicerFile.LayerHeight, 2); + + var (mat1, mat1Threshold) = GetLayer(layerIndex); + var (mat2, mat2Threshold) = GetLayer(++layerIndex); + + Debug.Write($" Stacking layer: {layerIndex} ({currentLayerHeight}mm)"); + + matSum ??= mat1.Clone(); + + CvInvoke.BitwiseXor(mat1Threshold, mat2Threshold, matXor); + if (matXorSum is null) + { + matXorSum = matXor.Clone(); + } + else + { + CvInvoke.Add(matXorSum, matXor, matXorSum); + } + + var currentLayerHeigthUm = currentLayerHeight * 1000.0; + + if (CvInvoke.CountNonZero(matXorSum) > 0) // Layers are different + { + bool meetRequirement = false; + for (; erodeCount <= _maximumErodes; ) + { + erodeCount++; + //maxErodeCount = Math.Max(maxErodeCount, erodeCount); + + /*var slope = Math.Atan(currentLayerHeigthUm / (double) (xyResolutionUm * erodeCount)) * (180 / Math.PI); + var stepover = Math.Round(currentLayerHeigthUm / Math.Tan(slope * (Math.PI / 180))); + Debug.Write($" [Slope: {slope:F2} Stepover: {stepover} <= {xyResolutionUm} = {stepover <= (double) xyResolutionUm}]"); + + if (stepover > (double) xyResolutionUm) + { + meetRequirement = false; + break; + }*/ + + CvInvoke.Erode(matXorSum, matXor, kernel, anchor, 1, BorderType.Reflect101, default); + if (CvInvoke.CountNonZero(matXor) == 0) // Image pixels exhausted and got empty image, can pack and go next + { + meetRequirement = true; + break; + } + } + + //if ((!meetRequirement || erodeCount >= _maximumErodes) && _minimumLayerHeight < (decimal) currentLayerHeight + // To many pixels, image still not blank, pack the previous group and start again from current height + if (!meetRequirement && _minimumLayerHeight < (decimal) currentLayerHeight) + { + currentLayerHeight = previousLayerHeight; + Debug.WriteLine(string.Empty); + break; + } + } + else + { + //erodeCount++; // Safe check + Debug.Write(" [Equal layer]"); + } + + layerSum++; + CvInvoke.Add(matSum, mat2, matSum); + Debug.WriteLine(string.Empty); + } + + if (layerSum > 1) report.StackedLayers += layerSum; + Debug.WriteLine($" Packing {layerSum} layers with {currentLayerHeight}mm"); + // Add the result + /*var thisPosZ = Math.Round(layers[^1].PositionZ + currentLayerHeight, 2); + if (layers.Count > 0 && thisPosZ != slicerFile[layerIndex].PositionZ) + { + Debug.WriteLine($"{layerIndex}: ({thisPosZ} != {slicerFile[layerIndex].PositionZ}) Height mismatch!!"); + }*/ + var positionZ = GetLastPositionZ(currentLayerHeight); + if ((decimal)positionZ != (decimal)SlicerFile[layerIndex-1].PositionZ) + { + Debug.WriteLine($"{layerIndex}: ({positionZ}mm != {SlicerFile[layerIndex-1].PositionZ}mm) Height mismatch!!"); + throw new InvalidOperationException($"Model height integrity has been violated at layer {layerIndex}/{layers.Count} ({positionZ}mm != {SlicerFile[layerIndex - 1].PositionZ}mm), this operation will not proceed."); + } + AddNewLayer(matSum, currentLayerHeight); + } + + for (int i = (int) LayerIndexEnd; i >= 0; i--) + { + if(matCache[i] is null) break; + matCache[i].Dispose(); + matThresholdCache[i].Dispose(); + } + + for (uint layerIndex = LayerIndexEnd+1; layerIndex < SlicerFile.LayerCount; layerIndex++) // Add left-overs + { + ReUseLayer(layerIndex); + } + + SlicerFile.SuppressRebuildProperties = true; + SlicerFile.LayerManager.Layers = layers.ToArray(); + SlicerFile.LayerManager.RebuildLayersProperties(false); + + // Set exposures times + var exposureDictionary = ExposureTableDictionary; + for (uint layerIndex = 0; layerIndex < SlicerFile.LayerCount; layerIndex++) + { + float bottomExposure = SlicerFile.BottomExposureTime; + float exposure = SlicerFile.ExposureTime; + if(exposureDictionary.TryGetValue((decimal)SlicerFile[layerIndex].LayerHeight, out var item)) + { + bottomExposure = (float) item.BottomExposure; + exposure = (float) item.Exposure; + } + + SlicerFile[layerIndex].ExposureTime = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomExposure, exposure); + } + //var layer = slicerFile.LayerManager.Layers[^1]; + + /*Debug.WriteLine(layer.ExposureTime); + Debug.WriteLine(layer.Index); + Debug.WriteLine(layer.Filename); + Debug.WriteLine(layer.IsNormalLayer); + Debug.WriteLine(layer.IsBottomLayer); + Debug.WriteLine(layer.LiftHeight); + Debug.WriteLine(layer.LiftSpeed); + Debug.WriteLine(layer.LightOffDelay); + Debug.WriteLine(layer.LightPWM); + Debug.WriteLine(layer.BoundingRectangle);*/ + //Debug.WriteLine(layer.LayerHeight); + //Debug.WriteLine(layer); + /*Debug.WriteLine(slicerFile.LayerManager); + foreach (var layer in slicerFile) + { + Debug.WriteLine(layer.Index); + }*/ + + SlicerFile.SuppressRebuildProperties = false; + + report.NewLayerCount = SlicerFile.LayerCount; + report.NewPrintTime = SlicerFile.PrintTimeOrComputed; + Tag = report; + + return true; + } + + #endregion + } +} diff --git a/UVtools.Core/Operations/OperationEditParameters.cs b/UVtools.Core/Operations/OperationEditParameters.cs index b31e017..b59de70 100644 --- a/UVtools.Core/Operations/OperationEditParameters.cs +++ b/UVtools.Core/Operations/OperationEditParameters.cs @@ -19,6 +19,9 @@ namespace UVtools.Core.Operations { #region Members private bool _perLayerOverride; + private uint _setNumberOfLayer = 1; + private uint _skipNumberOfLayer = 0; + #endregion #region Overrides @@ -92,23 +95,73 @@ namespace UVtools.Core.Operations get => _perLayerOverride; set => RaiseAndSetIfChanged(ref _perLayerOverride, value); } - #endregion - public OperationEditParameters() + /// + /// Gets or sets the number of sequential layers to set the parameters + /// + public uint SetNumberOfLayer { + get => _setNumberOfLayer; + set => RaiseAndSetIfChanged(ref _setNumberOfLayer, value); } - public OperationEditParameters(FileFormat.PrintParameterModifier[] modifiers) + /// + /// Gets or sets the number of sequential layers to skip after set a layer + /// + public uint SkipNumberOfLayer { - Modifiers = modifiers; + get => _skipNumberOfLayer; + set => RaiseAndSetIfChanged(ref _skipNumberOfLayer, value); } + #endregion + + #region Constructor + + public OperationEditParameters() { } + + public OperationEditParameters(FileFormat slicerFile) : base(slicerFile) { } + + public override void InitWithSlicerFile() + { + base.InitWithSlicerFile(); + SlicerFile.RefreshPrintParametersModifiersValues(); + Modifiers = SlicerFile.PrintParameterModifiers; + } + + #endregion + #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - slicerFile.EditPrintParameters(this); - return true; + if (PerLayerOverride) + { + uint setLayers = 0; + for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++) + { + SlicerFile[layerIndex].SetValuesFromPrintParametersModifiers(Modifiers); + if (SkipNumberOfLayer <= 0) continue; + setLayers++; + if (setLayers >= SetNumberOfLayer) + { + setLayers = 0; + layerIndex += SkipNumberOfLayer; + } + } + + foreach (var modifier in Modifiers) + { + modifier.OldValue = modifier.NewValue; + } + SlicerFile.RebuildGCode(); + } + else + { + SlicerFile.SetValuesFromPrintParametersModifiers(); + } + + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationFlip.cs b/UVtools.Core/Operations/OperationFlip.cs index 5381d1b..26fbf18 100644 --- a/UVtools.Core/Operations/OperationFlip.cs +++ b/UVtools.Core/Operations/OperationFlip.cs @@ -87,6 +87,14 @@ namespace UVtools.Core.Operations } #endregion + #region Constructor + + public OperationFlip() { } + + public OperationFlip(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Equality protected bool Equals(OperationFlip other) @@ -113,24 +121,22 @@ namespace UVtools.Core.Operations #endregion #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; Execute(mat); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; lock (progress.Mutex) { progress++; } }); - progress.Token.ThrowIfCancellationRequested(); - return true; + + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationInfill.cs b/UVtools.Core/Operations/OperationInfill.cs index b05891e..a655c71 100644 --- a/UVtools.Core/Operations/OperationInfill.cs +++ b/UVtools.Core/Operations/OperationInfill.cs @@ -97,24 +97,44 @@ namespace UVtools.Core.Operations #endregion + #region Constructor + + public OperationInfill() { } + + public OperationInfill(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Equality + private bool Equals(OperationInfill other) + { + return _infillType == other._infillType && _wallThickness == other._wallThickness && _infillThickness == other._infillThickness && _infillSpacing == other._infillSpacing && _infillBrightness == other._infillBrightness; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is OperationInfill other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine((int) _infillType, _wallThickness, _infillThickness, _infillSpacing, _infillBrightness); + } + #endregion #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); - Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; Execute(mat, layerIndex); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; lock (progress.Mutex) { @@ -122,7 +142,7 @@ namespace UVtools.Core.Operations } }); - return true; + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationLayerClone.cs b/UVtools.Core/Operations/OperationLayerClone.cs index 5ec7c28..9eca95c 100644 --- a/UVtools.Core/Operations/OperationLayerClone.cs +++ b/UVtools.Core/Operations/OperationLayerClone.cs @@ -76,11 +76,19 @@ namespace UVtools.Core.Operations #endregion + #region Constructor + + public OperationLayerClone() { } + + public OperationLayerClone(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - var oldLayers = slicerFile.LayerManager.Layers; + var oldLayers = SlicerFile.LayerManager.Layers; uint totalClones = (LayerIndexEnd - LayerIndexStart + 1) * Clones; uint newLayerCount = (uint) (oldLayers.Length + totalClones); @@ -107,11 +115,9 @@ namespace UVtools.Core.Operations newLayerIndex++; } - slicerFile.LayerManager.Layers = layers; - - progress.Token.ThrowIfCancellationRequested(); + SlicerFile.LayerManager.Layers = layers; - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs index a540004..86b2b94 100644 --- a/UVtools.Core/Operations/OperationLayerImport.cs +++ b/UVtools.Core/Operations/OperationLayerImport.cs @@ -39,12 +39,14 @@ namespace UVtools.Core.Operations } #endregion + #region Members private ImportTypes _importType = ImportTypes.Stack; private uint _startLayerIndex; private bool _extendBeyondLayerCount = true; private bool _discardUnmodifiedLayers; private ushort _stackMargin = 50; private ObservableCollection _files = new(); + #endregion #region Overrides @@ -115,9 +117,6 @@ namespace UVtools.Core.Operations #region Properties - [XmlIgnore] - public Size FileResolution { get; } - public ImportTypes ImportType { get => _importType; @@ -172,14 +171,11 @@ namespace UVtools.Core.Operations #region Constructor - public OperationLayerImport() - { - } + public OperationLayerImport() { } + + public OperationLayerImport(FileFormat slicerFile) : base(slicerFile) { } + - public OperationLayerImport(Size fileResolution) - { - FileResolution = fileResolution; - } #endregion #region Methods @@ -224,10 +220,10 @@ namespace UVtools.Core.Operations return result; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - slicerFile.SuppressRebuildProperties = true; + progress.ItemCount = 0; + SlicerFile.SuppressRebuildProperties = true; List fileFormats = new(); List> keyImage = new(); @@ -289,7 +285,7 @@ namespace UVtools.Core.Operations if (_importType == ImportTypes.Stack) { - new OperationMove(slicerFile, Enumerations.Anchor.TopLeft){LayerIndexEnd = slicerFile.LastLayerIndex}.Execute(slicerFile, progress); + new OperationMove(SlicerFile, Enumerations.Anchor.TopLeft).Execute(progress); } foreach (var fileFormat in fileFormats) @@ -299,7 +295,7 @@ namespace UVtools.Core.Operations fileFormat.Decode(fileFormat.FileFullPath, progress); } - var boundingRectangle = slicerFile.LayerManager.GetBoundingRectangle(progress); + var boundingRectangle = SlicerFile.LayerManager.GetBoundingRectangle(progress); var fileFormatBoundingRectangle = fileFormat.LayerManager.GetBoundingRectangle(progress); var roiRectangle = Rectangle.Empty; @@ -307,26 +303,26 @@ namespace UVtools.Core.Operations switch (_importType) { case ImportTypes.Insert: - if (slicerFile.Resolution != fileFormat.Resolution && - (slicerFile.Resolution.Width < fileFormat.LayerManager.BoundingRectangle.Width || - slicerFile.Resolution.Height < fileFormat.LayerManager.BoundingRectangle.Height)) continue; - slicerFile.LayerManager.Reallocate(_startLayerIndex, fileFormat.LayerCount); + if (SlicerFile.Resolution != fileFormat.Resolution && + (SlicerFile.Resolution.Width < fileFormat.LayerManager.BoundingRectangle.Width || + SlicerFile.Resolution.Height < fileFormat.LayerManager.BoundingRectangle.Height)) continue; + SlicerFile.LayerManager.Reallocate(_startLayerIndex, fileFormat.LayerCount); break; case ImportTypes.Replace: case ImportTypes.Stack: - if (slicerFile.Resolution != fileFormat.Resolution && - (slicerFile.Resolution.Width < fileFormat.LayerManager.BoundingRectangle.Width || - slicerFile.Resolution.Height < fileFormat.LayerManager.BoundingRectangle.Height)) continue; + if (SlicerFile.Resolution != fileFormat.Resolution && + (SlicerFile.Resolution.Width < fileFormat.LayerManager.BoundingRectangle.Width || + SlicerFile.Resolution.Height < fileFormat.LayerManager.BoundingRectangle.Height)) continue; - if(fileFormatBoundingRectangle.Width >= slicerFile.ResolutionX || fileFormatBoundingRectangle.Height >= slicerFile.ResolutionY) + if(fileFormatBoundingRectangle.Width >= SlicerFile.ResolutionX || fileFormatBoundingRectangle.Height >= SlicerFile.ResolutionY) continue; int x = 0; int y = 0; if (boundingRectangle.Right + _stackMargin + fileFormatBoundingRectangle.Width < - slicerFile.ResolutionX) + SlicerFile.ResolutionX) { x = boundingRectangle.Right + _stackMargin; } @@ -335,29 +331,29 @@ namespace UVtools.Core.Operations y = boundingRectangle.Bottom + _stackMargin; } - if (y >= slicerFile.ResolutionY) + if (y >= SlicerFile.ResolutionY) continue; roiRectangle = new Rectangle(x, y, fileFormatBoundingRectangle.Width, fileFormatBoundingRectangle.Height); if (_extendBeyondLayerCount) { - int layerCountDifference = (int) (_startLayerIndex + fileFormat.LayerCount - slicerFile.LayerCount); + int layerCountDifference = (int) (_startLayerIndex + fileFormat.LayerCount - SlicerFile.LayerCount); if (layerCountDifference > 0) { - slicerFile.LayerManager.ReallocateEnd((uint) layerCountDifference, _importType == ImportTypes.Stack); + SlicerFile.LayerManager.ReallocateEnd((uint) layerCountDifference, _importType == ImportTypes.Stack); } } break; case ImportTypes.Merge: - if (slicerFile.Resolution != fileFormat.Resolution) continue; + if (SlicerFile.Resolution != fileFormat.Resolution) continue; if (_extendBeyondLayerCount) { - int layerCountDifference = (int)(_startLayerIndex + fileFormat.LayerCount - slicerFile.LayerCount); + int layerCountDifference = (int)(_startLayerIndex + fileFormat.LayerCount - SlicerFile.LayerCount); if (layerCountDifference > 0) { - slicerFile.LayerManager.ReallocateEnd((uint)layerCountDifference, true); + SlicerFile.LayerManager.ReallocateEnd((uint)layerCountDifference, true); } } break; @@ -365,7 +361,7 @@ namespace UVtools.Core.Operations case ImportTypes.BitwiseAnd: case ImportTypes.BitwiseOr: case ImportTypes.BitwiseXOr: - if (slicerFile.Resolution != fileFormat.Resolution) continue; + if (SlicerFile.Resolution != fileFormat.Resolution) continue; break; } @@ -381,88 +377,88 @@ namespace UVtools.Core.Operations { case ImportTypes.Insert: { - if (layerIndex >= slicerFile.LayerCount) return; - if (slicerFile.Resolution == fileFormat.Resolution) + if (layerIndex >= SlicerFile.LayerCount) return; + if (SlicerFile.Resolution == fileFormat.Resolution) { - slicerFile[layerIndex] = fileFormat[i]; + SlicerFile[layerIndex] = fileFormat[i]; break; } using var layer = fileFormat[i].LayerMat; - using var layerRoi = layer.RoiFromCenter(slicerFile.Resolution); - slicerFile[layerIndex] = new Layer(layerIndex, layerRoi, slicerFile); + using var layerRoi = layer.RoiFromCenter(SlicerFile.Resolution); + SlicerFile[layerIndex] = new Layer(layerIndex, layerRoi, SlicerFile); break; } case ImportTypes.Replace: { - if (layerIndex >= slicerFile.LayerCount) return; - if (slicerFile.Resolution == fileFormat.Resolution) + if (layerIndex >= SlicerFile.LayerCount) return; + if (SlicerFile.Resolution == fileFormat.Resolution) { - slicerFile[layerIndex] = fileFormat[i]; + SlicerFile[layerIndex] = fileFormat[i]; break; } using var layer = fileFormat[i].LayerMat; - using var layerRoi = layer.RoiFromCenter(slicerFile.Resolution); - slicerFile[layerIndex] = new Layer(layerIndex, layerRoi, slicerFile); + using var layerRoi = layer.RoiFromCenter(SlicerFile.Resolution); + SlicerFile[layerIndex] = new Layer(layerIndex, layerRoi, SlicerFile); break; } case ImportTypes.Stack: { - if (layerIndex >= slicerFile.LayerCount) return; - using var mat = slicerFile[layerIndex].LayerMat; + if (layerIndex >= SlicerFile.LayerCount) return; + using var mat = SlicerFile[layerIndex].LayerMat; using var importMat = fileFormat[i].LayerMat; var matRoi = new Mat(mat, roiRectangle); var importMatRoi = new Mat(importMat, fileFormatBoundingRectangle); importMatRoi.CopyTo(matRoi); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; break; } case ImportTypes.Merge: { - if (layerIndex >= slicerFile.LayerCount) return; - using var originalMat = slicerFile[layerIndex].LayerMat; + if (layerIndex >= SlicerFile.LayerCount) return; + using var originalMat = SlicerFile[layerIndex].LayerMat; using var newMat = fileFormat[i].LayerMat; CvInvoke.Add(originalMat, newMat, newMat); - slicerFile[layerIndex].LayerMat = newMat; + SlicerFile[layerIndex].LayerMat = newMat; break; } case ImportTypes.Subtract: { - if (layerIndex >= slicerFile.LayerCount) return; - using var originalMat = slicerFile[layerIndex].LayerMat; + if (layerIndex >= SlicerFile.LayerCount) return; + using var originalMat = SlicerFile[layerIndex].LayerMat; using var newMat = fileFormat[i].LayerMat; CvInvoke.Subtract(originalMat, newMat, newMat); - slicerFile[layerIndex].LayerMat = newMat; + SlicerFile[layerIndex].LayerMat = newMat; break; } case ImportTypes.BitwiseAnd: { - if (layerIndex >= slicerFile.LayerCount) return; - using var originalMat = slicerFile[layerIndex].LayerMat; + if (layerIndex >= SlicerFile.LayerCount) return; + using var originalMat = SlicerFile[layerIndex].LayerMat; using var newMat = fileFormat[i].LayerMat; CvInvoke.BitwiseAnd(originalMat, newMat, newMat); - slicerFile[layerIndex].LayerMat = newMat; + SlicerFile[layerIndex].LayerMat = newMat; break; } case ImportTypes.BitwiseOr: { - if (layerIndex >= slicerFile.LayerCount) return; - using var originalMat = slicerFile[layerIndex].LayerMat; + if (layerIndex >= SlicerFile.LayerCount) return; + using var originalMat = SlicerFile[layerIndex].LayerMat; using var newMat = fileFormat[i].LayerMat; CvInvoke.BitwiseOr(originalMat, newMat, newMat); - slicerFile[layerIndex].LayerMat = newMat; + SlicerFile[layerIndex].LayerMat = newMat; break; } case ImportTypes.BitwiseXOr: { - if (layerIndex >= slicerFile.LayerCount) return; - using var originalMat = slicerFile[layerIndex].LayerMat; + if (layerIndex >= SlicerFile.LayerCount) return; + using var originalMat = SlicerFile[layerIndex].LayerMat; using var newMat = fileFormat[i].LayerMat; CvInvoke.BitwiseXor(originalMat, newMat, newMat); - slicerFile[layerIndex].LayerMat = newMat; + SlicerFile[layerIndex].LayerMat = newMat; break; } default: @@ -483,20 +479,20 @@ namespace UVtools.Core.Operations if (_importType == ImportTypes.Stack) { - new OperationMove(slicerFile) { LayerIndexEnd = slicerFile.LastLayerIndex }.Execute(slicerFile, progress); + new OperationMove(SlicerFile).Execute(progress); } if (lastProcessedLayerIndex <= -1) return false; - if (lastProcessedLayerIndex + 1 < slicerFile.LayerCount && _discardUnmodifiedLayers) + if (lastProcessedLayerIndex + 1 < SlicerFile.LayerCount && _discardUnmodifiedLayers) { - slicerFile.LayerManager.ReallocateRange(0, (uint) lastProcessedLayerIndex); + SlicerFile.LayerManager.ReallocateRange(0, (uint) lastProcessedLayerIndex); } - slicerFile.LayerManager.RebuildLayersProperties(); - slicerFile.SuppressRebuildProperties = false; + SlicerFile.LayerManager.RebuildLayersProperties(); + SlicerFile.SuppressRebuildProperties = false; - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs index bcd93d5..a68e367 100644 --- a/UVtools.Core/Operations/OperationLayerReHeight.cs +++ b/UVtools.Core/Operations/OperationLayerReHeight.cs @@ -99,6 +99,14 @@ namespace UVtools.Core.Operations } #endregion + #region Constructor + + public OperationLayerReHeight() { } + + public OperationLayerReHeight(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Subclasses public class OperationLayerReHeightItem { @@ -124,12 +132,11 @@ namespace UVtools.Core.Operations #endregion #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, Item.LayerCount); + progress.ItemCount = Item.LayerCount; - var oldLayers = slicerFile.LayerManager.Layers; + var oldLayers = SlicerFile.LayerManager.Layers; var layers = new Layer[Item.LayerCount]; @@ -171,10 +178,10 @@ namespace UVtools.Core.Operations } } - slicerFile.LayerManager.Layers = layers; - slicerFile.LayerHeight = (float)Item.LayerHeight; + SlicerFile.LayerManager.Layers = layers; + SlicerFile.LayerHeight = (float)Item.LayerHeight; - return true; + return !progress.Token.IsCancellationRequested; } #endregion } diff --git a/UVtools.Core/Operations/OperationLayerRemove.cs b/UVtools.Core/Operations/OperationLayerRemove.cs index 5fa1907..117712a 100644 --- a/UVtools.Core/Operations/OperationLayerRemove.cs +++ b/UVtools.Core/Operations/OperationLayerRemove.cs @@ -45,20 +45,28 @@ namespace UVtools.Core.Operations #region Properties + #endregion + + #region Constructor + + public OperationLayerRemove() { } + + public OperationLayerRemove(FileFormat slicerFile) : base(slicerFile) { } + #endregion #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(false); + progress.CanCancel = false; var layersRemove = new List(); for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++) { layersRemove.Add(layerIndex); } - return RemoveLayers(slicerFile, layersRemove, progress); + return RemoveLayers(SlicerFile, layersRemove, progress); } public static bool RemoveLayers(FileFormat slicerFile, List layersRemove, OperationProgress progress = null) @@ -70,7 +78,7 @@ namespace UVtools.Core.Operations progress.Reset("removed layers", (uint)layersRemove.Count); var oldLayers = slicerFile.LayerManager.Layers; - float layerHeight = slicerFile.LayerHeight; + var layerHeight = slicerFile.LayerHeight; var layers = new Layer[oldLayers.Length - layersRemove.Count]; @@ -92,7 +100,7 @@ namespace UVtools.Core.Operations } else { - posZ = (float)Math.Round(layers[newLayerIndex - 1].PositionZ + layerHeight, 2); + posZ = layers[newLayerIndex - 1].PositionZ + layerHeight; } } diff --git a/UVtools.Core/Operations/OperationMask.cs b/UVtools.Core/Operations/OperationMask.cs index 972157f..3309e2f 100644 --- a/UVtools.Core/Operations/OperationMask.cs +++ b/UVtools.Core/Operations/OperationMask.cs @@ -57,6 +57,14 @@ namespace UVtools.Core.Operations public bool HaveMask => !(Mask is null); #endregion + #region Constructor + + public OperationMask() { } + + public OperationMask(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Methods public void InvertMask() @@ -65,25 +73,21 @@ namespace UVtools.Core.Operations CvInvoke.BitwiseNot(Mask, Mask); } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); - Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; Execute(mat); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; lock (progress.Mutex) { progress++; } }); - progress.Token.ThrowIfCancellationRequested(); - return true; + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationMorph.cs b/UVtools.Core/Operations/OperationMorph.cs index 4ff99ad..4c0ef1a 100644 --- a/UVtools.Core/Operations/OperationMorph.cs +++ b/UVtools.Core/Operations/OperationMorph.cs @@ -109,6 +109,14 @@ namespace UVtools.Core.Operations #endregion + #region Constructor + + public OperationMorph() { } + + public OperationMorph(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Equality private bool Equals(OperationMorph other) @@ -137,11 +145,8 @@ namespace UVtools.Core.Operations #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); - var isFade = Chamfer; LayerManager.MutateGetVarsIterationChamfer( LayerIndexStart, @@ -160,17 +165,17 @@ namespace UVtools.Core.Operations if (progress.Token.IsCancellationRequested) return; int iterations = LayerManager.MutateGetIterationVar(isFade, (int)IterationsStart, (int)IterationsEnd, iterationSteps, maxIteration, LayerIndexStart, (uint)layerIndex); - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; Execute(mat, iterations); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; lock (progress.Mutex) { progress++; } }); - progress.Token.ThrowIfCancellationRequested(); - return true; + + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationMove.cs b/UVtools.Core/Operations/OperationMove.cs index 12bcae0..30a3dee 100644 --- a/UVtools.Core/Operations/OperationMove.cs +++ b/UVtools.Core/Operations/OperationMove.cs @@ -170,6 +170,31 @@ namespace UVtools.Core.Operations #endregion + #region Constructor + + public OperationMove() { } + + public OperationMove(FileFormat slicerFile, Enumerations.Anchor anchor = Enumerations.Anchor.MiddleCenter) : base(slicerFile) + { + ROI = slicerFile.LayerManager.BoundingRectangle; + _imageWidth = slicerFile.ResolutionX; + _imageHeight = slicerFile.ResolutionY; + _anchor = anchor; + } + + public OperationMove(FileFormat slicerFile, Rectangle srcRoi, Enumerations.Anchor anchor = Enumerations.Anchor.MiddleCenter) : this(slicerFile, anchor) + { + if(!srcRoi.IsEmpty) ROI = srcRoi; + } + + /*public OperationMove(FileFormat slicerFile, Rectangle srcRoi, Mat mat, Enumerations.Anchor anchor = Enumerations.Anchor.MiddleCenter) : this(slicerFile, srcRoi, anchor) + { + ImageWidth = (uint) mat.Width; + ImageHeight = (uint) mat.Height; + }*/ + + #endregion + #region Methods public void CalculateDstRoi() { @@ -214,42 +239,15 @@ namespace UVtools.Core.Operations _dstRoi.Y += MarginTop; _dstRoi.Y -= MarginBottom; - IsWithinBoundary = !(DstRoi.IsEmpty || DstRoi.X < 0 || DstRoi.Y < 0 || - DstRoi.Width == 0 || DstRoi.Right > ImageWidth || - DstRoi.Height == 0 || DstRoi.Bottom > ImageHeight); + IsWithinBoundary = !(_dstRoi.IsEmpty || _dstRoi.X < 0 || _dstRoi.Y < 0 || + _dstRoi.Width == 0 || _dstRoi.Right > ImageWidth || + _dstRoi.Height == 0 || _dstRoi.Bottom > ImageHeight); RaisePropertyChanged(nameof(DstRoi)); RaisePropertyChanged(nameof(LocationXStr)); RaisePropertyChanged(nameof(LocationYStr)); } - public OperationMove() - { - } - - public OperationMove(FileFormat slicerFile, Enumerations.Anchor anchor = Enumerations.Anchor.MiddleCenter) - { - ROI = slicerFile.LayerManager.BoundingRectangle; - ImageWidth = slicerFile.ResolutionX; - ImageHeight = slicerFile.ResolutionY; - Anchor = anchor; - } - - public OperationMove(Rectangle srcRoi, uint imageWidth, uint imageHeight, Enumerations.Anchor anchor = Enumerations.Anchor.MiddleCenter) - { - ROI = srcRoi; - ImageWidth = imageWidth; - ImageHeight = imageHeight; - Anchor = anchor; - } - - public OperationMove(Rectangle srcRoi, Size resolution, Enumerations.Anchor anchor = Enumerations.Anchor.MiddleCenter) - { - ROI = srcRoi; - ImageWidth = (uint)resolution.Width; - ImageHeight = (uint)resolution.Height; - Anchor = anchor; - } public void Reset() { @@ -271,21 +269,19 @@ namespace UVtools.Core.Operations return IsWithinBoundary; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); - - if (ROI == Rectangle.Empty) ROI = slicerFile.LayerManager.GetBoundingRectangle(progress); + if (ROI.IsEmpty) ROI = SlicerFile.LayerManager.GetBoundingRectangle(progress); + CalculateDstRoi(); Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using (var mat = slicerFile[layerIndex].LayerMat) + using (var mat = SlicerFile[layerIndex].LayerMat) { Execute(mat); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; } lock (progress.Mutex) @@ -294,18 +290,15 @@ namespace UVtools.Core.Operations } }); - slicerFile.LayerManager.BoundingRectangle = Rectangle.Empty; + SlicerFile.LayerManager.BoundingRectangle = Rectangle.Empty; - progress.Token.ThrowIfCancellationRequested(); - - return true; + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) { - - if (ImageWidth == 0) ImageWidth = (uint) mat.Width; - if (ImageHeight == 0) ImageHeight = (uint) mat.Height; + if (_imageWidth == 0) _imageWidth = (uint) mat.Width; + if (_imageHeight == 0) _imageHeight = (uint) mat.Height; using var srcRoi = new Mat(mat, ROI); using var dstRoi = new Mat(mat, DstRoi); diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs index 97d75f7..3cd36bf 100644 --- a/UVtools.Core/Operations/OperationPattern.cs +++ b/UVtools.Core/Operations/OperationPattern.cs @@ -186,19 +186,21 @@ namespace UVtools.Core.Operations public Size GetPatternVolume => new(Cols * ROI.Width + (Cols - 1) * ColSpacing, Rows * ROI.Height + (Rows - 1) * RowSpacing); #endregion - public OperationPattern() - { - } + #region Constructor - public OperationPattern(Rectangle srcRoi, Size resolution) + public OperationPattern() { } + + public OperationPattern(FileFormat slicerFile, Rectangle srcRoi = default) : base(slicerFile) { - ImageWidth = (uint) resolution.Width; - ImageHeight = (uint) resolution.Height; + ImageWidth = slicerFile.ResolutionX; + ImageHeight = slicerFile.ResolutionY; - SetRoi(srcRoi); + SetRoi(srcRoi.IsEmpty ? slicerFile.BoundingRectangle : srcRoi); Fill(); } + #endregion + #region Methods public void SetAnchor(byte value) { @@ -272,16 +274,13 @@ namespace UVtools.Core.Operations return result; } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); - Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; using var layerRoi = new Mat(mat, ROI); using var dstLayer = mat.CloneBlank(); for (ushort col = 0; col < Cols; col++) @@ -292,7 +291,7 @@ namespace UVtools.Core.Operations layerRoi.CopyTo(dstRoi); } //Execute(mat); - slicerFile[layerIndex].LayerMat = dstLayer; + SlicerFile[layerIndex].LayerMat = dstLayer; lock (progress.Mutex) { @@ -300,19 +299,19 @@ namespace UVtools.Core.Operations } }); - slicerFile.LayerManager.BoundingRectangle = Rectangle.Empty; + SlicerFile.LayerManager.BoundingRectangle = Rectangle.Empty; progress.Token.ThrowIfCancellationRequested(); if (Anchor == Enumerations.Anchor.None) return true; - var operationMove = new OperationMove(slicerFile.LayerManager.BoundingRectangle, ImageWidth, ImageHeight, Anchor) + var operationMove = new OperationMove(SlicerFile, Anchor) { LayerIndexStart = LayerIndexStart, LayerIndexEnd = LayerIndexEnd }; - operationMove.Execute(slicerFile, progress); + operationMove.Execute(progress); - return true; + return !progress.Token.IsCancellationRequested; } /*public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationPixelDimming.cs b/UVtools.Core/Operations/OperationPixelDimming.cs index 25c16e8..4e40285 100644 --- a/UVtools.Core/Operations/OperationPixelDimming.cs +++ b/UVtools.Core/Operations/OperationPixelDimming.cs @@ -138,6 +138,14 @@ namespace UVtools.Core.Operations } #endregion + #region Constructor + + public OperationPixelDimming() { } + + public OperationPixelDimming(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Properties public uint WallThicknessStart @@ -534,11 +542,8 @@ namespace UVtools.Core.Operations } } - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); - if (Pattern is null) { Pattern = new Matrix(2, 2) @@ -560,7 +565,7 @@ namespace UVtools.Core.Operations AlternatePattern ??= Pattern; - using var blankMat = EmguExtensions.InitMat(slicerFile.Resolution); + using var blankMat = EmguExtensions.InitMat(SlicerFile.Resolution); using var matPattern = blankMat.CloneBlank(); using var matAlternatePattern = blankMat.CloneBlank(); var target = GetRoiOrDefault(blankMat); @@ -573,9 +578,9 @@ namespace UVtools.Core.Operations Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; Execute(mat, layerIndex, patternMask, alternatePatternMask); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; lock (progress.Mutex) { @@ -583,8 +588,7 @@ namespace UVtools.Core.Operations } }); - progress.Token.ThrowIfCancellationRequested(); - return true; + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationProgress.cs b/UVtools.Core/Operations/OperationProgress.cs index 077c4ba..4eea251 100644 --- a/UVtools.Core/Operations/OperationProgress.cs +++ b/UVtools.Core/Operations/OperationProgress.cs @@ -58,7 +58,7 @@ namespace UVtools.Core.Operations _canCancel = canCancel; } - public Stopwatch StopWatch { get; } = new Stopwatch(); + public Stopwatch StopWatch { get; } = new (); /// /// Gets or sets if operation can be cancelled @@ -82,7 +82,8 @@ namespace UVtools.Core.Operations set => RaiseAndSetIfChanged(ref _title, value); } - public string ElapsedTimeStr => $"{StopWatch.Elapsed.Minutes}m {StopWatch.Elapsed.Seconds}s {StopWatch.Elapsed.Milliseconds}ms"; + public string ElapsedTimeStr => $"{StopWatch.Elapsed.Minutes}m {StopWatch.Elapsed.Seconds}s"; + //{StopWatch.Elapsed.Milliseconds} ms /// /// Gets or sets the item name for the operation @@ -123,21 +124,26 @@ namespace UVtools.Core.Operations } } + /// + /// Gets or sets an tag + /// + public object Tag { get; set; } + /// /// Gets the remaining items to be processed /// - public uint RemainingItems => ItemCount - ProcessedItems; + public uint RemainingItems => _itemCount - _processedItems; public int ProgressStep => (int)ProgressPercent; public string Description => ToString(); - public bool IsIndeterminate => ItemCount == 0; + public bool IsIndeterminate => _itemCount == 0; /// /// Gets the progress from 0 to 100% /// - public double ProgressPercent => ItemCount == 0 ? 0 : Math.Round(ProcessedItems * 100.0 / ItemCount, 2).Clamp(0, 100); + public double ProgressPercent => _itemCount == 0 ? 0 : Math.Round(_processedItems * 100.0 / _itemCount, 2).Clamp(0, 100); public static OperationProgress operator +(OperationProgress progress, uint value) { @@ -167,9 +173,9 @@ namespace UVtools.Core.Operations public override string ToString() { - return ItemCount == 0 ? - $"{ProcessedItems}/? {ItemName}" : - $"{ProcessedItems}/{ItemCount} {ItemName} | {ProgressPercent:0.00}%"; + return _itemCount == 0 ? +$"{_processedItems}/? {_itemName}" : +$"{_processedItems.ToString().PadLeft(_itemCount.ToString().Length, '0')}/{_itemCount} {_itemName} | {ProgressPercent:0.00}%"; } public void TriggerRefresh() diff --git a/UVtools.Core/Operations/OperationRaftRelief.cs b/UVtools.Core/Operations/OperationRaftRelief.cs index c76b55a..d5634c6 100644 --- a/UVtools.Core/Operations/OperationRaftRelief.cs +++ b/UVtools.Core/Operations/OperationRaftRelief.cs @@ -63,6 +63,14 @@ namespace UVtools.Core.Operations } #endregion + #region Constructor + + public OperationRaftRelief() { } + + public OperationRaftRelief(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Properties public static Array RaftReliefItems => Enum.GetValues(typeof(RaftReliefTypes)); @@ -149,11 +157,10 @@ namespace UVtools.Core.Operations #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { + progress.ItemCount = 0; const uint minLength = 5; - progress ??= new OperationProgress(); - //progress.Reset(operation.ProgressAction); Mat supportsMat = null; var anchor = new Point(-1, -1); @@ -161,11 +168,11 @@ namespace UVtools.Core.Operations uint firstSupportLayerIndex = 0; - for (; firstSupportLayerIndex < slicerFile.LayerCount; firstSupportLayerIndex++) + for (; firstSupportLayerIndex < SlicerFile.LayerCount; firstSupportLayerIndex++) { - progress.Reset("Tracing raft", slicerFile.LayerCount, firstSupportLayerIndex); + progress.Reset("Tracing raft", SlicerFile.LayerCount, firstSupportLayerIndex); if (progress.Token.IsCancellationRequested) return false; - supportsMat = GetRoiOrDefault(slicerFile[firstSupportLayerIndex].LayerMat); + supportsMat = GetRoiOrDefault(SlicerFile[firstSupportLayerIndex].LayerMat); var circles = CvInvoke.HoughCircles(supportsMat, HoughModes.Gradient, 1, 20, 100, 30, 5, 200); if (circles.Length >= minLength) break; @@ -187,7 +194,7 @@ namespace UVtools.Core.Operations switch (ReliefType) { - case OperationRaftRelief.RaftReliefTypes.Relief: + case RaftReliefTypes.Relief: patternMat = EmguExtensions.InitMat(supportsMat.Size); int shapeSize = HoleDiameter + HoleSpacing; using (var shape = EmguExtensions.InitMat(new Size(shapeSize, shapeSize))) @@ -208,7 +215,7 @@ namespace UVtools.Core.Operations } break; - case OperationRaftRelief.RaftReliefTypes.Dimming: + case RaftReliefTypes.Dimming: patternMat = EmguExtensions.InitMat(supportsMat.Size, color); break; } @@ -216,7 +223,8 @@ namespace UVtools.Core.Operations progress.Reset(ProgressAction, firstSupportLayerIndex); Parallel.For(IgnoreFirstLayers, firstSupportLayerIndex, layerIndex => { - using (Mat dst = slicerFile[layerIndex].LayerMat) + if (progress.Token.IsCancellationRequested) return; + using (Mat dst = SlicerFile[layerIndex].LayerMat) { var target = GetRoiOrDefault(dst); @@ -242,7 +250,7 @@ namespace UVtools.Core.Operations } - slicerFile[layerIndex].LayerMat = dst; + SlicerFile[layerIndex].LayerMat = dst; } lock (progress.Mutex) @@ -255,7 +263,7 @@ namespace UVtools.Core.Operations supportsMat.Dispose(); patternMat?.Dispose(); - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationRedrawModel.cs b/UVtools.Core/Operations/OperationRedrawModel.cs index 7f156b1..7cb6da3 100644 --- a/UVtools.Core/Operations/OperationRedrawModel.cs +++ b/UVtools.Core/Operations/OperationRedrawModel.cs @@ -81,6 +81,14 @@ namespace UVtools.Core.Operations } #endregion + #region Constructor + + public OperationRedrawModel() { } + + public OperationRedrawModel(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Properties [XmlIgnore] @@ -151,21 +159,20 @@ namespace UVtools.Core.Operations public FileFormat IsFileValid(bool returnNewInstance = false) => FileFormat.FindByExtension(_filePath, true, returnNewInstance); - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - var otherFile = IsFileValid(true); otherFile.Decode(_filePath, progress); progress.Reset(ProgressAction, otherFile.LayerCount); - int startLayerIndex = (int)(slicerFile.LayerCount - otherFile.LayerCount); + int startLayerIndex = (int)(SlicerFile.LayerCount - otherFile.LayerCount); if (startLayerIndex < 0) return false; Parallel.For(0, otherFile.LayerCount, layerIndex => { + if (progress.Token.IsCancellationRequested) return; var fullMatLayerIndex = startLayerIndex + layerIndex; - using var fullMat = slicerFile[fullMatLayerIndex].LayerMat; + using var fullMat = SlicerFile[fullMatLayerIndex].LayerMat; using var bodyMat = otherFile[layerIndex].LayerMat; using var fullMatRoi = GetRoiOrDefault(fullMat); using var bodyMatRoi = GetRoiOrDefault(bodyMat); @@ -232,7 +239,7 @@ namespace UVtools.Core.Operations if (modified) { - slicerFile[fullMatLayerIndex].LayerMat = fullMat; + SlicerFile[fullMatLayerIndex].LayerMat = fullMat; } lock (progress.Mutex) @@ -241,7 +248,7 @@ namespace UVtools.Core.Operations } }); - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs index 9b48ab5..68c84b0 100644 --- a/UVtools.Core/Operations/OperationRepairLayers.cs +++ b/UVtools.Core/Operations/OperationRepairLayers.cs @@ -16,7 +16,6 @@ using System.Threading.Tasks; using System.Xml.Serialization; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; using Emgu.CV.Util; using UVtools.Core.Extensions; using UVtools.Core.FileFormats; @@ -64,6 +63,14 @@ namespace UVtools.Core.Operations } #endregion + #region Constructor + + public OperationRepairLayers() { } + + public OperationRepairLayers(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Properties public bool RepairIslands { @@ -116,10 +123,8 @@ namespace UVtools.Core.Operations #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - // Remove islands if (!ReferenceEquals(Issues, null) && !ReferenceEquals(IslandDetectionConfig, null) @@ -127,7 +132,7 @@ namespace UVtools.Core.Operations && RemoveIslandsBelowEqualPixelCount > 0 && RemoveIslandsRecursiveIterations != 1) { - progress.Reset("Removed recursive islands", 0); + progress.Reset("Removed recursive islands"); ushort limit = RemoveIslandsRecursiveIterations == 0 ? ushort.MaxValue : RemoveIslandsRecursiveIterations; @@ -152,7 +157,7 @@ namespace UVtools.Core.Operations .Select(grp => grp.First()) .ToList();*/ islandConfig.WhiteListLayers = islandsToRecompute.ToList(); - recursiveIssues = slicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapsConfig, touchingBoundsConfig, emptyLayersConfig); + recursiveIssues = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapsConfig, touchingBoundsConfig, emptyLayersConfig); //Debug.WriteLine(i); } @@ -167,7 +172,7 @@ namespace UVtools.Core.Operations Parallel.ForEach(issuesGroup, group => { if (progress.Token.IsCancellationRequested) return; - Layer layer = slicerFile[group.Key]; + Layer layer = SlicerFile[group.Key]; Mat image = layer.LayerMat; Span bytes = image.GetPixelSpan(); foreach (var issue in group) @@ -184,7 +189,7 @@ namespace UVtools.Core.Operations } var nextLayerIndex = group.Key + 1; - if (nextLayerIndex < slicerFile.LayerCount) + if (nextLayerIndex < SlicerFile.LayerCount) islandsToRecompute.Add(nextLayerIndex); layer.LayerMat = image; @@ -200,7 +205,7 @@ namespace UVtools.Core.Operations Parallel.For(LayerIndexStart, LayerIndexEnd, layerIndex => { if (progress.Token.IsCancellationRequested) return; - Layer layer = slicerFile[layerIndex]; + Layer layer = SlicerFile[layerIndex]; Mat image = null; void initImage() @@ -294,7 +299,7 @@ namespace UVtools.Core.Operations List removeLayers = new List(); for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++) { - if (slicerFile[layerIndex].NonZeroPixelCount == 0) + if (SlicerFile[layerIndex].NonZeroPixelCount == 0) { removeLayers.Add(layerIndex); } @@ -302,13 +307,11 @@ namespace UVtools.Core.Operations if (removeLayers.Count > 0) { - OperationLayerRemove.RemoveLayers(slicerFile, removeLayers, progress); + OperationLayerRemove.RemoveLayers(SlicerFile, removeLayers, progress); } } - progress.Token.ThrowIfCancellationRequested(); - - return true; + return !progress.Token.IsCancellationRequested; } #endregion diff --git a/UVtools.Core/Operations/OperationResize.cs b/UVtools.Core/Operations/OperationResize.cs index 0f909e5..1917696 100644 --- a/UVtools.Core/Operations/OperationResize.cs +++ b/UVtools.Core/Operations/OperationResize.cs @@ -103,9 +103,13 @@ namespace UVtools.Core.Operations } #endregion - public OperationResize() - { - } + #region Constructor + + public OperationResize() { } + + public OperationResize(FileFormat slicerFile) : base(slicerFile) { } + + #endregion public override string ToString() { @@ -145,11 +149,9 @@ namespace UVtools.Core.Operations #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { if (X == 1m && Y == 1m) return false; - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); decimal xSteps = Math.Abs(X - 1) / (LayerIndexEnd - LayerIndexStart); decimal ySteps = Math.Abs(Y - 1) / (LayerIndexEnd - LayerIndexStart); @@ -195,12 +197,12 @@ namespace UVtools.Core.Operations if (newX == 1.0m && newY == 1.0m) return; - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; Execute(mat, newX / 100m, newY / 100m); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; }); - progress.Token.ThrowIfCancellationRequested(); - return true; + + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationRotate.cs b/UVtools.Core/Operations/OperationRotate.cs index f9a864e..c428b6c 100644 --- a/UVtools.Core/Operations/OperationRotate.cs +++ b/UVtools.Core/Operations/OperationRotate.cs @@ -42,6 +42,14 @@ namespace UVtools.Core.Operations } #endregion + #region Constructor + + public OperationRotate() { } + + public OperationRotate(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Properties public decimal AngleDegrees { @@ -74,24 +82,21 @@ namespace UVtools.Core.Operations #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; Execute(mat); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; lock (progress.Mutex) { progress++; } }); - progress.Token.ThrowIfCancellationRequested(); - return true; + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs index ca6577f..e7e8e4d 100644 --- a/UVtools.Core/Operations/OperationSolidify.cs +++ b/UVtools.Core/Operations/OperationSolidify.cs @@ -20,13 +20,18 @@ namespace UVtools.Core.Operations [Serializable] public sealed class OperationSolidify : Operation { + #region Enums public enum AreaCheckTypes { More, Less } + #endregion + + #region Members private uint _minimumArea = 1; private AreaCheckTypes _areaCheckType = AreaCheckTypes.More; + #endregion #region Overrides @@ -70,25 +75,31 @@ namespace UVtools.Core.Operations #endregion + #region Constructor + + public OperationSolidify() { } + + public OperationSolidify(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using var mat = slicerFile[layerIndex].LayerMat; + using var mat = SlicerFile[layerIndex].LayerMat; Execute(mat); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; lock (progress.Mutex) { progress++; } }); - progress.Token.ThrowIfCancellationRequested(); - return true; + + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/Operations/OperationThreshold.cs b/UVtools.Core/Operations/OperationThreshold.cs index f3776a4..b85d6b4 100644 --- a/UVtools.Core/Operations/OperationThreshold.cs +++ b/UVtools.Core/Operations/OperationThreshold.cs @@ -47,6 +47,14 @@ namespace UVtools.Core.Operations } #endregion + #region Constructor + + public OperationThreshold() { } + + public OperationThreshold(FileFormat slicerFile) : base(slicerFile) { } + + #endregion + #region Properties public byte Threshold { @@ -71,17 +79,15 @@ namespace UVtools.Core.Operations #region Methods - public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + protected override bool ExecuteInternally(OperationProgress progress) { - progress ??= new OperationProgress(); - progress.Reset(ProgressAction, LayerRangeCount); Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => { if (progress.Token.IsCancellationRequested) return; - using (var mat = slicerFile[layerIndex].LayerMat) + using (var mat = SlicerFile[layerIndex].LayerMat) { Execute(mat); - slicerFile[layerIndex].LayerMat = mat; + SlicerFile[layerIndex].LayerMat = mat; } lock (progress.Mutex) @@ -89,9 +95,8 @@ namespace UVtools.Core.Operations progress++; } }); - progress.Token.ThrowIfCancellationRequested(); - return true; + return !progress.Token.IsCancellationRequested; } public override bool Execute(Mat mat, params object[] arguments) diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 9bfaefd..2ca19df 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ https://github.com/sn4k3/UVtools https://github.com/sn4k3/UVtools MSLA/DLP, file analysis, calibration, repair, conversion and manipulation - 2.3.2 + 2.4.0 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 @@ -45,7 +45,7 @@ - + diff --git a/UVtools.InstallerMM/UVtools.InstallerMM.wxs b/UVtools.InstallerMM/UVtools.InstallerMM.wxs index f7bceba..0bda982 100644 --- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs +++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs @@ -152,6 +152,9 @@ + + + @@ -227,12 +230,18 @@ + + + + + + @@ -263,6 +272,18 @@ + + + + + + + + + + + + @@ -299,12 +320,30 @@ + + + + + + + + + + + + + + + + + + @@ -811,9 +850,9 @@ - - + + @@ -825,6 +864,15 @@ + + + + + + + + + @@ -1090,59 +1138,186 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + + + + + + + + + + + + - - + + + + + + + - - + + + + + - - - + + + - - + + - - + + - - + + - - + + + + - - + + - - + + - - + + diff --git a/UVtools.Platforms/arch-x64/libcvextern.so b/UVtools.Platforms/arch-x64/libcvextern.so index b8b0375..4027368 100644 Binary files a/UVtools.Platforms/arch-x64/libcvextern.so and b/UVtools.Platforms/arch-x64/libcvextern.so differ diff --git a/UVtools.Platforms/osx-x64/libcvextern.dylib b/UVtools.Platforms/osx-x64/libcvextern.dylib index 1af1f8c..1b40298 100644 Binary files a/UVtools.Platforms/osx-x64/libcvextern.dylib and b/UVtools.Platforms/osx-x64/libcvextern.dylib differ diff --git a/UVtools.Scripts/Erode-Bottom.ps1 b/UVtools.Scripts/Erode-Bottom.ps1 index 3087662..5f72bcd 100644 --- a/UVtools.Scripts/Erode-Bottom.ps1 +++ b/UVtools.Scripts/Erode-Bottom.ps1 @@ -110,19 +110,19 @@ $slicerFile.Decode($inputFile, $progress); ################################################### # Morph bottom erode Write-Output "Eroding bottoms with ${iterations} iterations, please wait..." -$morph = New-Object UVtools.Core.Operations.OperationMorph +$morph = [UVtools.Core.Operations.OperationMorph]::new($slicerFile) $morph.MorphOperation = [Emgu.CV.CvEnum.MorphOp]::Erode $morph.IterationsStart = $iterations -$morph.LayerIndexEnd = $slicerFile.BottomLayerCount - 1 -if(!$morph.Execute($slicerFile, $progress)){ return; } +$morph.SelectBottomLayers() +if(!$morph.Execute($progress)){ return; } ############## # Dont touch # ############## # Save file with _modified name appended -$filePath = [System.IO.Path]::GetDirectoryName($inputFile); -$fileExt = [System.IO.Path]::GetExtension($inputFile); +$filePath = [System.IO.Path]::GetDirectoryName($inputFile) +$fileExt = [System.IO.Path]::GetExtension($inputFile) $fileNoExt = [System.IO.Path]::GetFileNameWithoutExtension($inputFile) $fileOutput = "${filePath}${dirSeparator}${fileNoExt}_modified${fileExt}" Write-Output "Saving as ${fileNoExt}_modified${fileExt}, please wait..." diff --git a/UVtools.Scripts/README.md b/UVtools.Scripts/README.md index 447a3f9..b89927b 100644 --- a/UVtools.Scripts/README.md +++ b/UVtools.Scripts/README.md @@ -79,31 +79,30 @@ Take **[Erode-Bottom.ps1](https://github.com/sn4k3/UVtools/blob/master/UVtools.S * Example: ```Powershell # Erode bottom layers - $morph = New-Object UVtools.Core.Operations.OperationMorph + $morph = [UVtools.Core.Operations.OperationMorph]::new($slicerFile) $morph.MorphOperation = [Emgu.CV.CvEnum.MorphOp]::Erode $morph.IterationsStart = $iterations - $morph.LayerIndexEnd = $slicerFile.BottomLayerCount - 1 - if(!$morph.Execute($slicerFile, $progress)){ return; } + $morph.SelectBottomLayers() + if(!$morph.Execute($progress)){ return } # Reuse object and erode normal layers with 1 less iteration $morph.IterationsStart = $iterations - 1 - $morph.LayerIndexStart = $slicerFile.BottomLayerCount - $morph.LayerIndexEnd = $slicerFile.LayerCount - 1 - if(!$morph.Execute($slicerFile, $progress)){ return } + $morph.SelectNormalLayers() + if(!$morph.Execute($progress)){ return } - # Rotate layer 0, 45 - $rotate = New-Object UVtools.Core.Operations.OperationRotate + # Rotate all layers, 45 + $rotate = [UVtools.Core.Operations.OperationRotate]::new($slicerFile) $rotate.AngleDegrees = 45; - if(!$rotate.Execute($slicerFile, $progress)){ return } + if(!$rotate.Execute($progress)){ return } # Rotate layer 1, 90 $rotate.LayerIndexStart = 1 $rotate.LayerIndexEnd = 1 $rotate.AngleDegrees = 90 - if(!$rotate.Execute($slicerFile, $progress)){ return } + if(!$rotate.Execute($progress)){ return } ``` ## Contribute with your scripts -If you make a usefull script and want to contribute you can share and publish your script under [github - issues](https://github.com/sn4k3/UVtools/issues/new?assignees=sn4k3&labels=script&template=script.md&title=%5BSCRIPT%5D+). +If you make a usefull script and want to contribute you can share and publish your script under [github - discussions - scripts](https://github.com/sn4k3/UVtools/discussions/categories/scripts). After analyzation it will be published on the repository \ No newline at end of file diff --git a/UVtools.WPF/App.axaml b/UVtools.WPF/App.axaml index 3eb218e..ede140d 100644 --- a/UVtools.WPF/App.axaml +++ b/UVtools.WPF/App.axaml @@ -2,9 +2,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="UVtools.WPF.App"> - - - - + + + + + diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index 440cfff..3e330b0 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -24,13 +24,12 @@ using UVtools.Core; using UVtools.Core.FileFormats; using UVtools.WPF.Extensions; using UVtools.WPF.Structures; -using Size = System.Drawing.Size; namespace UVtools.WPF { public class App : Application { - public static IThemeSelector? ThemeSelector { get; set; } + public static ThemeSelector ThemeSelector { get; set; } public static MainWindow MainWindow; public static FileFormat SlicerFile = null; @@ -53,7 +52,7 @@ namespace UVtools.WPF OperationProfiles.Load(); - ThemeSelector = Avalonia.ThemeManager.ThemeSelector.Create(Path.Combine(ApplicationPath, "Assets", "Themes")); + /*ThemeSelector = ThemeSelector.Create(Path.Combine(ApplicationPath, "Assets", "Themes")); ThemeSelector.LoadSelectedTheme(Path.Combine(UserSettings.SettingsFolder, "selected.theme")); if (ThemeSelector.SelectedTheme.Name == "UVtoolsDark" || ThemeSelector.SelectedTheme.Name == "Light") { @@ -63,13 +62,16 @@ namespace UVtools.WPF theme.ApplyTheme(); break; } - } - - MainWindow = new MainWindow(); + }*/ + MainWindow = new MainWindow(); try { - CvInvoke.CheckLibraryLoaded(); + if(!CvInvoke.Init()) + await MainWindow.MessageBoxError("UVtools can not init OpenCV library\n" + + "Please build or install this dependencies in order to run UVtools\n" + + "Check manual or page at 'Requirements' section for help", + "UVtools can not run"); } catch (Exception e) { @@ -82,8 +84,7 @@ namespace UVtools.WPF } desktop.MainWindow = MainWindow; - desktop.Exit += (sender, e) - => ThemeSelector.SaveSelectedTheme(Path.Combine(UserSettings.SettingsFolder, "selected.theme")); + //desktop.Exit += (sender, e) => ThemeSelector.SaveSelectedTheme(Path.Combine(UserSettings.SettingsFolder, "selected.theme")); } base.OnFrameworkInitializationCompleted(); @@ -191,6 +192,8 @@ namespace UVtools.WPF if (OperatingSystem.IsLinux()) { + var folder1 = $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.config{Path.DirectorySeparatorChar}PrusaSlicer"; + if (Directory.Exists(folder1)) return folder1; return $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.PrusaSlicer"; } diff --git a/UVtools.WPF/Assets/Icons/dynamic-layers-16x16.png b/UVtools.WPF/Assets/Icons/dynamic-layers-16x16.png new file mode 100644 index 0000000..1644ec4 Binary files /dev/null and b/UVtools.WPF/Assets/Icons/dynamic-layers-16x16.png differ diff --git a/UVtools.WPF/Assets/Styles/Styles.xaml b/UVtools.WPF/Assets/Styles/Styles.xaml new file mode 100644 index 0000000..f86e52c --- /dev/null +++ b/UVtools.WPF/Assets/Styles/Styles.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UVtools.WPF/Assets/Styles/StylesLight.xaml b/UVtools.WPF/Assets/Styles/StylesLight.xaml new file mode 100644 index 0000000..d030ff4 --- /dev/null +++ b/UVtools.WPF/Assets/Styles/StylesLight.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml index 01da1d5..848c04f 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml +++ b/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml @@ -10,18 +10,17 @@ ("KernelCtrl"); _timer = new Timer(20) @@ -60,7 +54,6 @@ namespace UVtools.WPF.Controls.Calibrators { case ToolWindow.Callbacks.Init: case ToolWindow.Callbacks.ProfileLoaded: - Operation.Resolution = App.SlicerFile.Resolution; Operation.PropertyChanged += (sender, e) => { _timer.Stop(); diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml new file mode 100644 index 0000000..13e29fc --- /dev/null +++ b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml @@ -0,0 +1,592 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs new file mode 100644 index 0000000..c5be0cf --- /dev/null +++ b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml.cs @@ -0,0 +1,136 @@ +using System.Linq; +using System.Timers; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Media.Imaging; +using Avalonia.Threading; +using DynamicData; +using MessageBox.Avalonia.Enums; +using UVtools.Core.FileFormats; +using UVtools.Core.Operations; +using UVtools.WPF.Controls.Tools; +using UVtools.WPF.Extensions; +using UVtools.WPF.Windows; + +namespace UVtools.WPF.Controls.Calibrators +{ + public class CalibrateExposureFinderControl : ToolControl + { + public OperationCalibrateExposureFinder Operation => BaseOperation as OperationCalibrateExposureFinder; + + private Timer _timer; + + private Bitmap _previewImage; + private DataGrid _exposureTable; + + public Bitmap PreviewImage + { + get => _previewImage; + set => RaiseAndSetIfChanged(ref _previewImage, value); + } + + public bool CanSupportPerLayerSettings => SlicerFile.HavePrintParameterPerLayerModifier(FileFormat.PrintParameterModifier.ExposureSeconds); + + public CalibrateExposureFinderControl() + { + InitializeComponent(); + _exposureTable = this.FindControl("ExposureTable"); + BaseOperation = new OperationCalibrateExposureFinder(SlicerFile); + + _timer = new Timer(100) + { + AutoReset = false + }; + _timer.Elapsed += (sender, e) => Dispatcher.UIThread.InvokeAsync(UpdatePreview); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public override void Callback(ToolWindow.Callbacks callback) + { + if (App.SlicerFile is null) return; + switch (callback) + { + case ToolWindow.Callbacks.Init: + case ToolWindow.Callbacks.ProfileLoaded: + Operation.PropertyChanged += (sender, e) => + { + _timer.Stop(); + _timer.Start(); + }; + _timer.Stop(); + _timer.Start(); + break; + } + } + + public void UpdatePreview() + { + var layers = Operation.GetLayers(); + _previewImage?.Dispose(); + PreviewImage = layers[^1].ToBitmap(); + foreach (var layer in layers) + { + layer.Dispose(); + } + } + + public async void GenerateExposureTable() + { + if (Operation.ExposureTable.Count > 0) + { + if (await ParentWindow.MessageBoxQuestion( + "This automatic exposure table generation will clear the current table data!\n" + + "Do you want to continue?" + ) != ButtonResult.Yes) return; + } + + Operation.GenerateExposure(); + } + + public async void ExposureTableAddManual() + { + var exposure = Operation.ExposureManualEntry; + if (!exposure.IsValid) + { + await ParentWindow.MessageBoxError( + $"Layer height and exposures must be higher than zero (0).\n{exposure}"); + return; + } + + if (Operation.ExposureTable.Contains(exposure)) + { + await ParentWindow.MessageBoxError( + $"The configured layer height and exposure data already exists on the table.\n{exposure}"); + return; + } + + Operation.ExposureTable.Add(exposure); + Operation.SanitizeExposureTable(); + } + + public async void ExposureTableClearEntries() + { + if (Operation.ExposureTable.Count <= 0) return; + if (await ParentWindow.MessageBoxQuestion( + $"Are you sure you want to all the {Operation.ExposureTable.Count} entries?" + ) != ButtonResult.Yes) return; + + Operation.ExposureTable.Clear(); + } + + public async void ExposureTableRemoveSelectedEntries() + { + if (_exposureTable.SelectedItems.Count <= 0) return; + if (await ParentWindow.MessageBoxQuestion( + $"Are you sure you want to remove the {_exposureTable.SelectedItems.Count} selected entries?" + ) != ButtonResult.Yes) return; + + Operation.ExposureTable.RemoveMany(_exposureTable.SelectedItems.Cast()); + } + } +} diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExternalTestsControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateExternalTestsControl.axaml index 0b8421f..64dd735 100644 --- a/UVtools.WPF/Controls/Calibrators/CalibrateExternalTestsControl.axaml +++ b/UVtools.WPF/Controls/Calibrators/CalibrateExternalTestsControl.axaml @@ -13,6 +13,8 @@ Content="Photonsters Validation Matrix / Exposure finder" Command="{Binding ButtonClicked}" VerticalAlignment="Center" + HorizontalAlignment="Stretch" + HorizontalContentAlignment="Center" CommandParameter="https://www.thingiverse.com/thing:4707289"/> public static readonly StyledProperty OrientationProperty = - ScrollBar.OrientationProperty.AddOwner(); + ScrollBar.OrientationProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty IsSnapToTickEnabledProperty = - AvaloniaProperty.Register(nameof(IsSnapToTickEnabled), false); + AvaloniaProperty.Register(nameof(IsSnapToTickEnabled), false); /// /// Defines the property. /// public static readonly StyledProperty TickFrequencyProperty = - AvaloniaProperty.Register(nameof(TickFrequency), 0.0); + AvaloniaProperty.Register(nameof(TickFrequency), 0.0); /// /// Defines the property. /// public static readonly StyledProperty TickPlacementProperty = - AvaloniaProperty.Register(nameof(TickPlacement), 0d); + AvaloniaProperty.Register(nameof(TickPlacement), 0d); /// /// Defines the property. /// public static readonly StyledProperty> TicksProperty = - TickBar.TicksProperty.AddOwner(); + TickBar.TicksProperty.AddOwner(); // Slider required parts private bool _isDragging = false; @@ -71,11 +74,13 @@ namespace UVtools.WPF.Controls /// static SliderEx() { - PressedMixin.Attach(); - OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); + PressedMixin.Attach(); + OrientationProperty.OverrideDefaultValue(typeof(SliderEx), Orientation.Horizontal); Thumb.DragStartedEvent.AddClassHandler((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); + + ValueProperty.OverrideMetadata(new DirectPropertyMetadata(enableDataValidation: true)); } /// @@ -100,8 +105,8 @@ namespace UVtools.WPF.Controls /// public Orientation Orientation { - get => GetValue(OrientationProperty); - set => SetValue(OrientationProperty, value); + get { return GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } } /// @@ -109,8 +114,8 @@ namespace UVtools.WPF.Controls /// public bool IsSnapToTickEnabled { - get => GetValue(IsSnapToTickEnabledProperty); - set => SetValue(IsSnapToTickEnabledProperty, value); + get { return GetValue(IsSnapToTickEnabledProperty); } + set { SetValue(IsSnapToTickEnabledProperty, value); } } /// @@ -118,8 +123,8 @@ namespace UVtools.WPF.Controls /// public double TickFrequency { - get => GetValue(TickFrequencyProperty); - set => SetValue(TickFrequencyProperty, value); + get { return GetValue(TickFrequencyProperty); } + set { SetValue(TickFrequencyProperty, value); } } /// @@ -128,8 +133,8 @@ namespace UVtools.WPF.Controls /// public TickPlacement TickPlacement { - get => GetValue(TickPlacementProperty); - set => SetValue(TickPlacementProperty, value); + get { return GetValue(TickPlacementProperty); } + set { SetValue(TickPlacementProperty, value); } } /// @@ -182,9 +187,11 @@ namespace UVtools.WPF.Controls private void TrackPressed(object sender, PointerPressedEventArgs e) { - if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return; - MoveToPoint(e.GetCurrentPoint(_track)); - _isDragging = true; + if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + { + MoveToPoint(e.GetCurrentPoint(_track)); + _isDragging = true; + } } private void MoveToPoint(PointerPoint x) @@ -205,6 +212,14 @@ namespace UVtools.WPF.Controls Value = IsSnapToTickEnabled ? SnapToTick(finalValue) : finalValue; } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) + { + if (property == ValueProperty) + { + DataValidationErrors.SetError(this, value.Error); + } + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -233,49 +248,51 @@ namespace UVtools.WPF.Controls _isDragging = false; } - /// /// Snap the input 'value' to the closest tick. /// /// Value that want to snap to closest Tick. private double SnapToTick(double value) { - if (!IsSnapToTickEnabled) return value; - double previous = Minimum; - double next = Maximum; + if (IsSnapToTickEnabled) + { + double previous = Minimum; + double next = Maximum; - // This property is rarely set so let's try to avoid the GetValue - var ticks = Ticks; + // This property is rarely set so let's try to avoid the GetValue + var ticks = Ticks; - // If ticks collection is available, use it. - // Note that ticks may be unsorted. - if ((ticks != null) && (ticks.Count > 0)) - { - foreach (var tick in ticks) + // If ticks collection is available, use it. + // Note that ticks may be unsorted. + if ((ticks != null) && (ticks.Count > 0)) { - if (MathUtilities.AreClose(tick, value)) - { - return value; - } - - if (MathUtilities.LessThan(tick, value) && MathUtilities.GreaterThan(tick, previous)) - { - previous = tick; - } - else if (MathUtilities.GreaterThan(tick, value) && MathUtilities.LessThan(tick, next)) + for (int i = 0; i < ticks.Count; i++) { - next = tick; + double tick = ticks[i]; + if (MathUtilities.AreClose(tick, value)) + { + return value; + } + + if (MathUtilities.LessThan(tick, value) && MathUtilities.GreaterThan(tick, previous)) + { + previous = tick; + } + else if (MathUtilities.GreaterThan(tick, value) && MathUtilities.LessThan(tick, next)) + { + next = tick; + } } } - } - else if (MathUtilities.GreaterThan(TickFrequency, 0.0)) - { - previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency); - next = Math.Min(Maximum, previous + TickFrequency); - } + else if (MathUtilities.GreaterThan(TickFrequency, 0.0)) + { + previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency); + next = Math.Min(Maximum, previous + TickFrequency); + } - // Choose the closest value between previous and next. If tie, snap to 'next'. - value = MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous; + // Choose the closest value between previous and next. If tie, snap to 'next'. + value = MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous; + } return value; } diff --git a/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs index 623633e..18ad8ff 100644 --- a/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolArithmeticControl.axaml.cs @@ -11,14 +11,7 @@ namespace UVtools.WPF.Controls.Tools public ToolArithmeticControl() { InitializeComponent(); - BaseOperation = new OperationArithmetic(); - Operation.PropertyChanged += (sender, e) => - { - if (e.PropertyName == nameof(Operation.Sentence)) - { - ParentWindow.ButtonOkEnabled = !string.IsNullOrWhiteSpace(Operation.Sentence); - } - }; + BaseOperation = new OperationArithmetic(SlicerFile); } private void InitializeComponent() @@ -31,7 +24,15 @@ namespace UVtools.WPF.Controls.Tools switch (callback) { case ToolWindow.Callbacks.Init: - ParentWindow.ButtonOkEnabled = false; + case ToolWindow.Callbacks.ProfileLoaded: + ParentWindow.ButtonOkEnabled = !string.IsNullOrWhiteSpace(Operation.Sentence); + Operation.PropertyChanged += (sender, e) => + { + if (e.PropertyName == nameof(Operation.Sentence)) + { + ParentWindow.ButtonOkEnabled = !string.IsNullOrWhiteSpace(Operation.Sentence); + } + }; break; } } diff --git a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml index 36bbe84..e37b992 100644 --- a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml @@ -19,6 +19,7 @@ HorizontalAlignment="Left" SelectedIndex="{Binding SelectedAlgorithmIndex}" Width="450" + HorizontalContentAlignment="Stretch" Items="{Binding Operation.BlurTypes}" /> @@ -31,7 +32,7 @@ Grid.Row="2" Grid.Column="2" HorizontalAlignment="Left" - Width="80" + Width="150" Minimum="1" IsEnabled="{Binding IsSizeEnabled}" Value="{Binding Operation.Size}" diff --git a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs index c9ea8c3..89f6322 100644 --- a/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolBlurControl.axaml.cs @@ -41,7 +41,7 @@ namespace UVtools.WPF.Controls.Tools public ToolBlurControl() { InitializeComponent(); - BaseOperation = new OperationBlur(); + BaseOperation = new OperationBlur(SlicerFile); _kernelCtrl = this.Find("KernelCtrl"); } diff --git a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml index 8e3c9aa..d71fd0f 100644 --- a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml @@ -3,31 +3,31 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - MaxWidth="720" + MaxWidth="800" x:Class="UVtools.WPF.Controls.Tools.ToolCalculatorControl"> - - + BorderBrush="Black" BorderThickness="1"> + + - - + + + ColumnDefinitions="Auto,10,150,5,Auto,30,Auto,10,150,5,Auto"> - - + BorderBrush="Black" BorderThickness="1"> + + - - + + @@ -270,7 +271,7 @@ Minimum="0" Maximum="1000" Increment="1.0" - FormatString="{}{0:0.00}" + FormatString="F02" Value="{Binding Operation.CalcLightOffDelay.LiftHeight}"/> - - + BorderBrush="Black" BorderThickness="1"> + + - - + + @@ -586,7 +587,7 @@ Minimum="0" Maximum="100000" Increment="0.01" - FormatString="{}{0:0.00}" + FormatString="F02" Value="{Binding Operation.CalcOptimalModelTilt.DisplayWidth}" /> diff --git a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs index 893dcd5..f5fff6f 100644 --- a/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolCalculatorControl.axaml.cs @@ -21,16 +21,7 @@ namespace UVtools.WPF.Controls.Tools public ToolCalculatorControl() { InitializeComponent(); - BaseOperation = new OperationCalculator - { - CalcMillimetersToPixels = new OperationCalculator.MillimetersToPixels(SlicerFile.Resolution, SlicerFile.Display), - CalcLightOffDelay = new OperationCalculator.LightOffDelayC( - (decimal)SlicerFile.LiftHeight, (decimal)SlicerFile.BottomLiftHeight, - (decimal)SlicerFile.LiftSpeed, (decimal)SlicerFile.BottomLiftSpeed, - (decimal)SlicerFile.RetractSpeed, (decimal)SlicerFile.RetractSpeed), - CalcOptimalModelTilt = new OperationCalculator.OptimalModelTilt(SlicerFile.Resolution, SlicerFile.Display, (decimal) SlicerFile.LayerHeight) - }; - + BaseOperation = new OperationCalculator(SlicerFile); Operation.CalcLightOffDelay.PropertyChanged += (sender, e) => { if (e.PropertyName != nameof(Operation.CalcLightOffDelay.LightOffDelay) && diff --git a/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml b/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml index 744358a..26797c0 100644 --- a/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml @@ -4,8 +4,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="UVtools.WPF.Controls.Tools.ToolChangeResolutionControl"> - - + + @@ -14,8 +14,8 @@ MinWidth="100" Minimum="1" Maximum="50000" - Value="{Binding Operation.NewResolutionX}" - /> + Width="150" + Value="{Binding Operation.NewResolutionX}"/> @@ -23,6 +23,7 @@ MinWidth="100" Minimum="1" Maximum="50000" + Width="150" Value="{Binding Operation.NewResolutionY}" /> diff --git a/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml.cs index 2762a3c..059a13d 100644 --- a/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolChangeResolutionControl.axaml.cs @@ -34,7 +34,7 @@ namespace UVtools.WPF.Controls.Tools public ToolChangeResolutionControl() { InitializeComponent(); - BaseOperation = new OperationChangeResolution(App.SlicerFile.Resolution, App.SlicerFile.LayerManager.BoundingRectangle); + BaseOperation = new OperationChangeResolution(SlicerFile); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolControl.axaml b/UVtools.WPF/Controls/Tools/ToolControl.axaml index dafec5a..9fca886 100644 --- a/UVtools.WPF/Controls/Tools/ToolControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolControl.axaml @@ -3,6 +3,5 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="UVtools.WPF.Controls.Tools.ToolControl" - > + x:Class="UVtools.WPF.Controls.Tools.ToolControl"> diff --git a/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs index 8ec6d5b..bf66f21 100644 --- a/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Avalonia.Markup.Xaml; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; using UVtools.Core.Operations; using UVtools.WPF.Extensions; @@ -22,6 +23,8 @@ namespace UVtools.WPF.Controls.Tools } } + public FileFormat SlicerFile => App.SlicerFile; + public ToolWindow ParentWindow { get; set; } = null; public bool CanRun { get; set; } = true; diff --git a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml new file mode 100644 index 0000000..2ff77bf --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs new file mode 100644 index 0000000..abe252b --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs @@ -0,0 +1,86 @@ +using System; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using UVtools.Core.FileFormats; +using UVtools.Core.Operations; +using UVtools.WPF.Extensions; +using UVtools.WPF.Windows; + +namespace UVtools.WPF.Controls.Tools +{ + public class ToolDynamicLayerHeightControl : ToolControl + { + public OperationDynamicLayerHeight Operation => BaseOperation as OperationDynamicLayerHeight; + + public double LayerHeight => SlicerFile.LayerHeight; + public double MinimumLayerHeight => Math.Round(SlicerFile.LayerHeight*2, 2); + public double MaximumLayerHeight => FileFormat.MaximumLayerHeight; + + private DataGrid ExposureTable; + + public ToolDynamicLayerHeightControl() + { + BaseOperation = new OperationDynamicLayerHeight(SlicerFile); + + if (SlicerFile.LayerHeight * 2 > FileFormat.MaximumLayerHeight) + { + App.MainWindow.MessageBoxError($"This file already uses the maximum layer height possible ({SlicerFile.LayerHeight}mm).\n" + + $"Layers can not be stacked, please re-slice your file with the lowest layer height of 0.01mm.", + $"{BaseOperation.Title} unable to run"); + CanRun = false; + return; + } + + for (uint layerIndex = 1; layerIndex < SlicerFile.LayerCount; layerIndex++) + { + if ((decimal)Math.Round(SlicerFile[layerIndex].PositionZ - SlicerFile[layerIndex - 1].PositionZ, 2) == + (decimal)SlicerFile.LayerHeight) continue; + App.MainWindow.MessageBoxError($"This file contain layer(s) with modified positions, starting at layer {layerIndex}.\n" + + $"This tool requires sequential layers with equal height.\n" + + $"If you run this tool before, you cant re-run.", + $"{BaseOperation.Title} unable to run"); + CanRun = false; + return; + } + + if (!SlicerFile.HavePrintParameterPerLayerModifier(FileFormat.PrintParameterModifier.ExposureSeconds)) + { + App.MainWindow.MessageBoxWaring($"Your printer seems to not support this tool, still you are allowed to run it for analyze, packing layers or simulation.\n" + + $"Do not print this file after run this tool on a not compatible printer, it will result in malformed model and height violation.\n" + + $"Run this at your own risk!", + BaseOperation.Title).ConfigureAwait(false); + } + + + InitializeComponent(); + + ExposureTable = this.FindControl("ExposureTable"); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + + public override void Callback(ToolWindow.Callbacks callback) + { + if (SlicerFile is null) return; + switch (callback) + { + case ToolWindow.Callbacks.Init: + case ToolWindow.Callbacks.ProfileLoaded: + /*Operation.PropertyChanged += (sender, e) => + { + if (e.PropertyName.Equals(nameof(Operation.CacheObjectCount))) + { + RaisePropertyChanged(nameof(CacheRAMUsed)); + return; + } + };*/ + Operation.RebuildAutoExposureTable(); + break; + } + } + } +} diff --git a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml index e485b25..cc7cbe5 100644 --- a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml @@ -5,20 +5,47 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="UVtools.WPF.Controls.Tools.ToolEditParametersControl"> - + + + + + + + + + + + + + + ShowGridLines="True"> 0) { - NewValue.FormatString = "{0:0.00}"; + NewValue.FormatString = "F02"; } Unit = new TextBlock @@ -87,7 +89,8 @@ namespace UVtools.WPF.Controls.Tools VerticalAlignment = VerticalAlignment.Center, Tag = this, Padding = new Thickness(5), - Content = new Image {Source = App.GetBitmapFromAsset("/Assets/Icons/undo-16x16.png")} + Content = new Image {Source = App.GetBitmapFromAsset("/Assets/Icons/undo-16x16.png")}, + HorizontalAlignment = HorizontalAlignment.Stretch }; ResetButton.Click += ResetButtonOnClick; NewValue.ValueChanged += NewValueOnValueChanged; @@ -110,8 +113,7 @@ namespace UVtools.WPF.Controls.Tools { InitializeComponent(); - App.SlicerFile.RefreshPrintParametersModifiersValues(); - BaseOperation = new OperationEditParameters(App.SlicerFile.PrintParameterModifiers); + BaseOperation = new OperationEditParameters(SlicerFile); if (Operation.Modifiers is null || Operation.Modifiers.Length == 0) { diff --git a/UVtools.WPF/Controls/Tools/ToolFlipControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolFlipControl.axaml.cs index 7899b4d..44ea089 100644 --- a/UVtools.WPF/Controls/Tools/ToolFlipControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolFlipControl.axaml.cs @@ -10,7 +10,7 @@ namespace UVtools.WPF.Controls.Tools public ToolFlipControl() { InitializeComponent(); - BaseOperation = new OperationFlip(); + BaseOperation = new OperationFlip(SlicerFile); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml b/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml index f09dc61..4fa2208 100644 --- a/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml @@ -19,6 +19,7 @@ Grid.Column="2" Items="{Binding Operation.InfillAlgorithmTypes}" SelectedItem="{Binding Operation.InfillType}" + HorizontalAlignment="Stretch" /> diff --git a/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml.cs index a710a9e..c8fbb10 100644 --- a/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolInfillControl.axaml.cs @@ -10,7 +10,7 @@ namespace UVtools.WPF.Controls.Tools public ToolInfillControl() { InitializeComponent(); - BaseOperation = new OperationInfill(); + BaseOperation = new OperationInfill(SlicerFile); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml index d877261..98c3084 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml @@ -11,7 +11,7 @@ Text="Clones:"/> { RaisePropertyChanged(nameof(InfoLayersStr)); diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml index fa74937..9e40511 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml @@ -7,7 +7,7 @@ + ColumnDefinitions="Auto,10,150,5,Auto,20,Auto"> diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs index db58958..0fc4755 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs @@ -90,9 +90,7 @@ namespace UVtools.WPF.Controls.Tools public ToolLayerImportControl() { InitializeComponent(); - BaseOperation = new OperationLayerImport(App.SlicerFile.Resolution); - Operation.Files.CollectionChanged += (sender, args) => RefreshGUI(); - Operation.PropertyChanged += (sender, args) => RefreshGUI(); + BaseOperation = new OperationLayerImport(SlicerFile); FilesListBox = this.Find("FilesListBox"); FilesListBox.DoubleTapped += (sender, args) => { @@ -152,7 +150,10 @@ namespace UVtools.WPF.Controls.Tools switch (callback) { case ToolWindow.Callbacks.Init: + case ToolWindow.Callbacks.ProfileLoaded: RefreshGUI(); + Operation.Files.CollectionChanged += (sender, args) => RefreshGUI(); + Operation.PropertyChanged += (sender, args) => RefreshGUI(); break; } } diff --git a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml index 21c0e98..3db4d3c 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml @@ -4,13 +4,13 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="UVtools.WPF.Controls.Tools.ToolLayerReHeightControl"> - + diff --git a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs index 09d3ebb..4b6e093 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerReHeightControl.axaml.cs @@ -8,7 +8,7 @@ namespace UVtools.WPF.Controls.Tools { public OperationLayerReHeight Operation => BaseOperation as OperationLayerReHeight; - public OperationLayerReHeight.OperationLayerReHeightItem[] Presets => OperationLayerReHeight.GetItems( + public OperationLayerReHeight.OperationLayerReHeightItem[] Presets => App.SlicerFile is null ? null : OperationLayerReHeight.GetItems( App.SlicerFile.LayerCount, (decimal)App.SlicerFile.LayerHeight); @@ -17,9 +17,9 @@ namespace UVtools.WPF.Controls.Tools public ToolLayerReHeightControl() { InitializeComponent(); - BaseOperation = new OperationLayerReHeight(); - - if (Presets.Length == 0) + BaseOperation = new OperationLayerReHeight(SlicerFile); + var presets = Presets; + if (presets is null || presets.Length == 0) { App.MainWindow.MessageBoxInfo("No valid configuration to be able to re-height.\n" + "As workaround clone first or last layer and try re run this tool.", "Not possible to re-height"); @@ -27,7 +27,7 @@ namespace UVtools.WPF.Controls.Tools } else { - Operation.Item = Presets[0]; + Operation.Item = presets[0]; } } diff --git a/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs index f4be3b7..6e56c20 100644 --- a/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs @@ -31,7 +31,7 @@ namespace UVtools.WPF.Controls.Tools public ToolLayerRemoveControl() { InitializeComponent(); - BaseOperation = new OperationLayerRemove(); + BaseOperation = new OperationLayerRemove(SlicerFile); Operation.PropertyChanged += (sender, args) => { RaisePropertyChanged(nameof(InfoLayersStr)); diff --git a/UVtools.WPF/Controls/Tools/ToolMaskControl.axaml b/UVtools.WPF/Controls/Tools/ToolMaskControl.axaml index da77c3c..2773294 100644 --- a/UVtools.WPF/Controls/Tools/ToolMaskControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolMaskControl.axaml @@ -5,7 +5,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="800" x:Class="UVtools.WPF.Controls.Tools.ToolMaskControl"> - + + + + + + - - - - + + - - - + - + + + + + + diff --git a/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml.cs index 83934ff..ad5cd81 100644 --- a/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; using UVtools.Core.FileFormats; using UVtools.Core.Operations; +using UVtools.WPF.Windows; namespace UVtools.WPF.Controls.Tools { @@ -12,7 +13,7 @@ namespace UVtools.WPF.Controls.Tools public ToolRedrawModelControl() { InitializeComponent(); - BaseOperation = new OperationRedrawModel(); + BaseOperation = new OperationRedrawModel(SlicerFile); } private void InitializeComponent() @@ -20,6 +21,24 @@ namespace UVtools.WPF.Controls.Tools AvaloniaXamlLoader.Load(this); } + public override void Callback(ToolWindow.Callbacks callback) + { + switch (callback) + { + case ToolWindow.Callbacks.Init: + case ToolWindow.Callbacks.ProfileLoaded: + ParentWindow.ButtonOkEnabled = !string.IsNullOrWhiteSpace(Operation.FilePath); + Operation.PropertyChanged += (sender, e) => + { + if (e.PropertyName == nameof(Operation.FilePath)) + { + ParentWindow.ButtonOkEnabled = !string.IsNullOrWhiteSpace(Operation.FilePath); + } + }; + break; + } + } + public async void ImportFile() { var filters = Helpers.ToAvaloniaFileFilter(FileFormat.AllFileFiltersAvalonia); diff --git a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml index 5a71eb5..d20808e 100644 --- a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml @@ -5,51 +5,35 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="UVtools.WPF.Controls.Tools.ToolRepairLayersControl"> - + - - - - - - - + /> + - - - - - - - + /> + - - - - - - - - - - + + + @@ -57,15 +41,9 @@ Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" - Text="Remove islands equal or smaller than:"> - - - - - - - + ToolTip.Tip="The pixel area threshold above which islands will not be removed by this repair. + Islands remaining after repair will require supports to be added manually." + Text="Remove islands equal or smaller than:"/> - - - - - - - + /> + - - - - - - - + Using this function with high values can be extremely slow depending on resolution and issue count." + Text="Recursively remove islands for up to:"/> - - - - - - - + Using this function with high values can be extremely slow depending on resolution and issue count." + Value="{Binding Operation.RemoveIslandsRecursiveIterations}" + /> + + + RowDefinitions="Auto,10,Auto" + ColumnDefinitions="Auto,10,155"> - - - - - - - + WARNING: Using high iteration values can destroy your model. Low values recommended." + Text="Gap closing iterations:"/> + - - - - - - - + WARNING: Using high iteration values can destroy your model. Low values recommended." + Value="{Binding Operation.GapClosingIterations}" + /> + - - - - - - - + Set iterations to 0 to disable. + WARNING: Even at low settings this operation has the potential to introduce new islands, as it may remove supporting material from previous layers." + Text="Noise removal iterations:"/> + - - - - - - - + Set iterations to 0 to disable. + WARNING: Even at low settings this operation has the potential to introduce new islands, as it may remove supporting material from previous layers." + Value="{Binding Operation.NoiseRemovalIterations}" + /> + diff --git a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs index 0c317d8..14ed006 100644 --- a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs @@ -11,7 +11,7 @@ namespace UVtools.WPF.Controls.Tools public ToolRepairLayersControl() { InitializeComponent(); - BaseOperation = new OperationRepairLayers + BaseOperation = new OperationRepairLayers(SlicerFile) { RepairIslands = UserSettings.Instance.LayerRepair.RepairIslands, RepairResinTraps = UserSettings.Instance.LayerRepair.RepairResinTraps, diff --git a/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml b/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml index 300ecc8..ff8571c 100644 --- a/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml @@ -2,31 +2,27 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="200" + mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="200" x:Class="UVtools.WPF.Controls.Tools.ToolResizeControl"> - + Value="{Binding Operation.X}"/> + IsEnabled="{Binding !#ConstrainXY.IsChecked}"/> diff --git a/UVtools.WPF/Controls/Tools/ToolRotateControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolRotateControl.axaml.cs index c09b075..0fb6e9f 100644 --- a/UVtools.WPF/Controls/Tools/ToolRotateControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolRotateControl.axaml.cs @@ -10,7 +10,7 @@ namespace UVtools.WPF.Controls.Tools public ToolRotateControl() { InitializeComponent(); - BaseOperation = new OperationRotate(); + BaseOperation = new OperationRotate(SlicerFile); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml b/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml index fe7cc24..bad072e 100644 --- a/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolSolidifyControl.axaml @@ -12,7 +12,7 @@ @@ -45,13 +45,13 @@ diff --git a/UVtools.WPF/Controls/Tools/ToolThresholdControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolThresholdControl.axaml.cs index 648bbbf..3be8386 100644 --- a/UVtools.WPF/Controls/Tools/ToolThresholdControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolThresholdControl.axaml.cs @@ -74,7 +74,7 @@ namespace UVtools.WPF.Controls.Tools public ToolThresholdControl() { InitializeComponent(); - BaseOperation = new OperationThreshold(); + BaseOperation = new OperationThreshold(SlicerFile); } private void InitializeComponent() diff --git a/UVtools.WPF/Controls/WindowEx.cs b/UVtools.WPF/Controls/WindowEx.cs index 5f870f2..4c28b9a 100644 --- a/UVtools.WPF/Controls/WindowEx.cs +++ b/UVtools.WPF/Controls/WindowEx.cs @@ -6,15 +6,18 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; -using System.Threading.Tasks; +using Avalonia; using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Styling; namespace UVtools.WPF.Controls { - public class WindowEx : Window, INotifyPropertyChanged + public class WindowEx : Window, INotifyPropertyChanged, IStyleable { #region BindableBase /// @@ -58,6 +61,8 @@ namespace UVtools.WPF.Controls } #endregion + Type IStyleable.StyleKey => typeof(Window); + public DialogResults DialogResult { get; set; } = DialogResults.Unknown; public enum DialogResults { @@ -66,6 +71,14 @@ namespace UVtools.WPF.Controls Cancel } + public WindowEx() + { +#if DEBUG + this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Control)); +#endif + //TransparencyLevelHint = WindowTransparencyLevel.AcrylicBlur; + } + public void CloseWithResult() { Close(DialogResult); diff --git a/UVtools.WPF/Extensions/WindowExtensions.cs b/UVtools.WPF/Extensions/WindowExtensions.cs index 22a871f..954297b 100644 --- a/UVtools.WPF/Extensions/WindowExtensions.cs +++ b/UVtools.WPF/Extensions/WindowExtensions.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Threading; -using Avalonia.VisualTree; using MessageBox.Avalonia.DTO; using MessageBox.Avalonia.Enums; @@ -36,7 +35,6 @@ namespace UVtools.WPF.Extensions MaxWidth = window.GetScreenWorkingArea().Width - UserSettings.Instance.General.WindowsHorizontalMargin, ShowInCenter = true }); - return await messageBoxStandardWindow.ShowDialog(window); } @@ -49,6 +47,9 @@ namespace UVtools.WPF.Extensions public static async Task MessageBoxQuestion(this Window window, string message, string title = null, ButtonEnum buttons = ButtonEnum.YesNo, Style style = Style.None) => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", buttons, Icon.Setting, WindowStartupLocation.CenterOwner, style); + public static async Task MessageBoxWaring(this Window window, string message, string title = null, ButtonEnum buttons = ButtonEnum.Ok, Style style = Style.None) + => await window.MessageBoxGeneric(message, title ?? $"{window.Title} - Question", buttons, Icon.Warning, WindowStartupLocation.CenterOwner, style); + public static void ShowDialogSync(this Window window, Window parent = null) { diff --git a/UVtools.WPF/MainWindow.Information.cs b/UVtools.WPF/MainWindow.Information.cs index 1de8ac1..9b0bb1a 100644 --- a/UVtools.WPF/MainWindow.Information.cs +++ b/UVtools.WPF/MainWindow.Information.cs @@ -335,13 +335,14 @@ namespace UVtools.WPF var layer = LayerCache.Layer; CurrentLayerProperties.Clear(); CurrentLayerProperties.Add(new StringTag(nameof(layer.Index), $"{layer.Index}")); + CurrentLayerProperties.Add(new StringTag(nameof(layer.LayerHeight), $"{layer.LayerHeight:F2}mm")); //CurrentLayerProperties.Add(new KeyValuePair(nameof(layer.Filename), layer.Filename)); - CurrentLayerProperties.Add(new StringTag(nameof(layer.PositionZ), $"{layer.PositionZ.ToString(CultureInfo.InvariantCulture)}mm")); + CurrentLayerProperties.Add(new StringTag(nameof(layer.PositionZ), $"{layer.PositionZ:F2}mm")); CurrentLayerProperties.Add(new StringTag(nameof(layer.IsBottomLayer), layer.IsBottomLayer.ToString())); CurrentLayerProperties.Add(new StringTag(nameof(layer.IsModified), layer.IsModified.ToString())); //CurrentLayerProperties.Add(new StringTag(nameof(layer.BoundingRectangle), layer.BoundingRectangle.ToString())); //CurrentLayerProperties.Add(new StringTag(nameof(layer.NonZeroPixelCount), layer.NonZeroPixelCount.ToString())); - CurrentLayerProperties.Add(new StringTag(nameof(layer.ExposureTime), $"{layer.ExposureTime.ToString(CultureInfo.InvariantCulture)}s")); + CurrentLayerProperties.Add(new StringTag(nameof(layer.ExposureTime), $"{layer.ExposureTime:F2}s")); if (ReferenceEquals(SlicerFile.PrintParameterPerLayerModifiers, null)) return; diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index f2dcee6..85de78b 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -1,8 +1,7 @@ - - + @@ -761,6 +760,7 @@ @@ -770,13 +770,12 @@ - + - - + + - + + RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto" + ColumnDefinitions="Auto,10,130,5,40"> + SelectedItem="{Binding DrawingPixelDrawing.LineType}"/> + SelectedItem="{Binding DrawingPixelDrawing.BrushShape}"/> @@ -849,14 +848,14 @@ + ClipValueToMinMax="True" + HorizontalAlignment="Stretch" + Value="{Binding DrawingPixelDrawing.Thickness}"/> @@ -868,16 +867,16 @@ + Text="{Binding DrawingPixelDrawing.RemovePixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="{Binding DrawingPixelDrawing.PixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="Layers depth below:" /> - + ClipValueToMinMax="True" + HorizontalAlignment="Stretch" + Value="{Binding DrawingPixelDrawing.LayersBelow}"/> + + - + ClipValueToMinMax="True" + HorizontalAlignment="Stretch" + Value="{Binding DrawingPixelDrawing.LayersAbove}"/> @@ -938,9 +933,7 @@ - + @@ -951,15 +944,15 @@ - + + RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto" + ColumnDefinitions="Auto,10,130,5,40"> @@ -982,6 +976,7 @@ Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" + Width="180" Items="{Binding DrawingPixelText.FontFaces}" SelectedItem="{Binding DrawingPixelText.Font}"/> @@ -997,6 +992,7 @@ Minimum="0.1" Maximum="255" Increment="0.1" + ClipValueToMinMax="True" Value="{Binding DrawingPixelText.FontScale}"/> @@ -1011,6 +1007,7 @@ Grid.ColumnSpan="3" Minimum="1" Maximum="255" + ClipValueToMinMax="True" Value="{Binding DrawingPixelText.Thickness}"/> + Text="{Binding DrawingPixelText.RemovePixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="{Binding DrawingPixelText.PixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="Layers depth below:" /> - + ClipValueToMinMax="True" + Value="{Binding DrawingPixelText.LayersBelow}"/> + + - + ClipValueToMinMax="True" + Value="{Binding DrawingPixelText.LayersAbove}"/> @@ -1110,9 +1099,7 @@ - + @@ -1123,14 +1110,15 @@ - + - + + RowDefinitions="Auto,10,Auto,10,Auto" + ColumnDefinitions="Auto,10,*,10,40"> + Text="{Binding DrawingPixelEraser.PixelBrightnessPercent, StringFormat=\{0:0\}%}" /> - + Text="Layers depth below:" /> - + ClipValueToMinMax="True" + Value="{Binding DrawingPixelEraser.LayersBelow}"/> + - + ClipValueToMinMax="True" + Value="{Binding DrawingPixelEraser.LayersAbove}"/> + @@ -1204,14 +1185,14 @@ - + - + ColumnDefinitions="Auto,10,*,5,35"> + Text="{Binding DrawingPixelSupport.PixelBrightnessPercent, StringFormat=\{0:0\}%}" /> @@ -1300,14 +1281,14 @@ - + + ColumnDefinitions="Auto,10,*,5,Auto"> @@ -1586,13 +1568,12 @@ + Grid.Row="2" ColumnDefinitions="*,20,40"> + HorizontalAlignment="Stretch"> - + @@ -1636,6 +1617,7 @@ ToolTip.Tip="Navigate to down layer [Ctrl + Down]" HotKey="Ctrl + Down" Interval="100" + HorizontalAlignment="Stretch" IsEnabled="{Binding CanGoDown}" Command="{Binding GoPreviousLayer}" > @@ -1705,6 +1687,7 @@ @@ -1716,6 +1699,7 @@ @@ -1727,6 +1711,7 @@ @@ -1738,6 +1723,7 @@ @@ -1750,6 +1736,7 @@ ToolTip.Tip="Click to access the various outlines." Command="{Binding OpenContextMenu}" CommandParameter="LayerPreviewOutline" + VerticalAlignment="Stretch" Margin="1,0,0,0" > @@ -1778,6 +1765,7 @@ @@ -1792,6 +1780,7 @@ - + - + @@ -205,14 +216,13 @@ + Classes="GroupBox" + Margin="5"> - + - + + Classes="GroupBox" + Margin="5"> - + @@ -530,7 +539,7 @@ Margin="10,0,0,0" VerticalAlignment="Center" SelectedIndex="{Binding Settings.LayerPreview.ZoomToFitPrintVolumeBounds}" - > + HorizontalAlignment="Stretch"> @@ -549,12 +558,13 @@ VerticalAlignment="Center" Items="{Binding ZoomRanges}" SelectedIndex="{Binding Settings.LayerPreview.ZoomLockLevelIndex}" + HorizontalAlignment="Stretch" /> @@ -564,13 +574,12 @@ + Classes="GroupBox" + Margin="5"> - + + Classes="GroupBox" + Margin="5"> @@ -656,7 +664,7 @@ @@ -672,24 +680,23 @@ + Classes="GroupBox" + Margin="5"> - - - - + + + Classes="GroupBox" + Margin="5"> - - + - + - + - + - + - - + - @@ -65,7 +57,7 @@ + Text="{Binding LayerStartMM, StringFormat=(\{0:F2\}mm)}" /> + Text="{Binding LayerEndMM, StringFormat=(\{0:F2\}mm)}" /> @@ -180,18 +174,14 @@ + IsVisible="{Binding IsROIVisible}"> @@ -258,13 +247,14 @@ IsEnabled="{Binding Profiles.Count}" IsVisible="{Binding Profiles.Count}" SelectedItem="{Binding SelectedProfileItem}" + HorizontalAlignment="Stretch" Items="{Binding Profiles}" />