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>2022-02-13 05:15:34 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2022-02-13 05:15:34 +0300
commit554794d0d8408974e45d6dd730adb38acee7434a (patch)
tree93df72ae93faff1869ba39cd4cb6fcc807ed8edb
parentd9ebd966835acc61fb021846221add26de552799 (diff)
v2.28.0v2.28.0
- **Core:** - (Add) Utilities methods (`MinimumSpeed`, `MaximumSpeed`, `CreateMat`, `CreateMatWithDummyPixel`, `FileFormat.CopyParameters`) - (Improvement) Issues - Print Height: Group all layers outside the valid print height - **FileFormats:** - (Add) Generic / Phrozen ZIP format - (Add) Information/modifier to file formats to tell whatever is possible to use custom PositionZ per layer - (Add) Safe checks in order to run some tools, related to the previous "PositionZ" point - (Improvement) if blank, allow the previous layer to have a higher Z position than the successor layer - (Improvement) SL1: Implement the missing keys from new features of PrusaSlicer 2.4.0 - (Fix) Calling a partial save action without a progress instance would cause a crash - (Fix) GCode: Unable to parse the "Wait time after lift" when a second lift (TSMC) was present, leading to a sum on "Wait time before cure" - **Tools:** - (Add) Timelapse: Raise the build platform to a set position every odd-even height to be able to take a photo and create a time-lapse video of the print - (Add) Scripting: ScriptToggleSwitchInput - **Raise on print finish:** - (Add) Reapply check and prevent run the tool in that case - (Fix) It was incorrectly marked to be able to run in partial mode - (Fix) The dummy pixel was beeing set to a wrong location - **Layers:**: - (Change) When set Wait times to a negative value, it will set the global wait time value accordingly - (Change) Allow ExposureTime to be 0 - **Commandline arguments** - (Add) --run-operation \<input_file\> \<operation_file.uvtop\> - (Add) --run-script \<input_file\> \<script_file.cs\> - (Add) --copy-parameters \<from_file\> \<to_file\> - **UI:** - (Add) When open a file with missing crucial information, prompt the user to fill in that information (Optional) - (Add) Warn user about misfunction when open a file with invalid layer height of 0mm - (Improvement) Layer information: Only show the "Exposure time" when its availiable on the file format - (Improvement) When a file is about to get auto-converted once loaded and the output file already exists, prompt user for overwrite action - (Improvement) Set default culture info at top most of the program to avoid strange problems with commandline arguments - (Upgrade) .NET from 5.0.13 to 5.0.14 - (Downgrade) OpenCV from 4.5.5 to 4.5.4 due the crash while detecting islands (Linux and MacOS) (#411, #415, #416)
-rw-r--r--CHANGELOG.md37
-rw-r--r--CREDITS.md4
-rw-r--r--README.md24
-rw-r--r--UVtools.Core/Extensions/RectangleExtensions.cs3
-rw-r--r--UVtools.Core/FileFormats/CTBEncryptedFile.cs1
-rw-r--r--UVtools.Core/FileFormats/CWSFile.cs1
-rw-r--r--UVtools.Core/FileFormats/ChituboxFile.cs2
-rw-r--r--UVtools.Core/FileFormats/ChituboxZipFile.cs108
-rw-r--r--UVtools.Core/FileFormats/FileFormat.cs211
-rw-r--r--UVtools.Core/FileFormats/GenericZIPFile.cs327
-rw-r--r--UVtools.Core/FileFormats/OSLAFile.cs1
-rw-r--r--UVtools.Core/FileFormats/PhotonWorkshopFile.cs1
-rw-r--r--UVtools.Core/FileFormats/SL1File.cs20
-rw-r--r--UVtools.Core/FileFormats/UVJFile.cs1
-rw-r--r--UVtools.Core/FileFormats/VDTFile.cs2
-rw-r--r--UVtools.Core/FileFormats/ZCodeFile.cs1
-rw-r--r--UVtools.Core/FileFormats/ZCodexFile.cs1
-rw-r--r--UVtools.Core/GCode/GCodeLayer.cs36
-rw-r--r--UVtools.Core/Layers/Layer.cs70
-rw-r--r--UVtools.Core/Layers/LayerManager.cs3
-rw-r--r--UVtools.Core/Managers/IssueManager.cs9
-rw-r--r--UVtools.Core/Operations/Operation.cs28
-rw-r--r--UVtools.Core/Operations/OperationDoubleExposure.cs2
-rw-r--r--UVtools.Core/Operations/OperationDynamicLayerHeight.cs7
-rw-r--r--UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs39
-rw-r--r--UVtools.Core/Operations/OperationTimelapse.cs418
-rw-r--r--UVtools.Core/Scripting/ScriptToggleSwitchInput.cs23
-rw-r--r--UVtools.Core/UVtools.Core.csproj2
-rw-r--r--UVtools.InstallerMM/UVtools.InstallerMM.wxs6
-rw-r--r--UVtools.Platforms/osx-x64/libcvextern.dylibbin48634844 -> 48183260 bytes
-rw-r--r--UVtools.ScriptSample/ScriptCloneSettings.cs2
-rw-r--r--UVtools.ScriptSample/ScriptDebandingZSample.cs2
-rw-r--r--UVtools.ScriptSample/ScriptTimelapseSample.cs7
-rw-r--r--UVtools.WPF/App.axaml.cs4
-rw-r--r--UVtools.WPF/Assets/Icons/camera-16x16.pngbin0 -> 145 bytes
-rw-r--r--UVtools.WPF/Assets/Styles/Styles.xaml5
-rw-r--r--UVtools.WPF/Assets/Styles/StylesLight.xaml4
-rw-r--r--UVtools.WPF/ConsoleArguments.cs149
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml8
-rw-r--r--UVtools.WPF/Controls/Tools/ToolControl.axaml.cs4
-rw-r--r--UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs3
-rw-r--r--UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs28
-rw-r--r--UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml281
-rw-r--r--UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml.cs22
-rw-r--r--UVtools.WPF/MainWindow.Information.cs7
-rw-r--r--UVtools.WPF/MainWindow.Suggestions.cs2
-rw-r--r--UVtools.WPF/MainWindow.axaml2
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs188
-rw-r--r--UVtools.WPF/Program.cs8
-rw-r--r--UVtools.WPF/Structures/OperationProfiles.cs1
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj4
-rw-r--r--UVtools.WPF/Windows/MissingInformationWindow.axaml107
-rw-r--r--UVtools.WPF/Windows/MissingInformationWindow.axaml.cs93
-rw-r--r--UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml3
-rw-r--r--build/WingetPublish.ps14
55 files changed, 2119 insertions, 207 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e8f5c9..f0e0b26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,42 @@
# Changelog
+## 13/02/2022 - v2.28.0
+
+- **Core:**
+ - (Add) Utilities methods (`MinimumSpeed`, `MaximumSpeed`, `CreateMat`, `CreateMatWithDummyPixel`, `FileFormat.CopyParameters`)
+ - (Improvement) Issues - Print Height: Group all layers outside the valid print height
+ - (Fix) `Rectangle.Center` return a wrong center
+- **FileFormats:**
+ - (Add) Generic / Phrozen ZIP format
+ - (Add) Information/modifier to file formats to tell whatever is possible to use custom PositionZ per layer
+ - (Add) Safe checks in order to run some tools, related to the previous "PositionZ" point
+ - (Improvement) if blank, allow the previous layer to have a higher Z position than the successor layer
+ - (Improvement) SL1: Implement the missing keys from new features of PrusaSlicer 2.4.0
+ - (Fix) Calling a partial save action without a progress instance would cause a crash
+ - (Fix) GCode: Unable to parse the "Wait time after lift" when a second lift (TSMC) was present, leading to a sum on "Wait time before cure"
+- **Tools:**
+ - (Add) Timelapse: Raise the build platform to a set position every odd-even height to be able to take a photo and create a time-lapse video of the print
+ - (Add) Scripting: ScriptToggleSwitchInput
+ - **Raise on print finish:**
+ - (Add) Reapply check and prevent run the tool in that case
+ - (Fix) It was incorrectly marked to be able to run in partial mode
+ - (Fix) The dummy pixel was beeing set to a wrong location
+- **Layers:**:
+ - (Change) When set Wait times to a negative value, it will set the global wait time value accordingly
+ - (Change) Allow ExposureTime to be 0
+- **Commandline arguments**
+ - (Add) --run-operation \<input_file\> \<operation_file.uvtop\>
+ - (Add) --run-script \<input_file\> \<script_file.cs\>
+ - (Add) --copy-parameters \<from_file\> \<to_file\>
+- **UI:**
+ - (Add) When open a file with missing crucial information, prompt the user to fill in that information (Optional)
+ - (Add) Warn user about misfunction when open a file with invalid layer height of 0mm
+ - (Improvement) Layer information: Only show the "Exposure time" when its availiable on the file format
+ - (Improvement) When a file is about to get auto-converted once loaded and the output file already exists, prompt user for overwrite action
+- (Improvement) Set default culture info at top most of the program to avoid strange problems with commandline arguments
+- (Upgrade) .NET from 5.0.13 to 5.0.14
+- (Downgrade) OpenCV from 4.5.5 to 4.5.4 due the crash while detecting islands (Linux and MacOS) (#411, #415, #416)
+
## 27/01/2022 - v2.27.7
- **Pixel Arithmetic:**
diff --git a/CREDITS.md b/CREDITS.md
index be8c698..bcdabe4 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -66,4 +66,6 @@
- Tim Savage
- Peopoly Inc
- Riccardo Kocmann
-- Joshua Pitts \ No newline at end of file
+- Joshua Pitts
+- Tim Anderson
+- Sakari Toivonen \ No newline at end of file
diff --git a/README.md b/README.md
index 2305a9c..38f18d6 100644
--- a/README.md
+++ b/README.md
@@ -99,6 +99,7 @@ But also, i need victims for test subject. Proceed at your own risk!
- LGS120 (Longer Orange 120)
- LGS4K (Longer Orange 4K & mono)
- Flashforge SVGX
+- ZIP (Generic / Phrozen Zip)
- VDA.ZIP (Voxeldance Additive)
- VDT (Voxeldance Tango)
- UVJ (Zip file format for manual manipulation)
@@ -170,6 +171,18 @@ The UVtools executable allow to set some arguments to do special functions:
- **Example 1:** UVtools --export-mesh model.zip
- **Example 2:** UVtools --export-mesh model.zip model_exported.stl
- **Note:** Nothing happen when providing wrong files, will quit.
+- **Run a operation and save the file**
+ - **Syntax:** UVtools --run-operation \<input_file\> \<operation_file.uvtop\>
+ - **Example 1:** UVtools --run-operation model.zip MyMorph.uvtop
+ - **Note:** Nothing happen when providing wrong files, will quit.
+- **Run a script and save the file**
+ - **Syntax:** UVtools --run-script \<input_file\> \<script_file.cs\>
+ - **Example 1:** UVtools --run-script model.zip myScript.cs
+ - **Note:** Nothing happen when providing wrong files, will quit.
+- **Copy print parameters from one file to another**
+ - **Syntax:** UVtools --copy-parameters \<from_file\> \<to_file\>
+ - **Example 1:** UVtools --copy-parameters model.zip otherfile.zip
+ - **Note:** Nothing happen when providing wrong files, will quit.
# Requirements
@@ -209,6 +222,12 @@ sudo apt-get update
sudo apt-get install -y libjpeg-dev libpng-dev libgeotiff-dev libdc1394-22 libavcodec-dev libavformat-dev libswscale-dev libopenexr24 libtbb-dev libgl1-mesa-dev libgdiplus
```
+**Ubuntu 21.xx extra requirement:**
+
+```bash
+sudo ln -s /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/libdl.so
+```
+
### Compile libcvextern.so:
@@ -306,6 +325,11 @@ sudo yum install -y https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-non
sudo yum install -y libjpeg-devel libjpeg-turbo-devel libpng-devel libgeotiff-devel libdc1394-devel ffmpeg-devel tbb-devel mesa-libGL
```
+**Fedora 35 extra requirement:**
+
+```bash
+sudo ln -s /usr/lib64/libdl.so.2 /usr/lib64/libdl.so
+```
### Compile libcvextern.so:
diff --git a/UVtools.Core/Extensions/RectangleExtensions.cs b/UVtools.Core/Extensions/RectangleExtensions.cs
index b122051..dbdd653 100644
--- a/UVtools.Core/Extensions/RectangleExtensions.cs
+++ b/UVtools.Core/Extensions/RectangleExtensions.cs
@@ -5,7 +5,6 @@
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
-using System;
using System.Drawing;
namespace UVtools.Core.Extensions
@@ -14,7 +13,7 @@ namespace UVtools.Core.Extensions
{
public static Point Center(this Rectangle src)
{
- return new Point(src.Right / 2, src.Bottom / 2);
+ return new Point(src.Left + src.Width / 2, src.Top + src.Height / 2);
}
}
diff --git a/UVtools.Core/FileFormats/CTBEncryptedFile.cs b/UVtools.Core/FileFormats/CTBEncryptedFile.cs
index 3403072..4766f84 100644
--- a/UVtools.Core/FileFormats/CTBEncryptedFile.cs
+++ b/UVtools.Core/FileFormats/CTBEncryptedFile.cs
@@ -638,6 +638,7 @@ namespace UVtools.Core.FileFormats
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.LightOffDelay,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.ExposureTime,
diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs
index 9531cc9..ae85c0e 100644
--- a/UVtools.Core/FileFormats/CWSFile.cs
+++ b/UVtools.Core/FileFormats/CWSFile.cs
@@ -345,6 +345,7 @@ namespace UVtools.Core.FileFormats
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.ExposureTime,
PrintParameterModifier.WaitTimeAfterCure,
diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs
index c993e98..0bc0765 100644
--- a/UVtools.Core/FileFormats/ChituboxFile.cs
+++ b/UVtools.Core/FileFormats/ChituboxFile.cs
@@ -1164,6 +1164,7 @@ namespace UVtools.Core.FileFormats
{
return new[]
{
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.LightOffDelay,
PrintParameterModifier.ExposureTime,
PrintParameterModifier.LiftHeight,
@@ -1176,6 +1177,7 @@ namespace UVtools.Core.FileFormats
{
return new[]
{
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.LightOffDelay,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.ExposureTime,
diff --git a/UVtools.Core/FileFormats/ChituboxZipFile.cs b/UVtools.Core/FileFormats/ChituboxZipFile.cs
index 7cd44d2..3155454 100644
--- a/UVtools.Core/FileFormats/ChituboxZipFile.cs
+++ b/UVtools.Core/FileFormats/ChituboxZipFile.cs
@@ -119,6 +119,7 @@ namespace UVtools.Core.FileFormats
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.ExposureTime,
PrintParameterModifier.WaitTimeAfterCure,
@@ -403,39 +404,35 @@ namespace UVtools.Core.FileFormats
protected override void EncodeInternally(OperationProgress progress)
{
- using (ZipArchive outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Create))
+ using var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Create);
+ if (Thumbnails.Length > 0 && Thumbnails[0] is not null)
{
- if (Thumbnails.Length > 0 && Thumbnails[0] is not null)
- {
- using (Stream stream = outputFile.CreateEntry("preview.png").Open())
- {
- stream.WriteBytes(Thumbnails[0].GetPngByes());
- stream.Close();
- }
- }
+ using var stream = outputFile.CreateEntry("preview.png").Open();
+ stream.WriteBytes(Thumbnails[0].GetPngByes());
+ stream.Close();
+ }
- if (Thumbnails.Length > 1 && Thumbnails[1] is not null)
- {
- using Stream stream = outputFile.CreateEntry("preview_cropping.png").Open();
- using var vec = new VectorOfByte();
- stream.WriteBytes(Thumbnails[1].GetPngByes());
- stream.Close();
- }
+ if (Thumbnails.Length > 1 && Thumbnails[1] is not null)
+ {
+ using var stream = outputFile.CreateEntry("preview_cropping.png").Open();
+ using var vec = new VectorOfByte();
+ stream.WriteBytes(Thumbnails[1].GetPngByes());
+ stream.Close();
+ }
- if (!IsPHZZip)
- {
- RebuildGCode();
- outputFile.PutFileContent(GCodeFilename, GCodeStr, ZipArchiveMode.Create);
- }
+ if (!IsPHZZip)
+ {
+ RebuildGCode();
+ outputFile.PutFileContent(GCodeFilename, GCodeStr, ZipArchiveMode.Create);
+ }
- for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
- progress.Token.ThrowIfCancellationRequested();
- Layer layer = this[layerIndex];
- outputFile.PutFileContent($"{layerIndex + 1}.png", layer.CompressedBytes,
- ZipArchiveMode.Create);
- progress++;
- }
+ for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+ var layer = this[layerIndex];
+ outputFile.PutFileContent($"{layerIndex + 1}.png", layer.CompressedBytes,
+ ZipArchiveMode.Create);
+ progress++;
}
}
@@ -448,33 +445,31 @@ namespace UVtools.Core.FileFormats
{
//Clear();
//throw new FileLoadException("run.gcode not found", fileFullPath);
- using (TextReader tr = new StreamReader(entry.Open()))
+ using TextReader tr = new StreamReader(entry.Open());
+ string line;
+ GCode.Clear();
+ while ((line = tr.ReadLine()) != null)
{
- string line;
- GCode.Clear();
- while ((line = tr.ReadLine()) != null)
+ GCode.AppendLine(line);
+ if (string.IsNullOrEmpty(line)) continue;
+
+ if (line[0] != ';')
{
- GCode.AppendLine(line);
- if (string.IsNullOrEmpty(line)) continue;
-
- if (line[0] != ';')
- {
- continue;
- }
-
- var splitLine = line.Split(':');
- if (splitLine.Length < 2) continue;
-
- foreach (var propertyInfo in HeaderSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
- {
- var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
- if (displayNameAttribute is null) continue;
- if (!splitLine[0].Trim(' ', ';').Equals(displayNameAttribute.DisplayName)) continue;
- Helpers.SetPropertyValue(propertyInfo, HeaderSettings, splitLine[1].Trim());
- }
+ continue;
+ }
+
+ var splitLine = line.Split(':');
+ if (splitLine.Length < 2) continue;
+
+ foreach (var propertyInfo in HeaderSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType<DisplayNameAttribute>().FirstOrDefault();
+ if (displayNameAttribute is null) continue;
+ if (!splitLine[0].Trim(' ', ';').Equals(displayNameAttribute.DisplayName)) continue;
+ Helpers.SetPropertyValue(propertyInfo, HeaderSettings, splitLine[1].Trim());
}
- tr.Close();
}
+ tr.Close();
}
else
{
@@ -525,15 +520,6 @@ namespace UVtools.Core.FileFormats
GCode.ParseLayersFromGCode(this);
}
- if (HeaderSettings.LayerCount > 0 && ResolutionX == 0)
- {
- using (var mat = this[0].LayerMat)
- {
- HeaderSettings.ResolutionX = (uint)mat.Width;
- HeaderSettings.ResolutionY = (uint)mat.Height;
- }
- }
-
entry = inputFile.GetEntry("preview.png");
if (entry is not null)
{
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index e04dda0..007dbff 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -147,6 +147,7 @@ namespace UVtools.Core.FileFormats
{
#region Instances
+ public static PrintParameterModifier PositionZ { get; } = new ("Position Z", "Absolute Z position", "mm",0, 100000, Layer.HeightPrecision);
public static PrintParameterModifier BottomLayerCount { get; } = new ("Bottom layers count", "Number of bottom/burn-in layers", "layers",0, ushort.MaxValue, 0);
public static PrintParameterModifier BottomLightOffDelay { get; } = new("Bottom light-off seconds", "Total motor movement time + rest time to wait before cure a new bottom layer", "s");
@@ -187,7 +188,7 @@ namespace UVtools.Core.FileFormats
public static PrintParameterModifier BottomLightPWM { get; } = new ("Bottom light PWM", "UV LED power for bottom layers", "☀", 1, byte.MaxValue, 0);
public static PrintParameterModifier LightPWM { get; } = new ("Light PWM", "UV LED power for layers", "☀", 1, byte.MaxValue, 0);
- public static PrintParameterModifier[] Parameters = {
+ /*public static PrintParameterModifier[] Parameters = {
BottomLayerCount,
BottomWaitTimeBeforeCure,
@@ -209,7 +210,7 @@ namespace UVtools.Core.FileFormats
BottomLightPWM,
LightPWM
- };
+ };*/
#endregion
#region Properties
@@ -279,6 +280,25 @@ namespace UVtools.Core.FileFormats
#endregion
#region Overrides
+
+ protected bool Equals(PrintParameterModifier other)
+ {
+ return Name == other.Name;
+ }
+
+ 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((PrintParameterModifier) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ return (Name != null ? Name.GetHashCode() : 0);
+ }
+
public override string ToString()
{
return $"{nameof(Name)}: {Name}, {nameof(Description)}: {Description}, {nameof(ValueUnit)}: {ValueUnit}, {nameof(Minimum)}: {Minimum}, {nameof(Maximum)}: {Maximum}, {nameof(DecimalPlates)}: {DecimalPlates}, {nameof(OldValue)}: {OldValue}, {nameof(NewValue)}: {NewValue}, {nameof(HasChanged)}: {HasChanged}";
@@ -311,6 +331,7 @@ namespace UVtools.Core.FileFormats
new CXDLPFile(), // Creality Box
new LGSFile(), // LGS, LGS30
new FlashForgeSVGXFile(), // SVGX
+ new GenericZIPFile(), // Generic zip files
new VDAFile(), // VDA
new VDTFile(), // VDT
new UVJFile(), // UVJ
@@ -468,6 +489,38 @@ namespace UVtools.Core.FileFormats
public static FileFormat Open(string fileFullPath, OperationProgress progress = null) =>
Open(fileFullPath, FileDecodeType.Full, progress);
+ /// <summary>
+ /// Copy parameters from one file to another
+ /// </summary>
+ /// <param name="from">From source file</param>
+ /// <param name="to">To target file</param>
+ /// <returns>Number of affected parameters</returns>
+ public static uint CopyParameters(FileFormat from, FileFormat to)
+ {
+ if (ReferenceEquals(from, to)) return 0;
+ if (from.PrintParameterModifiers is null || to.PrintParameterModifiers is null) return 0;
+
+ uint count = 0;
+
+ to.RefreshPrintParametersModifiersValues();
+ var targetPrintModifier = to.PrintParameterModifiers.ToArray();
+ from.RefreshPrintParametersModifiersValues();
+ foreach (var sourceModifier in from.PrintParameterModifiers)
+ {
+ if (!targetPrintModifier.Contains(sourceModifier)) continue;
+
+ var fromValueStr = from.GetValueFromPrintParameterModifier(sourceModifier).ToString();
+ var toValueStr = to.GetValueFromPrintParameterModifier(sourceModifier).ToString();
+
+ if (decimal.TryParse(fromValueStr, out var fromValue) && decimal.TryParse(toValueStr, out var toValue) && fromValue != toValue)
+ {
+ to.SetValueFromPrintParameterModifier(sourceModifier, fromValue);
+ count++;
+ }
+ }
+
+ return count;
+ }
public static byte[] EncodeImage(string dataType, Mat mat)
{
@@ -879,7 +932,7 @@ namespace UVtools.Core.FileFormats
/// </summary>
public string FileFullPath { get; set; }
- public string FileDirectoryPath => Path.GetDirectoryName(FileFullPath);
+ public string DirectoryPath => Path.GetDirectoryName(FileFullPath);
public string Filename => Path.GetFileName(FileFullPath);
public string FileExtension => Path.GetExtension(FileFullPath);
public string FilenameNoExt => GetFileNameStripExtensions(FileFullPath);
@@ -1267,6 +1320,11 @@ namespace UVtools.Core.FileFormats
}
/// <summary>
+ /// First layer index, this is always 0
+ /// </summary>
+ public const uint FirstLayerIndex = 0;
+
+ /// <summary>
/// Gets the last layer index
/// </summary>
public uint LastLayerIndex => LayerManager.LastLayerIndex;
@@ -1713,6 +1771,95 @@ namespace UVtools.Core.FileFormats
set => RaiseAndSet(ref _lightPwm, value);
}
+ /// <summary>
+ /// Gets the minimum used speed for bottom layers in mm/min
+ /// </summary>
+ public float MinimumBottomSpeed
+ {
+ get
+ {
+ float speed = float.MaxValue;
+ if (BottomLiftSpeed > 0) speed = Math.Min(speed, BottomLiftSpeed);
+ if (BottomLiftSpeed2 > 0) speed = Math.Min(speed, BottomLiftSpeed2);
+ if (BottomRetractSpeed > 0) speed = Math.Min(speed, BottomRetractSpeed);
+ if (BottomRetractSpeed2 > 0) speed = Math.Min(speed, BottomRetractSpeed2);
+ if (Math.Abs(speed - float.MaxValue) < 0.01) return 0;
+
+ return speed;
+ }
+ }
+
+ /// <summary>
+ /// Gets the minimum used speed for normal bottom layers in mm/min
+ /// </summary>
+ public float MinimumNormalSpeed
+ {
+ get
+ {
+ float speed = float.MaxValue;
+ if (LiftSpeed > 0) speed = Math.Min(speed, LiftSpeed);
+ if (LiftSpeed2 > 0) speed = Math.Min(speed, LiftSpeed2);
+ if (RetractSpeed > 0) speed = Math.Min(speed, RetractSpeed);
+ if (RetractSpeed2 > 0) speed = Math.Min(speed, RetractSpeed2);
+ if (Math.Abs(speed - float.MaxValue) < 0.01) return 0;
+
+ return speed;
+ }
+ }
+
+ /// <summary>
+ /// Gets the minimum used speed in mm/min
+ /// </summary>
+ public float MinimumSpeed
+ {
+ get
+ {
+ var bottomSpeed = MinimumBottomSpeed;
+ var normalSpeed = MinimumNormalSpeed;
+ if (bottomSpeed <= 0) return normalSpeed;
+ if (normalSpeed <= 0) return bottomSpeed;
+
+ return Math.Min(bottomSpeed, normalSpeed);
+ }
+ }
+
+ /// <summary>
+ /// Gets the maximum used speed for bottom layers in mm/min
+ /// </summary>
+ public float MaximumBottomSpeed
+ {
+ get
+ {
+ float speed = BottomLiftSpeed;
+ speed = Math.Max(speed, BottomLiftSpeed2);
+ speed = Math.Max(speed, BottomRetractSpeed);
+ speed = Math.Max(speed, BottomRetractSpeed2);
+
+ return speed;
+ }
+ }
+
+ /// <summary>
+ /// Gets the maximum used speed for normal bottom layers in mm/min
+ /// </summary>
+ public float MaximumNormalSpeed
+ {
+ get
+ {
+ float speed = LiftSpeed;
+ speed = Math.Max(speed, LiftSpeed2);
+ speed = Math.Max(speed, RetractSpeed);
+ speed = Math.Max(speed, RetractSpeed2);
+
+ return speed;
+ }
+ }
+
+ /// <summary>
+ /// Gets the maximum used speed in mm/min
+ /// </summary>
+ public float MaximumSpeed => Math.Max(MaximumBottomSpeed, MaximumNormalSpeed);
+
public bool CanUseBottomLayerCount => HavePrintParameterModifier(PrintParameterModifier.BottomLayerCount);
public bool CanUseBottomLightOffDelay => HavePrintParameterModifier(PrintParameterModifier.BottomLightOffDelay);
@@ -1769,6 +1916,7 @@ namespace UVtools.Core.FileFormats
public bool CanUseLightPWM => HavePrintParameterModifier(PrintParameterModifier.LightPWM);
public bool CanUseAnyLightPWM => CanUseBottomLightPWM || CanUseLightPWM;
+ public bool CanUseLayerPositionZ => HaveLayerParameterModifier(PrintParameterModifier.PositionZ);
public bool CanUseLayerWaitTimeBeforeCure => HaveLayerParameterModifier(PrintParameterModifier.WaitTimeBeforeCure);
public bool CanUseLayerExposureTime => HaveLayerParameterModifier(PrintParameterModifier.ExposureTime);
public bool CanUseLayerWaitTimeAfterCure => HaveLayerParameterModifier(PrintParameterModifier.WaitTimeAfterCure);
@@ -2686,7 +2834,6 @@ namespace UVtools.Core.FileFormats
}
bool reSaveFile = LayerManager.Sanitize();
-
if (reSaveFile)
{
Save(progress);
@@ -3038,6 +3185,11 @@ namespace UVtools.Core.FileFormats
if (PrintParameterPerLayerModifiers is null) return;
var layer = this[layerIndex];
+ if (PrintParameterPerLayerModifiers.Contains(PrintParameterModifier.PositionZ))
+ {
+ PrintParameterModifier.PositionZ.Value = (decimal)layer.PositionZ;
+ }
+
if (PrintParameterPerLayerModifiers.Contains(PrintParameterModifier.LightOffDelay))
{
PrintParameterModifier.LightOffDelay.Value = (decimal)layer.LightOffDelay;
@@ -3515,6 +3667,7 @@ namespace UVtools.Core.FileFormats
}
+ progress ??= new OperationProgress();
PartialSaveInternally(progress);
}
@@ -3902,6 +4055,56 @@ namespace UVtools.Core.FileFormats
public Point DisplayToPixelPosition(float x, float y) => new(DisplayToPixelPositionX(x), DisplayToPixelPositionY(y));
public Point DisplayToPixelPosition(PointF point) => new(DisplayToPixelPositionX(point.X), DisplayToPixelPositionY(point.Y));
+ /// <summary>
+ /// Creates a empty mat of file <see cref="Resolution"/> size and create a dummy pixel to prevent a empty layer detection
+ /// </summary>
+ /// <param name="dummyPixelLocation">Location to set the dummy pixel, use a negative value (-1,-1) to set to the bounding center</param>
+ /// <param name="dummyPixelBrightness">Dummy pixel brightness</param>
+ /// <returns></returns>
+ public Mat CreateMatWithDummyPixel(Point dummyPixelLocation, byte dummyPixelBrightness)
+ {
+ var newMat = EmguExtensions.InitMat(Resolution);
+ if (dummyPixelBrightness > 0)
+ {
+ if (dummyPixelLocation.IsAnyNegative()) dummyPixelLocation = BoundingRectangle.Center();
+ newMat.SetByte(newMat.GetPixelPos(dummyPixelLocation), dummyPixelBrightness);
+ }
+
+ return newMat;
+ }
+
+ /// <summary>
+ /// Creates a empty mat of file <see cref="Resolution"/> size and create a dummy pixel to prevent a empty layer detection
+ /// </summary>
+ /// <param name="dummyPixelLocation">Location to set the dummy pixel, use a negative value (-1,-1) to set to the bounding center</param>
+ /// <returns></returns>
+ public Mat CreateMatWithDummyPixel(Point dummyPixelLocation) => CreateMatWithDummyPixel(dummyPixelLocation, SupportsGCode ? (byte) 1 : (byte) 128);
+
+ /// <summary>
+ /// Creates a empty mat of file <see cref="Resolution"/> size
+ /// </summary>
+ /// <param name="dummyPixelBrightness">Dummy pixel brightness</param>
+ /// <returns></returns>
+ public Mat CreateMatWithDummyPixel(byte dummyPixelBrightness) => CreateMatWithDummyPixel(BoundingRectangle.Center(), dummyPixelBrightness);
+
+ /// <summary>
+ /// Creates a empty mat of file <see cref="Resolution"/> size
+ /// </summary>
+ /// <returns></returns>
+ public Mat CreateMatWithDummyPixel() => CreateMatWithDummyPixel(SupportsGCode ? (byte)1 : (byte)128);
+
+ /// <summary>
+ /// Creates a empty mat of file <see cref="Resolution"/> size
+ /// </summary>
+ /// <param name="initMat">True to black out the mat</param>
+ /// <returns></returns>
+ public Mat CreateMat(bool initMat = true)
+ {
+ return initMat
+ ? EmguExtensions.InitMat(Resolution)
+ : new Mat(Resolution, DepthType.Cv8U, 1);
+ }
+
#endregion
}
}
diff --git a/UVtools.Core/FileFormats/GenericZIPFile.cs b/UVtools.Core/FileFormats/GenericZIPFile.cs
new file mode 100644
index 0000000..34b353c
--- /dev/null
+++ b/UVtools.Core/FileFormats/GenericZIPFile.cs
@@ -0,0 +1,327 @@
+/*
+ * 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.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Xml.Serialization;
+using Emgu.CV;
+using Emgu.CV.CvEnum;
+using Emgu.CV.Util;
+using UVtools.Core.Extensions;
+using UVtools.Core.Layers;
+using UVtools.Core.Operations;
+
+namespace UVtools.Core.FileFormats
+{
+ #region Sub Classes
+ [Serializable]
+ [XmlRoot(ElementName = "Manifest")]
+ public class GenericZipManifest
+ {
+ public string CreatedBy { get; set; } = About.SoftwareWithVersion;
+
+ public string UpdatedBy { get; set; } = About.SoftwareWithVersion;
+
+ public string CreatedDate { get; set; } = DateTime.UtcNow.ToString("u");
+
+ public string LastModifiedDate { get; set; } = DateTime.UtcNow.ToString("u");
+
+ public float LayerHeight { get; set; }
+
+ public ushort ResolutionX { get; set; }
+
+ public ushort ResolutionY { get; set; }
+
+ public float DisplayWidth { get; set; }
+
+ public float DisplayHeight { get; set; }
+
+ public float MachineZ { get; set; }
+
+ public void Update()
+ {
+ UpdatedBy = About.SoftwareWithVersion;
+ LastModifiedDate = DateTime.UtcNow.ToString("u");
+ }
+ }
+ #endregion
+
+ public class GenericZIPFile : FileFormat
+ {
+ #region Constants
+ private const string ManifestFileName = "manifest.uvtools";
+ #endregion
+
+ #region Properties
+ public GenericZipManifest ManifestFile { get; set; } = new ();
+
+ public override FileFormatType FileType => FileFormatType.Archive;
+
+ public override FileExtension[] FileExtensions { get; } = {
+ new(typeof(GenericZIPFile), "zip", "Generic / Phrozen Zip")
+ };
+
+ public override uint ResolutionX
+ {
+ get => ManifestFile.ResolutionX;
+ set
+ {
+ ManifestFile.ResolutionX = (ushort) value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override uint ResolutionY
+ {
+ get => ManifestFile.ResolutionY;
+ set
+ {
+ ManifestFile.ResolutionY = (ushort)value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float DisplayWidth
+ {
+ get => ManifestFile.DisplayWidth;
+ set
+ {
+ ManifestFile.DisplayWidth = value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float DisplayHeight
+ {
+ get => ManifestFile.DisplayHeight;
+ set
+ {
+ ManifestFile.DisplayHeight = value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float MachineZ
+ {
+ get => ManifestFile.MachineZ > 0 ? ManifestFile.MachineZ : base.MachineZ;
+ set
+ {
+ ManifestFile.MachineZ = value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float LayerHeight
+ {
+ get => ManifestFile.LayerHeight;
+ set
+ {
+ ManifestFile.LayerHeight = Layer.RoundHeight(value);
+ RaisePropertyChanged();
+ }
+ }
+
+ /*public override uint LayerCount
+ {
+ get => base.LayerCount;
+ set => base.LayerCount = ManifestFile.Slices.LayerCount = base.LayerCount;
+ }*/
+
+ public override Size[] ThumbnailsOriginalSize { get; } =
+ {
+ new(854, 480),
+ new(472, 240)
+ };
+
+
+ public override object[] Configs => new object[] {
+ ManifestFile
+ };
+
+ #endregion
+
+ #region Constructor
+ public GenericZIPFile()
+ { }
+ #endregion
+
+ #region Methods
+
+ public override bool CanProcess(string fileFullPath)
+ {
+ if(!base.CanProcess(fileFullPath)) return false;
+
+ try
+ {
+ using var zip = ZipFile.Open(fileFullPath, ZipArchiveMode.Read);
+ foreach (var entry in zip.Entries)
+ {
+ if (entry.Name == ManifestFileName) return true;
+ if (entry.Name.EndsWith(".gcode")) return false;
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ return false;
+ }
+
+
+ return true;
+ }
+
+ protected override void EncodeInternally(OperationProgress progress)
+ {
+ using var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Create);
+ if (Thumbnails.Length > 0 && Thumbnails[0] is not null)
+ {
+ using var stream = outputFile.CreateEntry("preview.png").Open();
+ stream.WriteBytes(Thumbnails[0].GetPngByes());
+ stream.Close();
+ }
+
+ if (Thumbnails.Length > 1 && Thumbnails[1] is not null)
+ {
+ using var stream = outputFile.CreateEntry("preview_cropping.png").Open();
+ using var vec = new VectorOfByte();
+ stream.WriteBytes(Thumbnails[1].GetPngByes());
+ stream.Close();
+ }
+
+ for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ {
+ progress.Token.ThrowIfCancellationRequested();
+ var layer = this[layerIndex];
+ var filename = $"{layerIndex + 1}.png";
+ outputFile.PutFileContent(filename, layer.CompressedBytes, ZipArchiveMode.Create);
+ progress++;
+ }
+
+ ManifestFile.Update();
+
+ XmlSerializer serializer = new(ManifestFile.GetType());
+ XmlSerializerNamespaces ns = new();
+ ns.Add("", "");
+ var entry = outputFile.CreateEntry(ManifestFileName);
+ using var streamManifest = entry.Open();
+ serializer.Serialize(streamManifest, ManifestFile, ns);
+ }
+
+ protected override void DecodeInternally(OperationProgress progress)
+ {
+ using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read))
+ {
+ var entry = inputFile.Entries.FirstOrDefault(zipEntry => zipEntry.Name == ManifestFileName);
+ if (entry is not null)
+ {
+ try
+ {
+ var serializer = new XmlSerializer(ManifestFile.GetType());
+ using var stream = entry.Open();
+ ManifestFile = (GenericZipManifest) serializer.Deserialize(stream);
+ }
+ catch (Exception e)
+ {
+ // Not required
+ //Clear();
+ //throw new FileLoadException($"Unable to deserialize '{entry.Name}'\n{e}", FileFullPath);
+ }
+ }
+
+ uint layerCount = 0;
+ foreach (var zipEntry in inputFile.Entries)
+ {
+ if (!zipEntry.Name.EndsWith(".png")) continue;
+ var filename = Path.GetFileNameWithoutExtension(zipEntry.Name);
+ if (!filename.All(char.IsDigit)) continue;
+ if (!uint.TryParse(filename, out var layerIndex)) continue;
+ layerCount = Math.Max(layerCount, layerIndex);
+ }
+
+ if (layerCount == 0)
+ {
+ Clear();
+ throw new FileLoadException("Unable to detect layer images in the file", FileFullPath);
+ }
+
+ LayerManager.Init(layerCount, DecodeType == FileDecodeType.Partial);
+ progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
+
+
+ for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ {
+ if (progress.Token.IsCancellationRequested) break;
+ var filename = $"{layerIndex + 1}.png";
+ entry = inputFile.GetEntry(filename);
+ if (entry is null)
+ {
+ Clear();
+ throw new FileLoadException($"Layer {filename} not found", FileFullPath);
+ }
+
+ if (DecodeType == FileDecodeType.Full)
+ {
+ using var stream = entry.Open();
+ this[layerIndex] = new Layer(layerIndex, stream, LayerManager);
+ }
+
+ progress++;
+ }
+
+ entry = inputFile.GetEntry("preview.png");
+ if (entry is not null)
+ {
+ Thumbnails[0] = new Mat();
+ CvInvoke.Imdecode(entry.Open().ToArray(), ImreadModes.AnyColor, Thumbnails[0]);
+ }
+
+ entry = inputFile.GetEntry("preview_cropping.png");
+ if (entry is not null)
+ {
+ var count = CreatedThumbnailsCount;
+ Thumbnails[count] = new Mat();
+ CvInvoke.Imdecode(entry.Open().ToArray(), ImreadModes.AnyColor, Thumbnails[count]);
+ }
+ }
+
+ LayerManager.GetBoundingRectangle(progress);
+ }
+
+ protected override void PartialSaveInternally(OperationProgress progress)
+ {
+ using var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update);
+ bool deleted;
+
+ do
+ {
+ deleted = false;
+ foreach (var zipEntry in outputFile.Entries)
+ {
+ if (zipEntry.Name != ManifestFileName) continue;
+ zipEntry.Delete();
+ deleted = true;
+ break;
+ }
+ } while (deleted);
+
+ ManifestFile.Update();
+
+ XmlSerializer serializer = new(ManifestFile.GetType());
+ XmlSerializerNamespaces ns = new();
+ ns.Add("", "");
+ var entry = outputFile.CreateEntry(ManifestFileName);
+ using var stream = entry.Open();
+ serializer.Serialize(stream, ManifestFile, ns);
+ }
+ #endregion
+ }
+}
diff --git a/UVtools.Core/FileFormats/OSLAFile.cs b/UVtools.Core/FileFormats/OSLAFile.cs
index 8d487ab..ae44a71 100644
--- a/UVtools.Core/FileFormats/OSLAFile.cs
+++ b/UVtools.Core/FileFormats/OSLAFile.cs
@@ -293,6 +293,7 @@ namespace UVtools.Core.FileFormats
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.ExposureTime,
PrintParameterModifier.WaitTimeAfterCure,
diff --git a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
index 78137af..ddc6081 100644
--- a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
+++ b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
@@ -1100,6 +1100,7 @@ namespace UVtools.Core.FileFormats
}
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.ExposureTime,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
diff --git a/UVtools.Core/FileFormats/SL1File.cs b/UVtools.Core/FileFormats/SL1File.cs
index 3a4159f..34c45fa 100644
--- a/UVtools.Core/FileFormats/SL1File.cs
+++ b/UVtools.Core/FileFormats/SL1File.cs
@@ -104,6 +104,9 @@ namespace UVtools.Core.FileFormats
#region Corrections
public string RelativeCorrection { get; set; } = "1,1";
+ public float RelativeCorrectionX { get; set; } = 1;
+ public float RelativeCorrectionY { get; set; } = 1;
+ public float RelativeCorrectionZ { get; set; } = 1;
public float AbsoluteCorrection { get; set; }
public float ElefantFootCompensation { get; set; } = 0.2f;
public float ElefantFootMinWidth { get; set; } = 0.2f;
@@ -136,6 +139,7 @@ namespace UVtools.Core.FileFormats
public string MaterialVendor { get; set; }
public string MaterialType { get; set; }
public string SlaMaterialSettingsId { get; set; }
+ public string MaterialColour { get; set; } = "#29B2B2";
public float BottleCost { get; set; }
public float BottleVolume { get; set; }
public float BottleWeight { get; set; }
@@ -156,7 +160,16 @@ namespace UVtools.Core.FileFormats
#endregion
#region Corrections
- public string MaterialCorrection { get; set; }
+ public string MaterialCorrection { get; set; } = "1,1,1";
+ public float MaterialCorrectionX { get; set; } = 1;
+ public float MaterialCorrectionY { get; set; } = 1;
+ public float MaterialCorrectionZ { get; set; } = 1;
+
+ #endregion
+
+ #region Material print profile
+
+ public string MaterialPrintSpeed { get; set; } = "fast";
#endregion
@@ -246,6 +259,7 @@ namespace UVtools.Core.FileFormats
#region Advanced
public float SliceClosingRadius { set; get; }
+ public string SlicingMode { set; get; } = "regular";
#endregion
#region Output
@@ -274,6 +288,7 @@ namespace UVtools.Core.FileFormats
public string JobDir { get; set; }
public float ExpTime { get; set; }
public float ExpTimeFirst { get; set; }
+ public float ExpUserProfile { get; set; }
//public string FileCreationTimestamp { get; set; }
public string FileCreationTimestamp {
get
@@ -284,6 +299,7 @@ namespace UVtools.Core.FileFormats
}
set{}
}
+ public byte Hollow { get; set; }
public float LayerHeight { get; set; }
public string MaterialName { get; set; } = About.Software;
public ushort NumFade { get; set; }
@@ -663,7 +679,7 @@ namespace UVtools.Core.FileFormats
{
throw new FileLoadException($"Malformed file: {IniPrusaslicer} is missing.");
}
-
+
SuppressRebuildPropertiesWork(() =>
{
BottomLightOffDelay = LookupCustomValue(Keyword_BottomLightOffDelay, 0f);
diff --git a/UVtools.Core/FileFormats/UVJFile.cs b/UVtools.Core/FileFormats/UVJFile.cs
index 9fabb76..954816a 100644
--- a/UVtools.Core/FileFormats/UVJFile.cs
+++ b/UVtools.Core/FileFormats/UVJFile.cs
@@ -215,6 +215,7 @@ namespace UVtools.Core.FileFormats
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.LightOffDelay,
PrintParameterModifier.ExposureTime,
diff --git a/UVtools.Core/FileFormats/VDTFile.cs b/UVtools.Core/FileFormats/VDTFile.cs
index 98341e3..4e0186b 100644
--- a/UVtools.Core/FileFormats/VDTFile.cs
+++ b/UVtools.Core/FileFormats/VDTFile.cs
@@ -238,7 +238,7 @@ namespace UVtools.Core.FileFormats
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
-
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.LightOffDelay,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.ExposureTime,
diff --git a/UVtools.Core/FileFormats/ZCodeFile.cs b/UVtools.Core/FileFormats/ZCodeFile.cs
index 6841e32..e1d5ee7 100644
--- a/UVtools.Core/FileFormats/ZCodeFile.cs
+++ b/UVtools.Core/FileFormats/ZCodeFile.cs
@@ -218,6 +218,7 @@ namespace UVtools.Core.FileFormats
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.ExposureTime,
PrintParameterModifier.WaitTimeAfterCure,
diff --git a/UVtools.Core/FileFormats/ZCodexFile.cs b/UVtools.Core/FileFormats/ZCodexFile.cs
index e8d7eaf..2774dfb 100644
--- a/UVtools.Core/FileFormats/ZCodexFile.cs
+++ b/UVtools.Core/FileFormats/ZCodexFile.cs
@@ -177,6 +177,7 @@ namespace UVtools.Core.FileFormats
};
public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = {
+ PrintParameterModifier.PositionZ,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.RetractSpeed,
diff --git a/UVtools.Core/GCode/GCodeLayer.cs b/UVtools.Core/GCode/GCodeLayer.cs
index 55df78a..2ff853e 100644
--- a/UVtools.Core/GCode/GCodeLayer.cs
+++ b/UVtools.Core/GCode/GCodeLayer.cs
@@ -154,7 +154,7 @@ namespace UVtools.Core.GCode
public void AssignMovements(GCodeBuilder.GCodePositioningTypes positionType)
{
if (Movements.Count == 0) return;
- var currentZ = PreviousPositionZ;
+ float currentZ = PreviousPositionZ;
PositionZ = null;
LiftHeight = null;
@@ -165,18 +165,20 @@ namespace UVtools.Core.GCode
RetractHeight2 = null;
RetractSpeed2 = null;
+ var previousLayerEmpty = LayerIndex > 0 && SlicerFile[LayerIndex.Value - 1].NonZeroPixelCount <= 1;
+
for (int i = 0; i < Movements.Count; i++)
{
var (pos, speed) = Movements[i];
- float heightRaw;
+ float partialPositionZ;
switch (positionType)
{
case GCodeBuilder.GCodePositioningTypes.Absolute:
- heightRaw = Layer.RoundHeight(pos - currentZ);
+ partialPositionZ = Layer.RoundHeight(pos - currentZ);
currentZ = pos;
break;
case GCodeBuilder.GCodePositioningTypes.Partial:
- heightRaw = pos;
+ partialPositionZ = pos;
currentZ = Layer.RoundHeight(currentZ + pos);
break;
default:
@@ -184,26 +186,31 @@ namespace UVtools.Core.GCode
}
// Fail-safe check
- if (currentZ < PreviousPositionZ)
- throw new NotSupportedException("GCode parsing error: Attempting to crash the print on the LCD with negative position.\n" +
+
+ if (currentZ < PreviousPositionZ && !previousLayerEmpty)
+ {
+ throw new NotSupportedException(
+ $"GCode parsing error: Attempting to crash the print on the LCD due a lower position ({currentZ}mm) than the previous layer ({PreviousPositionZ}mm).\n" +
"Do not attempt to print this file!");
+ }
- // Is position Z
+ // Last movement on the list, must be position Z
if (i == Movements.Count - 1)
{
PositionZ = currentZ;
- if (LiftHeight.HasValue)
+ if (LiftHeight.HasValue && partialPositionZ < 0)
{
- RetractSpeed = speed; // A lift exists, set to retract speed of this move
+ RetractSpeed = speed; // A lift exists, and its descending, set to retract speed of this move
}
- continue;
+ break;
}
- if (heightRaw == 0) continue;
- var height = Math.Abs(heightRaw);
+ if (partialPositionZ == 0) continue;
+ var height = Math.Abs(partialPositionZ);
- if (heightRaw > 0) // Is a lift
+ if (partialPositionZ > 0) // Is a lift
{
+ // Lift 1
if (!LiftHeight.HasValue)
{
LiftHeight = height;
@@ -211,6 +218,7 @@ namespace UVtools.Core.GCode
continue;
}
+ // Lift 2
LiftHeight2 ??= 0;
LiftHeight2 += height;
LiftSpeed2 = speed;
@@ -240,7 +248,7 @@ namespace UVtools.Core.GCode
LiftHeight = liftHeight;
}
- if (RetractHeight2.HasValue) // Need to fix the propose of this value
+ if (RetractHeight2.HasValue) // Need to fix the purpose of this value
{
RetractHeight2 = Layer.RoundHeight(LiftHeightTotal - RetractHeight2.Value);
(RetractSpeed, RetractSpeed2) = (RetractSpeed2, RetractSpeed);
diff --git a/UVtools.Core/Layers/Layer.cs b/UVtools.Core/Layers/Layer.cs
index ab57fb5..60283f4 100644
--- a/UVtools.Core/Layers/Layer.cs
+++ b/UVtools.Core/Layers/Layer.cs
@@ -234,7 +234,8 @@ namespace UVtools.Core.Layers
get => _waitTimeBeforeCure;
set
{
- value = Math.Max(0, (float)Math.Round(value, 2));
+ value = (float)Math.Round(value, 2);
+ if (value < 0) value = SlicerFile.GetBottomOrNormalValue(this, SlicerFile.BottomWaitTimeBeforeCure, SlicerFile.WaitTimeBeforeCure);
if (!RaiseAndSetIfChanged(ref _waitTimeBeforeCure, value)) return;
SlicerFile?.UpdatePrintTimeQueued();
}
@@ -249,7 +250,7 @@ namespace UVtools.Core.Layers
set
{
value = (float)Math.Round(value, 2);
- if (value <= 0) value = SlicerFile.GetBottomOrNormalValue(this, SlicerFile.BottomExposureTime, SlicerFile.ExposureTime);
+ if (value < 0) value = SlicerFile.GetBottomOrNormalValue(this, SlicerFile.BottomExposureTime, SlicerFile.ExposureTime);
if(!RaiseAndSetIfChanged(ref _exposureTime, value)) return;
SlicerFile?.UpdatePrintTimeQueued();
}
@@ -265,7 +266,8 @@ namespace UVtools.Core.Layers
get => _waitTimeAfterCure;
set
{
- value = Math.Max(0, (float)Math.Round(value, 2));
+ value = (float)Math.Round(value, 2);
+ if (value < 0) value = SlicerFile.GetBottomOrNormalValue(this, SlicerFile.BottomWaitTimeAfterCure, SlicerFile.WaitTimeAfterCure);
if (!RaiseAndSetIfChanged(ref _waitTimeAfterCure, value)) return;
SlicerFile?.UpdatePrintTimeQueued();
}
@@ -369,7 +371,8 @@ namespace UVtools.Core.Layers
get => _waitTimeAfterLift;
set
{
- value = Math.Max(0, (float)Math.Round(value, 2));
+ value = (float)Math.Round(value, 2);
+ if (value < 0) value = SlicerFile.GetBottomOrNormalValue(this, SlicerFile.BottomWaitTimeAfterLift, SlicerFile.WaitTimeAfterLift);
if (!RaiseAndSetIfChanged(ref _waitTimeAfterLift, value)) return;
SlicerFile?.UpdatePrintTimeQueued();
}
@@ -446,6 +449,40 @@ namespace UVtools.Core.Layers
}
/// <summary>
+ /// Gets the minimum used speed in mm/min
+ /// </summary>
+ public float MinimumSpeed
+ {
+ get
+ {
+ float speed = float.MaxValue;
+ if (LiftSpeed > 0) speed = Math.Min(speed, LiftSpeed);
+ if (LiftSpeed2 > 0) speed = Math.Min(speed, LiftSpeed2);
+ if (RetractSpeed > 0) speed = Math.Min(speed, RetractSpeed);
+ if (RetractSpeed2 > 0) speed = Math.Min(speed, RetractSpeed2);
+ if (Math.Abs(speed - float.MaxValue) < 0.01) return 0;
+
+ return speed;
+ }
+ }
+
+ /// <summary>
+ /// Gets the maximum used speed in mm/min
+ /// </summary>
+ public float MaximumSpeed
+ {
+ get
+ {
+ float speed = LiftSpeed;
+ speed = Math.Max(speed, LiftSpeed2);
+ speed = Math.Max(speed, RetractSpeed);
+ speed = Math.Max(speed, RetractSpeed2);
+
+ return speed;
+ }
+ }
+
+ /// <summary>
/// Gets if this layer can be exposed to UV light
/// </summary>
public bool CanExpose => _exposureTime > 0 && _lightPWM > 0;
@@ -912,6 +949,12 @@ namespace UVtools.Core.Layers
public bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, decimal value)
{
+ if (ReferenceEquals(modifier, FileFormat.PrintParameterModifier.PositionZ))
+ {
+ PositionZ = (float)value;
+ return true;
+ }
+
if (ReferenceEquals(modifier, FileFormat.PrintParameterModifier.LightOffDelay))
{
LightOffDelay = (float)value;
@@ -1166,6 +1209,25 @@ namespace UVtools.Core.Layers
return result;
}*/
+ /// <summary>
+ /// Copy all lift parameters from this layer to an target layer
+ /// </summary>
+ /// <param name="layer"></param>
+ public void CopyLiftTo(Layer layer)
+ {
+ layer.LiftHeight = _liftHeight;
+ layer.LiftHeight2 = _liftHeight2;
+ layer.LiftSpeed = _liftSpeed;
+ layer.LiftSpeed2 = _liftSpeed2;
+ layer.RetractHeight2 = _retractHeight2;
+ layer.RetractSpeed = _retractSpeed;
+ layer.RetractSpeed2 = _retractSpeed2;
+ }
+
+ /// <summary>
+ /// Copy the image and related parameters from this layer to an target layer
+ /// </summary>
+ /// <param name="layer"></param>
public void CopyImageTo(Layer layer)
{
if (!HaveImage) return;
diff --git a/UVtools.Core/Layers/LayerManager.cs b/UVtools.Core/Layers/LayerManager.cs
index 207a8f7..cfa7611 100644
--- a/UVtools.Core/Layers/LayerManager.cs
+++ b/UVtools.Core/Layers/LayerManager.cs
@@ -463,7 +463,8 @@ namespace UVtools.Core
if (this[layerIndex] is null) throw new InvalidDataException($"Layer {layerIndex} was defined but doesn't contain a valid image.");
if (layerIndex <= 0) continue;
// Check for bigger position z than it successor
- if (this[layerIndex - 1].PositionZ > this[layerIndex].PositionZ) throw new InvalidDataException($"Layer {layerIndex - 1} ({this[layerIndex - 1].PositionZ}mm) have a higher Z position than the successor layer {layerIndex} ({this[layerIndex].PositionZ}mm).\n");
+ if (this[layerIndex - 1].PositionZ > this[layerIndex].PositionZ && this[layerIndex - 1].NonZeroPixelCount > 1)
+ throw new InvalidDataException($"Layer {layerIndex - 1} ({this[layerIndex - 1].PositionZ}mm) have a higher Z position than the successor layer {layerIndex} ({this[layerIndex].PositionZ}mm).\n");
}
if (SlicerFile.ResolutionX == 0 || SlicerFile.ResolutionY == 0)
diff --git a/UVtools.Core/Managers/IssueManager.cs b/UVtools.Core/Managers/IssueManager.cs
index 735ab77..b3614a3 100644
--- a/UVtools.Core/Managers/IssueManager.cs
+++ b/UVtools.Core/Managers/IssueManager.cs
@@ -168,13 +168,8 @@ namespace UVtools.Core.Managers
float printHeightWithOffset = Layer.RoundHeight(SlicerFile.MachineZ + printHeightConfig.Offset);
if (SlicerFile.PrintHeight > printHeightWithOffset)
{
- foreach (var layer in SlicerFile)
- {
- if (layer.PositionZ > printHeightWithOffset)
- {
- AddIssue(new MainIssue(MainIssue.IssueType.PrintHeight, new Issue(layer)));
- }
- }
+ var issues = (from layer in SlicerFile where layer.PositionZ > printHeightWithOffset select new Issue(layer)).ToList();
+ if(issues.Count > 0) AddIssue(new MainIssue(MainIssue.IssueType.PrintHeight, issues));
}
}
diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs
index cee3c13..ad35b05 100644
--- a/UVtools.Core/Operations/Operation.cs
+++ b/UVtools.Core/Operations/Operation.cs
@@ -10,6 +10,7 @@ using System;
using System.Drawing;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Emgu.CV;
@@ -320,7 +321,14 @@ namespace UVtools.Core.Operations
return string.IsNullOrWhiteSpace(message);
}
- public virtual string ValidateInternally() => null;
+ public virtual string ValidateInternally()
+ {
+ if (!ValidateSpawn(out var message))
+ {
+ return message;
+ }
+ return null;
+ }
/// <summary>
/// Validates the operation
@@ -604,6 +612,24 @@ namespace UVtools.Core.Operations
#region Static Methods
+ public static Operation Deserialize(string path)
+ {
+ if (!File.Exists(path)) return null;
+
+ var fileText = File.ReadAllText(path);
+ var match = Regex.Match(fileText, @"(?:<\/\s*Operation)([a-zA-Z0-9_]+)(?:\s*>)");
+ if (!match.Success) return null;
+ if (match.Groups.Count < 1) return null;
+ var operationName = match.Groups[1].Value;
+ var baseType = typeof(Operation).FullName;
+ if (string.IsNullOrWhiteSpace(baseType)) return null;
+ var classname = baseType + operationName + ", UVtools.Core";
+ var type = Type.GetType(classname);
+ if (type is null) return null;
+
+ return Deserialize(path, type);
+ }
+
public static Operation Deserialize(string path, Type type)
{
XmlSerializer serializer = new(type);
diff --git a/UVtools.Core/Operations/OperationDoubleExposure.cs b/UVtools.Core/Operations/OperationDoubleExposure.cs
index 8c11c6e..dae57bf 100644
--- a/UVtools.Core/Operations/OperationDoubleExposure.cs
+++ b/UVtools.Core/Operations/OperationDoubleExposure.cs
@@ -59,7 +59,7 @@ namespace UVtools.Core.Operations
public override string ValidateSpawn()
{
- if (!SlicerFile.CanUseLayerLiftHeight || !SlicerFile.CanUseLayerExposureTime)
+ if (!SlicerFile.CanUseLayerPositionZ || !SlicerFile.CanUseLayerLiftHeight || !SlicerFile.CanUseLayerExposureTime)
{
return NotSupportedMessage;
}
diff --git a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
index a9ae826..f761640 100644
--- a/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
+++ b/UVtools.Core/Operations/OperationDynamicLayerHeight.cs
@@ -142,10 +142,15 @@ namespace UVtools.Core.Operations
public override string ValidateSpawn()
{
+ if (!SlicerFile.CanUseLayerPositionZ || !SlicerFile.CanUseLayerExposureTime)
+ {
+ return NotSupportedMessage;
+ }
+
if (SlicerFile.LayerHeight * 2 > FileFormat.MaximumLayerHeight)
{
return $"This file already uses the maximum layer height possible ({SlicerFile.LayerHeight}mm).\n" +
- $"Layers can not be stacked, please re-slice your file with the lowest layer height of 0.01mm.";
+ "Layers can not be stacked, please re-slice your file with the lowest layer height of 0.01mm.";
}
for (uint layerIndex = 1; layerIndex < SlicerFile.LayerCount; layerIndex++)
diff --git a/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs b/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs
index 387b43c..d545b5c 100644
--- a/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs
+++ b/UVtools.Core/Operations/OperationRaiseOnPrintFinish.cs
@@ -19,7 +19,6 @@ namespace UVtools.Core.Operations
public class OperationRaiseOnPrintFinish : Operation
{
#region Constants
- public const byte DummyPixelBrightness = 128;
#endregion
#region Members
@@ -30,8 +29,6 @@ namespace UVtools.Core.Operations
#region Overrides
- public override bool CanRunInPartialMode => true;
-
public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None;
public override string Title => "Raise platform on print finish";
@@ -51,11 +48,23 @@ namespace UVtools.Core.Operations
public override string ValidateSpawn()
{
- if(!SlicerFile.CanUseLayerLiftHeight)
+ if(!SlicerFile.CanUseLayerPositionZ)
{
return NotSupportedMessage;
}
+ if (SlicerFile.LayerCount >= 2)
+ {
+ var layerHeight = SlicerFile.LastLayer.LayerHeight;
+ var criteria = Math.Max((float) Layer.MaximumHeight, SlicerFile.LayerHeight);
+
+ if (layerHeight > criteria)
+ {
+ return $"With a difference of {layerHeight}mm between the last two layers, it looks like this tool had already been applied.\n" +
+ $"The difference must be less or equal to {criteria}mm in order to run this tool.";
+ }
+ }
+
return null;
}
@@ -150,34 +159,24 @@ namespace UVtools.Core.Operations
protected override bool ExecuteInternally(OperationProgress progress)
{
- return Execute(null, null);
- //return !progress.Token.IsCancellationRequested;
- }
-
- public override bool Execute(Mat mat, params object[] arguments)
- {
var layer = SlicerFile.LastLayer.Clone();
layer.PositionZ = (float)_positionZ;
- layer.ExposureTime = 0.05f; // Very low exposure time
+ layer.ExposureTime = SlicerFile.SupportsGCode ? 0 : 0.05f; // Very low exposure time
layer.LightPWM = 0; // Try to disable light if possible
layer.SetNoDelays();
- using var newMat = EmguExtensions.InitMat(SlicerFile.Resolution);
- if(_outputDummyPixel)
- {
- newMat.SetByte(newMat.GetPixelPos(layer.BoundingRectangle.Center()), DummyPixelBrightness);
- }
-
+ using var newMat = _outputDummyPixel
+ ? SlicerFile.CreateMatWithDummyPixel(layer.BoundingRectangle.Center())
+ : SlicerFile.CreateMat();
+
layer.LayerMat = newMat;
SlicerFile.SuppressRebuildPropertiesWork(() =>
{
SlicerFile.LayerManager.Append(layer);
- return true;
});
-
return true;
+ //return !progress.Token.IsCancellationRequested;
}
-
#endregion
}
}
diff --git a/UVtools.Core/Operations/OperationTimelapse.cs b/UVtools.Core/Operations/OperationTimelapse.cs
new file mode 100644
index 0000000..084ea57
--- /dev/null
+++ b/UVtools.Core/Operations/OperationTimelapse.cs
@@ -0,0 +1,418 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using UVtools.Core.FileFormats;
+using UVtools.Core.Layers;
+
+namespace UVtools.Core.Operations
+{
+ [Serializable]
+ public class OperationTimelapse : Operation
+ {
+ #region Constants
+ #endregion
+
+ #region Enums
+ public enum TimelapseRaiseMode
+ {
+ [Description("Lift height: Use the lift sequence to raise to the set postion (Faster)")]
+ LiftHeight,
+
+ [Description("Virtual layer: Print a blank layer to simulate and raise to the set position (Slower)")]
+ VirtualLayer,
+ }
+ #endregion
+
+ #region Members
+ private decimal _raisePositionZ;
+ private bool _outputDummyPixel = true;
+ private decimal _raiseEachNthHeight = 1;
+ private TimelapseRaiseMode _raiseMode = TimelapseRaiseMode.LiftHeight;
+ private decimal _waitTimeAfterLift = 1;
+ private decimal _exposureTime = 1;
+ private bool _useCustomLift;
+ private decimal _slowLiftHeight = 3;
+ private decimal _liftSpeed;
+ private decimal _liftSpeed2;
+ private decimal _slowRetractHeight = 3;
+ private decimal _retractSpeed;
+ private decimal _retractSpeed2;
+
+ #endregion
+
+ #region Overrides
+
+ public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.Normal;
+
+ public override string Title => "Timelapse";
+ public override string Description =>
+ "Raise the build platform to a set position every odd-even height to be able to take a photo and create a time-lapse video of the print.\n" +
+ "You will require external hardware to take the photos, and create the time-lapse video by your own.\n" +
+ "NOTE: Only use this tool once. It will delay the total print time significantly.";
+
+ public override string ConfirmationText =>
+ $"raise the platform at every odd-even {_raiseEachNthHeight}mm to Z={_raisePositionZ}mm";
+
+ public override string ProgressTitle => "Raising layers";
+
+ public override string ProgressAction => "Raised layer";
+
+ public override string ValidateSpawn()
+ {
+ if(!SlicerFile.CanUseLayerPositionZ && !SlicerFile.CanUseLayerLiftHeight)
+ {
+ return NotSupportedMessage;
+ }
+
+ return null;
+ }
+
+ public override string ValidateInternally()
+ {
+ var sb = new StringBuilder();
+
+ if (!ValidateSpawn(out var message))
+ {
+ sb.AppendLine(message);
+ }
+
+ if ((_raiseMode == TimelapseRaiseMode.LiftHeight && !SlicerFile.CanUseLayerLiftHeight) ||
+ (_raiseMode == TimelapseRaiseMode.VirtualLayer && !SlicerFile.CanUseLayerPositionZ))
+ {
+ return $"The raise method {_raiseMode} is not compatible with this printer / file format, please choose other method.";
+ }
+
+ return sb.ToString();
+ }
+
+ public override string ToString()
+ {
+ var result = $"[Mode: {_raiseMode}] [Z={_raisePositionZ}mm] [Raise each: {_raiseEachNthHeight}mm]";
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
+ #endregion
+
+ #region Properties
+
+ /// <summary>
+ /// Gets the minimum possible
+ /// </summary>
+ public float MinimumPositionZ => Layer.RoundHeight(SlicerFile.PrintHeight + SlicerFile.LayerHeight);
+
+ public TimelapseRaiseMode RaiseMode
+ {
+ get => _raiseMode;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _raiseMode, value)) return;
+ RaisePropertyChanged(nameof(IsLiftHeightMode));
+ RaisePropertyChanged(nameof(IsVirtualLayerMode));
+ }
+ }
+
+ public bool IsLiftHeightMode => _raiseMode is TimelapseRaiseMode.LiftHeight;
+ public bool IsVirtualLayerMode => _raiseMode is TimelapseRaiseMode.VirtualLayer;
+
+ /// <summary>
+ /// Sets or gets the Z position to raise to
+ /// </summary>
+ public decimal RaisePositionZ
+ {
+ get => _raisePositionZ;
+ set => RaiseAndSetIfChanged(ref _raisePositionZ, Layer.RoundHeight(Math.Clamp(value, 10, 10000)));
+ }
+
+ /// <summary>
+ /// True to output a dummy pixel on bounding rectangle position to avoid empty layer and blank image, otherwise set to false
+ /// </summary>
+ public bool OutputDummyPixel
+ {
+ get => _outputDummyPixel;
+ set => RaiseAndSetIfChanged(ref _outputDummyPixel, value);
+ }
+
+ /// <summary>
+ /// Gets or sets the alternating height in millimeters to raise when, it will raise only at each defined millimeters and skip the same next millimeters
+ /// </summary>
+ public decimal RaiseEachNthHeight
+ {
+ get => _raiseEachNthHeight;
+ set => RaiseAndSetIfChanged(ref _raiseEachNthHeight, Math.Max(0, value));
+ }
+
+ public ushort RaiseEachNthLayers
+ {
+ get
+ {
+ if (_raiseEachNthHeight == 0) return 1;
+ return (ushort)Math.Max(1, _raiseEachNthHeight / (decimal)SlicerFile.LayerHeight);
+ }
+ }
+
+ public decimal WaitTimeAfterLift
+ {
+ get => _waitTimeAfterLift;
+ set => RaiseAndSetIfChanged(ref _waitTimeAfterLift, Math.Max(0, value));
+ }
+
+ public decimal ExposureTime
+ {
+ get => _exposureTime;
+ set => RaiseAndSetIfChanged(ref _exposureTime, Math.Max(0, value));
+ }
+
+ public bool UseCustomLift
+ {
+ get => _useCustomLift;
+ set => RaiseAndSetIfChanged(ref _useCustomLift, value);
+ }
+
+ public decimal SlowLiftHeight
+ {
+ get => _slowLiftHeight;
+ set => RaiseAndSetIfChanged(ref _slowLiftHeight, Math.Max(0, value));
+ }
+
+ public decimal LiftSpeed
+ {
+ get => _liftSpeed;
+ set => RaiseAndSetIfChanged(ref _liftSpeed, Math.Max(0, value));
+ }
+
+ public decimal LiftSpeed2
+ {
+ get => _liftSpeed2;
+ set => RaiseAndSetIfChanged(ref _liftSpeed2, Math.Max(0, value));
+ }
+
+ public decimal SlowRetractHeight
+ {
+ get => _slowRetractHeight;
+ set => RaiseAndSetIfChanged(ref _slowRetractHeight, Math.Max(0, value));
+ }
+
+ public decimal RetractSpeed
+ {
+ get => _retractSpeed;
+ set => RaiseAndSetIfChanged(ref _retractSpeed, Math.Max(0, value));
+ }
+
+ public decimal RetractSpeed2
+ {
+ get => _retractSpeed2;
+ set => RaiseAndSetIfChanged(ref _retractSpeed2, Math.Max(0, value));
+ }
+
+ #endregion
+
+ #region Constructor
+
+ public OperationTimelapse()
+ {
+ }
+
+ public OperationTimelapse(FileFormat slicerFile) : base(slicerFile)
+ {
+ if (_raisePositionZ <= 0) _raisePositionZ = (decimal)SlicerFile.MachineZ;
+ if(_exposureTime <= 0) _exposureTime = SlicerFile.SupportsGCode ? 0 : 0.05M;
+
+ if (_liftSpeed <= 0) _liftSpeed = (decimal) SlicerFile.LiftSpeed;
+ if (_liftSpeed2 <= 0) _liftSpeed2 = (decimal) SlicerFile.MaximumSpeed;
+
+ if (SlicerFile.CanUseLayerRetractSpeed2) // TSMC
+ {
+ if (_retractSpeed <= 0) _retractSpeed = (decimal)SlicerFile.MaximumSpeed;
+ if (_retractSpeed2 <= 0) _retractSpeed2 = (decimal)SlicerFile.RetractSpeed2;
+ }
+ else
+ {
+ if (_retractSpeed <= 0) _retractSpeed = (decimal)SlicerFile.RetractSpeed;
+ }
+
+ }
+
+ #endregion
+
+ #region Equality
+
+ protected bool Equals(OperationTimelapse other)
+ {
+ return _raisePositionZ == other._raisePositionZ && _outputDummyPixel == other._outputDummyPixel && _raiseEachNthHeight == other._raiseEachNthHeight && _raiseMode == other._raiseMode && _waitTimeAfterLift == other._waitTimeAfterLift && _exposureTime == other._exposureTime && _useCustomLift == other._useCustomLift && _slowLiftHeight == other._slowLiftHeight && _liftSpeed == other._liftSpeed && _liftSpeed2 == other._liftSpeed2 && _slowRetractHeight == other._slowRetractHeight && _retractSpeed == other._retractSpeed && _retractSpeed2 == other._retractSpeed2;
+ }
+
+ 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((OperationTimelapse) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+ hashCode.Add(_raisePositionZ);
+ hashCode.Add(_outputDummyPixel);
+ hashCode.Add(_raiseEachNthHeight);
+ hashCode.Add((int) _raiseMode);
+ hashCode.Add(_waitTimeAfterLift);
+ hashCode.Add(_exposureTime);
+ hashCode.Add(_useCustomLift);
+ hashCode.Add(_slowLiftHeight);
+ hashCode.Add(_liftSpeed);
+ hashCode.Add(_liftSpeed2);
+ hashCode.Add(_slowRetractHeight);
+ hashCode.Add(_retractSpeed);
+ hashCode.Add(_retractSpeed2);
+ return hashCode.ToHashCode();
+ }
+
+ #endregion
+
+ #region Methods
+
+ public void OptimizeRaisePositionZ()
+ {
+ RaisePositionZ = (decimal) Math.Min(SlicerFile.MachineZ, SlicerFile.PrintHeight + 5);
+ }
+
+ public void MaxRaisePositionZ()
+ {
+ RaisePositionZ = (decimal) SlicerFile.MachineZ;
+ }
+
+ protected override bool ExecuteInternally(OperationProgress progress)
+ {
+ var virtualLayers = new List<uint>();
+ float checkpointHeight = SlicerFile[0].PositionZ;
+ for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
+ {
+ progress++;
+ var layer = SlicerFile[layerIndex];
+ if (_raiseEachNthHeight > 0 && (decimal)Layer.RoundHeight(layer.PositionZ - checkpointHeight) < _raiseEachNthHeight) continue;
+ if ((decimal)layer.PositionZ >= _raisePositionZ) break; // pass the target height, do not continue
+ checkpointHeight = layer.PositionZ;
+
+ switch (_raiseMode)
+ {
+ case TimelapseRaiseMode.LiftHeight:
+ if (_useCustomLift)
+ {
+ layer.LiftSpeed = (float)_liftSpeed;
+ layer.RetractSpeed = (float)_retractSpeed;
+
+ if (SlicerFile.CanUseLayerLiftHeight2)
+ {
+ layer.LiftHeight = (float)_slowLiftHeight;
+ }
+
+ if (SlicerFile.CanUseLayerLiftSpeed2)
+ {
+ layer.LiftSpeed2 = (float)_liftSpeed2;
+ }
+
+ if (SlicerFile.CanUseLayerRetractHeight2)
+ {
+ layer.RetractHeight2 = (float)_slowRetractHeight;
+ }
+
+ if (SlicerFile.CanUseLayerRetractSpeed2)
+ {
+ layer.RetractSpeed2 = (float)_retractSpeed2;
+ }
+ }
+
+ if (SlicerFile.CanUseLayerLiftHeight2 && (layer.LiftHeight2 > 0 || _useCustomLift && _slowLiftHeight > 0)) // TSMC
+ {
+ layer.LiftHeight2 = Math.Max(0, (float)_raisePositionZ - layer.PositionZ - layer.LiftHeight);
+ }
+ else
+ {
+ layer.LiftHeightTotal = Math.Max(layer.LiftHeightTotal, (float)_raisePositionZ - layer.PositionZ);
+ }
+
+ if (SlicerFile.CanUseLayerWaitTimeAfterLift && _waitTimeAfterLift > 0)
+ {
+ layer.WaitTimeAfterLift = (float) _waitTimeAfterLift;
+ }
+
+ break;
+ case TimelapseRaiseMode.VirtualLayer:
+ virtualLayers.Add(layerIndex);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(RaiseMode));
+ }
+ }
+
+ if (virtualLayers.Count > 0 && _raiseMode == TimelapseRaiseMode.VirtualLayer)
+ {
+ using var mat = _outputDummyPixel
+ ? SlicerFile.CreateMatWithDummyPixel()
+ : SlicerFile.CreateMat();
+
+ var layer = new Layer(SlicerFile.LayerCount, mat, SlicerFile)
+ {
+ PositionZ = (float) _raisePositionZ,
+ ExposureTime = (float) _exposureTime,
+ // This layer does not require a lift procedure
+ LiftHeightTotal = SlicerFile.SupportsGCode ? 0 : 0.1f,
+ LiftSpeed = SlicerFile.MaximumSpeed,
+ LiftSpeed2 = SlicerFile.MaximumSpeed,
+ RetractSpeed = SlicerFile.MaximumSpeed,
+ RetractSpeed2 = SlicerFile.MaximumSpeed,
+ RetractHeight2 = 0
+ };
+
+ layer.SetNoDelays();
+
+ /*if (_useCustomLift)
+ {
+ layer.LiftSpeed = (float) _liftSpeed;
+ layer.RetractSpeed = (float) _retractSpeed;
+
+ if (SlicerFile.CanUseLayerLiftSpeed2)
+ {
+ layer.LiftSpeed2 = (float) _liftSpeed2;
+ }
+
+ if (SlicerFile.CanUseLayerRetractSpeed2)
+ {
+ layer.RetractSpeed2 = (float) _retractSpeed2;
+ }
+ }*/
+
+ var layers = SlicerFile.ToList();
+ for (int i = 0; i < virtualLayers.Count; i++)
+ {
+ /*var newLayer = layer.Clone();
+ if (!_useCustomLift)
+ {
+ var intersectLayer = Math.Clamp(virtualLayers[i] + i, 0, SlicerFile.LastLayerIndex);
+ SlicerFile[intersectLayer].CopyLiftTo(newLayer);
+ // This layer does not require a lift procedure
+ newLayer.LiftHeightTotal = SlicerFile.SupportsGCode ? 0 : 0.1f;
+ newLayer.RetractHeight2 = 0;
+ }*/
+ layers.Insert((int)(virtualLayers[i] + i), layer.Clone());
+ }
+ SlicerFile.SuppressRebuildPropertiesWork(() => SlicerFile.LayerManager.Layers = layers.ToArray());
+ }
+
+ return !progress.Token.IsCancellationRequested;
+ }
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/Scripting/ScriptToggleSwitchInput.cs b/UVtools.Core/Scripting/ScriptToggleSwitchInput.cs
new file mode 100644
index 0000000..b7968e9
--- /dev/null
+++ b/UVtools.Core/Scripting/ScriptToggleSwitchInput.cs
@@ -0,0 +1,23 @@
+/*
+ * 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.Scripting
+{
+ public class ScriptToggleSwitchInput : ScriptBaseInput<bool>
+ {
+ /// <summary>
+ /// Gets or sets the text when the switch is turned off
+ /// </summary>
+ public string OffText { get; set; }
+
+ /// <summary>
+ /// Gets or sets the text when the switch is turned on
+ /// </summary>
+ public string OnText { get; set; }
+ }
+}
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index d6cc8f7..b020595 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.27.7</Version>
+ <Version>2.28.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 0d117a8..291c1b6 100644
--- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs
+++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
@@ -314,9 +314,6 @@
<Component Id="owc6F31DEF09608AA645959666A4CB7FBBC" Guid="c94570a5-5bb4-a53f-e12f-9581bddf7d08" Win64="yes">
<File Id="owf6F31DEF09608AA645959666A4CB7FBBC" Source="$(var.SourceDir)\mscordaccore.dll" KeyPath="yes" />
</Component>
- <Component Id="owcBCADC5DC1EF24D8E4326B45081E898A1" Guid="80807612-765f-6dc1-1534-37735987a448" Win64="yes">
- <File Id="owfBCADC5DC1EF24D8E4326B45081E898A1" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_5.0.1321.56516.dll" KeyPath="yes" />
- </Component>
<Component Id="owc5FC34571A1AE47A011FC6C2A95B00DA6" Guid="2591451e-0fe5-ccad-abf6-1d1097251253" Win64="yes">
<File Id="owf5FC34571A1AE47A011FC6C2A95B00DA6" Source="$(var.SourceDir)\mscordbi.dll" KeyPath="yes" />
</Component>
@@ -1413,6 +1410,9 @@
<Component Id="owc2B1C136C44C740F48CDFE91A80F48B4B" Guid="2B1C136C-44C7-40F4-8CDF-E91A80F48B4B">
<File Id="owf2B1C136C44C740F48CDFE91A80F48B4B" Source="$(var.SourceDir)\opencv_videoio_ffmpeg455_64.dll" KeyPath="yes" />
</Component>
+ <Component Id="owc32E45984571E4D3293714751B0645034" Guid="32E45984-571E-4D32-9371-4751B0645034">
+ <File Id="owf32E45984571E4D3293714751B0645034" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_5.0.1422.5710.dll" KeyPath="yes" />
+ </Component>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="scd220707349D4C8FA275285514283F3E2A" Name="UVtools" />
diff --git a/UVtools.Platforms/osx-x64/libcvextern.dylib b/UVtools.Platforms/osx-x64/libcvextern.dylib
index eb8bdfc..7b8afcf 100644
--- a/UVtools.Platforms/osx-x64/libcvextern.dylib
+++ b/UVtools.Platforms/osx-x64/libcvextern.dylib
Binary files differ
diff --git a/UVtools.ScriptSample/ScriptCloneSettings.cs b/UVtools.ScriptSample/ScriptCloneSettings.cs
index 76ac8f1..9b229b3 100644
--- a/UVtools.ScriptSample/ScriptCloneSettings.cs
+++ b/UVtools.ScriptSample/ScriptCloneSettings.cs
@@ -70,7 +70,7 @@ namespace UVtools.ScriptSample
Script.UserInputs.Add(Report);
Script.UserInputs.Add(OpenReport);
Script.UserInputs.Add(FolderPath);
- FolderPath.Value = SlicerFile.FileDirectoryPath;
+ FolderPath.Value = SlicerFile.DirectoryPath;
}
/// <summary>
diff --git a/UVtools.ScriptSample/ScriptDebandingZSample.cs b/UVtools.ScriptSample/ScriptDebandingZSample.cs
index debd6cd..6f8503e 100644
--- a/UVtools.ScriptSample/ScriptDebandingZSample.cs
+++ b/UVtools.ScriptSample/ScriptDebandingZSample.cs
@@ -153,7 +153,7 @@ namespace UVtools.ScriptSample
var pixelPos = firstLayer.BoundingRectangle.Center();
mat.SetByte(pixelPos.X, pixelPos.Y, 1); // Print a very fade pixel to ignore empty layer detection
firstLayer.LayerMat = mat;
- firstLayer.ExposureTime = SlicerFile.SupportsGCode ? 0 : 0.1f;
+ firstLayer.ExposureTime = SlicerFile.SupportsGCode ? 0 : 0.05f;
SlicerFile.SuppressRebuildPropertiesWork(() => { SlicerFile.LayerManager.Prepend(firstLayer); });
}
diff --git a/UVtools.ScriptSample/ScriptTimelapseSample.cs b/UVtools.ScriptSample/ScriptTimelapseSample.cs
index 113a07e..75fda23 100644
--- a/UVtools.ScriptSample/ScriptTimelapseSample.cs
+++ b/UVtools.ScriptSample/ScriptTimelapseSample.cs
@@ -52,9 +52,10 @@ namespace UVtools.ScriptSample
DecimalPlates = 2
};
- private ScriptCheckBoxInput InputUseVirtualLayer = new()
+ private ScriptToggleSwitchInput InputUseVirtualLayer = new()
{
- Label = "Use blank layers to go to the target height",
+ OnText = "Use blank layers to go to the target height",
+ OffText = "Use lift movement to go to the target height",
ToolTip = "Use this option if you printer is unable to use large lifts or waits after lift"
};
@@ -140,7 +141,7 @@ namespace UVtools.ScriptSample
var layer = new Layer(SlicerFile.LayerCount, mat, SlicerFile)
{
PositionZ = InputPositionZ.Value,
- ExposureTime = SlicerFile.SupportsGCode ? 0 : 0.1f,
+ ExposureTime = SlicerFile.SupportsGCode ? 0 : 0.05f,
LiftSpeed = InputLiftSpeed.Value,
RetractSpeed = InputRetractSpeed.Value
};
diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs
index c302212..cad9b28 100644
--- a/UVtools.WPF/App.axaml.cs
+++ b/UVtools.WPF/App.axaml.cs
@@ -8,7 +8,6 @@
using System;
using System.Diagnostics;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -44,9 +43,6 @@ namespace UVtools.WPF
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
- CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
- CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
-
UserSettings.Load();
UserSettings.SetVersion();
diff --git a/UVtools.WPF/Assets/Icons/camera-16x16.png b/UVtools.WPF/Assets/Icons/camera-16x16.png
new file mode 100644
index 0000000..423499c
--- /dev/null
+++ b/UVtools.WPF/Assets/Icons/camera-16x16.png
Binary files differ
diff --git a/UVtools.WPF/Assets/Styles/Styles.xaml b/UVtools.WPF/Assets/Styles/Styles.xaml
index 9c6fbee..7e36b51 100644
--- a/UVtools.WPF/Assets/Styles/Styles.xaml
+++ b/UVtools.WPF/Assets/Styles/Styles.xaml
@@ -14,6 +14,11 @@
<Setter Property="FontWeight" Value="Bold" />
</Style>
+ <Style Selector="Border.Header">
+ <Setter Property="Padding" Value="10,10" />
+ <Setter Property="Margin" Value="0,0,0,10" />
+ </Style>
+
<Style Selector="Border.FooterActions">
<Setter Property="Padding" Value="10,20" />
<Setter Property="Margin" Value="0,10,0, 0" />
diff --git a/UVtools.WPF/Assets/Styles/StylesLight.xaml b/UVtools.WPF/Assets/Styles/StylesLight.xaml
index 2a966ef..ca9b660 100644
--- a/UVtools.WPF/Assets/Styles/StylesLight.xaml
+++ b/UVtools.WPF/Assets/Styles/StylesLight.xaml
@@ -13,6 +13,10 @@
<Setter Property="Background" Value="LightBlue" />
</Style>
+ <Style Selector="Border.Header">
+ <Setter Property="Background" Value="LightGray" />
+ </Style>
+
<Style Selector="Border.FooterActions">
<Setter Property="Background" Value="LightGray" />
</Style>
diff --git a/UVtools.WPF/ConsoleArguments.cs b/UVtools.WPF/ConsoleArguments.cs
index cccc72e..f2cd96d 100644
--- a/UVtools.WPF/ConsoleArguments.cs
+++ b/UVtools.WPF/ConsoleArguments.cs
@@ -195,6 +195,155 @@ namespace UVtools.WPF
return true;
}
+ // Run operation and save file
+ if (args[0] is "--run-operation")
+ {
+ if (args.Length < 3)
+ {
+ Console.WriteLine("Invalid syntax: <input_file> <operation_file.uvtop>");
+ return true;
+ }
+
+ if (!File.Exists(args[1]))
+ {
+ Console.WriteLine($"Input file does not exists: {args[1]}");
+ return true;
+ }
+
+ if (!File.Exists(args[2]))
+ {
+ Console.WriteLine($"Operation file does not exists: {args[2]}");
+ return true;
+ }
+
+ var slicerFile = FileFormat.FindByExtensionOrFilePath(args[1], true);
+ if (slicerFile is null)
+ {
+ Console.WriteLine($"Invalid input file: {args[1]}");
+ return true;
+ }
+
+ var operation = Operation.Deserialize(args[2]);
+
+ if (operation is null)
+ {
+ Console.WriteLine($"Invalid operation file: {args[2]}");
+ return true;
+ }
+
+ Console.WriteLine($"Loading file: {args[1]}");
+ slicerFile.Decode(args[1]);
+
+ Console.WriteLine($"Running operation: {operation.Title}");
+ operation.SlicerFile = slicerFile;
+ operation.Execute();
+ slicerFile.Save();
+ Console.WriteLine("Saved");
+ Console.WriteLine("OK");
+
+ return true;
+ }
+
+ // Run operation and save file
+ if (args[0] is "--run-script")
+ {
+ if (args.Length < 3)
+ {
+ Console.WriteLine("Invalid syntax: <input_file> <script_file.cs>");
+ return true;
+ }
+
+ if (!File.Exists(args[1]))
+ {
+ Console.WriteLine($"Input file does not exists: {args[1]}");
+ return true;
+ }
+
+ if (!File.Exists(args[2]) || !(args[2].EndsWith(".csx") || args[2].EndsWith(".cs")))
+ {
+ Console.WriteLine($"Script file does not exists or invalid: {args[2]}");
+ return true;
+ }
+
+ var slicerFile = FileFormat.FindByExtensionOrFilePath(args[1], true);
+ if (slicerFile is null)
+ {
+ Console.WriteLine($"Invalid input file: {args[1]}");
+ return true;
+ }
+
+ Console.WriteLine($"Loading file: {args[1]}");
+ slicerFile.Decode(args[1]);
+
+ var operation = new OperationScripting(slicerFile);
+ operation.ReloadScriptFromFile(args[2]);
+
+ Console.WriteLine($"Running script: {operation.Title}");
+ operation.Execute();
+ slicerFile.Save();
+ Console.WriteLine("Saved");
+ Console.WriteLine("OK");
+
+ return true;
+ }
+
+ // Run operation and save file
+ if (args[0] is "--copy-parameters")
+ {
+ if (args.Length < 3)
+ {
+ Console.WriteLine("Invalid syntax: <from_file> <to_file>");
+ return true;
+ }
+
+ if (args[1] == args[2])
+ {
+ Console.WriteLine($"Source file must be different from target file");
+ return true;
+ }
+
+ if (!File.Exists(args[1]))
+ {
+ Console.WriteLine($"Source file does not exists: {args[1]}");
+ return true;
+ }
+
+ if (!File.Exists(args[2]))
+ {
+ Console.WriteLine($"Target file does not exists: {args[1]}");
+ return true;
+ }
+
+ var fromFile = FileFormat.FindByExtensionOrFilePath(args[1], true);
+ if (fromFile is null)
+ {
+ Console.WriteLine($"Invalid source file: {args[1]}");
+ return true;
+ }
+
+ var toFile = FileFormat.FindByExtensionOrFilePath(args[2], true);
+ if (toFile is null)
+ {
+ Console.WriteLine($"Invalid target file: {args[2]}");
+ return true;
+ }
+
+ Console.WriteLine("Loading files");
+ fromFile.Decode(args[1], FileFormat.FileDecodeType.Partial);
+ toFile.Decode(args[2], FileFormat.FileDecodeType.Partial);
+
+ var count = FileFormat.CopyParameters(fromFile, toFile);
+ Console.WriteLine($"Modified parameters: {count}");
+ if (count > 0)
+ {
+ toFile.Save();
+ Console.WriteLine("Saved");
+ }
+ Console.WriteLine("OK");
+
+ return true;
+ }
+
if (args[0] == "--crypt-ctb")
{
if (!File.Exists(args[1]))
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
index 66b1285..8145d61 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateExposureFinderControl.axaml
@@ -758,7 +758,13 @@
<Expander
Header="Multiple exposures"
- IsExpanded="True" IsVisible="{Binding CanSupportPerLayerSettings}">
+ IsExpanded="True">
+ <Expander.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="CanSupportPerLayerPositionZ"/>
+ <Binding Path="CanSupportPerLayerExposureTime"/>
+ </MultiBinding>
+ </Expander.IsVisible>
<StackPanel Spacing="10">
diff --git a/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs
index db3e05e..df6afbc 100644
--- a/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolControl.axaml.cs
@@ -40,9 +40,11 @@ namespace UVtools.WPF.Controls.Tools
InitializeComponent();
}
- public ToolControl(Operation operation) : this()
+ public ToolControl(Operation operation)
{
BaseOperation = operation;
+ if (!ValidateSpawn()) return;
+ InitializeComponent();
}
private void InitializeComponent()
diff --git a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs
index 9c7d5bf..15aee68 100644
--- a/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolDynamicLayerHeightControl.axaml.cs
@@ -1,6 +1,5 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-using UVtools.Core;
using UVtools.Core.FileFormats;
using UVtools.Core.Layers;
using UVtools.Core.Operations;
@@ -24,7 +23,7 @@ namespace UVtools.WPF.Controls.Tools
BaseOperation = new OperationDynamicLayerHeight(SlicerFile);
if (!ValidateSpawn()) return;
- if (!SlicerFile.HaveLayerParameterModifier(FileFormat.PrintParameterModifier.ExposureTime))
+ if (!SlicerFile.CanUseLayerExposureTime)
{
App.MainWindow.MessageBoxWaring($"Your printer seems to not support this tool, still you are allowed to run it for analyze, packing layers or simulation.\n" +
$"Do not print this file after run this tool on a not compatible printer, it will result in malformed model and height violation.\n" +
diff --git a/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
index e86c387..ac5e73e 100644
--- a/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
@@ -152,7 +152,7 @@ namespace UVtools.WPF.Controls.Tools
{
var variable = Operation.ScriptGlobals.Script.UserInputs[i];
- if (!string.IsNullOrWhiteSpace(variable.Label) && variable is not ScriptCheckBoxInput)
+ if (!string.IsNullOrWhiteSpace(variable.Label) && variable is not ScriptCheckBoxInput and not ScriptToggleSwitchInput)
{
TextBlock tbLabel = new()
{
@@ -489,6 +489,32 @@ namespace UVtools.WPF.Controls.Tools
continue;
}
+ case ScriptToggleSwitchInput inputToggleSwitch:
+ {
+ var control = new ToggleSwitch
+ {
+ OnContent = inputToggleSwitch.OnText,
+ OffContent = inputToggleSwitch.OffText,
+ IsChecked = inputToggleSwitch.Value
+ };
+
+ var valueProperty = control.GetObservable(ToggleSwitch.IsCheckedProperty);
+ valueProperty.Subscribe(value =>
+ {
+ if (value != null) inputToggleSwitch.Value = value.Value;
+ });
+
+ _scriptVariablesGrid.Children.Add(control);
+ Grid.SetRow(control, i * 2);
+ Grid.SetColumn(control, 2);
+
+ if (!string.IsNullOrWhiteSpace(variable.ToolTip))
+ {
+ ToolTip.SetTip(control, variable.ToolTip);
+ }
+
+ continue;
+ }
case ScriptTextBoxInput inputTextBox:
{
TextBox control = new()
diff --git a/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml b/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml
new file mode 100644
index 0000000..edabb6f
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml
@@ -0,0 +1,281 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="UVtools.WPF.Controls.Tools.ToolTimelapseControl">
+ <StackPanel Spacing="10" Orientation="Vertical">
+ <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,10,Auto">
+
+ <TextBlock Grid.Row="0" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Raise method:"/>
+
+ <ComboBox Grid.Row="0" Grid.Column="2"
+ Width="500"
+ HorizontalContentAlignment="Stretch"
+ Items="{Binding Operation.RaiseMode, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
+ SelectedItem="{Binding Operation.RaiseMode, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="The Z position to raise the build plate to.
+ &#x0a;If the set position is lower than the model total height, the time-lapse function won't have effect further the set height."
+ Text="Raise plate to:"/>
+
+ <StackPanel Grid.Row="2" Grid.Column="2"
+ Orientation="Horizontal" Spacing="5">
+ <NumericUpDown Classes="ValueLabel ValueLabel_mm"
+ Minimum="10"
+ Maximum="10000"
+ Increment="1"
+ Width="100"
+ Value="{Binding Operation.RaisePositionZ}"/>
+
+ <Button Content="Auto" Command="{Binding Operation.OptimizeRaisePositionZ}"/>
+ <Button Content="Max" Command="{Binding Operation.MaxRaisePositionZ}"/>
+
+ <TextBlock VerticalAlignment="Center"
+ FontWeight="Bold"
+ Text="{Binding SlicerFile.PrintHeight, StringFormat=Print height: {0}mm}"/>
+ </StackPanel>
+
+ <TextBlock Grid.Row="4" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="The alternating height in millimeters to raise when, it will raise only at each defined millimeters and skip the same next millimeters.
+ &#x0a;Use 0mm to raise at each layer."
+ Text="Raise each:"/>
+
+ <NumericUpDown Grid.Row="4" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mm"
+ HorizontalAlignment="Left"
+ Minimum="0"
+ Maximum="10000"
+ Increment="0.5"
+ Width="100"
+ Value="{Binding Operation.RaiseEachNthHeight}"/>
+
+
+ <!-- LiftHeight: Wait time after lift -->
+ <TextBlock Grid.Row="6" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="The time to wait on the set position.
+ &#x0a;Use the lowest as possible but enough to take the photo while standing still."
+ IsVisible="{Binding Operation.IsLiftHeightMode}"
+ Text="Wait time:"/>
+
+ <NumericUpDown Grid.Row="6" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_s"
+ HorizontalAlignment="Left"
+ Minimum="0"
+ Maximum="100"
+ Increment="0.5"
+ Width="100"
+ Value="{Binding Operation.WaitTimeAfterLift}">
+ <NumericUpDown.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="Operation.IsLiftHeightMode"/>
+ <Binding Path="SlicerFile.CanUseLayerWaitTimeAfterLift"/>
+ </MultiBinding>
+ </NumericUpDown.IsVisible>
+ </NumericUpDown>
+
+ <TextBlock Grid.Row="6" Grid.Column="2"
+ VerticalAlignment="Center"
+ ToolTip.Tip="You won't be able to choose a time to wait while standing still.
+ &#x0a;Advice: Take the photo when printer is de-accelerating near the target height."
+ Text="(Not supported by your printer / file format)">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="Operation.IsLiftHeightMode"/>
+ <Binding Path="!SlicerFile.CanUseLayerWaitTimeAfterLift"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
+
+ <!-- VirtualLayer: Exposure time -->
+ <TextBlock Grid.Row="6" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="The exposure time of the virtual layer.
+ &#x0a;This can be used to trigger an external light sensor.
+ &#x0a;Use the lowest as possible but enough to sensor to trigger.
+ &#x0a;If you want to disable this set to 0.05s as most file formats are not compatible with 0s."
+ IsVisible="{Binding Operation.IsVirtualLayerMode}"
+ Text="Exposure time:"/>
+
+ <StackPanel Grid.Row="6" Grid.Column="2"
+ Orientation="Horizontal" Spacing="10">
+
+ <NumericUpDown
+ Classes="ValueLabel ValueLabel_s"
+ HorizontalAlignment="Left"
+ Minimum="0"
+ Maximum="100"
+ Increment="0.5"
+ Width="100"
+ Value="{Binding Operation.ExposureTime}">
+ <NumericUpDown.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="Operation.IsVirtualLayerMode"/>
+ <Binding Path="SlicerFile.CanUseLayerExposureTime"/>
+ </MultiBinding>
+ </NumericUpDown.IsVisible>
+ </NumericUpDown>
+
+
+ <CheckBox VerticalAlignment="Center"
+ ToolTip.Tip="If enabled, output a dummy pixel inside the layer bound to prevent a empty image and to ensure the correct handle by the firmware. This will also prevent layer being removed by auto-fix issues (Empty Layers)."
+ Content="Output a dummy pixel"
+ IsVisible="{Binding Operation.IsVirtualLayerMode}"
+ IsChecked="{Binding Operation.OutputDummyPixel}"/>
+ </StackPanel>
+
+ <TextBlock Grid.Row="6" Grid.Column="2"
+ VerticalAlignment="Center"
+ ToolTip.Tip="You won't be able to choose a exposure time for the virtual layer.
+ &#x0a;Global exposure time will be used instead.
+ &#x0a;Advice: Take the photo when printer is de-accelerating near the target height."
+ Text="(Not supported by your printer / file format)">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="Operation.IsVirtualLayerMode"/>
+ <Binding Path="!SlicerFile.CanUseLayerExposureTime"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
+
+ <CheckBox Grid.Row="8" Grid.Column="2"
+ VerticalAlignment="Center"
+ Content="Set custom lift parameters"
+ IsChecked="{Binding Operation.UseCustomLift}">
+ <CheckBox.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="Operation.IsLiftHeightMode"/>
+ <Binding Path="SlicerFile.CanUseLayerLiftHeight"/>
+ </MultiBinding>
+ </CheckBox.IsVisible>
+ </CheckBox>
+ </Grid>
+
+ <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,10,210,5,Auto,5,210">
+ <Grid.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="Operation.IsLiftHeightMode"/>
+ <Binding Path="Operation.UseCustomLift"/>
+ <Binding Path="SlicerFile.CanUseLayerLiftHeight"/>
+ </MultiBinding>
+ </Grid.IsVisible>
+
+ <TextBlock Grid.Row="0" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="The slow first lift sequence (TSMC).
+&#x0a;Use a low value or 0 to disable."
+ Text="Slow lift height:"/>
+
+ <NumericUpDown Grid.Row="0" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mm"
+ Minimum="0"
+ Maximum="20"
+ Increment="1"
+ IsVisible="{Binding SlicerFile.CanUseLayerLiftHeight2}"
+ Value="{Binding Operation.SlowLiftHeight}">
+ </NumericUpDown>
+
+ <TextBlock Grid.Row="0" Grid.Column="2"
+ VerticalAlignment="Center"
+ ToolTip.Tip="(Not supported by your printer / file format)"
+ IsVisible="{Binding !SlicerFile.CanUseLayerLiftHeight2}"
+ Text="(Not supported)"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Lift speed:"/>
+
+ <NumericUpDown Grid.Row="2" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mmmin"
+ Minimum="0"
+ Maximum="10000"
+ Increment="10"
+ IsVisible="{Binding SlicerFile.CanUseLayerLiftSpeed}"
+ Value="{Binding Operation.LiftSpeed}">
+ </NumericUpDown>
+
+ <TextBlock Grid.Row="2" Grid.Column="2"
+ VerticalAlignment="Center"
+ ToolTip.Tip="(Not supported by your printer / file format)"
+ IsVisible="{Binding !SlicerFile.CanUseLayerLiftSpeed}"
+ Text="(Not supported)"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsVisible="{Binding SlicerFile.CanUseLayerLiftSpeed2}"
+ Text="-"/>
+
+ <NumericUpDown Grid.Row="2" Grid.Column="6"
+ Classes="ValueLabel ValueLabel_mmmin"
+ Minimum="0"
+ Maximum="10000"
+ Increment="10"
+ IsVisible="{Binding SlicerFile.CanUseLayerLiftSpeed2}"
+ Value="{Binding Operation.LiftSpeed2}"/>
+
+ <TextBlock Grid.Row="4" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="The slow last retract sequence (TSMC).
+&#x0a;Use a low value or 0 to disable."
+ Text="Slow retract height:"/>
+
+ <NumericUpDown Grid.Row="4" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mm"
+ Minimum="0"
+ Maximum="20"
+ Increment="1"
+ IsVisible="{Binding SlicerFile.CanUseLayerRetractHeight2}"
+ Value="{Binding Operation.SlowRetractHeight}">
+ </NumericUpDown>
+
+ <TextBlock Grid.Row="4" Grid.Column="2"
+ VerticalAlignment="Center"
+ ToolTip.Tip="(Not supported by your printer / file format)"
+ IsVisible="{Binding !SlicerFile.CanUseLayerRetractHeight2}"
+ Text="(Not supported)"/>
+
+
+ <TextBlock Grid.Row="6" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Retract speed:"/>
+
+ <NumericUpDown Grid.Row="6" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mmmin"
+ Minimum="0"
+ Maximum="10000"
+ Increment="10"
+ IsVisible="{Binding SlicerFile.CanUseLayerRetractSpeed}"
+ Value="{Binding Operation.RetractSpeed}">
+ </NumericUpDown>
+
+ <TextBlock Grid.Row="6" Grid.Column="2"
+ VerticalAlignment="Center"
+ ToolTip.Tip="(Not supported by your printer / file format)"
+ IsVisible="{Binding !SlicerFile.CanUseLayerRetractSpeed}"
+ Text="(Not supported)"/>
+
+ <TextBlock Grid.Row="6" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsVisible="{Binding SlicerFile.CanUseLayerRetractSpeed2}"
+ Text="-"/>
+
+ <NumericUpDown Grid.Row="6" Grid.Column="6"
+ Classes="ValueLabel ValueLabel_mmmin"
+ Minimum="0"
+ Maximum="10000"
+ Increment="10"
+ IsVisible="{Binding SlicerFile.CanUseLayerRetractSpeed2}"
+ Value="{Binding Operation.RetractSpeed2}"/>
+
+ </Grid>
+
+ </StackPanel>
+</UserControl>
diff --git a/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml.cs
new file mode 100644
index 0000000..5b68845
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolTimelapseControl.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia.Markup.Xaml;
+using UVtools.Core.Operations;
+
+namespace UVtools.WPF.Controls.Tools
+{
+ public partial class ToolTimelapseControl : ToolControl
+ {
+ public OperationTimelapse Operation => BaseOperation as OperationTimelapse;
+
+ public ToolTimelapseControl()
+ {
+ BaseOperation = new OperationTimelapse(SlicerFile);
+ if (!ValidateSpawn()) return;
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/UVtools.WPF/MainWindow.Information.cs b/UVtools.WPF/MainWindow.Information.cs
index b1613bd..586c489 100644
--- a/UVtools.WPF/MainWindow.Information.cs
+++ b/UVtools.WPF/MainWindow.Information.cs
@@ -350,11 +350,12 @@ namespace UVtools.WPF
CurrentLayerProperties.Add(new ValueDescription($"{Layer.ShowHeight(layer.PositionZ)}mm", nameof(layer.PositionZ)));
CurrentLayerProperties.Add(new ValueDescription(layer.IsBottomLayer.ToString(), nameof(layer.IsBottomLayer)));
CurrentLayerProperties.Add(new ValueDescription(layer.IsModified.ToString(), nameof(layer.IsModified)));
- CurrentLayerProperties.Add(new ValueDescription($"{layer.ExposureTime:F2}s", nameof(layer.ExposureTime)));
-
+
+ if (SlicerFile.CanUseExposureTime)
+ CurrentLayerProperties.Add(new ValueDescription($"{layer.ExposureTime:F2}s", nameof(layer.ExposureTime)));
+
if (SlicerFile.SupportPerLayerSettings)
{
-
if (SlicerFile.CanUseLayerLiftHeight)
CurrentLayerProperties.Add(new ValueDescription($"{layer.LiftHeight.ToString(CultureInfo.InvariantCulture)}mm @ {layer.LiftSpeed.ToString(CultureInfo.InvariantCulture)}mm/min", nameof(layer.LiftHeight)));
if (SlicerFile.CanUseLayerLiftHeight2)
diff --git a/UVtools.WPF/MainWindow.Suggestions.cs b/UVtools.WPF/MainWindow.Suggestions.cs
index db7afc7..be034dc 100644
--- a/UVtools.WPF/MainWindow.Suggestions.cs
+++ b/UVtools.WPF/MainWindow.Suggestions.cs
@@ -28,9 +28,11 @@ namespace UVtools.WPF
#region Properties
public Suggestion[] Suggestions { get; } =
{
+#if DEBUG
//new SuggestionBottomLayerCount(),
//new SuggestionWaitTimeAfterCure(),
//new SuggestionLayerHeight()
+#endif
};
public RangeObservableCollection<Suggestion> SuggestionsAvailable { get; } = new();
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index d1c757e..d141d39 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -112,7 +112,7 @@
</MenuItem.Icon>
</MenuItem>
- <MenuItem Name="MainMenu.File.OpenCurrentFileFolder" Header="Open current file fo_lder" HotKey="Ctrl+Shift+L" InputGesture="Ctrl+Shift+L" IsEnabled="{Binding IsFileLoaded}" Command="{Binding MenuFileOpenCurrentFileFolderClicked}">
+ <MenuItem Name="MainMenu.File.OpenContainingFileFolder" Header="Open containing fo_lder" HotKey="Ctrl+Shift+L" InputGesture="Ctrl+Shift+L" IsEnabled="{Binding IsFileLoaded}" Command="{Binding MenuFileOpenContainingFolderClicked}">
<MenuItem.Icon>
<Image Source="\Assets\Icons\folder-open-16x16.png"/>
</MenuItem.Icon>
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index 4ecd338..92d60f3 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -20,7 +20,6 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
-using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using UVtools.AvaloniaControls;
@@ -32,7 +31,6 @@ using UVtools.Core.Managers;
using UVtools.Core.Network;
using UVtools.Core.Objects;
using UVtools.Core.Operations;
-using UVtools.Core.Suggestions;
using UVtools.Core.SystemOS;
using UVtools.WPF.Controls;
using UVtools.WPF.Controls.Calibrators;
@@ -271,6 +269,14 @@ namespace UVtools.WPF
},
new()
{
+ Tag = new OperationTimelapse(),
+ Icon = new Avalonia.Controls.Image
+ {
+ Source = new Bitmap(App.GetAsset("/Assets/Icons/camera-16x16.png"))
+ }
+ },
+ new()
+ {
Tag = new OperationScripting(),
Icon = new Avalonia.Controls.Image
{
@@ -1218,7 +1224,7 @@ namespace UVtools.WPF
public void MenuFileOpenNewWindowClicked() => OpenFile(true);
public void MenuFileOpenInPartialModeClicked() => OpenFile(false, FileFormat.FileDecodeType.Partial);
- public void MenuFileOpenCurrentFileFolderClicked()
+ public void MenuFileOpenContainingFolderClicked()
{
if (!IsFileLoaded) return;
SystemAware.SelectFileOnExplorer(SlicerFile.FileFullPath);
@@ -1508,18 +1514,8 @@ namespace UVtools.WPF
if(!IsFileLoaded) continue;
try
{
- var fileText = await File.ReadAllTextAsync(files[i]);
- var match = Regex.Match(fileText, @"(?:<\/\s*Operation)([a-zA-Z0-9_]+)(?:\s*>)");
- if(!match.Success) continue;
- if(match.Groups.Count < 1) continue;
- var operationName = match.Groups[1].Value;
- var baseType = typeof(Operation).FullName;
- if(string.IsNullOrWhiteSpace(baseType)) continue;
- var classname = baseType + operationName+", UVtools.Core";
- var type = Type.GetType(classname);
- if(type is null) continue;
- var operation = Operation.Deserialize(files[i], type);
- await ShowRunOperation(type, operation);
+ var operation = Operation.Deserialize(files[i]);
+ await ShowRunOperation(operation);
}
catch (Exception e)
{
@@ -1624,43 +1620,97 @@ namespace UVtools.WPF
if (!string.IsNullOrWhiteSpace(convertFileExtension))
{
convertFileExtension = convertFileExtension.ToLower(CultureInfo.InvariantCulture);
- var convertToFormat = FileFormat.FindByExtensionOrFilePath(convertFileExtension);
- if (convertToFormat is not null)
+ var fileExtension = FileFormat.FindExtension(convertFileExtension);
+ //var convertToFormat = FileFormat.FindByExtensionOrFilePath(convertFileExtension);
+ var convertToFormat = fileExtension?.GetFileFormat();
+ if (fileExtension is not null && convertToFormat is not null)
{
- var directory = Path.GetDirectoryName(SlicerFile.FileFullPath);
+ var directory = SlicerFile.DirectoryPath;
var filename = FileFormat.GetFileNameStripExtensions(SlicerFile.FileFullPath);
+ var targetFilename = $"{filename}.{convertFileExtension}";
+ var outputFile = Path.Combine(directory, targetFilename);
FileFormat convertedFile = null;
- IsGUIEnabled = false;
- ShowProgressWindow($"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {convertFileExtension}");
-
- task = await Task.Factory.StartNew(() =>
+ bool canConvert = true;
+ if (File.Exists(outputFile))
{
- try
+ var result = await this.MessageBoxQuestion(
+ $"The file '{SlicerFile.Filename}' is about to get auto-converted to '{targetFilename}'.\n" +
+ $"But a file with same name already exists on the output directory '{directory}'.\n" +
+ "Do you want to overwrite the existing file?\n\n" +
+ "Yes: Overwrite the file.\n" +
+ "No: Choose a location for the file.\n" +
+ "Cancel: Do not auto-convert the file.",
+
+ $"File '{SlicerFile.Filename}' already exists",
+ ButtonEnum.YesNoCancel);
+
+ if (result is ButtonResult.Cancel or ButtonResult.Abort)
{
- convertedFile = SlicerFile.Convert(convertToFormat,
- Path.Combine(directory, $"{filename}.{convertFileExtension}"), 0,
- Progress);
- return true;
+ canConvert = false;
}
- catch (OperationCanceledException)
- { }
- catch (Exception exception)
+ else if (result == ButtonResult.No)
{
- Dispatcher.UIThread.InvokeAsync(async () =>
- await this.MessageBoxError(exception.ToString(),
- "Error while converting the file"));
+ var dialog = new SaveFileDialog
+ {
+ Directory = directory,
+ InitialFileName = filename,
+ DefaultExtension = $".{convertFileExtension}",
+ Filters = new List<FileDialogFilter>
+ {
+ new()
+ {
+ Name = fileExtension.Description,
+ Extensions = new List<string>{ convertFileExtension }
+ }
+ }
+ };
+
+ var saveResult = await dialog.ShowAsync(this);
+ if (string.IsNullOrWhiteSpace(saveResult))
+ {
+ canConvert = false;
+ }
+ else
+ {
+ outputFile = saveResult;
+ }
}
+ }
- return false;
- });
+ if (canConvert)
+ {
+ IsGUIEnabled = false;
+ ShowProgressWindow(
+ $"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {convertFileExtension}");
- IsGUIEnabled = true;
+ task = await Task.Factory.StartNew(() =>
+ {
+ try
+ {
+ convertedFile = SlicerFile.Convert(convertToFormat, outputFile, 0, Progress);
+ return true;
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ catch (Exception exception)
+ {
+ Dispatcher.UIThread.InvokeAsync(async () =>
+ await this.MessageBoxError(exception.ToString(),
+ "Error while converting the file"));
+ }
- if (task && convertedFile is not null)
- {
- SlicerFile = convertedFile;
- AddRecentFile(SlicerFile.FileFullPath);
+ return false;
+ });
+
+ IsGUIEnabled = true;
+
+ if (task && convertedFile is not null)
+ {
+ SlicerFile = convertedFile;
+ AddRecentFile(SlicerFile.FileFullPath);
+ }
}
}
}
@@ -1799,13 +1849,38 @@ namespace UVtools.WPF
}
}
- ResetDataContext();
+ if (mat is not null && mat.Size != SlicerFile.Resolution)
+ {
+ var result = await this.MessageBoxWaring($"Layer image resolution of {mat.Size} mismatch with printer resolution of {SlicerFile.Resolution}.\n" +
+ "1) Printing this file can lead to problems or malformed model, please verify your slicer printer settings;\n" +
+ "2) Processing this file with some of the tools can lead to program crash or misfunction;\n" +
+ "3) If you used PrusaSlicer to slice this file, you must use it with compatible UVtools printer profiles (Help - Install profiles into PrusaSlicer).\n\n" +
+ "Click 'Yes' to auto fix and set the file resolution with the layer resolution, but only use this option if you are sure it's ok to!\n" +
+ "Click 'No' to continue as it is and ignore this warning, you can still repair issues and use some of the tools.",
+ "File and layer resolution mismatch!", ButtonEnum.YesNo);
+ if(result == ButtonResult.Yes)
+ {
+ SlicerFile.Resolution = mat.Size;
+ RaisePropertyChanged(nameof(LayerResolutionStr));
+ }
+ }
- ForceUpdateActualLayer(actualLayer.Clamp(actualLayer, SliderMaximumValue));
+ if (SlicerFile.LayerHeight <= 0)
+ {
+ /*await this.MessageBoxWaring(
+ $"This file have a incorrect or not present layer height of {SlicerFile.LayerHeight}mm\n" +
+ $"It may not be required for some printers to work properly, but this information is crucial to {About.Software}.\n",
+ "Incorrect layer height detected");*/
- if (Settings.LayerPreview.ZoomToFitPrintVolumeBounds)
+ await new MissingInformationWindow().ShowDialog(this);
+ }
+
+ if (SlicerFile.LayerHeight <= 0)
{
- ZoomToFit();
+ await this.MessageBoxWaring(
+ $"This file have a incorrect or not present layer height of {SlicerFile.LayerHeight}mm\n" +
+ $"It may not be required for some printers to work properly, but this information is crucial to {About.Software}.\n",
+ "Incorrect layer height detected");
}
var display = SlicerFile.Display;
@@ -1830,20 +1905,13 @@ namespace UVtools.WPF
"Incorrect image ratio detected");
}
- if (mat is not null && mat.Size != SlicerFile.Resolution)
+ ResetDataContext();
+
+ ForceUpdateActualLayer(actualLayer.Clamp(actualLayer, SliderMaximumValue));
+
+ if (Settings.LayerPreview.ZoomToFitPrintVolumeBounds)
{
- var result = await this.MessageBoxWaring($"Layer image resolution of {mat.Size} mismatch with printer resolution of {SlicerFile.Resolution}.\n" +
- "1) Printing this file can lead to problems or malformed model, please verify your slicer printer settings;\n" +
- "2) Processing this file with some of the tools can lead to program crash or misfunction;\n" +
- "3) If you used PrusaSlicer to slice this file, you must use it with compatible UVtools printer profiles (Help - Install profiles into PrusaSlicer).\n\n" +
- "Click 'Yes' to auto fix and set the file resolution with the layer resolution, but only use this option if you are sure it's ok to!\n" +
- "Click 'No' to continue as it is and ignore this warning, you can still repair issues and use some of the tools.",
- "File and layer resolution mismatch!", ButtonEnum.YesNo);
- if(result == ButtonResult.Yes)
- {
- SlicerFile.Resolution = mat.Size;
- RaisePropertyChanged(nameof(LayerResolutionStr));
- }
+ ZoomToFit();
}
SlicerFile.IssueManager.CollectionChanged += (sender, e) =>
@@ -2166,7 +2234,11 @@ namespace UVtools.WPF
}
-#region Operations
+ #region Operations
+
+ public async Task<Operation> ShowRunOperation(Operation loadOperation) =>
+ await ShowRunOperation(loadOperation.GetType(), loadOperation);
+
public async Task<Operation> ShowRunOperation(Type type, Operation loadOperation = null)
{
var operation = await ShowOperation(type, loadOperation);
diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs
index c00ec07..bd608d7 100644
--- a/UVtools.WPF/Program.cs
+++ b/UVtools.WPF/Program.cs
@@ -1,7 +1,10 @@
using System;
using System.Diagnostics;
+using System.Globalization;
+using System.IO;
using System.Runtime.ExceptionServices;
using Avalonia;
+using UVtools.Core.FileFormats;
using UVtools.WPF.Extensions;
namespace UVtools.WPF
@@ -17,6 +20,9 @@ namespace UVtools.WPF
[STAThread]
public static void Main(string[] args)
{
+ CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
+ CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
+
ProgramStartupTime = Stopwatch.StartNew();
Args = args;
try
@@ -28,7 +34,7 @@ namespace UVtools.WPF
Console.WriteLine(e);
return;
}
-
+
/*Slicer slicer = new(Size.Empty, SizeF.Empty, "D:\\Cube100x100x100.stl");
var slices = slicer.SliceModel(0.05f);
diff --git a/UVtools.WPF/Structures/OperationProfiles.cs b/UVtools.WPF/Structures/OperationProfiles.cs
index e48d401..1fdbe3e 100644
--- a/UVtools.WPF/Structures/OperationProfiles.cs
+++ b/UVtools.WPF/Structures/OperationProfiles.cs
@@ -45,6 +45,7 @@ namespace UVtools.WPF.Structures
[XmlElement(typeof(OperationDynamicLifts))]
[XmlElement(typeof(OperationRaiseOnPrintFinish))]
[XmlElement(typeof(OperationChangeResolution))]
+ [XmlElement(typeof(OperationTimelapse))]
[XmlElement(typeof(OperationLayerExportGif))]
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index c03ce26..013498f 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.27.7</Version>
+ <Version>2.28.0</Version>
<Platforms>AnyCPU;x64</Platforms>
<PackageIcon>UVtools.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
@@ -45,7 +45,7 @@
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.12" />
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.5.5.4823" />
<PackageReference Include="MessageBox.Avalonia" Version="1.7.1" />
- <PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.11" />
+ <PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.12" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UVtools.AvaloniaControls\UVtools.AvaloniaControls.csproj" />
diff --git a/UVtools.WPF/Windows/MissingInformationWindow.axaml b/UVtools.WPF/Windows/MissingInformationWindow.axaml
new file mode 100644
index 0000000..3c134da
--- /dev/null
+++ b/UVtools.WPF/Windows/MissingInformationWindow.axaml
@@ -0,0 +1,107 @@
+<uc:WindowEx xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:uc="clr-namespace:UVtools.WPF.Controls"
+ mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
+ x:Class="UVtools.WPF.Windows.MissingInformationWindow"
+ CanResize="False"
+ SizeToContent="Height"
+ WindowStartupLocation="CenterOwner"
+ Width="500"
+ Icon="/Assets/Icons/UVtools.ico"
+ Title="Missing information on the file">
+ <StackPanel Orientation="Vertical" Spacing="10">
+ <Border Classes="Header">
+ <TextBox Classes="TransparentReadOnly"
+ Text="There are crucial information missing on this file in order to UVtools work properly and/or provide the correct information.
+&#x0a;Fill in the missing information only if you know the correct values, otherwise leave it unchanged if unsure.
+&#x0a;In most cases the missing information is not required by the printer.
+&#x0a;Note: Some fields may rebuild the layers information."/>
+ </Border>
+
+
+ <Border Margin="20,0">
+ <Grid RowDefinitions="Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,10,220,10,Auto">
+
+ <TextBlock Grid.Row="0" Grid.Column="0"
+ VerticalAlignment="Center"
+ IsVisible="{Binding LayerHeightIsVisible}"
+ Text="Layer height:"/>
+
+ <NumericUpDown Grid.Row="0" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mm"
+ IsVisible="{Binding LayerHeightIsVisible}"
+ Minimum="0"
+ Maximum="0.200"
+ Increment="0.01"
+ Value="{Binding LayerHeight}"/>
+
+ <TextBlock Grid.Row="0" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsVisible="{Binding LayerHeightIsVisible}"
+ Text="(Critical)"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="0"
+ VerticalAlignment="Center"
+ IsVisible="{Binding DisplayWidthIsVisible}"
+ Text="Display width:"/>
+
+ <NumericUpDown Grid.Row="2" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mm"
+ IsVisible="{Binding DisplayWidthIsVisible}"
+ Minimum="0"
+ Maximum="10000"
+ Increment="10"
+ Value="{Binding DisplayWidth}"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsVisible="{Binding DisplayWidthIsVisible}"
+ Text="(Regular)"/>
+
+ <TextBlock Grid.Row="4" Grid.Column="0"
+ VerticalAlignment="Center"
+ IsVisible="{Binding DisplayHeightIsVisible}"
+ Text="Display height:"/>
+
+ <NumericUpDown Grid.Row="4" Grid.Column="2"
+ Classes="ValueLabel ValueLabel_mm"
+ IsVisible="{Binding DisplayHeightIsVisible}"
+ Minimum="0"
+ Maximum="10000"
+ Increment="10"
+ Value="{Binding DisplayHeight}"/>
+
+ <TextBlock Grid.Row="4" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsVisible="{Binding DisplayHeightIsVisible}"
+ Text="(Regular)"/>
+
+ </Grid>
+ </Border>
+
+ <Border Classes="FooterActions">
+ <StackPanel Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
+ <Button Padding="10"
+ IsDefault="True"
+ Command="{Binding Apply}">
+ <StackPanel Orientation="Horizontal" Spacing="10">
+ <Image Source="/Assets/Icons/accept-16x16.png"/>
+ <TextBlock Text="Apply"/>
+ </StackPanel>
+ </Button>
+
+ <Button Padding="10"
+ IsCancel="True"
+ Command="{Binding Close}">
+ <StackPanel Orientation="Horizontal" Spacing="10">
+ <Image Source="/Assets/Icons/exit-16x16.png"/>
+ <TextBlock Text="Cancel"/>
+ </StackPanel>
+ </Button>
+ </StackPanel>
+ </Border>
+ </StackPanel>
+</uc:WindowEx>
diff --git a/UVtools.WPF/Windows/MissingInformationWindow.axaml.cs b/UVtools.WPF/Windows/MissingInformationWindow.axaml.cs
new file mode 100644
index 0000000..13dfcd7
--- /dev/null
+++ b/UVtools.WPF/Windows/MissingInformationWindow.axaml.cs
@@ -0,0 +1,93 @@
+/*
+ * 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 Avalonia;
+using Avalonia.Markup.Xaml;
+using MessageBox.Avalonia.Enums;
+using UVtools.WPF.Controls;
+using UVtools.WPF.Extensions;
+
+namespace UVtools.WPF.Windows
+{
+ public partial class MissingInformationWindow : WindowEx
+ {
+ #region Members
+ private decimal _layerHeight;
+ private decimal _displayWidth;
+ private decimal _displayHeight;
+ #endregion
+
+ #region Properties
+ public decimal LayerHeight
+ {
+ get => _layerHeight;
+ set => RaiseAndSetIfChanged(ref _layerHeight, value);
+ }
+
+ public bool LayerHeightIsVisible => SlicerFile?.LayerHeight <= 0;
+
+ public decimal DisplayWidth
+ {
+ get => _displayWidth;
+ set => RaiseAndSetIfChanged(ref _displayWidth, value);
+ }
+
+ public bool DisplayWidthIsVisible => SlicerFile?.DisplayWidth <= 0;
+
+ public decimal DisplayHeight
+ {
+ get => _displayHeight;
+ set => RaiseAndSetIfChanged(ref _displayHeight, value);
+ }
+
+ public bool DisplayHeightIsVisible => SlicerFile?.DisplayHeight <= 0;
+ #endregion
+
+ public MissingInformationWindow()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+ if (SlicerFile is not null)
+ {
+ _layerHeight = (decimal) SlicerFile.LayerHeight;
+ _displayWidth = (decimal) SlicerFile.DisplayWidth;
+ _displayHeight = (decimal) SlicerFile.DisplayHeight;
+ }
+
+ DataContext = this;
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public async void Apply()
+ {
+ if (await this.MessageBoxQuestion("Are you sure you want to submit and apply the information?", "Submit and apply the information?") != ButtonResult.Yes) return;
+
+ if ((decimal)SlicerFile.DisplayWidth != _displayWidth && _displayWidth > 0)
+ {
+ SlicerFile.DisplayWidth = (float) _displayWidth;
+ }
+ if ((decimal)SlicerFile.DisplayHeight != _displayHeight && _displayHeight > 0)
+ {
+ SlicerFile.DisplayHeight = (float)_displayHeight;
+ }
+ if ((decimal)SlicerFile.LayerHeight != _layerHeight && _layerHeight > 0)
+ {
+ SlicerFile.LayerHeight = (float)_layerHeight;
+ SlicerFile.LayerManager.RebuildLayersProperties();
+ }
+
+ DialogResult = DialogResults.OK;
+ Close();
+ }
+ }
+}
diff --git a/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml
index 0d5ec67..0663d04 100644
--- a/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml
+++ b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml
@@ -111,8 +111,7 @@
Spacing="5">
<Button Padding="10"
IsDefault="True"
- Command="{Binding InstallProfiles}"
- >
+ Command="{Binding InstallProfiles}">
<StackPanel Orientation="Horizontal" Spacing="10">
<Image Source="/Assets/Icons/accept-16x16.png"/>
<TextBlock Text="Install selected profiles"/>
diff --git a/build/WingetPublish.ps1 b/build/WingetPublish.ps1
index 7e6bbe3..661ad43 100644
--- a/build/WingetPublish.ps1
+++ b/build/WingetPublish.ps1
@@ -62,8 +62,8 @@ $actionInput = Read-Host -Prompt "Do you want to update the manifest with the cu
if($actionInput -eq "y" || $actionInput -eq "yes")
{
Remove-Item $outputFolder -Recurse -ErrorAction Ignore # Clean
- #wingetcreate.exe update PTRTECH.UVtools
- wingetcreate.exe update PTRTECH.UVtools --urls $msiUrl --version $version --token $wingetTokenKeyFile --submit
+ #wingetcreate.exe update PTRTECH.UVtools --interactive
+ wingetcreate.exe update PTRTECH.UVtools --urls "$msiUrl|x64" --version $version --token $wingetTokenKeyFile --submit
Remove-Item $outputFolder -Recurse -ErrorAction Ignore # Clean
}