diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2022-02-13 05:15:34 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2022-02-13 05:15:34 +0300 |
commit | 554794d0d8408974e45d6dd730adb38acee7434a (patch) | |
tree | 93df72ae93faff1869ba39cd4cb6fcc807ed8edb | |
parent | d9ebd966835acc61fb021846221add26de552799 (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)
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:** @@ -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 @@ -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 Binary files differindex eb8bdfc..7b8afcf 100644 --- a/UVtools.Platforms/osx-x64/libcvextern.dylib +++ b/UVtools.Platforms/osx-x64/libcvextern.dylib 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 Binary files differnew file mode 100644 index 0000000..423499c --- /dev/null +++ b/UVtools.WPF/Assets/Icons/camera-16x16.png 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. + 
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. + 
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. + 
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. + 
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. + 
This can be used to trigger an external light sensor. + 
Use the lowest as possible but enough to sensor to trigger. + 
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. + 
Global exposure time will be used instead. + 
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). +
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). +
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. +
Fill in the missing information only if you know the correct values, otherwise leave it unchanged if unsure. +
In most cases the missing information is not required by the printer. +
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 } |