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

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2021-03-19 07:48:45 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2021-03-19 07:48:45 +0300
commit3f838001c7fe5d67dadfd3d70e7ea012f99fabe7 (patch)
tree31a69dc9c7cf09106b7bb2fca3b25f4bd1360517
parentcb1070df5e6690f6e31753c58576621c5e587737 (diff)
v2.7.0v2.7.0
* **Core:** * Write "Unhandled Exceptions" to an "errors.log" file at user settings directory * Increase layer height max precision from 2 to 3 decimals * **Settings - Layer Preview:** * Allow to set hollow line thickness to -1 to fill the area * Add tooltip for "Auto rotate on load": Auto rotate the layer preview on file load for a landscape viewport * Add option to masks outline color and thickness * Add option to clear ROI when adding masks * Add option "Auto flip on load": Auto flip the layer preview on file load if the file is marked to print mirrored on the printer LCD * **Layer preview:** * Add selectable rotation directions 90º (CW and CCW) * Add preview flip (CTRL+F) horizontally and/or vertically * Add maskable regions to process on a layer (SHIFT + Alt + Click) on a area * ROI: Shortcut "Shift + left click" now also selects hollow black areas inside a white perimeter * ROI: Shortcut "ESC + Shift" to clear only the ROI and leave masks in * Fix a crash when using the pixel picker tool outside image bounds * **Pixel editor:** * Change drawings brush diameter limit from 255 to 4000 maximum * When using layers below go lower than layer 0 it no longer apply the modifications * **File formats:** * Add an internal GCodeBuilder to generate identical gcode within formats with auto convertion, managing features and parsing information * Internally rearrange formats properties and pass values to the base class * Fix "Save As" filename when using formats with dual extensions * CBDDLP and CTB: "LightPWM" was setting "BottomLightPWM" instead * CWS: Fix problem with filenames with dots (.) and ending with numbers (#171) * CWS: Improved the enconding and decoding performance * CWS: Implement all available print paramenters and per layer, missing values are got from gcode interpretation * CWS: Use set "light off delay" layer value instead of calculating it * CWS: Get light off delay per layer parsed from gcode * CWS - RGB flavour (Bene4 Mono): Warn about wrong resolution if width not multiples of 3 (#171) * ZCode: Allow to set Bottom and Light intensity (LightPWM) on paramters and per layer * ZCode: Allow to change bottom light pwm independent from normal light pwm * LGS: Light off and bottom light off was setting the value on the wrong units * UVJ: Unable to set per layer parameters * **Issues:** * When computing islands and resin traps together, they will not compute in parallel anymore to prevent CPU and tasks exaustion, it will calculate islands first and then resin traps, this should also speed up the process on weaker machines * Gather resin trap areas together when computing for other issues to spare a decoding cycle latter * When using a threshold for islands detection it was also appling it to the overhangs * Fix the spare decoding conditional cycle for partial scans * Change resin trap search from parallel to sync to prevent fake detections and missing joints at cost of speed (#13) * **Tools:** * Add layer selector: 'From first to current layer' and 'From current to last layer' * I printed this file: Multiplier - Number of time(s) the file has been printed. Half numbers can be used to consume from a failed print. Example: 0.5x if a print canceled at 50% progress * Pixel dimming: Increase wall thickness default from 5px to 10px * Import layers: Importing layers was not marking layers as modified, then the save file won't save the new images in, to prevent other similar bugs, all layers that got replaced will be auto marked as modified
-rw-r--r--CHANGELOG.md50
-rw-r--r--CREDITS.md7
-rw-r--r--CreateRelease.WPF.ps12
-rw-r--r--PrusaSlicer/printer/AnyCubic Photon Zero.ini6
-rw-r--r--UVtools.Core/Extensions/PathExtensions.cs9
-rw-r--r--UVtools.Core/Extensions/SizeExtensions.cs8
-rw-r--r--UVtools.Core/Extensions/TimeExtensions.cs39
-rw-r--r--UVtools.Core/FileFormats/CWSFile.cs592
-rw-r--r--UVtools.Core/FileFormats/ChituboxFile.cs120
-rw-r--r--UVtools.Core/FileFormats/ChituboxZipFile.cs339
-rw-r--r--UVtools.Core/FileFormats/FDGFile.cs147
-rw-r--r--UVtools.Core/FileFormats/FileFormat.cs190
-rw-r--r--UVtools.Core/FileFormats/LGSFile.cs94
-rw-r--r--UVtools.Core/FileFormats/PHZFile.cs135
-rw-r--r--UVtools.Core/FileFormats/PhotonSFile.cs85
-rw-r--r--UVtools.Core/FileFormats/PhotonWorkshopFile.cs82
-rw-r--r--UVtools.Core/FileFormats/SL1File.cs48
-rw-r--r--UVtools.Core/FileFormats/UVJFile.cs95
-rw-r--r--UVtools.Core/FileFormats/ZCodeFile.cs279
-rw-r--r--UVtools.Core/FileFormats/ZCodexFile.cs88
-rw-r--r--UVtools.Core/GCode/GCodeBuilder.cs792
-rw-r--r--UVtools.Core/GCode/GCodeCommand.cs120
-rw-r--r--UVtools.Core/Layer/Layer.cs37
-rw-r--r--UVtools.Core/Layer/LayerManager.cs882
-rw-r--r--UVtools.Core/Objects/BindableBase.cs6
-rw-r--r--UVtools.Core/Objects/ExposureItem.cs4
-rw-r--r--UVtools.Core/Objects/GCodeBuilder.cs180
-rw-r--r--UVtools.Core/Objects/StaticObjects.cs3
-rw-r--r--UVtools.Core/Operations/Operation.cs95
-rw-r--r--UVtools.Core/Operations/OperationArithmetic.cs20
-rw-r--r--UVtools.Core/Operations/OperationBlur.cs5
-rw-r--r--UVtools.Core/Operations/OperationCalibrateElephantFoot.cs6
-rw-r--r--UVtools.Core/Operations/OperationCalibrateExposureFinder.cs12
-rw-r--r--UVtools.Core/Operations/OperationCalibrateGrayscale.cs8
-rw-r--r--UVtools.Core/Operations/OperationCalibrateStressTower.cs4
-rw-r--r--UVtools.Core/Operations/OperationCalibrateTolerance.cs6
-rw-r--r--UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs4
-rw-r--r--UVtools.Core/Operations/OperationDynamicLayerHeight.cs17
-rw-r--r--UVtools.Core/Operations/OperationFlip.cs3
-rw-r--r--UVtools.Core/Operations/OperationIPrintedThisFile.cs53
-rw-r--r--UVtools.Core/Operations/OperationInfill.cs9
-rw-r--r--UVtools.Core/Operations/OperationLayerReHeight.cs10
-rw-r--r--UVtools.Core/Operations/OperationLayerRemove.cs4
-rw-r--r--UVtools.Core/Operations/OperationMask.cs3
-rw-r--r--UVtools.Core/Operations/OperationMorph.cs3
-rw-r--r--UVtools.Core/Operations/OperationMove.cs3
-rw-r--r--UVtools.Core/Operations/OperationPattern.cs2
-rw-r--r--UVtools.Core/Operations/OperationPixelDimming.cs15
-rw-r--r--UVtools.Core/Operations/OperationProgress.cs15
-rw-r--r--UVtools.Core/Operations/OperationRaftRelief.cs45
-rw-r--r--UVtools.Core/Operations/OperationRedrawModel.cs2
-rw-r--r--UVtools.Core/Operations/OperationResize.cs2
-rw-r--r--UVtools.Core/Operations/OperationRotate.cs4
-rw-r--r--UVtools.Core/Operations/OperationSolidify.cs13
-rw-r--r--UVtools.Core/Operations/OperationThreshold.cs4
-rw-r--r--UVtools.Core/UVtools.Core.csproj2
-rw-r--r--UVtools.InstallerMM/UVtools.InstallerMM.wxs4
-rw-r--r--UVtools.WPF/Controls/AdvancedImageBox.axaml.cs22
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml9
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml10
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml4
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml8
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml4
-rw-r--r--UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml4
-rw-r--r--UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs5
-rw-r--r--UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml19
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs7
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs7
-rw-r--r--UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolResizeControl.axaml4
-rw-r--r--UVtools.WPF/ErrorLog.cs31
-rw-r--r--UVtools.WPF/MainWindow.Clipboard.cs6
-rw-r--r--UVtools.WPF/MainWindow.GCode.cs2
-rw-r--r--UVtools.WPF/MainWindow.Information.cs11
-rw-r--r--UVtools.WPF/MainWindow.Issues.cs1
-rw-r--r--UVtools.WPF/MainWindow.LayerPreview.cs423
-rw-r--r--UVtools.WPF/MainWindow.PixelEditor.cs16
-rw-r--r--UVtools.WPF/MainWindow.axaml47
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs38
-rw-r--r--UVtools.WPF/Program.cs23
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj4
-rw-r--r--UVtools.WPF/UserSettings.cs49
-rw-r--r--UVtools.WPF/Windows/SettingsWindow.axaml61
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml87
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml.cs98
87 files changed, 3541 insertions, 2277 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9072cf..676cce4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,60 @@
# Changelog
+## 19/03/2021 - v2.7.0
+
+* **Core:**
+ * Write "Unhandled Exceptions" to an "errors.log" file at user settings directory
+ * Increase layer height max precision from 2 to 3 decimals
+* **Settings - Layer Preview:**
+ * Allow to set hollow line thickness to -1 to fill the area
+ * Add tooltip for "Auto rotate on load": Auto rotate the layer preview on file load for a landscape viewport
+ * Add option to masks outline color and thickness
+ * Add option to clear ROI when adding masks
+ * Add option "Auto flip on load": Auto flip the layer preview on file load if the file is marked to print mirrored on the printer LCD
+* **Layer preview:**
+ * Add selectable rotation directions 90º (CW and CCW)
+ * Add preview flip (CTRL+F) horizontally and/or vertically
+ * Add maskable regions to process on a layer (SHIFT + Alt + Click) on a area
+ * ROI: Shortcut "Shift + left click" now also selects hollow black areas inside a white perimeter
+ * ROI: Shortcut "ESC + Shift" to clear only the ROI and leave masks in
+ * Fix a crash when using the pixel picker tool outside image bounds
+* **Pixel editor:**
+ * Change drawings brush diameter limit from 255 to 4000 maximum
+ * When using layers below go lower than layer 0 it no longer apply the modifications
+* **File formats:**
+ * Add an internal GCodeBuilder to generate identical gcode within formats with auto convertion, managing features and parsing information
+ * Internally rearrange formats properties and pass values to the base class
+ * Fix "Save As" filename when using formats with dual extensions
+ * CBDDLP and CTB: "LightPWM" was setting "BottomLightPWM" instead
+ * CWS: Fix problem with filenames with dots (.) and ending with numbers (#171)
+ * CWS: Improved the enconding and decoding performance
+ * CWS: Implement all available print paramenters and per layer, missing values are got from gcode interpretation
+ * CWS: Use set "light off delay" layer value instead of calculating it
+ * CWS: Get light off delay per layer parsed from gcode
+ * CWS - RGB flavour (Bene4 Mono): Warn about wrong resolution if width not multiples of 3 (#171)
+ * ZCode: Allow to set Bottom and Light intensity (LightPWM) on paramters and per layer
+ * ZCode: Allow to change bottom light pwm independent from normal light pwm
+ * LGS: Light off and bottom light off was setting the value on the wrong units
+ * UVJ: Unable to set per layer parameters
+* **Issues:**
+ * When computing islands and resin traps together, they will not compute in parallel anymore to prevent CPU and tasks exaustion, it will calculate islands first and then resin traps, this should also speed up the process on weaker machines
+ * Gather resin trap areas together when computing for other issues to spare a decoding cycle latter
+ * When using a threshold for islands detection it was also appling it to the overhangs
+ * Fix the spare decoding conditional cycle for partial scans
+ * Change resin trap search from parallel to sync to prevent fake detections and missing joints at cost of speed (#13)
+* **Tools:**
+ * Add layer selector: 'From first to current layer' and 'From current to last layer'
+ * I printed this file: Multiplier - Number of time(s) the file has been printed. Half numbers can be used to consume from a failed print. Example: 0.5x if a print canceled at 50% progress
+ * Pixel dimming: Increase wall thickness default from 5px to 10px
+ * Import layers: Importing layers was not marking layers as modified, then the save file won't save the new images in, to prevent other similar bugs, all layers that got replaced will be auto marked as modified
+
## 05/03/2021 - v2.6.2
* (Add) Edit print paramenters: Option to enable or disable the 'Propagate modifications to layers' when working with global parameters
* (Change) PrusaSlicer printer: Auto convert format from ctb to photon for Anycubic Photon
* **(Fix) Change resolution:**
* It was placing the object on random layers on the wrong position, shifting the object
- * Add informaton on the too description to warn about diferent pixel pitch for target printer
+ * Add informaton on the tool description to warn about diferent pixel pitch for target printer
* Add current pixel pitch in microns if available
* (Fix) Exposure time finder: Multiple exposures was getting bottom and normal time from base file instead of commum properties fields
diff --git a/CREDITS.md b/CREDITS.md
index f759378..ab77ebd 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -9,7 +9,6 @@
* https://github.com/byancey
### Photonsters (Photon Owners Group)
-
* For information about cbddlp file format
* https://github.com/Photonsters
@@ -19,12 +18,10 @@
* https://github.com/cbiffle/catibo
### Jason S. McMullan (ezrec)
-
* For ideas, file formats and permission to use his project (uv3dp)
* https://github.com/ezrec/uv3dp
### Khalil Nurullah
-
* For beta testing new features
# Supporters / Contributors
@@ -50,4 +47,6 @@
* Randy Savell
* Steve Clynes
* Nicholas Taylor
-* Nadine Maillard \ No newline at end of file
+* Nadine Maillard
+* Sidney Cheng
+* Ben Ford \ No newline at end of file
diff --git a/CreateRelease.WPF.ps1 b/CreateRelease.WPF.ps1
index fd94e32..32aac4c 100644
--- a/CreateRelease.WPF.ps1
+++ b/CreateRelease.WPF.ps1
@@ -30,7 +30,7 @@ class FixedEncoder : System.Text.UTF8Encoding {
### Configuration ###
####################################
$enableMSI = $true
-$buildOnly = $null
+#$buildOnly = $null
#$buildOnly = "win-x64"
# Profilling
$stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch
diff --git a/PrusaSlicer/printer/AnyCubic Photon Zero.ini b/PrusaSlicer/printer/AnyCubic Photon Zero.ini
index 4a82e05..7b64510 100644
--- a/PrusaSlicer/printer/AnyCubic Photon Zero.ini
+++ b/PrusaSlicer/printer/AnyCubic Photon Zero.ini
@@ -1,12 +1,12 @@
-# generated by PrusaSlicer 2.3.0+win64 on 2021-01-13 at 02:39:53 UTC
+# generated by PrusaSlicer 2.3.0+win64 on 2021-03-17 at 02:45:57 UTC
absolute_correction = 0
area_fill = 50
bed_custom_model =
bed_custom_texture =
-bed_shape = 0x0,55.4x0,55.4x98.6,0x98.6
+bed_shape = 0x0,55.4x0,55.4x98.63,0x98.63
default_sla_material_profile = Prusa Orange Tough 0.05
default_sla_print_profile = 0.05 Normal
-display_height = 98.6
+display_height = 98.63
display_mirror_x = 1
display_mirror_y = 0
display_orientation = landscape
diff --git a/UVtools.Core/Extensions/PathExtensions.cs b/UVtools.Core/Extensions/PathExtensions.cs
index 73937bb..e40290b 100644
--- a/UVtools.Core/Extensions/PathExtensions.cs
+++ b/UVtools.Core/Extensions/PathExtensions.cs
@@ -21,14 +21,19 @@ namespace UVtools.Core.Extensions
return splitPath.Length == 0 ? string.Empty : splitPath[0];
}
- public static string GetFileNameStripExtensions(string path, List<string> extensions)
+ public static string GetFileNameStripExtensions(string path, List<string> extensions, out string strippedExtension)
{
+ strippedExtension = string.Empty;
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);
+ if (path.EndsWith(dotExtension))
+ {
+ strippedExtension = extension;
+ return path.Remove(path.Length - dotExtension.Length);
+ }
}
return path;
diff --git a/UVtools.Core/Extensions/SizeExtensions.cs b/UVtools.Core/Extensions/SizeExtensions.cs
index d87a3bd..f170e12 100644
--- a/UVtools.Core/Extensions/SizeExtensions.cs
+++ b/UVtools.Core/Extensions/SizeExtensions.cs
@@ -46,6 +46,13 @@ namespace UVtools.Core.Extensions
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);
+ /// <summary>
+ /// Exchange width with height
+ /// </summary>
+ /// <param name="size"></param>
+ /// <returns></returns>
+ public static Size Invert(this Size size) => new(size.Height, size.Width);
+
public static int Area(this Rectangle rect)
{
return rect.Width * rect.Height;
@@ -61,6 +68,7 @@ namespace UVtools.Core.Extensions
return Math.Max(size.Width, size.Height);
}
+
public static float Area(this RectangleF rect, int round = -1)
{
return round >= 0 ? (float) Math.Round(rect.Width * rect.Height, round) : rect.Width * rect.Height;
diff --git a/UVtools.Core/Extensions/TimeExtensions.cs b/UVtools.Core/Extensions/TimeExtensions.cs
new file mode 100644
index 0000000..6b85322
--- /dev/null
+++ b/UVtools.Core/Extensions/TimeExtensions.cs
@@ -0,0 +1,39 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+
+using System;
+
+namespace UVtools.Core.Extensions
+{
+ public static class TimeExtensions
+ {
+ /// <summary>
+ /// Converts seconds to milliseconds
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="rounding"></param>
+ /// <returns></returns>
+ public static float SecondsToMilliseconds(float value, byte rounding = 2) => (float)Math.Round(value * 1000f, rounding);
+
+ /// <summary>
+ /// Converts seconds to milliseconds
+ /// </summary>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ public static uint SecondsToMillisecondsUint(float value) => (uint)value * 1000;
+
+ /// <summary>
+ /// Converts milliseconds to seconds
+ /// </summary>
+ /// <param name="value"></param>
+ /// <param name="rounding"></param>
+ /// <returns></returns>
+ public static float MillisecondsToSeconds(float value, byte rounding = 2) => (float)Math.Round(value / 1000f, rounding);
+ }
+}
diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs
index 6c752c5..d620337 100644
--- a/UVtools.Core/FileFormats/CWSFile.cs
+++ b/UVtools.Core/FileFormats/CWSFile.cs
@@ -22,6 +22,7 @@ using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
using UVtools.Core.Extensions;
+using UVtools.Core.GCode;
using UVtools.Core.Operations;
namespace UVtools.Core.FileFormats
@@ -313,11 +314,13 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.BottomExposureSeconds,
PrintParameterModifier.ExposureSeconds,
-
- PrintParameterModifier.BottomLiftSpeed,
+ PrintParameterModifier.BottomLiftHeight,
PrintParameterModifier.LiftHeight,
- PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.BottomLiftSpeed,
PrintParameterModifier.LiftSpeed,
+ PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.BottomLightOffDelay,
+ PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLightPWM,
PrintParameterModifier.LightPWM,
@@ -328,6 +331,7 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.LightOffDelay,
PrintParameterModifier.LightPWM,
};
@@ -381,12 +385,8 @@ namespace UVtools.Core.FileFormats
public override float MaxPrintHeight
{
- get => OutputSettings.PlatformZSize;
- set
- {
- OutputSettings.PlatformZSize = (float) Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ get => OutputSettings.PlatformZSize > 0 ? OutputSettings.PlatformZSize : base.MaxPrintHeight;
+ set => base.MaxPrintHeight = OutputSettings.PlatformZSize = (float)Math.Round(value, 2);
}
public override bool MirrorDisplay
@@ -416,121 +416,110 @@ namespace UVtools.Core.FileFormats
get => SliceSettings.Thickness > 0 ? SliceSettings.Thickness : OutputSettings.LayerThickness;
set
{
- OutputSettings.LayerThickness = SliceSettings.Thickness = (float)Math.Round(value, 2);
+ OutputSettings.LayerThickness = SliceSettings.Thickness = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
- set
- {
- OutputSettings.LayersNum = LayerCount;
- SliceSettings.LayersNum = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- }
+ get => base.LayerCount;
+ set => base.LayerCount = OutputSettings.LayersNum = SliceSettings.LayersNum = base.LayerCount;
}
public override ushort BottomLayerCount
{
get => SliceSettings.HeadLayersNum;
- set
- {
- SliceSettings.HeadLayersNum = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLayerCount = SliceSettings.HeadLayersNum = value;
}
public override float BottomExposureTime
{
- get => (float) Math.Round(SliceSettings.HeadLayersExpoMs / 1000f, 2);
+ get => TimeExtensions.MillisecondsToSeconds(OutputSettings.BottomLayersTime);
set
{
OutputSettings.BottomLayersTime =
- SliceSettings.HeadLayersExpoMs = (uint) (value * 1000f);
- RaisePropertyChanged();
+ SliceSettings.HeadLayersExpoMs = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.BottomExposureTime = value;
}
}
public override float ExposureTime
{
- get => (float) Math.Round(SliceSettings.LayersExpoMs / 1000f, 2);
+ get => TimeExtensions.MillisecondsToSeconds(OutputSettings.LayerTime);
set
{
OutputSettings.LayerTime =
- SliceSettings.LayersExpoMs = (uint) (value * 1000f);
- RaisePropertyChanged();
+ SliceSettings.LayersExpoMs = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.ExposureTime = value;
}
}
- public override float BottomLiftSpeed
+ public override float LiftHeight
{
- get => OutputSettings.ZBottomLiftFeedRate;
- set
- {
- OutputSettings.ZBottomLiftFeedRate = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ get => OutputSettings.LiftDistance;
+ set => base.LiftHeight = OutputSettings.LiftDistance = SliceSettings.LiftDistance = (float)Math.Round(value, 2);
}
- public override float LiftHeight
+ public override float BottomLiftSpeed
{
- get => SliceSettings.LiftDistance;
- set
- {
- OutputSettings.LiftDistance =
- SliceSettings.LiftDistance = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ get => OutputSettings.ZBottomLiftFeedRate;
+ set => base.BottomLiftSpeed = OutputSettings.ZBottomLiftFeedRate = (float)Math.Round(value, 2);
}
+
public override float LiftSpeed
{
- get => SliceSettings.LiftDownSpeed;
- set
- {
+ get => OutputSettings.ZLiftFeedRate;
+ set =>
+ base.LiftSpeed =
OutputSettings.ZLiftFeedRate =
- SliceSettings.LiftUpSpeed =
- SliceSettings.LiftDownSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ SliceSettings.LiftUpSpeed = (float)Math.Round(value, 2);
}
public override float RetractSpeed
{
get => OutputSettings.ZLiftRetractRate;
+ set => base.RetractSpeed = OutputSettings.ZLiftRetractRate = SliceSettings.LiftDownSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float LightOffDelay
+ {
+ get => TimeExtensions.MillisecondsToSeconds(SliceSettings.WaitBeforeExpoMs);
set
{
- OutputSettings.ZLiftRetractRate = (float)Math.Round(value, 2);
- RaisePropertyChanged();
+ SliceSettings.WaitBeforeExpoMs = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.LightOffDelay = value;
}
}
public override byte BottomLightPWM
{
get => OutputSettings.BottomLightPWM;
- set
- {
- OutputSettings.BottomLightPWM = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLightPWM = OutputSettings.BottomLightPWM = value;
}
public override byte LightPWM
{
get => OutputSettings.LightPWM;
- set
- {
- OutputSettings.LightPWM = value;
- RaisePropertyChanged();
- }
+ set => base.LightPWM = OutputSettings.LightPWM = value;
}
public override object[] Configs => Printer == PrinterType.Wanhao ? new object[] { SliceBuildConfig, OutputSettings } : new object[] { SliceSettings, OutputSettings};
#endregion
+ public CWSFile()
+ {
+ GCode.UseComments = true;
+ GCode.GCodePositioningType = GCodeBuilder.GCodePositioningTypes.Partial;
+ GCode.GCodeSpeedUnit = GCodeBuilder.GCodeSpeedUnits.MillimetersPerMinute;
+ GCode.GCodeTimeUnit = GCodeBuilder.GCodeTimeUnits.Milliseconds;
+ GCode.GCodeShowImageType = GCodeBuilder.GCodeShowImageTypes.LayerIndexZero;
+ GCode.CommandShowImageM6054.Reset(";<Slice>", "{0}");
+ GCode.CommandWaitG4.Reset(";<Delay>", "{0}");
+ }
+
#region Methods
protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
@@ -547,246 +536,303 @@ namespace UVtools.Core.FileFormats
}
}
- var filename = Path.GetFileName(fileFullPath);
- filename = filename.Substring(0, filename.IndexOf('.'));
-
- using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create))
+ if (Printer == PrinterType.BeneMono)
{
- if (Printer == PrinterType.Wanhao)
+ if (ResolutionX % 3 != 0)
{
- var manifest = new CWSManifest
- {
- GCode = {Name = $"{filename}.gcode"},
- Slices = new CWSManifest.Slice[LayerCount],
- SliceProfile = {Name = $"{filename}.slicing"}
- };
+ throw new InvalidOperationException($"Resolution width of {ResolutionX}px is invalid. Width must be in multiples of 3.\n" +
+ $"Fix your printer slicing settings with the correct with.");
+ }
+ }
- for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
- manifest.Slices[layerIndex] = new CWSManifest.Slice(this[layerIndex].FormatFileName(filename));
- }
+ var filename = Path.GetFileNameWithoutExtension(fileFullPath);
+ if (fileFullPath.EndsWith(TemporaryFileAppend))
+ {
+ filename = Path.GetFileNameWithoutExtension(filename);
+ }
- XmlSerializer serializer = new XmlSerializer(manifest.GetType());
- XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
- ns.Add("", "");
- var entry = outputFile.CreateEntry(CWSManifest.FileName);
- using (var stream = entry.Open())
- {
- serializer.Serialize(stream, manifest, ns);
- }
+ if (char.IsDigit(filename[^1]))
+ {
+ throw new InvalidOperationException($"Filename for this format should not end with a digit: {filename}");
+ }
- serializer = new XmlSerializer(SliceBuildConfig.GetType());
- entry = outputFile.CreateEntry($"{filename}.slicing");
- using (var stream = entry.Open())
- {
- serializer.Serialize(stream, SliceBuildConfig, ns);
- }
- }
- else
+ using ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create);
+ if (Printer == PrinterType.Wanhao)
+ {
+ var manifest = new CWSManifest
{
- string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits";
- var entry = outputFile.CreateEntry("slice.conf");
+ GCode = {Name = $"{filename}.gcode"},
+ Slices = new CWSManifest.Slice[LayerCount],
+ SliceProfile = {Name = $"{filename}.slicing"}
+ };
- using (TextWriter tw = new StreamWriter(entry.Open()))
- {
+ for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ {
+ manifest.Slices[layerIndex] = new CWSManifest.Slice(this[layerIndex].FormatFileName(filename));
+ }
- tw.WriteLine($"# {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}");
- tw.WriteLine("# conf version 1.0");
- tw.WriteLine("");
+ XmlSerializer serializer = new XmlSerializer(manifest.GetType());
+ XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
+ ns.Add("", "");
+ var entry = outputFile.CreateEntry(CWSManifest.FileName);
+ using (var stream = entry.Open())
+ {
+ serializer.Serialize(stream, manifest, ns);
+ }
- foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
- {
- var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
- if (ReferenceEquals(displayNameAttribute, null)) continue;
- tw.WriteLine($"{displayNameAttribute.DisplayName.PadRight(24)}= {propertyInfo.GetValue(SliceSettings)}");
- }
- }
+ serializer = new XmlSerializer(SliceBuildConfig.GetType());
+ entry = outputFile.CreateEntry($"{filename}.slicing");
+ using (var stream = entry.Open())
+ {
+ serializer.Serialize(stream, SliceBuildConfig, ns);
}
-
+ }
+ else
+ {
+ string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits";
+ var entry = outputFile.CreateEntry("slice.conf");
+ using TextWriter tw = new StreamWriter(entry.Open());
+ tw.WriteLine($"# {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}");
+ tw.WriteLine("# conf version 1.0");
+ tw.WriteLine("");
- //for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- Parallel.For(0, LayerCount,
- new ParallelOptions{MaxDegreeOfParallelism = Printer == PrinterType.BeneMono ? 1 : 1 },
- layerIndex =>
+ foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
+ if (ReferenceEquals(displayNameAttribute, null)) continue;
+ tw.WriteLine($"{displayNameAttribute.DisplayName.PadRight(24)}= {propertyInfo.GetValue(SliceSettings)}");
+ }
+ }
+
+ if (Printer == PrinterType.BeneMono)
+ {
+ Parallel.For(0, LayerCount,
+ //new ParallelOptions { MaxDegreeOfParallelism = Printer == PrinterType.BeneMono ? 1 : 1 },
+ layerIndex =>
{
if (progress.Token.IsCancellationRequested) return;
- Layer layer = this[layerIndex];
+ var layer = this[layerIndex];
var layerImagePath = layer.FormatFileName(filename);
- if (Printer == PrinterType.BeneMono)
+ using (var mat = layer.LayerMat)
+ using (var matEncode = new Mat(mat.Height, mat.Step / 3, DepthType.Cv8U, 3))
{
- using (var mat = layer.LayerMat)
- using (var matEncode = new Mat(mat.Height, mat.Step / 3, DepthType.Cv8U, 3))
+ var span = mat.GetPixelSpan<byte>();
+ var spanEncode = matEncode.GetPixelSpan<byte>();
+ for (int i = 0; i < span.Length; i++)
{
- var span = mat.GetPixelSpan<byte>();
- var spanEncode = matEncode.GetPixelSpan<byte>();
- for (int i = 0; i < span.Length; i++)
- {
- spanEncode[i] = span[i];
- }
-
- using (VectorOfByte vec = new VectorOfByte())
- {
- CvInvoke.Imencode(".png", matEncode, vec);
- lock (progress.Mutex)
- {
- outputFile.PutFileContent(layerImagePath, vec.ToArray(), ZipArchiveMode.Create);
- }
- }
+ spanEncode[i] = span[i];
}
- }
- else
- {
- outputFile.PutFileContent(layerImagePath, layer.CompressedBytes, ZipArchiveMode.Create);
- }
- lock (progress.Mutex)
- {
- progress++;
+ using VectorOfByte vec = new();
+ CvInvoke.Imencode(".png", matEncode, vec);
+ lock (progress.Mutex)
+ {
+ outputFile.PutFileContent(layerImagePath, vec.ToArray(), ZipArchiveMode.Create);
+ progress++;
+ }
}
});
-
- RebuildGCode();
- outputFile.PutFileContent($"{filename}.gcode", GCode.ToString(), ZipArchiveMode.Create);
}
+ else
+ {
+ for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+ var layer = this[layerIndex];
+ var layerImagePath = layer.FormatFileName(filename);
+ outputFile.PutFileContent(layerImagePath, layer.CompressedBytes, ZipArchiveMode.Create);
+ progress++;
+ }
+ }
+
+ RebuildGCode();
+ outputFile.PutFileContent($"{filename}.gcode", GCode.ToString(), ZipArchiveMode.Create);
}
protected override void DecodeInternally(string fileFullPath, OperationProgress progress)
{
- using (var inputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Read))
+ using var inputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Read);
+ var entry = inputFile.GetEntry("manifest.xml");
+ if (entry is not null) // Wanhao
{
- var entry = inputFile.GetEntry("manifest.xml");
- if (entry is not null) // Wanhao
+ //DecodeXML(fileFullPath, inputFile, progress);
+ Printer = PrinterType.Wanhao;
+
+ try
{
- //DecodeXML(fileFullPath, inputFile, progress);
- Printer = PrinterType.Wanhao;
+ var serializer = new XmlSerializer(typeof(CWSManifest));
+ using var stream = entry.Open();
+ var manifest = (CWSManifest)serializer.Deserialize(stream);
+ OutputSettings.LayersNum = (uint) manifest.Slices.Length;
+ }
+ catch (Exception e)
+ {
+ Clear();
+ throw new FileLoadException($"Unable to deserialize '{entry.Name}'\n{e}", fileFullPath);
+ }
+
+ entry = inputFile.Entries.FirstOrDefault(e => e.Name.EndsWith(".slicing"));
+
+ if (entry is not null)
+ {
+ //Clear();
+ //throw new FileLoadException(".slicing file not found, unable to proceed.", fileFullPath);
try
{
- var serializer = new XmlSerializer(typeof(CWSManifest));
- using (var stream = entry.Open())
- {
- var manifest = (CWSManifest)serializer.Deserialize(stream);
- OutputSettings.LayersNum = (uint) manifest.Slices.Length;
- }
+ var serializer = new XmlSerializer(typeof(CWSSliceBuildConfig));
+ using var stream = entry.Open();
+ SliceBuildConfig = (CWSSliceBuildConfig)serializer.Deserialize(stream);
}
catch (Exception e)
{
Clear();
throw new FileLoadException($"Unable to deserialize '{entry.Name}'\n{e}", fileFullPath);
}
-
+ }
+ }
+ else // Novamaker
+ {
+ entry = inputFile.GetEntry("slice.conf");
+ if (entry is null)
+ {
+ Clear();
+ throw new FileLoadException("slice.conf not found", fileFullPath);
+ }
+
+ using TextReader tr = new StreamReader(entry.Open());
+ string line;
+ while ((line = tr.ReadLine()) != null)
+ {
+ line = line.Replace("# ", string.Empty);
+ if (string.IsNullOrEmpty(line)) continue;
+ //if(line[0] == '#') continue;
- entry = inputFile.Entries.FirstOrDefault(e => e.Name.EndsWith(".slicing"));
+ var splitLine = line.Split('=');
+ if (splitLine.Length < 2) continue;
- if (!(entry is null))
+ foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
- //Clear();
- //throw new FileLoadException(".slicing file not found, unable to proceed.", fileFullPath);
- try
- {
- var serializer = new XmlSerializer(typeof(CWSSliceBuildConfig));
- using (var stream = entry.Open())
- {
- SliceBuildConfig = (CWSSliceBuildConfig)serializer.Deserialize(stream);
- }
- }
- catch (Exception e)
- {
- Clear();
- throw new FileLoadException($"Unable to deserialize '{entry.Name}'\n{e}", fileFullPath);
- }
+ var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
+ if (displayNameAttribute is null) continue;
+ if (!splitLine[0].Trim().Equals(displayNameAttribute.DisplayName)) continue;
+ Helpers.SetPropertyValue(propertyInfo, SliceSettings, splitLine[1].Trim());
}
}
- else // Novamaker
+ tr.Close();
+ }
+
+ entry = inputFile.Entries.FirstOrDefault(e => e.Name.EndsWith(".gcode"));
+ if (entry is null)
+ {
+ Clear();
+ throw new FileLoadException("Unable to find .gcode file",
+ fileFullPath);
+ }
+
+ using (TextReader tr = new StreamReader(entry.Open()))
+ {
+ string line;
+ GCode.Clear();
+ while ((line = tr.ReadLine()) != null)
{
- entry = inputFile.GetEntry("slice.conf");
- if (ReferenceEquals(entry, null))
+ GCode.AppendLine(line);
+ if (string.IsNullOrEmpty(line)) continue;
+
+ if (line[0] != ';')
{
- Clear();
- throw new FileLoadException("slice.conf not found", fileFullPath);
+ continue;
}
- using (TextReader tr = new StreamReader(entry.Open()))
+ var splitLine = line.Split('=');
+ if (splitLine.Length < 2) continue;
+
+ foreach (var propertyInfo in OutputSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
- string line;
- while ((line = tr.ReadLine()) != null)
+ var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
+ if (displayNameAttribute is null) continue;
+ if (!splitLine[0].Trim(' ', ';', '(').Equals(displayNameAttribute.DisplayName)) continue;
+ try
{
- line = line.Replace("# ", string.Empty);
- if (string.IsNullOrEmpty(line)) continue;
- //if(line[0] == '#') continue;
-
- var splitLine = line.Split('=');
- if (splitLine.Length < 2) continue;
-
- foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
- {
- var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
- if (ReferenceEquals(displayNameAttribute, null)) continue;
- if (!splitLine[0].Trim().Equals(displayNameAttribute.DisplayName)) continue;
- Helpers.SetPropertyValue(propertyInfo, SliceSettings, splitLine[1].Trim());
- }
+ Helpers.SetPropertyValue(propertyInfo, OutputSettings, splitLine[1].Trim(' ', ')', 'p', 'x', 'm', 'n', 's', '/'));
+ }
+ catch
+ {
+ // ignored
}
- tr.Close();
+
+ //Debug.WriteLine(splitLine[1].Trim(' ', ')', 'm', 'n', '/'));
}
}
+ tr.Close();
+ }
- entry = inputFile.Entries.FirstOrDefault(e => e.Name.EndsWith(".gcode"));
- if (entry is null)
- {
- Clear();
- throw new FileLoadException("Unable to find .gcode file",
- fileFullPath);
- }
+ LayerManager = new LayerManager(OutputSettings.LayersNum, this);
+
+ progress.ItemCount = OutputSettings.LayersNum;
- using (TextReader tr = new StreamReader(entry.Open()))
+ if(LayerCount > 0)
+ {
+ var inputFilename = Path.GetFileNameWithoutExtension(fileFullPath);
+ foreach (var pngEntry in inputFile.Entries)
{
- string line;
- GCode = new StringBuilder();
- while ((line = tr.ReadLine()) != null)
+ if (!pngEntry.Name.EndsWith(".png")) continue;
+ var filename = Path.GetFileNameWithoutExtension(pngEntry.Name).Replace(inputFilename, string.Empty, StringComparison.Ordinal);
+
+ var layerIndexStr = string.Empty;
+ var layerStr = filename;
+ for (int i = layerStr.Length - 1; i >= 0; i--)
{
- GCode.AppendLine(line);
- if (string.IsNullOrEmpty(line)) continue;
+ if (layerStr[i] < '0' || layerStr[i] > '9') break;
+ layerIndexStr = $"{layerStr[i]}{layerIndexStr}";
+ }
- if (line[0] != ';')
- {
- continue;
- }
+ if (string.IsNullOrEmpty(layerIndexStr)) return;
+ if (!uint.TryParse(layerIndexStr, out var layerIndex)) continue;
+ using var stream = pngEntry.Open();
+ this[layerIndex] = new Layer(layerIndex, stream, LayerManager);
+ }
- var splitLine = line.Split('=');
- if (splitLine.Length < 2) continue;
+ GCode.ParseLayersFromGCode(this);
- foreach (var propertyInfo in OutputSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ var firstLayer = FirstLayer;
+ if (firstLayer is not null)
+ {
+ if (Printer == PrinterType.Unknown)
+ {
+ using Mat mat = new ();
+ CvInvoke.Imdecode(firstLayer.CompressedBytes, ImreadModes.AnyColor, mat);
+ Printer = mat.NumberOfChannels == 1 ? PrinterType.Elfin : PrinterType.BeneMono;
+ }
+
+ if (Printer == PrinterType.BeneMono)
+ {
+ Parallel.For(0, LayerCount, layerIndex =>
{
- var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
- if (ReferenceEquals(displayNameAttribute, null)) continue;
- if (!splitLine[0].Trim(' ', ';', '(').Equals(displayNameAttribute.DisplayName)) continue;
- try
- {
- Helpers.SetPropertyValue(propertyInfo, OutputSettings, splitLine[1].Trim(' ', ')', 'p', 'x', 'm', 'n', 's', '/'));
- }
- catch
+ if (progress.Token.IsCancellationRequested) return;
+ var layer = this[layerIndex];
+ using Mat mat = new();
+ CvInvoke.Imdecode(layer.CompressedBytes, ImreadModes.Color, mat);
+ using Mat matDecode = new(mat.Height, mat.Step, DepthType.Cv8U, 1);
+ var span = mat.GetPixelSpan<byte>();
+ var spanDecode = matDecode.GetPixelSpan<byte>();
+ for (int i = 0; i < span.Length; i++)
{
- // ignored
+ spanDecode[i] = span[i];
}
-
- //Debug.WriteLine(splitLine[1].Trim(' ', ')', 'm', 'n', '/'));
- }
+
+ layer.LayerMat = matDecode;
+ layer.IsModified = false;
+ progress.LockAndIncrement();
+ });
}
- tr.Close();
}
+ }
- LayerManager = new LayerManager(OutputSettings.LayersNum, this);
-
- progress.ItemCount = OutputSettings.LayersNum;
-
- var gcode = GCode.ToString();
- //float currentHeight = 0;
-
-
- inputFile.Entries.AsParallel().ForAllInApproximateOrder(zipArchiveEntry =>
+ /*inputFile.Entries.AsParallel().ForAllInApproximateOrder(zipArchiveEntry =>
//foreach (var zipArchiveEntry in inputFile.Entries)
{
if (!zipArchiveEntry.Name.EndsWith(".png") || progress.Token.IsCancellationRequested) return;
@@ -828,7 +874,7 @@ namespace UVtools.Core.FileFormats
//var currPos = Regex.Match(stripGcode, "G1 Z([+-]?([0-9]*[.])?[0-9]+)", RegexOptions.IgnoreCase);
var moveG1Regex = Regex.Match(stripGcode, @"G1 Z([+-]?([0-9]*[.])?[0-9]+) F(\d+)", RegexOptions.IgnoreCase);
var pwmM106Regex = Regex.Match(stripGcode, @"M106 S(\d+)", RegexOptions.IgnoreCase);
- var exposureTimeRegex = Regex.Match(stripGcode, ";<Delay> (\\d+)", RegexOptions.IgnoreCase);
+ var waitRegex = Regex.Match(stripGcode, ";<Delay> (\\d+)", RegexOptions.IgnoreCase);
if (moveG1Regex.Success)
{
@@ -852,40 +898,23 @@ namespace UVtools.Core.FileFormats
OutputSettings.BottomLightPWM = pwm;
}
- if (exposureTimeRegex.Success)
- {
- exposureTime = (float)Math.Round(float.Parse(exposureTimeRegex.Groups[1].Value, CultureInfo.InvariantCulture) / 1000f, 2);
- }
-
- /*var pwm = Regex.Match(stripGcode, "M106 S(\\d+)", RegexOptions.IgnoreCase);
- if (layerIndex < InitialLayerCount)
- {
- OutputSettings.BottomLayerLightPWM = byte.Parse(pwm.Groups[1].Value);
- }
- else
+ if (waitRegex.Success)
{
- OutputSettings.LayerLightPWM = byte.Parse(pwm.Groups[1].Value);
- }*/
-
- /*if (currPos.Success)
- {
- var nextMatch = currPos.NextMatch();
- if (nextMatch.Success)
+ exposureTime = (float)Math.Round(float.Parse(waitRegex.Groups[1].Value, CultureInfo.InvariantCulture) / 1000f, 2);
+ waitRegex = waitRegex.NextMatch();
+ if (waitRegex.Success)
{
- currentHeight =
- (float) Math.Round(
- currentHeight + float.Parse(currPos.Groups[1].Value) +
- float.Parse(currPos.NextMatch().Groups[1].Value), 2);
+ lightOffDelay = (float)Math.Round(float.Parse(waitRegex.Groups[1].Value, CultureInfo.InvariantCulture) / 1000f, 2);
}
- else
+ else // Only one match, meaning light off delay is not present
{
- currentHeight = (float) Math.Round(currentHeight + float.Parse(currPos.Groups[1].Value), 2);
+ lightOffDelay = GetInitialLayerValueOrNormal(layerIndex, BottomLightOffDelay, LightOffDelay);
}
- }*/
+ }
byte[] buffer;
- lock (progress)
+ lock (progress.Mutex)
{
using (var stream = zipArchiveEntry.Open())
{
@@ -947,17 +976,25 @@ namespace UVtools.Core.FileFormats
progress++;
});
- }
+ // Fix missing values from configuration
+ if (LayerCount > 0 && this[0] is not null)
+ {
+ SuppressRebuildProperties = true;
+ BottomLiftHeight = this[0].LiftHeight;
+ BottomLightOffDelay = this[0].LightOffDelay;
+ SuppressRebuildProperties = false;
+ }*/
LayerManager.GetBoundingRectangle(progress);
}
public override void RebuildGCode()
{
- string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits";
- GCode = new StringBuilder();
- GCode.AppendLine($"; {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}");
- GCode.AppendLine(";(****Build and Slicing Parameters ****)");
+ //string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits";
+ //GCode.Clear();
+ //GCode.AppendLine($"; {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}");
+ StringBuilder sb = new();
+ sb.AppendLine(";(****Build and Slicing Parameters ****)");
foreach (var propertyInfo in OutputSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
@@ -965,15 +1002,16 @@ namespace UVtools.Core.FileFormats
if (ReferenceEquals(displayNameAttribute, null)) continue;
if (propertyInfo.Name.Equals(nameof(OutputSettings.LayersNum)))
{
- GCode.AppendLine($";{displayNameAttribute.DisplayName.PadRight(24)} = {propertyInfo.GetValue(OutputSettings)}");
+ sb.AppendLine($";{displayNameAttribute.DisplayName} = {propertyInfo.GetValue(OutputSettings)}");
}
else
{
- GCode.AppendLine($";({displayNameAttribute.DisplayName.PadRight(24)} = {propertyInfo.GetValue(OutputSettings)})");
+ sb.AppendLine($";({displayNameAttribute.DisplayName} = {propertyInfo.GetValue(OutputSettings)})");
}
-
}
- GCode.AppendLine();
+
+ GCode.RebuildGCode(this, sb);
+ /*GCode.AppendLine();
GCode.AppendFormat(Printer == PrinterType.Wanhao ? WanhaoStartGCode : GCodeStart, Environment.NewLine);
@@ -1006,7 +1044,7 @@ namespace UVtools.Core.FileFormats
{
if (layer.LiftHeight > 0)
{
- float downPos = (float) Math.Round(layer.LiftHeight - layer.PositionZ + lastZPosition, 2);
+ float downPos = Layer.RoundHeight(layer.LiftHeight - layer.PositionZ + lastZPosition);
if (Printer == PrinterType.Wanhao)
{
GCode.AppendLine($";********** Lift Sequence {layerIndex} ********");
@@ -1026,14 +1064,14 @@ namespace UVtools.Core.FileFormats
}
else
{
- GCode.AppendLine($"G1 Z{Math.Round(layer.PositionZ - lastZPosition, 2)} F{layer.LiftSpeed}");
+ GCode.AppendLine($"G1 Z{Layer.RoundHeight(layer.PositionZ - lastZPosition)} F{layer.LiftSpeed}");
}
}
if (Printer != PrinterType.Wanhao)
{
// delay = max(extra['wait'], 500) + int(((int(lift)/(extra['lift_feed']/60)) + (int(lift)/(extra['lift_retract']/60)))*1000)
- uint extraDelay =
+ /*uint extraDelay =
OperationCalculator.LightOffDelayC.CalculateMilliseconds(layer.LiftHeight, layer.LiftSpeed,
layer.RetractSpeed);
if (layerIndex < BottomLayerCount)
@@ -1045,14 +1083,14 @@ namespace UVtools.Core.FileFormats
extraDelay += Math.Max(OutputSettings.BlankingLayerTime, 500);
}
- GCode.AppendLine($"{GCodeKeywordDelay} {extraDelay}");
+ GCode.AppendLine($"{GCodeKeywordDelay} {layer.LightOffDelay * 1000}");
GCode.AppendLine();
}
lastZPosition = layer.PositionZ;
}
- GCode.AppendFormat(Printer == PrinterType.Wanhao ? WanhaoGCodeEnd : GCodeEnd, Environment.NewLine, SliceSettings.LiftWhenFinished);
+ GCode.AppendFormat(Printer == PrinterType.Wanhao ? WanhaoGCodeEnd : GCodeEnd, Environment.NewLine, SliceSettings.LiftWhenFinished);*/
}
public override void SaveAs(string filePath = null, OperationProgress progress = null)
diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs
index de24c76..de0a898 100644
--- a/UVtools.Core/FileFormats/ChituboxFile.cs
+++ b/UVtools.Core/FileFormats/ChituboxFile.cs
@@ -983,13 +983,13 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.BottomExposureSeconds,
PrintParameterModifier.ExposureSeconds,
- PrintParameterModifier.BottomLightOffDelay,
- PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLiftHeight,
PrintParameterModifier.BottomLiftSpeed,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.BottomLightOffDelay,
+ PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLightPWM,
PrintParameterModifier.LightPWM,
@@ -1011,7 +1011,7 @@ namespace UVtools.Core.FileFormats
};
}
- if (HeaderSettings.Version >= 2)
+ if (HeaderSettings.Version <= 2)
{
return new[]
{
@@ -1073,11 +1073,7 @@ namespace UVtools.Core.FileFormats
public override float MaxPrintHeight
{
get => HeaderSettings.BedSizeZ > 0 ? HeaderSettings.BedSizeZ : base.MaxPrintHeight;
- set
- {
- HeaderSettings.BedSizeZ = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.MaxPrintHeight = HeaderSettings.BedSizeZ = (float)Math.Round(value, 2);
}
public override bool MirrorDisplay
@@ -1113,7 +1109,7 @@ namespace UVtools.Core.FileFormats
get => HeaderSettings.LayerHeightMilimeter;
set
{
- HeaderSettings.LayerHeightMilimeter = (float)Math.Round(value, 2);
+ HeaderSettings.LayerHeightMilimeter = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
@@ -1121,141 +1117,85 @@ namespace UVtools.Core.FileFormats
public override float PrintHeight
{
get => base.PrintHeight;
- set
- {
- HeaderSettings.TotalHeightMilimeter = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.PrintHeight = HeaderSettings.TotalHeightMilimeter = base.PrintHeight;
}
public override uint LayerCount
{
- set
- {
- HeaderSettings.LayerCount = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- }
+ get => base.LayerCount;
+ set => base.LayerCount = HeaderSettings.LayerCount = base.LayerCount;
}
public override ushort BottomLayerCount
{
get => (ushort) HeaderSettings.BottomLayersCount;
- set
- {
- HeaderSettings.BottomLayersCount = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLayerCount = (ushort) (HeaderSettings.BottomLayersCount = value);
}
public override float BottomExposureTime
{
get => HeaderSettings.BottomExposureSeconds;
- set
- {
- HeaderSettings.BottomExposureSeconds = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomExposureTime = HeaderSettings.BottomExposureSeconds = (float) Math.Round(value, 2);
}
public override float ExposureTime
{
get => HeaderSettings.LayerExposureSeconds;
- set
- {
- HeaderSettings.LayerExposureSeconds = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.ExposureTime = HeaderSettings.LayerExposureSeconds = (float)Math.Round(value, 2);
}
public override float BottomLightOffDelay
{
get => PrintParametersSettings.BottomLightOffDelay;
- set
- {
- PrintParametersSettings.BottomLightOffDelay = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLightOffDelay = PrintParametersSettings.BottomLightOffDelay = (float)Math.Round(value, 2);
}
public override float LightOffDelay
{
get => PrintParametersSettings.LightOffDelay;
- set
- {
- HeaderSettings.LightOffDelay = PrintParametersSettings.LightOffDelay = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LightOffDelay = HeaderSettings.LightOffDelay = PrintParametersSettings.LightOffDelay = (float)Math.Round(value, 2);
}
public override float BottomLiftHeight
{
get => PrintParametersSettings.BottomLiftHeight;
- set
- {
- PrintParametersSettings.BottomLiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftHeight = PrintParametersSettings.BottomLiftHeight = (float)Math.Round(value, 2);
}
public override float LiftHeight
{
get => PrintParametersSettings.LiftHeight;
- set
- {
- PrintParametersSettings.LiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = PrintParametersSettings.LiftHeight = (float)Math.Round(value, 2);
}
public override float BottomLiftSpeed
{
get => PrintParametersSettings.BottomLiftSpeed;
- set
- {
- PrintParametersSettings.BottomLiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftSpeed = PrintParametersSettings.BottomLiftSpeed = (float)Math.Round(value, 2);
}
public override float LiftSpeed
{
get => PrintParametersSettings.LiftSpeed;
- set
- {
- PrintParametersSettings.LiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftSpeed = PrintParametersSettings.LiftSpeed = (float)Math.Round(value, 2);
}
public override float RetractSpeed
{
get => PrintParametersSettings.RetractSpeed;
- set
- {
- PrintParametersSettings.RetractSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.RetractSpeed = PrintParametersSettings.RetractSpeed = (float)Math.Round(value, 2);
}
public override byte BottomLightPWM
{
- get => (byte) HeaderSettings.BottomLightPWM;
- set
- {
- HeaderSettings.BottomLightPWM = value;
- RaisePropertyChanged();
- }
+ get => (byte)HeaderSettings.BottomLightPWM;
+ set => base.BottomLightPWM = (byte) (HeaderSettings.BottomLightPWM = value);
}
public override byte LightPWM
{
- get => (byte) HeaderSettings.BottomLightPWM;
- set
- {
- HeaderSettings.BottomLightPWM = value;
- RaisePropertyChanged();
- }
+ get => (byte)HeaderSettings.LightPWM;
+ set => base.LightPWM = (byte) (HeaderSettings.LightPWM = value);
}
public override float PrintTime
@@ -1281,21 +1221,13 @@ namespace UVtools.Core.FileFormats
public override float MaterialGrams
{
get => PrintParametersSettings.WeightG;
- set
- {
- PrintParametersSettings.WeightG = (float)Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialGrams = PrintParametersSettings.WeightG = (float)Math.Round(value, 3);
}
public override float MaterialCost
{
get => (float) Math.Round(PrintParametersSettings.CostDollars, 3);
- set
- {
- PrintParametersSettings.CostDollars = (float) Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialCost = PrintParametersSettings.CostDollars = (float) Math.Round(value, 3);
}
public override string MachineName
@@ -1303,10 +1235,8 @@ namespace UVtools.Core.FileFormats
get => SlicerInfoSettings.MachineName;
set
{
- SlicerInfoSettings.MachineName = value;
+ base.MachineName = SlicerInfoSettings.MachineName = value;
SlicerInfoSettings.MachineNameSize = (uint) SlicerInfoSettings.MachineName.Length;
- RequireFullEncode = true;
- RaisePropertyChanged();
}
}
diff --git a/UVtools.Core/FileFormats/ChituboxZipFile.cs b/UVtools.Core/FileFormats/ChituboxZipFile.cs
index b5ce0fb..7e90511 100644
--- a/UVtools.Core/FileFormats/ChituboxZipFile.cs
+++ b/UVtools.Core/FileFormats/ChituboxZipFile.cs
@@ -9,17 +9,16 @@
using System;
using System.ComponentModel;
using System.Drawing;
-using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
-using System.Text;
-using System.Text.RegularExpressions;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
using UVtools.Core.Extensions;
+using UVtools.Core.GCode;
+using UVtools.Core.Objects;
using UVtools.Core.Operations;
namespace UVtools.Core.FileFormats
@@ -28,21 +27,7 @@ namespace UVtools.Core.FileFormats
{
#region Constants
- public const string GCodeStart = ";START_GCODE_BEGIN{0}" +
- "G21 ;Set units to be mm{0}" +
- "G90 ;Absolute Positioning{0}" +
- "M17 ;Enable motors{0}" +
- "G28 Z0 ;Home Z{0}" +
- //"G91 ;Relative Positioning{0}" +
- "M106 S0 ;Light off{0}" +
- ";START_GCODE_END{0}{0}";
-
- public const string GCodeEnd = ";END_GCODE_BEGIN{0}" +
- "M106 S0 ;Light off{0}" +
- "G1 Z{1} F25 ;Raize Z{0}" +
- "M18 ;Disable Motors{0}" +
- ";END_GCODE_END{0}";
-
+ public const string GCodeFilename = "run.gcode";
#endregion
#region Sub Classes
@@ -64,9 +49,9 @@ namespace UVtools.Core.FileFormats
[DisplayName("machineY")] public float MachineY { get; set; }
[DisplayName("machineZ")] public float MachineZ { get; set; }
[DisplayName("projectType")] public string ProjectType { get; set; } = "Normal";
- [DisplayName("normalExposureTime")] public float LayerExposureTime { get; set; } = 7; // 35s
+ [DisplayName("normalExposureTime")] public float ExposureTime { get; set; } = 7; // 35s
[DisplayName("bottomLayExposureTime")] public float BottomLayExposureTime { get; set; } = 35; // 35s
- [DisplayName("bottomLayerExposureTime")] public float BottomLayerExposureTime { get; set; } = 35; // 35s
+ [DisplayName("bottomLayerExposureTime")] public float BottomExposureTime { get; set; } = 35; // 35s
[DisplayName("normalDropSpeed")] public float RetractSpeed { get; set; } = 150; // 150 mm/m
[DisplayName("normalLayerLiftSpeed")] public float LiftSpeed { get; set; } = 60; // 60 mm/m
[DisplayName("normalLayerLiftHeight")] public float LiftHeight { get; set; } = 5; // 5 mm
@@ -77,8 +62,8 @@ namespace UVtools.Core.FileFormats
[DisplayName("totalLayer")] public uint LayerCount { get; set; }
[DisplayName("bottomLayerLiftHeight")] public float BottomLiftHeight { get; set; } = 5;
[DisplayName("bottomLayerLiftSpeed")] public float BottomLiftSpeed { get; set; } = 60;
- [DisplayName("bottomLightOffTime")] public float BottomLightOffTime { get; set; }
- [DisplayName("lightOffTime")] public float LightOffTime { get; set; }
+ [DisplayName("bottomLightOffTime")] public float BottomLightOffDelay { get; set; }
+ [DisplayName("lightOffTime")] public float LightOffDelay { get; set; }
[DisplayName("bottomPWMLight")] public byte BottomLightPWM { get; set; } = 255;
[DisplayName("PWMLight")] public byte LightPWM { get; set; } = 255;
[DisplayName("antiAliasLevel")] public byte AntiAliasing { get; set; } = 1;
@@ -100,13 +85,13 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.BottomExposureSeconds,
PrintParameterModifier.ExposureSeconds,
- PrintParameterModifier.BottomLightOffDelay,
- PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLiftHeight,
PrintParameterModifier.BottomLiftSpeed,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.BottomLightOffDelay,
+ PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLightPWM,
PrintParameterModifier.LightPWM,
@@ -114,10 +99,10 @@ namespace UVtools.Core.FileFormats
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
PrintParameterModifier.ExposureSeconds,
- PrintParameterModifier.LightOffDelay,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.LightOffDelay,
PrintParameterModifier.LightPWM,
};
@@ -168,11 +153,7 @@ namespace UVtools.Core.FileFormats
public override float MaxPrintHeight
{
get => HeaderSettings.MachineZ > 0 ? HeaderSettings.MachineZ : base.MaxPrintHeight;
- set
- {
- HeaderSettings.MachineZ = value;
- RaisePropertyChanged();
- }
+ set => base.MaxPrintHeight = HeaderSettings.MachineZ = (float)Math.Round(value, 2);
}
public override bool MirrorDisplay
@@ -201,139 +182,87 @@ namespace UVtools.Core.FileFormats
get => HeaderSettings.LayerHeight;
set
{
- HeaderSettings.LayerHeight = (float)Math.Round(value, 2);
+ HeaderSettings.LayerHeight = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
- set
- {
- HeaderSettings.LayerCount = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- }
+ get => base.LayerCount;
+ set => base.LayerCount = HeaderSettings.LayerCount = base.LayerCount;
}
public override ushort BottomLayerCount
{
get => HeaderSettings.BottomLayerCount;
- set
- {
- HeaderSettings.BottomLayerCount = HeaderSettings.BottomLayCount = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLayerCount = HeaderSettings.BottomLayerCount = HeaderSettings.BottomLayCount = value;
}
public override float BottomExposureTime
{
- get => HeaderSettings.BottomLayerExposureTime;
- set
- {
- HeaderSettings.BottomLayerExposureTime = HeaderSettings.BottomLayExposureTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ get => HeaderSettings.BottomExposureTime;
+ set => base.BottomExposureTime = HeaderSettings.BottomExposureTime = HeaderSettings.BottomLayExposureTime = (float)Math.Round(value, 2);
}
public override float ExposureTime
{
- get => HeaderSettings.LayerExposureTime;
- set
- {
- HeaderSettings.LayerExposureTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
- }
-
- public override float BottomLightOffDelay
- {
- get => HeaderSettings.BottomLightOffTime;
- set
- {
- HeaderSettings.BottomLightOffTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
- }
-
- public override float LightOffDelay
- {
- get => HeaderSettings.LightOffTime;
- set
- {
- HeaderSettings.LightOffTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ get => HeaderSettings.ExposureTime;
+ set => base.ExposureTime = HeaderSettings.ExposureTime = (float)Math.Round(value, 2);
}
public override float BottomLiftHeight
{
get => HeaderSettings.BottomLiftHeight;
- set
- {
- HeaderSettings.BottomLiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftHeight = HeaderSettings.BottomLiftHeight = (float)Math.Round(value, 2);
}
public override float LiftHeight
{
get => HeaderSettings.LiftHeight;
- set
- {
- HeaderSettings.LiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = HeaderSettings.LiftHeight = (float)Math.Round(value, 2);
}
public override float BottomLiftSpeed
{
get => HeaderSettings.BottomLiftSpeed;
- set
- {
- HeaderSettings.BottomLiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed = (float)Math.Round(value, 2);
}
public override float LiftSpeed
{
get => HeaderSettings.LiftSpeed;
- set
- {
- HeaderSettings.LiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftSpeed = HeaderSettings.LiftSpeed = (float)Math.Round(value, 2);
}
public override float RetractSpeed
{
get => HeaderSettings.RetractSpeed;
- set
- {
- HeaderSettings.RetractSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.RetractSpeed = HeaderSettings.RetractSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomLightOffDelay
+ {
+ get => HeaderSettings.BottomLightOffDelay;
+ set => base.BottomLightOffDelay = HeaderSettings.BottomLightOffDelay = (float)Math.Round(value, 2);
+ }
+
+ public override float LightOffDelay
+ {
+ get => HeaderSettings.LightOffDelay;
+ set => base.LightOffDelay = HeaderSettings.LightOffDelay = (float)Math.Round(value, 2);
}
public override byte BottomLightPWM
{
get => HeaderSettings.BottomLightPWM;
- set
- {
- HeaderSettings.BottomLightPWM = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLightPWM = HeaderSettings.BottomLightPWM = value;
}
public override byte LightPWM
{
get => HeaderSettings.LightPWM;
- set
- {
- HeaderSettings.LightPWM = value;
- RaisePropertyChanged();
- }
+ set => base.LightPWM = HeaderSettings.LightPWM = value;
}
public override float PrintTime
@@ -389,12 +318,7 @@ namespace UVtools.Core.FileFormats
public override string MachineName
{
get => HeaderSettings.MachineType;
- set
- {
- HeaderSettings.MachineType = value;
- RequireFullEncode = true;
- RaisePropertyChanged();
- }
+ set => base.MachineName = HeaderSettings.MachineType = value;
}
public override object[] Configs => new object[] { HeaderSettings };
@@ -402,6 +326,15 @@ namespace UVtools.Core.FileFormats
public bool IsPHZZip;
#endregion
+ public ChituboxZipFile()
+ {
+ GCode.UseComments = true;
+ GCode.GCodePositioningType = GCodeBuilder.GCodePositioningTypes.Absolute;
+ GCode.GCodeSpeedUnit = GCodeBuilder.GCodeSpeedUnits.MillimetersPerMinute;
+ GCode.GCodeTimeUnit = GCodeBuilder.GCodeTimeUnits.Milliseconds;
+ GCode.GCodeShowImageType = GCodeBuilder.GCodeShowImageTypes.FilenameNonZeroPNG;
+ }
+
#region Methods
protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
@@ -437,7 +370,7 @@ namespace UVtools.Core.FileFormats
if (!IsPHZZip)
{
RebuildGCode();
- outputFile.PutFileContent("run.gcode", GCode.ToString(), ZipArchiveMode.Create);
+ outputFile.PutFileContent(GCodeFilename, GCode.ToString(), ZipArchiveMode.Create);
}
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
@@ -455,7 +388,7 @@ namespace UVtools.Core.FileFormats
{
using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read))
{
- var entry = inputFile.GetEntry("run.gcode");
+ var entry = inputFile.GetEntry(GCodeFilename);
if (entry is not null)
{
//Clear();
@@ -463,7 +396,7 @@ namespace UVtools.Core.FileFormats
using (TextReader tr = new StreamReader(entry.Open()))
{
string line;
- GCode = new StringBuilder();
+ GCode.Clear();
while ((line = tr.ReadLine()) != null)
{
GCode.AppendLine(line);
@@ -480,7 +413,7 @@ namespace UVtools.Core.FileFormats
foreach (var propertyInfo in HeaderSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
- if (ReferenceEquals(displayNameAttribute, null)) continue;
+ if (displayNameAttribute is null) continue;
if (!splitLine[0].Trim(' ', ';').Equals(displayNameAttribute.DisplayName)) continue;
Helpers.SetPropertyValue(propertyInfo, HeaderSettings, splitLine[1].Trim());
}
@@ -512,116 +445,30 @@ namespace UVtools.Core.FileFormats
var gcode = GCode?.ToString();
float lastPostZ = LayerHeight;
- for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++)
+ for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
if (progress.Token.IsCancellationRequested) break;
entry = inputFile.GetEntry($"{layerIndex+1}.png");
- if (ReferenceEquals(entry, null))
+ if (entry is null)
{
Clear();
throw new FileLoadException($"Layer {layerIndex+1} not found", fileFullPath);
}
- if (IsPHZZip) // PHZ file
- {
- this[layerIndex] = new Layer(layerIndex, entry.Open(), LayerManager);
- progress++;
- continue;;
- }
-
-
- var startStr = $";LAYER_START:{layerIndex}";
- gcode = gcode.Substring(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length);
- var stripGcode = gcode.Substring(0, gcode.IndexOf(";LAYER_END")).Trim(' ', '\n', '\r', '\t');
- //var startCurrPos = stripGcode.Remove(0, ";currPos:".Length);
-
- float posZ = lastPostZ;
- float liftHeight = GetInitialLayerValueOrNormal(layerIndex, BottomLiftHeight, LiftHeight);
- float liftSpeed = GetInitialLayerValueOrNormal(layerIndex, BottomLiftSpeed, LiftSpeed);
- float retractSpeed = RetractSpeed;
- float lightOffDelay = 0;
- byte pwm = GetInitialLayerValueOrNormal(layerIndex, BottomLightPWM, LightPWM); ;
- float exposureTime = GetInitialLayerValueOrNormal(layerIndex, BottomExposureTime, ExposureTime);
-
- //var currPosRegex = Regex.Match(stripGcode, @";currPos:([+-]?([0-9]*[.])?[0-9]+)", RegexOptions.IgnoreCase);
- var moveG0Regex = Regex.Match(stripGcode, @"G0 Z([+-]?([0-9]*[.])?[0-9]+) F(\d+)", RegexOptions.IgnoreCase);
- var waitG4Regex = Regex.Match(stripGcode, @"G4 P(\d+)", RegexOptions.IgnoreCase);
- var pwmM106Regex = Regex.Match(stripGcode, @"M106 S(\d+)", RegexOptions.IgnoreCase);
-
-
- /*if (currPosRegex.Success)
- {
- var posZRegex = currPosRegex.Groups[1].Value;
- posZ = float.Parse(posZRegex, CultureInfo.InvariantCulture);
- }
- else
- {
- posZ = GetHeightFromLayer(layerIndex);
- }*/
-
- if (moveG0Regex.Success)
- {
- float liftHeightTemp = float.Parse(moveG0Regex.Groups[1].Value, CultureInfo.InvariantCulture);
- float liftSpeedTemp = float.Parse(moveG0Regex.Groups[3].Value, CultureInfo.InvariantCulture);
- moveG0Regex = moveG0Regex.NextMatch();
- if (moveG0Regex.Success)
- {
- float retractHeight = float.Parse(moveG0Regex.Groups[1].Value, CultureInfo.InvariantCulture);
- retractSpeed = float.Parse(moveG0Regex.Groups[3].Value, CultureInfo.InvariantCulture);
- liftHeight = (float) Math.Round(liftHeightTemp - retractHeight, 2);
- liftSpeed = liftSpeedTemp;
- lastPostZ = posZ = retractHeight;
- }
- else
- {
- lastPostZ = posZ = liftHeightTemp;
- }
- }
-
- if (pwmM106Regex.Success)
- {
- pwm = byte.Parse(pwmM106Regex.Groups[1].Value);
- }
- if (layerIndex == 0)
- {
- HeaderSettings.BottomLightPWM = pwm;
- }
- /*else if(layerIndex)
- {
- HeaderSettings.LightPWM = byte.Parse(pwmM106Regex.Groups[1].Value);
- }*/
-
- if (waitG4Regex.Success)
- {
- lightOffDelay = (float) Math.Round(float.Parse(waitG4Regex.Groups[1].Value, CultureInfo.InvariantCulture) / 1000f, 2);
- waitG4Regex = waitG4Regex.NextMatch();
- if (waitG4Regex.Success)
- {
- exposureTime = (float) Math.Round(float.Parse(waitG4Regex.Groups[1].Value, CultureInfo.InvariantCulture) / 1000f, 2);
- }
- else // Only one match, meaning light off delay is not present
- {
- lightOffDelay = GetInitialLayerValueOrNormal(layerIndex, BottomLightOffDelay, LightOffDelay);
- }
- }
+ using var stream = entry.Open();
+ this[layerIndex] = new Layer(layerIndex, stream, LayerManager);
- this[layerIndex] = new Layer(layerIndex, entry.Open(), LayerManager)
- {
- PositionZ = posZ,
- ExposureTime = exposureTime,
- LiftHeight = liftHeight,
- LiftSpeed = liftSpeed,
- RetractSpeed = retractSpeed,
- LightOffDelay = lightOffDelay,
- LightPWM = pwm,
- };
progress++;
}
- if (GCode is null) // PHZ file
+ if (IsPHZZip) // PHZ file
{
LayerManager.RebuildLayersProperties();
}
+ else
+ {
+ GCode.ParseLayersFromGCode(this);
+ }
if (HeaderSettings.LayerCount > 0 && ResolutionX == 0)
{
@@ -633,7 +480,7 @@ namespace UVtools.Core.FileFormats
}
entry = inputFile.GetEntry("preview.png");
- if (!ReferenceEquals(entry, null))
+ if (entry is not null)
{
Thumbnails[0] = new Mat();
CvInvoke.Imdecode(entry.Open().ToArray(), ImreadModes.AnyColor, Thumbnails[0]);
@@ -654,65 +501,7 @@ namespace UVtools.Core.FileFormats
public override void RebuildGCode()
{
if (IsPHZZip) return;
- string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits";
- GCode = new StringBuilder();
- GCode.AppendLine($"; {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}");
-
- foreach (var propertyInfo in HeaderSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
- {
- var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
- if (displayNameAttribute is null) continue;
- GCode.AppendLine($";{displayNameAttribute.DisplayName}:{propertyInfo.GetValue(HeaderSettings)}");
- }
-
- GCode.AppendLine();
- GCode.AppendFormat(GCodeStart, Environment.NewLine);
-
- float lastZPosition = 0;
-
- for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
- var layer = this[layerIndex];
- var exposureTime = layer.ExposureTime * 1000;
- var liftHeight = layer.LiftHeight;
- var liftZHeight = Math.Round(liftHeight + layer.PositionZ, 2);
- var liftSpeed = layer.LiftSpeed;
- var retractSpeed = layer.RetractSpeed;
- var lightOffDelay = layer.LightOffDelay * 1000;
- var pwmValue = layer.LightPWM;
-
- GCode.AppendLine($";LAYER_START:{layerIndex}");
- GCode.AppendLine($";currPos:{layer.PositionZ}");
- if (layer.ExposureTime > 0 && pwmValue > 0)
- {
- GCode.AppendLine($"M6054 \"{layerIndex + 1}.png\";show Image");
- }
-
- // Absolute gcode
- if (liftHeight > 0 && liftZHeight > layer.PositionZ)
- {
- GCode.AppendLine($"G0 Z{liftZHeight} F{liftSpeed};Z Lift");
- GCode.AppendLine($"G0 Z{layer.PositionZ} F{retractSpeed};Layer position");
- }
- else if (lastZPosition < layer.PositionZ)
- GCode.AppendLine($"G0 Z{layer.PositionZ} F{retractSpeed};Layer position");
-
- GCode.AppendLine($"G4 P{lightOffDelay};Stabilization delay");
-
- if (layer.ExposureTime > 0 && pwmValue > 0)
- {
- GCode.AppendLine($"M106 S{pwmValue};light on");
- GCode.AppendLine($"G4 P{exposureTime};Cure time");
- GCode.AppendLine("M106 S0;light off");
- }
-
- GCode.AppendLine(";LAYER_END");
- GCode.AppendLine();
-
- lastZPosition = layer.PositionZ;
- }
-
- GCode.AppendFormat(GCodeEnd, Environment.NewLine, HeaderSettings.MachineZ);
+ GCode.RebuildGCode(this, new object[]{ HeaderSettings });
}
public override void SaveAs(string filePath = null, OperationProgress progress = null)
@@ -746,7 +535,7 @@ namespace UVtools.Core.FileFormats
if (!IsPHZZip)
{
- outputFile.PutFileContent("run.gcode", GCode.ToString(), ZipArchiveMode.Update);
+ outputFile.PutFileContent(GCodeFilename, GCode.ToString(), ZipArchiveMode.Update);
}
}
diff --git a/UVtools.Core/FileFormats/FDGFile.cs b/UVtools.Core/FileFormats/FDGFile.cs
index 5f4c04f..2febaf6 100644
--- a/UVtools.Core/FileFormats/FDGFile.cs
+++ b/UVtools.Core/FileFormats/FDGFile.cs
@@ -666,7 +666,7 @@ namespace UVtools.Core.FileFormats
public LayerData[] LayersDefinitions { get; private set; }
- public Dictionary<string, LayerData> LayersHash { get; } = new Dictionary<string, LayerData>();
+ public Dictionary<string, LayerData> LayersHash { get; } = new();
public override FileFormatType FileType => FileFormatType.Binary;
@@ -680,13 +680,13 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.BottomExposureSeconds,
PrintParameterModifier.ExposureSeconds,
- PrintParameterModifier.BottomLightOffDelay,
- PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLiftHeight,
PrintParameterModifier.BottomLiftSpeed,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.BottomLightOffDelay,
+ PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLightPWM,
PrintParameterModifier.LightPWM,
@@ -744,12 +744,8 @@ namespace UVtools.Core.FileFormats
public override float MaxPrintHeight
{
- get => HeaderSettings.BedSizeZ;
- set
- {
- HeaderSettings.BedSizeZ = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ get => HeaderSettings.BedSizeZ > 0 ? HeaderSettings.BedSizeZ : base.MaxPrintHeight;
+ set => base.MaxPrintHeight = HeaderSettings.BedSizeZ = (float)Math.Round(value, 2);
}
public override bool MirrorDisplay
@@ -777,150 +773,93 @@ namespace UVtools.Core.FileFormats
get => HeaderSettings.LayerHeightMilimeter;
set
{
- HeaderSettings.LayerHeightMilimeter = (float)Math.Round(value, 2);
+ HeaderSettings.LayerHeightMilimeter = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override float PrintHeight
{
- get => base.PrintHeight;
- set
- {
- HeaderSettings.OverallHeightMilimeter = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ get => HeaderSettings.OverallHeightMilimeter;
+ set => base.PrintHeight = HeaderSettings.OverallHeightMilimeter = base.PrintHeight;
}
public override uint LayerCount
{
- set
- {
- HeaderSettings.LayerCount = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- HeaderSettings.OverallHeightMilimeter = PrintHeight;
- }
+ get => base.LayerCount;
+ set => base.LayerCount = HeaderSettings.LayerCount = base.LayerCount;
}
public override ushort BottomLayerCount
{
- get => (ushort)HeaderSettings.BottomLayersCount;
- set
- {
- HeaderSettings.BottomLayersCount2 = HeaderSettings.BottomLayersCount = value;
- RaisePropertyChanged();
- }
+ get => (ushort) HeaderSettings.BottomLayersCount;
+ set => base.BottomLayerCount = (ushort) (HeaderSettings.BottomLayersCount2 = HeaderSettings.BottomLayersCount = value);
}
public override float BottomExposureTime
{
get => HeaderSettings.BottomExposureSeconds;
- set
- {
- HeaderSettings.BottomExposureSeconds = value;
- RaisePropertyChanged();
- }
+ set => base.BottomExposureTime = HeaderSettings.BottomExposureSeconds = value;
}
public override float ExposureTime
{
get => HeaderSettings.LayerExposureSeconds;
- set
- {
- HeaderSettings.LayerExposureSeconds = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
- }
-
- public override float BottomLightOffDelay
- {
- get => HeaderSettings.BottomLightOffDelay;
- set
- {
- HeaderSettings.BottomLightOffDelay = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
- }
-
- public override float LightOffDelay
- {
- get => HeaderSettings.LightOffDelay;
- set
- {
- HeaderSettings.LightOffDelay = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.ExposureTime = HeaderSettings.LayerExposureSeconds = (float)Math.Round(value, 2);
}
public override float BottomLiftHeight
{
get => HeaderSettings.BottomLiftHeight;
- set
- {
- HeaderSettings.BottomLiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftHeight = HeaderSettings.BottomLiftHeight = (float)Math.Round(value, 2);
}
public override float LiftHeight
{
get => HeaderSettings.LiftHeight;
- set
- {
- HeaderSettings.LiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = HeaderSettings.LiftHeight = (float)Math.Round(value, 2);
}
public override float BottomLiftSpeed
{
get => HeaderSettings.BottomLiftSpeed;
- set
- {
- HeaderSettings.BottomLiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed = (float)Math.Round(value, 2);
}
public override float LiftSpeed
{
get => HeaderSettings.LiftSpeed;
- set
- {
- HeaderSettings.LiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftSpeed = HeaderSettings.LiftSpeed = (float)Math.Round(value, 2);
}
public override float RetractSpeed
{
get => HeaderSettings.RetractSpeed;
- set
- {
- HeaderSettings.RetractSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.RetractSpeed = HeaderSettings.RetractSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomLightOffDelay
+ {
+ get => HeaderSettings.BottomLightOffDelay;
+ set => base.BottomLightOffDelay = HeaderSettings.BottomLightOffDelay = (float)Math.Round(value, 2);
+ }
+
+ public override float LightOffDelay
+ {
+ get => HeaderSettings.LightOffDelay;
+ set => base.LightOffDelay = HeaderSettings.LightOffDelay = (float)Math.Round(value, 2);
}
public override byte BottomLightPWM
{
- get => (byte)HeaderSettings.BottomLightPWM;
- set
- {
- HeaderSettings.BottomLightPWM = value;
- RaisePropertyChanged();
- }
+ get => (byte) HeaderSettings.BottomLightPWM;
+ set => base.BottomLightPWM = (byte) (HeaderSettings.BottomLightPWM = value);
}
public override byte LightPWM
{
- get => (byte)HeaderSettings.BottomLightPWM;
- set
- {
- HeaderSettings.BottomLightPWM = value;
- RaisePropertyChanged();
- }
+ get => (byte) HeaderSettings.BottomLightPWM;
+ set => base.LightPWM = (byte) (HeaderSettings.BottomLightPWM = value);
}
public override float PrintTime
@@ -946,21 +885,13 @@ namespace UVtools.Core.FileFormats
public override float MaterialGrams
{
get => (float)Math.Round(HeaderSettings.WeightG, 3);
- set
- {
- HeaderSettings.WeightG = (float)Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialGrams = HeaderSettings.WeightG = (float)Math.Round(value, 3);
}
public override float MaterialCost
{
get => (float) Math.Round(HeaderSettings.CostDollars, 3);
- set
- {
- HeaderSettings.CostDollars = (float)Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialCost = HeaderSettings.CostDollars = (float)Math.Round(value, 3);
}
public override string MachineName
@@ -968,10 +899,8 @@ namespace UVtools.Core.FileFormats
get => HeaderSettings.MachineName;
set
{
- HeaderSettings.MachineName = value;
+ base.MachineName = HeaderSettings.MachineName = value;
HeaderSettings.MachineNameSize = (uint)HeaderSettings.MachineName.Length;
- RequireFullEncode = true;
- RaisePropertyChanged();
}
}
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index 3760cb9..e6c7dd7 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -19,6 +19,7 @@ using System.Threading.Tasks;
using Emgu.CV;
using Emgu.CV.CvEnum;
using UVtools.Core.Extensions;
+using UVtools.Core.GCode;
using UVtools.Core.Objects;
using UVtools.Core.Operations;
@@ -304,6 +305,18 @@ namespace UVtools.Core.FileFormats
{
return (from t in AvailableFormats where type == t.GetType() select createNewInstance ? (FileFormat) Activator.CreateInstance(type) : t).FirstOrDefault();
}
+
+ public static string GetFileNameStripExtensions(string filepath)
+ {
+ //if (file.EndsWith(TemporaryFileAppend)) file = Path.GetFileNameWithoutExtension(file);
+ return PathExtensions.GetFileNameStripExtensions(filepath, AllFileExtensionsString.OrderByDescending(s => s.Length).ToList(), out _);
+ }
+
+ public static string GetFileNameStripExtensions(string filepath, out string strippedExtension)
+ {
+ //if (file.EndsWith(TemporaryFileAppend)) file = Path.GetFileNameWithoutExtension(file);
+ return PathExtensions.GetFileNameStripExtensions(filepath, AllFileExtensionsString.OrderByDescending(s => s.Length).ToList(), out strippedExtension);
+ }
#endregion
#region Members
@@ -312,6 +325,23 @@ namespace UVtools.Core.FileFormats
private float _printTime;
private float _materialMilliliters;
private float _maxPrintHeight;
+ private ushort _bottomLayerCount = DefaultBottomLayerCount;
+ private float _bottomExposureTime = DefaultBottomExposureTime;
+ private float _exposureTime = DefaultExposureTime;
+ private float _bottomLiftHeight = DefaultBottomLiftHeight;
+ private float _liftHeight = DefaultLiftHeight;
+ private float _bottomLiftSpeed = DefaultBottomLiftSpeed;
+ private float _liftSpeed = DefaultLiftSpeed;
+ private float _retractSpeed = DefaultRetractSpeed;
+ private float _bottomLightOffDelay = DefaultBottomLightOffDelay;
+ private float _lightOffDelay = DefaultLightOffDelay;
+ private byte _bottomLightPwm = DefaultBottomLightPWM;
+ private byte _lightPwm = DefaultLightPWM;
+ private string _machineName = "Unknown";
+ private string _materialName;
+ private float _materialGrams;
+ private float _materialCost;
+
#endregion
#region Properties
@@ -479,6 +509,16 @@ namespace UVtools.Core.FileFormats
}
/// <summary>
+ /// Gets the first layer
+ /// </summary>
+ public Layer FirstLayer => _layerManager?.FirstLayer;
+
+ /// <summary>
+ /// Gets the last layer
+ /// </summary>
+ public Layer LastLayer => _layerManager?.LastLayer;
+
+ /// <summary>
/// Gets the bounding rectangle of the object
/// </summary>
public Rectangle BoundingRectangle => _layerManager?.BoundingRectangle ?? Rectangle.Empty;
@@ -494,7 +534,7 @@ namespace UVtools.Core.FileFormats
public bool RequireFullEncode
{
get => _haveModifiedLayers || LayerManager.IsModified;
- set => _haveModifiedLayers = value;
+ set => RaiseAndSetIfChanged(ref _haveModifiedLayers, value);
} // => LayerManager.IsModified;
/// <summary>
@@ -675,10 +715,7 @@ namespace UVtools.Core.FileFormats
public virtual float PrintHeight
{
get => LayerCount == 0 ? 0 : this[LayerCount - 1]?.PositionZ ?? 0;
- set
- {
- RaisePropertyChanged();
- }
+ set => RaisePropertyChanged();
}
/// <summary>
@@ -689,7 +726,7 @@ namespace UVtools.Core.FileFormats
/// <summary>
/// Checks if this file format supports per layer settings
/// </summary>
- public virtual bool SupportPerLayerSettings => !(PrintParameterPerLayerModifiers is null || PrintParameterPerLayerModifiers.Length == 0);
+ public virtual bool SupportPerLayerSettings => PrintParameterPerLayerModifiers is not null && PrintParameterPerLayerModifiers.Length > 0;
/// <summary>
/// Gets or sets the layer count
@@ -708,7 +745,15 @@ namespace UVtools.Core.FileFormats
/// <summary>
/// Gets or sets the number of initial layer count
/// </summary>
- public virtual ushort BottomLayerCount { get; set; } = DefaultBottomLayerCount;
+ public virtual ushort BottomLayerCount
+ {
+ get => _bottomLayerCount;
+ set
+ {
+ RaiseAndSet(ref _bottomLayerCount, value);
+ RaisePropertyChanged(nameof(NormalLayerCount));
+ }
+ }
/// <summary>
/// Gets the number of normal layer count
@@ -718,57 +763,101 @@ namespace UVtools.Core.FileFormats
/// <summary>
/// Gets or sets the initial exposure time for <see cref="BottomLayerCount"/> in seconds
/// </summary>
- public virtual float BottomExposureTime { get; set; } = DefaultBottomExposureTime;
+ public virtual float BottomExposureTime
+ {
+ get => _bottomExposureTime;
+ set => RaiseAndSet(ref _bottomExposureTime, value);
+ }
/// <summary>
/// Gets or sets the normal layer exposure time in seconds
/// </summary>
- public virtual float ExposureTime { get; set; } = DefaultExposureTime;
+ public virtual float ExposureTime
+ {
+ get => _exposureTime;
+ set => RaiseAndSet(ref _exposureTime, value);
+ }
/// <summary>
- /// Gets or sets the bottom layer off time in seconds
+ /// Gets or sets the bottom lift height in mm
/// </summary>
- public virtual float BottomLightOffDelay { get; set; } = DefaultBottomLightOffDelay;
+ public virtual float BottomLiftHeight
+ {
+ get => _bottomLiftHeight;
+ set => RaiseAndSet(ref _bottomLiftHeight, value);
+ }
/// <summary>
- /// Gets or sets the layer off time in seconds
+ /// Gets or sets the lift height in mm
/// </summary>
- public virtual float LightOffDelay { get; set; } = DefaultLightOffDelay;
+ public virtual float LiftHeight
+ {
+ get => _liftHeight;
+ set => RaiseAndSet(ref _liftHeight, value);
+ }
/// <summary>
- /// Gets or sets the bottom lift height in mm
+ /// Gets or sets the bottom lift speed in mm/min
/// </summary>
- public virtual float BottomLiftHeight { get; set; } = DefaultBottomLiftHeight;
+ public virtual float BottomLiftSpeed
+ {
+ get => _bottomLiftSpeed;
+ set => RaiseAndSet(ref _bottomLiftSpeed, value);
+ }
/// <summary>
- /// Gets or sets the lift height in mm
+ /// Gets or sets the speed in mm/min
/// </summary>
- public virtual float LiftHeight { get; set; } = DefaultLiftHeight;
+ public virtual float LiftSpeed
+ {
+ get => _liftSpeed;
+ set => RaiseAndSet(ref _liftSpeed, value);
+ }
/// <summary>
- /// Gets or sets the bottom lift speed in mm/min
+ /// Gets the speed in mm/min for the retracts
/// </summary>
- public virtual float BottomLiftSpeed { get; set; } = DefaultBottomLiftSpeed;
+ public virtual float RetractSpeed
+ {
+ get => _retractSpeed;
+ set => RaiseAndSet(ref _retractSpeed, value);
+ }
/// <summary>
- /// Gets or sets the speed in mm/min
+ /// Gets or sets the bottom layer off time in seconds
/// </summary>
- public virtual float LiftSpeed { get; set; } = DefaultLiftSpeed;
+ public virtual float BottomLightOffDelay
+ {
+ get => _bottomLightOffDelay;
+ set => RaiseAndSet(ref _bottomLightOffDelay, value);
+ }
/// <summary>
- /// Gets the speed in mm/min for the retracts
+ /// Gets or sets the layer off time in seconds
/// </summary>
- public virtual float RetractSpeed { get; set; } = DefaultRetractSpeed;
+ public virtual float LightOffDelay
+ {
+ get => _lightOffDelay;
+ set => RaiseAndSet(ref _lightOffDelay, value);
+ }
/// <summary>
/// Gets or sets the bottom pwm value from 0 to 255
/// </summary>
- public virtual byte BottomLightPWM { get; set; } = DefaultBottomLightPWM;
+ public virtual byte BottomLightPWM
+ {
+ get => _bottomLightPwm;
+ set => RaiseAndSet(ref _bottomLightPwm, value);
+ }
/// <summary>
/// Gets or sets the pwm value from 0 to 255
/// </summary>
- public virtual byte LightPWM { get; set; } = DefaultLightPWM;
+ public virtual byte LightPWM
+ {
+ get => _lightPwm;
+ set => RaiseAndSet(ref _lightPwm, value);
+ }
#endregion
@@ -865,32 +954,53 @@ namespace UVtools.Core.FileFormats
}
//public float MaterialMillilitersComputed =>
-
+
/// <summary>
/// Gets the estimate material in grams
/// </summary>
- public virtual float MaterialGrams { get; set; }
+ public virtual float MaterialGrams
+ {
+ get => _materialGrams;
+ set => RaiseAndSetIfChanged(ref _materialGrams, value);
+ }
/// <summary>
/// Gets the estimate material cost
/// </summary>
- public virtual float MaterialCost { get; set; }
+ public virtual float MaterialCost
+ {
+ get => _materialCost;
+ set => RaiseAndSetIfChanged(ref _materialCost, value);
+ }
/// <summary>
/// Gets the material name
/// </summary>
- public virtual string MaterialName { get; set; }
+ public virtual string MaterialName
+ {
+ get => _materialName;
+ set => RaiseAndSetIfChanged(ref _materialName, value);
+ }
/// <summary>
/// Gets the machine name
/// </summary>
- public virtual string MachineName { get; set; } = "Unknown";
+ public virtual string MachineName
+ {
+ get => _machineName;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _machineName, value)) return;
+ if(FileType == FileFormatType.Binary) RequireFullEncode = true;
+ }
+
+ }
/// <summary>
/// Gets the GCode, returns null if not supported
/// </summary>
- public StringBuilder GCode { get; set; }
+ public GCodeBuilder GCode { get; set; } = new();
/// <summary>
/// Gets the GCode, returns null if not supported
@@ -900,7 +1010,7 @@ namespace UVtools.Core.FileFormats
/// <summary>
/// Gets if this file have available gcode
/// </summary>
- public bool HaveGCode => GCode is not null;
+ public bool HaveGCode => !GCode?.IsEmpty ?? false;
/// <summary>
/// Get all configuration objects with properties and values
@@ -938,7 +1048,11 @@ namespace UVtools.Core.FileFormats
e.PropertyName == nameof(LightPWM)
)
{
- LayerManager.RebuildLayersProperties(false, e.PropertyName);
+ if (LayerManager is not null)
+ {
+ LayerManager.RebuildLayersProperties(false, e.PropertyName);
+ }
+
if(e.PropertyName != nameof(BottomLightPWM) && e.PropertyName != nameof(LightPWM))
PrintTime = PrintTimeComputed;
return;
@@ -1012,7 +1126,7 @@ namespace UVtools.Core.FileFormats
{
FileFullPath = null;
LayerManager = null;
- GCode = null;
+ GCode.Clear();
if (Thumbnails is not null)
{
@@ -1395,7 +1509,7 @@ namespace UVtools.Core.FileFormats
/// <returns>The height in mm</returns>
public float GetHeightFromLayer(uint layerIndex, bool realHeight = true)
{
- return (float)Math.Round((layerIndex+(realHeight ? 1 : 0)) * LayerHeight, 2);
+ return Layer.RoundHeight((layerIndex + (realHeight ? 1 : 0)) * LayerHeight);
}
/// <summary>
@@ -1652,7 +1766,11 @@ namespace UVtools.Core.FileFormats
/// <summary>
/// Rebuilds GCode based on current settings
/// </summary>
- public virtual void RebuildGCode() { }
+ public virtual void RebuildGCode()
+ {
+ if (GCode.IsEmpty) return;
+ GCode.RebuildGCode(this);
+ }
/// <summary>
/// Saves current configuration on input file
diff --git a/UVtools.Core/FileFormats/LGSFile.cs b/UVtools.Core/FileFormats/LGSFile.cs
index 52f56a1..da5a3e4 100644
--- a/UVtools.Core/FileFormats/LGSFile.cs
+++ b/UVtools.Core/FileFormats/LGSFile.cs
@@ -207,8 +207,8 @@ namespace UVtools.Core.FileFormats
public override FileFormatType FileType => FileFormatType.Binary;
public override FileExtension[] FileExtensions { get; } = {
- new FileExtension("lgs", "Longer Orange 10"),
- new FileExtension("lgs30", "Longer Orange 30"),
+ new ("lgs", "Longer Orange 10"),
+ new ("lgs30", "Longer Orange 30"),
};
public override PrintParameterModifier[] PrintParameterModifiers { get; } =
@@ -217,12 +217,12 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.BottomExposureSeconds,
PrintParameterModifier.ExposureSeconds,
- PrintParameterModifier.BottomLightOffDelay,
- PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLiftHeight,
PrintParameterModifier.BottomLiftSpeed,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
+ PrintParameterModifier.BottomLightOffDelay,
+ PrintParameterModifier.LightOffDelay,
};
public override byte ThumbnailsCount { get; } = 1;
@@ -298,108 +298,90 @@ namespace UVtools.Core.FileFormats
get => HeaderSettings.LayerHeight;
set
{
- HeaderSettings.LayerHeight = value;
+ HeaderSettings.LayerHeight = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
- set
- {
- HeaderSettings.LayerCount = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- }
+ get => base.LayerCount;
+ set => base.LayerCount = HeaderSettings.LayerCount = base.LayerCount;
}
public override ushort BottomLayerCount
{
- get => (ushort)(HeaderSettings.BottomHeight / LayerHeight);
+ get => (ushort) (HeaderSettings.BottomHeight / LayerHeight);
set
{
HeaderSettings.BottomHeight = value * LayerHeight;
- RaisePropertyChanged();
+ base.BottomLayerCount = value;
}
}
public override float BottomExposureTime
{
- get => (float)Math.Round(HeaderSettings.BottomExposureTimeMs / 1000, 2);
+ get => TimeExtensions.MillisecondsToSeconds(HeaderSettings.BottomExposureTimeMs);
set
{
- HeaderSettings.BottomExposureTimeMs = value * 1000;
- RaisePropertyChanged();
+ HeaderSettings.BottomExposureTimeMs = TimeExtensions.SecondsToMilliseconds(value);
+ base.BottomExposureTime = value;
}
}
public override float ExposureTime
{
- get => (float)Math.Round(HeaderSettings.ExposureTimeMs / 1000, 2);
- set
- {
- HeaderSettings.ExposureTimeMs = value * 1000;
- RaisePropertyChanged();
- }
- }
-
- public override float BottomLightOffDelay
- {
- get => HeaderSettings.BottomLightOffDelayMs;
- set
- {
- HeaderSettings.BottomLightOffDelayMs = value;
- RaisePropertyChanged();
- }
- }
-
- public override float LightOffDelay
- {
- get => HeaderSettings.LightOffDelayMs;
+ get => TimeExtensions.MillisecondsToSeconds(HeaderSettings.ExposureTimeMs);
set
{
- HeaderSettings.LightOffDelayMs = value;
- RaisePropertyChanged();
+ HeaderSettings.ExposureTimeMs = TimeExtensions.SecondsToMilliseconds(value);
+ base.ExposureTime = value;
}
}
public override float BottomLiftHeight
{
get => HeaderSettings.BottomLiftHeight;
- set
- {
- HeaderSettings.BottomLiftHeight = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLiftHeight = HeaderSettings.BottomLiftHeight = value;
}
public override float LiftHeight
{
get => HeaderSettings.LiftHeight;
- set
- {
- HeaderSettings.LiftHeight = value;
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = HeaderSettings.LiftHeight = value;
}
public override float BottomLiftSpeed
{
get => HeaderSettings.BottomLiftSpeed;
+ set => base.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed_ = value;
+ }
+
+ public override float LiftSpeed
+ {
+ get => HeaderSettings.LiftSpeed;
+ set => base.LiftSpeed = HeaderSettings.LiftSpeed = HeaderSettings.LiftSpeed_ = value;
+ }
+
+ public override float RetractSpeed => LiftSpeed;
+
+ public override float BottomLightOffDelay
+ {
+ get => TimeExtensions.MillisecondsToSeconds(HeaderSettings.BottomLightOffDelayMs);
set
{
- HeaderSettings.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed_ = value;
- RaisePropertyChanged();
+ HeaderSettings.BottomLightOffDelayMs = TimeExtensions.SecondsToMilliseconds(value);
+ base.BottomLightOffDelay = value;
}
}
- public override float LiftSpeed
+ public override float LightOffDelay
{
- get => HeaderSettings.LiftSpeed;
+ get => TimeExtensions.MillisecondsToSeconds(HeaderSettings.LightOffDelayMs);
set
{
- HeaderSettings.LiftSpeed = HeaderSettings.LiftSpeed_ = value;
- RaisePropertyChanged();
+ HeaderSettings.LightOffDelayMs = TimeExtensions.SecondsToMilliseconds(value);
+ base.LightOffDelay = value;
}
}
@@ -411,7 +393,7 @@ namespace UVtools.Core.FileFormats
public override string MaterialName => "Unknown";
public override string MachineName => null;*/
-
+
public override object[] Configs => new object[] { HeaderSettings };
#endregion
diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs
index ac28db9..861d163 100644
--- a/UVtools.Core/FileFormats/PHZFile.cs
+++ b/UVtools.Core/FileFormats/PHZFile.cs
@@ -698,13 +698,13 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.BottomExposureSeconds,
PrintParameterModifier.ExposureSeconds,
- PrintParameterModifier.BottomLightOffDelay,
- PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLiftHeight,
PrintParameterModifier.BottomLiftSpeed,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.BottomLightOffDelay,
+ PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLightPWM,
PrintParameterModifier.LightPWM,
@@ -795,7 +795,7 @@ namespace UVtools.Core.FileFormats
get => HeaderSettings.LayerHeightMilimeter;
set
{
- HeaderSettings.LayerHeightMilimeter = (float)Math.Round(value, 2);
+ HeaderSettings.LayerHeightMilimeter = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
@@ -803,142 +803,85 @@ namespace UVtools.Core.FileFormats
public override float PrintHeight
{
get => base.PrintHeight;
- set
- {
- HeaderSettings.OverallHeightMilimeter = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.PrintHeight = HeaderSettings.OverallHeightMilimeter = base.PrintHeight;
}
public override uint LayerCount
{
- set
- {
- HeaderSettings.LayerCount = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- HeaderSettings.OverallHeightMilimeter = PrintHeight;
- }
+ get => base.LayerCount;
+ set => HeaderSettings.LayerCount = base.LayerCount;
}
public override ushort BottomLayerCount
{
- get => (ushort)HeaderSettings.BottomLayersCount;
- set
- {
- HeaderSettings.BottomLayersCount2 = HeaderSettings.BottomLayersCount = value;
- RaisePropertyChanged();
- }
+ get => (ushort) HeaderSettings.BottomLayersCount;
+ set => base.BottomLayerCount = (ushort) (HeaderSettings.BottomLayersCount2 = HeaderSettings.BottomLayersCount = value);
}
public override float BottomExposureTime
{
get => HeaderSettings.BottomExposureSeconds;
- set
- {
- HeaderSettings.BottomExposureSeconds = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomExposureTime = HeaderSettings.BottomExposureSeconds = (float)Math.Round(value, 2);
}
public override float ExposureTime
{
get => HeaderSettings.LayerExposureSeconds;
- set
- {
- HeaderSettings.LayerExposureSeconds = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
- }
-
- public override float BottomLightOffDelay
- {
- get => HeaderSettings.BottomLightOffDelay;
- set
- {
- HeaderSettings.BottomLightOffDelay = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
- }
-
- public override float LightOffDelay
- {
- get => HeaderSettings.LightOffDelay;
- set
- {
- HeaderSettings.LightOffDelay = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.ExposureTime = HeaderSettings.LayerExposureSeconds = (float)Math.Round(value, 2);
}
public override float BottomLiftHeight
{
get => HeaderSettings.BottomLiftHeight;
- set
- {
- HeaderSettings.BottomLiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftHeight = HeaderSettings.BottomLiftHeight = (float)Math.Round(value, 2);
}
public override float LiftHeight
{
get => HeaderSettings.LiftHeight;
- set
- {
- HeaderSettings.LiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = HeaderSettings.LiftHeight = (float)Math.Round(value, 2);
}
public override float BottomLiftSpeed
{
get => HeaderSettings.BottomLiftSpeed;
- set
- {
- HeaderSettings.BottomLiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed = (float)Math.Round(value, 2);
}
public override float LiftSpeed
{
get => HeaderSettings.LiftSpeed;
- set
- {
- HeaderSettings.LiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftSpeed = HeaderSettings.LiftSpeed = (float)Math.Round(value, 2);
}
public override float RetractSpeed
{
get => HeaderSettings.RetractSpeed;
- set
- {
- HeaderSettings.RetractSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.RetractSpeed = HeaderSettings.RetractSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomLightOffDelay
+ {
+ get => HeaderSettings.BottomLightOffDelay;
+ set => base.BottomLightOffDelay = HeaderSettings.BottomLightOffDelay = (float)Math.Round(value, 2);
+ }
+
+ public override float LightOffDelay
+ {
+ get => HeaderSettings.LightOffDelay;
+ set => base.LightOffDelay = HeaderSettings.LightOffDelay = (float)Math.Round(value, 2);
}
public override byte BottomLightPWM
{
- get => (byte)HeaderSettings.BottomLightPWM;
- set
- {
- HeaderSettings.BottomLightPWM = value;
- RaisePropertyChanged();
- }
+ get => (byte) HeaderSettings.BottomLightPWM;
+ set => base.BottomLightPWM = (byte) (HeaderSettings.BottomLightPWM = value);
}
public override byte LightPWM
{
- get => (byte)HeaderSettings.BottomLightPWM;
- set
- {
- HeaderSettings.BottomLightPWM = value;
- RaisePropertyChanged();
- }
+ get => (byte) HeaderSettings.LightPWM;
+ set => base.LightPWM = (byte) (HeaderSettings.LightPWM = value);
}
public override float PrintTime
@@ -964,21 +907,13 @@ namespace UVtools.Core.FileFormats
public override float MaterialGrams
{
get => (float) Math.Round(HeaderSettings.WeightG, 3);
- set
- {
- HeaderSettings.WeightG = (float) Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialGrams = HeaderSettings.WeightG = (float) Math.Round(value, 3);
}
public override float MaterialCost
{
get => (float) Math.Round(HeaderSettings.CostDollars, 3);
- set
- {
- HeaderSettings.CostDollars = (float)Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialCost = HeaderSettings.CostDollars = (float)Math.Round(value, 3);
}
public override string MachineName
@@ -986,10 +921,8 @@ namespace UVtools.Core.FileFormats
get => HeaderSettings.MachineName;
set
{
- HeaderSettings.MachineName = value;
+ base.MachineName = HeaderSettings.MachineName = value;
HeaderSettings.MachineNameSize = (uint)HeaderSettings.MachineName.Length;
- RequireFullEncode = true;
- RaisePropertyChanged();
}
}
diff --git a/UVtools.Core/FileFormats/PhotonSFile.cs b/UVtools.Core/FileFormats/PhotonSFile.cs
index 27fb6fa..50e99c6 100644
--- a/UVtools.Core/FileFormats/PhotonSFile.cs
+++ b/UVtools.Core/FileFormats/PhotonSFile.cs
@@ -265,90 +265,44 @@ namespace UVtools.Core.FileFormats
get => (float) Math.Round(HeaderSettings.LayerHeight);
set
{
- HeaderSettings.LayerHeight = (float)Math.Round(value, 2);
+ HeaderSettings.LayerHeight = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
- set
- {
- LayerSettings.LayerCount = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- }
+ get => base.LayerCount;
+ set => base.LayerCount = LayerSettings.LayerCount = LayerCount;
}
public override ushort BottomLayerCount
{
get => (ushort) HeaderSettings.BottomLayerCount;
- set
- {
- HeaderSettings.BottomLayerCount = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLayerCount = (ushort) (HeaderSettings.BottomLayerCount = value);
}
public override float BottomExposureTime
{
get => (float) HeaderSettings.BottomExposureSeconds;
- set
- {
- HeaderSettings.BottomExposureSeconds = value;
- RaisePropertyChanged();
- }
+ set => base.BottomExposureTime = (float) (HeaderSettings.BottomExposureSeconds = value);
}
public override float ExposureTime
{
- get => (float)HeaderSettings.ExposureSeconds;
- set
- {
- HeaderSettings.ExposureSeconds = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
- }
-
- public override float LightOffDelay
- {
- get => (float) HeaderSettings.LightOffDelay;
- set
- {
- HeaderSettings.LightOffDelay = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ get => (float) HeaderSettings.ExposureSeconds;
+ set => base.ExposureTime = (float) (HeaderSettings.ExposureSeconds = Math.Round(value, 2));
}
- /*public override float BottomLiftHeight
- {
- get => HeaderSettings.BottomLiftHeight;
- set
- {
- HeaderSettings.BottomLiftHeight = value;
- RaisePropertyChanged();
- }
- }*/
-
+ public override float BottomLiftHeight => LightOffDelay;
+
public override float LiftHeight
{
get => (float) HeaderSettings.LiftHeight;
- set
- {
- HeaderSettings.LiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = (float) (HeaderSettings.LiftHeight = Math.Round(value, 2));
}
- /*public override float BottomLiftSpeed
- {
- get => HeaderSettings.BottomLiftSpeed;
- set
- {
- HeaderSettings.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed_ = value;
- RaisePropertyChanged();
- }
- }*/
+ public override float BottomLiftSpeed => LiftSpeed;
public override float LiftSpeed
{
@@ -356,7 +310,7 @@ namespace UVtools.Core.FileFormats
set
{
HeaderSettings.LiftSpeed = Math.Round(value / 60.0, 2);
- RaisePropertyChanged();
+ base.LiftSpeed = value;
}
}
@@ -365,12 +319,20 @@ namespace UVtools.Core.FileFormats
get => (float)Math.Round(HeaderSettings.RetractSpeed * 60.0, 2);
set
{
- HeaderSettings.RetractSpeed = Math.Round(value / 60.0, 2);
- RaisePropertyChanged();
+ HeaderSettings.RetractSpeed = (float) Math.Round(value / 60.0, 2);
+ base.RetractSpeed = value;
}
}
+ public override float BottomLightOffDelay => LightOffDelay;
+ public override float LightOffDelay
+ {
+ get => (float)HeaderSettings.LightOffDelay;
+ set => base.LightOffDelay = (float)(HeaderSettings.LightOffDelay = Math.Round(value, 2));
+ }
+
+
public override float MaterialMilliliters
{
get => base.MaterialMilliliters;
@@ -502,9 +464,6 @@ namespace UVtools.Core.FileFormats
throw new FileLoadException("Not a valid PHOTONS file! TAGs doesn't match", fileFullPath);
}
- HeaderSettings.LayerHeight = Math.Round(HeaderSettings.LayerHeight, 2);
- HeaderSettings.VolumeMl = Math.Round(HeaderSettings.VolumeMl, 2);
-
int previewSize = (int) (HeaderSettings.PreviewResolutionX * HeaderSettings.PreviewResolutionY * 2);
byte[] previewData = new byte[previewSize];
diff --git a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
index 67f295f..7ef1934 100644
--- a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
+++ b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
@@ -1061,96 +1061,60 @@ namespace UVtools.Core.FileFormats
get => HeaderSettings.LayerHeight;
set
{
- HeaderSettings.LayerHeight = (float)Math.Round(value, 2);
+ HeaderSettings.LayerHeight = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
- set
- {
- LayersDefinition.LayerCount = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- }
+ get => base.LayerCount;
+ set => base.LayerCount = LayersDefinition.LayerCount = base.LayerCount;
}
public override ushort BottomLayerCount
{
- get => (ushort)HeaderSettings.BottomLayersCount;
- set
- {
- HeaderSettings.BottomLayersCount = value;
- RaisePropertyChanged();
- }
+ get => (ushort) HeaderSettings.BottomLayersCount;
+ set => base.BottomLayerCount = (ushort) (HeaderSettings.BottomLayersCount = value);
}
public override float BottomExposureTime
{
get => HeaderSettings.BottomExposureSeconds;
- set
- {
- HeaderSettings.BottomExposureSeconds = (float) Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomExposureTime = HeaderSettings.BottomExposureSeconds = (float) Math.Round(value, 2);
}
public override float ExposureTime
{
get => HeaderSettings.LayerExposureTime;
- set
- {
- HeaderSettings.LayerExposureTime = (float) Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.ExposureTime = HeaderSettings.LayerExposureTime = (float) Math.Round(value, 2);
}
- public override float BottomLightOffDelay
- {
- get => LightOffDelay;
- set => LightOffDelay = value;
- }
+ public override float BottomLightOffDelay => LightOffDelay;
public override float LightOffDelay
{
get => HeaderSettings.LightOffDelay;
- set
- {
- HeaderSettings.LightOffDelay = (float) Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LightOffDelay = HeaderSettings.LightOffDelay = (float) Math.Round(value, 2);
}
- public override float BottomLiftHeight
- {
- get => LiftHeight;
- set => LiftHeight = value;
- }
+ public override float BottomLiftHeight => LiftHeight;
public override float LiftHeight
{
get => HeaderSettings.LiftHeight;
- set
- {
- HeaderSettings.LiftHeight = (float) Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = HeaderSettings.LiftHeight = (float) Math.Round(value, 2);
}
- public override float BottomLiftSpeed
- {
- get => LiftSpeed;
- set => LiftSpeed = value;
- }
+ public override float BottomLiftSpeed => LiftSpeed;
public override float LiftSpeed
{
- get => (float) Math.Round(HeaderSettings.LiftSpeed * 60, 2);
+ get => (float)Math.Round(HeaderSettings.LiftSpeed * 60, 2);
set
{
HeaderSettings.LiftSpeed = (float) Math.Round(value / 60, 2);
- RaisePropertyChanged();
+ base.LiftSpeed = value;
}
}
@@ -1160,7 +1124,7 @@ namespace UVtools.Core.FileFormats
set
{
HeaderSettings.RetractSpeed = (float) Math.Round(value / 60, 2);
- RaisePropertyChanged();
+ base.RetractSpeed = value;
}
}
@@ -1187,21 +1151,13 @@ namespace UVtools.Core.FileFormats
public override float MaterialGrams
{
get => (float) Math.Round(HeaderSettings.WeightG, 3);
- set
- {
- HeaderSettings.WeightG = (float) Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialGrams = HeaderSettings.WeightG = (float) Math.Round(value, 3);
}
public override float MaterialCost
{
get => (float) Math.Round(HeaderSettings.Price, 3);
- set
- {
- HeaderSettings.Price = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.MaterialCost = HeaderSettings.Price = (float)Math.Round(value, 3);
}
public override string MachineName
@@ -1480,7 +1436,7 @@ namespace UVtools.Core.FileFormats
{
this[layerIndex] = new Layer((uint) layerIndex, image, LayerManager)
{
- PositionZ = (float) Math.Round(LayersDefinition[(uint)layerIndex].LayerHeight, 2),
+ PositionZ = Layer.RoundHeight(LayersDefinition[(uint)layerIndex].LayerHeight),
ExposureTime = LayersDefinition[(uint)layerIndex].ExposureTime,
LiftHeight = LayersDefinition[(uint)layerIndex].LiftHeight,
LiftSpeed = (float)Math.Round(LayersDefinition[(uint)layerIndex].LiftSpeed * 60, 2),
@@ -1498,7 +1454,7 @@ namespace UVtools.Core.FileFormats
{
for (uint layerIndex = 1; layerIndex < LayerCount; layerIndex++)
{
- this[layerIndex].PositionZ = (float)Math.Round(this[layerIndex-1].PositionZ + this[layerIndex].PositionZ, 2);
+ this[layerIndex].PositionZ = Layer.RoundHeight(this[layerIndex-1].PositionZ + this[layerIndex].PositionZ);
}
}
}
diff --git a/UVtools.Core/FileFormats/SL1File.cs b/UVtools.Core/FileFormats/SL1File.cs
index e33b4e7..0c083bc 100644
--- a/UVtools.Core/FileFormats/SL1File.cs
+++ b/UVtools.Core/FileFormats/SL1File.cs
@@ -268,7 +268,7 @@ namespace UVtools.Core.FileFormats
public float LayerHeight { get; set; }
public string MaterialName { get; set; } = About.Software;
public ushort NumFade { get; set; }
- public ushort NumFast { get; set; }
+ public uint NumFast { get; set; }
public ushort NumSlow { get; set; }
public string PrintProfile { get; set; } = About.Software;
public float PrintTime { get; set; }
@@ -388,49 +388,37 @@ namespace UVtools.Core.FileFormats
get => OutputConfigSettings.LayerHeight;
set
{
- OutputConfigSettings.LayerHeight = (float)Math.Round(value, 2);
+ OutputConfigSettings.LayerHeight = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
+ get => base.LayerCount;
set
{
OutputConfigSettings.NumSlow = 0;
- OutputConfigSettings.NumFast = (ushort) LayerCount;
- RaisePropertyChanged();
+ base.LayerCount = OutputConfigSettings.NumFast = base.LayerCount;
}
}
public override ushort BottomLayerCount
{
get => OutputConfigSettings.NumFade;
- set
- {
- OutputConfigSettings.NumFade = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLayerCount = OutputConfigSettings.NumFade = value;
}
public override float BottomExposureTime
{
get => OutputConfigSettings.ExpTimeFirst;
- set
- {
- OutputConfigSettings.ExpTimeFirst = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomExposureTime = OutputConfigSettings.ExpTimeFirst = (float)Math.Round(value, 2);
}
public override float ExposureTime
{
get => OutputConfigSettings.ExpTime;
- set
- {
- OutputConfigSettings.ExpTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.ExposureTime = OutputConfigSettings.ExpTime = (float)Math.Round(value, 2);
}
public override float PrintTime
@@ -453,32 +441,20 @@ namespace UVtools.Core.FileFormats
}
}
- public override float MaterialGrams
- {
- get => (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 3);
- set { }
- }
+ public override float MaterialGrams => (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 3);
- public override float MaterialCost => MaterialSettings.BottleVolume > 0 ? (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.BottleCost / MaterialSettings.BottleVolume, 3) : 0;
+ public override float MaterialCost => MaterialSettings.BottleVolume > 0 ? (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.BottleCost / MaterialSettings.BottleVolume, 3) : 0;
public override string MaterialName
{
get => OutputConfigSettings.MaterialName;
- set
- {
- OutputConfigSettings.MaterialName = value;
- RaisePropertyChanged();
- }
+ set => base.MaterialName = OutputConfigSettings.MaterialName = value;
}
public override string MachineName
{
get => PrinterSettings.PrinterSettingsId;
- set
- {
- PrinterSettings.PrinterSettingsId = value;
- RaisePropertyChanged();
- }
+ set => base.MachineName = PrinterSettings.PrinterSettingsId = value;
}
public override object[] Configs => new object[] { PrinterSettings, MaterialSettings, PrintSettings, OutputConfigSettings };
@@ -652,6 +628,7 @@ namespace UVtools.Core.FileFormats
throw new FileLoadException($"Malformed file: {IniPrusaslicer} is missing.");
}
+ SuppressRebuildProperties = true;
BottomLiftHeight = LookupCustomValue(Keyword_BottomLiftHeight, DefaultBottomLiftHeight);
BottomLiftSpeed = LookupCustomValue(Keyword_BottomLiftSpeed, DefaultBottomLiftSpeed);
LiftHeight = LookupCustomValue(Keyword_LiftHeight, DefaultLiftHeight);
@@ -661,6 +638,7 @@ namespace UVtools.Core.FileFormats
LightOffDelay = LookupCustomValue(Keyword_LightOffDelay, DefaultLightOffDelay);
BottomLightPWM = LookupCustomValue(Keyword_BottomLightPWM, DefaultLightPWM);
LightPWM = LookupCustomValue(Keyword_LightPWM, DefaultBottomLightPWM);
+ SuppressRebuildProperties = false;
LayerManager = new LayerManager((uint) (OutputConfigSettings.NumSlow + OutputConfigSettings.NumFast), this);
diff --git a/UVtools.Core/FileFormats/UVJFile.cs b/UVtools.Core/FileFormats/UVJFile.cs
index f638aea..e30f5da 100644
--- a/UVtools.Core/FileFormats/UVJFile.cs
+++ b/UVtools.Core/FileFormats/UVJFile.cs
@@ -150,6 +150,17 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.LightPWM,
};
+ public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.ExposureSeconds,
+ PrintParameterModifier.LiftHeight,
+ PrintParameterModifier.LiftSpeed,
+ PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.LightOffDelay,
+
+ PrintParameterModifier.BottomLightPWM,
+ PrintParameterModifier.LightPWM,
+ };
+
public override byte ThumbnailsCount { get; } = 2;
public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(400, 400), new System.Drawing.Size(800, 480) };
@@ -207,146 +218,92 @@ namespace UVtools.Core.FileFormats
}
- public override bool SupportPerLayerSettings => true;
-
public override float LayerHeight
{
get => JsonSettings.Properties.Size.LayerHeight;
set
{
- JsonSettings.Properties.Size.LayerHeight = (float)Math.Round(value, 2);
+ JsonSettings.Properties.Size.LayerHeight = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
- set
- {
- JsonSettings.Properties.Size.Layers = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- }
+ get => base.LayerCount;
+ set => base.LayerCount = JsonSettings.Properties.Size.Layers = base.LayerCount;
}
public override ushort BottomLayerCount
{
get => JsonSettings.Properties.Bottom.Count;
- set
- {
- JsonSettings.Properties.Bottom.Count = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLayerCount = JsonSettings.Properties.Bottom.Count = value;
}
public override float BottomExposureTime
{
get => JsonSettings.Properties.Bottom.LightOnTime;
- set
- {
- JsonSettings.Properties.Bottom.LightOnTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomExposureTime = JsonSettings.Properties.Bottom.LightOnTime = (float)Math.Round(value, 2);
}
public override float ExposureTime
{
get => JsonSettings.Properties.Exposure.LightOnTime;
- set
- {
- JsonSettings.Properties.Exposure.LightOnTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.ExposureTime = JsonSettings.Properties.Exposure.LightOnTime = (float)Math.Round(value, 2);
}
public override float BottomLightOffDelay
{
get => JsonSettings.Properties.Bottom.LightOffTime;
- set
- {
- JsonSettings.Properties.Bottom.LightOffTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLightOffDelay = JsonSettings.Properties.Bottom.LightOffTime = (float)Math.Round(value, 2);
}
public override float LightOffDelay
{
get => JsonSettings.Properties.Exposure.LightOffTime;
- set
- {
- JsonSettings.Properties.Exposure.LightOffTime = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LightOffDelay = JsonSettings.Properties.Exposure.LightOffTime = (float)Math.Round(value, 2);
}
public override float BottomLiftHeight
{
get => JsonSettings.Properties.Bottom.LiftHeight;
- set
- {
- JsonSettings.Properties.Bottom.LiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftHeight = JsonSettings.Properties.Bottom.LiftHeight = (float)Math.Round(value, 2);
}
public override float LiftHeight
{
get => JsonSettings.Properties.Exposure.LiftHeight;
- set
- {
- JsonSettings.Properties.Exposure.LiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = JsonSettings.Properties.Exposure.LiftHeight = (float)Math.Round(value, 2);
}
public override float BottomLiftSpeed
{
get => JsonSettings.Properties.Bottom.LiftSpeed;
- set
- {
- JsonSettings.Properties.Bottom.LiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftSpeed = JsonSettings.Properties.Bottom.LiftSpeed = (float)Math.Round(value, 2);
}
public override float LiftSpeed
{
get => JsonSettings.Properties.Exposure.LiftSpeed;
- set
- {
- JsonSettings.Properties.Exposure.LiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftSpeed = JsonSettings.Properties.Exposure.LiftSpeed = (float)Math.Round(value, 2);
}
public override float RetractSpeed
{
get => JsonSettings.Properties.Exposure.RetractSpeed;
- set
- {
- JsonSettings.Properties.Exposure.RetractSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.RetractSpeed = JsonSettings.Properties.Exposure.RetractSpeed = (float)Math.Round(value, 2);
}
public override byte BottomLightPWM
{
get => JsonSettings.Properties.Bottom.LightPWM;
- set
- {
- JsonSettings.Properties.Bottom.LightPWM = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLightPWM = JsonSettings.Properties.Bottom.LightPWM = value;
}
public override byte LightPWM
{
get => JsonSettings.Properties.Exposure.LightPWM;
- set
- {
- JsonSettings.Properties.Exposure.LightPWM = value;
- RaisePropertyChanged();
- }
+ set => base.LightPWM = JsonSettings.Properties.Exposure.LightPWM = value;
}
public override object[] Configs => new[] {(object) JsonSettings.Properties.Size, JsonSettings.Properties.Size.Millimeter, JsonSettings.Properties.Bottom, JsonSettings.Properties.Exposure};
diff --git a/UVtools.Core/FileFormats/ZCodeFile.cs b/UVtools.Core/FileFormats/ZCodeFile.cs
index 1dcb2fe..bb113a2 100644
--- a/UVtools.Core/FileFormats/ZCodeFile.cs
+++ b/UVtools.Core/FileFormats/ZCodeFile.cs
@@ -9,12 +9,10 @@
using System;
using System.Collections.Generic;
using System.Drawing;
-using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
-using System.Text.RegularExpressions;
using System.Xml.Serialization;
using Emgu.CV;
using Emgu.CV.CvEnum;
@@ -23,6 +21,8 @@ using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.OpenSsl;
using UVtools.Core.Extensions;
+using UVtools.Core.GCode;
+using UVtools.Core.Objects;
using UVtools.Core.Operations;
namespace UVtools.Core.FileFormats
@@ -100,7 +100,7 @@ namespace UVtools.Core.FileFormats
public byte AntiAliasingGrey { get; set; }
[XmlAttribute("led_power")]
- public ushort LedPower { get; set; } = 300;
+ public ushort LedPower { get; set; } = ZCodeFile.MaxLEDPower;
}
public ZcodePrintProfileSlice Slice { get; set; } = new();
@@ -149,6 +149,7 @@ namespace UVtools.Core.FileFormats
public const string GCodeFilename = "lcd.gcode";
public const string ManifestFilename = "task.xml";
public const string PreviewFilename = "preview.png";
+ public const ushort MaxLEDPower = 300;
public const string GCodeStart = "G21;{0}" + // Set units to be mm
"G90;{0}" + // Absolute Positioning
@@ -197,16 +198,16 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.BottomExposureSeconds,
PrintParameterModifier.ExposureSeconds,
- PrintParameterModifier.BottomLightOffDelay,
- PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomLiftHeight,
PrintParameterModifier.BottomLiftSpeed,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.BottomLightOffDelay,
+ PrintParameterModifier.LightOffDelay,
- //PrintParameterModifier.BottomLightPWM,
- //PrintParameterModifier.LightPWM,
+ PrintParameterModifier.BottomLightPWM,
+ PrintParameterModifier.LightPWM,
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
@@ -215,7 +216,7 @@ namespace UVtools.Core.FileFormats
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
- //PrintParameterModifier.LightPWM,
+ PrintParameterModifier.LightPWM,
};
public override byte ThumbnailsCount { get; } = 1;
@@ -281,120 +282,97 @@ namespace UVtools.Core.FileFormats
get => ManifestFile.Job.LayerHeight;
set
{
- ManifestFile.Job.LayerHeight = ManifestFile.Profile.Slice.LayerHeight = (float)Math.Round(value, 2);
+ ManifestFile.Job.LayerHeight = ManifestFile.Profile.Slice.LayerHeight = Layer.RoundHeight(value);
RaisePropertyChanged();
}
}
public override uint LayerCount
{
- set
- {
- ManifestFile.Job.LayerCount = LayerCount;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
- }
+ get => base.LayerCount;
+ set => base.LayerCount = ManifestFile.Job.LayerCount = base.LayerCount;
}
public override ushort BottomLayerCount
{
get => ManifestFile.Profile.Slice.BottomLayerCount;
- set
- {
- ManifestFile.Profile.Slice.BottomLayerCount = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLayerCount = ManifestFile.Profile.Slice.BottomLayerCount = value;
}
public override float BottomExposureTime
{
- get => (float)Math.Round(ManifestFile.Profile.Slice.BottomExposureTime / 1000f, 2);
+ get => TimeExtensions.MillisecondsToSeconds(ManifestFile.Profile.Slice.BottomExposureTime);
set
{
- ManifestFile.Profile.Slice.BottomExposureTime = (uint) (value * 1000);
- RaisePropertyChanged();
+ ManifestFile.Profile.Slice.BottomExposureTime = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.BottomExposureTime = value;
}
}
public override float ExposureTime
{
- get => (float)Math.Round(ManifestFile.Profile.Slice.ExposureTime / 1000f, 2);
- set
- {
- ManifestFile.Profile.Slice.ExposureTime = (uint)(value * 1000);
- RaisePropertyChanged();
- }
- }
-
- public override float BottomLightOffDelay
- {
- get => (float)Math.Round(ManifestFile.Profile.Slice.BottomLightOffDelay / 1000f, 2);
+ get => TimeExtensions.MillisecondsToSeconds(ManifestFile.Profile.Slice.ExposureTime);
set
{
- ManifestFile.Profile.Slice.BottomLightOffDelay = (uint)(value * 1000);
- RaisePropertyChanged();
- }
- }
-
- public override float LightOffDelay
- {
- get => (float)Math.Round(ManifestFile.Profile.Slice.LightOffDelay / 1000f, 2);
- set
- {
- ManifestFile.Profile.Slice.LightOffDelay = (uint)(value * 1000);
- RaisePropertyChanged();
+ ManifestFile.Profile.Slice.ExposureTime = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.ExposureTime = value;
}
}
public override float BottomLiftHeight
{
get => ManifestFile.Profile.Slice.BottomLiftHeight;
- set
- {
- ManifestFile.Profile.Slice.BottomLiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.BottomLiftHeight = ManifestFile.Profile.Slice.BottomLiftHeight = (float)Math.Round(value, 2);
}
public override float LiftHeight
{
get => ManifestFile.Profile.Slice.LiftHeight;
- set
- {
- ManifestFile.Profile.Slice.LiftHeight = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = ManifestFile.Profile.Slice.LiftHeight = (float)Math.Round(value, 2);
}
public override float BottomLiftSpeed
{
get => ManifestFile.Profile.Slice.BottomLiftSpeed;
+ set => base.BottomLiftSpeed = ManifestFile.Profile.Slice.BottomLiftSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float LiftSpeed
+ {
+ get => ManifestFile.Profile.Slice.LiftSpeed;
+ set => base.LiftSpeed = ManifestFile.Profile.Slice.LiftSpeed = (float)Math.Round(value, 2);
+ }
+
+ public override float BottomLightOffDelay
+ {
+ get => TimeExtensions.MillisecondsToSeconds(ManifestFile.Profile.Slice.BottomLightOffDelay);
set
{
- ManifestFile.Profile.Slice.BottomLiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
+ ManifestFile.Profile.Slice.BottomLightOffDelay = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.BottomLightOffDelay = value;
}
}
- public override float LiftSpeed
+ public override float LightOffDelay
{
- get => ManifestFile.Profile.Slice.LiftSpeed;
+ get => (float)Math.Round(ManifestFile.Profile.Slice.LightOffDelay / 1000f, 2);
set
{
- ManifestFile.Profile.Slice.LiftSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
+ ManifestFile.Profile.Slice.LightOffDelay = (uint)(value * 1000);
+ base.LightOffDelay = value;
}
}
- /*public override float RetractSpeed
+ public override byte LightPWM
{
- get => HeaderSettings.RetractSpeed;
+ get => (byte)(byte.MaxValue * ManifestFile.Profile.Slice.LedPower / MaxLEDPower);
set
{
- HeaderSettings.RetractSpeed = (float)Math.Round(value, 2);
- RaisePropertyChanged();
+ ManifestFile.Profile.Slice.LedPower = (ushort)(MaxLEDPower * value / byte.MaxValue);
+ base.LightPWM = value;
+ RaisePropertyChanged(nameof(BottomLightPWM));
}
- }*/
+ }
public override float PrintTime
{
@@ -419,21 +397,13 @@ namespace UVtools.Core.FileFormats
public override float MaterialGrams
{
get => ManifestFile.Job.WeightG;
- set
- {
- ManifestFile.Job.WeightG = (float)Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialGrams = ManifestFile.Job.WeightG = (float)Math.Round(value, 3);
}
public override float MaterialCost
{
get => ManifestFile.Job.Price;
- set
- {
- ManifestFile.Job.Price = (float)Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialCost = ManifestFile.Job.Price = (float)Math.Round(value, 3);
}
/*public override string MaterialName
@@ -449,18 +419,24 @@ namespace UVtools.Core.FileFormats
public override string MachineName
{
get => ManifestFile.Device.MachineModel;
- set
- {
- ManifestFile.Device.MachineModel = value;
- RequireFullEncode = true;
- RaisePropertyChanged();
- }
+ set => base.MachineName = ManifestFile.Device.MachineModel = value;
}
public override object[] Configs => new object[] { ManifestFile.Device, ManifestFile.Job, ManifestFile.Profile.Slice };
#endregion
+ public ZCodeFile()
+ {
+ GCode.UseTailComma = true;
+ GCode.UseComments = false;
+ GCode.GCodePositioningType = GCodeBuilder.GCodePositioningTypes.Absolute;
+ GCode.GCodeSpeedUnit = GCodeBuilder.GCodeSpeedUnits.CentimetersPerMinute;
+ GCode.GCodeTimeUnit = GCodeBuilder.GCodeTimeUnits.Milliseconds;
+ GCode.GCodeShowImageType = GCodeBuilder.GCodeShowImageTypes.FilenameNonZeroPNG;
+ GCode.MaxLEDPower = MaxLEDPower;
+ }
+
#region Methods
protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
@@ -527,15 +503,11 @@ namespace UVtools.Core.FileFormats
throw new FileLoadException($"{GCodeFilename} not found", fileFullPath);
}
- GCode = new();
-
var encryptEngine = new RsaEngine();
using var txtreader = new StringReader(GCodeRSAPublicKey);
var keyParameter = (AsymmetricKeyParameter)new PemReader(txtreader).ReadObject();
encryptEngine.Init(true, keyParameter);
-
-
using (TextReader tr = new StreamReader(entry.Open()))
{
string line;
@@ -543,10 +515,7 @@ namespace UVtools.Core.FileFormats
while ((line = tr.ReadLine()) != null)
{
if (string.IsNullOrEmpty(line)) continue;
- if (!line.EndsWith("=="))
- {
- continue;
- }
+ if (!line.EndsWith("==")) continue;
byte[] data = System.Convert.FromBase64String(line);
var decodedBytes = encryptEngine.ProcessBlock(data, 0, data.Length);
@@ -562,8 +531,8 @@ namespace UVtools.Core.FileFormats
LayerManager = new LayerManager(ManifestFile.Job.LayerCount, this);
progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
- var gcode = GCode.ToString();
- float lastPostZ = LayerHeight;
+ //var gcode = GCode.ToString();
+ //float lastPostZ = LayerHeight;
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
@@ -575,76 +544,14 @@ namespace UVtools.Core.FileFormats
throw new FileLoadException($"Layer {layerIndex+1} not found", fileFullPath);
}
- var startStr = $"M6054 \"{layerIndex+1}.png\"";
- gcode = gcode.Substring(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length + 1);
- var stripGcode = gcode.Substring(0, gcode.IndexOf("M106 S0")).Trim(' ', '\n', '\r', '\t');
- //var startCurrPos = stripGcode.Remove(0, ";currPos:".Length);
-
- float posZ = lastPostZ;
- float liftHeight = GetInitialLayerValueOrNormal(layerIndex, BottomLiftHeight, LiftHeight);
- float liftSpeed = GetInitialLayerValueOrNormal(layerIndex, BottomLiftSpeed, LiftSpeed);
- float retractSpeed = RetractSpeed;
- float lightOffDelay = 0;
- byte pwm = GetInitialLayerValueOrNormal(layerIndex, BottomLightPWM, LightPWM); ;
- float exposureTime = GetInitialLayerValueOrNormal(layerIndex, BottomExposureTime, ExposureTime);
-
- //var currPosRegex = Regex.Match(stripGcode, @";currPos:([+-]?([0-9]*[.])?[0-9]+)", RegexOptions.IgnoreCase);
- var moveG0Regex = Regex.Match(stripGcode, @"G0 Z([+-]?([0-9]*[.])?[0-9]+) F(\d+)", RegexOptions.IgnoreCase);
- var waitG4Regex = Regex.Match(stripGcode, @"G4 P(\d+)", RegexOptions.IgnoreCase);
- var pwmM106Regex = Regex.Match(stripGcode, @"M106 S(\d+)", RegexOptions.IgnoreCase);
-
- if (moveG0Regex.Success)
- {
- float liftHeightTemp = float.Parse(moveG0Regex.Groups[1].Value, CultureInfo.InvariantCulture);
- float liftSpeedTemp = float.Parse(moveG0Regex.Groups[3].Value, CultureInfo.InvariantCulture);
- moveG0Regex = moveG0Regex.NextMatch();
- if (moveG0Regex.Success)
- {
- float retractHeight = float.Parse(moveG0Regex.Groups[1].Value, CultureInfo.InvariantCulture);
- retractSpeed = float.Parse(moveG0Regex.Groups[3].Value, CultureInfo.InvariantCulture) * 10;
- liftHeight = (float) Math.Round(liftHeightTemp - retractHeight, 2);
- liftSpeed = liftSpeedTemp * 10;
- lastPostZ = posZ = retractHeight;
- }
- else
- {
- lastPostZ = posZ = liftHeightTemp;
- }
- }
-
- if (pwmM106Regex.Success)
- {
- //pwm = byte.Parse(pwmM106Regex.Groups[1].Value);
- }
-
- if (waitG4Regex.Success)
- {
- lightOffDelay = (float) Math.Round(float.Parse(waitG4Regex.Groups[1].Value, CultureInfo.InvariantCulture) / 1000f, 2);
- waitG4Regex = waitG4Regex.NextMatch();
- if (waitG4Regex.Success)
- {
- exposureTime = (float) Math.Round(float.Parse(waitG4Regex.Groups[1].Value, CultureInfo.InvariantCulture) / 1000f, 2);
- }
- else // Only one match, meaning light off delay is not present
- {
- lightOffDelay = GetInitialLayerValueOrNormal(layerIndex, BottomLightOffDelay, LightOffDelay);
- }
- }
+ using var stream = entry.Open();
+ this[layerIndex] = new Layer(layerIndex, stream, LayerManager);
- this[layerIndex] = new Layer(layerIndex, entry.Open(), LayerManager)
- {
- PositionZ = posZ,
- ExposureTime = exposureTime,
- LiftHeight = liftHeight,
- LiftSpeed = liftSpeed,
- RetractSpeed = retractSpeed,
- LightOffDelay = lightOffDelay,
- LightPWM = pwm,
- };
progress++;
}
-
+ GCode.ParseLayersFromGCode(this);
+
entry = inputFile.GetEntry(PreviewFilename);
if (entry is not null)
{
@@ -656,56 +563,6 @@ namespace UVtools.Core.FileFormats
LayerManager.GetBoundingRectangle(progress);
}
- public override void RebuildGCode()
- {
- GCode = new();
-
- GCode.AppendFormat(GCodeStart, Environment.NewLine);
-
- float lastZPosition = 0;
-
- for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
- var layer = this[layerIndex];
- var exposureTime = layer.ExposureTime * 1000;
- var liftHeight = layer.LiftHeight;
- var liftZHeight = Math.Round(liftHeight + layer.PositionZ, 2);
- var liftSpeed = Math.Round(layer.LiftSpeed / 10, 2);
- var retractSpeed = Math.Round(layer.RetractSpeed / 10, 2);
- var lightOffDelay = layer.LightOffDelay * 1000;
- var pwmValue = layer.LightPWM;
-
- if (layer.ExposureTime > 0 && pwmValue > 0)
- {
- GCode.AppendLine($"M6054 \"{layerIndex + 1}.png\";");
- }
-
- // Absolute gcode
- if (liftHeight > 0 && liftZHeight > layer.PositionZ)
- {
- GCode.AppendLine($"G0 Z{liftZHeight} F{liftSpeed};");
- GCode.AppendLine($"G0 Z{layer.PositionZ} F{retractSpeed};");
- }
- else if (lastZPosition < layer.PositionZ)
- {
- GCode.AppendLine($"G0 Z{layer.PositionZ} F{retractSpeed};");
- }
-
- GCode.AppendLine($"G4 P{lightOffDelay};");
-
- if (layer.ExposureTime > 0 && pwmValue > 0)
- {
- GCode.AppendLine($"M106 S300;");
- GCode.AppendLine($"G4 P{exposureTime};");
- GCode.AppendLine("M106 S0;");
- }
-
- lastZPosition = layer.PositionZ;
- }
-
- GCode.AppendFormat(GCodeEnd, Environment.NewLine, MaxPrintHeight);
- }
-
public override void SaveAs(string filePath = null, OperationProgress progress = null)
{
if (RequireFullEncode)
@@ -762,10 +619,12 @@ namespace UVtools.Core.FileFormats
var keyParameter = (AsymmetricKeyParameter)new PemReader(txtreader).ReadObject();
encryptEngine.Init(true, keyParameter);
- using StringReader sr = new StringReader(GCode.ToString());
+ using StringReader sr = new(GCode.ToString());
string line;
while ((line = sr.ReadLine()) != null)
{
+ line = line.Trim();
+ if (line == string.Empty || line[0] == ';') continue; // No empty lines nor comment start lines
progress += (uint)line.Length;
byte[] data = Encoding.UTF8.GetBytes(line);
diff --git a/UVtools.Core/FileFormats/ZCodexFile.cs b/UVtools.Core/FileFormats/ZCodexFile.cs
index b7dca11..16cdcd7 100644
--- a/UVtools.Core/FileFormats/ZCodexFile.cs
+++ b/UVtools.Core/FileFormats/ZCodexFile.cs
@@ -231,7 +231,7 @@ namespace UVtools.Core.FileFormats
get => ResinMetadataSettings.LayerThickness;
set
{
- ResinMetadataSettings.LayerThickness = (float)Math.Round(value, 2);
+ ResinMetadataSettings.LayerThickness = Layer.RoundHeight(value);
UserSettings.LayerThickness = $"{ResinMetadataSettings.LayerThickness} mm";
RaisePropertyChanged();
}
@@ -239,82 +239,72 @@ namespace UVtools.Core.FileFormats
public override uint LayerCount
{
+ get => base.LayerCount;
set
{
- ResinMetadataSettings.TotalLayersCount = LayerCount;
+ base.LayerCount = ResinMetadataSettings.TotalLayersCount = base.LayerCount;
UserSettings.MaxLayer = LastLayerIndex;
- RaisePropertyChanged();
- RaisePropertyChanged(nameof(NormalLayerCount));
+
}
}
public override ushort BottomLayerCount
{
get => ResinMetadataSettings.BottomLayersNumber;
- set
- {
- ResinMetadataSettings.BottomLayersNumber = UserSettings.BottomLayersCount = value;
- RaisePropertyChanged();
- }
+ set => base.BottomLayerCount = ResinMetadataSettings.BottomLayersNumber = UserSettings.BottomLayersCount = value;
}
public override float BottomExposureTime
{
- get => (float) Math.Round(UserSettings.BottomLayerExposureTime / 1000f, 2);
+ get => TimeExtensions.MillisecondsToSeconds(UserSettings.BottomLayerExposureTime);
set
{
- ResinMetadataSettings.BottomLayersTime = UserSettings.BottomLayerExposureTime = (uint) (value * 1000f);
- RaisePropertyChanged();
+ ResinMetadataSettings.BottomLayersTime = UserSettings.BottomLayerExposureTime = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.BottomExposureTime = value;
}
}
public override float ExposureTime
{
- get => (float) Math.Round(UserSettings.LayerExposureTime / 1000f, 2);
+ get => TimeExtensions.MillisecondsToSeconds(UserSettings.LayerExposureTime);
set
{
- ResinMetadataSettings.LayerTime = UserSettings.LayerExposureTime = (uint) (value * 1000f);
- RaisePropertyChanged();
+ ResinMetadataSettings.LayerTime = UserSettings.LayerExposureTime = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.ExposureTime = value;
}
}
+ public override float BottomLiftHeight => LiftHeight;
+
public override float LiftHeight
{
get => UserSettings.ZLiftDistance;
- set
- {
- UserSettings.ZLiftDistance = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftHeight = UserSettings.ZLiftDistance = (float)Math.Round(value, 2);
}
+ public override float BottomLiftSpeed => LiftSpeed;
+
public override float LiftSpeed
{
get => UserSettings.ZLiftFeedRate;
- set
- {
- UserSettings.ZLiftFeedRate = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.LiftSpeed = UserSettings.ZLiftFeedRate = (float)Math.Round(value, 2);
}
public override float RetractSpeed
{
get => UserSettings.ZLiftRetractRate;
- set
- {
- UserSettings.ZLiftRetractRate = (float)Math.Round(value, 2);
- RaisePropertyChanged();
- }
+ set => base.RetractSpeed = UserSettings.ZLiftRetractRate = (float)Math.Round(value, 2);
}
+ public override float BottomLightOffDelay => LightOffDelay;
+
public override float LightOffDelay
{
- get => (float) Math.Round(ResinMetadataSettings.BlankingLayerTime / 1000f, 2);
+ get => TimeExtensions.MillisecondsToSeconds(ResinMetadataSettings.BlankingLayerTime);
set
{
- UserSettings.ExposureOffTime = ResinMetadataSettings.BlankingLayerTime = (uint)(value * 1000);
- RaisePropertyChanged();
+ UserSettings.ExposureOffTime = ResinMetadataSettings.BlankingLayerTime = TimeExtensions.SecondsToMillisecondsUint(value);
+ base.LightOffDelay = value;
}
}
@@ -344,31 +334,19 @@ namespace UVtools.Core.FileFormats
public override float MaterialGrams
{
get => (float) Math.Round(ResinMetadataSettings.TotalMaterialWeightUsed, 3);
- set
- {
- ResinMetadataSettings.TotalMaterialWeightUsed = (float) Math.Round(value, 3);
- RaisePropertyChanged();
- }
+ set => base.MaterialGrams = ResinMetadataSettings.TotalMaterialWeightUsed = (float) Math.Round(value, 3);
}
public override string MaterialName
{
get => ResinMetadataSettings.Material;
- set
- {
- ResinMetadataSettings.Material = value;
- RaisePropertyChanged();
- }
+ set => base.MaterialName = ResinMetadataSettings.Material = value;
}
public override string MachineName
{
get => ZCodeMetadataSettings.PrinterName;
- set
- {
- ZCodeMetadataSettings.PrinterName = value;
- RaisePropertyChanged();
- }
+ set => base.MachineName = ZCodeMetadataSettings.PrinterName = value;
}
public override object[] Configs => new[] {(object) ResinMetadataSettings, UserSettings, ZCodeMetadataSettings};
@@ -412,7 +390,7 @@ namespace UVtools.Core.FileFormats
}
}
- GCode = new StringBuilder(GCodeStart);
+ GCode.Clear();
float lastZPosition = 0;
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
@@ -427,11 +405,11 @@ namespace UVtools.Core.FileFormats
if (layer.LiftHeight > 0)
{
GCode.AppendLine($"G1 Z{layer.LiftHeight} F{layer.LiftSpeed}");
- GCode.AppendLine($"G1 Z-{Math.Round(layer.LiftHeight - layer.PositionZ + lastZPosition, 2)} F{layer.RetractSpeed}");
+ GCode.AppendLine($"G1 Z-{Layer.RoundHeight(layer.LiftHeight - layer.PositionZ + lastZPosition)} F{layer.RetractSpeed}");
}
else
{
- GCode.AppendLine($"G1 Z{Math.Round(layer.PositionZ- lastZPosition, 2)} F{layer.LiftSpeed}");
+ GCode.AppendLine($"G1 Z{Layer.RoundHeight(layer.PositionZ- lastZPosition)} F{layer.LiftSpeed}");
}
}
/*else
@@ -508,7 +486,7 @@ namespace UVtools.Core.FileFormats
}
LayerManager = new LayerManager(ResinMetadataSettings.TotalLayersCount, this);
- GCode = new StringBuilder();
+ GCode.Clear();
using (TextReader tr = new StreamReader(entry.Open()))
{
string line;
@@ -516,7 +494,7 @@ namespace UVtools.Core.FileFormats
int layerFileIndex = 0;
string layerimagePath = null;
float currentHeight = 0;
- while (!ReferenceEquals(line = tr.ReadLine(), null))
+ while ((line = tr.ReadLine()) is not null)
{
GCode.AppendLine(line);
if (line.StartsWith(GCodeKeywordSlice))
@@ -572,11 +550,11 @@ M106 S0
liftSpeed = liftSpeedTemp;
var retractHeight = float.Parse(moveG1Regex.Groups[1].Value, CultureInfo.InvariantCulture);
retractSpeed = float.Parse(moveG1Regex.Groups[3].Value, CultureInfo.InvariantCulture);
- currentHeight = (float)Math.Round(currentHeight + liftHeightTemp + retractHeight, 2);
+ currentHeight = Layer.RoundHeight(currentHeight + liftHeightTemp + retractHeight);
}
else
{
- currentHeight = (float) Math.Round(currentHeight + liftHeightTemp);
+ currentHeight = Layer.RoundHeight(currentHeight + liftHeightTemp);
}
}
diff --git a/UVtools.Core/GCode/GCodeBuilder.cs b/UVtools.Core/GCode/GCodeBuilder.cs
new file mode 100644
index 0000000..6f86920
--- /dev/null
+++ b/UVtools.Core/GCode/GCodeBuilder.cs
@@ -0,0 +1,792 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using UVtools.Core.Extensions;
+using UVtools.Core.FileFormats;
+using UVtools.Core.Objects;
+
+namespace UVtools.Core.GCode
+{
+ public class GCodeBuilder : BindableBase
+ {
+ #region Commands
+
+ public GCodeCommand CommandUnitsMillimetersG21 { get; } = new("G21", null, "Set units to be mm");
+ public GCodeCommand CommandPositioningAbsoluteG90 { get; } = new("G90", null, "Absolute positioning");
+ public GCodeCommand CommandPositioningPartialG91 { get; } = new("G91", null, "Partial positioning");
+
+ public GCodeCommand CommandTurnMotorsOnM17 { get; } = new("M17", null, "Enable motors");
+ public GCodeCommand CommandTurnMotorsOffM18 { get; } = new("M18", null, "Disable motors");
+
+ public GCodeCommand CommandHomeG28 { get; } = new("G28", "Z0", "Home Z");
+
+ public GCodeCommand CommandMoveG0 { get; } = new("G0", "Z{0} F{1}", "Move Z");
+ public GCodeCommand CommandMoveG1 { get; } = new("G1", "Z{0} F{1}", "Move Z");
+
+ public GCodeCommand CommandWaitG4 { get; } = new("G4", "P{0}", "Delay");
+ public GCodeCommand CommandShowImageM6054 = new("M6054", "\"{0}\"", "Show image");
+ public GCodeCommand CommandClearImage = new(";<Slice> Blank"); // Clear image
+ public GCodeCommand CommandTurnLEDM106 { get; } = new("M106", "S{0}", "Turn LED");
+ #endregion
+
+ #region Enums
+
+ public enum GCodePositioningTypes : byte
+ {
+ Absolute,
+ Partial
+ }
+
+ public enum GCodeTimeUnits : byte
+ {
+ /// <summary>
+ /// ms
+ /// </summary>
+ Milliseconds,
+ /// <summary>
+ /// s
+ /// </summary>
+ Seconds
+ }
+
+ public enum GCodeSpeedUnits : byte
+ {
+ /// <summary>
+ /// mm/s
+ /// </summary>
+ MillimetersPerSecond,
+ /// <summary>
+ /// mm/m
+ /// </summary>
+ MillimetersPerMinute,
+ /// <summary>
+ /// cm/m
+ /// </summary>
+ CentimetersPerMinute,
+ }
+
+ public enum GCodeShowImageTypes : byte
+ {
+ FilenameZeroPNG,
+ FilenameNonZeroPNG,
+ LayerIndexZero,
+ LayerIndexNonZero,
+ }
+ #endregion
+
+ #region Members
+ private readonly StringBuilder _gcode = new();
+
+ private bool _useTailComma = true;
+ private bool _useComments = true;
+ private GCodePositioningTypes _gCodePositioningType = GCodePositioningTypes.Absolute;
+ private GCodeTimeUnits _gCodeTimeUnit = GCodeTimeUnits.Milliseconds;
+ private GCodeSpeedUnits _gCodeSpeedUnit = GCodeSpeedUnits.MillimetersPerMinute;
+ private GCodeShowImageTypes _gCodeShowImageType = GCodeShowImageTypes.FilenameNonZeroPNG;
+ private ushort _maxLedPower = byte.MaxValue;
+ private uint _lineCount;
+
+ #endregion
+
+ #region Properties
+
+ public GCodePositioningTypes GCodePositioningType
+ {
+ get => _gCodePositioningType;
+ set => RaiseAndSetIfChanged(ref _gCodePositioningType, value);
+ }
+
+ public GCodeTimeUnits GCodeTimeUnit
+ {
+ get => _gCodeTimeUnit;
+ set => RaiseAndSetIfChanged(ref _gCodeTimeUnit, value);
+ }
+
+ public GCodeSpeedUnits GCodeSpeedUnit
+ {
+ get => _gCodeSpeedUnit;
+ set => RaiseAndSetIfChanged(ref _gCodeSpeedUnit, value);
+ }
+
+ public GCodeShowImageTypes GCodeShowImageType
+ {
+ get => _gCodeShowImageType;
+ set => RaiseAndSetIfChanged(ref _gCodeShowImageType, value);
+ }
+
+ public bool UseTailComma
+ {
+ get => _useTailComma;
+ set => RaiseAndSetIfChanged(ref _useTailComma, value);
+ }
+
+ public bool UseComments
+ {
+ get => _useComments;
+ set => RaiseAndSetIfChanged(ref _useComments, value);
+ }
+
+ public ushort MaxLEDPower
+ {
+ get => _maxLedPower;
+ set => RaiseAndSetIfChanged(ref _maxLedPower, value);
+ }
+
+ public string BeginStartGCodeComments { get; set; } = ";START_GCODE_BEGIN";
+ public string EndStartGCodeComments { get; set; } = ";END_GCODE_BEGIN";
+
+ public string BeginLayerComments { get; set; } = ";LAYER_START:{0}" + Environment.NewLine +
+ ";PositionZ:{1}mm";
+
+ public string EndLayerComments { get; set; } = ";LAYER_END";
+
+ public string BeginEndGCodeComments { get; set; } = ";START_GCODE_END";
+ public string EndEndGCodeComments { get; set; } = ";END_GCODE_END" + Environment.NewLine +
+ ";<Completed>";
+
+ public uint LineCount
+ {
+ get => _lineCount;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _lineCount, value)) return;
+ //RaisePropertyChanged(nameof(IsEmpty));
+ }
+ }
+
+ public bool IsEmpty => _lineCount <= 0 || _gcode.Length <= 0;
+ public int Length => _gcode.Length;
+
+ #endregion
+
+ #region StringBuilder
+
+ public void Append(string text)
+ {
+ _gcode.Append(text);
+ }
+
+ public void Append(StringBuilder sb)
+ {
+ _gcode.Append(sb);
+ }
+
+ public void AppendLine()
+ {
+ _gcode.AppendLine();
+ //LineCount++;
+ }
+
+ public void AppendLine(string line)
+ {
+ if (line is null) return;
+ _gcode.AppendLine(line);
+ LineCount++;
+ }
+
+ public void AppendLine(string line, params object[] args)
+ {
+ if (line is null) return;
+ _gcode.AppendLine(string.Format(line, args));
+ LineCount++;
+ }
+
+ public void AppendLine(GCodeCommand command)
+ {
+ if (!command.Enabled) return;
+ AppendLine(command.ToString(_useComments, _useTailComma));
+ LineCount++;
+ }
+
+ public void AppendLineOverrideComment(GCodeCommand command, string comment, params object[] args)
+ {
+ if (!command.Enabled) return;
+ AppendLine(command.ToStringOverrideComment(_useComments, _useTailComma, comment, args));
+ LineCount++;
+ }
+
+ public void AppendLine(GCodeCommand command, params object[] args)
+ {
+ if (!command.Enabled) return;
+ AppendLine(command.ToString(_useComments, _useTailComma, args));
+ LineCount++;
+ }
+
+ public void AppendFormat(string format, params object[] args)
+ {
+ _gcode.AppendFormat(format, args);
+ LineCount += (uint)format.Count(c => c == '\n');
+ }
+
+ public void AppendLineIfCanComment(string line)
+ {
+ if (string.IsNullOrWhiteSpace(line) || !_useComments) return;
+ AppendLine(line);
+ }
+
+ public void AppendLineIfCanComment(string line, params object[] args)
+ {
+ if (string.IsNullOrWhiteSpace(line) || !_useComments) return;
+ AppendLine(line, args);
+ }
+
+ public void AppendComment(string comment)
+ {
+ if (string.IsNullOrWhiteSpace(comment) || !_useComments) return;
+ AppendLine($";{comment}");
+ }
+
+ public void Clear()
+ {
+ _gcode.Clear();
+ LineCount = 0;
+ }
+
+ public override string ToString() => _gcode.ToString();
+ #endregion
+
+ #region Methods
+
+ public string FormatGCodeLine(string line, string comment = null)
+ {
+ if (line[0] == ';') return line;
+ if (_useComments && !string.IsNullOrWhiteSpace(comment))
+ {
+ line += $";{comment}";
+ }
+ else if (_useTailComma)
+ {
+ line += ';';
+ }
+
+ return line;
+ }
+
+ public void AppendUVtools()
+ {
+ string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits";
+ var version = Assembly.GetExecutingAssembly().GetName().Version;
+ AppendComment($"Generated by {About.Software} v{version.ToString(3)} {arch} @ {DateTime.Now}");
+ }
+
+ public void AppendStartGCode()
+ {
+ AppendLineIfCanComment(BeginStartGCodeComments);
+ AppendUnitsMmG21();
+ AppendPositioningType();
+ AppendLightOffM106();
+ AppendEnableMotors();
+ AppendClearImage();
+ AppendHomeZG28();
+ AppendLineIfCanComment(EndStartGCodeComments);
+ AppendLine();
+ }
+
+ public void AppendEndGCode(float raiseZ = 0, float feedRate = 0)
+ {
+ AppendLineIfCanComment(BeginEndGCodeComments);
+ AppendLightOffM106();
+ if (raiseZ > 0)
+ {
+ AppendMoveG0(raiseZ, feedRate);
+ }
+
+ AppendDisableMotors();
+ AppendLineIfCanComment(EndEndGCodeComments);
+ }
+
+ public void AppendUnitsMmG21()
+ {
+ AppendLine(CommandUnitsMillimetersG21);
+ }
+
+ public void AppendPositioningType()
+ {
+ switch (GCodePositioningType)
+ {
+ case GCodePositioningTypes.Absolute:
+ AppendLine(CommandPositioningAbsoluteG90);
+ break;
+ case GCodePositioningTypes.Partial:
+ AppendLine(CommandPositioningPartialG91);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ public void AppendEnableMotors()
+ {
+ AppendLine(CommandTurnMotorsOnM17);
+ }
+
+ public void AppendDisableMotors()
+ {
+ AppendLine(CommandTurnMotorsOffM18);
+ }
+
+ public void AppendTurnMotors(bool enable)
+ {
+ if (enable)
+ AppendEnableMotors();
+ else
+ AppendDisableMotors();
+ }
+
+ public void AppendHomeZG28()
+ {
+ AppendLine(CommandHomeG28);
+ }
+
+
+ public void AppendMoveG0(float z, float feedRate)
+ {
+ if(z == 0 || feedRate <= 0) return;
+ AppendLine(CommandMoveG0, z, feedRate);
+ }
+
+ public void AppendLiftMoveG0(float upZ, float upFeedRate, float downZ, float downFeedRate, float stabilizationDelay = 0)
+ {
+ if (upZ == 0 || upFeedRate <= 0) return;
+ AppendLineOverrideComment(CommandMoveG0, "Z Lift", upZ, upFeedRate); // Z Lift
+ if (downZ != 0 && downFeedRate > 0)
+ {
+ AppendLineOverrideComment(CommandMoveG0, "Retract to layer height", downZ, downFeedRate);
+ }
+
+ if (stabilizationDelay > 0)
+ {
+ AppendWaitG4(stabilizationDelay);
+ }
+ }
+
+ public void AppendMoveG1(float z, float feedRate)
+ {
+ if (z == 0 || feedRate <= 0) return;
+ AppendLine(CommandMoveG1, z, feedRate);
+ }
+
+ public void AppendLiftMoveG1(float upZ, float upFeedRate, float downZ, float downFeedRate, float stabilizationDelay = 0)
+ {
+ if (upZ == 0 || upFeedRate <= 0) return;
+ AppendLineOverrideComment(CommandMoveG1, "Lift Z", upZ, upFeedRate); // Z Lift
+ if (downZ != 0 && downFeedRate > 0)
+ {
+ AppendLineOverrideComment(CommandMoveG1, "Retract to layer height", downZ, downFeedRate);
+ }
+
+ if (stabilizationDelay > 0)
+ {
+ AppendWaitG4(stabilizationDelay);
+ }
+ }
+
+ public void AppendWaitG4(float time, string comment = null)
+ {
+ if (time < 0) return;
+ AppendLineOverrideComment(CommandWaitG4, comment, time);
+ }
+
+ public void AppendTurnLightM106(ushort value)
+ {
+ AppendLineOverrideComment(CommandTurnLEDM106, "Turn LED " + (value == 0 ? "OFF" : "ON"), value);
+ }
+
+ public void AppendLightOffM106() => AppendTurnLightM106(0);
+ public void AppendLightFullM106() => AppendTurnLightM106(_maxLedPower);
+
+ public void AppendClearImage()
+ {
+ AppendLine(CommandClearImage);
+ }
+
+ public void AppendExposure(float time, ushort pwmValue = 255)
+ {
+ if (pwmValue <= 0 || time <= 0) return;
+
+ AppendTurnLightM106(pwmValue);
+ AppendWaitG4(time, "Cure time/delay");
+ AppendLightOffM106();
+ AppendClearImage();
+ }
+
+ public void AppendShowImageM6054(string filename)
+ {
+ AppendLine(CommandShowImageM6054, filename);
+ }
+
+ public void AppendShowImageM6054(uint layerIndex)
+ {
+ AppendLine(CommandShowImageM6054, layerIndex);
+ }
+
+ /*public void AppendLayer(Layer layer)
+ {
+
+ }*/
+
+ public string GetShowImageString(uint layerIndex) => _gCodeShowImageType switch
+ {
+ GCodeShowImageTypes.FilenameZeroPNG => $"{layerIndex}.png",
+ GCodeShowImageTypes.FilenameNonZeroPNG => $"{layerIndex + 1}.png",
+ GCodeShowImageTypes.LayerIndexZero => $"{layerIndex}",
+ GCodeShowImageTypes.LayerIndexNonZero => $"{layerIndex + 1}",
+ _ => throw new ArgumentOutOfRangeException()
+ };
+
+ public void RebuildGCode(FileFormat slicerFile, StringBuilder header) => RebuildGCode(slicerFile, header?.ToString());
+ public void RebuildGCode(FileFormat slicerFile, string header = null)
+ {
+ if (slicerFile.LayerCount == 0) return;
+ Clear();
+
+ AppendUVtools();
+
+ if (!string.IsNullOrWhiteSpace(header))
+ {
+ Append(header);
+ AppendLine();
+ }
+
+ AppendStartGCode();
+
+ float lastZPosition = 0;
+
+ // Defaults for: Absolute, mm/m and s
+ for (uint layerIndex = 0; layerIndex < slicerFile.LayerCount; layerIndex++)
+ {
+ var layer = slicerFile[layerIndex];
+ float exposureTime = layer.ExposureTime;
+ float liftHeight = layer.LiftHeight;
+ float liftZPos = Layer.RoundHeight(liftHeight + layer.PositionZ);
+ float liftZPosAbs = liftZPos;
+ float liftSpeed = layer.LiftSpeed;
+ float retractPos = layer.PositionZ;
+ float retractSpeed = layer.RetractSpeed;
+ float lightOffDelay = layer.LightOffDelay;
+ ushort pwmValue = layer.LightPWM;
+ if (_maxLedPower != byte.MaxValue)
+ {
+ pwmValue = (ushort)(_maxLedPower * pwmValue / byte.MaxValue);
+ }
+
+ switch (GCodePositioningType)
+ {
+ case GCodePositioningTypes.Partial:
+ liftZPos = liftHeight;
+ retractPos = Layer.RoundHeight(layer.PositionZ - lastZPosition - liftHeight);
+ break;
+ }
+
+ switch (GCodeTimeUnit)
+ {
+ case GCodeTimeUnits.Milliseconds:
+ exposureTime *= 1000;
+ lightOffDelay *= 1000;
+ break;
+ }
+
+ switch (GCodeSpeedUnit)
+ {
+ case GCodeSpeedUnits.MillimetersPerSecond:
+ liftSpeed = (float)Math.Round(liftSpeed / 60, 2);
+ retractSpeed = (float)Math.Round(retractSpeed / 60, 2);
+ break;
+ case GCodeSpeedUnits.CentimetersPerMinute:
+ liftSpeed = (float)Math.Round(liftSpeed / 10, 2);
+ retractSpeed = (float)Math.Round(retractSpeed / 10);
+ break;
+ }
+
+ AppendLineIfCanComment(BeginLayerComments, layerIndex, layer.PositionZ);
+
+ if (layer.CanExpose)
+ {
+ AppendShowImageM6054(GetShowImageString(layerIndex));
+ }
+
+ if (liftHeight > 0 && liftZPosAbs > layer.PositionZ)
+ {
+ AppendLiftMoveG0(liftZPos, liftSpeed, retractPos, retractSpeed);
+ }
+ else if (lastZPosition < layer.PositionZ) // Ensure Z is on correct position
+ {
+ switch (GCodePositioningType)
+ {
+ case GCodePositioningTypes.Absolute:
+ AppendMoveG0(layer.PositionZ, liftSpeed);
+ break;
+ case GCodePositioningTypes.Partial:
+ AppendMoveG0(Layer.RoundHeight(layer.PositionZ - lastZPosition), liftSpeed);
+ break;
+ }
+
+ }
+
+ AppendWaitG4(lightOffDelay, "Stabilization delay");
+ AppendExposure(exposureTime, pwmValue);
+
+ AppendLineIfCanComment(EndLayerComments, layerIndex, layer.PositionZ);
+ AppendLine();
+
+ lastZPosition = layer.PositionZ;
+ }
+
+ float finalRaiseZPosition = slicerFile.MaxPrintHeight;
+ switch (GCodePositioningType)
+ {
+
+ case GCodePositioningTypes.Partial:
+ finalRaiseZPosition = Layer.RoundHeight(finalRaiseZPosition - lastZPosition);
+ break;
+ }
+
+ AppendEndGCode(finalRaiseZPosition, slicerFile.LiftSpeed);
+ }
+
+ public void RebuildGCode(FileFormat slicerFile, object[] configs, string separator = ":")
+ {
+ StringBuilder sb = null;
+ if (configs is not null)
+ {
+ sb = new StringBuilder();
+ foreach (var config in configs)
+ {
+ foreach (var propertyInfo in config.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
+ string name;
+ if (displayNameAttribute is null)
+ {
+ name = propertyInfo.Name;
+ if(name == "Item") continue;
+ }
+ else
+ {
+ name = displayNameAttribute.DisplayName;
+ }
+ sb.AppendLine($";{name}{separator}{propertyInfo.GetValue(config)}");
+ }
+ }
+ }
+
+ RebuildGCode(slicerFile, sb);
+ }
+
+ public GCodePositioningTypes ParsePositioningTypeFromGCode(string gcode)
+ {
+ using var reader = new StringReader(gcode);
+ string line;
+ while ((line = reader.ReadLine()) != null)
+ {
+ if (line.StartsWith(CommandPositioningAbsoluteG90.Command)) return GCodePositioningTypes.Absolute;
+ if (line.StartsWith(CommandPositioningPartialG91.Command)) return GCodePositioningTypes.Partial;
+ }
+
+ return _gCodePositioningType;
+ }
+
+ public void ParseLayersFromGCode(FileFormat slicerFile, bool rebuildGlobalTable = true) =>
+ ParseLayersFromGCode(slicerFile, null, rebuildGlobalTable);
+
+ public void ParseLayersFromGCode(FileFormat slicerFile, string gcode, bool rebuildGlobalTable = true)
+ {
+ if (slicerFile.LayerCount == 0) return;
+
+ if (string.IsNullOrWhiteSpace(gcode))
+ {
+ gcode = _gcode.ToString();
+ if (string.IsNullOrWhiteSpace(gcode)) return;
+ }
+
+ var positionType = ParsePositioningTypeFromGCode(gcode);
+
+
+ float positionZ = 0;
+ for (uint layerIndex = 0; layerIndex < slicerFile.LayerCount; layerIndex++)
+ {
+ var layer = slicerFile[layerIndex];
+ if(layer is null) continue;
+ var startStr = CommandShowImageM6054.ToStringWithoutComments(GetShowImageString(layerIndex));
+ var endStr = CommandShowImageM6054.ToStringWithoutComments(GetShowImageString(layerIndex+1));
+ gcode = gcode.Substring(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length + 1);
+ var endStrIndex = gcode.IndexOf(endStr, StringComparison.Ordinal);
+ var stripGcode = endStrIndex > 0 ? gcode.Substring(0, endStrIndex) : gcode;/*.Trim(' ', '\n', '\r', '\t');*/
+
+ float liftHeight = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLiftHeight, slicerFile.LiftHeight);
+ float liftSpeed = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLiftSpeed, slicerFile.LiftSpeed);
+ float retractSpeed = slicerFile.RetractSpeed;
+ float lightOffDelay = 0;
+ byte pwm = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLightPWM, slicerFile.LightPWM);
+ float exposureTime = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomExposureTime, slicerFile.ExposureTime);
+ var moveG0Regex = Regex.Match(stripGcode, CommandMoveG0.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(\d+)"), RegexOptions.IgnoreCase);
+ var moveG1Regex = Regex.Match(stripGcode, CommandMoveG1.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(\d+)"), RegexOptions.IgnoreCase);
+ var waitG4Regex = Regex.Match(stripGcode, CommandWaitG4.ToStringWithoutComments(@"(\d+)"), RegexOptions.IgnoreCase);
+ var pwmM106Regex = Regex.Match(stripGcode, CommandTurnLEDM106.ToStringWithoutComments(@"(\d+)"), RegexOptions.IgnoreCase);
+ var moveRegex = moveG0Regex.Success ? moveG0Regex : moveG1Regex;
+
+ if (moveRegex.Success)
+ {
+ float liftPosTemp = float.Parse(moveRegex.Groups[1].Value, CultureInfo.InvariantCulture);
+ float liftSpeedTemp = float.Parse(moveRegex.Groups[3].Value, CultureInfo.InvariantCulture);
+
+ switch (GCodeSpeedUnit)
+ {
+ case GCodeSpeedUnits.MillimetersPerSecond:
+ liftSpeedTemp = (float) Math.Round(liftSpeedTemp / 60, 2);
+ break;
+ case GCodeSpeedUnits.MillimetersPerMinute:
+ break;
+ case GCodeSpeedUnits.CentimetersPerMinute:
+ liftSpeedTemp *= 10;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ moveRegex = moveRegex.NextMatch();
+ if (moveRegex.Success)
+ {
+ float retractPos = float.Parse(moveRegex.Groups[1].Value, CultureInfo.InvariantCulture);
+ retractSpeed = float.Parse(moveRegex.Groups[3].Value, CultureInfo.InvariantCulture);
+ liftSpeed = liftSpeedTemp;
+
+ switch (positionType)
+ {
+ case GCodePositioningTypes.Absolute:
+ liftHeight = Layer.RoundHeight(liftPosTemp - retractPos);
+ positionZ = retractPos;
+ break;
+ case GCodePositioningTypes.Partial:
+ liftHeight = liftPosTemp;
+ positionZ = Layer.RoundHeight(positionZ + liftPosTemp + retractPos);
+ break;
+ }
+
+ switch (GCodeSpeedUnit)
+ {
+ case GCodeSpeedUnits.MillimetersPerSecond:
+ retractSpeed = (float)Math.Round(retractSpeed / 60, 2);
+ break;
+ case GCodeSpeedUnits.MillimetersPerMinute:
+ break;
+ case GCodeSpeedUnits.CentimetersPerMinute:
+ retractSpeed *= 10;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ else
+ {
+ switch (positionType)
+ {
+ case GCodePositioningTypes.Absolute:
+ positionZ = liftPosTemp;
+ break;
+ case GCodePositioningTypes.Partial:
+ positionZ = Layer.RoundHeight(positionZ + liftPosTemp);
+ break;
+ }
+
+ }
+ }
+
+ if (pwmM106Regex.Success)
+ {
+ if (_maxLedPower == byte.MaxValue)
+ {
+ pwm = byte.Parse(pwmM106Regex.Groups[1].Value);
+ }
+ else
+ {
+ ushort pwmValue = ushort.Parse(pwmM106Regex.Groups[1].Value);
+ pwm = (byte)(pwmValue * byte.MaxValue / _maxLedPower);
+ }
+ }
+
+ if (waitG4Regex.Success)
+ {
+ lightOffDelay = float.Parse(waitG4Regex.Groups[1].Value, CultureInfo.InvariantCulture);
+ switch (GCodeTimeUnit)
+ {
+ case GCodeTimeUnits.Milliseconds:
+ lightOffDelay = TimeExtensions.MillisecondsToSeconds(lightOffDelay);
+ break;
+ }
+
+ waitG4Regex = waitG4Regex.NextMatch();
+ if (waitG4Regex.Success)
+ {
+ exposureTime = float.Parse(waitG4Regex.Groups[1].Value, CultureInfo.InvariantCulture);
+ switch (GCodeTimeUnit)
+ {
+ case GCodeTimeUnits.Milliseconds:
+ exposureTime = TimeExtensions.MillisecondsToSeconds(exposureTime);
+ break;
+ }
+ }
+ else // Only one match, meaning light off delay is not present
+ {
+ lightOffDelay = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLightOffDelay, slicerFile.LightOffDelay);
+ }
+ }
+
+ layer.PositionZ = positionZ;
+ layer.ExposureTime = exposureTime;
+ layer.LiftHeight = liftHeight;
+ layer.LiftSpeed = liftSpeed;
+ layer.RetractSpeed = retractSpeed;
+ layer.LightOffDelay = lightOffDelay;
+ layer.LightPWM = pwm;
+ }
+
+ if (rebuildGlobalTable)
+ {
+ slicerFile.SuppressRebuildProperties = true;
+ var bottomLayer = slicerFile[0];
+ if (bottomLayer is not null)
+ {
+ if (bottomLayer.ExposureTime > 0) slicerFile.BottomExposureTime = bottomLayer.ExposureTime;
+ if (bottomLayer.LiftHeight > 0) slicerFile.BottomLiftHeight = bottomLayer.LiftHeight;
+ if (bottomLayer.LiftSpeed > 0) slicerFile.BottomLiftSpeed = bottomLayer.LiftSpeed;
+ if (bottomLayer.RetractSpeed > 0) slicerFile.RetractSpeed = bottomLayer.RetractSpeed;
+ if (bottomLayer.LightOffDelay > 0) slicerFile.BottomLightOffDelay = bottomLayer.LightOffDelay;
+ if (bottomLayer.LightPWM > 0) slicerFile.BottomLightPWM = bottomLayer.LightPWM;
+ }
+
+ var normalLayer = slicerFile[slicerFile.LastLayerIndex];
+ if (normalLayer is not null)
+ {
+ if (normalLayer.ExposureTime > 0) slicerFile.ExposureTime = normalLayer.ExposureTime;
+ if (normalLayer.LiftHeight > 0) slicerFile.LiftHeight = normalLayer.LiftHeight;
+ if (normalLayer.LiftSpeed > 0) slicerFile.LiftSpeed = normalLayer.LiftSpeed;
+ if (normalLayer.RetractSpeed > 0) slicerFile.RetractSpeed = normalLayer.RetractSpeed;
+ if (normalLayer.LightOffDelay > 0) slicerFile.LightOffDelay = normalLayer.LightOffDelay;
+ if (normalLayer.LightPWM > 0) slicerFile.LightPWM = normalLayer.LightPWM;
+ }
+
+ slicerFile.SuppressRebuildProperties = false;
+ }
+ }
+
+ public StringReader GetStringReader()
+ {
+ return new(_gcode.ToString());
+ }
+ }
+ #endregion
+}
diff --git a/UVtools.Core/GCode/GCodeCommand.cs b/UVtools.Core/GCode/GCodeCommand.cs
new file mode 100644
index 0000000..98f8c61
--- /dev/null
+++ b/UVtools.Core/GCode/GCodeCommand.cs
@@ -0,0 +1,120 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+namespace UVtools.Core.GCode
+{
+ public class GCodeCommand
+ {
+ /// <summary>
+ /// Gets or sets if this command is enabled
+ /// </summary>
+ public bool Enabled { get; set; } = true;
+
+ /// <summary>
+ /// Gets or sets the command name
+ /// </summary>
+ public string Command { get; set; }
+
+ /// <summary>
+ /// Gets or sets the arguments for this command
+ /// </summary>
+ public string Arguments { get; set; }
+
+ /// <summary>
+ /// Gets or sets the comment
+ /// </summary>
+ public string Comment { get; set; }
+
+ public GCodeCommand() { }
+
+ public GCodeCommand(string command, string arguments = null, string comment = null, bool enabled = true)
+ {
+ Enabled = enabled;
+ Command = command;
+ Arguments = arguments;
+ Comment = comment;
+ }
+
+ public void Reset(string command, string arguments, string comment, bool enabled = true)
+ {
+ Enabled = enabled;
+ Command = command;
+ Arguments = arguments;
+ Comment = comment;
+ }
+
+ public void Reset(string command, string arguments)
+ {
+ Command = command;
+ Arguments = arguments;
+ }
+
+ public void Reset(string command)
+ {
+ Command = command;
+ }
+
+
+ public string ToString(bool showComment, bool showTailComma = true, string overrideComment = null)
+ {
+ var result = Command;
+ if (!string.IsNullOrWhiteSpace(Arguments))
+ result += $" {Arguments}";
+
+ if (result[0] == ';') return result;
+
+ var comment = string.IsNullOrWhiteSpace(overrideComment) ? Comment : overrideComment;
+ if (showComment && !string.IsNullOrWhiteSpace(comment))
+ {
+ result += $";{comment}";
+ }
+ else if (showTailComma)
+ {
+ result += ';';
+ }
+
+ return result;
+ }
+ public string ToString(bool showComment, bool showTailComma = true, params object[] args) =>
+ string.Format(ToString(showComment, showTailComma), args);
+ public string ToStringOverrideComment(string comment, params object[] args) => ToStringOverrideComment(true, true, comment, args);
+ public string ToStringOverrideComment(bool showComment, bool showTailComma, string comment, params object[] args) =>
+ string.Format(ToString(showComment, showTailComma, comment), args);
+ public string ToString(params object[] args) => ToString(true, true, args);
+ public string ToStringWithoutComments() => ToString(false, false);
+ public string ToStringWithoutComments(params object[] args) => ToString(false, false, args);
+
+ public override string ToString()
+ {
+ var result = Command;
+ if (!string.IsNullOrWhiteSpace(Arguments))
+ result += $" {Arguments}";
+ if (!string.IsNullOrWhiteSpace(Comment))
+ result += $";{Comment}";
+ return result;
+ }
+
+ protected bool Equals(GCodeCommand other)
+ {
+ return Command == other.Command;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((GCodeCommand) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return (Command != null ? Command.GetHashCode() : 0);
+ }
+ }
+}
diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs
index f5828c5..a202ccf 100644
--- a/UVtools.Core/Layer/Layer.cs
+++ b/UVtools.Core/Layer/Layer.cs
@@ -9,7 +9,6 @@ using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
-using System.Timers;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
@@ -25,6 +24,10 @@ namespace UVtools.Core
/// </summary>
public class Layer : BindableBase, IEquatable<Layer>, IEquatable<uint>
{
+ #region Constants
+ public const byte HeightPrecision = 3;
+ #endregion
+
#region Members
private byte[] _compressedBytes;
private uint _nonZeroPixelCount;
@@ -66,6 +69,11 @@ namespace UVtools.Core
}
}
+ /// <summary>
+ /// Gets if this layer is empty/all black pixels
+ /// </summary>
+ public bool IsEmpty => _nonZeroPixelCount == 0;
+
public float ExposureMillimeters
{
get
@@ -196,6 +204,11 @@ namespace UVtools.Core
}
/// <summary>
+ /// Gets if this layer can be exposed to UV light
+ /// </summary>
+ public bool CanExpose => _exposureTime > 0 && _lightPwm > 0;
+
+ /// <summary>
/// Gets or sets the layer position on Z in mm
/// </summary>
public float PositionZ
@@ -220,7 +233,7 @@ namespace UVtools.Core
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);
+ var layerHeight = RoundHeight(_positionZ - previousLayer.PositionZ);
//Debug.WriteLine($"Layer {_index}-{previousLayer.Index}: {_positionZ} - {previousLayer.PositionZ}: {layerHeight}");
if (layerHeight == 0f) continue;
if (layerHeight < 0f) break;
@@ -503,12 +516,10 @@ namespace UVtools.Core
needDispose = true;
}
- using (var nonZeroMat = new Mat())
- {
- CvInvoke.FindNonZero(mat, nonZeroMat);
- NonZeroPixelCount = (uint)nonZeroMat.Height;
- BoundingRectangle = NonZeroPixelCount > 0 ? CvInvoke.BoundingRectangle(nonZeroMat) : Rectangle.Empty;
- }
+ using var nonZeroMat = new Mat();
+ CvInvoke.FindNonZero(mat, nonZeroMat);
+ NonZeroPixelCount = (uint)nonZeroMat.Height;
+ BoundingRectangle = NonZeroPixelCount > 0 ? CvInvoke.BoundingRectangle(nonZeroMat) : Rectangle.Empty;
if (needDispose) mat.Dispose();
@@ -779,7 +790,17 @@ namespace UVtools.Core
MaterialMilliliters = _materialMilliliters
};
}
+ #endregion
+
+ #region Static Methods
+
+ public static float RoundHeight(float height) => (float) Math.Round(height, HeightPrecision);
+ public static double RoundHeight(double height) => Math.Round(height, HeightPrecision);
+ public static decimal RoundHeight(decimal height) => Math.Round(height, HeightPrecision);
+ public static string ShowHeight(float height) => string.Format($"{{0:F{HeightPrecision}}}", height);
+ public static string ShowHeight(double height) => string.Format($"{{0:F{HeightPrecision}}}", height);
+ public static string ShowHeight(decimal height) => string.Format($"{{0:F{HeightPrecision}}}", height);
#endregion
}
diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs
index dda835f..600647c 100644
--- a/UVtools.Core/Layer/LayerManager.cs
+++ b/UVtools.Core/Layer/LayerManager.cs
@@ -47,10 +47,21 @@ namespace UVtools.Core
SlicerFile.RequireFullEncode = true;
if (value is null) return;
SlicerFile.PrintTime = SlicerFile.PrintTimeComputed;
+ SlicerFile.RebuildGCode();
}
}
/// <summary>
+ /// Gets the first layer
+ /// </summary>
+ public Layer FirstLayer => _layers?[0];
+
+ /// <summary>
+ /// Gets the last layer
+ /// </summary>
+ public Layer LastLayer => _layers?[^1];
+
+ /// <summary>
/// Gets the bounding rectangle of the object
/// </summary>
private Rectangle _boundingRectangle = Rectangle.Empty;
@@ -304,8 +315,8 @@ namespace UVtools.Core
public Rectangle GetBoundingRectangle(OperationProgress progress = null)
{
- progress ??= new OperationProgress(OperationProgress.StatusOptimizingBounds, Count-1);
if (!_boundingRectangle.IsEmpty || Count == 0 || this[0] is null) return _boundingRectangle;
+ progress ??= new OperationProgress(OperationProgress.StatusOptimizingBounds, Count - 1);
_boundingRectangle = this[0].BoundingRectangle;
if (_boundingRectangle.IsEmpty) // Safe checking
{
@@ -350,9 +361,12 @@ namespace UVtools.Core
/// <param name="makeClone">True to add a clone of the layer</param>
public void AddLayer(uint index, Layer layer, bool makeClone = false)
{
- //layer.Index = index;
- Layers[index] = makeClone ? layer.Clone() : layer;
- layer.ParentLayerManager = this;
+ if (Layers[index] is not null && layer is not null) layer.IsModified = true;
+ Layers[index] = makeClone && layer is not null ? layer.Clone() : layer;
+ if (layer is not null)
+ {
+ layer.ParentLayerManager = this;
+ }
}
/// <summary>
@@ -410,29 +424,32 @@ namespace UVtools.Core
return MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, layerIndex);
}
- public List<LayerIssue> GetAllIssues(
+ public unsafe List<LayerIssue> GetAllIssuesBeta(
IslandDetectionConfiguration islandConfig = null,
- OverhangDetectionConfiguration overhangConfig = null,
+ OverhangDetectionConfiguration overhangConfig = null,
ResinTrapDetectionConfiguration resinTrapConfig = null,
TouchingBoundDetectionConfiguration touchBoundConfig = null,
bool emptyLayersConfig = true,
List<LayerIssue> ignoredIssues = null,
OperationProgress progress = null)
{
-
islandConfig ??= new IslandDetectionConfiguration();
overhangConfig ??= new OverhangDetectionConfiguration();
resinTrapConfig ??= new ResinTrapDetectionConfiguration();
touchBoundConfig ??= new TouchingBoundDetectionConfiguration();
progress ??= new OperationProgress();
-
+
var result = new ConcurrentBag<LayerIssue>();
- var layerHollowAreas = new ConcurrentDictionary<uint, List<LayerHollowArea>>();
+ if (!islandConfig.Enabled && !overhangConfig.Enabled && !resinTrapConfig.Enabled && !touchBoundConfig.Enabled && !emptyLayersConfig) return result.ToList();
- bool islandsFinished = false;
+ ConcurrentDictionary<uint, List<LayerHollowArea>> layerHollowAreas = new();
+ List<uint> actionLayers = new();
+ bool checkedEmptyLayers = false;
+ Mat emptyMat = new ();
+ Mat[] cachedLayers = new Mat[Count];
+ const uint cacheCount = 300;
bool IsIgnored(LayerIssue issue) => !(ignoredIssues is null) && ignoredIssues.Count > 0 && ignoredIssues.Contains(issue);
-
bool AddIssue(LayerIssue issue)
{
if (IsIgnored(issue)) return false;
@@ -440,417 +457,632 @@ namespace UVtools.Core
return true;
}
- progress.Reset(OperationProgress.StatusIslands, Count);
+ Mat GetCachedMat(uint layerIndex)
+ {
+ if (cachedLayers[layerIndex] is not null) return cachedLayers[layerIndex];
+ Parallel.For(layerIndex, Math.Min(layerIndex + cacheCount, Count), i =>
+ {
+ if (this[i].IsEmpty) return; // empty layers
+ cachedLayers[i] = this[i].LayerMat;
+ });
+ return cachedLayers[layerIndex];
+ }
- Parallel.Invoke(() =>
+
+ if (touchBoundConfig.Enabled || resinTrapConfig.Enabled ||
+ (islandConfig.Enabled && islandConfig.WhiteListLayers is null) ||
+ (overhangConfig.Enabled && overhangConfig.WhiteListLayers is null)
+ )
{
- if (!islandConfig.Enabled && !overhangConfig.Enabled && !touchBoundConfig.Enabled)
+ checkedEmptyLayers = true;
+ for (uint layerIndex = 0; layerIndex < Count; layerIndex++)
{
- islandsFinished = true;
- return;
+ var layer = this[layerIndex];
+ if (layer.IsEmpty)
+ {
+ cachedLayers[layerIndex] = emptyMat;
+ if (emptyLayersConfig)
+ {
+ AddIssue(new LayerIssue(layer, LayerIssue.IssueType.EmptyLayer));
+ }
+ continue;
+ }
+ actionLayers.Add(layerIndex);
}
+ }
- // Detect contours
- Parallel.ForEach(this,
- //new ParallelOptions{MaxDegreeOfParallelism = 1},
- layer =>
+ if (!checkedEmptyLayers && emptyLayersConfig)
+ {
+ checkedEmptyLayers = true;
+ for (uint layerIndex = 0; layerIndex < Count; layerIndex++)
+ {
+ var layer = this[layerIndex];
+ if (layer.IsEmpty)
{
- if (progress.Token.IsCancellationRequested) return;
- if (layer.NonZeroPixelCount == 0)
+ AddIssue(new LayerIssue(layer, LayerIssue.IssueType.EmptyLayer));
+ }
+ }
+ }
+
+ for (var i = 0; i < actionLayers.Count; i++)
+ {
+ var rootLayerIndex = actionLayers[i];
+ GetCachedMat(rootLayerIndex);
+ progress.Token.ThrowIfCancellationRequested();
+ uint layerAdvance = (uint) Math.Min(i + cacheCount, actionLayers.Count);
+ Parallel.For(i, layerAdvance, l =>
+ {
+ var layerIndex = actionLayers[(int) l];
+ var layer = this[layerIndex];
+ if (layer.IsEmpty)
+ {
+ lock (progress.Mutex)
{
- if (emptyLayersConfig)
- {
- AddIssue(new LayerIssue(layer, LayerIssue.IssueType.EmptyLayer));
- }
+ progress++;
+ }
+
+ return; // Empty layer
+ }
+
+ var image = cachedLayers[layerIndex];
- lock (progress.Mutex)
+ int step = image.Step;
+ var span = image.GetBytePointer();
+
+ if (touchBoundConfig.Enabled)
+ {
+ // TouchingBounds Checker
+ List<Point> pixels = new List<Point>();
+ bool touchTop = layer.BoundingRectangle.Top <= touchBoundConfig.MarginTop;
+ bool touchBottom = layer.BoundingRectangle.Bottom >= image.Height - touchBoundConfig.MarginBottom;
+ bool touchLeft = layer.BoundingRectangle.Left <= touchBoundConfig.MarginLeft;
+ bool touchRight = layer.BoundingRectangle.Right >= image.Width - touchBoundConfig.MarginRight;
+ if (touchTop || touchBottom)
+ {
+ for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds
{
- progress++;
- }
+ if (touchTop)
+ {
+ for (int y = 0; y < touchBoundConfig.MarginTop; y++) // Top
+ {
+ if (span[image.GetPixelPos(x, y)] >=
+ touchBoundConfig.MinimumPixelBrightness)
+ {
+ pixels.Add(new Point(x, y));
+ }
+ }
+ }
- return;
+ if (touchBottom)
+ {
+ for (int y = image.Height - touchBoundConfig.MarginBottom;
+ y < image.Height;
+ y++) // Bottom
+ {
+ if (span[image.GetPixelPos(x, y)] >=
+ touchBoundConfig.MinimumPixelBrightness)
+ {
+ pixels.Add(new Point(x, y));
+ }
+ }
+ }
+ }
}
- // Spare a decoding cycle
- if (!touchBoundConfig.Enabled &&
- !overhangConfig.Enabled &&
- (layer.Index == 0 ||
- (
- (overhangConfig.WhiteListLayers is not null && !overhangConfig.WhiteListLayers.Contains(layer.Index)) &&
- (islandConfig.WhiteListLayers is not null && !islandConfig.WhiteListLayers.Contains(layer.Index))
- )
- )
- )
+ if (touchLeft || touchRight)
{
- lock (progress.Mutex)
+ for (int y = touchBoundConfig.MarginTop;
+ y < image.Height - touchBoundConfig.MarginBottom;
+ y++) // Check Left and Right bounds
{
- progress++;
+ if (touchLeft)
+ {
+ for (int x = 0; x < touchBoundConfig.MarginLeft; x++) // Left
+ {
+ if (span[image.GetPixelPos(x, y)] >=
+ touchBoundConfig.MinimumPixelBrightness)
+ {
+ pixels.Add(new Point(x, y));
+ }
+ }
+ }
+
+ if (touchRight)
+ {
+ for (int x = image.Width - touchBoundConfig.MarginRight; x < image.Width; x++) // Right
+ {
+ if (span[image.GetPixelPos(x, y)] >=
+ touchBoundConfig.MinimumPixelBrightness)
+ {
+ pixels.Add(new Point(x, y));
+ }
+ }
+ }
}
- return;
}
- using (var image = layer.LayerMat)
+ if (pixels.Count > 0)
+ {
+ AddIssue(new LayerIssue(layer, LayerIssue.IssueType.TouchingBound,
+ pixels.ToArray()));
+ }
+ }
+
+ lock (progress.Mutex)
+ {
+ progress++;
+ }
+ });
+
+ for (; i < layerAdvance - 1; i++)
+ {
+ cachedLayers[actionLayers[i]].Dispose();
+ cachedLayers[actionLayers[i]] = null;
+ }
+
+
+ // Wait for jobs
+ /*foreach (var task in taskList)
+ {
+ task.Wait();
+ }
+
+ if (layerIndex > 0)
+ {
+ cachedLayers[layerIndex - 1].Dispose();
+ cachedLayers[layerIndex - 1] = null;
+ }*/
+ }
+
+ return result.ToList();
+ }
+
+ public List<LayerIssue> GetAllIssues(
+ IslandDetectionConfiguration islandConfig = null,
+ OverhangDetectionConfiguration overhangConfig = null,
+ ResinTrapDetectionConfiguration resinTrapConfig = null,
+ TouchingBoundDetectionConfiguration touchBoundConfig = null,
+ bool emptyLayersConfig = true,
+ List<LayerIssue> ignoredIssues = null,
+ OperationProgress progress = null)
+ {
+
+ islandConfig ??= new IslandDetectionConfiguration();
+ overhangConfig ??= new OverhangDetectionConfiguration();
+ resinTrapConfig ??= new ResinTrapDetectionConfiguration();
+ touchBoundConfig ??= new TouchingBoundDetectionConfiguration();
+ progress ??= new OperationProgress();
+
+ var result = new ConcurrentBag<LayerIssue>();
+ var layerHollowAreas = new ConcurrentDictionary<uint, List<LayerHollowArea>>();
+
+ bool IsIgnored(LayerIssue issue) => !(ignoredIssues is null) && ignoredIssues.Count > 0 && ignoredIssues.Contains(issue);
+
+ bool AddIssue(LayerIssue issue)
+ {
+ if (IsIgnored(issue)) return false;
+ result.Add(issue);
+ return true;
+ }
+
+ if (islandConfig.Enabled || overhangConfig.Enabled || resinTrapConfig.Enabled || touchBoundConfig.Enabled || emptyLayersConfig)
+ {
+ progress.Reset(OperationProgress.StatusIslands, Count);
+
+ // Detect contours
+ Parallel.ForEach(this,
+ //new ParallelOptions{MaxDegreeOfParallelism = 1},
+ layer =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ if (layer.IsEmpty)
+ {
+ if (emptyLayersConfig)
{
- int step = image.Step;
- var span = image.GetPixelSpan<byte>();
+ AddIssue(new LayerIssue(layer, LayerIssue.IssueType.EmptyLayer));
+ }
+
+ progress.LockAndIncrement();
+
+ return;
+ }
+
+ // Spare a decoding cycle
+ if (!touchBoundConfig.Enabled &&
+ !resinTrapConfig.Enabled &&
+ !overhangConfig.Enabled || overhangConfig.Enabled && (layer.Index == 0 || overhangConfig.WhiteListLayers is not null && !overhangConfig.WhiteListLayers.Contains(layer.Index)) &&
+ !islandConfig.Enabled || islandConfig.Enabled && (layer.Index == 0 || islandConfig.WhiteListLayers is not null && !islandConfig.WhiteListLayers.Contains(layer.Index))
+ )
+ {
+ progress.LockAndIncrement();
+
+ return;
+ }
+
+ using (var image = layer.LayerMat)
+ {
+ int step = image.Step;
+ var span = image.GetPixelSpan<byte>();
- if (touchBoundConfig.Enabled)
+ if (touchBoundConfig.Enabled)
+ {
+ // TouchingBounds Checker
+ List<Point> pixels = new ();
+ bool touchTop = layer.BoundingRectangle.Top <= touchBoundConfig.MarginTop;
+ bool touchBottom = layer.BoundingRectangle.Bottom >=
+ image.Height - touchBoundConfig.MarginBottom;
+ bool touchLeft = layer.BoundingRectangle.Left <= touchBoundConfig.MarginLeft;
+ bool touchRight = layer.BoundingRectangle.Right >=
+ image.Width - touchBoundConfig.MarginRight;
+ if (touchTop || touchBottom)
{
- // TouchingBounds Checker
- List<Point> pixels = new List<Point>();
- bool touchTop = layer.BoundingRectangle.Top <= touchBoundConfig.MarginTop;
- bool touchBottom = layer.BoundingRectangle.Bottom >= image.Height - touchBoundConfig.MarginBottom;
- bool touchLeft = layer.BoundingRectangle.Left <= touchBoundConfig.MarginLeft;
- bool touchRight = layer.BoundingRectangle.Right >= image.Width - touchBoundConfig.MarginRight;
- if (touchTop || touchBottom)
+ for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds
{
- for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds
+ if (touchTop)
{
- if (touchTop)
+ for (int y = 0; y < touchBoundConfig.MarginTop; y++) // Top
{
- for (int y = 0; y < touchBoundConfig.MarginTop; y++) // Top
+ if (span[image.GetPixelPos(x, y)] >=
+ touchBoundConfig.MinimumPixelBrightness)
{
- if (span[image.GetPixelPos(x, y)] >=
- touchBoundConfig.MinimumPixelBrightness)
- {
- pixels.Add(new Point(x, y));
- }
+ pixels.Add(new Point(x, y));
}
}
+ }
- if (touchBottom)
+ if (touchBottom)
+ {
+ for (int y = image.Height - touchBoundConfig.MarginBottom;
+ y < image.Height;
+ y++) // Bottom
{
- for (int y = image.Height - touchBoundConfig.MarginBottom; y < image.Height; y++) // Bottom
+ if (span[image.GetPixelPos(x, y)] >=
+ touchBoundConfig.MinimumPixelBrightness)
{
- if (span[image.GetPixelPos(x, y)] >=
- touchBoundConfig.MinimumPixelBrightness)
- {
- pixels.Add(new Point(x, y));
- }
+ pixels.Add(new Point(x, y));
}
}
-
}
+
}
+ }
- if (touchLeft || touchRight)
+ if (touchLeft || touchRight)
+ {
+ for (int y = touchBoundConfig.MarginTop;
+ y < image.Height - touchBoundConfig.MarginBottom;
+ y++) // Check Left and Right bounds
{
- for (int y = touchBoundConfig.MarginTop; y < image.Height - touchBoundConfig.MarginBottom; y++) // Check Left and Right bounds
+ if (touchLeft)
{
- if (touchLeft)
+ for (int x = 0; x < touchBoundConfig.MarginLeft; x++) // Left
{
- for (int x = 0; x < touchBoundConfig.MarginLeft; x++) // Left
+ if (span[image.GetPixelPos(x, y)] >=
+ touchBoundConfig.MinimumPixelBrightness)
{
- if (span[image.GetPixelPos(x, y)] >=
- touchBoundConfig.MinimumPixelBrightness)
- {
- pixels.Add(new Point(x, y));
- }
+ pixels.Add(new Point(x, y));
}
}
+ }
- if (touchRight)
+ if (touchRight)
+ {
+ for (int x = image.Width - touchBoundConfig.MarginRight;
+ x < image.Width;
+ x++) // Right
{
- for (int x = image.Width - touchBoundConfig.MarginRight; x < image.Width; x++) // Right
+ if (span[image.GetPixelPos(x, y)] >=
+ touchBoundConfig.MinimumPixelBrightness)
{
- if (span[image.GetPixelPos(x, y)] >=
- touchBoundConfig.MinimumPixelBrightness)
- {
- pixels.Add(new Point(x, y));
- }
+ pixels.Add(new Point(x, y));
}
}
}
}
-
- if (pixels.Count > 0)
- {
- AddIssue(new LayerIssue(layer, LayerIssue.IssueType.TouchingBound,
- pixels.ToArray()));
- }
}
- if (layer.Index == 0)
+ if (pixels.Count > 0)
{
- lock (progress.Mutex)
- {
- progress++;
- }
-
- return; // No islands nor overhangs for layer 0
+ AddIssue(new LayerIssue(layer, LayerIssue.IssueType.TouchingBound,
+ pixels.ToArray()));
}
+ }
+ if (layer.Index > 0) // No islands nor overhangs for layer 0
+ {
Mat previousImage = null;
Span<byte> previousSpan = null;
if (islandConfig.Enabled)
{
+ bool canProcessCheck = true;
if (islandConfig.WhiteListLayers is not null) // Check white list
{
if (!islandConfig.WhiteListLayers.Contains(layer.Index))
{
- lock (progress.Mutex)
- {
- progress++;
- }
-
- return;
+ canProcessCheck = false;
}
}
- if (islandConfig.BinaryThreshold > 0)
+ if (canProcessCheck)
{
- CvInvoke.Threshold(image, image, islandConfig.BinaryThreshold, 255,
- ThresholdType.Binary);
- }
-
- using (Mat labels = new Mat())
- using (Mat stats = new Mat())
- using (Mat centroids = new Mat())
- {
- var numLabels = CvInvoke.ConnectedComponentsWithStats(image, labels, stats,
- centroids,
- islandConfig.AllowDiagonalBonds
- ? LineType.EightConnected
- : LineType.FourConnected);
-
- // Get array that contains details of each connected component
- var ccStats = stats.GetData();
- //stats[i][0]: Left Edge of Connected Component
- //stats[i][1]: Top Edge of Connected Component
- //stats[i][2]: Width of Connected Component
- //stats[i][3]: Height of Connected Component
- //stats[i][4]: Total Area (in pixels) in Connected Component
-
- Span<int> labelSpan = labels.GetPixelSpan<int>();
-
- for (int i = 1; i < numLabels; i++)
+ bool needDispose = false;
+ Mat islandImage;
+ if (islandConfig.BinaryThreshold > 0)
+ {
+ needDispose = true;
+ islandImage = new();
+ CvInvoke.Threshold(image, islandImage, islandConfig.BinaryThreshold, byte.MaxValue,
+ ThresholdType.Binary);
+ }
+ else
{
- Rectangle rect = new Rectangle(
- (int) ccStats.GetValue(i, (int) ConnectedComponentsTypes.Left),
- (int) ccStats.GetValue(i, (int) ConnectedComponentsTypes.Top),
- (int) ccStats.GetValue(i, (int) ConnectedComponentsTypes.Width),
- (int) ccStats.GetValue(i, (int) ConnectedComponentsTypes.Height));
+ islandImage = image;
+ }
- if (rect.Area() < islandConfig.RequiredAreaToProcessCheck)
- continue;
+ using (Mat labels = new Mat())
+ using (Mat stats = new Mat())
+ using (Mat centroids = new Mat())
+ {
+ var numLabels = CvInvoke.ConnectedComponentsWithStats(islandImage, labels, stats,
+ centroids,
+ islandConfig.AllowDiagonalBonds
+ ? LineType.EightConnected
+ : LineType.FourConnected);
- if (previousImage is null)
+ if(needDispose)
{
- previousImage = this[layer.Index - 1].LayerMat;
- previousSpan = previousImage.GetPixelSpan<byte>();
+ islandImage.Dispose();
}
- List<Point> points = new List<Point>();
- uint pixelsSupportingIsland = 0;
+ // Get array that contains details of each connected component
+ var ccStats = stats.GetData();
+ //stats[i][0]: Left Edge of Connected Component
+ //stats[i][1]: Top Edge of Connected Component
+ //stats[i][2]: Width of Connected Component
+ //stats[i][3]: Height of Connected Component
+ //stats[i][4]: Total Area (in pixels) in Connected Component
+
+ Span<int> labelSpan = labels.GetPixelSpan<int>();
- for (int y = rect.Y; y < rect.Bottom; y++)
- for (int x = rect.X; x < rect.Right; x++)
+ for (int i = 1; i < numLabels; i++)
{
- int pixel = step * y + x;
- if (
- labelSpan[pixel] !=
- i || // Background pixel or a pixel from another component within the bounding rectangle
- span[pixel] <
- islandConfig
- .RequiredPixelBrightnessToProcessCheck // Low brightness, ignore
- ) continue;
-
- points.Add(new Point(x, y));
-
- if (previousSpan[pixel] >=
- islandConfig.RequiredPixelBrightnessToSupport)
+ Rectangle rect = new Rectangle(
+ (int) ccStats.GetValue(i, (int) ConnectedComponentsTypes.Left),
+ (int) ccStats.GetValue(i, (int) ConnectedComponentsTypes.Top),
+ (int) ccStats.GetValue(i, (int) ConnectedComponentsTypes.Width),
+ (int) ccStats.GetValue(i, (int) ConnectedComponentsTypes.Height));
+
+ if (rect.Area() < islandConfig.RequiredAreaToProcessCheck)
+ continue;
+
+ if (previousImage is null)
{
- pixelsSupportingIsland++;
+ previousImage = this[layer.Index - 1].LayerMat;
+ previousSpan = previousImage.GetPixelSpan<byte>();
}
- }
- if (points.Count == 0) continue; // Should never happen
+ List<Point> points = new List<Point>();
+ uint pixelsSupportingIsland = 0;
+
+ for (int y = rect.Y; y < rect.Bottom; y++)
+ for (int x = rect.X; x < rect.Right; x++)
+ {
+ int pixel = step * y + x;
+ if (
+ labelSpan[pixel] !=
+ i || // Background pixel or a pixel from another component within the bounding rectangle
+ span[pixel] <
+ islandConfig
+ .RequiredPixelBrightnessToProcessCheck // Low brightness, ignore
+ ) continue;
+
+ points.Add(new Point(x, y));
+
+ if (previousSpan[pixel] >=
+ islandConfig.RequiredPixelBrightnessToSupport)
+ {
+ pixelsSupportingIsland++;
+ }
+ }
- var requiredSupportingPixels = Math.Max(1, points.Count * islandConfig.RequiredPixelsToSupportMultiplier);
+ if (points.Count == 0) continue; // Should never happen
- /*if (pixelsSupportingIsland >= islandConfig.RequiredPixelsToSupport)
- isIsland = false; // Not a island, bounding is strong, i think...
- else if (pixelsSupportingIsland > 0 &&
- points.Count < islandConfig.RequiredPixelsToSupport &&
- pixelsSupportingIsland >= Math.Max(1, points.Count / 2))
- isIsland = false; // Not a island, but maybe weak bounding...*/
+ var requiredSupportingPixels = Math.Max(1,
+ points.Count * islandConfig.RequiredPixelsToSupportMultiplier);
- LayerIssue island = null;
- if (pixelsSupportingIsland < requiredSupportingPixels)
- {
- island = new LayerIssue(layer, LayerIssue.IssueType.Island,
- points.ToArray(),
- rect);
- /*AddIssue(new LayerIssue(layer, LayerIssue.IssueType.Island,
- points.ToArray(),
- rect));*/
- }
+ /*if (pixelsSupportingIsland >= islandConfig.RequiredPixelsToSupport)
+ isIsland = false; // Not a island, bounding is strong, i think...
+ else if (pixelsSupportingIsland > 0 &&
+ points.Count < islandConfig.RequiredPixelsToSupport &&
+ pixelsSupportingIsland >= Math.Max(1, points.Count / 2))
+ isIsland = false; // Not a island, but maybe weak bounding...*/
- // Check for overhangs
- if (overhangConfig.Enabled && !overhangConfig.IndependentFromIslands && island is null
- || island is not null && islandConfig.EnhancedDetection && pixelsSupportingIsland >= 10
- )
- {
- points.Clear();
- using (var imageRoi = new Mat(image, rect))
- using (var previousImageRoi = new Mat(previousImage, rect))
- using (var subtractedImage = new Mat())
+ LayerIssue island = null;
+ if (pixelsSupportingIsland < requiredSupportingPixels)
{
- var anchor = new Point(-1, -1);
- CvInvoke.Subtract(imageRoi, previousImageRoi, subtractedImage);
- CvInvoke.Threshold(subtractedImage, subtractedImage, 127, 255, ThresholdType.Binary);
+ island = new LayerIssue(layer, LayerIssue.IssueType.Island,
+ points.ToArray(),
+ rect);
+ /*AddIssue(new LayerIssue(layer, LayerIssue.IssueType.Island,
+ points.ToArray(),
+ rect));*/
+ }
- CvInvoke.Erode(subtractedImage, subtractedImage, CvInvoke.GetStructuringElement(ElementShape.Rectangle,
- new Size(3, 3), anchor),
- anchor, overhangConfig.ErodeIterations, BorderType.Default,
- new MCvScalar());
+ // Check for overhangs
+ if (overhangConfig.Enabled && !overhangConfig.IndependentFromIslands &&
+ island is null
+ || island is not null && islandConfig.EnhancedDetection &&
+ pixelsSupportingIsland >= 10
+ )
+ {
+ points.Clear();
+ using (var imageRoi = new Mat(image, rect))
+ using (var previousImageRoi = new Mat(previousImage, rect))
+ using (var subtractedImage = new Mat())
+ {
+ var anchor = new Point(-1, -1);
+ CvInvoke.Subtract(imageRoi, previousImageRoi, subtractedImage);
+ CvInvoke.Threshold(subtractedImage, subtractedImage, 127, 255,
+ ThresholdType.Binary);
- var subtractedSpan = subtractedImage.GetPixelSpan<byte>();
+ CvInvoke.Erode(subtractedImage, subtractedImage,
+ CvInvoke.GetStructuringElement(ElementShape.Rectangle,
+ new Size(3, 3), anchor),
+ anchor, overhangConfig.ErodeIterations, BorderType.Default,
+ new MCvScalar());
- for (int y = 0; y < subtractedImage.Height; y++)
- for (int x = 0; x < subtractedImage.Step; x++)
- {
- int labelX = rect.X + x;
- int labelY = rect.Y + y;
- int pixel = subtractedImage.GetPixelPos(x, y);
- int pixelLabel = labelY * step + labelX;
- if (labelSpan[pixelLabel] != i || subtractedSpan[pixel] == 0) continue;
+ var subtractedSpan = subtractedImage.GetPixelSpan<byte>();
- points.Add(new Point(labelX, labelY));
- }
+ for (int y = 0; y < subtractedImage.Height; y++)
+ for (int x = 0; x < subtractedImage.Step; x++)
+ {
+ int labelX = rect.X + x;
+ int labelY = rect.Y + y;
+ int pixel = subtractedImage.GetPixelPos(x, y);
+ int pixelLabel = labelY * step + labelX;
+ if (labelSpan[pixelLabel] != i || subtractedSpan[pixel] == 0)
+ continue;
- if (points.Count >= overhangConfig.RequiredPixelsToConsider) // Overhang
- {
- AddIssue(new LayerIssue(
- layer, LayerIssue.IssueType.Overhang, points.ToArray(), rect
- ));
- }
- else if(islandConfig.EnhancedDetection) // No overhang
- {
- island = null;
+ points.Add(new Point(labelX, labelY));
+ }
+
+ if (points.Count >= overhangConfig.RequiredPixelsToConsider
+ ) // Overhang
+ {
+ AddIssue(new LayerIssue(
+ layer, LayerIssue.IssueType.Overhang, points.ToArray(), rect
+ ));
+ }
+ else if (islandConfig.EnhancedDetection) // No overhang
+ {
+ island = null;
+ }
}
}
- }
- if(island is not null)
- AddIssue(island);
+ if (island is not null)
+ AddIssue(island);
+ }
}
-
-
}
}
-
- if (!islandConfig.Enabled && overhangConfig.Enabled ||
- (islandConfig.Enabled && overhangConfig.Enabled && overhangConfig.IndependentFromIslands))
+
+ // Overhangs
+ if (!islandConfig.Enabled && overhangConfig.Enabled ||
+ (islandConfig.Enabled && overhangConfig.Enabled &&
+ overhangConfig.IndependentFromIslands))
{
+ bool canProcessCheck = true;
if (overhangConfig.WhiteListLayers is not null) // Check white list
{
if (!overhangConfig.WhiteListLayers.Contains(layer.Index))
{
- lock (progress.Mutex)
- {
- progress++;
- }
-
- return;
+ canProcessCheck = false;
}
}
- if (previousImage is null)
+ if (canProcessCheck)
{
- previousImage = this[layer.Index - 1].LayerMat;
- }
+ if (previousImage is null)
+ {
+ previousImage = this[layer.Index - 1].LayerMat;
+ }
- using (var subtractedImage = new Mat())
- using (var vecPoints = new VectorOfPoint())
- {
- var anchor = new Point(-1, -1);
+ using (var subtractedImage = new Mat())
+ using (var vecPoints = new VectorOfPoint())
+ {
+ var anchor = new Point(-1, -1);
- CvInvoke.Subtract(image, previousImage, subtractedImage);
- CvInvoke.Threshold(subtractedImage, subtractedImage, 127, 255, ThresholdType.Binary);
+ CvInvoke.Subtract(image, previousImage, subtractedImage);
+ CvInvoke.Threshold(subtractedImage, subtractedImage, 127, 255,
+ ThresholdType.Binary);
- CvInvoke.Erode(subtractedImage, subtractedImage,
- CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3,3), anchor),
- anchor, overhangConfig.ErodeIterations, BorderType.Default, new MCvScalar());
+ CvInvoke.Erode(subtractedImage, subtractedImage,
+ CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3),
+ anchor),
+ anchor, overhangConfig.ErodeIterations, BorderType.Default,
+ new MCvScalar());
- CvInvoke.FindNonZero(subtractedImage, vecPoints);
- if (vecPoints.Size >= overhangConfig.RequiredPixelsToConsider)
- {
- AddIssue(new LayerIssue(
- layer, LayerIssue.IssueType.Overhang, vecPoints.ToArray(), layer.BoundingRectangle
- ));
+ CvInvoke.FindNonZero(subtractedImage, vecPoints);
+ if (vecPoints.Size >= overhangConfig.RequiredPixelsToConsider)
+ {
+ AddIssue(new LayerIssue(
+ layer, LayerIssue.IssueType.Overhang, vecPoints.ToArray(),
+ layer.BoundingRectangle
+ ));
+ }
}
}
}
previousImage?.Dispose();
-
}
- lock (progress.Mutex)
- {
- progress++;
- }
- }); // Parallel end
- islandsFinished = true;
- }, () =>
- {
- if (!resinTrapConfig.Enabled) return;
- // Detect contours
- Parallel.ForEach(this,
- //new ParallelOptions{MaxDegreeOfParallelism = 1},
- layer =>
- {
- if (progress.Token.IsCancellationRequested) return;
- using (var image = layer.LayerMat)
+ if (resinTrapConfig.Enabled)
{
+ bool needDispose = false;
+ Mat resinTrapImage;
if (resinTrapConfig.BinaryThreshold > 0)
{
- CvInvoke.Threshold(image, image, resinTrapConfig.BinaryThreshold, 255, ThresholdType.Binary);
+ resinTrapImage = new Mat();
+ CvInvoke.Threshold(image, resinTrapImage, resinTrapConfig.BinaryThreshold, byte.MaxValue, ThresholdType.Binary);
+ }
+ else
+ {
+ needDispose = true;
+ resinTrapImage = image;
}
var listHollowArea = new List<LayerHollowArea>();
- using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint())
- {
- using (Mat hierarchy = new Mat())
- {
+ using VectorOfVectorOfPoint contours = new();
+ using Mat hierarchy = new();
+ CvInvoke.FindContours(resinTrapImage, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple);
- CvInvoke.FindContours(image, contours, hierarchy, RetrType.Ccomp,
- ChainApproxMethod.ChainApproxSimple);
+ if(needDispose)
+ {
+ resinTrapImage.Dispose();
+ }
- var arr = hierarchy.GetData();
- //
- //hierarchy[i][0]: the index of the next contour of the same level
- //hierarchy[i][1]: the index of the previous contour of the same level
- //hierarchy[i][2]: the index of the first child
- //hierarchy[i][3]: the index of the parent
- //
+ var arr = hierarchy.GetData();
+ //
+ //hierarchy[i][0]: the index of the next contour of the same level
+ //hierarchy[i][1]: the index of the previous contour of the same level
+ //hierarchy[i][2]: the index of the first child
+ //hierarchy[i][3]: the index of the parent
+ //
- for (int i = 0; i < contours.Size; i++)
- {
- if ((int) arr.GetValue(0, i, 2) != -1 || (int) arr.GetValue(0, i, 3) == -1)
- continue;
+ for (int i = 0; i < contours.Size; i++)
+ {
+ if ((int)arr.GetValue(0, i, 2) != -1 || (int)arr.GetValue(0, i, 3) == -1)
+ continue;
- var rect = CvInvoke.BoundingRectangle(contours[i]);
- if(rect.Area() < resinTrapConfig.RequiredAreaToProcessCheck) continue;
+ var rect = CvInvoke.BoundingRectangle(contours[i]);
+ if (rect.Area() < resinTrapConfig.RequiredAreaToProcessCheck) continue;
- listHollowArea.Add(new LayerHollowArea(contours[i].ToArray(),
- rect,
- layer.Index == 0 || layer.Index == Count - 1 // First and Last layers, always drains
- ? LayerHollowArea.AreaType.Drain
- : LayerHollowArea.AreaType.Unknown));
+ listHollowArea.Add(new LayerHollowArea(contours[i].ToArray(),
+ rect,
+ layer.Index == 0 ||
+ layer.Index == Count - 1 // First and Last layers, always drains
+ ? LayerHollowArea.AreaType.Drain
+ : LayerHollowArea.AreaType.Unknown));
- if (listHollowArea.Count > 0)
- layerHollowAreas.TryAdd(layer.Index, listHollowArea);
- }
- }
+ if (listHollowArea.Count > 0)
+ layerHollowAreas.TryAdd(layer.Index, listHollowArea);
}
}
- });
+ }
+
+ progress.LockAndIncrement();
+ }); // Parallel end
+ }
+
+ if (resinTrapConfig.Enabled)
+ {
+ progress.Reset(OperationProgress.StatusResinTraps, Count);
for (uint layerIndex = 1; layerIndex < Count - 1; layerIndex++) // First and Last layers, always drains
{
@@ -860,51 +1092,54 @@ namespace UVtools.Core
byte areaCount = 0;
//foreach (var area in areas)
-
- Parallel.ForEach(from t in areas where t.Type == LayerHollowArea.AreaType.Unknown select t, area =>
+
+ //Parallel.ForEach(from t in areas where t.Type == LayerHollowArea.AreaType.Unknown select t,
+ // new ParallelOptions{MaxDegreeOfParallelism = 1}, area =>
+ foreach(var area in areas)
{
- if (progress.Token.IsCancellationRequested) return;
- if (area.Type != LayerHollowArea.AreaType.Unknown) return; // processed, ignore
+ //if (progress.Token.IsCancellationRequested) return;
+ if (area.Type != LayerHollowArea.AreaType.Unknown) continue; // processed, ignore
+ progress.Token.ThrowIfCancellationRequested();
area.Type = LayerHollowArea.AreaType.Trap;
areaCount++;
- List<LayerHollowArea> linkedAreas = new List<LayerHollowArea>();
+ List<LayerHollowArea> linkedAreas = new();
- for (sbyte dir = 1; dir >= -1 && area.Type != LayerHollowArea.AreaType.Drain; dir -= 2)
- //Parallel.ForEach(new sbyte[] {1, -1}, new ParallelOptions {MaxDegreeOfParallelism = 2}, dir =>
+ for (sbyte dir = 1; dir >= -1 && area.Type != LayerHollowArea.AreaType.Drain; dir -= 2)
+ //Parallel.ForEach(new sbyte[] {1, -1}, new ParallelOptions {MaxDegreeOfParallelism = 2}, dir =>
{
- Queue<LayerHollowArea> queue = new Queue<LayerHollowArea>();
+ Queue<LayerHollowArea> queue = new();
queue.Enqueue(area);
area.Processed = false;
int nextLayerIndex = (int) layerIndex;
while (queue.Count > 0 && area.Type != LayerHollowArea.AreaType.Drain)
{
- if (progress.Token.IsCancellationRequested) return;
+ //if (progress.Token.IsCancellationRequested) return;
+ progress.Token.ThrowIfCancellationRequested();
LayerHollowArea checkArea = queue.Dequeue();
if (checkArea.Processed) continue;
checkArea.Processed = true;
nextLayerIndex += dir;
-
+
if (nextLayerIndex < 0 || nextLayerIndex >= Count)
break; // Exhausted layers
- bool haveNextAreas = layerHollowAreas.TryGetValue((uint) nextLayerIndex, out var nextAreas);
- Dictionary<int, LayerHollowArea> intersectingAreas = new Dictionary<int, LayerHollowArea>();
+ bool haveNextAreas =
+ layerHollowAreas.TryGetValue((uint) nextLayerIndex, out var nextAreas);
+ Dictionary<int, LayerHollowArea> intersectingAreas = new();
- if (islandsFinished)
- {
- progress.Reset(OperationProgress.StatusResinTraps, Count, (uint) nextLayerIndex);
- }
+ progress.Reset(OperationProgress.StatusResinTraps, Count, (uint) nextLayerIndex);
using (var image = this[nextLayerIndex].LayerMat)
{
var span = image.GetPixelSpan<byte>();
using (var emguImage = image.CloneBlank())
{
- using(var vec = new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour)))
+ using (var vec =
+ new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour)))
{
- CvInvoke.DrawContours(emguImage, vec, -1, new MCvScalar(255), -1);
+ CvInvoke.DrawContours(emguImage, vec, -1, EmguExtensions.WhiteByte, -1);
}
using (var intersectingAreasMat = image.CloneBlank())
@@ -916,11 +1151,9 @@ namespace UVtools.Core
if (!checkArea.BoundingRectangle.IntersectsWith(
nextArea.BoundingRectangle)) continue;
intersectingAreas.Add(intersectingAreas.Count + 1, nextArea);
- using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(nextArea.Contour)))
- {
- CvInvoke.DrawContours(intersectingAreasMat, vec, -1,
- new MCvScalar(intersectingAreas.Count), -1);
- }
+ using var vec = new VectorOfVectorOfPoint(new VectorOfPoint(nextArea.Contour));
+ CvInvoke.DrawContours(intersectingAreasMat, vec, -1,
+ new MCvScalar(intersectingAreas.Count), -1);
}
}
@@ -945,7 +1178,8 @@ namespace UVtools.Core
pixelPos++;
if (spanContour[pixelPos] != 255) continue; // No contour
- if (span[pixelPos] > resinTrapConfig.MaximumPixelBrightnessToDrain) continue; // Threshold to ignore white area
+ if (span[pixelPos] > resinTrapConfig.MaximumPixelBrightnessToDrain)
+ continue; // Threshold to ignore white area
blackCount++;
if (intersectingAreas.Count > 0) // Have areas, can be on same area path or not
@@ -968,7 +1202,8 @@ namespace UVtools.Core
linkedAreas.Add(intersectingAreas[i]);
intersectingAreas.Remove(i);
- if (intersectingAreas.Count == 0) // Intersection areas sweep end, quit this path
+ if (intersectingAreas.Count == 0
+ ) // Intersection areas sweep end, quit this path
{
exitPixelLoop = true;
break;
@@ -1008,7 +1243,9 @@ namespace UVtools.Core
}*/
}
- else if (blackCount > Math.Min(checkArea.Contour.Length / 2, resinTrapConfig.RequiredBlackPixelsToDrain)) // Black pixel without next areas = Drain
+ else if (blackCount > Math.Min(checkArea.Contour.Length / 2,
+ resinTrapConfig.RequiredBlackPixelsToDrain)
+ ) // Black pixel without next areas = Drain
{
area.Type = LayerHollowArea.AreaType.Drain;
exitPixelLoop = true;
@@ -1017,7 +1254,8 @@ namespace UVtools.Core
} // X loop
} // Y loop
- if (queue.Count == 0 && blackCount > Math.Min(checkArea.Contour.Length / 2, resinTrapConfig.RequiredBlackPixelsToDrain))
+ if (queue.Count == 0 && blackCount > Math.Min(checkArea.Contour.Length / 2,
+ resinTrapConfig.RequiredBlackPixelsToDrain))
{
area.Type = LayerHollowArea.AreaType.Drain;
}
@@ -1032,9 +1270,9 @@ namespace UVtools.Core
{
linkedArea.Type = area.Type;
}
- });
+ }//);
}
- });
+ }
/*var resultSorted = result.ToList();
resultSorted.Sort((issue, layerIssue) =>
diff --git a/UVtools.Core/Objects/BindableBase.cs b/UVtools.Core/Objects/BindableBase.cs
index f2ce458..68a7611 100644
--- a/UVtools.Core/Objects/BindableBase.cs
+++ b/UVtools.Core/Objects/BindableBase.cs
@@ -65,6 +65,12 @@ namespace UVtools.Core.Objects
return true;
}
+ protected void RaiseAndSet<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ field = value;
+ RaisePropertyChanged(propertyName);
+ }
+
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
diff --git a/UVtools.Core/Objects/ExposureItem.cs b/UVtools.Core/Objects/ExposureItem.cs
index 817ee78..8e889f5 100644
--- a/UVtools.Core/Objects/ExposureItem.cs
+++ b/UVtools.Core/Objects/ExposureItem.cs
@@ -16,7 +16,7 @@ namespace UVtools.Core.Objects
public decimal LayerHeight
{
get => _layerHeight;
- set => RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2));
+ set => RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value));
}
@@ -59,7 +59,7 @@ namespace UVtools.Core.Objects
public ExposureItem(decimal layerHeight, decimal bottomExposure = 0, decimal exposure = 0, byte brightness = 255)
{
- _layerHeight = Math.Round(layerHeight, 2);
+ _layerHeight = Layer.RoundHeight(layerHeight);
_bottomExposure = Math.Round(bottomExposure, 2);
_exposure = Math.Round(exposure, 2);
_brightness = brightness;
diff --git a/UVtools.Core/Objects/GCodeBuilder.cs b/UVtools.Core/Objects/GCodeBuilder.cs
deleted file mode 100644
index bf11994..0000000
--- a/UVtools.Core/Objects/GCodeBuilder.cs
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * GNU AFFERO GENERAL PUBLIC LICENSE
- * Version 3, 19 November 2007
- * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
- * Everyone is permitted to copy and distribute verbatim copies
- * of this license document, but changing it is not allowed.
- */
-
-using System.Text;
-
-namespace UVtools.Core.Objects
-{
- public class GCodeBuilder : BindableBase
- {
- #region Enums
-
- public enum GCodeTimeUnits : byte
- {
- Milliseconds,
- Seconds
- }
-
- public enum GCodeSpeedUnits : byte
- {
- MillimetersPerSecond,
- MillimetersPerMinute,
- CentimetersPerMinute,
- }
- #endregion
-
- #region Members
- private readonly StringBuilder _gcode = new();
-
- private bool _appendComma = true;
- private bool _appendComment = true;
- private bool _useAbsoluteCommands = true;
- private GCodeTimeUnits _gCodeTimeUnit = GCodeTimeUnits.Milliseconds;
- private GCodeSpeedUnits _gCodeSpeedUnit = GCodeSpeedUnits.MillimetersPerMinute;
-
- #endregion
-
- #region Properties
-
- public bool UseAbsoluteCommands
- {
- get => _useAbsoluteCommands;
- set => RaiseAndSetIfChanged(ref _useAbsoluteCommands, value);
- }
-
- public GCodeTimeUnits GCodeTimeUnit
- {
- get => _gCodeTimeUnit;
- set => RaiseAndSetIfChanged(ref _gCodeTimeUnit, value);
- }
-
- public GCodeSpeedUnits GCodeSpeedUnit
- {
- get => _gCodeSpeedUnit;
- set => RaiseAndSetIfChanged(ref _gCodeSpeedUnit, value);
- }
-
- public bool AppendComma
- {
- get => _appendComma;
- set => RaiseAndSetIfChanged(ref _appendComma, value);
- }
-
- public bool AppendComment
- {
- get => _appendComment;
- set => RaiseAndSetIfChanged(ref _appendComment, value);
- }
- #endregion
-
- #region StringBuilder
- public void AppendLine() => _gcode.AppendLine();
- public void AppendLine(string line) => _gcode.AppendLine(line);
- public void AppendFormat(string format, params object?[] args) => _gcode.AppendFormat(format, args);
- public void Clear() => _gcode.Clear();
- public override string ToString() => _gcode.ToString();
- #endregion
-
- #region Methods
-
- public string FormatGCodeLine(string line, string comment)
- {
- if (_appendComma || _appendComment)
- line += ';';
- if (_appendComment)
- line += comment;
-
- return line;
- }
-
- public void AppendUnitsMmG21()
- {
- AppendLine(FormatGCodeLine("G21", "Set units to be mm"));
- }
-
- public void AppendPositioningType()
- {
- if (_useAbsoluteCommands)
- {
- AppendLine(FormatGCodeLine("G90", "Absolute positioning"));
- }
- else
- {
- AppendLine(FormatGCodeLine("G91", "Partial positioning"));
- }
- }
-
- public void AppendEnableMotors()
- {
- AppendLine(FormatGCodeLine("M17", "Enable motors"));
- }
-
- public void AppendDisableMotors()
- {
- AppendLine(FormatGCodeLine("M18", "Disable motors"));
- }
-
- public void AppendTurnMotors(bool enable)
- {
- if (enable)
- AppendEnableMotors();
- else
- AppendDisableMotors();
- }
-
- public void AppendHomeZG28()
- {
- AppendLine(FormatGCodeLine("G28 Z0", "Home Z"));
- }
-
-
- public void AppendMoveG0(float z, float feedRate)
- {
- AppendLine(FormatGCodeLine($"G0 Z{z} F{feedRate}", "Z Move"));
- }
-
- public void AppendLiftMoveG0(float upZ, float upFeedRate, float downZ, float downFeedRate)
- {
- AppendLine(FormatGCodeLine($"G0 Z{upZ} F{upFeedRate}", "Z Lift"));
- AppendLine(FormatGCodeLine($"G0 Z{downZ} F{downFeedRate}", "Retract to layer height"));
- }
-
- public void AppendWaitG4(float time)
- {
- AppendLine(FormatGCodeLine($"G4 P{time}", "Delay"));
- }
-
- public void AppendTurnLightM106(ushort value)
- {
- AppendLine(FormatGCodeLine($"M106 S{value}", "Turn LED"));
- }
-
- public void AppendLightOffM106() => AppendTurnLightM106(0);
- public void AppendLightFullM106() => AppendTurnLightM106(255);
-
- public void AppendExposure(ushort value, float time)
- {
- if (time <= 0) return;
-
- AppendTurnLightM106(value);
- AppendWaitG4(time);
- AppendLightOffM106();
- }
-
- public void AppendShowImageM6054(string name)
- {
- AppendLine(FormatGCodeLine($"M6054 \"{name}\"","Show image"));
- }
-
- public void AppendLineComment(string comment)
- {
- AppendLine($";{comment}");
- }
- }
- #endregion
-}
diff --git a/UVtools.Core/Objects/StaticObjects.cs b/UVtools.Core/Objects/StaticObjects.cs
index 254b2c3..5f4dbf4 100644
--- a/UVtools.Core/Objects/StaticObjects.cs
+++ b/UVtools.Core/Objects/StaticObjects.cs
@@ -1,4 +1,5 @@
-using System.IO;
+using System;
+using System.IO;
using System.Security.Cryptography;
namespace UVtools.Core.Objects
diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs
index 2171e3b..99079de 100644
--- a/UVtools.Core/Operations/Operation.cs
+++ b/UVtools.Core/Operations/Operation.cs
@@ -10,6 +10,8 @@ using System;
using System.Drawing;
using System.Xml.Serialization;
using Emgu.CV;
+using Emgu.CV.Util;
+using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
using UVtools.Core.Objects;
@@ -20,6 +22,7 @@ namespace UVtools.Core.Operations
{
#region Members
private Rectangle _roi = Rectangle.Empty;
+ private Point[][] _maskPoints;
private uint _layerIndexEnd;
private uint _layerIndexStart;
private string _profileName;
@@ -84,6 +87,11 @@ namespace UVtools.Core.Operations
public virtual bool CanROI => true;
/// <summary>
+ /// Gets if this operation can make use maskable areas
+ /// </summary>
+ public virtual bool CanMask => CanROI;
+
+ /// <summary>
/// Gets if this operation can store profiles
/// </summary>
public virtual bool CanHaveProfiles => true;
@@ -170,12 +178,33 @@ namespace UVtools.Core.Operations
public Rectangle ROI
{
get => _roi;
- set => RaiseAndSetIfChanged(ref _roi, value);
+ set
+ {
+ if (!CanROI) return;
+ RaiseAndSetIfChanged(ref _roi, value);
+ }
}
public bool HaveROI => !ROI.IsEmpty;
/// <summary>
+ /// Gets or sets an Mask to process this operation
+ /// </summary>
+ [XmlIgnore]
+ public Point[][] MaskPoints
+ {
+ get => _maskPoints;
+ set
+ {
+ if (!CanMask) return;
+ if(!RaiseAndSetIfChanged(ref _maskPoints, value)) return;
+ //if(HaveMask) ROI = Rectangle.Empty;
+ }
+ }
+
+ public bool HaveMask => _maskPoints is not null && _maskPoints.Length > 0;
+
+ /// <summary>
/// Gets if this operation have been executed once
/// </summary>
[XmlIgnore]
@@ -280,26 +309,55 @@ namespace UVtools.Core.Operations
/// </summary>
public virtual void InitWithSlicerFile() { }
+ public void SetROIIfEmpty(Rectangle roi)
+ {
+ if (HaveROI) return;
+ ROI = roi;
+ }
+
+ public void SetMasksIfEmpty(Point[][] points)
+ {
+ if (HaveMask) return;
+ MaskPoints = points;
+ }
+
public Mat GetRoiOrDefault(Mat defaultMat)
{
- return HaveROI ? new Mat(defaultMat, ROI) : defaultMat;
+ return HaveROI && defaultMat.Size != _roi.Size ? new Mat(defaultMat, _roi) : defaultMat;
}
- public virtual Operation Clone()
+ public Mat GetMask(Mat mat) => GetMask(_maskPoints, mat);
+
+ public Mat GetMask(Point[][] points, Mat mat)
{
- var operation = MemberwiseClone() as Operation;
- operation.SlicerFile = _slicerFile;
- return operation;
+ if (!HaveMask) return null;
+
+ var mask = mat.CloneBlank();
+ using VectorOfVectorOfPoint vec = new(points);
+ CvInvoke.DrawContours(mask, vec, -1, EmguExtensions.WhiteByte, -1);
+ return GetRoiOrDefault(mask);
}
- public override string ToString()
+ public void ApplyMask(Mat original, Mat result, Mat mask)
{
- if (!string.IsNullOrEmpty(ProfileName)) return ProfileName;
+ if (mask is null) return;
+ var originalRoi = GetRoiOrDefault(original);
+ var resultRoi = GetRoiOrDefault(result);
+ resultRoi.CopyTo(originalRoi, mask);
+ originalRoi.CopyTo(resultRoi);
+ }
- var result = $"{Title}: {LayerRangeString}";
- return result;
+ /// <summary>
+ /// Gets a mask and apply it
+ /// </summary>
+ /// <param name="original">Original unmodified image</param>
+ /// <param name="result">Result image which will also be modified</param>
+ public void ApplyMask(Mat original, Mat result)
+ {
+ using var mask = GetMask(result);
+ ApplyMask(original, result, mask);
}
-
+
protected virtual bool ExecuteInternally(OperationProgress progress)
{
@@ -324,6 +382,21 @@ namespace UVtools.Core.Operations
throw new NotImplementedException();
}
+ public virtual Operation Clone()
+ {
+ var operation = MemberwiseClone() as Operation;
+ operation.SlicerFile = _slicerFile;
+ return operation;
+ }
+
+ public override string ToString()
+ {
+ if (!string.IsNullOrEmpty(ProfileName)) return ProfileName;
+
+ var result = $"{Title}: {LayerRangeString}";
+ return result;
+ }
+
public virtual void Dispose() { }
#endregion
}
diff --git a/UVtools.Core/Operations/OperationArithmetic.cs b/UVtools.Core/Operations/OperationArithmetic.cs
index c4ef8dd..75edfad 100644
--- a/UVtools.Core/Operations/OperationArithmetic.cs
+++ b/UVtools.Core/Operations/OperationArithmetic.cs
@@ -217,20 +217,21 @@ namespace UVtools.Core.Operations
{
if (!IsValid) return false;
- using Mat result = SlicerFile[Operations[0].LayerIndex].LayerMat;
- Mat resultRoi = GetRoiOrDefault(result);
+ using var result = SlicerFile[Operations[0].LayerIndex].LayerMat;
+ using var resultRoi = GetRoiOrDefault(result);
for (int i = 1; i < Operations.Count; i++)
{
progress.Token.ThrowIfCancellationRequested();
using var image = SlicerFile[Operations[i].LayerIndex].LayerMat;
- Mat imageRoi = GetRoiOrDefault(image);
+ var imageRoi = GetRoiOrDefault(image);
+ using var imageMask = GetMask(image);
switch (Operations[i - 1].Operator)
{
case ArithmeticOperators.Add:
- CvInvoke.Add(resultRoi, imageRoi, resultRoi);
+ CvInvoke.Add(resultRoi, imageRoi, resultRoi, imageMask);
break;
case ArithmeticOperators.Subtract:
- CvInvoke.Subtract(resultRoi, imageRoi, resultRoi);
+ CvInvoke.Subtract(resultRoi, imageRoi, resultRoi, imageMask);
break;
case ArithmeticOperators.Multiply:
CvInvoke.Multiply(resultRoi, imageRoi, resultRoi);
@@ -239,13 +240,13 @@ namespace UVtools.Core.Operations
CvInvoke.Divide(resultRoi, imageRoi, resultRoi);
break;
case ArithmeticOperators.BitwiseAnd:
- CvInvoke.BitwiseAnd(resultRoi, imageRoi, resultRoi);
+ CvInvoke.BitwiseAnd(resultRoi, imageRoi, resultRoi, imageMask);
break;
case ArithmeticOperators.BitwiseOr:
- CvInvoke.BitwiseOr(resultRoi, imageRoi, resultRoi);
+ CvInvoke.BitwiseOr(resultRoi, imageRoi, resultRoi, imageMask);
break;
case ArithmeticOperators.BitwiseXor:
- CvInvoke.BitwiseXor(resultRoi, imageRoi, resultRoi);
+ CvInvoke.BitwiseXor(resultRoi, imageRoi, resultRoi, imageMask);
break;
}
}
@@ -257,7 +258,8 @@ namespace UVtools.Core.Operations
{
var mat = SlicerFile[layerIndex].LayerMat;
var matRoi = GetRoiOrDefault(mat);
- resultRoi.CopyTo(matRoi);
+ using var imageMask = GetMask(mat);
+ resultRoi.CopyTo(matRoi, imageMask);
SlicerFile[layerIndex].LayerMat = mat;
return;
}
diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs
index bc9b456..ff0acaf 100644
--- a/UVtools.Core/Operations/OperationBlur.cs
+++ b/UVtools.Core/Operations/OperationBlur.cs
@@ -160,7 +160,8 @@ namespace UVtools.Core.Operations
if (anchor.IsEmpty) anchor = new Point(-1, -1);
//if (size.IsEmpty) size = new Size(3, 3);
//if (anchor.IsEmpty) anchor = new Point(-1, -1);
- Mat target = GetRoiOrDefault(mat);
+ var target = GetRoiOrDefault(mat);
+ using var original = mat.Clone();
switch (BlurOperation)
{
case BlurAlgorithm.Blur:
@@ -183,6 +184,8 @@ namespace UVtools.Core.Operations
throw new ArgumentOutOfRangeException();
}
+ ApplyMask(original, mat);
+
return true;
}
diff --git a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
index 3b77e81..e5e3caa 100644
--- a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
+++ b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs
@@ -120,7 +120,7 @@ namespace UVtools.Core.Operations
get => _layerHeight;
set
{
- if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return;
+ if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
RaisePropertyChanged(nameof(BottomHeight));
RaisePropertyChanged(nameof(NormalHeight));
RaisePropertyChanged(nameof(TotalHeight));
@@ -176,8 +176,8 @@ namespace UVtools.Core.Operations
public uint LayerCount => (uint)(_bottomLayers + _normalLayers + (_extrudeText ? Math.Floor(_textHeight / _layerHeight) : 0));
- public decimal BottomHeight => Math.Round(LayerHeight * BottomLayers, 2);
- public decimal NormalHeight => Math.Round(LayerHeight * NormalLayers, 2);
+ public decimal BottomHeight => Layer.RoundHeight(LayerHeight * BottomLayers);
+ public decimal NormalHeight => Layer.RoundHeight(LayerHeight * NormalLayers);
public decimal TotalHeight => BottomHeight + NormalHeight;
diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
index eb515de..28df4f2 100644
--- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
+++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs
@@ -149,7 +149,7 @@ namespace UVtools.Core.Operations
}
}
if(!found)
- sb.AppendLine($"[ME]: {layerHeight:F2}mm layer height have no exposure(s).");
+ sb.AppendLine($"[ME]: {Layer.ShowHeight(layerHeight)}mm layer height have no exposure(s).");
}
}
@@ -211,7 +211,7 @@ namespace UVtools.Core.Operations
get => _layerHeight;
set
{
- if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return;
+ if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
RaisePropertyChanged(nameof(BottomLayersMM));
RaisePropertyChanged(nameof(AvailableLayerHeights));
}
@@ -229,7 +229,7 @@ namespace UVtools.Core.Operations
}
}
- public decimal BottomLayersMM => Math.Round(LayerHeight * BottomLayers, 2);
+ public decimal BottomLayersMM => Layer.RoundHeight(LayerHeight * BottomLayers);
public decimal BottomExposure
{
@@ -650,7 +650,7 @@ namespace UVtools.Core.Operations
List<ExposureItem> list = new();
for (decimal layerHeight = _layerHeight; layerHeight <= endLayerHeight; layerHeight += _multipleLayerHeightStep)
{
- layerHeights.Add(Math.Round(layerHeight, 2));
+ layerHeights.Add(Layer.RoundHeight(layerHeight));
}
return layerHeights.ToArray();
@@ -1264,11 +1264,11 @@ namespace UVtools.Core.Operations
for (decimal currentHeight = _layerHeight; currentHeight <= totalHeight; currentHeight += 0.01m)
{
- currentHeight = Math.Round(currentHeight, 2);
+ currentHeight = Layer.RoundHeight(currentHeight);
for (decimal layerHeight = _layerHeight; layerHeight <= endLayerHeight; layerHeight += _multipleLayerHeightStep)
{
progress.Token.ThrowIfCancellationRequested();
- layerHeight = Math.Round(layerHeight, 2);
+ layerHeight = Layer.RoundHeight(layerHeight);
if (_multipleExposures)
{
diff --git a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
index 8e960f3..68075b8 100644
--- a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
+++ b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs
@@ -121,7 +121,7 @@ namespace UVtools.Core.Operations
get => _layerHeight;
set
{
- if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return;
+ if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
RaisePropertyChanged(nameof(BottomHeight));
RaisePropertyChanged(nameof(InterfaceHeight));
RaisePropertyChanged(nameof(NormalHeight));
@@ -169,9 +169,9 @@ namespace UVtools.Core.Operations
public uint LayerCount => (uint) (_bottomLayers + _interfaceLayers + _normalLayers);
- public decimal BottomHeight => Math.Round(LayerHeight * _bottomLayers, 2);
- public decimal InterfaceHeight => Math.Round(LayerHeight * _interfaceLayers, 2);
- public decimal NormalHeight => Math.Round(LayerHeight * _normalLayers, 2);
+ public decimal BottomHeight => Layer.RoundHeight(LayerHeight * _bottomLayers);
+ public decimal InterfaceHeight => Layer.RoundHeight(LayerHeight * _interfaceLayers);
+ public decimal NormalHeight => Layer.RoundHeight(LayerHeight * _normalLayers);
public decimal TotalHeight => BottomHeight + InterfaceHeight + NormalHeight;
diff --git a/UVtools.Core/Operations/OperationCalibrateStressTower.cs b/UVtools.Core/Operations/OperationCalibrateStressTower.cs
index 1318f09..231ba70 100644
--- a/UVtools.Core/Operations/OperationCalibrateStressTower.cs
+++ b/UVtools.Core/Operations/OperationCalibrateStressTower.cs
@@ -146,7 +146,7 @@ namespace UVtools.Core.Operations
get => _layerHeight;
set
{
- if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return;
+ if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
RaisePropertyChanged(nameof(BottomLayersMM));
RaisePropertyChanged(nameof(LayerCount));
}
@@ -164,7 +164,7 @@ namespace UVtools.Core.Operations
}
}
- public decimal BottomLayersMM => Math.Round(LayerHeight * BottomLayers, 2);
+ public decimal BottomLayersMM => Layer.RoundHeight(LayerHeight * BottomLayers);
public decimal BottomExposure
{
diff --git a/UVtools.Core/Operations/OperationCalibrateTolerance.cs b/UVtools.Core/Operations/OperationCalibrateTolerance.cs
index 2a34193..f5d7a13 100644
--- a/UVtools.Core/Operations/OperationCalibrateTolerance.cs
+++ b/UVtools.Core/Operations/OperationCalibrateTolerance.cs
@@ -151,7 +151,7 @@ namespace UVtools.Core.Operations
get => _layerHeight;
set
{
- if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return;
+ if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
RaisePropertyChanged(nameof(BottomLayersMM));
RaisePropertyChanged(nameof(LayerCount));
RaisePropertyChanged(nameof(RealZSize));
@@ -171,7 +171,7 @@ namespace UVtools.Core.Operations
}
}
- public decimal BottomLayersMM => Math.Round(LayerHeight * BottomLayers, 2);
+ public decimal BottomLayersMM => Layer.RoundHeight(LayerHeight * BottomLayers);
public decimal BottomExposure
{
@@ -616,7 +616,7 @@ namespace UVtools.Core.Operations
currentX += xPixels + PartMargin;
}
- pointTextList.Add(new KeyValuePair<Point, string>(partCenterText, step > 0 ? $"+{step:0.00}" : $"{step:0.00}"));
+ pointTextList.Add(new KeyValuePair<Point, string>(partCenterText, step > 0 ? $"+{step:F2}" : $"{step:F2}"));
return true;
}
diff --git a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
index a36d4a9..0c9c1e2 100644
--- a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
+++ b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs
@@ -146,7 +146,7 @@ namespace UVtools.Core.Operations
get => _layerHeight;
set
{
- if(!RaiseAndSetIfChanged(ref _layerHeight, Math.Round(value, 2))) return;
+ if(!RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value))) return;
RaisePropertyChanged(nameof(BottomLayersMM));
RaisePropertyChanged(nameof(LayerCount));
RaisePropertyChanged(nameof(RealZSize));
@@ -166,7 +166,7 @@ namespace UVtools.Core.Operations
}
}
- public decimal BottomLayersMM => Math.Round(LayerHeight * BottomLayers, 2);
+ public decimal BottomLayersMM => Layer.RoundHeight(LayerHeight * BottomLayers);
public decimal BottomExposure
{
diff --git a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
index 83e213b..c53ecdf 100644
--- a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
+++ b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
@@ -127,7 +127,7 @@ namespace UVtools.Core.Operations
layerHeight <= _maximumLayerHeight;
layerHeight = layerHeight + (decimal) SlicerFile.LayerHeight)
{
- layerHeight = Math.Round(layerHeight, 2);
+ layerHeight = Layer.RoundHeight(layerHeight);
if (exposureTable.TryGetValue(layerHeight, out var exposure))
{
if (exposure.BottomExposure <= 0 || exposure.Exposure <= 0)
@@ -202,7 +202,7 @@ namespace UVtools.Core.Operations
get => _minimumLayerHeight;
set
{
- if (!RaiseAndSetIfChanged(ref _minimumLayerHeight, Math.Round(value, 2))) return;
+ if (!RaiseAndSetIfChanged(ref _minimumLayerHeight, Layer.RoundHeight(value))) return;
//RaisePropertyChanged(nameof(ExposureData));
//if (!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
@@ -213,7 +213,7 @@ namespace UVtools.Core.Operations
get => _maximumLayerHeight;
set
{
- if(!RaiseAndSetIfChanged(ref _maximumLayerHeight, Math.Round(value, 2))) return;
+ if(!RaiseAndSetIfChanged(ref _maximumLayerHeight, Layer.RoundHeight(value))) return;
//RaisePropertyChanged(nameof(ExposureData));
if(!IsExposureSetTypeManual) RebuildAutoExposureTable();
}
@@ -489,7 +489,7 @@ namespace UVtools.Core.Operations
});
}
- float GetLastPositionZ(float layerHeight) => layers.Count > 0 ? (float)Math.Round(layers[^1].PositionZ + layerHeight, 2) : layerHeight;
+ float GetLastPositionZ(float layerHeight) => layers.Count > 0 ? Layer.RoundHeight(layers[^1].PositionZ + layerHeight) : layerHeight;
(Mat, Mat) GetLayer(uint layerIndex)
{
@@ -562,7 +562,7 @@ namespace UVtools.Core.Operations
}
var previousLayerHeight = currentLayerHeight;
- currentLayerHeight = (float)Math.Round(currentLayerHeight + SlicerFile.LayerHeight, 2);
+ currentLayerHeight = Layer.RoundHeight(currentLayerHeight + SlicerFile.LayerHeight);
var (mat1, mat1Threshold) = GetLayer(layerIndex);
var (mat2, mat2Threshold) = GetLayer(++layerIndex);
@@ -632,11 +632,8 @@ namespace UVtools.Core.Operations
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)
{
diff --git a/UVtools.Core/Operations/OperationFlip.cs b/UVtools.Core/Operations/OperationFlip.cs
index 26fbf18..e2c55bb 100644
--- a/UVtools.Core/Operations/OperationFlip.cs
+++ b/UVtools.Core/Operations/OperationFlip.cs
@@ -141,6 +141,7 @@ namespace UVtools.Core.Operations
public override bool Execute(Mat mat, params object[] arguments)
{
+ using var original = mat.Clone();
var target = GetRoiOrDefault(mat);
if (MakeCopy)
@@ -154,6 +155,8 @@ namespace UVtools.Core.Operations
CvInvoke.Flip(target, target, FlipTypeOpenCV);
}
+ ApplyMask(original, mat);
+
return true;
}
#endregion
diff --git a/UVtools.Core/Operations/OperationIPrintedThisFile.cs b/UVtools.Core/Operations/OperationIPrintedThisFile.cs
index 851c4d1..602f624 100644
--- a/UVtools.Core/Operations/OperationIPrintedThisFile.cs
+++ b/UVtools.Core/Operations/OperationIPrintedThisFile.cs
@@ -18,9 +18,10 @@ namespace UVtools.Core.Operations
public class OperationIPrintedThisFile : Operation
{
#region Members
+ private Material _materialItem;
private decimal _volume;
private float _printTime;
- private Material _materialItem;
+ private decimal _multiplier = 1;
#endregion
@@ -36,7 +37,7 @@ namespace UVtools.Core.Operations
public override string Description => "Select a material and consume resin from stock and print time.";
public override string ConfirmationText =>
- $"consume {_volume}ml and {PrintTimeHours}h on:\n{_materialItem} ?";
+ $"consume {FinalVolume}ml and {FinalPrintTimeHours:F4}h on:\n{_materialItem} ?";
public override string ProgressTitle =>
$"Consuming";
@@ -65,7 +66,7 @@ namespace UVtools.Core.Operations
public override string ToString()
{
- var result = $"{_volume}ml {PrintTimeHours}h";
+ var result = $"{FinalVolume}ml {FinalPrintTimeHours:F4}h on {_materialItem.Name}";
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
return result;
}
@@ -82,9 +83,15 @@ namespace UVtools.Core.Operations
public decimal Volume
{
get => _volume;
- set => RaiseAndSetIfChanged(ref _volume, value);
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _volume, value)) return;
+ RaisePropertyChanged(nameof(FinalVolume));
+ }
}
+ public decimal FinalVolume => _volume * _multiplier;
+
public float PrintTime
{
get => _printTime;
@@ -92,10 +99,28 @@ namespace UVtools.Core.Operations
{
if(!RaiseAndSetIfChanged(ref _printTime, value)) return;
RaisePropertyChanged(nameof(PrintTimeHours));
+ RaisePropertyChanged(nameof(FinalPrintTime));
+ RaisePropertyChanged(nameof(FinalPrintTimeHours));
}
}
public float PrintTimeHours => _printTime / 60 / 60;
+ public float FinalPrintTime => _printTime * (float)_multiplier;
+ public float FinalPrintTimeHours => FinalPrintTime / 60 / 60;
+
+ /// <summary>
+ /// Number of times this file has been printed
+ /// </summary>
+ public decimal Multiplier
+ {
+ get => _multiplier;
+ set
+ {
+ if (!RaiseAndSetIfChanged(ref _multiplier, value)) return;
+ RaisePropertyChanged(nameof(FinalVolume));
+ RaisePropertyChanged(nameof(FinalPrintTime));
+ }
+ }
public MaterialManager Manager => MaterialManager.Instance;
@@ -123,7 +148,7 @@ namespace UVtools.Core.Operations
{
if (MaterialItem is null) return !progress.Token.IsCancellationRequested;
- MaterialItem.Consume(_volume, _printTime);
+ MaterialItem.Consume(FinalVolume, FinalPrintTime);
MaterialManager.Save();
return !progress.Token.IsCancellationRequested;
@@ -133,6 +158,24 @@ namespace UVtools.Core.Operations
#region Equality
+ protected bool Equals(OperationIPrintedThisFile other)
+ {
+ return _volume == other._volume && _printTime.Equals(other._printTime) && Equals(_materialItem, other._materialItem) && _multiplier == other._multiplier;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((OperationIPrintedThisFile) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(_volume, _printTime, _materialItem, _multiplier);
+ }
+
#endregion
}
}
diff --git a/UVtools.Core/Operations/OperationInfill.cs b/UVtools.Core/Operations/OperationInfill.cs
index 2fad72f..8ffdde1 100644
--- a/UVtools.Core/Operations/OperationInfill.cs
+++ b/UVtools.Core/Operations/OperationInfill.cs
@@ -155,9 +155,10 @@ namespace UVtools.Core.Operations
var infillColor = new MCvScalar(InfillBrightness);
Mat patternMask = null;
- using Mat erode = new Mat();
- using Mat diff = new Mat();
+ using Mat erode = new ();
+ using Mat diff = new ();
Mat target = GetRoiOrDefault(mat);
+ using var mask = GetMask(mat);
if (InfillType == InfillAlgorithm.Cubic ||
@@ -304,8 +305,8 @@ namespace UVtools.Core.Operations
CvInvoke.Subtract(target, erode, diff);
- CvInvoke.BitwiseAnd(erode, patternMask, target);
- CvInvoke.Add(target, diff, target);
+ CvInvoke.BitwiseAnd(erode, patternMask, target, mask);
+ CvInvoke.Add(target, diff, target, mask);
patternMask?.Dispose();
return true;
diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs
index a68e367..fd45ac4 100644
--- a/UVtools.Core/Operations/OperationLayerReHeight.cs
+++ b/UVtools.Core/Operations/OperationLayerReHeight.cs
@@ -79,19 +79,19 @@ namespace UVtools.Core.Operations
{
if (layerHeight / i < 0.01m) break;
if ((layerCount * (decimal)i).DecimalDigits() > 0) continue; // Cant multiply layers, no half layers!
- if ((layerHeight / i).DecimalDigits() > 2) continue; // Cant divide height, more than 2 digits
+ if ((layerHeight / i).DecimalDigits() > 3) continue; // Cant divide height, more than 3 digits
- var item = new OperationLayerReHeightItem(false, i, Math.Round(layerHeight / i, 2), layerCount * i);
+ var item = new OperationLayerReHeightItem(false, i, Layer.RoundHeight(layerHeight / i), layerCount * i);
list.Add(item);
}
for (byte i = 2; i < 255; i++) // Go higher heights
{
- if (layerHeight * i > 0.2m) break;
+ if (layerHeight * i > 0.20m) break;
if ((layerCount / (decimal)i).DecimalDigits() > 0) continue; // Cant divide layers, no half layers!
- if ((layerHeight * i).DecimalDigits() > 2) continue; // Cant multiply height, more than 2 digits
+ if ((layerHeight * i).DecimalDigits() > 3) continue; // Cant multiply height, more than 3 digits
- var item = new OperationLayerReHeightItem(true, i, Math.Round(layerHeight * i, 2), layerCount / i);
+ var item = new OperationLayerReHeightItem(true, i, Layer.RoundHeight(layerHeight * i), layerCount / i);
list.Add(item);
}
diff --git a/UVtools.Core/Operations/OperationLayerRemove.cs b/UVtools.Core/Operations/OperationLayerRemove.cs
index c4d3ad2..406a24f 100644
--- a/UVtools.Core/Operations/OperationLayerRemove.cs
+++ b/UVtools.Core/Operations/OperationLayerRemove.cs
@@ -8,8 +8,6 @@
using System;
using System.Collections.Generic;
-using System.Text;
-using Emgu.CV;
using UVtools.Core.FileFormats;
namespace UVtools.Core.Operations
@@ -100,7 +98,7 @@ namespace UVtools.Core.Operations
}
else
{
- posZ = (float) Math.Round(layers[newLayerIndex - 1].PositionZ + layerHeight, 2);
+ posZ = Layer.RoundHeight(layers[newLayerIndex - 1].PositionZ + layerHeight);
}
}
diff --git a/UVtools.Core/Operations/OperationMask.cs b/UVtools.Core/Operations/OperationMask.cs
index 3309e2f..809fdbb 100644
--- a/UVtools.Core/Operations/OperationMask.cs
+++ b/UVtools.Core/Operations/OperationMask.cs
@@ -93,8 +93,9 @@ namespace UVtools.Core.Operations
public override bool Execute(Mat mat, params object[] arguments)
{
var target = GetRoiOrDefault(mat);
+ using var mask = GetMask(mat);
if (Mask.Size != target.Size) return false;
- CvInvoke.BitwiseAnd(target, Mask, target);
+ CvInvoke.BitwiseAnd(target, Mask, target, mask);
return true;
}
diff --git a/UVtools.Core/Operations/OperationMorph.cs b/UVtools.Core/Operations/OperationMorph.cs
index 4c0ef1a..bc5845b 100644
--- a/UVtools.Core/Operations/OperationMorph.cs
+++ b/UVtools.Core/Operations/OperationMorph.cs
@@ -185,8 +185,11 @@ namespace UVtools.Core.Operations
{
iterations = (int) arguments[0];
}
+
+ using var original = mat.Clone();
var target = GetRoiOrDefault(mat);
CvInvoke.MorphologyEx(target, target, MorphOperation, Kernel.Matrix, Kernel.Anchor, iterations, BorderType.Reflect101, default);
+ ApplyMask(original, mat);
return true;
}
diff --git a/UVtools.Core/Operations/OperationMove.cs b/UVtools.Core/Operations/OperationMove.cs
index 30a3dee..27ed2c2 100644
--- a/UVtools.Core/Operations/OperationMove.cs
+++ b/UVtools.Core/Operations/OperationMove.cs
@@ -20,6 +20,9 @@ namespace UVtools.Core.Operations
public class OperationMove : Operation
{
#region Overrides
+
+ public override bool CanMask => false;
+
public override string Title => "Move";
public override string Description =>
"Change or copy the position of the model on the build plate.\n" +
diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs
index 3cd36bf..432aa95 100644
--- a/UVtools.Core/Operations/OperationPattern.cs
+++ b/UVtools.Core/Operations/OperationPattern.cs
@@ -34,7 +34,7 @@ namespace UVtools.Core.Operations
#endregion
#region Overrides
-
+ public override bool CanMask => false;
public override string Title => "Pattern";
public override string Description =>
"Duplicates the model in a rectangular pattern around the build plate.\n" +
diff --git a/UVtools.Core/Operations/OperationPixelDimming.cs b/UVtools.Core/Operations/OperationPixelDimming.cs
index 4e40285..8dd158a 100644
--- a/UVtools.Core/Operations/OperationPixelDimming.cs
+++ b/UVtools.Core/Operations/OperationPixelDimming.cs
@@ -36,8 +36,8 @@ namespace UVtools.Core.Operations
#endregion
#region Members
- private uint _wallThicknessStart = 5;
- private uint _wallThicknessEnd = 5;
+ private uint _wallThicknessStart = 10;
+ private uint _wallThicknessEnd = 10;
private bool _wallsOnly;
private bool _chamfer;
private Matrix<byte> _pattern;
@@ -613,7 +613,8 @@ namespace UVtools.Core.Operations
using Mat erode = new Mat();
using Mat diff = new Mat();
- Mat target = GetRoiOrDefault(mat);
+ var target = GetRoiOrDefault(mat);
+ using var mask = GetMask(mat);
CvInvoke.Erode(target, erode, kernel, anchor, wallThickness, BorderType.Reflect101, default);
@@ -622,13 +623,13 @@ namespace UVtools.Core.Operations
if (WallsOnly)
{
- CvInvoke.BitwiseAnd(diff, IsNormalPattern(layerIndex) ? patternMask : alternatePatternMask, target);
- CvInvoke.Add(erode, target, target);
+ CvInvoke.BitwiseAnd(diff, IsNormalPattern(layerIndex) ? patternMask : alternatePatternMask, target, mask);
+ CvInvoke.Add(erode, target, target, mask);
}
else
{
- CvInvoke.BitwiseAnd(erode, IsNormalPattern(layerIndex) ? patternMask : alternatePatternMask, target);
- CvInvoke.Add(target, diff, target);
+ CvInvoke.BitwiseAnd(erode, IsNormalPattern(layerIndex) ? patternMask : alternatePatternMask, target, mask);
+ CvInvoke.Add(target, diff, target, mask);
}
return true;
diff --git a/UVtools.Core/Operations/OperationProgress.cs b/UVtools.Core/Operations/OperationProgress.cs
index 4eea251..027243e 100644
--- a/UVtools.Core/Operations/OperationProgress.cs
+++ b/UVtools.Core/Operations/OperationProgress.cs
@@ -28,12 +28,13 @@ namespace UVtools.Core.Operations
public const string StatusExtracting = "Extracting";
public const string StatusIslands = "Layers processed (Islands/Overhangs)";
+ public const string StatusResinTrapsOptimized = "Layers optimized (Resin traps)";
public const string StatusResinTraps = "Layers processed (Resin traps)";
public const string StatusRepairLayers = "Repaired Layers";
- public object Mutex = new object();
+ public object Mutex = new();
- public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource();
+ public CancellationTokenSource TokenSource { get; } = new();
public CancellationToken Token => TokenSource.Token;
private bool _canCancel = true;
@@ -175,7 +176,7 @@ namespace UVtools.Core.Operations
{
return _itemCount == 0 ?
$"{_processedItems}/? {_itemName}" :
-$"{_processedItems.ToString().PadLeft(_itemCount.ToString().Length, '0')}/{_itemCount} {_itemName} | {ProgressPercent:0.00}%";
+$"{_processedItems.ToString().PadLeft(_itemCount.ToString().Length, '0')}/{_itemCount} {_itemName} | {ProgressPercent:F2}%";
}
public void TriggerRefresh()
@@ -185,5 +186,13 @@ $"{_processedItems.ToString().PadLeft(_itemCount.ToString().Length, '0')}/{_item
//OnPropertyChanged(nameof(ProgressPercent));
//OnPropertyChanged(nameof(Description));
}
+
+ public void LockAndIncrement()
+ {
+ lock (Mutex)
+ {
+ ProcessedItems++;
+ }
+ }
}
}
diff --git a/UVtools.Core/Operations/OperationRaftRelief.cs b/UVtools.Core/Operations/OperationRaftRelief.cs
index c19bd91..8a7c3f1 100644
--- a/UVtools.Core/Operations/OperationRaftRelief.cs
+++ b/UVtools.Core/Operations/OperationRaftRelief.cs
@@ -242,36 +242,35 @@ namespace UVtools.Core.Operations
Parallel.For(_ignoreFirstLayers, firstSupportLayerIndex, layerIndex =>
{
if (progress.Token.IsCancellationRequested) return;
- using (Mat dst = SlicerFile[layerIndex].LayerMat)
- {
- var target = GetRoiOrDefault(dst);
+ using var result = SlicerFile[layerIndex].LayerMat;
+ using var original = result.Clone();
+ var target = GetRoiOrDefault(result);
- switch (ReliefType)
- {
- case RaftReliefTypes.Relief:
- case RaftReliefTypes.Dimming:
- using (Mat mask = new Mat())
- {
- /*CvInvoke.Subtract(target, supportsMat, mask);
+ switch (ReliefType)
+ {
+ case RaftReliefTypes.Relief:
+ case RaftReliefTypes.Dimming:
+ using (Mat mask = new Mat())
+ {
+ /*CvInvoke.Subtract(target, supportsMat, mask);
CvInvoke.Erode(mask, mask, kernel, anchor, operation.WallMargin, BorderType.Reflect101, new MCvScalar());
CvInvoke.Subtract(target, patternMat, target, mask);*/
- CvInvoke.Erode(target, mask, kernel, anchor, WallMargin, BorderType.Reflect101,
- default);
- CvInvoke.Subtract(mask, supportsMat, mask);
- CvInvoke.Subtract(target, patternMat, target, mask);
- }
+ CvInvoke.Erode(target, mask, kernel, anchor, WallMargin, BorderType.Reflect101,
+ default);
+ CvInvoke.Subtract(mask, supportsMat, mask);
+ CvInvoke.Subtract(target, patternMat, target, mask);
+ }
- break;
- case RaftReliefTypes.Decimate:
- supportsMat.CopyTo(target);
- break;
- }
-
-
- SlicerFile[layerIndex].LayerMat = dst;
+ break;
+ case RaftReliefTypes.Decimate:
+ supportsMat.CopyTo(target);
+ break;
}
+ ApplyMask(original, result);
+ SlicerFile[layerIndex].LayerMat = result;
+
lock (progress.Mutex)
{
progress++;
diff --git a/UVtools.Core/Operations/OperationRedrawModel.cs b/UVtools.Core/Operations/OperationRedrawModel.cs
index 7cb6da3..06ae5ff 100644
--- a/UVtools.Core/Operations/OperationRedrawModel.cs
+++ b/UVtools.Core/Operations/OperationRedrawModel.cs
@@ -173,6 +173,7 @@ namespace UVtools.Core.Operations
if (progress.Token.IsCancellationRequested) return;
var fullMatLayerIndex = startLayerIndex + layerIndex;
using var fullMat = SlicerFile[fullMatLayerIndex].LayerMat;
+ using var original = fullMat.Clone();
using var bodyMat = otherFile[layerIndex].LayerMat;
using var fullMatRoi = GetRoiOrDefault(fullMat);
using var bodyMatRoi = GetRoiOrDefault(bodyMat);
@@ -239,6 +240,7 @@ namespace UVtools.Core.Operations
if (modified)
{
+ ApplyMask(original, fullMat);
SlicerFile[fullMatLayerIndex].LayerMat = fullMat;
}
diff --git a/UVtools.Core/Operations/OperationResize.cs b/UVtools.Core/Operations/OperationResize.cs
index 1917696..1cc1105 100644
--- a/UVtools.Core/Operations/OperationResize.cs
+++ b/UVtools.Core/Operations/OperationResize.cs
@@ -215,8 +215,10 @@ namespace UVtools.Core.Operations
yScale = (decimal) arguments[1];
}
+ using var original = mat.Clone();
var target = GetRoiOrDefault(mat);
target.TransformFromCenter((double) xScale, (double) yScale);
+ ApplyMask(original, mat);
return true;
}
diff --git a/UVtools.Core/Operations/OperationRotate.cs b/UVtools.Core/Operations/OperationRotate.cs
index c428b6c..e8641c9 100644
--- a/UVtools.Core/Operations/OperationRotate.cs
+++ b/UVtools.Core/Operations/OperationRotate.cs
@@ -101,8 +101,10 @@ namespace UVtools.Core.Operations
public override bool Execute(Mat mat, params object[] arguments)
{
- Mat target = GetRoiOrDefault(mat);
+ using var original = mat.Clone();
+ var target = GetRoiOrDefault(mat);
target.Rotate((double)AngleDegrees);
+ ApplyMask(original, mat);
return true;
}
diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs
index 056f930..566cc5b 100644
--- a/UVtools.Core/Operations/OperationSolidify.cs
+++ b/UVtools.Core/Operations/OperationSolidify.cs
@@ -104,10 +104,11 @@ namespace UVtools.Core.Operations
public override bool Execute(Mat mat, params object[] arguments)
{
- using Mat filteredMat = new Mat();
- using VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
- using Mat hierarchy = new Mat();
- Mat target = GetRoiOrDefault(mat);
+ using Mat filteredMat = new();
+ using VectorOfVectorOfPoint contours = new();
+ using Mat hierarchy = new();
+ using var original = mat.Clone();
+ var target = GetRoiOrDefault(mat);
CvInvoke.Threshold(target, filteredMat, 127, 255, ThresholdType.Binary); // Clean AA
CvInvoke.FindContours(filteredMat, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple);
@@ -129,9 +130,11 @@ namespace UVtools.Core.Operations
}
- CvInvoke.DrawContours(target, contours, i, new MCvScalar(255), -1);
+ CvInvoke.DrawContours(target, contours, i, EmguExtensions.WhiteByte, -1);
}
+ ApplyMask(original, mat);
+
return true;
}
diff --git a/UVtools.Core/Operations/OperationThreshold.cs b/UVtools.Core/Operations/OperationThreshold.cs
index b85d6b4..9768b23 100644
--- a/UVtools.Core/Operations/OperationThreshold.cs
+++ b/UVtools.Core/Operations/OperationThreshold.cs
@@ -101,8 +101,10 @@ namespace UVtools.Core.Operations
public override bool Execute(Mat mat, params object[] arguments)
{
- Mat target = GetRoiOrDefault(mat);
+ using var original = mat.Clone();
+ var target = GetRoiOrDefault(mat);
CvInvoke.Threshold(target, target, Threshold, Maximum, Type);
+ ApplyMask(original, mat);
return true;
}
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 7a0a7a8..4ba8152 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
- <Version>2.6.2</Version>
+ <Version>2.7.0</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
diff --git a/UVtools.InstallerMM/UVtools.InstallerMM.wxs b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
index bd96402..a7dca7f 100644
--- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs
+++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
@@ -311,8 +311,8 @@
<Component Id="owc6F31DEF09608AA645959666A4CB7FBBC" Guid="c94570a5-5bb4-a53f-e12f-9581bddf7d08">
<File Id="owf6F31DEF09608AA645959666A4CB7FBBC" Source="$(var.SourceDir)\mscordaccore.dll" KeyPath="yes" />
</Component>
- <Component Id="owc5F30043C81E98C2C1081E2D19E5EAF68" Guid="ca7139c0-9e2b-1afb-deec-76a162d7b807">
- <File Id="owf5F30043C81E98C2C1081E2D19E5EAF68" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_5.0.321.7212.dll" KeyPath="yes" />
+ <Component Id="owcBEB204AA759A78D6BCFF6B15617ACFA5" Guid="03dc9cfc-30ba-a457-dab7-639389f1eedb">
+ <File Id="owfBEB204AA759A78D6BCFF6B15617ACFA5" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_5.0.421.11614.dll" KeyPath="yes" />
</Component>
<Component Id="owc5FC34571A1AE47A011FC6C2A95B00DA6" Guid="2591451e-0fe5-ccad-abf6-1d1097251253">
<File Id="owf5FC34571A1AE47A011FC6C2A95B00DA6" Source="$(var.SourceDir)\mscordbi.dll" KeyPath="yes" />
diff --git a/UVtools.WPF/Controls/AdvancedImageBox.axaml.cs b/UVtools.WPF/Controls/AdvancedImageBox.axaml.cs
index 7707900..7776ce0 100644
--- a/UVtools.WPF/Controls/AdvancedImageBox.axaml.cs
+++ b/UVtools.WPF/Controls/AdvancedImageBox.axaml.cs
@@ -942,21 +942,13 @@ namespace UVtools.WPF.Controls
/// <summary>
/// Converts the given client size point to represent a coordinate on the source image.
/// </summary>
- /// <param name="point">The source point.</param>
- /// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
- public Point PointToImage(Point point)
- => PointToImage(point, false);
-
- /// <summary>
- /// Converts the given client size point to represent a coordinate on the source image.
- /// </summary>
/// <param name="x">The X co-ordinate of the point to convert.</param>
/// <param name="y">The Y co-ordinate of the point to convert.</param>
/// <param name="fitToBounds">
/// if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
/// </param>
/// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
- public Point PointToImage(double x, double y, bool fitToBounds = false)
+ public Point PointToImage(double x, double y, bool fitToBounds = true)
=> PointToImage(x, y, fitToBounds);
/// <summary>
@@ -968,7 +960,7 @@ namespace UVtools.WPF.Controls
/// if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
/// </param>
/// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
- public Point PointToImage(int x, int y, bool fitToBounds = false)
+ public Point PointToImage(int x, int y, bool fitToBounds = true)
{
return PointToImage(x, y, fitToBounds);
}
@@ -981,7 +973,7 @@ namespace UVtools.WPF.Controls
/// if set to <c>true</c> and the point is outside the bounds of the source image, it will be mapped to the nearest edge.
/// </param>
/// <returns><c>Point.Empty</c> if the point could not be matched to the source image, otherwise the new translated point</returns>
- public virtual Point PointToImage(Point point, bool fitToBounds)
+ public virtual Point PointToImage(Point point, bool fitToBounds = true)
{
double x;
double y;
@@ -990,13 +982,13 @@ namespace UVtools.WPF.Controls
if (!fitToBounds || viewport.Contains(point))
{
- x = ((point.X + Offset.X - viewport.X) / ZoomFactor);
- y = ((point.Y + Offset.Y - viewport.Y) / ZoomFactor);
+ x = (point.X + Offset.X - viewport.X) / ZoomFactor;
+ y = (point.Y + Offset.Y - viewport.Y) / ZoomFactor;
if (fitToBounds)
{
- x = x.Clamp(0, Image.Size.Width);
- y = y.Clamp(0, Image.Size.Height);
+ x = x.Clamp(0, Image.Size.Width-1);
+ y = y.Clamp(0, Image.Size.Height-1);
}
}
else
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml
index badccba..416e9d9 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml
@@ -44,7 +44,7 @@
/>
<TextBlock Grid.Row="2" Grid.Column="4"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
@@ -59,12 +59,11 @@
Minimum="1"
Maximum="1000"
FormatString="F02"
- Value="{Binding Operation.NormalLayers}"
- />
+ Value="{Binding Operation.NormalLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="10"
IsEnabled="{Binding !Operation.SyncLayers}"
VerticalAlignment="Center"
- Text="{Binding Operation.NormalHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.NormalHeight, StringFormat=\{0:F3\}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
@@ -163,7 +162,7 @@
Spacing="0">
<TextBlock FontWeight="Bold">
<TextBlock.Text>
- <MultiBinding StringFormat="{}{0} / {1:0.00}mm">
+ <MultiBinding StringFormat="{}{0} / {1:F3}mm">
<Binding Path="Operation.LayerCount"/>
<Binding Path="Operation.TotalHeight"/>
</MultiBinding>
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
index a3cf1dc..4f428d7 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
@@ -75,7 +75,7 @@
Value="{Binding Operation.BottomLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="8"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml
index fb483b9..76c82ff 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateGrayscaleControl.axaml
@@ -46,7 +46,7 @@
<TextBlock
FontWeight="Bold"
- Text="{Binding Operation.TotalHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.TotalHeight, StringFormat=\{0:F3\}mm}"/>
</StackPanel>
@@ -63,7 +63,7 @@
/>
<TextBlock Grid.Row="2" Grid.Column="4"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
<TextBlock Grid.Row="2" Grid.Column="6"
VerticalAlignment="Center"
@@ -77,7 +77,7 @@
Value="{Binding Operation.InterfaceLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="10"
VerticalAlignment="Center"
- Text="{Binding Operation.InterfaceHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.InterfaceHeight, StringFormat=\{0:F3\}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
@@ -90,7 +90,7 @@
Value="{Binding Operation.NormalLayers}"/>
<TextBlock Grid.Row="4" Grid.Column="4"
VerticalAlignment="Center"
- Text="{Binding Operation.NormalHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.NormalHeight, StringFormat=\{0:F3\}mm}"/>
<TextBlock Grid.Row="6" Grid.Column="0"
VerticalAlignment="Center"
@@ -164,7 +164,7 @@
<StackPanel>
<TextBlock FontWeight="Bold">
<TextBlock.Text>
- <MultiBinding StringFormat="Pie settings [{0} divisions with {1:0.00}º steps]">
+ <MultiBinding StringFormat="Pie settings [{0} divisions with {1:F2}º steps]">
<Binding Path="Operation.Divisions"/>
<Binding Path="Operation.AngleStep"/>
</MultiBinding>
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml
index d2c6ec5..584caa5 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateStressTowerControl.axaml
@@ -76,7 +76,7 @@
Value="{Binding Operation.BottomLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="8"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
@@ -183,7 +183,7 @@
<TextBlock
FontWeight="Bold"
- Text="{Binding Operation.TotalHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.TotalHeight, StringFormat=\{0:F3\}mm}"/>
</StackPanel>
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml
index 67ce63a..909fe77 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateToleranceControl.axaml
@@ -76,7 +76,7 @@
Value="{Binding Operation.BottomLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="8"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
@@ -140,7 +140,7 @@
<TextBlock
FontWeight="Bold"
- Text="{Binding Operation.RealZSize, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.RealZSize, StringFormat=\{0:F3\}mm}"/>
</StackPanel>
@@ -290,13 +290,13 @@
VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="2"
- Text="{Binding Operation.FemaleDiameterRealXSize, StringFormat=\{0:0.00\}mm}"
+ Text="{Binding Operation.FemaleDiameterRealXSize, StringFormat=\{0:F2\}mm}"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="8"
- Text="{Binding Operation.FemaleHoleDiameterRealXSize, StringFormat=\{0:0.00\}mm}"
+ Text="{Binding Operation.FemaleHoleDiameterRealXSize, StringFormat=\{0:F2\}mm}"
FontWeight="Bold"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml
index c6894f1..1d96018 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateXYZAccuracyControl.axaml
@@ -77,7 +77,7 @@
Value="{Binding Operation.BottomLayers}"/>
<TextBlock Grid.Row="2" Grid.Column="8"
VerticalAlignment="Center"
- Text="{Binding Operation.BottomHeight, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.BottomHeight, StringFormat=\{0:F3\}mm}"/>
<TextBlock Grid.Row="4" Grid.Column="0"
VerticalAlignment="Center"
@@ -169,7 +169,7 @@
<TextBlock
FontWeight="Bold"
- Text="{Binding Operation.RealZSize, StringFormat=\{0:0.00\}mm}"/>
+ Text="{Binding Operation.RealZSize, StringFormat=\{0:0.F3\}mm}"/>
</StackPanel>
diff --git a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml
index 383ac20..38e18af 100644
--- a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml
@@ -54,7 +54,7 @@
Minimum="{Binding SlicerFile.LayerHeight}"
Maximum="{Binding Operation.MaximumLayerHeight}"
Increment="0.01"
- FormatString="F2"
+ FormatString="F3"
Value="{Binding Operation.MinimumLayerHeight}"/>
<TextBlock Grid.Row="2" Grid.Column="4"
VerticalAlignment="Center"
@@ -72,7 +72,7 @@
Minimum="{Binding MinimumLayerHeight}"
Maximum="{Binding MaximumLayerHeight}"
Increment="0.01"
- FormatString="F2"
+ FormatString="F3"
Value="{Binding Operation.MaximumLayerHeight}"/>
<TextBlock Grid.Row="2" Grid.Column="10"
VerticalAlignment="Center"
diff --git a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs
index abe252b..c83933c 100644
--- a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using UVtools.Core;
using UVtools.Core.FileFormats;
using UVtools.Core.Operations;
using UVtools.WPF.Extensions;
@@ -13,7 +14,7 @@ namespace UVtools.WPF.Controls.Tools
public OperationDynamicLayerHeight Operation => BaseOperation as OperationDynamicLayerHeight;
public double LayerHeight => SlicerFile.LayerHeight;
- public double MinimumLayerHeight => Math.Round(SlicerFile.LayerHeight*2, 2);
+ public double MinimumLayerHeight => Layer.RoundHeight(SlicerFile.LayerHeight * 2);
public double MaximumLayerHeight => FileFormat.MaximumLayerHeight;
private DataGrid ExposureTable;
@@ -33,7 +34,7 @@ namespace UVtools.WPF.Controls.Tools
for (uint layerIndex = 1; layerIndex < SlicerFile.LayerCount; layerIndex++)
{
- if ((decimal)Math.Round(SlicerFile[layerIndex].PositionZ - SlicerFile[layerIndex - 1].PositionZ, 2) ==
+ if ((decimal)Math.Round(SlicerFile[layerIndex].PositionZ - SlicerFile[layerIndex - 1].PositionZ, Layer.HeightPrecision) ==
(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" +
diff --git a/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml b/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml
index 7d2e04f..de378c1 100644
--- a/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml
@@ -5,7 +5,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="UVtools.WPF.Controls.Tools.ToolIPrintedThisFileControl">
- <Grid RowDefinitions="Auto,10,Auto,10,Auto"
+ <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto"
ColumnDefinitions="Auto,10,Auto">
<TextBlock Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center"
@@ -48,6 +48,23 @@
<TextBlock VerticalAlignment="Center"
Text="{Binding Operation.PrintTimeHours, StringFormat=s / {0:N4}h}"/>
</StackPanel>
+
+ <TextBlock Grid.Row="6" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Number of time(s) the file has been printed.
+&#x0a;Half numbers can be used to consume from a failed print. Example: 0.5x if a print canceled at 50% progress."
+ Text="Multiplier:"/>
+
+ <StackPanel Grid.Row="6" Grid.Column="2"
+ Orientation="Horizontal" Spacing="5">
+ <NumericUpDown Minimum="0.01"
+ Maximum="500"
+ Increment="1"
+ MinWidth="200"
+ Value="{Binding Operation.Multiplier}"/>
+ <TextBlock VerticalAlignment="Center"
+ Text="time(s)"/>
+ </StackPanel>
</Grid>
</UserControl>
diff --git a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs
index 6bb905c..774f1cb 100644
--- a/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolLayerCloneControl.axaml.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Markup.Xaml;
+using UVtools.Core;
using UVtools.Core.Operations;
namespace UVtools.WPF.Controls.Tools
@@ -15,7 +16,7 @@ namespace UVtools.WPF.Controls.Tools
get
{
uint extraLayers = ExtraLayers;
- return $"Layers: {App.SlicerFile.LayerCount} → {App.SlicerFile.LayerCount + extraLayers} (+ {extraLayers})";
+ return $"Layers: {App.SlicerFile.LayerCount} → {SlicerFile.LayerCount + extraLayers} (+ {extraLayers})";
}
}
@@ -23,8 +24,8 @@ namespace UVtools.WPF.Controls.Tools
{
get
{
- float extraHeight = (float)Math.Round(ExtraLayers * App.SlicerFile.LayerHeight, 2);
- return $"Height: {App.SlicerFile.PrintHeight}mm → {Math.Round(App.SlicerFile.PrintHeight + extraHeight, 2)}mm (+ {extraHeight}mm)";
+ float extraHeight = Layer.RoundHeight(ExtraLayers * SlicerFile.LayerHeight);
+ return $"Height: {App.SlicerFile.PrintHeight}mm → {Layer.RoundHeight(SlicerFile.PrintHeight + extraHeight)}mm (+ {extraHeight}mm)";
}
}
diff --git a/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs
index 6e56c20..b3a9ea1 100644
--- a/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolLayerRemoveControl.axaml.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Markup.Xaml;
+using UVtools.Core;
using UVtools.Core.Operations;
namespace UVtools.WPF.Controls.Tools
@@ -15,7 +16,7 @@ namespace UVtools.WPF.Controls.Tools
get
{
uint extraLayers = ExtraLayers;
- return $"Layers: {App.SlicerFile.LayerCount} → {App.SlicerFile.LayerCount - extraLayers} (- {extraLayers})";
+ return $"Layers: {SlicerFile.LayerCount} → {SlicerFile.LayerCount - extraLayers} (- {extraLayers})";
}
}
@@ -23,8 +24,8 @@ namespace UVtools.WPF.Controls.Tools
{
get
{
- float extraHeight = (float)Math.Round(ExtraLayers * App.SlicerFile.LayerHeight, 2);
- return $"Height: {App.SlicerFile.PrintHeight}mm → {Math.Round(App.SlicerFile.PrintHeight - extraHeight, 2)}mm (- {extraHeight}mm)";
+ float extraHeight = Layer.RoundHeight(ExtraLayers * SlicerFile.LayerHeight);
+ return $"Height: {SlicerFile.PrintHeight}mm → {Layer.RoundHeight(App.SlicerFile.PrintHeight - extraHeight)}mm (- {extraHeight}mm)";
}
}
diff --git a/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml
index b9d3020..df1a49c 100644
--- a/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml
@@ -67,7 +67,7 @@ you must manually input the layer index of the last raft where it ends and suppo
<TextBlock
Grid.Row="6" Grid.Column="4"
IsVisible="{Binding !Operation.IsDecimate}"
- Text="{Binding Operation.BrightnessPercent, StringFormat=\{0:0.00\}%}" VerticalAlignment="Center"/>
+ Text="{Binding Operation.BrightnessPercent, StringFormat=\{0:F2\}%}" VerticalAlignment="Center"/>
diff --git a/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml
index 92bd9ef..79248d8 100644
--- a/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml
@@ -65,7 +65,7 @@
Value="{Binding Operation.Brightness}"/>
<TextBlock
VerticalAlignment="Center"
- Text="{Binding Operation.BrightnessPercent, StringFormat=\{0:0.00\}%}"/>
+ Text="{Binding Operation.BrightnessPercent, StringFormat=\{0:F2\}%}"/>
</StackPanel>
</Grid>
diff --git a/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml b/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml
index ff8571c..3afc296 100644
--- a/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolResizeControl.axaml
@@ -13,7 +13,7 @@
Minimum="1"
Maximum="10000"
Increment="0.1"
- FormatString="{}{0:0.00} %"
+ FormatString="{}{0:F2} %"
Value="{Binding Operation.X}"/>
<TextBlock
@@ -26,7 +26,7 @@
Minimum="1"
Maximum="10000"
Increment="0.1"
- FormatString="{}{0:0.00} %"
+ FormatString="{}{0:F2} %"
Value="{Binding Operation.Y}"
IsEnabled="{Binding !#ConstrainXY.IsChecked}"
/>
diff --git a/UVtools.WPF/ErrorLog.cs b/UVtools.WPF/ErrorLog.cs
new file mode 100644
index 0000000..d918064
--- /dev/null
+++ b/UVtools.WPF/ErrorLog.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+
+namespace UVtools.WPF
+{
+ public static class ErrorLog
+ {
+ private const string Filename = "errors.log";
+
+ public static string FullPath = Path.Combine(UserSettings.SettingsFolder, Filename);
+
+ public static void AppendLine(string errorType, string text)
+ {
+ try
+ {
+ File.AppendAllText(FullPath,
+ $"({errorType}) @ {DateTime.Now}: {text}{Environment.NewLine}");
+ }
+ catch (Exception exception)
+ {
+ Debug.WriteLine(exception);
+ }
+ }
+
+ public static StreamWriter AppendText()
+ {
+ return File.AppendText(FullPath);
+ }
+ }
+}
diff --git a/UVtools.WPF/MainWindow.Clipboard.cs b/UVtools.WPF/MainWindow.Clipboard.cs
index 7c536e5..bcbf66c 100644
--- a/UVtools.WPF/MainWindow.Clipboard.cs
+++ b/UVtools.WPF/MainWindow.Clipboard.cs
@@ -74,6 +74,12 @@ namespace UVtools.WPF
{
ROI = GetTransposedRectangle(clip.Operation.ROI);
}
+
+ if (clip.Operation.HaveMask)
+ {
+ AddMaskPoints(clip.Operation.MaskPoints);
+ }
+
var operation = await ShowRunOperation(clip.Operation.GetType(), clip.Operation);
if (operation is null)
{
diff --git a/UVtools.WPF/MainWindow.GCode.cs b/UVtools.WPF/MainWindow.GCode.cs
index 01f4793..18a7b27 100644
--- a/UVtools.WPF/MainWindow.GCode.cs
+++ b/UVtools.WPF/MainWindow.GCode.cs
@@ -20,7 +20,7 @@ namespace UVtools.WPF
public bool HaveGCode => IsFileLoaded && SlicerFile.HaveGCode;
public string GCodeStr => SlicerFile?.GCodeStr;
- public int GCodeLines => !HaveGCode ? 0 : SlicerFile.GCodeStr.Split('\n').Length;
+ public uint GCodeLines => !HaveGCode ? 0 : SlicerFile.GCode.LineCount;
public void OnClickRebuildGcode()
{
diff --git a/UVtools.WPF/MainWindow.Information.cs b/UVtools.WPF/MainWindow.Information.cs
index 58c4d3c..7e35a3c 100644
--- a/UVtools.WPF/MainWindow.Information.cs
+++ b/UVtools.WPF/MainWindow.Information.cs
@@ -19,6 +19,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using MessageBox.Avalonia.Enums;
+using UVtools.Core;
using UVtools.Core.FileFormats;
using UVtools.Core.Objects;
using UVtools.WPF.Extensions;
@@ -332,12 +333,13 @@ namespace UVtools.WPF
public void RefreshCurrentLayerData()
{
+
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 StringTag(nameof(layer.LayerHeight), $"{Layer.ShowHeight(layer.LayerHeight)}mm"));
//CurrentLayerProperties.Add(new KeyValuePair<string, string>(nameof(layer.Filename), layer.Filename));
- CurrentLayerProperties.Add(new StringTag(nameof(layer.PositionZ), $"{layer.PositionZ:F2}mm"));
+ CurrentLayerProperties.Add(new StringTag(nameof(layer.PositionZ), $"{Layer.ShowHeight(layer.PositionZ)}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()));
@@ -354,10 +356,11 @@ namespace UVtools.WPF
if (SlicerFile.PrintParameterPerLayerModifiers.Contains(FileFormat.PrintParameterModifier.RetractSpeed))
CurrentLayerProperties.Add(new StringTag(nameof(layer.RetractSpeed),
$"{layer.RetractSpeed}mm/min"));
- if (SlicerFile.PrintParameterPerLayerModifiers.Contains(FileFormat.PrintParameterModifier.LightOffDelay)
- )
+
+ if (SlicerFile.PrintParameterPerLayerModifiers.Contains(FileFormat.PrintParameterModifier.LightOffDelay))
CurrentLayerProperties.Add(new StringTag(nameof(layer.LightOffDelay),
$"{layer.LightOffDelay}s"));
+
if (SlicerFile.PrintParameterPerLayerModifiers.Contains(FileFormat.PrintParameterModifier.LightPWM))
CurrentLayerProperties.Add(new StringTag(nameof(layer.LightPWM), layer.LightPWM.ToString()));
}
diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs
index 87abea6..2971ea6 100644
--- a/UVtools.WPF/MainWindow.Issues.cs
+++ b/UVtools.WPF/MainWindow.Issues.cs
@@ -146,7 +146,6 @@ namespace UVtools.WPF
new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels)))
{
CvInvoke.DrawContours(image, contours, -1, new MCvScalar(255), -1);
- //CvInvoke.DrawContours(image, contours, -1, new MCvScalar(255), 2);
}
edited = true;
diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs
index 4146643..68e1137 100644
--- a/UVtools.WPF/MainWindow.LayerPreview.cs
+++ b/UVtools.WPF/MainWindow.LayerPreview.cs
@@ -60,6 +60,11 @@ namespace UVtools.WPF
private uint _actualLayer;
private bool _showLayerImageRotated;
+ private bool _showLayerImageRotateCwDirection = true;
+ private bool _showLayerImageRotateCcwDirection;
+ private bool _showLayerImageFlipped;
+ private bool _showLayerImageFlippedHorizontally = true;
+ private bool _showLayerImageFlippedVertically;
private bool _showLayerImageDifference;
private bool _showLayerImageIssues = true;
private bool _showLayerImageCrosshairs = true;
@@ -74,8 +79,9 @@ namespace UVtools.WPF
private long _showLayerRenderMs;
- public LayerCache LayerCache = new LayerCache();
+ public LayerCache LayerCache = new ();
private Point _lastPixelMouseLocation = Point.Empty;
+ private readonly List<Point[]> _maskPoints = new ();
public void InitLayerPreview()
@@ -173,11 +179,17 @@ namespace UVtools.WPF
get => _showLayerImageRotated;
set
{
- if (!RaiseAndSetIfChanged(ref _showLayerImageRotated, value)) return;
var rect = LayerImageBox.SelectionRegion;
if (!rect.IsEmpty)
{
- LayerImageBox.SelectionRegion = GetTransposedRectangle(rect.ToDotNet(), _showLayerImageRotated, true).ToAvalonia();
+ rect = GetTransposedRectangle(rect.ToDotNet(), true).ToAvalonia();
+ }
+
+ if (!RaiseAndSetIfChanged(ref _showLayerImageRotated, value) || !IsFileLoaded) return;
+
+ if (!rect.IsEmpty)
+ {
+ LayerImageBox.SelectionRegion = GetTransposedRectangle(rect.ToDotNet()).ToAvalonia();
}
ZoomToFit();
@@ -185,6 +197,122 @@ namespace UVtools.WPF
}
}
+ public bool ShowLayerImageRotateCWDirection
+ {
+ get => _showLayerImageRotateCwDirection;
+ set
+ {
+ var rect = LayerImageBox.SelectionRegion;
+ if (!rect.IsEmpty)
+ {
+ rect = GetTransposedRectangle(rect.ToDotNet(), true).ToAvalonia();
+ }
+
+ if (!RaiseAndSetIfChanged(ref _showLayerImageRotateCwDirection, value)) return;
+ if (!_showLayerImageRotated) return;
+
+ if (!rect.IsEmpty)
+ {
+ LayerImageBox.SelectionRegion = GetTransposedRectangle(rect.ToDotNet()).ToAvalonia();
+ }
+
+ ZoomToFit();
+ ShowLayer();
+ }
+ }
+
+ public bool ShowLayerImageRotateCCWDirection
+ {
+ get => _showLayerImageRotateCcwDirection;
+ set
+ {
+ var rect = LayerImageBox.SelectionRegion;
+ if (!rect.IsEmpty)
+ {
+ rect = GetTransposedRectangle(rect.ToDotNet(), true).ToAvalonia();
+ }
+
+ if (!RaiseAndSetIfChanged(ref _showLayerImageRotateCcwDirection, value)) return;
+ if (!_showLayerImageRotated) return;
+
+ if (!rect.IsEmpty)
+ {
+ LayerImageBox.SelectionRegion = GetTransposedRectangle(rect.ToDotNet()).ToAvalonia();
+ }
+
+ ZoomToFit();
+ ShowLayer();
+ }
+ }
+
+ public bool ShowLayerImageFlipped
+ {
+ get => _showLayerImageFlipped;
+ set
+ {
+ var rect = LayerImageBox.SelectionRegion;
+ if (!rect.IsEmpty)
+ {
+ rect = GetTransposedRectangle(rect.ToDotNet(), true).ToAvalonia();
+ }
+
+ if (!RaiseAndSetIfChanged(ref _showLayerImageFlipped, value)) return;
+
+ if (!rect.IsEmpty)
+ {
+ LayerImageBox.SelectionRegion = GetTransposedRectangle(rect.ToDotNet()).ToAvalonia();
+ }
+
+ ShowLayer();
+ }
+ }
+
+ public bool ShowLayerImageFlippedHorizontally
+ {
+ get => _showLayerImageFlippedHorizontally;
+ set
+ {
+ var rect = LayerImageBox.SelectionRegion;
+ if (!rect.IsEmpty)
+ {
+ rect = GetTransposedRectangle(rect.ToDotNet(), true).ToAvalonia();
+ }
+
+ if (!RaiseAndSetIfChanged(ref _showLayerImageFlippedHorizontally, value)) return;
+ if (!_showLayerImageFlipped) return;
+
+ if (!rect.IsEmpty)
+ {
+ LayerImageBox.SelectionRegion = GetTransposedRectangle(rect.ToDotNet()).ToAvalonia();
+ }
+
+ ShowLayer();
+ }
+ }
+
+ public bool ShowLayerImageFlippedVertically
+ {
+ get => _showLayerImageFlippedVertically;
+ set
+ {
+ var rect = LayerImageBox.SelectionRegion;
+ if (!rect.IsEmpty)
+ {
+ rect = GetTransposedRectangle(rect.ToDotNet(), true).ToAvalonia();
+ }
+
+ if (!RaiseAndSetIfChanged(ref _showLayerImageFlippedVertically, value)) return;
+ if (!_showLayerImageFlipped) return;
+
+ if (!rect.IsEmpty)
+ {
+ LayerImageBox.SelectionRegion = GetTransposedRectangle(rect.ToDotNet()).ToAvalonia();
+ }
+
+ ShowLayer();
+ }
+ }
+
public bool ShowLayerImageDifference
{
get => _showLayerImageDifference;
@@ -276,7 +404,9 @@ namespace UVtools.WPF
public string MinimumLayerString => SlicerFile is null ? "???" : $"{SlicerFile.LayerHeight}mm\n0";
public string MaximumLayerString => SlicerFile is null ? "???" : $"{SlicerFile.PrintHeight}mm\n{SlicerFile.LayerCount - 1}";
- public string ActualLayerTooltip => SlicerFile is null ? "???" : $"{LayerCache.Layer?.PositionZ:0.00}mm\n{ActualLayer}\n{(ActualLayer + 1) * 100 / (SlicerFile.LayerCount)}%";
+ public string ActualLayerTooltip => SlicerFile is null ? "???" : $"{Layer.ShowHeight(LayerCache.Layer?.PositionZ ?? 0)}mm\n" +
+ $"{ActualLayer}\n" +
+ $"{(ActualLayer + 1) * 100 / SlicerFile.LayerCount}%";
public uint SliderMaximumValue => SlicerFile?.LastLayerIndex ?? 0;
@@ -332,7 +462,10 @@ namespace UVtools.WPF
get
{
var roi = ROI;
- if(roi.IsEmpty) return "ROI: NS";
+ if (roi.IsEmpty)
+ {
+ return _maskPoints.Count > 0 ? $"Masks: {_maskPoints.Count}" : "ROI: NS";
+ }
var text = $"ROI: {roi} ({roi.Area()}px²)";
var roiMillimeters = ROIMillimeters;
if (!roiMillimeters.IsEmpty)
@@ -425,13 +558,13 @@ namespace UVtools.WPF
}
- #region ROI
+ #region ROI & Mask
public Rectangle ROI
{
get
{
var rect = LayerImageBox.SelectionRegion;
- return rect.IsEmpty ? Rectangle.Empty : GetTransposedRectangle(rect.ToDotNet(), false);
+ return rect.IsEmpty ? Rectangle.Empty : GetTransposedRectangle(rect.ToDotNet(), true);
}
set => LayerImageBox.SelectionRegion = value.ToAvalonia();
}
@@ -451,6 +584,53 @@ namespace UVtools.WPF
}
}
+ public List<Point[]> MaskPoints => _maskPoints;
+
+ /*private set
+ {
+ if(!RaiseAndSetIfChanged(ref _maskPoints, value)) return;
+ ShowLayer();
+ }*/
+ public void AddMaskPoints(Point[] points)
+ {
+ if (_maskPoints.RemoveAll(points1 => points1.SequenceEqual(points)) <= 0)
+ {
+ _maskPoints.Add(points);
+ }
+
+ if(_maskPoints.Count > 0 && Settings.LayerPreview.MaskClearROIAfterSet) ClearROI();
+
+ ShowLayer();
+ RaisePropertyChanged(nameof(LayerROIStr));
+ }
+
+ public void AddMaskPoints(Point[][] points)
+ {
+ _maskPoints.Clear();
+ _maskPoints.AddRange(points);
+ ShowLayer();
+ RaisePropertyChanged(nameof(LayerROIStr));
+ }
+
+ public void ClearMask()
+ {
+ if (_maskPoints.Count <= 0) return;
+ _maskPoints.Clear();
+ ShowLayer();
+ RaisePropertyChanged(nameof(LayerROIStr));
+ }
+
+ public void ClearROI()
+ {
+ ROI = Rectangle.Empty;
+ }
+
+ public void ClearROIAndMask()
+ {
+ ClearROI();
+ ClearMask();
+ }
+
public void OnROIClick()
{
ZoomToFit(ZoomToFitType.Selection);
@@ -706,6 +886,16 @@ namespace UVtools.WPF
}
}
+ if (_maskPoints is not null && _maskPoints.Count > 0)
+ {
+ using var vec = new VectorOfVectorOfPoint(_maskPoints.ToArray());
+ CvInvoke.DrawContours(LayerCache.ImageBgr, vec, -1,
+ new MCvScalar(Settings.LayerPreview.MaskOutlineColor.B,
+ Settings.LayerPreview.MaskOutlineColor.G,
+ Settings.LayerPreview.MaskOutlineColor.R),
+ Settings.LayerPreview.MaskOutlineLineThickness);
+ }
+
for (var index = 0; index < Drawings.Count; index++)
{
if (Drawings[index].LayerIndex != ActualLayer) continue;
@@ -760,12 +950,19 @@ namespace UVtools.WPF
}
else if (operation.OperationType == PixelOperation.PixelOperationType.Eraser)
{
+ //var pixelBrightness = LayerCache.Image.GetPixelPos(operation.Location);
if (imageSpan[LayerCache.Image.GetPixelPos(operation.Location)] < 10) continue;
var color = DrawingsGrid.SelectedItems.Contains(operation)
? Settings.PixelEditor.RemovePixelHighlightColor
: Settings.PixelEditor.RemovePixelColor;
for (int i = 0; i < LayerCache.LayerContours.Size; i++)
{
+ /*if (pixelBrightness < 10 && (int) LayerCache.LayerHierarchyJagged.GetValue(0, i, 2) != -1 ||
+ (int) LayerCache.LayerHierarchyJagged.GetValue(0, i, 3) == -1)
+ {
+ continue;
+ }*/
+
if (CvInvoke.PointPolygonTest(LayerCache.LayerContours[i], operation.Location, false) >= 0)
{
CvInvoke.DrawContours(LayerCache.ImageBgr, LayerCache.LayerContours, i,
@@ -820,11 +1017,25 @@ namespace UVtools.WPF
}
}
+ if (_showLayerImageFlipped)
+ {
+ var flipType = FlipType.None;
+ if (_showLayerImageFlippedHorizontally)
+ flipType |= FlipType.Horizontal;
+ if (_showLayerImageFlippedVertically)
+ flipType |= FlipType.Vertical;
+
+ if (flipType != FlipType.None)
+ CvInvoke.Flip(LayerCache.ImageBgr, LayerCache.ImageBgr, flipType);
+ }
+
if (_showLayerImageRotated)
{
- CvInvoke.Rotate(LayerCache.ImageBgr, LayerCache.ImageBgr, RotateFlags.Rotate90Clockwise);
+ CvInvoke.Rotate(LayerCache.ImageBgr, LayerCache.ImageBgr, _showLayerImageRotateCcwDirection ? RotateFlags.Rotate90CounterClockwise : RotateFlags.Rotate90Clockwise);
}
+
+
LayerImageBox.Image = LayerCache.Bitmap = LayerCache.ImageBgr.ToBitmap();
@@ -912,27 +1123,115 @@ namespace UVtools.WPF
lineThickness);
}
- public Point GetTransposedPoint(Point point, bool clockWise = true, bool ignoreLayerRotation = false)
+ public Point GetTransposedPoint(Point point, bool inverse = false)
{
- if (!_showLayerImageRotated && !ignoreLayerRotation) return point;
- return clockWise
- ? new Point(point.Y, LayerCache.Image.Height - 1 - point.X)
- : new Point(LayerCache.Image.Height - 1 - point.Y, point.X);
+ if (point.IsEmpty) return point;
+
+ void Flip()
+ {
+ if (!_showLayerImageFlipped) return;
+ if (_showLayerImageFlippedHorizontally)
+ {
+ point = new Point(LayerCache.Image.Width - 1 - point.X, point.Y);
+ }
+
+ if (_showLayerImageFlippedVertically)
+ {
+ point = new Point(point.X, LayerCache.Image.Height - 1 - point.Y);
+ }
+ }
+
+ void Rotate()
+ {
+ if (!_showLayerImageRotated) return;
+ if (_showLayerImageRotateCcwDirection)
+ {
+ point = inverse
+ ? new Point(point.Y, LayerCache.Image.Width - 1 - point.X) // 90º CCW
+ : new Point(LayerCache.Image.Width - 1 - point.Y, point.X); // 90º CW
+
+ }
+ else
+ {
+ point = inverse
+ ? new Point(LayerCache.Image.Height - 1 - point.Y, point.X) // 90º CW
+ : new Point(point.Y, LayerCache.Image.Height - 1 - point.X); // 90º CCW
+ }
+ }
+
+ if (inverse)
+ {
+ Flip();
+ Rotate();
+ }
+ else
+ {
+ Rotate();
+ Flip();
+ }
+
+ return point;
}
- public Rectangle GetTransposedRectangle(RectangleF rectangleF, bool clockWise = true, bool ignoreLayerRotation = false) =>
- GetTransposedRectangle(Rectangle.Round(rectangleF), clockWise, ignoreLayerRotation);
+ public Rectangle GetTransposedRectangle(RectangleF rectangleF, bool inverse = true) =>
+ GetTransposedRectangle(Rectangle.Round(rectangleF), inverse);
- public Rectangle GetTransposedRectangle(Rectangle rectangle, bool clockWise = true, bool ignoreLayerRotation = false)
+ public Rectangle GetTransposedRectangle(Rectangle rectangle, bool inverse = false)
{
- if (rectangle.IsEmpty || (!ignoreLayerRotation && !_showLayerImageRotated)) return rectangle;
- return clockWise
+ if (rectangle.IsEmpty) return rectangle;
+
+ void Flip()
+ {
+ if (!_showLayerImageFlipped) return;
+ if (_showLayerImageFlippedHorizontally)
+ {
+ rectangle.Location = new Point(LayerCache.Image.Width - 1 - rectangle.Right, rectangle.Y);
+ }
+
+ if (_showLayerImageFlippedVertically)
+ {
+ rectangle.Location = new Point(rectangle.X, LayerCache.Image.Height - 1 - rectangle.Bottom);
+ }
+ }
+
+ void Rotate()
+ {
+ if (!_showLayerImageRotated) return;
+ if (_showLayerImageRotateCcwDirection)
+ {
+ rectangle = !inverse
+ ? new Rectangle(rectangle.Y, LayerCache.Image.Width - rectangle.Right, rectangle.Height, rectangle.Width) // 90º CCW
+ : new Rectangle(LayerCache.Image.Width - rectangle.Bottom, rectangle.X, rectangle.Height, rectangle.Width); // 90º CW
+
+ }
+ else
+ {
+ rectangle = !inverse
+ ? new Rectangle(LayerCache.Image.Height - rectangle.Bottom, rectangle.X, rectangle.Height, rectangle.Width) // 90º CW
+ : new Rectangle(rectangle.Y, LayerCache.Image.Height - rectangle.Right, rectangle.Height, rectangle.Width); // 90º CCW
+ }
+ }
+
+ if (!inverse)
+ {
+ Flip();
+ Rotate();
+ }
+ else
+ {
+ Rotate();
+ Flip();
+ }
+
+ return rectangle;
+
+ /*return inverse
? new Rectangle(LayerCache.Image.Height - rectangle.Bottom,
rectangle.Left, rectangle.Height, rectangle.Width)
//: new Rectangle(ActualLayerImage.Width - rectangle.Bottom, rectangle.Left, rectangle.Width, rectangle.Height);
//: new Rectangle(ActualLayerImage.Width - rectangle.Bottom, ActualLayerImage.Height-rectangle.Right, rectangle.Width, rectangle.Height); // Rotate90FlipX: // = Rotate270FlipY
//: new Rectangle(rectangle.Top, rectangle.Left, rectangle.Width, rectangle.Height); // Rotate270FlipX: // = Rotate90FlipY
- : new Rectangle(rectangle.Top, LayerCache.Image.Height - rectangle.Right, rectangle.Height, rectangle.Width); // Rotate90FlipNone: // = Rotate270FlipXY
+ : new Rectangle(rectangle.Top, LayerCache.Image.Height - rectangle.Right, rectangle.Height, rectangle.Width); // Rotate90FlipNone: // = Rotate270FlipXY*/
}
/// <summary>
@@ -943,10 +1242,9 @@ namespace UVtools.WPF
/// </summary>
private Rectangle GetTransposedIssueBounds(LayerIssue issue)
{
- if (issue.X >= 0 && issue.Y >= 0 && (issue.BoundingRectangle.IsEmpty || issue.Size == 1) &&
- _showLayerImageRotated)
- return new Rectangle(LayerCache.Image.Height - 1 - issue.Y,
- issue.X, 1, 1);
+ if (issue.X >= 0 && issue.Y >= 0 && (issue.BoundingRectangle.IsEmpty || issue.Size == 1))
+ return new Rectangle(GetTransposedPoint(issue.FirstPoint, true), new Size(1, 1));
+ //return new Rectangle(LayerCache.Image.Height - 1 - issue.Y, issue.X, 1, 1);
return GetTransposedRectangle(issue.BoundingRectangle);
}
@@ -1078,7 +1376,7 @@ namespace UVtools.WPF
case ZoomToFitType.Auto:
if (Settings.LayerPreview.ZoomToFitPrintVolumeBounds ^ (_globalModifiers & KeyModifiers.Alt) != 0)
{
- if (!_showLayerImageRotated)
+ /*if (!_showLayerImageRotated)
{
LayerImageBox.ZoomToRegion(SlicerFile.LayerManager.BoundingRectangle, margin);
}
@@ -1089,7 +1387,8 @@ namespace UVtools.WPF
SlicerFile.LayerManager.BoundingRectangle.Height,
SlicerFile.LayerManager.BoundingRectangle.Width, margin
);
- }
+ }*/
+ LayerImageBox.ZoomToRegion(GetTransposedRectangle(SlicerFile.BoundingRectangle), margin);
}
else
{
@@ -1100,7 +1399,7 @@ namespace UVtools.WPF
LayerImageBox.ZoomToFit();
break;
case ZoomToFitType.Volume:
- LayerImageBox.ZoomToRegion(GetTransposedRectangle(SlicerFile.LayerManager.BoundingRectangle), margin);
+ LayerImageBox.ZoomToRegion(GetTransposedRectangle(SlicerFile.BoundingRectangle), margin);
break;
case ZoomToFitType.Selection:
LayerImageBox.ZoomToSelectionRegion(margin);
@@ -1152,6 +1451,13 @@ namespace UVtools.WPF
if (e.InitialPressMouseButton == MouseButton.Right)
{
if (!LayerImageBox.IsPointInImage(pointer.Position)) return;
+
+ if ((e.KeyModifiers & KeyModifiers.Alt) != 0)
+ {
+ SelectObjectMask(location);
+ return;
+ }
+
SelectObjectRoi(location);
return;
@@ -1213,7 +1519,18 @@ namespace UVtools.WPF
{
if (e.Key == Key.Escape)
{
- LayerImageBox.SelectNone();
+ if (e.KeyModifiers == KeyModifiers.Shift)
+ {
+ ClearROI();
+ }
+ /*else if(e.KeyModifiers == KeyModifiers.Alt)
+ {
+ ClearMask();
+ }*/
+ else
+ {
+ ClearROIAndMask();
+ }
e.Handled = true;
return;
}
@@ -1233,6 +1550,13 @@ namespace UVtools.WPF
e.Handled = true;
return;
}
+
+ if (e.Key == Key.F)
+ {
+ ShowLayerImageFlipped = !_showLayerImageFlipped;
+ e.Handled = true;
+ return;
+ }
}
}
@@ -1287,9 +1611,12 @@ namespace UVtools.WPF
var point = GetTransposedPoint(location);
var brightness = LayerCache.Image.GetByte(point);
- if (brightness == 0) return false;
for (int i = 0; i < LayerCache.LayerContours.Size; i++)
{
+ if (brightness == 0 &&
+ ((int) LayerCache.LayerHierarchyJagged.GetValue(0, i, 2) != -1 ||
+ (int) LayerCache.LayerHierarchyJagged.GetValue(0, i, 3) == -1)) continue;
+
if (CvInvoke.PointPolygonTest(LayerCache.LayerContours[i], point, false) >= 0)
{
var rectangle =
@@ -1329,10 +1656,33 @@ namespace UVtools.WPF
return (uint)rectangles.Count;
}
+ public bool SelectObjectMask(Point location)
+ {
+ var point = GetTransposedPoint(location);
+ var brightness = LayerCache.Image.GetByte(point);
+
+ for (int i = 0; i < LayerCache.LayerContours.Size; i++)
+ {
+ if (brightness == 0 &&
+ ((int)LayerCache.LayerHierarchyJagged.GetValue(0, i, 2) != -1 ||
+ (int)LayerCache.LayerHierarchyJagged.GetValue(0, i, 3) == -1)) continue;
+ if (CvInvoke.PointPolygonTest(LayerCache.LayerContours[i], point, false) >= 0)
+ {
+ //var rectangle = GetTransposedRectangle(CvInvoke.BoundingRectangle(LayerCache.LayerContours[i]));
+ //ROI = rectangle;
+ AddMaskPoints(LayerCache.LayerContours[i].ToArray());
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public void OnLayerPixelPickerClicked()
{
if (!LayerPixelPicker.IsSet) return;
- CenterLayerAt(GetTransposedPoint(LayerPixelPicker.Location, false), -1);
+ CenterLayerAt(GetTransposedPoint(LayerPixelPicker.Location, true), -1);
}
public async void SaveCurrentLayerImage()
@@ -1408,9 +1758,20 @@ namespace UVtools.WPF
//CvInvoke.Rectangle(cursor, new Rectangle(new Point(size.Width, 0), size), _pixelEditorCursorColor, 1, DrawingPixelText.LineType);
CvInvoke.PutText(cursor, text, new Point(size.Width, size.Height), DrawingPixelText.Font, DrawingPixelText.FontScale, _pixelEditorCursorColor, DrawingPixelText.Thickness, DrawingPixelText.LineType, DrawingPixelText.Mirror);
+ if (_showLayerImageFlipped)
+ {
+ var flipType = FlipType.None;
+ if (_showLayerImageFlippedHorizontally)
+ flipType |= FlipType.Horizontal;
+ if (_showLayerImageFlippedVertically)
+ flipType |= FlipType.Vertical;
+
+ if (flipType != FlipType.None)
+ CvInvoke.Flip(cursor, cursor, flipType);
+ }
if (_showLayerImageRotated)
{
- CvInvoke.Rotate(cursor, cursor, RotateFlags.Rotate90Clockwise);
+ CvInvoke.Rotate(cursor, cursor, _showLayerImageRotateCcwDirection ? RotateFlags.Rotate90CounterClockwise : RotateFlags.Rotate90Clockwise);
}
break;
case PixelOperation.PixelOperationType.Supports:
@@ -1439,7 +1800,7 @@ namespace UVtools.WPF
break;
}
- if (!(cursor is null))
+ if (cursor is not null)
{
LayerImageBox.TrackerImage = cursor.ToBitmap();
//cursor.Save("D:\\Cursor.png");
diff --git a/UVtools.WPF/MainWindow.PixelEditor.cs b/UVtools.WPF/MainWindow.PixelEditor.cs
index 04fee96..ea23b9a 100644
--- a/UVtools.WPF/MainWindow.PixelEditor.cs
+++ b/UVtools.WPF/MainWindow.PixelEditor.cs
@@ -63,7 +63,7 @@ namespace UVtools.WPF
return;
}
- Point location = GetTransposedPoint(operation.Location, false);
+ Point location = GetTransposedPoint(operation.Location, true);
if (Settings.LayerPreview.ZoomIssues ^ (_globalModifiers & KeyModifiers.Alt) != 0)
{
@@ -168,8 +168,8 @@ namespace UVtools.WPF
//Bitmap bmp = pbLayer.Image as Bitmap;
if (SelectedPixelOperationTabIndex == (byte)PixelOperation.PixelOperationType.Drawing)
{
- uint minLayer = Math.Max(0, _actualLayer - DrawingPixelDrawing.LayersBelow);
- uint maxLayer = Math.Min(SlicerFile.LayerCount - 1, _actualLayer + DrawingPixelDrawing.LayersAbove);
+ uint minLayer = (uint) Math.Max(0, (int)_actualLayer - DrawingPixelDrawing.LayersBelow);
+ uint maxLayer = Math.Min(SlicerFile.LastLayerIndex, _actualLayer + DrawingPixelDrawing.LayersAbove);
for (uint layerIndex = minLayer; layerIndex <= maxLayer; layerIndex++)
{
var operationDrawing = new PixelDrawing(layerIndex, realLocation, DrawingPixelDrawing.LineType,
@@ -247,9 +247,8 @@ namespace UVtools.WPF
{
if (string.IsNullOrEmpty(DrawingPixelText.Text) || DrawingPixelText.FontScale < 0.2) return;
- uint minLayer = Math.Max(0, ActualLayer - DrawingPixelText.LayersBelow);
- uint maxLayer = Math.Min(SlicerFile.LayerCount - 1,
- ActualLayer + DrawingPixelText.LayersAbove);
+ uint minLayer = (uint) Math.Max(0, (int)ActualLayer - DrawingPixelText.LayersBelow);
+ uint maxLayer = Math.Min(SlicerFile.LastLayerIndex, ActualLayer + DrawingPixelText.LayersAbove);
for (uint layerIndex = minLayer; layerIndex <= maxLayer; layerIndex++)
{
var operationText = new PixelText(layerIndex, realLocation, DrawingPixelText.LineType,
@@ -278,9 +277,8 @@ namespace UVtools.WPF
else if (SelectedPixelOperationTabIndex == (byte)PixelOperation.PixelOperationType.Eraser)
{
if (LayerCache.Image.GetByte(realLocation) < 10) return;
- uint minLayer = Math.Max(0, ActualLayer - DrawingPixelEraser.LayersBelow);
- uint maxLayer = Math.Min(SlicerFile.LayerCount - 1,
- ActualLayer + DrawingPixelEraser.LayersAbove);
+ uint minLayer = (uint) Math.Max(0, (int)ActualLayer - DrawingPixelEraser.LayersBelow);
+ uint maxLayer = Math.Min(SlicerFile.LastLayerIndex, ActualLayer + DrawingPixelEraser.LayersAbove);
for (uint layerIndex = minLayer; layerIndex <= maxLayer; layerIndex++)
{
var operationEraser = new PixelEraser(layerIndex, realLocation, DrawingPixelEraser.PixelBrightness);
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index 2229ca7..10587f1 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -851,7 +851,7 @@
Grid.Row="4"
Grid.Column="2"
Minimum="1"
- Maximum="255"
+ Maximum="4000"
HorizontalAlignment="Stretch"
Value="{Binding DrawingPixelDrawing.BrushSize}"/>
@@ -1567,7 +1567,7 @@
<Grid
IsEnabled="{Binding IsFileLoaded}"
DockPanel.Dock="Right"
- ColumnDefinitions="150"
+ ColumnDefinitions="160"
RowDefinitions="Auto,Auto,*,Auto,Auto,Auto,Auto" Margin="5">
<TextBlock
Text="{Binding MaximumLayerString}"
@@ -1709,14 +1709,48 @@
IsChecked="{Binding ShowLayerImageRotated}"
HotKey="Ctrl + R"
VerticalAlignment="Stretch"
- ToolTip.Tip="Auto rotate layer preview image at 90º (This can slow down the layer preview) [CTRL+R]"
- >
+ ToolTip.Tip="Auto rotate layer preview image at 90º (This can slow down the layer preview) [CTRL+R]">
+ <Button.ContextMenu>
+ <ContextMenu PlacementMode="Bottom">
+ <RadioButton
+ GroupName="ShowLayerImageRotateDirection"
+ IsChecked="{Binding ShowLayerImageRotateCWDirection}"
+ Content="90º Clockwise (CW)"/>
+ <RadioButton
+ GroupName="ShowLayerImageRotateDirection"
+ IsChecked="{Binding ShowLayerImageRotateCCWDirection}"
+ Content="90º Counter-clockwise (CCW)"/>
+ </ContextMenu>
+ </Button.ContextMenu>
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/undo-alt-16x16.png"/>
- <TextBlock Margin="5,0,5,0" Text="Rotate"/>
+ <TextBlock Margin="5,0,5,0" Text="Rotate ⮟"/>
</StackPanel>
</ToggleButton>
+
+ <ToggleButton
+ IsChecked="{Binding ShowLayerImageFlipped}"
+ HotKey="Ctrl + F"
+ VerticalAlignment="Stretch"
+ ToolTip.Tip="Auto flip layer preview image (This can slow down the layer preview) [CTRL+F]">
+ <Button.ContextMenu>
+ <ContextMenu PlacementMode="Bottom">
+ <CheckBox
+ IsChecked="{Binding ShowLayerImageFlippedHorizontally}"
+ Content="Horizontally"/>
+ <CheckBox
+ IsChecked="{Binding ShowLayerImageFlippedVertically}"
+ Content="Vertically"/>
+ </ContextMenu>
+ </Button.ContextMenu>
+ <StackPanel Orientation="Horizontal">
+ <Image Source="/Assets/Icons/flip-16x16.png"/>
+ <TextBlock Margin="5,0,5,0" Text="Flip ⮟"/>
+ </StackPanel>
+ </ToggleButton>
+
+
<ToggleButton
IsChecked="{Binding ShowLayerImageDifference}"
ToolTip.Tip="Show layer differences where darker pixels were also present on previous layer and the white pixels the difference between previous and current layer."
@@ -1894,7 +1928,8 @@
Command="{Binding OnROIClick}"
ToolTip.Tip="Region of interest selection over layer.
&#x0a;(NS): Not selected
-&#x0a;Click: go to region | ESC: Clear ROI"
+&#x0a;Click: go to region
+&#x0a;ESC: Clear ROI &amp; Masks | ESC + Shift: Clear ROI"
>
<StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
<Image Source="/Assets/Icons/object-group-16x16.png"/>
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index ba6d224..bc680e6 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -582,7 +582,7 @@ namespace UVtools.WPF
{
LayerImageBox.AutoPan = false;
LayerImageBox.Cursor = StaticControls.CrossCursor;
- TooltipOverlayText = "Pixel editing is on:\n" +
+ TooltipOverlayText = "Pixel editing is on (SHIFT):\n" +
"» Click over a pixel to draw\n" +
"» Hold CTRL to clear pixels";
UpdatePixelEditorCursor();
@@ -591,11 +591,12 @@ namespace UVtools.WPF
{
LayerImageBox.Cursor = StaticControls.CrossCursor;
LayerImageBox.SelectionMode = AdvancedImageBox.SelectionModes.Rectangle;
- TooltipOverlayText = "ROI selection mode:\n" +
- "» Left-click drag to select a fixed region\n" +
- "» Left-click + ALT drag to select specific objects\n" +
- "» Right click on a specific object to select it\n" +
- "Press Esc to clear the ROI";
+ TooltipOverlayText = "ROI & Mask selection mode (SHIFT):\n" +
+ "» Left-click drag to select a fixed ROI region\n" +
+ "» Left-click + ALT drag to select specific objects ROI\n" +
+ "» Right-click on a specific object to select it ROI\n" +
+ "» Right-click + ALT on a specific object to select it as a Mask\n" +
+ "Press Esc to clear the ROI & Masks";
}
IsTooltipOverlayVisible = Settings.LayerPreview.TooltipOverlay;
@@ -673,15 +674,16 @@ namespace UVtools.WPF
{
//await this.MessageBoxInfo(Path.Combine(App.ApplicationPath, "Assets", "Themes"));
if (!IsFileLoaded) return;
- var ext = Path.GetExtension(SlicerFile.FileFullPath);
- var extNoDot = ext.Remove(0, 1);
- var extension = FileExtension.Find(extNoDot);
+ var filename = FileFormat.GetFileNameStripExtensions(SlicerFile.FileFullPath, out var ext);
+ //var ext = Path.GetExtension(SlicerFile.FileFullPath);
+ //var extNoDot = ext.Remove(0, 1);
+ var extension = FileExtension.Find(ext);
if (extension is null)
{
await this.MessageBoxError("Unable to find the target extension.", "Invalid extension");
return;
}
- SaveFileDialog dialog = new SaveFileDialog
+ SaveFileDialog dialog = new()
{
DefaultExtension = extension.Extension,
Filters = new List<FileDialogFilter>
@@ -691,14 +693,14 @@ namespace UVtools.WPF
Name = extension.Description,
Extensions = new List<string>
{
- extNoDot
+ ext
}
}
},
Directory = string.IsNullOrEmpty(Settings.General.DefaultDirectorySaveFile)
? Path.GetDirectoryName(SlicerFile.FileFullPath)
: Settings.General.DefaultDirectorySaveFile,
- InitialFileName = $"{Settings.General.FileSaveNamePrefix}{Path.GetFileNameWithoutExtension(SlicerFile.FileFullPath)}{Settings.General.FileSaveNameSuffix}"
+ InitialFileName = $"{Settings.General.FileSaveNamePrefix}{filename}{Settings.General.FileSaveNameSuffix}"
};
var file = await dialog.ShowAsync(this);
if (string.IsNullOrEmpty(file)) return;
@@ -745,7 +747,7 @@ namespace UVtools.WPF
ClipboardManager.Instance.Reset();
SlicerFile?.Dispose();
- App.SlicerFile = null;
+ SlicerFile = null;
SlicerProperties.Clear();
Issues.Clear();
@@ -766,6 +768,8 @@ namespace UVtools.WPF
LayerImageBox.Image = null;
LayerPixelPicker.Reset();
+ ClearROIAndMask();
+
ResetDataContext();
}
@@ -979,8 +983,7 @@ namespace UVtools.WPF
if (convertToFormat is not null)
{
var directory = Path.GetDirectoryName(sl1File.FileFullPath);
- var extensions = FileFormat.AllFileExtensionsString.OrderByDescending(s => s.Length).ToList();
- var filename = PathExtensions.GetFileNameStripExtensions(sl1File.FileFullPath, extensions);
+ var filename = FileFormat.GetFileNameStripExtensions(sl1File.FileFullPath);
FileFormat convertedFile = null;
IsGUIEnabled = false;
@@ -1109,6 +1112,11 @@ namespace UVtools.WPF
_showLayerImageRotated = mat.Height > mat.Width;
}
+ if (SlicerFile.MirrorDisplay)
+ {
+ _showLayerImageFlipped = true;
+ }
+
ResetDataContext();
ForceUpdateActualLayer(actualLayer.Clamp(actualLayer, SliderMaximumValue));
diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs
index 26e70d7..57293c9 100644
--- a/UVtools.WPF/Program.cs
+++ b/UVtools.WPF/Program.cs
@@ -1,7 +1,7 @@
using System;
using System.Diagnostics;
+using System.Runtime.ExceptionServices;
using Avalonia;
-using UVtools.WPF.Extensions;
namespace UVtools.WPF
{
@@ -21,23 +21,32 @@ namespace UVtools.WPF
// Add the event handler for handling non-UI thread exceptions to the event.
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
-
+ //AppDomain.CurrentDomain.FirstChanceException += CurrentDomainOnFirstChanceException;
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
- private static async void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
+ private static void CurrentDomainOnFirstChanceException(object? sender, FirstChanceExceptionEventArgs e)
+ {
+ ErrorLog.AppendLine("First chance exception", e.Exception.ToString());
+ }
+
+
+ private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
- try
+ Exception ex = (Exception)e.ExceptionObject;
+ ErrorLog.AppendLine("Fatal Non-UI Error", ex.ToString());
+
+ /*try
{
- Exception ex = (Exception)e.ExceptionObject;
string errorMsg = "An application error occurred. Please contact the administrator with the following information:\n\n" +
$"{ex}";
await App.MainWindow.MessageBoxError(errorMsg, "Fatal Non-UI Error");
}
- catch (Exception)
+ catch (Exception exception)
{
- }
+ Debug.WriteLine(exception);
+ }*/
}
// Avalonia configuration, don't remove; also used by visual designer.
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index cb538f8..129509f 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -12,7 +12,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
- <Version>2.6.2</Version>
+ <Version>2.7.0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -31,7 +31,7 @@
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.0" />
<PackageReference Include="Avalonia.ThemeManager" Version="0.10.0" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.5.1.4349" />
- <PackageReference Include="MessageBox.Avalonia" Version="1.0.5" />
+ <PackageReference Include="MessageBox.Avalonia" Version="1.1.1" />
<PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.0" />
</ItemGroup>
<ItemGroup>
diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs
index fd06445..853c257 100644
--- a/UVtools.WPF/UserSettings.cs
+++ b/UVtools.WPF/UserSettings.cs
@@ -166,15 +166,18 @@ namespace UVtools.WPF
{
private Color _tooltipOverlayBackgroundColor = new Color(210, 255, 255, 192);
private bool _tooltipOverlay = true;
- private Color _volumeBoundsOutlineColor = new Color(210, 0, 255, 0);
+ private Color _volumeBoundsOutlineColor = new Color(255, 0, 255, 0);
private byte _volumeBoundsOutlineThickness = 3;
private bool _volumeBoundsOutline = true;
- private Color _layerBoundsOutlineColor = new Color(210, 0, 255, 0);
+ private Color _layerBoundsOutlineColor = new Color(255, 0, 255, 0);
private byte _layerBoundsOutlineThickness = 3;
private bool _layerBoundsOutline = false;
- private Color _hollowOutlineColor = new Color(210, 255, 165, 0);
- private byte _hollowOutlineLineThickness = 3;
+ private Color _hollowOutlineColor = new Color(255, 255, 165, 0);
+ private sbyte _hollowOutlineLineThickness = 5;
private bool _hollowOutline = false;
+ private Color _maskOutlineColor = new Color(255, 42, 157, 244);
+ private sbyte _maskOutlineLineThickness = 10;
+ private bool _maskClearRoiAfterSet = true;
private Color _previousLayerDifferenceColor = new Color(255, 255, 0, 255);
private Color _nextLayerDifferenceColor = new Color(255, 0, 255, 255);
private Color _bothLayerDifferenceColor = new Color(255, 255, 0, 0);
@@ -195,6 +198,7 @@ namespace UVtools.WPF
private uint _crosshairLength = 20;
private byte _crosshairMargin = 5;
private bool _autoRotateLayerBestView = true;
+ private bool _autoFlipLayerIfMirrored = true;
private bool _layerZoomToFitOnLoad = true;
private bool _showBackgroudGrid;
@@ -296,7 +300,7 @@ namespace UVtools.WPF
set => HollowOutlineColor = new Color(value);
}
- public byte HollowOutlineLineThickness
+ public sbyte HollowOutlineLineThickness
{
get => _hollowOutlineLineThickness;
set => RaiseAndSetIfChanged(ref _hollowOutlineLineThickness, value);
@@ -308,6 +312,35 @@ namespace UVtools.WPF
set => RaiseAndSetIfChanged(ref _hollowOutline, value);
}
+ public Color MaskOutlineColor
+ {
+ get => _maskOutlineColor;
+ set
+ {
+ RaiseAndSetIfChanged(ref _maskOutlineColor, value);
+ RaisePropertyChanged(nameof(MaskOutlineBrush));
+ }
+ }
+
+ [XmlIgnore]
+ public SolidColorBrush MaskOutlineBrush
+ {
+ get => new SolidColorBrush(_maskOutlineColor.ToAvalonia());
+ set => MaskOutlineColor = new Color(value);
+ }
+
+ public sbyte MaskOutlineLineThickness
+ {
+ get => _maskOutlineLineThickness;
+ set => RaiseAndSetIfChanged(ref _maskOutlineLineThickness, value);
+ }
+
+ public bool MaskClearROIAfterSet
+ {
+ get => _maskClearRoiAfterSet;
+ set => RaiseAndSetIfChanged(ref _maskClearRoiAfterSet, value);
+ }
+
public Color PreviousLayerDifferenceColor
{
get => _previousLayerDifferenceColor;
@@ -548,6 +581,12 @@ namespace UVtools.WPF
set => RaiseAndSetIfChanged(ref _autoRotateLayerBestView, value);
}
+ public bool AutoFlipLayerIfMirrored
+ {
+ get => _autoFlipLayerIfMirrored;
+ set => RaiseAndSetIfChanged(ref _autoFlipLayerIfMirrored, value);
+ }
+
public bool LayerZoomToFitOnLoad
{
get => _layerZoomToFitOnLoad;
diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml
index 86bfb5b..5a2cdc6 100644
--- a/UVtools.WPF/Windows/SettingsWindow.axaml
+++ b/UVtools.WPF/Windows/SettingsWindow.axaml
@@ -226,7 +226,7 @@
<StackPanel Orientation="Vertical">
<TextBlock Padding="10" Background="LightBlue" FontWeight="Bold" Text="Layer colors"/>
- <Grid Margin="10" RowDefinitions="Auto,10,Auto,10,Auto,10,Auto" ColumnDefinitions="Auto,Auto,Auto,Auto,*">
+ <Grid Margin="10" RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto" ColumnDefinitions="Auto,Auto,Auto,Auto,*">
<!--Tooltip overlay-->
<TextBlock Grid.Row="0" Grid.Column="0"
@@ -333,27 +333,55 @@
VerticalAlignment="Center"
Background="{Binding Settings.LayerPreview.HollowOutlineBrush}"
Command="{Binding SelectColor}"
- CommandParameter="HollowOutlineColor"
- />
+ CommandParameter="HollowOutlineColor"/>
<NumericUpDown Grid.Row="6" Grid.Column="2"
Margin="10,0,0,0"
VerticalAlignment="Center"
-
- Minimum="1"
- Maximum="50"
- Value="{Binding Settings.LayerPreview.HollowOutlineLineThickness}"
- />
+ Minimum="-1"
+ Maximum="127"
+ Value="{Binding Settings.LayerPreview.HollowOutlineLineThickness}"/>
<TextBlock Grid.Row="6" Grid.Column="3"
Margin="5,0,0,0"
VerticalAlignment="Center"
+ ToolTip.Tip="Set -1 to fill the area"
Text="Line thickness"/>
<CheckBox Grid.Row="6" Grid.Column="4"
Margin="10,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="Show by default"
- IsChecked="{Binding Settings.LayerPreview.HollowOutline}"
- />
+ IsChecked="{Binding Settings.LayerPreview.HollowOutline}"/>
+
+ <!--Masks-->
+ <TextBlock Grid.Row="8" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Mask area outline:"/>
+ <Button Grid.Row="8" Grid.Column="1"
+ Margin="10,0,0,0"
+ Padding="10"
+ BorderBrush="Black"
+ BorderThickness="2"
+ VerticalAlignment="Center"
+ Background="{Binding Settings.LayerPreview.MaskOutlineBrush}"
+ Command="{Binding SelectColor}"
+ CommandParameter="MaskOutlineColor"/>
+ <NumericUpDown Grid.Row="8" Grid.Column="2"
+ Margin="10,0,0,0"
+ VerticalAlignment="Center"
+ Minimum="-1"
+ Maximum="127"
+ Value="{Binding Settings.LayerPreview.MaskOutlineLineThickness}"/>
+ <TextBlock Grid.Row="8" Grid.Column="3"
+ Margin="5,0,0,0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Set -1 to fill the area"
+ Text="Line thickness"/>
+ <CheckBox Grid.Row="8" Grid.Column="4"
+ Margin="10,0,0,0"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ Content="Clear ROI when adding masks"
+ IsChecked="{Binding Settings.LayerPreview.MaskClearROIAfterSet}"/>
</Grid>
@@ -656,14 +684,19 @@
<StackPanel Orientation="Horizontal">
<CheckBox
Margin="10,10"
+ ToolTip.Tip="Auto rotate the layer preview on file load for a landscape viewport."
Content="Auto rotate on load"
- IsChecked="{Binding Settings.LayerPreview.AutoRotateLayerBestView}"
- />
+ IsChecked="{Binding Settings.LayerPreview.AutoRotateLayerBestView}"/>
+
+ <CheckBox
+ Margin="0,0,10,0"
+ ToolTip.Tip="Auto flip the layer preview on file load if the file is marked to print mirrored on the printer LCD."
+ Content="Auto flip on load"
+ IsChecked="{Binding Settings.LayerPreview.AutoFlipLayerIfMirrored}"/>
<CheckBox
Content="Zoom to fit on load"
- IsChecked="{Binding Settings.LayerPreview.LayerZoomToFitOnLoad}"
- />
+ IsChecked="{Binding Settings.LayerPreview.LayerZoomToFitOnLoad}"/>
</StackPanel>
diff --git a/UVtools.WPF/Windows/ToolWindow.axaml b/UVtools.WPF/Windows/ToolWindow.axaml
index 5bcd489..6019b5f 100644
--- a/UVtools.WPF/Windows/ToolWindow.axaml
+++ b/UVtools.WPF/Windows/ToolWindow.axaml
@@ -117,28 +117,36 @@
<MenuItem
Header="_All layers"
HotKey="Ctrl + Shift + A" InputGesture="Ctrl + Shift + A"
- Command="{Binding SelectAllLayers}"
- />
+ Command="{Binding SelectAllLayers}"/>
<MenuItem
- Header="_Current layer"
- HotKey="Ctrl + Shift + C" InputGesture="Ctrl + Shift + C"
- Command="{Binding SelectCurrentLayer}"
- />
+ Header="_Current layer"
+ HotKey="Ctrl + Shift + C" InputGesture="Ctrl + Shift + C"
+ Command="{Binding SelectCurrentLayer}"/>
+
+ <Separator />
+
+ <MenuItem
+ Header="From first to current layer"
+ HotKey="Ctrl + Alt + F" InputGesture="Ctrl + Alt + F"
+ Command="{Binding SelectFirstToCurrentLayer}"/>
+
+ <MenuItem
+ Header="From current to last layer"
+ HotKey="Ctrl + Alt + L" InputGesture="Ctrl + Alt + L"
+ Command="{Binding SelectCurrentToLastLayer}"/>
<Separator />
<MenuItem
Header="_Bottom layers"
HotKey="Ctrl + Shift + B" InputGesture="Ctrl + Shift + B"
- Command="{Binding SelectBottomLayers}"
- />
+ Command="{Binding SelectBottomLayers}"/>
<MenuItem
Header="_Normal layers"
HotKey="Ctrl + Shift + N" InputGesture="Ctrl + Shift + N"
- Command="{Binding SelectNormalLayers}"
- />
+ Command="{Binding SelectNormalLayers}"/>
<Separator />
@@ -161,12 +169,12 @@
<TextBlock
Grid.Row="2" Grid.Column="1"
HorizontalAlignment="Center"
- Text="{Binding LayerStartMM, StringFormat=(\{0:F2\}mm)}" />
+ Text="{Binding LayerStartMM, StringFormat=(\{0:F3\}mm)}" />
<TextBlock
Grid.Row="2" Grid.Column="3"
HorizontalAlignment="Center"
- Text="{Binding LayerEndMM, StringFormat=(\{0:F2\}mm)}" />
+ Text="{Binding LayerEndMM, StringFormat=(\{0:F3\}mm)}" />
<TextBlock
Grid.Row="2" Grid.Column="4"
@@ -180,32 +188,49 @@
</Border>
<!-- ROI -->
- <Border
- Grid.Row="2" Classes="GroupBox"
- Background="WhiteSmoke"
- Margin="5"
- IsVisible="{Binding IsROIVisible}">
+ <Border
+ Grid.Row="2" Classes="GroupBox"
+ Background="WhiteSmoke"
+ Margin="5"
+ IsVisible="{Binding IsROIOrMasksVisible}">
- <StackPanel Orientation="Vertical">
+ <StackPanel Orientation="Vertical">
<TextBlock
Classes="GroupBoxHeader"
- Text="ROI - Region of interest"/>
+ Text="Region of interest (ROI) and Masks"/>
- <TextBlock Margin="15"
- Text="{Binding ROI, StringFormat=Region: \{0\}}" />
+ <StackPanel Margin="15" Spacing="10">
+ <StackPanel Spacing="10"
+ Orientation="Horizontal"
+ IsVisible="{Binding IsROIVisible}">
+ <TextBlock VerticalAlignment="Center"
+ Text="{Binding ROI, StringFormat=Region: \{0\}}" />
- <StackPanel Spacing="20" Margin="15,0,15,15" Orientation="Horizontal">
- <CheckBox
- Content="Clear ROI after perform the operation"
- IsChecked="{Binding ClearROIAfterOperation}"
- />
- <Button
- Padding="5"
- Content="Clear ROI"
- Command="{Binding ClearROI}"/>
+ <Button
+ Padding="5"
+ Content="Clear ROI"
+ Command="{Binding ClearROI}"/>
</StackPanel>
-
+
+ <StackPanel Spacing="10"
+ Orientation="Horizontal"
+ IsVisible="{Binding IsMasksVisible}">
+ <TextBlock VerticalAlignment="Center"
+ Text="{Binding Masks.Length, StringFormat=Masks: \{0\}}" />
+
+ <Button
+ Padding="5"
+ Content="Clear Masks"
+ Command="{Binding ClearMasks}"/>
+ </StackPanel>
+
+ <CheckBox
+ Content="Clear ROI and Masks after perform the operation"
+ IsChecked="{Binding ClearROIAndMaskAfterOperation}"/>
+
</StackPanel>
+ </StackPanel>
+
</Border>
diff --git a/UVtools.WPF/Windows/ToolWindow.axaml.cs b/UVtools.WPF/Windows/ToolWindow.axaml.cs
index 14a1fb7..cb747e3 100644
--- a/UVtools.WPF/Windows/ToolWindow.axaml.cs
+++ b/UVtools.WPF/Windows/ToolWindow.axaml.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using Avalonia;
@@ -15,6 +16,7 @@ using UVtools.WPF.Controls;
using UVtools.WPF.Controls.Tools;
using UVtools.WPF.Extensions;
using UVtools.WPF.Structures;
+using Point = Avalonia.Point;
namespace UVtools.WPF.Windows
{
@@ -37,8 +39,9 @@ namespace UVtools.WPF.Windows
private uint _layerIndexStart;
private uint _layerIndexEnd;
private bool _isROIVisible;
+ private bool _isMasksVisible;
- private bool _clearRoiAfterOperation;
+ private bool _clearRoiAndMaskAfterOperation;
private bool _isProfilesVisible;
private ObservableCollection<Operation> _profiles = new();
@@ -113,7 +116,7 @@ namespace UVtools.WPF.Windows
ToolControl.BaseOperation.LayerIndexStart = value;
}
- value = value.Clamp(0, App.SlicerFile.LastLayerIndex);
+ value = value.Clamp(0, SlicerFile.LastLayerIndex);
if (!RaiseAndSetIfChanged(ref _layerIndexStart, value)) return;
RaisePropertyChanged(nameof(LayerStartMM));
RaisePropertyChanged(nameof(LayerRangeCountStr));
@@ -127,7 +130,7 @@ namespace UVtools.WPF.Windows
}
}
- public float LayerStartMM => App.SlicerFile[_layerIndexStart].PositionZ;
+ public float LayerStartMM => SlicerFile[_layerIndexStart].PositionZ;
public uint LayerIndexEnd
{
@@ -140,21 +143,21 @@ namespace UVtools.WPF.Windows
ToolControl.BaseOperation.LayerIndexEnd = value;
}
- value = value.Clamp(0, App.SlicerFile.LastLayerIndex);
+ value = value.Clamp(0, SlicerFile.LastLayerIndex);
if (!RaiseAndSetIfChanged(ref _layerIndexEnd, value)) return;
RaisePropertyChanged(nameof(LayerEndMM));
RaisePropertyChanged(nameof(LayerRangeCountStr));
}
}
- public float LayerEndMM => App.SlicerFile[_layerIndexEnd].PositionZ;
+ public float LayerEndMM => SlicerFile[_layerIndexEnd].PositionZ;
public string LayerRangeCountStr
{
get
{
uint layerCount = (uint) Math.Max(0, (int)LayerIndexEnd - LayerIndexStart + 1);
- return $"({layerCount} layers / {Math.Round(App.SlicerFile.LayerHeight * layerCount, 2)}mm)";
+ return $"({layerCount} layers / {Layer.ShowHeight(SlicerFile.LayerHeight * layerCount)}mm)";
}
}
@@ -176,17 +179,33 @@ namespace UVtools.WPF.Windows
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Current;
}
+ public void SelectFirstToCurrentLayer()
+ {
+ LayerIndexEnd = App.MainWindow.ActualLayer;
+ LayerIndexStart = 0;
+ if (!(ToolControl is null))
+ ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None;
+ }
+
+ public void SelectCurrentToLastLayer()
+ {
+ LayerIndexStart = App.MainWindow.ActualLayer;
+ LayerIndexEnd = SlicerFile.LastLayerIndex;
+ if (!(ToolControl is null))
+ ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None;
+ }
+
public void SelectBottomLayers()
{
LayerIndexStart = 0;
- LayerIndexEnd = App.SlicerFile.BottomLayerCount-1u;
+ LayerIndexEnd = SlicerFile.BottomLayerCount-1u;
if (!(ToolControl is null))
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Bottom;
}
public void SelectNormalLayers()
{
- LayerIndexStart = App.SlicerFile.BottomLayerCount;
+ LayerIndexStart = SlicerFile.BottomLayerCount;
LayerIndexEnd = MaximumLayerIndex;
if (!(ToolControl is null))
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Normal;
@@ -236,7 +255,9 @@ namespace UVtools.WPF.Windows
}
#endregion
- #region ROI
+ #region ROI & Masks
+
+ public bool IsROIOrMasksVisible => IsROIVisible || IsMasksVisible;
public bool IsROIVisible
{
@@ -245,15 +266,34 @@ namespace UVtools.WPF.Windows
if (ToolControl is null) return _isROIVisible;
return ToolControl.BaseOperation.CanROI && _isROIVisible;
}
- set => RaiseAndSetIfChanged(ref _isROIVisible, value);
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _isROIVisible, value)) return;
+ RaisePropertyChanged(nameof(IsROIOrMasksVisible));
+ }
+ }
+
+ public bool IsMasksVisible
+ {
+ get
+ {
+ if (ToolControl is null) return _isMasksVisible;
+ return ToolControl.BaseOperation.CanMask && _isMasksVisible;
+ }
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _isMasksVisible, value)) return;
+ RaisePropertyChanged(nameof(IsROIOrMasksVisible));
+ }
}
public Rectangle ROI => App.MainWindow.ROI;
+ public System.Drawing.Point[][] Masks => App.MainWindow.MaskPoints?.ToArray();
- public bool ClearROIAfterOperation
+ public bool ClearROIAndMaskAfterOperation
{
- get => _clearRoiAfterOperation;
- set => RaiseAndSetIfChanged(ref _clearRoiAfterOperation, value);
+ get => _clearRoiAndMaskAfterOperation;
+ set => RaiseAndSetIfChanged(ref _clearRoiAndMaskAfterOperation, value);
}
public async void ClearROI()
@@ -262,7 +302,17 @@ namespace UVtools.WPF.Windows
"This action can not be reverted, to select another ROI you must quit this window and select it on layer preview.",
"Clear the current ROI?") != ButtonResult.Yes) return;
IsROIVisible = false;
- App.MainWindow.LayerImageBox.SelectNone();
+ App.MainWindow.ClearROI();
+ ToolControl?.Callback(Callbacks.ClearROI);
+ }
+
+ public async void ClearMasks()
+ {
+ if (await this.MessageBoxQuestion("Are you sure you want to clear all masks?\n" +
+ "This action can not be reverted, to select another mask(s) you must quit this window and select it on layer preview.",
+ "Clear the all masks?") != ButtonResult.Yes) return;
+ IsMasksVisible = false;
+ App.MainWindow.ClearMask();
ToolControl?.Callback(Callbacks.ClearROI);
}
@@ -290,7 +340,7 @@ namespace UVtools.WPF.Windows
if (ToolControl is null) return;
var operation = _selectedProfileItem.Clone();
operation.ProfileName = null;
- operation.SlicerFile = App.SlicerFile;
+ operation.SlicerFile = SlicerFile;
ToolControl.BaseOperation = operation;
switch (operation.LayerRangeSelection)
{
@@ -491,6 +541,11 @@ namespace UVtools.WPF.Windows
{
IsROIVisible = true;
}
+
+ if (Masks?.Length > 0)
+ {
+ IsMasksVisible = true;
+ }
}
public ToolWindow(string description = null, bool layerRangeVisible = true) : this()
@@ -624,10 +679,11 @@ namespace UVtools.WPF.Windows
{
ToolControl.BaseOperation.LayerIndexStart = LayerIndexStart;
ToolControl.BaseOperation.LayerIndexEnd = LayerIndexEnd;
- if (IsROIVisible && ToolControl.BaseOperation.ROI.IsEmpty)
- {
- ToolControl.BaseOperation.ROI = App.MainWindow.ROI;
- }
+ /*if (IsROIVisible && ToolControl.BaseOperation.ROI.IsEmpty)
+ {
+ }*/
+ ToolControl.BaseOperation.SetROIIfEmpty(ROI);
+ ToolControl.BaseOperation.SetMasksIfEmpty(Masks);
if (!await ToolControl.ValidateForm()) return;
if (!string.IsNullOrEmpty(ToolControl.BaseOperation.ConfirmationText))
@@ -639,9 +695,9 @@ namespace UVtools.WPF.Windows
}
}
- if (ClearROIAfterOperation)
+ if (_clearRoiAndMaskAfterOperation)
{
- App.MainWindow.LayerImageBox.SelectNone();
+ App.MainWindow.ClearROIAndMask();
}
DialogResult = DialogResults.OK;