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

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2022-05-16 03:25:21 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2022-05-16 03:25:21 +0300
commitc800f887d2d50f9f34500c323f5cc8c750ef258b (patch)
treea15179000ac953154ef8b32f8152c2a6a796f427
parent51e4f329879298e589ce7f5cc1e0cd2db64c5394 (diff)
v3.4.2v3.4.2
- **Core:** - (Add) Getter `FileFormat.DisplayPixelCount` Gets the display total number of pixels (ResolutionX * ResolutionY) - (Add) Getter `Layer.NonZeroPixelRatio` Gets the ratio between non zero pixels and display number of pixels - (Add) Getter `Layer.NonZeroPixelPercentage` Gets the percentage of non zero pixels relative to the display number of pixels - (Add) Getter `Layer.PreviousHeightLayer()` Gets the previous layer with a different height from the current, returns null if no previous layer - (Add) Getter `Layer.NextHeightLayer()` Gets the next layer with a different height from the current, returns null if no next layer - (Add) Method `Layer.GetPreviousLayerWithAtLeastPixelCountOf()` Gets the previous layer matching at least a number of pixels, returns null if no previous layer - (Add) Method `Layer.GetNextLayerWithAtLeastPixelCountOf()` Gets the next layer matching at least a number of pixels, returns null if no next layer - (Add) Method `Operation.GetRoiOrVolumeBounds()` returns the selected ROI rectangle or model volume bounds rectangle - (Add) Documentation around `Operation` methods - (Fix) Open files in partial mode when the resolution is not defined would cause a `NullPointerException` (#474) - **Suggestion: Wait time before cure** - (Add) Proportional maximum time change: Sets the maximum allowed time difference relative to the previous layer (#471) - (Add) Proportional mass get modes: Previous, Average and Maximum relative to a defined height (#471) - (Change) Proportional set type sets fallback time to the first layer - (Fix) Proportional set type was taking current layer mass instead of looking to the previous cured layer (#471) - **Tools:** - **Edit print parameters:** - (Change) Incorporate the unit label into the numeric input box - (Change) Allow TSMC speeds to be 0 as minimum value (#472) - (Fix) PCB Exposure: The thumbnail has random noise around the image - **Settings:** - (Add) Tools: "Always prompt for confirmation before execute the operation" - (Fix) Changing layer compression method when no file is loaded would cause a error - **UI:** - (Add) Holding Shift key while drag and drop a .uvtop file will try to execute the operation without showing the window or prompt - (Add) Drag and drop a .cs or .csx file into UVtools will load and show the scripting dialog with the file selected - (Add) Errors that crash application will now show an report window with the crash information and able to fast report them - (Add) "Version" key and value on registry to tell the current installed version (Windows MSI only) - (Upgrade) AvaloniaUI from 0.10.13 to 0.10.14 - (Upgrade) .NET from 6.0.4 to 6.0.5
-rw-r--r--CHANGELOG.md34
-rw-r--r--RELEASE_NOTES.md34
-rw-r--r--UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj2
-rw-r--r--UVtools.Core/Extensions/EmguExtensions.cs2
-rw-r--r--UVtools.Core/FileFormats/CWSFile.cs1
-rw-r--r--UVtools.Core/FileFormats/FileFormat.cs31
-rw-r--r--UVtools.Core/FileFormats/PhotonWorkshopFile.cs16
-rw-r--r--UVtools.Core/Layers/Layer.cs89
-rw-r--r--UVtools.Core/Operations/Operation.cs222
-rw-r--r--UVtools.Core/Operations/OperationPCBExposure.cs6
-rw-r--r--UVtools.Core/Operations/OperationScripting.cs3
-rw-r--r--UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs171
-rw-r--r--UVtools.Core/SystemOS/SystemAware.cs10
-rw-r--r--UVtools.Core/UVtools.Core.csproj6
-rw-r--r--UVtools.Installer/Code/Product.wxs1
-rw-r--r--UVtools.InstallerMM/UVtools.InstallerMM.wxs6
-rw-r--r--UVtools.WPF/App.axaml.cs142
-rw-r--r--UVtools.WPF/ConsoleArguments.cs2
-rw-r--r--UVtools.WPF/Controls/Suggestions/SuggestionWaitTimeBeforeCureControl.axaml114
-rw-r--r--UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml8
-rw-r--r--UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs22
-rw-r--r--UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs2
-rw-r--r--UVtools.WPF/Controls/WindowEx.cs66
-rw-r--r--UVtools.WPF/ErrorLog.cs1
-rw-r--r--UVtools.WPF/Extensions/WindowExtensions.cs2
-rw-r--r--UVtools.WPF/MainWindow.LayerPreview.cs3
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs65
-rw-r--r--UVtools.WPF/Program.cs63
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj12
-rw-r--r--UVtools.WPF/UserSettings.cs8
-rw-r--r--UVtools.WPF/Windows/AboutWindow.axaml.cs18
-rw-r--r--UVtools.WPF/Windows/CantRunWindow.axaml4
-rw-r--r--UVtools.WPF/Windows/CantRunWindow.axaml.cs7
-rw-r--r--UVtools.WPF/Windows/MessageWindow.axaml90
-rw-r--r--UVtools.WPF/Windows/MessageWindow.axaml.cs141
-rw-r--r--UVtools.WPF/Windows/SettingsWindow.axaml3
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml.cs5
37 files changed, 1196 insertions, 216 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1797a49..06bbe51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,39 @@
# Changelog
+## 16/05/2022 - v3.4.2
+
+- **Core:**
+ - (Add) Getter `FileFormat.DisplayPixelCount` Gets the display total number of pixels (ResolutionX * ResolutionY)
+ - (Add) Getter `Layer.NonZeroPixelRatio` Gets the ratio between non zero pixels and display number of pixels
+ - (Add) Getter `Layer.NonZeroPixelPercentage` Gets the percentage of non zero pixels relative to the display number of pixels
+ - (Add) Getter `Layer.PreviousHeightLayer()` Gets the previous layer with a different height from the current, returns null if no previous layer
+ - (Add) Getter `Layer.NextHeightLayer()` Gets the next layer with a different height from the current, returns null if no next layer
+ - (Add) Method `Layer.GetPreviousLayerWithAtLeastPixelCountOf()` Gets the previous layer matching at least a number of pixels, returns null if no previous layer
+ - (Add) Method `Layer.GetNextLayerWithAtLeastPixelCountOf()` Gets the next layer matching at least a number of pixels, returns null if no next layer
+ - (Add) Method `Operation.GetRoiOrVolumeBounds()` returns the selected ROI rectangle or model volume bounds rectangle
+ - (Add) Documentation around `Operation` methods
+ - (Fix) Open files in partial mode when the resolution is not defined would cause a `NullPointerException` (#474)
+- **Suggestion: Wait time before cure**
+ - (Add) Proportional maximum time change: Sets the maximum allowed time difference relative to the previous layer (#471)
+ - (Add) Proportional mass get modes: Previous, Average and Maximum relative to a defined height (#471)
+ - (Change) Proportional set type sets fallback time to the first layer
+ - (Fix) Proportional set type was taking current layer mass instead of looking to the previous cured layer (#471)
+- **Tools:**
+ - **Edit print parameters:**
+ - (Change) Incorporate the unit label into the numeric input box
+ - (Change) Allow TSMC speeds to be 0 as minimum value (#472)
+ - (Fix) PCB Exposure: The thumbnail has random noise around the image
+- **Settings:**
+ - (Add) Tools: "Always prompt for confirmation before execute the operation"
+ - (Fix) Changing layer compression method when no file is loaded would cause a error
+- **UI:**
+ - (Add) Holding Shift key while drag and drop a .uvtop file will try to execute the operation without showing the window or prompt
+ - (Add) Drag and drop a .cs or .csx file into UVtools will load and show the scripting dialog with the file selected
+- (Add) Errors that crash application will now show an report window with the crash information and able to fast report them
+- (Add) "Version" key and value on registry to tell the current installed version (Windows MSI only)
+- (Upgrade) AvaloniaUI from 0.10.13 to 0.10.14
+- (Upgrade) .NET from 6.0.4 to 6.0.5
+-
## 02/05/2022 - v3.4.1
- (Add) Suggestion - Wait time before cure: Allow to set the number of layers to smooth transition from bottom to normal wait time (Defaults to 8)
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 516ea10..d889774 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,4 +1,32 @@
-- (Add) Suggestion - Wait time before cure: Allow to set the number of layers to smooth transition from bottom to normal wait time (Defaults to 8)
-- (Fix) Tool - PCB Exposure: Pixels per millimeter was been set to fixed value instead of use printer lcd pitch, causing wrong dimentions on different from 50µm pitch
-- (Fix) Tool - PCB Exposure: Unable to run the tool when the display size information isn't available
+- **Core:**
+ - (Add) Getter `FileFormat.DisplayPixelCount` Gets the display total number of pixels (ResolutionX * ResolutionY)
+ - (Add) Getter `Layer.NonZeroPixelRatio` Gets the ratio between non zero pixels and display number of pixels
+ - (Add) Getter `Layer.NonZeroPixelPercentage` Gets the percentage of non zero pixels relative to the display number of pixels
+ - (Add) Getter `Layer.PreviousHeightLayer()` Gets the previous layer with a different height from the current, returns null if no previous layer
+ - (Add) Getter `Layer.NextHeightLayer()` Gets the next layer with a different height from the current, returns null if no next layer
+ - (Add) Method `Layer.GetPreviousLayerWithAtLeastPixelCountOf()` Gets the previous layer matching at least a number of pixels, returns null if no previous layer
+ - (Add) Method `Layer.GetNextLayerWithAtLeastPixelCountOf()` Gets the next layer matching at least a number of pixels, returns null if no next layer
+ - (Add) Method `Operation.GetRoiOrVolumeBounds()` returns the selected ROI rectangle or model volume bounds rectangle
+ - (Add) Documentation around `Operation` methods
+ - (Fix) Open files in partial mode when the resolution is not defined would cause a `NullPointerException` (#474)
+- **Suggestion: Wait time before cure**
+ - (Add) Proportional maximum time change: Sets the maximum allowed time difference relative to the previous layer (#471)
+ - (Add) Proportional mass get modes: Previous, Average and Maximum relative to a defined height (#471)
+ - (Change) Proportional set type sets fallback time to the first layer
+ - (Fix) Proportional set type was taking current layer mass instead of looking to the previous cured layer (#471)
+- **Tools:**
+ - **Edit print parameters:**
+ - (Change) Incorporate the unit label into the numeric input box
+ - (Change) Allow TSMC speeds to be 0 as minimum value (#472)
+ - (Fix) PCB Exposure: The thumbnail has random noise around the image
+- **Settings:**
+ - (Add) Tools: "Always prompt for confirmation before execute the operation"
+ - (Fix) Changing layer compression method when no file is loaded would cause a error
+- **UI:**
+ - (Add) Holding Shift key while drag and drop a .uvtop file will try to execute the operation without showing the window or prompt
+ - (Add) Drag and drop a .cs or .csx file into UVtools will load and show the scripting dialog with the file selected
+- (Add) Errors that crash application will now show an report window with the crash information and able to fast report them
+- (Upgrade) AvaloniaUI from 0.10.13 to 0.10.14
+- (Upgrade) .NET from 6.0.4 to 6.0.5
+-
diff --git a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
index a3f8722..45106a3 100644
--- a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
+++ b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj
@@ -38,7 +38,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Avalonia" Version="0.10.13" />
+ <PackageReference Include="Avalonia" Version="0.10.14" />
</ItemGroup>
<ItemGroup>
diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs
index 5c506ae..833fd8d 100644
--- a/UVtools.Core/Extensions/EmguExtensions.cs
+++ b/UVtools.Core/Extensions/EmguExtensions.cs
@@ -489,7 +489,7 @@ public static class EmguExtensions
using var roi = src.Size == rect.Size ? src.Roi(src.Size) : src.Roi(rect);
var numberOfChannels = roi.NumberOfChannels;
- var cropped = new Mat(roi.Rows + margin.Height * 2, roi.Cols + margin.Width * 2, roi.Depth, numberOfChannels);
+ var cropped = InitMat(new Size(roi.Width + margin.Width * 2, roi.Height + margin.Height * 2), numberOfChannels, roi.Depth);
using var dest = new Mat(cropped, new Rectangle(margin.Width, margin.Height, roi.Width, roi.Height));
roi.CopyTo(dest);
diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs
index 8fb5f6a..b32d673 100644
--- a/UVtools.Core/FileFormats/CWSFile.cs
+++ b/UVtools.Core/FileFormats/CWSFile.cs
@@ -18,7 +18,6 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
-using System.Threading.Tasks;
using System.Xml.Serialization;
using UVtools.Core.Converters;
using UVtools.Core.Extensions;
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index 69ec05a..b62099d 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -172,7 +172,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
#region Instances
public static PrintParameterModifier PositionZ { get; } = new ("Position Z", "Absolute Z position", "mm", 0, 100000, 0.01, Layer.HeightPrecision);
public static PrintParameterModifier BottomLayerCount { get; } = new ("Bottom layer count", "Number of bottom/burn-in layers", "layers", 0, ushort.MaxValue, 1, 0);
- public static PrintParameterModifier TransitionLayerCount { get; } = new ("Transition layer count", "Number of transition layers", "layers",0, ushort.MaxValue, 1, 0);
+ public static PrintParameterModifier TransitionLayerCount { get; } = new ("Transition layer count", "Number of fade/transition layers", "layers",0, ushort.MaxValue, 1, 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");
public static PrintParameterModifier LightOffDelay { get; } = new("Light-off seconds", "Total motor movement time + rest time to wait before cure a new layer", "s");
@@ -180,8 +180,8 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
public static PrintParameterModifier BottomWaitTimeBeforeCure { get; } = new ("Bottom wait before cure", "Time to wait/rest before cure a new bottom layer\nChitubox: Rest after retract\nLychee: Wait before print", "s");
public static PrintParameterModifier WaitTimeBeforeCure { get; } = new ("Wait before cure", "Time to wait/rest before cure a new layer\nChitubox: Rest after retract\nLychee: Wait before print", "s");
- public static PrintParameterModifier BottomExposureTime { get; } = new ("Bottom exposure time", "Bottom layers cure time", "s", 0.1M);
- public static PrintParameterModifier ExposureTime { get; } = new ("Exposure time", "Layers cure time", "s", 0.1M);
+ public static PrintParameterModifier BottomExposureTime { get; } = new ("Bottom exposure time", "Bottom layers exposure time", "s", 0.1M);
+ public static PrintParameterModifier ExposureTime { get; } = new ("Exposure time", "Normal layers exposure time", "s", 0.1M);
public static PrintParameterModifier BottomWaitTimeAfterCure { get; } = new("Bottom wait after cure", "Time to wait/rest after cure a new bottom layer\nChitubox: Rest before lift\nLychee: Wait after print", "s");
public static PrintParameterModifier WaitTimeAfterCure { get; } = new("Wait after cure", "Time to wait/rest after cure a new bottom layer\nChitubox: Rest before lift\nLychee: Wait after print", "s");
@@ -189,14 +189,14 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
public static PrintParameterModifier BottomLiftHeight { get; } = new ("Bottom lift height", "Bottom lift/peel height between layers", "mm");
public static PrintParameterModifier LiftHeight { get; } = new ("Lift height", @"Lift/peel height between layers", "mm");
- public static PrintParameterModifier BottomLiftSpeed { get; } = new ("Bottom lift speed", null, "mm/min", 10, 5000, 5);
- public static PrintParameterModifier LiftSpeed { get; } = new ("Lift speed", null, "mm/min", 10, 5000, 5);
+ public static PrintParameterModifier BottomLiftSpeed { get; } = new ("Bottom lift speed", "Lift speed of bottom layers", "mm/min", 10, 5000, 5);
+ public static PrintParameterModifier LiftSpeed { get; } = new ("Lift speed", "Lift speed of normal layers", "mm/min", 10, 5000, 5);
public static PrintParameterModifier BottomLiftHeight2 { get; } = new("2) Bottom lift height", "Bottom second lift/peel height between layers", "mm");
public static PrintParameterModifier LiftHeight2 { get; } = new("2) Lift height", @"Second lift/peel height between layers", "mm");
- public static PrintParameterModifier BottomLiftSpeed2 { get; } = new("2) Bottom lift speed", null, "mm/min", 10, 5000, 5);
- public static PrintParameterModifier LiftSpeed2 { get; } = new("2) Lift speed", null, "mm/min", 10, 5000, 5);
+ public static PrintParameterModifier BottomLiftSpeed2 { get; } = new("2) Bottom lift speed", "Lift speed of bottom layers for the second lift sequence (TSMC)", "mm/min", 0, 5000, 5);
+ public static PrintParameterModifier LiftSpeed2 { get; } = new("2) Lift speed", "Lift speed of normal layers for the second lift sequence (TSMC)", "mm/min", 0, 5000, 5);
public static PrintParameterModifier BottomWaitTimeAfterLift { get; } = new("Bottom wait after lift", "Time to wait/rest after a lift/peel sequence at bottom layers\nChitubox: Rest after lift\nLychee: Wait after lift", "s");
public static PrintParameterModifier WaitTimeAfterLift { get; } = new("Wait after lift", "Time to wait/rest after a lift/peel sequence at layers\nChitubox: Rest after lift\nLychee: Wait after lift", "s");
@@ -204,10 +204,10 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
public static PrintParameterModifier BottomRetractSpeed { get; } = new ("Bottom retract speed", "Bottom down speed from lift height to next layer cure position", "mm/min", 10, 5000, 5);
public static PrintParameterModifier RetractSpeed { get; } = new ("Retract speed", "Down speed from lift height to next layer cure position", "mm/min", 10, 5000, 5);
- public static PrintParameterModifier BottomRetractHeight2 { get; } = new("2) Bottom retract height", null, "mm");
- public static PrintParameterModifier RetractHeight2 { get; } = new("2) Retract height", null, "mm");
- public static PrintParameterModifier BottomRetractSpeed2 { get; } = new("2) Bottom retract speed", null, "mm/min", 10, 5000, 5);
- public static PrintParameterModifier RetractSpeed2 { get; } = new("2) Retract speed", null, "mm/min", 10, 5000, 5);
+ public static PrintParameterModifier BottomRetractHeight2 { get; } = new("2) Bottom retract height", "Slow retract height of bottom layers (TSMC)", "mm");
+ public static PrintParameterModifier RetractHeight2 { get; } = new("2) Retract height", "Slow retract height of normal layers (TSMC)", "mm");
+ public static PrintParameterModifier BottomRetractSpeed2 { get; } = new("2) Bottom retract speed", "Slow retract speed of bottom layers (TSMC)", "mm/min", 0, 5000, 5);
+ public static PrintParameterModifier RetractSpeed2 { get; } = new("2) Retract speed", "Slow retract speed of normal layers (TSMC)", "mm/min", 0, 5000, 5);
public static PrintParameterModifier BottomLightPWM { get; } = new ("Bottom light PWM", "UV LED power for bottom layers", "☀", 1, byte.MaxValue, 5, 0);
public static PrintParameterModifier LightPWM { get; } = new ("Light PWM", "UV LED power for layers", "☀", 1, byte.MaxValue, 5, 0);
@@ -1428,7 +1428,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
}
/// <summary>
- /// Gets the bounding rectangle of the object
+ /// Gets the bounding rectangle of the model
/// </summary>
public Rectangle BoundingRectangle
{
@@ -1493,6 +1493,11 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
public abstract uint ResolutionY { get; set; }
/// <summary>
+ /// Gets the display total number of pixels (<see cref="ResolutionX"/> * <see cref="ResolutionY"/>)
+ /// </summary>
+ public uint DisplayPixelCount => ResolutionX * ResolutionY;
+
+ /// <summary>
/// Gets the size of display in millimeters
/// </summary>
public SizeF Display
@@ -5320,7 +5325,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor
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 (ResolutionX == 0 || ResolutionY == 0)
+ if ((ResolutionX == 0 || ResolutionY == 0) && DecodeType == FileDecodeType.Full)
{
var layer = FirstLayer;
if (layer is not null)
diff --git a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
index 43ab3a4..8ad7e92 100644
--- a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
+++ b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs
@@ -623,7 +623,7 @@ public class PhotonWorkshopFile : FileFormat
if (pixel > imageLength)
{
image.Dispose();
- throw new FileLoadException("Error image ran off the end");
+ throw new FileLoadException("Image ran off the end.");
}
}
}
@@ -763,6 +763,12 @@ public class PhotonWorkshopFile : FileFormat
//color &= 0xff;
+ if (pixelPos + repeat > imageLength)
+ {
+ mat.Dispose();
+ throw new FileLoadException($"Image ran off the end: {pixelPos}+ {repeat} = {pixelPos + repeat}, expecting: {imageLength}");
+ }
+
// We only need to set the non-zero pixels
mat.FillSpan(ref pixelPos, repeat, color);
@@ -772,18 +778,12 @@ public class PhotonWorkshopFile : FileFormat
//i++;
break;
}
-
- if (pixelPos > imageLength)
- {
- mat.Dispose();
- throw new FileLoadException($"Error image ran off the end: {pixelPos - repeat}({repeat}) of {imageLength}");
- }
}
if (pixelPos > 0 && pixelPos != imageLength)
{
mat.Dispose();
- throw new FileLoadException($"Error image ended short: {pixelPos} of {imageLength}");
+ throw new FileLoadException($"Image ended short: {pixelPos}, expecting: {imageLength}");
}
return mat;
diff --git a/UVtools.Core/Layers/Layer.cs b/UVtools.Core/Layers/Layer.cs
index e0b5683..6493a53 100644
--- a/UVtools.Core/Layers/Layer.cs
+++ b/UVtools.Core/Layers/Layer.cs
@@ -98,6 +98,8 @@ public class Layer : BindableBase, IEquatable<Layer>, IEquatable<uint>
internal set
{
if (!RaiseAndSetIfChanged(ref _nonZeroPixelCount, value)) return;
+ RaisePropertyChanged(nameof(NonZeroPixelRatio));
+ RaisePropertyChanged(nameof(NonZeroPixelPercentage));
RaisePropertyChanged(nameof(Area));
RaisePropertyChanged(nameof(Volume));
MaterialMilliliters = -1; // Recalculate
@@ -105,6 +107,32 @@ public class Layer : BindableBase, IEquatable<Layer>, IEquatable<uint>
}
/// <summary>
+ /// Gets the ratio between non zero pixels and display number of pixels
+ /// </summary>
+ public double NonZeroPixelRatio
+ {
+ get
+ {
+ var displayPixelCount = SlicerFile.DisplayPixelCount;
+ if (displayPixelCount == 0) return double.NaN;
+ return (double)_nonZeroPixelCount / displayPixelCount;
+ }
+ }
+
+ /// <summary>
+ /// Gets the percentage of non zero pixels relative to the display number of pixels
+ /// </summary>
+ public double NonZeroPixelPercentage
+ {
+ get
+ {
+ var pixelRatio = NonZeroPixelRatio;
+ if (double.IsNaN(pixelRatio)) return double.NaN;
+ return pixelRatio * 100.0;
+ }
+ }
+
+ /// <summary>
/// Gets if this layer is empty/all black pixels
/// </summary>
public bool IsEmpty => _nonZeroPixelCount == 0;
@@ -214,7 +242,37 @@ public class Layer : BindableBase, IEquatable<Layer>, IEquatable<uint>
if (IsFirstLayer || _index > SlicerFile.Count) return null;
return SlicerFile[_index - 1];
}
+ }
+
+ /// <summary>
+ /// Gets the previous layer with a different height from the current, returns null if no previous layer
+ /// </summary>
+ public Layer? PreviousHeightLayer
+ {
+ get
+ {
+ if (IsFirstLayer || _index > SlicerFile.Count) return null;
+ for (int i = (int)_index - 1; i >= 0; i--)
+ {
+ if (SlicerFile[i].PositionZ < _positionZ) return SlicerFile[i];
+ }
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the previous layer matching at least <param name="numberOfPixels"/> pixels, returns null if no previous layer
+ /// </summary>
+ public Layer? GetPreviousLayerWithAtLeastPixelCountOf(uint numberOfPixels)
+ {
+ if (IsFirstLayer || _index > SlicerFile.Count) return null;
+ for (int i = (int)_index - 1; i >= 0; i--)
+ {
+ if (SlicerFile[i].NonZeroPixelCount >= numberOfPixels) return SlicerFile[i];
+ }
+
+ return null;
}
/// <summary>
@@ -230,6 +288,37 @@ public class Layer : BindableBase, IEquatable<Layer>, IEquatable<uint>
}
/// <summary>
+ /// Gets the next layer with a different height from the current, returns null if no next layer
+ /// </summary>
+ public Layer? NextHeightLayer
+ {
+ get
+ {
+ if (_index >= SlicerFile.LastLayerIndex) return null;
+ for (var i = _index + 1; i < SlicerFile.LayerCount; i++)
+ {
+ if (SlicerFile[i].PositionZ > _positionZ) return SlicerFile[i];
+ }
+
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the next layer matching at least <param name="numberOfPixels"/> pixels, returns null if no next layer
+ /// </summary>
+ public Layer? GetNextLayerWithAtLeastPixelCountOf(uint numberOfPixels)
+ {
+ if (_index >= SlicerFile.LastLayerIndex) return null;
+ for (var i = _index + 1; i < SlicerFile.LayerCount; i++)
+ {
+ if (SlicerFile[i].NonZeroPixelCount >= numberOfPixels) return SlicerFile[i];
+ }
+
+ return null;
+ }
+
+ /// <summary>
/// Gets the layer index
/// </summary>
public uint Index
diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs
index 0bafa08..d1b50b4 100644
--- a/UVtools.Core/Operations/Operation.cs
+++ b/UVtools.Core/Operations/Operation.cs
@@ -66,6 +66,9 @@ public abstract class Operation : BindableBase, IDisposable
set => RaiseAndSetIfChanged(ref _importedFrom, value);
}
+ /// <summary>
+ /// Gets or sets the parent <see cref="FileFormat"/>
+ /// </summary>
[XmlIgnore]
public FileFormat SlicerFile
{
@@ -78,6 +81,9 @@ public abstract class Operation : BindableBase, IDisposable
}
}
+ /// <summary>
+ /// Gets the bounding rectangle of the model, preserved from any change during and after execution
+ /// </summary>
[XmlIgnore]
public Rectangle OriginalBoundingRectangle
{
@@ -85,6 +91,9 @@ public abstract class Operation : BindableBase, IDisposable
private set => RaiseAndSetIfChanged(ref _originalBoundingRectangle, value);
}
+ /// <summary>
+ /// Gets or sets any object which is not used internally
+ /// </summary>
[XmlIgnore]
public object? Tag { get; set; }
@@ -93,6 +102,9 @@ public abstract class Operation : BindableBase, IDisposable
/// </summary>
public string Id => GetType().Name.Remove(0, ClassNameLength);
+ /// <summary>
+ /// Gets the starting layer selection
+ /// </summary>
public virtual LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.All;
/// <summary>
@@ -104,6 +116,9 @@ public abstract class Operation : BindableBase, IDisposable
set => RaiseAndSetIfChanged(ref _layerRangeSelection, value);
}
+ /// <summary>
+ /// Gets a string representing the layer range, used with profiles
+ /// </summary>
public virtual string LayerRangeString
{
get
@@ -228,9 +243,19 @@ public abstract class Operation : BindableBase, IDisposable
}
}
+ /// <summary>
+ /// Gets if any bottom layer is included in the selected layer range
+ /// </summary>
public bool LayerRangeHaveBottoms => LayerIndexStart < (SlicerFile.FirstNormalLayer?.Index ?? 0);
+
+ /// <summary>
+ /// Gets if any normal layer is included in the selected layer range
+ /// </summary>
public bool LayerRangeHaveNormals => LayerIndexEnd >= (SlicerFile.FirstNormalLayer?.Index ?? 0);
+ /// <summary>
+ /// Gets the number of selected layers
+ /// </summary>
public uint LayerRangeCount => (uint)Math.Max(0, (int)LayerIndexEnd - LayerIndexStart + 1);
/// <summary>
@@ -242,6 +267,9 @@ public abstract class Operation : BindableBase, IDisposable
set => RaiseAndSetIfChanged(ref _profileName, value);
}
+ /// <summary>
+ /// Gets if this profile is the default to load
+ /// </summary>
public bool ProfileIsDefault
{
get => _profileIsDefault;
@@ -262,6 +290,9 @@ public abstract class Operation : BindableBase, IDisposable
}
}
+ /// <summary>
+ /// Gets if there is an ROI associated
+ /// </summary>
public bool HaveROI => !ROI.IsEmpty;
/// <summary>
@@ -279,8 +310,14 @@ public abstract class Operation : BindableBase, IDisposable
}
}
+ /// <summary>
+ /// Gets if there is masks associated
+ /// </summary>
public bool HaveMask => _maskPoints is not null && _maskPoints.Length > 0;
+ /// <summary>
+ /// Gets if there is roi or masks associated
+ /// </summary>
public bool HaveROIorMask => HaveROI || HaveMask;
/// <summary>
@@ -310,6 +347,9 @@ public abstract class Operation : BindableBase, IDisposable
#region Methods
+ /// <summary>
+ /// Gets if the operation can spawn
+ /// </summary>
public bool CanSpawn => string.IsNullOrWhiteSpace(ValidateSpawn());
/// <summary>
@@ -326,6 +366,10 @@ public abstract class Operation : BindableBase, IDisposable
return string.IsNullOrWhiteSpace(message);
}
+ /// <summary>
+ /// Validates the operation, return null or empty if validates
+ /// </summary>
+ /// <returns></returns>
public virtual string? ValidateInternally()
{
if (!ValidateSpawn(out var message))
@@ -345,11 +389,18 @@ public abstract class Operation : BindableBase, IDisposable
return ValidateInternally();
}
+ /// <summary>
+ /// Gets if the operation is able to execute
+ /// </summary>
+ /// <returns></returns>
public bool CanValidate()
{
return string.IsNullOrWhiteSpace(Validate());
}
+ /// <summary>
+ /// Selects all layers from first to last layer
+ /// </summary>
public void SelectAllLayers()
{
LayerIndexStart = 0;
@@ -357,12 +408,19 @@ public abstract class Operation : BindableBase, IDisposable
LayerRangeSelection = LayerRangeSelection.All;
}
+ /// <summary>
+ /// Selects one layer
+ /// </summary>
+ /// <param name="layerIndex">Layer index to select</param>
public void SelectCurrentLayer(uint layerIndex)
{
LayerIndexStart = LayerIndexEnd = layerIndex;
LayerRangeSelection = LayerRangeSelection.Current;
}
+ /// <summary>
+ /// Selects all bottom layers
+ /// </summary>
public void SelectBottomLayers()
{
LayerIndexStart = 0;
@@ -370,6 +428,9 @@ public abstract class Operation : BindableBase, IDisposable
LayerRangeSelection = LayerRangeSelection.Bottom;
}
+ /// <summary>
+ /// Selects all normal layers
+ /// </summary>
public void SelectNormalLayers()
{
LayerIndexStart = SlicerFile.FirstNormalLayer?.Index ?? 0;
@@ -377,30 +438,49 @@ public abstract class Operation : BindableBase, IDisposable
LayerRangeSelection = LayerRangeSelection.Normal;
}
+ /// <summary>
+ /// Select the first layer (0)
+ /// </summary>
public void SelectFirstLayer()
{
LayerIndexStart = LayerIndexEnd = 0;
LayerRangeSelection = LayerRangeSelection.First;
}
+ /// <summary>
+ /// Select the last layer
+ /// </summary>
public void SelectLastLayer()
{
LayerIndexStart = LayerIndexEnd = SlicerFile.LastLayerIndex;
LayerRangeSelection = LayerRangeSelection.Last;
}
+ /// <summary>
+ /// Selects from first to a layer index
+ /// </summary>
+ /// <param name="currentLayerIndex">To layer index to select</param>
public void SelectFirstToCurrentLayer(uint currentLayerIndex)
{
LayerIndexStart = 0;
LayerIndexEnd = Math.Min(currentLayerIndex, SlicerFile.LastLayerIndex);
}
+ /// <summary>
+ /// Selects from a layer index to the last layer
+ /// </summary>
+ /// <param name="currentLayerIndex">From layer index to select</param>
public void SelectCurrentToLastLayer(uint currentLayerIndex)
{
LayerIndexStart = Math.Min(currentLayerIndex, SlicerFile.LastLayerIndex);
LayerIndexEnd = SlicerFile.LastLayerIndex;
}
+ /// <summary>
+ /// Selects layer given a range type
+ /// </summary>
+ /// <param name="range"></param>
+ /// <exception cref="NotImplementedException"></exception>
public void SelectLayers(LayerRangeSelection range)
{
switch (range)
@@ -436,67 +516,141 @@ public abstract class Operation : BindableBase, IDisposable
/// </summary>
public virtual void InitWithSlicerFile() { }
+ /// <summary>
+ /// Clears the ROI and set to empty
+ /// </summary>
public void ClearROI()
{
ROI = Rectangle.Empty;
}
+ /// <summary>
+ /// Clear <see cref="ROI"/> and <see cref="MaskPoints"/>
+ /// </summary>
public void ClearROIandMasks()
{
ClearROI();
ClearMasks();
}
+ /// <summary>
+ /// Set <see cref="ROI"/> only if not set already
+ /// </summary>
+ /// <param name="roi">ROI to set</param>
public void SetROIIfEmpty(Rectangle roi)
{
if (HaveROI) return;
ROI = roi;
}
+ /// <summary>
+ /// Gets the <see cref="ROI"/> size, but if empty returns the file resolution size instead
+ /// </summary>
+ /// <returns></returns>
public Size GetRoiSizeOrDefault() => GetRoiSizeOrDefault(SlicerFile.Resolution);
- public Size GetRoiSizeOrDefault(Mat? defaultMat) => defaultMat is null ? GetRoiSizeOrDefault() : GetRoiSizeOrDefault(defaultMat.Size);
+
+ /// <summary>
+ /// Gets the <see cref="ROI"/> size, but if empty returns <see cref="src"/> size instead
+ /// </summary>
+ /// <param name="src"></param>
+ /// <returns></returns>
+ public Size GetRoiSizeOrDefault(Mat? src) => src is null ? GetRoiSizeOrDefault() : GetRoiSizeOrDefault(src.Size);
+
+ /// <summary>
+ /// Gets the <see cref="ROI"/> size, but if empty returns the size from <see cref="fallbackRectangle"/> instead
+ /// </summary>
+ /// <param name="fallbackRectangle"></param>
+ /// <returns></returns>
public Size GetRoiSizeOrDefault(Rectangle fallbackRectangle) => GetRoiSizeOrDefault(fallbackRectangle.Size);
+
+ /// <summary>
+ /// Gets the <see cref="ROI"/> size, but if empty returns the <see cref="fallbackSize"/> instead
+ /// </summary>
+ /// <param name="fallbackSize"></param>
+ /// <returns></returns>
public Size GetRoiSizeOrDefault(Size fallbackSize)
{
return HaveROI ? _roi.Size : fallbackSize;
}
+ /// <summary>
+ /// Gets the <see cref="ROI"/> size, but if empty returns the model volume bounds size instead
+ /// </summary>
+ /// <returns></returns>
public Size GetRoiSizeOrVolumeSize() => GetRoiSizeOrVolumeSize(_originalBoundingRectangle.Size);
+ /// <summary>
+ /// Gets the <see cref="ROI"/> size, but if empty returns the <see cref="fallbackSize"/> instead
+ /// </summary>
+ /// <param name="fallbackSize"></param>
+ /// <returns></returns>
public Size GetRoiSizeOrVolumeSize(Size fallbackSize)
{
return HaveROI ? _roi.Size : fallbackSize;
}
-
- public Mat GetRoiOrDefault(Mat defaultMat)
+ /// <summary>
+ /// Gets a cropped shared <see cref="Mat"/> from <see cref="src"/> by the <see cref="ROI"/>, but if empty return the <see cref="src"/> instead
+ /// </summary>
+ /// <param name="src"></param>
+ /// <returns></returns>
+ public Mat GetRoiOrDefault(Mat src)
{
- return HaveROI && defaultMat.Size != _roi.Size ? defaultMat.Roi(_roi) : defaultMat;
+ return HaveROI && src.Size != _roi.Size ? src.Roi(_roi) : src;
}
- public Mat GetRoiOrDefault(Mat defaultMat, Rectangle fallbackRoi)
+ /// <summary>
+ /// Gets a cropped shared <see cref="Mat"/> from <see cref="src"/> by the <see cref="ROI"/>, but if empty crop by <see cref="fallbackRoi"/>
+ /// </summary>
+ /// <param name="src"></param>
+ /// <param name="fallbackRoi"></param>
+ /// <returns></returns>
+ public Mat GetRoiOrDefault(Mat src, Rectangle fallbackRoi)
{
- if (HaveROI && defaultMat.Size != _roi.Size) return defaultMat.Roi(_roi);
- if (fallbackRoi.IsEmpty) return defaultMat;
- return defaultMat.Size != fallbackRoi.Size ? defaultMat.Roi(fallbackRoi) : defaultMat;
+ if (HaveROI && src.Size != _roi.Size) return src.Roi(_roi);
+ if (fallbackRoi.IsEmpty) return src;
+ return src.Size != fallbackRoi.Size ? src.Roi(fallbackRoi) : src;
}
+ /// <summary>
+ /// Gets a cropped shared <see cref="Mat"/> from <see cref="src"/> by the <see cref="ROI"/>, but if empty crop by <see cref="OriginalBoundingRectangle"/>
+ /// </summary>
+ /// <param name="defaultMat"></param>
+ /// <returns></returns>
public Mat GetRoiOrVolumeBounds(Mat defaultMat)
{
return GetRoiOrDefault(defaultMat, _originalBoundingRectangle);
}
+ /// <summary>
+ /// Gets the <see cref="ROI"/>, but if empty returns <see cref="OriginalBoundingRectangle"/>
+ /// </summary>
+ /// <returns></returns>
+ public Rectangle GetRoiOrVolumeBounds() => HaveROI ? _roi : _originalBoundingRectangle;
+
+ /// <summary>
+ /// Clears all masks
+ /// </summary>
public void ClearMasks()
{
MaskPoints = null;
}
+ /// <summary>
+ /// Sets masks only if they are empty
+ /// </summary>
+ /// <param name="points"></param>
public void SetMasksIfEmpty(Point[][] points)
{
if (HaveMask) return;
MaskPoints = points;
}
+ /// <summary>
+ /// Returns a mask given <see cref="MaskPoints"/>
+ /// </summary>
+ /// <param name="mat"></param>
+ /// <returns></returns>
public Mat? GetMask(Mat mat) => GetMask(_maskPoints, mat);
public Mat? GetMask(Point[][]? points, Mat mat)
@@ -507,6 +661,12 @@ public abstract class Operation : BindableBase, IDisposable
return GetRoiOrDefault(mask);
}
+ /// <summary>
+ /// Apply a mask to a mat <see cref="result"/>
+ /// </summary>
+ /// <param name="original">Original untouched mat</param>
+ /// <param name="result">Mat to modify and apply the mask</param>
+ /// <param name="mask">Mask</param>
public void ApplyMask(Mat original, Mat result, Mat? mask)
{
if (mask is null) return;
@@ -538,12 +698,23 @@ public abstract class Operation : BindableBase, IDisposable
ApplyMask(original, result, mask);
}
-
+ /// <summary>
+ /// Execute the operation internally, to be override by class
+ /// </summary>
+ /// <param name="progress"></param>
+ /// <returns></returns>
+ /// <exception cref="NotImplementedException"></exception>
protected virtual bool ExecuteInternally(OperationProgress progress)
{
throw new NotImplementedException();
}
+ /// <summary>
+ /// Execute the operation
+ /// </summary>
+ /// <param name="progress"></param>
+ /// <returns></returns>
+ /// <exception cref="InvalidOperationException"></exception>
public bool Execute(OperationProgress? progress = null)
{
if (_slicerFile is null) throw new InvalidOperationException($"{Title} can't execute due the lacking of a file parent.");
@@ -566,6 +737,13 @@ public abstract class Operation : BindableBase, IDisposable
public Task<bool> ExecuteAsync(OperationProgress? progress = null) => Task.Run(() => Execute(progress), progress?.Token ?? default);
+ /// <summary>
+ /// Execute the operation on a given <see cref="Mat"/>
+ /// </summary>
+ /// <param name="mat"></param>
+ /// <param name="arguments"></param>
+ /// <returns></returns>
+ /// <exception cref="NotImplementedException"></exception>
public virtual bool Execute(Mat mat, params object[]? arguments)
{
throw new NotImplementedException();
@@ -597,12 +775,21 @@ public abstract class Operation : BindableBase, IDisposable
operation.MaskPoints = MaskPoints;
}
+ /// <summary>
+ /// Serialize class to XML file
+ /// </summary>
+ /// <param name="path"></param>
+ /// <param name="indent"></param>
public void Serialize(string path, bool indent = false)
{
if(indent) XmlExtensions.SerializeToFile(this, path, XmlExtensions.SettingsIndent);
else XmlExtensions.SerializeToFile(this, path);
}
+ /// <summary>
+ /// Clone object
+ /// </summary>
+ /// <returns></returns>
public virtual Operation Clone()
{
var operation = MemberwiseClone() as Operation;
@@ -623,6 +810,11 @@ public abstract class Operation : BindableBase, IDisposable
#region Static Methods
+ /// <summary>
+ /// Deserialize <see cref="Operation"/> from a XML file
+ /// </summary>
+ /// <param name="path">XML file path</param>
+ /// <returns></returns>
public static Operation? Deserialize(string path)
{
if (!File.Exists(path)) return null;
@@ -641,6 +833,12 @@ public abstract class Operation : BindableBase, IDisposable
return Deserialize(path, type);
}
+ /// <summary>
+ /// Deserialize <see cref="Operation"/> from a XML file
+ /// </summary>
+ /// <param name="path">XML file path</param>
+ /// <param name="type"></param>
+ /// <returns></returns>
public static Operation Deserialize(string path, Type type)
{
var serializer = new XmlSerializer(type);
@@ -650,6 +848,12 @@ public abstract class Operation : BindableBase, IDisposable
return operation;
}
+ /// <summary>
+ /// Deserialize <see cref="Operation"/> from a XML file
+ /// </summary>
+ /// <param name="path">XML file path</param>
+ /// <param name="operation"></param>
+ /// <returns></returns>
public static Operation Deserialize(string path, Operation operation) => Deserialize(path, operation.GetType());
#endregion
diff --git a/UVtools.Core/Operations/OperationPCBExposure.cs b/UVtools.Core/Operations/OperationPCBExposure.cs
index d8c721d..3177263 100644
--- a/UVtools.Core/Operations/OperationPCBExposure.cs
+++ b/UVtools.Core/Operations/OperationPCBExposure.cs
@@ -195,9 +195,7 @@ public class OperationPCBExposure : Operation
protected override bool ExecuteInternally(OperationProgress progress)
{
using var mat = GetMat();
- var layer = new Layer(mat, SlicerFile);
- layer.SetNoDelays();
-
+
SlicerFile.SuppressRebuildPropertiesWork(() =>
{
SlicerFile.LayerHeight = (float) _layerHeight;
@@ -207,7 +205,7 @@ public class OperationPCBExposure : Operation
SlicerFile.LiftHeightTotal = 0;
SlicerFile.SetNoDelays();
- SlicerFile.Layers = new[] { layer };
+ SlicerFile.Layers = new[] { new Layer(mat, SlicerFile) };
}, true);
diff --git a/UVtools.Core/Operations/OperationScripting.cs b/UVtools.Core/Operations/OperationScripting.cs
index ce6835f..0ec7e6e 100644
--- a/UVtools.Core/Operations/OperationScripting.cs
+++ b/UVtools.Core/Operations/OperationScripting.cs
@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using System;
+using System.Diagnostics;
using System.IO;
using System.Xml.Serialization;
using UVtools.Core.FileFormats;
@@ -176,7 +177,7 @@ public sealed class OperationScripting : Operation
_scriptState = CSharpScript.RunAsync(_scriptText,
ScriptOptions.Default.AddReferences(typeof(About).Assembly).WithAllowUnsafe(true),
ScriptGlobals).Result;
-
+
var result = _scriptState.ContinueWithAsync("ScriptInit();").Result;
RaisePropertyChanged(nameof(CanExecute));
diff --git a/UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs b/UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs
index fa818de..5c84694 100644
--- a/UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs
+++ b/UVtools.Core/Suggestions/SuggestionWaitTimeBeforeCure.cs
@@ -18,7 +18,7 @@ namespace UVtools.Core.Suggestions;
public sealed class SuggestionWaitTimeBeforeCure : Suggestion
{
#region Enums
- public enum SuggestionWaitTimeBeforeCureSetType
+ public enum SuggestionWaitTimeBeforeCureSetType : byte
{
[Description("Fixed: Use a fixed time")]
Fixed,
@@ -27,10 +27,21 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion
[Description("Proportional to layer area")]
ProportionalLayerArea,
}
+
+ public enum SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom : byte
+ {
+ [Description("Previous mass")]
+ Previous,
+ [Description("Average of previous masses")]
+ Average,
+ [Description("Maximum of previous masses")]
+ Maximum,
+ }
#endregion
#region Members
private SuggestionWaitTimeBeforeCureSetType _setType = SuggestionWaitTimeBeforeCureSetType.Fixed;
+
private decimal _bottomHeight = 1;
private decimal _fixedBottomWaitTimeBeforeCure = 20;
private decimal _fixedWaitTimeBeforeCure = 2;
@@ -41,12 +52,16 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion
private uint _proportionalLayerPixels = 1000000;
private uint _proportionalBottomLayerArea = 1000;
private uint _proportionalLayerArea = 1000;
+ private decimal _proportionalBottomWaitTimeBeforeCureMaximumDifference = 1;
+ private decimal _proportionalWaitTimeBeforeCureMaximumDifference = 1;
+ private SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom _proportionalCalculateMassFrom = SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom.Previous;
+ private decimal _proportionalMassRelativeHeight = 0.2m;
private decimal _minimumBottomWaitTimeBeforeCure = 5;
private decimal _minimumWaitTimeBeforeCure = 1;
private decimal _maximumBottomWaitTimeBeforeCure = 120;
private decimal _maximumWaitTimeBeforeCure = 12;
private bool _createEmptyFirstLayer = true;
-
+
#endregion
#region Properties
@@ -154,11 +169,29 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion
public bool IsSetTypeFixed => _setType == SuggestionWaitTimeBeforeCureSetType.Fixed;
public bool IsSetTypeProportionalLayerPixels => _setType == SuggestionWaitTimeBeforeCureSetType.ProportionalLayerPixels;
public bool IsSetTypeProportionalLayerArea => _setType == SuggestionWaitTimeBeforeCureSetType.ProportionalLayerArea;
-
+
+ public SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom ProportionalCalculateMassFrom
+ {
+ get => _proportionalCalculateMassFrom;
+ set
+ {
+ if (!RaiseAndSetIfChanged(ref _proportionalCalculateMassFrom, value)) return;
+ RaisePropertyChanged(nameof(IsProportionalCalculateMassFromPrevious));
+ }
+ }
+
+ public decimal ProportionalMassRelativeHeight
+ {
+ get => _proportionalMassRelativeHeight;
+ set => RaiseAndSetIfChanged(ref _proportionalMassRelativeHeight, Math.Max(0, Math.Round(value, Layer.HeightPrecision)));
+ }
+
+ public bool IsProportionalCalculateMassFromPrevious => _proportionalCalculateMassFrom == SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom.Previous;
+
public decimal BottomHeight
{
get => _bottomHeight;
- set => RaiseAndSetIfChanged(ref _bottomHeight, value);
+ set => RaiseAndSetIfChanged(ref _bottomHeight, Math.Max(0, value));
}
public decimal FixedBottomWaitTimeBeforeCure
@@ -166,7 +199,7 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion
get => _fixedBottomWaitTimeBeforeCure;
set
{
- if(!RaiseAndSetIfChanged(ref _fixedBottomWaitTimeBeforeCure, Math.Round(value, 2))) return;
+ if(!RaiseAndSetIfChanged(ref _fixedBottomWaitTimeBeforeCure, Math.Round(Math.Max(0, value), 2))) return;
RaisePropertyChanged(nameof(WaitTimeBeforeCureTransitionDecrement));
}
}
@@ -176,7 +209,7 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion
get => _fixedWaitTimeBeforeCure;
set
{
- if (!RaiseAndSetIfChanged(ref _fixedWaitTimeBeforeCure, Math.Round(value, 2))) return;
+ if (!RaiseAndSetIfChanged(ref _fixedWaitTimeBeforeCure, Math.Round(Math.Max(0, value), 2))) return;
RaisePropertyChanged(nameof(WaitTimeBeforeCureTransitionDecrement));
}
}
@@ -196,13 +229,13 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion
public decimal ProportionalBottomWaitTimeBeforeCure
{
get => _proportionalBottomWaitTimeBeforeCure;
- set => RaiseAndSetIfChanged(ref _proportionalBottomWaitTimeBeforeCure, Math.Round(value, 2));
+ set => RaiseAndSetIfChanged(ref _proportionalBottomWaitTimeBeforeCure, Math.Round(Math.Max(0, value), 2));
}
public decimal ProportionalWaitTimeBeforeCure
{
get => _proportionalWaitTimeBeforeCure;
- set => RaiseAndSetIfChanged(ref _proportionalWaitTimeBeforeCure, Math.Round(value, 2));
+ set => RaiseAndSetIfChanged(ref _proportionalWaitTimeBeforeCure, Math.Round(Math.Max(0, value), 2));
}
@@ -230,28 +263,40 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion
set => RaiseAndSetIfChanged(ref _proportionalLayerArea, Math.Max(1, value));
}
+ public decimal ProportionalBottomWaitTimeBeforeCureMaximumDifference
+ {
+ get => _proportionalBottomWaitTimeBeforeCureMaximumDifference;
+ set => RaiseAndSetIfChanged(ref _proportionalBottomWaitTimeBeforeCureMaximumDifference, Math.Round(Math.Max(0, value), 2));
+ }
+
+ public decimal ProportionalWaitTimeBeforeCureMaximumDifference
+ {
+ get => _proportionalWaitTimeBeforeCureMaximumDifference;
+ set => RaiseAndSetIfChanged(ref _proportionalWaitTimeBeforeCureMaximumDifference, Math.Round(Math.Max(0, value), 2));
+ }
+
public decimal MinimumBottomWaitTimeBeforeCure
{
get => _minimumBottomWaitTimeBeforeCure;
- set => RaiseAndSetIfChanged(ref _minimumBottomWaitTimeBeforeCure, Math.Round(value, 2));
+ set => RaiseAndSetIfChanged(ref _minimumBottomWaitTimeBeforeCure, Math.Round(Math.Max(0, value), 2));
}
public decimal MinimumWaitTimeBeforeCure
{
get => _minimumWaitTimeBeforeCure;
- set => RaiseAndSetIfChanged(ref _minimumWaitTimeBeforeCure, Math.Round(value, 2));
+ set => RaiseAndSetIfChanged(ref _minimumWaitTimeBeforeCure, Math.Round(Math.Max(0, value), 2));
}
public decimal MaximumBottomWaitTimeBeforeCure
{
get => _maximumBottomWaitTimeBeforeCure;
- set => RaiseAndSetIfChanged(ref _maximumBottomWaitTimeBeforeCure, Math.Round(value, 2));
+ set => RaiseAndSetIfChanged(ref _maximumBottomWaitTimeBeforeCure, Math.Round(Math.Max(0, value), 2));
}
public decimal MaximumWaitTimeBeforeCure
{
get => _maximumWaitTimeBeforeCure;
- set => RaiseAndSetIfChanged(ref _maximumWaitTimeBeforeCure, Math.Round(value, 2));
+ set => RaiseAndSetIfChanged(ref _maximumWaitTimeBeforeCure, Math.Round(Math.Max(0, value), 2));
}
public bool CreateEmptyFirstLayer
@@ -410,23 +455,97 @@ public sealed class SuggestionWaitTimeBeforeCure : Suggestion
}
if (layer.NonZeroPixelCount <= 1) return 0; // Empty layer, don't need wait time
-
- return _setType switch
+
+ float mass = 0;
+ if (layer.Index > 0 && _proportionalCalculateMassFrom != SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom.Previous && _proportionalMassRelativeHeight > 0)
+ {
+ //var previousLayer = layer.GetPreviousLayerWithAtLeastPixelCountOf(2); // Skip all previous empty layer
+ //if (previousLayer is not null) layer = previousLayer;
+ uint count = 0;
+
+ Layer? previousLayer = layer;
+
+ while ((previousLayer = previousLayer!.PreviousLayer) is not null && (layer.PositionZ - previousLayer.PositionZ) <= (float)_proportionalMassRelativeHeight)
+ {
+ if(previousLayer.NonZeroPixelCount < 2) continue; // Skip empty layers
+
+ count++;
+ switch (_proportionalCalculateMassFrom)
+ {
+ case SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom.Average:
+ mass += _setType == SuggestionWaitTimeBeforeCureSetType.ProportionalLayerPixels
+ ? previousLayer.NonZeroPixelCount
+ : previousLayer.GetArea();
+ break;
+ case SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom.Maximum:
+ mass = Math.Max(_setType == SuggestionWaitTimeBeforeCureSetType.ProportionalLayerPixels
+ ? previousLayer.NonZeroPixelCount
+ : previousLayer.GetArea(), mass);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ if (_proportionalCalculateMassFrom == SuggestionWaitTimeBeforeCureProportionalCalculateMassFrom.Average && mass > 0 && count > 0)
+ {
+ mass /= count;
+ }
+ }
+
+ if (mass <= 0)
+ {
+ var previousLayer = layer.GetPreviousLayerWithAtLeastPixelCountOf(2); // Skip all previous empty layer
+ if (previousLayer is null)
+ {
+ return isBottomLayer ? (float)_fixedBottomWaitTimeBeforeCure : (float)_fixedWaitTimeBeforeCure;
+ }
+ mass = _setType == SuggestionWaitTimeBeforeCureSetType.ProportionalLayerPixels
+ ? previousLayer.NonZeroPixelCount
+ : previousLayer.GetArea();
+ }
+
+ float value = _setType switch
{
- SuggestionWaitTimeBeforeCureSetType.ProportionalLayerPixels => (float)Math.Round(
- (isBottomLayer
- ? layer.NonZeroPixelCount * _proportionalBottomWaitTimeBeforeCure / _proportionalBottomLayerPixels
- : layer.NonZeroPixelCount * _proportionalWaitTimeBeforeCure / _proportionalLayerPixels).Clamp(
- isBottomLayer ? _minimumBottomWaitTimeBeforeCure : _minimumWaitTimeBeforeCure,
- isBottomLayer ? _maximumBottomWaitTimeBeforeCure : _maximumWaitTimeBeforeCure), 2),
- SuggestionWaitTimeBeforeCureSetType.ProportionalLayerArea => (float) Math.Round(
- (isBottomLayer
- ? (decimal)layer.GetArea() * _proportionalBottomWaitTimeBeforeCure / _proportionalBottomLayerArea
- : (decimal)layer.GetArea() * _proportionalWaitTimeBeforeCure / _proportionalLayerArea).Clamp(
- isBottomLayer ? _minimumBottomWaitTimeBeforeCure : _minimumWaitTimeBeforeCure,
- isBottomLayer ? _maximumBottomWaitTimeBeforeCure : _maximumWaitTimeBeforeCure), 2),
+ SuggestionWaitTimeBeforeCureSetType.ProportionalLayerPixels => (float) (isBottomLayer
+ ? (decimal)mass * _proportionalBottomWaitTimeBeforeCure / _proportionalBottomLayerPixels
+ : (decimal)mass * _proportionalWaitTimeBeforeCure / _proportionalLayerPixels),
+ SuggestionWaitTimeBeforeCureSetType.ProportionalLayerArea => (float) (isBottomLayer
+ ? (decimal)mass * _proportionalBottomWaitTimeBeforeCure / _proportionalBottomLayerArea
+ : (decimal)mass * _proportionalWaitTimeBeforeCure / _proportionalLayerArea),
_ => throw new ArgumentOutOfRangeException()
};
+
+ if (isBottomLayer)
+ {
+ if (_proportionalBottomWaitTimeBeforeCureMaximumDifference > 0)
+ {
+ var previousLayer = layer.GetPreviousLayerWithAtLeastPixelCountOf(2);
+ if(previousLayer is not null)
+ {
+ value = value.Clamp(
+ Math.Max(0, previousLayer.WaitTimeBeforeCure - (float)_proportionalBottomWaitTimeBeforeCureMaximumDifference),
+ previousLayer.WaitTimeBeforeCure + (float)_proportionalBottomWaitTimeBeforeCureMaximumDifference);
+ }
+ }
+ }
+ else
+ {
+ if (_proportionalWaitTimeBeforeCureMaximumDifference > 0)
+ {
+ var previousLayer = layer.GetPreviousLayerWithAtLeastPixelCountOf(2);
+ if (previousLayer is not null)
+ {
+ value = value.Clamp(
+ Math.Max(0, previousLayer.WaitTimeBeforeCure - (float)_proportionalWaitTimeBeforeCureMaximumDifference),
+ previousLayer.WaitTimeBeforeCure + (float)_proportionalWaitTimeBeforeCureMaximumDifference);
+ }
+ }
+ }
+
+ return (float)Math.Round((decimal)value, 2).Clamp(
+ isBottomLayer ? _minimumBottomWaitTimeBeforeCure : _minimumWaitTimeBeforeCure,
+ isBottomLayer ? _maximumBottomWaitTimeBeforeCure : _maximumWaitTimeBeforeCure);
}
public float CalculateWaitTime(Layer layer) => CalculateWaitTime(false, layer);
diff --git a/UVtools.Core/SystemOS/SystemAware.cs b/UVtools.Core/SystemOS/SystemAware.cs
index a7d7573..2b89217 100644
--- a/UVtools.Core/SystemOS/SystemAware.cs
+++ b/UVtools.Core/SystemOS/SystemAware.cs
@@ -255,6 +255,16 @@ public static class SystemAware
}
}
+ public static void StartThisApplication(string? arguments = null)
+ {
+ var executable = Environment.ProcessPath;
+
+ if (File.Exists(executable)) // Direct execute
+ {
+ StartProcess(executable, arguments);
+ }
+ }
+
public static string GetExecutableName(string executable)
{
return OperatingSystem.IsWindows() ? $"{executable}.exe" : executable;
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index f9417e3..7c95a6a 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>3.4.1</Version>
+ <Version>3.4.2</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
@@ -66,11 +66,11 @@
<PackageReference Include="Emgu.CV.runtime.windows" Version="4.5.5.4823" />
<PackageReference Include="K4os.Compression.LZ4" Version="1.2.16" />
<PackageReference Include="KdTree" Version="1.4.1" />
- <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.1.0" />
+ <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.2.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
- <PackageReference Include="System.Text.Json" Version="6.0.3" />
+ <PackageReference Include="System.Text.Json" Version="6.0.4" />
</ItemGroup>
<Target Name="PreparePackageReleaseNotesFromFile" BeforeTargets="GenerateNuspec">
diff --git a/UVtools.Installer/Code/Product.wxs b/UVtools.Installer/Code/Product.wxs
index d392cc8..b14c5f7 100644
--- a/UVtools.Installer/Code/Product.wxs
+++ b/UVtools.Installer/Code/Product.wxs
@@ -35,6 +35,7 @@
<!-- Install directory -->
<RegistryKey Root="HKCU" Key="Software\UVtools" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes" />
<RegistryValue Root="HKCU" Key="Software\UVtools" Name="InstallDir" Value="[INSTALLLOCATION]" Type="string" />
+ <RegistryValue Root="HKCU" Key="Software\UVtools" Name="Version" Value="$(var.MSIProductVersion)" Type="string" />
<!-- Open file with UVtools -->
<RegistryKey Root="HKCR" Key="*\shell\UVtools" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="yes" />
diff --git a/UVtools.InstallerMM/UVtools.InstallerMM.wxs b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
index e28de76..5e61ad8 100644
--- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs
+++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
@@ -2,7 +2,7 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<?define ComponentRules="OneToOne"?>
<!-- SourceDir instructs IsWiX the location of the directory that contains files for this merge module -->
- <?define SourceDir="..\publish\UVtools_win-x64_v3.4.1"?>
+ <?define SourceDir="..\publish\UVtools_win-x64_v3.4.2"?>
<Module Id="UVtools" Language="1033" Version="1.0.0.0">
<Package Id="12aaa1cf-ff06-4a02-abd5-2ac01ac4f83b" Manufacturer="PTRTECH" InstallerVersion="200" Keywords="MSLA, DLP" Description="MSLA/DLP, file analysis, repair, conversion and manipulation" InstallScope="perMachine" Platform="x64" />
<Directory Id="TARGETDIR" Name="SourceDir">
@@ -1554,8 +1554,8 @@
<Component Id="owcD2245B900E5C448485AD96FE57629D9D" Guid="D2245B90-0E5C-4484-85AD-96FE57629D9D">
<File Id="owfD2245B900E5C448485AD96FE57629D9D" Source="$(var.SourceDir)\UVtoolsCmd.runtimeconfig.json" KeyPath="yes" />
</Component>
- <Component Id="owc47E738FB97A34F13812E87CF7F2B4643" Guid="47E738FB-97A3-4F13-812E-87CF7F2B4643">
- <File Id="owf47E738FB97A34F13812E87CF7F2B4643" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_6.0.422.16404.dll" KeyPath="yes" />
+ <Component Id="owc5D9B7363F82B4971A54B614C6E9F5EF1" Guid="5D9B7363-F82B-4971-A54B-614C6E9F5EF1">
+ <File Id="owf5D9B7363F82B4971A54B614C6E9F5EF1" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_6.0.522.21309.dll" KeyPath="yes" />
</Component>
</Directory>
<Directory Id="ProgramMenuFolder">
diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs
index 163df81..0558025 100644
--- a/UVtools.WPF/App.axaml.cs
+++ b/UVtools.WPF/App.axaml.cs
@@ -12,6 +12,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Web;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
@@ -25,7 +26,6 @@ using UVtools.Core;
using UVtools.Core.FileFormats;
using UVtools.Core.Managers;
using UVtools.Core.SystemOS;
-using UVtools.WPF.Extensions;
using UVtools.WPF.Structures;
using UVtools.WPF.Windows;
using Bitmap = Avalonia.Media.Imaging.Bitmap;
@@ -180,10 +180,6 @@ public class App : Application
UserSettings.Load();
UserSettings.SetVersion();
- MaterialManager.Load();
- OperationProfiles.Load();
- SuggestionManager.Load();
-
/*ThemeSelector = ThemeSelector.Create(Path.Combine(ApplicationPath, "Assets", "Themes"));
ThemeSelector.LoadSelectedTheme(Path.Combine(UserSettings.SettingsFolder, "selected.theme"));
if (ThemeSelector.SelectedTheme.Name == "UVtoolsDark" || ThemeSelector.SelectedTheme.Name == "Light")
@@ -208,9 +204,92 @@ public class App : Application
ApplyTheme();
}
- try
+ if (Program.IsCrashReport)
{
- if (CvInvoke.Init())
+ //Program.Args = new[] {"--crash-report", "test2", "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." };
+ if (string.IsNullOrWhiteSpace(Program.Args[1])) return;
+ if(string.IsNullOrWhiteSpace(Program.Args[2])) return;
+ var category = Program.Args[1];
+ var message = $"{Program.Args[2]}\nCategory: {category}";
+
+ var bugReportMessageMk = $"# Report\n```\n{message}\n```";
+
+ try
+ {
+ var append = $"\n\n# System\n{AboutWindow.GetEssentialInformationStatic()}";
+ message += append;
+ bugReportMessageMk += append;
+ }
+ catch
+ {
+ // ignored
+ }
+
+ var append2 = $"\n\nMachine date time: {DateTime.Now}\n UTC date time: {DateTime.UtcNow}";
+ message += append2;
+ bugReportMessageMk += $"{append2}\n\n# Additional information and Workflow\nComplete with additional information and the workflow that caused this crash.";
+
+ try
+ {
+ Current?.Clipboard?.SetTextAsync(bugReportMessageMk);
+ }
+ catch
+ {
+ // ignored
+ }
+
+
+ var reportButton = MessageWindow.CreateButton("Report", "fas fa-bug");
+ reportButton.Click += (sender, e) =>
+ {
+ Current?.Clipboard?.SetTextAsync(bugReportMessageMk);
+ using var reader = new StringReader(message);
+ SystemAware.OpenBrowser($"https://github.com/sn4k3/UVtools/issues/new?assignees=sn4k3&labels=&template=bug_report.md&title={HttpUtility.UrlEncode($"[Crash] {reader.ReadLine()}")}&body={HttpUtility.UrlEncode("<!--\n# Instructions:\n1. Click on this box;\n2. Select all it text (Ctrl + A);\n3. Paste the report content (Ctrl + V);\n4. Review the content;\n5. Submit the issue.\n!-->")}");
+ e.Handled = true;
+ };
+
+ var helpButton = MessageWindow.CreateButton("Help", "fas fa-question");
+ helpButton.Click += (sender, e) =>
+ {
+ Current?.Clipboard?.SetTextAsync(bugReportMessageMk);
+ SystemAware.OpenBrowser("https://github.com/sn4k3/UVtools/discussions/categories/q-a");
+ e.Handled = true;
+ };
+
+ var restartButton = MessageWindow.CreateButton("Restart", "fas fa-redo-alt");
+ restartButton.Click += (sender, e) =>
+ {
+ SystemAware.StartThisApplication();
+ };
+
+ desktop.MainWindow = new MessageWindow($"{About.SoftwareWithVersion} - Crash report",
+ "far fa-frown",
+ $"{About.Software} crashed due an unexpected {category.ToLowerInvariant()} error.\nYou can report this error if you find necessary.\nFind more details below:\n",
+ message,
+ new[]
+ {
+ reportButton,
+ helpButton,
+ restartButton,
+ MessageWindow.CreateCloseButton("fas fa-sign-out-alt")
+ });
+ }
+ else
+ {
+ try
+ {
+ if (!CvInvoke.Init())
+ {
+ desktop.MainWindow = new CantRunWindow();
+ }
+ }
+ catch (Exception e)
+ {
+ desktop.MainWindow = new CantRunWindow();
+ Console.WriteLine(e.ToString());
+ }
+
+ if (desktop.MainWindow is null)
{
if (Design.IsDesignMode)
{
@@ -226,56 +305,15 @@ public class App : Application
};
}
- MainWindow = new MainWindow();
- if (UserSettings.Instance.General.StartMaximized)
- {
- MainWindow.WindowState = WindowState.Maximized;
- }
- else
- {
- if (UserSettings.Instance.General.RestoreWindowLastSize)
- {
- MainWindow.Width = UserSettings.Instance.General.LastWindowBounds.Width;
- MainWindow.Height = UserSettings.Instance.General.LastWindowBounds.Height;
- }
+ MaterialManager.Load();
+ OperationProfiles.Load();
+ SuggestionManager.Load();
- if (UserSettings.Instance.General.RestoreWindowLastPosition)
- {
- MainWindow.Position = new PixelPoint(UserSettings.Instance.General.LastWindowBounds.Location.X, UserSettings.Instance.General.LastWindowBounds.Location.Y);
- }
- }
+ MainWindow = new MainWindow();
desktop.MainWindow = MainWindow;
}
- else
- {
- desktop.MainWindow = new CantRunWindow();
- }
- }
- catch (Exception e)
- {
- desktop.MainWindow = new CantRunWindow();
- Console.WriteLine(e.ToString());
}
-
- /*try
- {
- if(!CvInvoke.Init())
- await MainWindow.MessageBoxError("UVtools can not init OpenCV library\n" +
- "Please build or install this dependencies in order to run UVtools\n" +
- "Check manual or page at 'Requirements' section for help",
- "UVtools can not run");
- }
- catch (Exception e)
- {
- await MainWindow.MessageBoxError("UVtools can not run due lack of dependencies from cvextern/OpenCV\n" +
- "Please build or install this dependencies in order to run UVtools\n" +
- "Check manual or page at 'Requirements' section for help\n\n" +
- "Additional information:\n" +
- $"{e}", "UVtools can not run");
- return;
- }*/
-
//desktop.Exit += (sender, e) => ThemeSelector.SaveSelectedTheme(Path.Combine(UserSettings.SettingsFolder, "selected.theme"));
}
diff --git a/UVtools.WPF/ConsoleArguments.cs b/UVtools.WPF/ConsoleArguments.cs
index 788b104..552b500 100644
--- a/UVtools.WPF/ConsoleArguments.cs
+++ b/UVtools.WPF/ConsoleArguments.cs
@@ -25,7 +25,7 @@ public static class ConsoleArguments
/// <returns>True if is a valid argument, otherwise false</returns>
public static bool ParseArgs(string[] args)
{
- if(args is null || args.Length == 0) return false;
+ if(args.Length == 0) return false;
if (args[0] is "--cmd" && args.Length > 1)
{
diff --git a/UVtools.WPF/Controls/Suggestions/SuggestionWaitTimeBeforeCureControl.axaml b/UVtools.WPF/Controls/Suggestions/SuggestionWaitTimeBeforeCureControl.axaml
index 596e1e4..47d0c07 100644
--- a/UVtools.WPF/Controls/Suggestions/SuggestionWaitTimeBeforeCureControl.axaml
+++ b/UVtools.WPF/Controls/Suggestions/SuggestionWaitTimeBeforeCureControl.axaml
@@ -2,9 +2,9 @@
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"
+ mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="600"
x:Class="UVtools.WPF.Controls.Suggestions.SuggestionWaitTimeBeforeCureControl">
- <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,2,Auto,10,Auto,10,Auto,10,Auto,2,Auto,10,Auto"
+ <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,2,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,2,Auto,10,Auto"
ColumnDefinitions="Auto,10,190,5,Auto,5,190">
<CheckBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="5"
@@ -107,7 +107,12 @@
IsVisible="{Binding Suggestion.IsSetTypeFixed}"
Value="{Binding Suggestion.WaitTimeBeforeCureTransitionLayerCount}"/>
- <TextBlock Grid.Row="10" Grid.Column="6"
+ <TextBlock Grid.Row="10" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !Suggestion.IsSetTypeFixed}"
+ Text="-"/>
+
+ <TextBlock Grid.Row="10" Grid.Column="6"
VerticalAlignment="Center"
ToolTip.Tip="Decrement step per layer"
IsEnabled="{Binding Suggestion.WaitTimeBeforeCureTransitionLayerCount}"
@@ -149,7 +154,12 @@
IsVisible="{Binding Suggestion.IsSetTypeProportionalLayerPixels}"
Value="{Binding Suggestion.ProportionalBottomLayerPixels}"/>
- <NumericUpDown Grid.Row="12" Grid.Column="6"
+ <TextBlock Grid.Row="12" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !Suggestion.IsSetTypeFixed}"
+ Text="-"/>
+
+ <NumericUpDown Grid.Row="12" Grid.Column="6"
VerticalAlignment="Center"
Classes="ValueLabel ValueLabel_px"
Minimum="1"
@@ -182,25 +192,101 @@
IsVisible="{Binding Suggestion.IsSetTypeProportionalLayerArea}"
Value="{Binding Suggestion.ProportionalLayerArea}"/>
+ <TextBlock Grid.Row="14" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Sets the maximum allowed time difference relative to the previous layer.
+&#x0a;Use this option if large time variances are introducing visible layer lines in the model.
+&#x0a;Use 0 to disable this constrain."
+ IsVisible="{Binding !Suggestion.IsSetTypeFixed}"
+ Text="Max. time difference:"/>
+
+ <NumericUpDown Grid.Row="14" Grid.Column="2"
+ VerticalAlignment="Center"
+ Classes="ValueLabel ValueLabel_s"
+ Minimum="0"
+ Maximum="100"
+ FormatString="F2"
+ Increment="0.50"
+ IsVisible="{Binding !Suggestion.IsSetTypeFixed}"
+ Value="{Binding Suggestion.ProportionalBottomWaitTimeBeforeCureMaximumDifference}"/>
+
+ <TextBlock Grid.Row="14" Grid.Column="4"
+ VerticalAlignment="Center"
+ IsVisible="{Binding !Suggestion.IsSetTypeFixed}"
+ Text="-"/>
+
+ <NumericUpDown Grid.Row="14" Grid.Column="6"
+ VerticalAlignment="Center"
+ Classes="ValueLabel ValueLabel_s"
+ Minimum="0"
+ Maximum="100"
+ FormatString="F2"
+ Increment="0.50"
+ IsVisible="{Binding !Suggestion.IsSetTypeFixed}"
+ Value="{Binding Suggestion.ProportionalWaitTimeBeforeCureMaximumDifference}"/>
+
+
+ <TextBlock Grid.Row="16" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Sets the method to get the proportional mass value"
+ IsVisible="{Binding !Suggestion.IsSetTypeFixed}"
+ Text="Proportional to:"/>
+
+ <ComboBox Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="5"
+ HorizontalAlignment="Stretch"
+ IsVisible="{Binding !Suggestion.IsSetTypeFixed}"
+ Items="{Binding Suggestion.ProportionalCalculateMassFrom, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
+ SelectedItem="{Binding Suggestion.ProportionalCalculateMassFrom, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
+
+
+ <TextBlock Grid.Row="18" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Relative back height in millimeters to look back and calculate the average or the maximum within the defined range"
+ Text="Look back height:">
+ <TextBlock.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="!Suggestion.IsSetTypeFixed"/>
+ <Binding Path="!Suggestion.IsProportionalCalculateMassFromPrevious"/>
+ </MultiBinding>
+ </TextBlock.IsVisible>
+ </TextBlock>
+
+ <NumericUpDown Grid.Row="18" Grid.Column="2"
+ VerticalAlignment="Center"
+ Classes="ValueLabel ValueLabel_mm"
+ Minimum="0"
+ Maximum="1000"
+ FormatString="F3"
+ Increment="0.01"
+ Value="{Binding Suggestion.ProportionalMassRelativeHeight}">
+ <NumericUpDown.IsVisible>
+ <MultiBinding Converter="{x:Static BoolConverters.And}">
+ <Binding Path="!Suggestion.IsSetTypeFixed"/>
+ <Binding Path="!Suggestion.IsProportionalCalculateMassFromPrevious"/>
+ </MultiBinding>
+ </NumericUpDown.IsVisible>
+ </NumericUpDown>
+
+
- <TextBlock Grid.Row="14" Grid.Column="2"
+ <TextBlock Grid.Row="20" Grid.Column="2"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
Text="Minimum:"/>
- <TextBlock Grid.Row="14" Grid.Column="6"
+ <TextBlock Grid.Row="20" Grid.Column="6"
VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
Text="Maximum:"/>
- <TextBlock Grid.Row="16" Grid.Column="0"
+ <TextBlock Grid.Row="22" Grid.Column="0"
VerticalAlignment="Center"
ToolTip.Tip="Sets the limits for the bottom layers (Min-Max)"
Text="Limits (bottom):"/>
- <NumericUpDown Grid.Row="16" Grid.Column="2"
+ <NumericUpDown Grid.Row="22" Grid.Column="2"
VerticalAlignment="Center"
Classes="ValueLabel ValueLabel_s"
Minimum="0"
@@ -209,11 +295,11 @@
Increment="{Binding SlicerFile.LayerHeight}"
Value="{Binding Suggestion.MinimumBottomWaitTimeBeforeCure}"/>
- <TextBlock Grid.Row="16" Grid.Column="4"
+ <TextBlock Grid.Row="22" Grid.Column="4"
VerticalAlignment="Center"
Text="-"/>
- <NumericUpDown Grid.Row="16" Grid.Column="6"
+ <NumericUpDown Grid.Row="22" Grid.Column="6"
VerticalAlignment="Center"
Classes="ValueLabel ValueLabel_s"
Minimum="0"
@@ -222,12 +308,12 @@
Increment="0.50"
Value="{Binding Suggestion.MaximumBottomWaitTimeBeforeCure}"/>
- <TextBlock Grid.Row="18" Grid.Column="0"
+ <TextBlock Grid.Row="24" Grid.Column="0"
VerticalAlignment="Center"
ToolTip.Tip="Sets the limits for the normal layers (Min-Max)"
Text="Limits (normal):"/>
- <NumericUpDown Grid.Row="18" Grid.Column="2"
+ <NumericUpDown Grid.Row="24" Grid.Column="2"
VerticalAlignment="Center"
Classes="ValueLabel ValueLabel_s"
Minimum="0"
@@ -236,11 +322,11 @@
Increment="0.50"
Value="{Binding Suggestion.MinimumWaitTimeBeforeCure}"/>
- <TextBlock Grid.Row="18" Grid.Column="4"
+ <TextBlock Grid.Row="24" Grid.Column="4"
VerticalAlignment="Center"
Text="-"/>
- <NumericUpDown Grid.Row="18" Grid.Column="6"
+ <NumericUpDown Grid.Row="24" Grid.Column="6"
VerticalAlignment="Center"
Classes="ValueLabel ValueLabel_s"
Minimum="0"
diff --git a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml
index 7925ec7..95bdf78 100644
--- a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml
@@ -50,7 +50,7 @@
<Grid
Name="grid"
RowDefinitions="Auto"
- ColumnDefinitions="Auto,Auto,*,Auto,Auto"
+ ColumnDefinitions="Auto,Auto,*,Auto"
VerticalAlignment="Center"
ShowGridLines="True">
@@ -77,12 +77,6 @@
<TextBlock
Grid.Column="3"
VerticalAlignment="Center"
- FontWeight="Bold"
- Padding="15"
- Text="Unit"/>
- <TextBlock
- Grid.Column="4"
- VerticalAlignment="Center"
HorizontalAlignment="Center"
FontWeight="Bold"
Padding="15"
diff --git a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs
index c920819..f748c32 100644
--- a/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolEditParametersControl.axaml.cs
@@ -1,5 +1,7 @@
-using System.ComponentModel;
+using System;
+using System.ComponentModel;
using System.Globalization;
+using System.Threading;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
@@ -27,7 +29,7 @@ public class ToolEditParametersControl : ToolControl
public TextBlock Name { get; }
public TextBlock OldValue { get; }
public NumericUpDown NewValue { get; }
- public TextBlock Unit { get; }
+ //public TextBlock Unit { get; }
public Button ResetButton { get; }
public RowControl(FileFormat.PrintParameterModifier modifier)
@@ -73,13 +75,19 @@ public class ToolEditParametersControl : ToolControl
NewValue.FormatString = $"F{modifier.DecimalPlates}";
}
- Unit = new TextBlock
+ if (!string.IsNullOrWhiteSpace(modifier.ValueUnit))
+ {
+ var valueLabel = modifier.ValueUnit == "☀" ? "sun" : modifier.ValueUnit.Replace("/", string.Empty);
+ NewValue.Classes = new Classes("ValueLabel", $"ValueLabel_{valueLabel}");
+ }
+
+ /*Unit = new TextBlock
{
Text = modifier.ValueUnit,
VerticalAlignment = VerticalAlignment.Center,
Padding = new Thickness(10, 0, 15, 0),
Tag = this
- };
+ };*/
ResetButton = new Button
{
@@ -141,7 +149,7 @@ public class ToolEditParametersControl : ToolControl
grid.Children.Add(rowControl.Name);
grid.Children.Add(rowControl.OldValue);
grid.Children.Add(rowControl.NewValue);
- grid.Children.Add(rowControl.Unit);
+ //grid.Children.Add(rowControl.Unit);
grid.Children.Add(rowControl.ResetButton);
Grid.SetRow(rowControl.Name, rowIndex);
Grid.SetColumn(rowControl.Name, column++);
@@ -152,8 +160,8 @@ public class ToolEditParametersControl : ToolControl
Grid.SetRow(rowControl.NewValue, rowIndex);
Grid.SetColumn(rowControl.NewValue, column++);
- Grid.SetRow(rowControl.Unit, rowIndex);
- Grid.SetColumn(rowControl.Unit, column++);
+ //Grid.SetRow(rowControl.Unit, rowIndex);
+ //Grid.SetColumn(rowControl.Unit, column++);
Grid.SetRow(rowControl.ResetButton, rowIndex);
Grid.SetColumn(rowControl.ResetButton, column++);
diff --git a/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
index 09823a2..9e85acc 100644
--- a/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolScriptingControl.axaml.cs
@@ -1,6 +1,4 @@
using System;
-using System.IO;
-using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
diff --git a/UVtools.WPF/Controls/WindowEx.cs b/UVtools.WPF/Controls/WindowEx.cs
index 9e1a94e..2b0099f 100644
--- a/UVtools.WPF/Controls/WindowEx.cs
+++ b/UVtools.WPF/Controls/WindowEx.cs
@@ -17,6 +17,7 @@ using Avalonia.Styling;
using UVtools.Core.FileFormats;
using UVtools.Core.SystemOS;
using UVtools.WPF.Extensions;
+using Size = Avalonia.Size;
namespace UVtools.WPF.Controls;
@@ -63,7 +64,17 @@ public class WindowEx : Window, INotifyPropertyChanged, IStyleable
_propertyChanged?.Invoke(this, e);
}
#endregion
-
+
+ #region Enum
+
+ public enum WindowConstrainsMaxSizeType : byte
+ {
+ None,
+ UserSettings,
+ Ratio
+ }
+ #endregion
+
Type IStyleable.StyleKey => typeof(Window);
public DialogResults DialogResult { get; set; } = DialogResults.Unknown;
@@ -78,6 +89,21 @@ public class WindowEx : Window, INotifyPropertyChanged, IStyleable
public double WindowMaxHeight => this.GetScreenWorkingArea().Height - UserSettings.Instance.General.WindowsVerticalMargin;
+ public Size WindowMaxSize
+ {
+ get
+ {
+ var size = this.GetScreenWorkingArea();
+ return new Size(size.Width - UserSettings.Instance.General.WindowsHorizontalMargin,
+ this.GetScreenWorkingArea().Height - UserSettings.Instance.General.WindowsVerticalMargin);
+ }
+ }
+
+ public WindowConstrainsMaxSizeType WindowConstrainMaxSize { get; set; } = WindowConstrainsMaxSizeType.UserSettings;
+
+ public double WindowsWidthMaxSizeRatio { get; set; } = 1;
+ public double WindowsHeightMaxSizeRatio { get; set; } = 1;
+
public UserSettings Settings => UserSettings.Instance;
public virtual FileFormat SlicerFile
@@ -97,10 +123,42 @@ public class WindowEx : Window, INotifyPropertyChanged, IStyleable
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
- if (!CanResize && WindowState == WindowState.Normal)
+ AutoConstainsWindowMaxSize();
+ }
+
+ /*protected override Size MeasureOverride(Size availableSize)
+ {
+ var result = base.MeasureOverride(availableSize);
+ if (SizeToContent == SizeToContent.Manual) return result;
+
+ if (MaxWidth > 0 && MaxWidth < result.Width)
+ {
+ result = result.WithWidth(MaxWidth);
+ }
+ if (MaxHeight > 0 && MaxHeight < result.Height)
+ {
+ result = result.WithHeight(MaxHeight);
+ }
+
+ return result;
+ }*/
+
+ public void AutoConstainsWindowMaxSize()
+ {
+ if (!CanResize && WindowState == WindowState.Normal && WindowConstrainMaxSize != WindowConstrainsMaxSizeType.None)
{
- MaxWidth = WindowMaxWidth;
- MaxHeight = WindowMaxHeight;
+ switch (WindowConstrainMaxSize)
+ {
+ case WindowConstrainsMaxSizeType.UserSettings:
+ MaxWidth = WindowMaxWidth;
+ MaxHeight = WindowMaxHeight;
+ break;
+ case WindowConstrainsMaxSizeType.Ratio:
+ var size = this.GetScreenWorkingArea();
+ if (WindowsWidthMaxSizeRatio is > 0 and < 1) MaxWidth = size.Width * WindowsWidthMaxSizeRatio;
+ if (WindowsHeightMaxSizeRatio is > 0 and < 1) MaxHeight = size.Height * WindowsHeightMaxSizeRatio;
+ break;
+ }
}
}
diff --git a/UVtools.WPF/ErrorLog.cs b/UVtools.WPF/ErrorLog.cs
index 0955aed..1dd6775 100644
--- a/UVtools.WPF/ErrorLog.cs
+++ b/UVtools.WPF/ErrorLog.cs
@@ -20,6 +20,7 @@ public static class ErrorLog
}
catch (Exception exception)
{
+ Console.WriteLine(exception);
Debug.WriteLine(exception);
}
}
diff --git a/UVtools.WPF/Extensions/WindowExtensions.cs b/UVtools.WPF/Extensions/WindowExtensions.cs
index 76cbc30..8a67220 100644
--- a/UVtools.WPF/Extensions/WindowExtensions.cs
+++ b/UVtools.WPF/Extensions/WindowExtensions.cs
@@ -95,7 +95,7 @@ public static class WindowExtensions
public static Screen GetCurrentScreen(this Window window)
{
return //window.Screens.ScreenFromVisual(window) ??
- window.Screens.ScreenFromVisual(App.MainWindow) ??
+ window.Screens.ScreenFromVisual(App.MainWindow ?? window) ??
window.Screens.Primary ??
window.Screens.All[0];
}
diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs
index ff0db6f..37e697e 100644
--- a/UVtools.WPF/MainWindow.LayerPreview.cs
+++ b/UVtools.WPF/MainWindow.LayerPreview.cs
@@ -491,8 +491,7 @@ public partial class MainWindow
get
{
if (!LayerCache.IsCached) return "Pixels: 0";
- var pixelPercent = Math.Round(LayerCache.Layer.NonZeroPixelCount * 100.0 / (SlicerFile.ResolutionX * SlicerFile.ResolutionY), 2);
- var text = $"Pixels: {LayerCache.Layer.NonZeroPixelCount} ({pixelPercent}%)";
+ var text = $"Pixels: {LayerCache.Layer.NonZeroPixelCount} ({LayerCache.Layer.NonZeroPixelPercentage:F2}%)";
var volume = LayerCache.Layer.Volume;
if (volume > 0)
{
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index 1c24f95..aa65ff6 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -22,6 +22,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using Org.BouncyCastle.Operators;
using UVtools.AvaloniaControls;
using UVtools.Core;
using UVtools.Core.Extensions;
@@ -250,6 +251,30 @@ public partial class MainWindow : WindowEx
public MainWindow()
{
+ if (Settings.General.StartMaximized)
+ {
+ WindowState = WindowState.Maximized;
+ }
+ else
+ {
+ if (Settings.General.RestoreWindowLastPosition)
+ {
+ Position = new PixelPoint(Settings.General.LastWindowBounds.Location.X, Settings.General.LastWindowBounds.Location.Y);
+ }
+
+ if (Settings.General.RestoreWindowLastSize)
+ {
+ Width = Settings.General.LastWindowBounds.Width;
+ Height = Settings.General.LastWindowBounds.Height;
+ }
+
+ var windowSize = this.GetScreenWorkingArea();
+ if (Width >= windowSize.Width || Height >= windowSize.Height)
+ {
+ WindowState = WindowState.Maximized;
+ }
+ }
+
InitializeComponent();
//App.ThemeSelector?.EnableThemes(this);
@@ -703,7 +728,6 @@ public partial class MainWindow : WindowEx
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
- var windowSize = this.GetScreenWorkingArea();
var clientSizeObs = this.GetObservable(ClientSizeProperty);
clientSizeObs.Subscribe(size =>
@@ -726,11 +750,6 @@ public partial class MainWindow : WindowEx
ProcessFiles(args.Data.GetFileNames()?.ToArray());
});
- if (!Settings.General.StartMaximized && (ClientSize.Width >= windowSize.Width || ClientSize.Height >= windowSize.Height))
- {
- WindowState = WindowState.Maximized;
- }
-
AddLog($"{About.Software} start", Program.ProgramStartupTime.Elapsed.TotalSeconds);
if (Settings.General.CheckForUpdatesOnStartup)
@@ -1067,7 +1086,7 @@ public partial class MainWindow : WindowEx
App.ApplyTheme();
}
- if (oldLayerCompressionCodec != Settings.General.LayerCompressionCodec)
+ if (oldLayerCompressionCodec != Settings.General.LayerCompressionCodec && IsFileLoaded)
{
IsGUIEnabled = false;
ShowProgressWindow($"Changing layers compression codec from {oldLayerCompressionCodec.ToString().ToUpper()} to {Settings.General.LayerCompressionCodec.ToString().ToUpper()}");
@@ -1210,7 +1229,7 @@ public partial class MainWindow : WindowEx
private void UpdateTitle()
{
var title = $"{About.Software} ";
-
+
if (IsFileLoaded)
{
title += $"File: {SlicerFile.Filename} ({Math.Round(LastStopWatch.ElapsedMilliseconds / 1000m, 2)}s) ";
@@ -1254,17 +1273,39 @@ public partial class MainWindow : WindowEx
try
{
var operation = Operation.Deserialize(files[i]);
- await ShowRunOperation(operation);
+ operation.SlicerFile = SlicerFile;
+ if ((_globalModifiers & KeyModifiers.Shift) != 0) await RunOperation(operation);
+ else await ShowRunOperation(operation);
}
catch (Exception e)
{
Debug.WriteLine(e);
- throw;
}
continue;
}
+ if (files[i].EndsWith(".cs") || files[i].EndsWith(".csx"))
+ {
+ if (!IsFileLoaded) continue;
+ try
+ {
+ var operation = new OperationScripting(SlicerFile);
+ operation.ReloadScriptFromFile(files[i]);
+ if (operation.CanExecute)
+ {
+ if ((_globalModifiers & KeyModifiers.Shift) != 0) await RunOperation(operation);
+ else await ShowRunOperation(operation);
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+
+ continue;
+ }
+
if (i == 0 && !openNewWindow && (_globalModifiers & KeyModifiers.Shift) == 0)
{
@@ -1293,7 +1334,7 @@ public partial class MainWindow : WindowEx
var fileNameOnly = Path.GetFileName(fileName);
SlicerFile = FileFormat.FindByExtensionOrFilePath(fileName, true);
if (SlicerFile is null) return;
-
+
IsGUIEnabled = false;
ShowProgressWindow($"Opening: {fileNameOnly}");
@@ -2024,6 +2065,8 @@ public partial class MainWindow : WindowEx
public async Task<bool> RunOperation(Operation baseOperation)
{
if (baseOperation is null) return false;
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+ baseOperation.SlicerFile ??= SlicerFile;
switch (baseOperation)
{
diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs
index 2e71d42..b72d439 100644
--- a/UVtools.WPF/Program.cs
+++ b/UVtools.WPF/Program.cs
@@ -1,17 +1,12 @@
using System;
using System.Diagnostics;
-using System.Drawing;
using System.Globalization;
-using System.Runtime.ExceptionServices;
+using System.Threading.Tasks;
using Avalonia;
-using Emgu.CV;
-using Emgu.CV.Structure;
using Projektanker.Icons.Avalonia;
using Projektanker.Icons.Avalonia.FontAwesome;
using Projektanker.Icons.Avalonia.MaterialDesign;
-using UVtools.Core.Extensions;
-using UVtools.Core.Gerber;
-using UVtools.WPF.Extensions;
+using UVtools.Core.SystemOS;
namespace UVtools.WPF;
@@ -19,7 +14,8 @@ namespace UVtools.WPF;
public static class Program
{
- public static string[] Args = null!;
+ public static string[] Args = Array.Empty<string>();
+ public static bool IsCrashReport;
public static Stopwatch ProgramStartupTime = null!;
// Initialization code. Don't use any Avalonia, third-party APIs or any
@@ -43,6 +39,11 @@ public static class Program
return;
}
+ if (Args.Length > 2 && Args[0] == "--crash-report")
+ {
+ IsCrashReport = true;
+ }
+
/*Slicer slicer = new(Size.Empty, SizeF.Empty, "D:\\Cube100x100x100.stl");
var slices = slicer.SliceModel(0.05f);
@@ -60,33 +61,43 @@ public static class Program
//var machinesText = Machine.GenerateMachinePresetsFromPrusaSlicer();
// Add the event handler for handling non-UI thread exceptions to the event.
- AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
+ AppDomain.CurrentDomain.UnhandledException += (sender, e) => HandleUnhandledException("Non-UI", (Exception)e.ExceptionObject);
+ TaskScheduler.UnobservedTaskException += (sender, e) => HandleUnhandledException("Task", e.Exception);
//AppDomain.CurrentDomain.FirstChanceException += CurrentDomainOnFirstChanceException;
- BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+
+ try
+ {
+ BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+ }
+ catch (Exception e)
+ {
+ HandleUnhandledException("Application", e);
+ }
+
// Closing
}
- private static void CurrentDomainOnFirstChanceException(object? sender, FirstChanceExceptionEventArgs e)
+ private static void HandleUnhandledException(string category, Exception ex)
{
- ErrorLog.AppendLine("First chance exception", e.Exception.ToString());
- }
-
-
- private static async void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
- {
- var ex = (Exception)e.ExceptionObject;
- ErrorLog.AppendLine("Fatal Non-UI Error", ex.ToString());
+ ErrorLog.AppendLine($"Fatal {category} Error", ex.ToString());
- try
- {
- var errorMsg = $"An application error occurred. Please contact the administrator with the following information:\n\n{ex}";
- await App.MainWindow.MessageBoxError(errorMsg, "Fatal Non-UI Error");
- }
- catch (Exception exception)
+ if (!IsCrashReport)
{
- Debug.WriteLine(exception);
+ try
+ {
+ SystemAware.StartThisApplication($"--crash-report \"{category}\" \"{ex}\"");
+ //var errorMsg = $"An application error occurred. Please contact the administrator with the following information:\n\n{ex}";
+ //await App.MainWindow.MessageBoxError(errorMsg, "Fatal Non-UI Error");
+ }
+ catch (Exception exception)
+ {
+ Debug.WriteLine(exception);
+ Console.WriteLine(exception);
+ }
}
+
+ Environment.Exit(-1);
}
// Avalonia configuration, don't remove; also used by visual designer.
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index 32d4b43..3be333b 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>3.4.1</Version>
+ <Version>3.4.2</Version>
<Platforms>AnyCPU;x64</Platforms>
<PackageIcon>UVtools.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
@@ -39,15 +39,15 @@
<NoWarn>1701;1702;</NoWarn>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Avalonia" Version="0.10.13" />
- <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.13" />
- <PackageReference Include="Avalonia.Desktop" Version="0.10.13" />
- <PackageReference Include="Avalonia.Diagnostics" Version="0.10.13" />
+ <PackageReference Include="Avalonia" Version="0.10.14" />
+ <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.14" />
+ <PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
+ <PackageReference Include="Avalonia.Diagnostics" Version="0.10.14" />
<PackageReference Include="MessageBox.Avalonia" Version="2.0.0" />
<PackageReference Include="Projektanker.Icons.Avalonia" Version="4.4.0" />
<PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="4.4.0" />
<PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="4.4.0" />
- <PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.12" />
+ <PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.13" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UVtools.AvaloniaControls\UVtools.AvaloniaControls.csproj" />
diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs
index 37adc53..69c45d6 100644
--- a/UVtools.WPF/UserSettings.cs
+++ b/UVtools.WPF/UserSettings.cs
@@ -12,6 +12,7 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
+using System.Reactive.Disposables;
using System.Xml.Serialization;
using Avalonia.Media;
using JetBrains.Annotations;
@@ -1394,6 +1395,7 @@ public sealed class UserSettings : BindableBase
public sealed class ToolsUserSettings : BindableBase
{
private bool _expandDescriptions = true;
+ private bool _promptForConfirmation = true;
private bool _restoreLastUsedSettings;
private bool _lastUsedSettingsKeepOnCloseFile = true;
private bool _lastUsedSettingsPriorityOverDefaultProfile = true;
@@ -1404,6 +1406,12 @@ public sealed class UserSettings : BindableBase
set => RaiseAndSetIfChanged(ref _expandDescriptions, value);
}
+ public bool PromptForConfirmation
+ {
+ get => _promptForConfirmation;
+ set => RaiseAndSetIfChanged(ref _promptForConfirmation, value);
+ }
+
public bool RestoreLastUsedSettings
{
get => _restoreLastUsedSettings;
diff --git a/UVtools.WPF/Windows/AboutWindow.axaml.cs b/UVtools.WPF/Windows/AboutWindow.axaml.cs
index 9fa0726..420f8f0 100644
--- a/UVtools.WPF/Windows/AboutWindow.axaml.cs
+++ b/UVtools.WPF/Windows/AboutWindow.axaml.cs
@@ -132,6 +132,24 @@ public class AboutWindow : WindowEx
public void OpenLicense() => SystemAware.OpenBrowser(About.LicenseUrl);
+ public static string GetEssentialInformationStatic()
+ {
+ var message = new StringBuilder();
+ message.AppendLine($"{About.SoftwareWithVersionArch}");
+ message.AppendLine($"Operative system: {OSDescription}");
+ message.AppendLine($"Processor: {ProcessorName}");
+ message.AppendLine($"Processor cores: {ProcessorCount}");
+ message.AppendLine($"Memory RAM: {MemoryRAMDescription}");
+ message.AppendLine($"Runtime: {RuntimeDescription}");
+ message.AppendLine($"Framework: {FrameworkDescription}");
+ message.AppendLine($"AvaloniaUI: {AvaloniaUIDescription}");
+ message.AppendLine($"OpenCV: {OpenCVVersion}");
+ message.AppendLine();
+ message.AppendLine($"Path: {App.ApplicationPath}");
+ message.AppendLine($"Executable: {App.AppExecutable}");
+ return message.ToString();
+ }
+
private string GetEssentialInformation()
{
var message = new StringBuilder();
diff --git a/UVtools.WPF/Windows/CantRunWindow.axaml b/UVtools.WPF/Windows/CantRunWindow.axaml
index 18e5307..870251c 100644
--- a/UVtools.WPF/Windows/CantRunWindow.axaml
+++ b/UVtools.WPF/Windows/CantRunWindow.axaml
@@ -1,4 +1,4 @@
-<Window xmlns="https://github.com/avaloniaui"
+<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"
@@ -99,4 +99,4 @@
</Border>
</Grid>
-</Window>
+</uc:WindowEx>
diff --git a/UVtools.WPF/Windows/CantRunWindow.axaml.cs b/UVtools.WPF/Windows/CantRunWindow.axaml.cs
index d3cb4db..c3612ca 100644
--- a/UVtools.WPF/Windows/CantRunWindow.axaml.cs
+++ b/UVtools.WPF/Windows/CantRunWindow.axaml.cs
@@ -6,21 +6,18 @@
* of this license document, but changing it is not allowed.
*/
using Avalonia;
-using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using UVtools.Core.SystemOS;
+using UVtools.WPF.Controls;
namespace UVtools.WPF.Windows
{
- public partial class CantRunWindow : Window
+ public partial class CantRunWindow : WindowEx
{
public CantRunWindow()
{
InitializeComponent();
DataContext = this;
-#if DEBUG
- this.AttachDevTools();
-#endif
}
private void InitializeComponent()
diff --git a/UVtools.WPF/Windows/MessageWindow.axaml b/UVtools.WPF/Windows/MessageWindow.axaml
new file mode 100644
index 0000000..3215c96
--- /dev/null
+++ b/UVtools.WPF/Windows/MessageWindow.axaml
@@ -0,0 +1,90 @@
+<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"
+ xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="UVtools.WPF.Windows.MessageWindow"
+ Icon="/Assets/Icons/UVtools.ico"
+ WindowStartupLocation="CenterOwner"
+ CanResize="False"
+ SizeToContent="Manual"
+ MinWidth="300"
+ MinHeight="200"
+ Width="800"
+ Height="600"
+ WindowConstrainMaxSize="Ratio"
+ WindowsWidthMaxSizeRatio="0.75"
+ WindowsHeightMaxSizeRatio="0.75"
+ Title="UVtools Message">
+ <Grid RowDefinitions="Auto,*,Auto">
+ <Border Grid.Row="0"
+ Padding="20"
+ IsVisible="{Binding HeaderIsVisible}"
+ Classes="Header">
+
+ <Grid ColumnDefinitions="Auto,10,*">
+ <i:Icon Grid.Column="0"
+ FontSize="{Binding HeaderIconSize}"
+ FontWeight="Bold"
+ IsVisible="{Binding HeaderIcon, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
+ Value="{Binding HeaderIcon}"
+ Foreground="{StaticResource LogoColor}"
+ HorizontalAlignment="Center"/>
+
+ <TextBox Grid.Column="2"
+ Classes="TransparentReadOnly"
+ FontWeight="Bold"
+ IsVisible="{Binding HeaderTitle, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
+ Text="{Binding HeaderTitle}"/>
+ </Grid>
+ </Border>
+
+
+ <TextBox Grid.Row="1"
+ Classes="TransparentReadOnly"
+ Padding="20"
+ ScrollViewer.HorizontalScrollBarVisibility="Auto"
+ ScrollViewer.VerticalScrollBarVisibility="Auto"
+ TextWrapping="Wrap"
+ Text="{Binding Message}"/>
+
+ <Border Grid.Row="2"
+ Margin="0"
+ Classes="FooterActions">
+
+ <Grid ColumnDefinitions="Auto,*">
+ <Button Grid.Column="0"
+ IsVisible="{Binding AboutButtonIsVisible}"
+ Command="{Binding OpenAboutWindow}">
+ <StackPanel Orientation="Horizontal" Spacing="10">
+ <Image Source="/Assets/Icons/UVtools.ico"
+ Height="26"/>
+
+ <TextBlock VerticalAlignment="Center"
+ Text="About"/>
+ </StackPanel>
+ </Button>
+
+
+ <StackPanel Grid.Column="1" Orientation="Horizontal"
+ Name="ButtonsRightPanel"
+ Spacing="5"
+ HorizontalAlignment="Right">
+
+ <uc:ButtonWithIcon Command="{Binding Close}"
+ IsCancel="True"
+ Padding="10"
+ VerticalContentAlignment="Center"
+ Icon="fas fa-sign-out-alt"
+ Text="Close"/>
+ </StackPanel>
+ </Grid>
+
+
+
+
+ </Border>
+ </Grid>
+</uc:WindowEx>
diff --git a/UVtools.WPF/Windows/MessageWindow.axaml.cs b/UVtools.WPF/Windows/MessageWindow.axaml.cs
new file mode 100644
index 0000000..407fd80
--- /dev/null
+++ b/UVtools.WPF/Windows/MessageWindow.axaml.cs
@@ -0,0 +1,141 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.Markup.Xaml;
+using UVtools.WPF.Controls;
+
+namespace UVtools.WPF.Windows
+{
+ public partial class MessageWindow : WindowEx
+ {
+ private string? _headerIcon;
+ private ushort _headerIconSize = 64;
+ private string _headerTitle;
+ private bool _aboutButtonIsVisible = true;
+ private string _message;
+
+ private readonly StackPanel _buttonsRightPanel;
+
+ public string? HeaderIcon
+ {
+ get => _headerIcon;
+ set
+ {
+ if (!RaiseAndSetIfChanged(ref _headerIcon, value)) return;
+ RaisePropertyChanged(nameof(HeaderIsVisible));
+ }
+ }
+
+ public ushort HeaderIconSize
+ {
+ get => _headerIconSize;
+ set => RaiseAndSetIfChanged(ref _headerIconSize, value);
+ }
+
+ public string? HeaderTitle
+ {
+ get => _headerTitle;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _headerTitle, value)) return;
+ RaisePropertyChanged(nameof(HeaderIsVisible));
+ }
+ }
+
+ public bool HeaderIsVisible => !string.IsNullOrWhiteSpace(HeaderIcon) || !string.IsNullOrWhiteSpace(HeaderTitle);
+
+ public string Message
+ {
+ get => _message;
+ set => RaiseAndSetIfChanged(ref _message, value);
+ }
+
+ public bool AboutButtonIsVisible
+ {
+ get => _aboutButtonIsVisible;
+ set => RaiseAndSetIfChanged(ref _aboutButtonIsVisible, value);
+ }
+
+ public MessageWindow()
+ {
+ InitializeComponent();
+
+ CanResize = Settings.General.WindowsCanResize;
+ if (WindowStartupLocation == WindowStartupLocation.CenterOwner && Owner is null)
+ {
+ WindowStartupLocation = WindowStartupLocation.CenterScreen;
+ }
+
+ AutoConstainsWindowMaxSize();
+
+ _buttonsRightPanel = this.FindControl<StackPanel>("ButtonsRightPanel");
+ DataContext = this;
+ }
+
+ public MessageWindow(string title, string? headerIcon, string? headerTitle, string message, ButtonWithIcon[]? buttons = null) : this()
+ {
+ Title = title;
+ HeaderIcon = headerIcon;
+ HeaderTitle = headerTitle;
+ Message = message;
+
+ if (buttons is not null && buttons.Length > 0)
+ {
+ _buttonsRightPanel.Children.Clear();
+ _buttonsRightPanel.Children.AddRange(buttons);
+
+ foreach (var button in buttons)
+ {
+ button.Click += (sender, e) =>
+ {
+ if (e.Handled) return;
+ Close(button);
+ e.Handled = true;
+ };
+ }
+ }
+ }
+
+ public MessageWindow(string title, string message, ButtonWithIcon[]? buttons = null) : this(title, null, null, message, buttons) { }
+
+ /*protected override void OnOpened(EventArgs e)
+ {
+ base.OnOpened(e);
+ }*/
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public async void OpenAboutWindow()
+ {
+ await new AboutWindow().ShowDialog(this);
+ }
+
+ public static ButtonWithIcon CreateButton(string? text, string? icon, int padding = 10) =>
+ new()
+ {
+ Icon = icon,
+ Text = text,
+ VerticalAlignment = VerticalAlignment.Center,
+ Padding = new Thickness(padding)
+ };
+
+ public static ButtonWithIcon CreateButton(string? text, int padding = 10) => CreateButton(text, null, padding);
+
+ public static ButtonWithIcon CreateCancelButton(string? icon = null, int padding = 10)
+ {
+ var btn = CreateButton("Cancel", icon, padding);
+ btn.IsCancel = true;
+ return btn;
+ }
+
+ public static ButtonWithIcon CreateCloseButton(string? icon = null, int padding = 10)
+ {
+ var btn = CreateButton("Close", icon, padding);
+ btn.IsCancel = true;
+ return btn;
+ }
+ }
+}
diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml
index 88c8e5d..cda1778 100644
--- a/UVtools.WPF/Windows/SettingsWindow.axaml
+++ b/UVtools.WPF/Windows/SettingsWindow.axaml
@@ -1886,6 +1886,9 @@
ToolTip.Tip="Disable this if you want to gain some vertical space for windows, you can still toggle the descriptions and read them if required."
Content="Expand and show descriptions by default"/>
+ <CheckBox IsChecked="{Binding Settings.Tools.PromptForConfirmation}"
+ Content="Always prompt for confirmation before execute the operation"/>
+
<CheckBox IsChecked="{Binding Settings.Tools.RestoreLastUsedSettings}"
Content="Keep and restore the last used settings on operations per user session/instance"/>
diff --git a/UVtools.WPF/Windows/ToolWindow.axaml.cs b/UVtools.WPF/Windows/ToolWindow.axaml.cs
index fea7a28..0dc2542 100644
--- a/UVtools.WPF/Windows/ToolWindow.axaml.cs
+++ b/UVtools.WPF/Windows/ToolWindow.axaml.cs
@@ -815,10 +815,9 @@ public class ToolWindow : WindowEx
ToolControl.BaseOperation.SetMasksIfEmpty(Masks);
if (!await ToolControl.ValidateForm()) return;
- if (!string.IsNullOrEmpty(ToolControl.BaseOperation.ConfirmationText))
+ if (Settings.Tools.PromptForConfirmation && !string.IsNullOrEmpty(ToolControl.BaseOperation.ConfirmationText))
{
- var result =
- await this.MessageBoxQuestion(
+ var result = await this.MessageBoxQuestion(
$"Are you sure you want to {ToolControl.BaseOperation.ConfirmationText}");
if (result != ButtonResult.Yes) return;
}