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>2020-09-22 06:29:11 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2020-09-22 06:29:11 +0300
commit732d27e647b51a65c2736320d4f0ee590acb0d12 (patch)
treebc7d6b7074d0f4d6826707e59285ecf3aac2aca4
parent0ce27a1f9c61b6f852f44efa02a1b7fc834c9efe (diff)
WPF progress
-rw-r--r--UVtools.Core/FileFormats/FileFormat.cs3
-rw-r--r--UVtools.Core/FileFormats/IFileFormat.cs8
-rw-r--r--UVtools.Core/FileFormats/PHZFile.cs1
-rw-r--r--UVtools.Core/Operations/OperationProgress.cs71
-rw-r--r--UVtools.GUI/Forms/FrmLoading.cs2
-rw-r--r--UVtools.WPF/App.axaml1
-rw-r--r--UVtools.WPF/App.axaml.cs16
-rw-r--r--UVtools.WPF/AppSettings.cs2
-rw-r--r--UVtools.WPF/Controls/AdvancedImageBox.cs13
-rw-r--r--UVtools.WPF/LayerCache.cs16
-rw-r--r--UVtools.WPF/MainWindow.axaml204
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs194
-rw-r--r--UVtools.WPF/Program.cs15
-rw-r--r--UVtools.WPF/Structures/LogItem.cs61
-rw-r--r--UVtools.WPF/Structures/PixelPicker.cs46
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj1
-rw-r--r--UVtools.WPF/Windows/ProgressWindow.axaml19
-rw-r--r--UVtools.WPF/Windows/ProgressWindow.axaml.cs61
18 files changed, 644 insertions, 90 deletions
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index 375d315..7c72f15 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -321,6 +321,8 @@ namespace UVtools.Core.FileFormats
set => _haveModifiedLayers = value;
} // => LayerManager.IsModified;
+ public Size Resolution => new Size((int) ResolutionX, (int) ResolutionY);
+
public abstract uint ResolutionX { get; set; }
public abstract uint ResolutionY { get; set; }
@@ -363,6 +365,7 @@ namespace UVtools.Core.FileFormats
public StringBuilder GCode { get; set; }
+ public bool HaveGCode => !(GCode is null);
public abstract object[] Configs { get; }
public bool IsValid => !ReferenceEquals(FileFullPath, null);
diff --git a/UVtools.Core/FileFormats/IFileFormat.cs b/UVtools.Core/FileFormats/IFileFormat.cs
index 0ca1b84..e166b55 100644
--- a/UVtools.Core/FileFormats/IFileFormat.cs
+++ b/UVtools.Core/FileFormats/IFileFormat.cs
@@ -7,6 +7,7 @@
*/
using System;
+using System.Drawing;
using System.Text;
using Emgu.CV;
using UVtools.Core.Operations;
@@ -82,6 +83,8 @@ namespace UVtools.Core.FileFormats
/// </summary>
LayerManager LayerManager { get; set; }
+ Size Resolution { get; }
+
/// <summary>
/// Gets the image width resolution
/// </summary>
@@ -208,6 +211,11 @@ namespace UVtools.Core.FileFormats
StringBuilder GCode { get; set; }
/// <summary>
+ /// Gets if this file have available gcode
+ /// </summary>
+ bool HaveGCode { get; }
+
+ /// <summary>
/// Get all configuration objects with properties and values
/// </summary>
object[] Configs { get; }
diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs
index b331ace..8a68196 100644
--- a/UVtools.Core/FileFormats/PHZFile.cs
+++ b/UVtools.Core/FileFormats/PHZFile.cs
@@ -978,6 +978,7 @@ namespace UVtools.Core.FileFormats
public override void Decode(string fileFullPath, OperationProgress progress = null)
{
base.Decode(fileFullPath, progress);
+ if(progress is null) progress = new OperationProgress(OperationProgress.StatusGatherLayers, LayerCount);
using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read))
{
diff --git a/UVtools.Core/Operations/OperationProgress.cs b/UVtools.Core/Operations/OperationProgress.cs
index f34d339..3ffce1f 100644
--- a/UVtools.Core/Operations/OperationProgress.cs
+++ b/UVtools.Core/Operations/OperationProgress.cs
@@ -6,11 +6,14 @@
* of this license document, but changing it is not allowed.
*/
using System;
+using System.Diagnostics;
using System.Threading;
+using UVtools.Core.Extensions;
+using UVtools.Core.Objects;
namespace UVtools.Core.Operations
{
- public sealed class OperationProgress
+ public sealed class OperationProgress : BindableBase
{
public const string StatusDecodeThumbnails = "Decoded Thumbnails";
public const string StatusGatherLayers = "Gathered Layers";
@@ -34,6 +37,11 @@ namespace UVtools.Core.Operations
public CancellationToken Token => TokenSource.Token;
private bool _canCancel = true;
+ private string _title = "Operation";
+ private string _itemName = StatusDecodeLayers;
+ private uint _processedItems;
+ private uint _itemCount;
+
public
OperationProgress()
@@ -50,6 +58,8 @@ namespace UVtools.Core.Operations
_canCancel = canCancel;
}
+ public Stopwatch StopWatch { get; } = new Stopwatch();
+
/// <summary>
/// Gets or sets if operation can be cancelled
/// </summary>
@@ -60,35 +70,74 @@ namespace UVtools.Core.Operations
if (!_canCancel) return _canCancel;
return !Token.IsCancellationRequested && Token.CanBeCanceled && _canCancel;
}
- set => _canCancel = value;
+ set => SetProperty(ref _canCancel, value);
+ }
+
+ /// <summary>
+ /// Gets or sets the item name for the operation
+ /// </summary>
+ public string Title
+ {
+ get => _title;
+ set => SetProperty(ref _title, value);
}
+ public string ElapsedTimeStr => $"{StopWatch.Elapsed.Minutes}m {StopWatch.Elapsed.Seconds}s {StopWatch.Elapsed.Milliseconds}ms";
+
/// <summary>
/// Gets or sets the item name for the operation
/// </summary>
- public string ItemName { get; set; } = StatusDecodeLayers;
+ public string ItemName
+ {
+ get => _itemName;
+ set => SetProperty(ref _itemName, value);
+ }
/// <summary>
/// Gets or sets the number of processed items
/// </summary>
- public uint ProcessedItems { get; set; }
+ public uint ProcessedItems
+ {
+ get => _processedItems;
+ set
+ {
+ //_processedItems = value;
+ SetProperty(ref _processedItems, value);
+ OnPropertyChanged(nameof(ProgressPercent));
+ OnPropertyChanged(nameof(Description));
+ }
+ }
/// <summary>
/// Gets or sets the total of item count on this operation
/// </summary>
- public uint ItemCount { get; set; }
+ public uint ItemCount
+ {
+ get => _itemCount;
+ set
+ {
+ SetProperty(ref _itemCount, value);
+ OnPropertyChanged(nameof(IsIndeterminate));
+ OnPropertyChanged(nameof(ProgressPercent));
+ OnPropertyChanged(nameof(Description));
+ }
+ }
/// <summary>
/// Gets the remaining items to be processed
/// </summary>
public uint RemainingItems => ItemCount - ProcessedItems;
- public int ProgressStep => (int) Math.Min(ProcessedItems * 100 / ItemCount, 100);
+ public int ProgressStep => (int)ProgressPercent;
+
+ public string Description => ToString();
+
+ public bool IsIndeterminate => ItemCount == 0;
/// <summary>
/// Gets the progress from 0 to 100%
/// </summary>
- public double ProgressPercent => Math.Round(ProcessedItems * 100.0 / ItemCount, 2);
+ public double ProgressPercent => ItemCount == 0 ? 0 : Math.Round(ProcessedItems * 100.0 / ItemCount, 2).Clamp(0, 100);
public static OperationProgress operator +(OperationProgress progress, uint value)
{
@@ -122,5 +171,13 @@ namespace UVtools.Core.Operations
$"{ProcessedItems}/? {ItemName}" :
$"{ProcessedItems}/{ItemCount} {ItemName} | {ProgressPercent:0.00}%";
}
+
+ public void TriggerRefresh()
+ {
+ OnPropertyChanged(nameof(ElapsedTimeStr));
+ OnPropertyChanged(nameof(CanCancel));
+ //OnPropertyChanged(nameof(ProgressPercent));
+ //OnPropertyChanged(nameof(Description));
+ }
}
}
diff --git a/UVtools.GUI/Forms/FrmLoading.cs b/UVtools.GUI/Forms/FrmLoading.cs
index 61cd510..8806c57 100644
--- a/UVtools.GUI/Forms/FrmLoading.cs
+++ b/UVtools.GUI/Forms/FrmLoading.cs
@@ -11,7 +11,7 @@ namespace UVtools.GUI.Forms
public partial class FrmLoading : Form
{
public Stopwatch StopWatch { get; } = new Stopwatch();
- public OperationProgress Progress { get; set; }
+ public OperationProgress Progress { get; private set; }
private LogItem OperationLog;
diff --git a/UVtools.WPF/App.axaml b/UVtools.WPF/App.axaml
index 9b64e5d..3eb218e 100644
--- a/UVtools.WPF/App.axaml
+++ b/UVtools.WPF/App.axaml
@@ -4,6 +4,7 @@
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
+ <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
<StyleInclude Source="avares://ThemeEditor.Controls.ColorPicker/ColorPicker.xaml"/>
</Application.Styles>
</Application>
diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs
index 9e0b688..f22ddb1 100644
--- a/UVtools.WPF/App.axaml.cs
+++ b/UVtools.WPF/App.axaml.cs
@@ -6,6 +6,8 @@
* of this license document, but changing it is not allowed.
*/
+using System;
+using System.Diagnostics;
using System.Globalization;
using System.Threading;
using Avalonia;
@@ -49,7 +51,19 @@ namespace UVtools.WPF
}
#region Utilities
-
+ public static void NewInstance(string filePath)
+ {
+ try
+ {
+ var info = new ProcessStartInfo("UVtools.exe", $"\"{filePath}\"");
+ Process.Start(info)?.Dispose();
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+
+ }
#endregion
}
diff --git a/UVtools.WPF/AppSettings.cs b/UVtools.WPF/AppSettings.cs
index 9b35e1f..162d76c 100644
--- a/UVtools.WPF/AppSettings.cs
+++ b/UVtools.WPF/AppSettings.cs
@@ -27,7 +27,7 @@ namespace UVtools.WPF
/// <summary>
/// Returns the zoom level that will be used for autozoom actions
/// </summary>
- public const ushort LockedZoomLevel = 1200;
+ public static int LockedZoomLevel => ZoomLevels[UserSettings.Instance.LayerPreview.ZoomLockLevelIndex + ZoomLevelSkipCount];
/// <summary>
diff --git a/UVtools.WPF/Controls/AdvancedImageBox.cs b/UVtools.WPF/Controls/AdvancedImageBox.cs
index 513e11d..0922927 100644
--- a/UVtools.WPF/Controls/AdvancedImageBox.cs
+++ b/UVtools.WPF/Controls/AdvancedImageBox.cs
@@ -408,8 +408,17 @@ namespace UVtools.WPF.Controls
set
{
_image = value;
- SizedContainer.Width = _image.Size.Width;
- SizedContainer.Height = _image.Size.Height;
+ if (_image is null)
+ {
+ SizedContainer.Width = 0;
+ SizedContainer.Height = 0;
+ }
+ else
+ {
+ SizedContainer.Width = _image.Size.Width;
+ SizedContainer.Height = _image.Size.Height;
+ }
+
InvalidateVisual();
}
}
diff --git a/UVtools.WPF/LayerCache.cs b/UVtools.WPF/LayerCache.cs
index 53660fc..e46d4e7 100644
--- a/UVtools.WPF/LayerCache.cs
+++ b/UVtools.WPF/LayerCache.cs
@@ -10,28 +10,35 @@ using System;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Util;
+using UVtools.Core;
namespace UVtools.WPF
{
public sealed class LayerCache
{
+ private Layer _layer;
private Mat _image;
private Array _layerHierarchyJagged;
private VectorOfVectorOfPoint _layerContours;
private Mat _layerHierarchy;
- public Mat Image
+ public bool IsCached => !ReferenceEquals(_layer, null);
+
+ public Layer Layer
{
- get => _image;
+ get => _layer;
set
{
Clear();
- _image = value;
+ _layer = value;
+ _image = _layer.LayerMat;
ImageBgr = new Mat();
- CvInvoke.CvtColor(value, ImageBgr, ColorConversion.Gray2Bgr);
+ CvInvoke.CvtColor(_image, ImageBgr, ColorConversion.Gray2Bgr);
}
}
+ public Mat Image => _image;
+
public Mat ImageBgr { get; set; }
public VectorOfVectorOfPoint LayerContours
@@ -81,6 +88,7 @@ namespace UVtools.WPF
/// </summary>
public void Clear()
{
+ _layer = null;
_image?.Dispose();
ImageBgr?.Dispose();
_layerContours?.Dispose();
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index 35058e9..76f60c1 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -11,27 +11,42 @@
Icon="/Assets/Icons/UVtools.ico"
MinWidth="1024"
MinHeight="600"
+ DragDrop.AllowDrop="True"
>
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Name="MainMenu.File" Header="_File">
- <MenuItem Name="MainMenu.File.Open" Header="_Open" HotKey="Ctrl+O" InputGesture="Ctrl+O" Command="{Binding MenuFileOpenClicked}">
+ <MenuItem
+ Name="MainMenu.File.Open"
+ Header="_Open"
+ HotKey="Ctrl+O" InputGesture="Ctrl+O"
+ Command="{Binding MenuFileOpenClicked}">
<MenuItem.Icon>
<Image Source="\Assets\Icons\open-16x16.png"/>
</MenuItem.Icon>
</MenuItem>
- <MenuItem Name="MainMenu.File.OpenNewWindow" Header="Open in _new window" HotKey="Ctrl+Shift+O" InputGesture="Ctrl+Shift+O" Command="{Binding ButtonClicked}">
+ <MenuItem Name="MainMenu.File.OpenNewWindow" Header="Open in _new window" HotKey="Ctrl+Shift+O" InputGesture="Ctrl+Shift+O" Command="{Binding MenuFileOpenNewWindowClicked}">
<MenuItem.Icon>
<Image Source="\Assets\Icons\open-16x16.png"/>
</MenuItem.Icon>
</MenuItem>
- <MenuItem Name="MainMenu.File.Reload" Header="_Reload" HotKey="Ctrl+F5" InputGesture="Ctrl+F5" IsEnabled="{Binding IsFileLoaded}">
+ <MenuItem
+ Name="MainMenu.File.Reload"
+ Header="_Reload"
+ HotKey="Ctrl+F5" InputGesture="Ctrl+F5"
+ IsEnabled="{Binding IsFileLoaded}"
+ Command="{Binding ReloadFile}">
<MenuItem.Icon>
<Image Source="\Assets\Icons\file-refresh-16x16.png"/>
</MenuItem.Icon>
</MenuItem>
- <MenuItem Name="MainMenu.File.Save" Header="_Save" HotKey="Ctrl+S" InputGesture="Ctrl+S">
+ <MenuItem
+ Name="MainMenu.File.Save"
+ Header="_Save"
+ HotKey="Ctrl+S" InputGesture="Ctrl+S"
+ IsEnabled="{Binding CanSave}"
+ >
<MenuItem.Icon>
<Image Source="\Assets\Icons\save-16x16.png"/>
</MenuItem.Icon>
@@ -41,7 +56,12 @@
<Image Source="\Assets\Icons\save-as-16x16.png"/>
</MenuItem.Icon>
</MenuItem>
- <MenuItem Name="MainMenu.File.Close" Header="_Close" HotKey="Ctrl+W" InputGesture="Ctrl+W" IsEnabled="{Binding IsFileLoaded}">
+ <MenuItem
+ Name="MainMenu.File.Close"
+ Header="_Close"
+ HotKey="Ctrl+W" InputGesture="Ctrl+W"
+ IsEnabled="{Binding IsFileLoaded}"
+ Command="{Binding CloseFile}">
<MenuItem.Icon>
<Image Source="\Assets\Icons\file-close-16x16.png"/>
</MenuItem.Icon>
@@ -102,35 +122,106 @@
</MenuItem>
</Menu>
- <TabControl IsEnabled="{Binding IsFileLoaded}" DockPanel.Dock="Left" Width="300">
- <TabItem Header="Information" VerticalContentAlignment="Center">
- <TextBlock Text="I am in the circle page !" HorizontalAlignment="Left" VerticalAlignment="Center"/>
- </TabItem>
+ <TabControl
+ IsEnabled="{Binding IsFileLoaded}"
+ DockPanel.Dock="Left"
+ Width="400"
+ SelectedIndex="3">
<TabItem>
<TabItem.Header>
- <TextBlock VerticalAlignment="Center">Issues</TextBlock>
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal">
+ <Image Source="/Assets/Icons/button-info-16x16.png" Width="16"/>
+ <TextBlock Margin="5,0,0,0">Information</TextBlock>
+ </StackPanel>
+ </TabItem.Header>
+ </TabItem>
+
+ <TabItem IsVisible="{Binding SlicerFile.HaveGCode}">
+ <TabItem.Header>
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal">
+ <Image Source="/Assets/Icons/code-16x16.png" Width="16"/>
+ <TextBlock Margin="5,0,0,0">Gcode</TextBlock>
+ </StackPanel>
</TabItem.Header>
- <StackPanel>
- <TextBlock Text="I am in the triangle page ! I'll put a button to show you that each page contains what you want." HorizontalAlignment="Left" VerticalAlignment="Center"/>
- <Button>A button in the triangle page !</Button>
- </StackPanel>
</TabItem>
+
+
<TabItem>
<TabItem.Header>
- <TextBlock VerticalAlignment="Center">GCode</TextBlock>
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal">
+ <Image Source="/Assets/Icons/warning-16x16.png" Width="16"/>
+ <TextBlock Margin="5,0,0,0">Issues</TextBlock>
+ </StackPanel>
</TabItem.Header>
- <StackPanel Orientation="Horizontal">
- <TextBlock Text="Square : " HorizontalAlignment="Left" VerticalAlignment="Center"/>
- <Rectangle Fill="Blue" Width="63" Height="41"/>
- </StackPanel>
+
+
</TabItem>
+
<TabItem>
<TabItem.Header>
- <TextBlock VerticalAlignment="Center">Log</TextBlock>
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal">
+ <Image Source="/Assets/Icons/log-16x16.png" Width="16"/>
+ <TextBlock Margin="5,0,0,0">Log</TextBlock>
+ </StackPanel>
</TabItem.Header>
- <StackPanel Orientation="Horizontal">
- <TextBlock Text="Square : " HorizontalAlignment="Left" VerticalAlignment="Center"/>
- <Rectangle Fill="Blue" Width="63" Height="41"/>
+
+ <StackPanel Orientation="Vertical">
+
+ <Grid ColumnDefinitions="Auto,Auto,*">
+ <Button
+ Grid.Column="0"
+ Command="{Binding Logs.Clear}">
+ <StackPanel Orientation="Horizontal">
+ <Image Source="/Assets/Icons/trash-16x16.png" Width="16"/>
+ <TextBlock Margin="5,0,0,0">Clear</TextBlock>
+ </StackPanel>
+ </Button>
+
+ <CheckBox
+ Grid.Column="1"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Shows extra information usefull to debug problems."
+ IsChecked="{Binding IsVerbose}"
+ Content="Verbose"
+ Margin="5,0,0,0"
+ />
+
+ <TextBlock
+ Grid.Column="2"
+ HorizontalAlignment="Right"
+ VerticalAlignment="Center"
+ Text="{Binding Logs.Count, StringFormat=Operations: \{0\}}"/>
+ </Grid>
+
+
+ <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Orientation="Horizontal">
+
+
+
+ </StackPanel>
+ <DataGrid Items="{Binding Logs}"
+ CanUserReorderColumns="True"
+ CanUserResizeColumns="True"
+ CanUserSortColumns="True"
+ GridLinesVisibility="All"
+ IsReadOnly="True"
+ ClipboardCopyMode="IncludeHeader"
+ >
+ <DataGrid.Columns>
+ <DataGridTextColumn Header="#"
+ Binding="{Binding Index}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Started"
+ Binding="{Binding StartTime}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Time(s)"
+ Binding="{Binding ElapsedTime}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Description"
+ Binding="{Binding Description}"
+ Width="Auto" />
+ </DataGrid.Columns>
+ </DataGrid>
</StackPanel>
</TabItem>
</TabControl>
@@ -287,7 +378,7 @@
<ToggleButton
IsChecked="{Binding ShowLayerImageIssues}"
- ToolTip.Tip="Highlight Issues on current layer.\nValid only if Issues are calculated."
+ ToolTip.Tip="Highlight Issues on current layer. Valid only if Issues are calculated."
>
<StackPanel Orientation="Horizontal">
<Image Source="/Assets/Icons/warning-16x16.png"/>
@@ -349,8 +440,69 @@
Name="Layer.Image"
/>
- <StackPanel HorizontalAlignment="Left" Grid.Row="2" Orientation="Horizontal" Spacing="1">
- <TextBlock Text="{Binding ShowLayerRenderMs}ms"/>
+ <StackPanel Margin="0,5,0,0" Grid.Row="2" Orientation="Horizontal" Spacing="5">
+ <StackPanel
+ ToolTip.Tip="Number of pixels to cure on this layer image and the percetange of them against total lcd pixels"
+ VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
+ <Image Source="/Assets/Icons/pixel-16x16.png"/>
+ <TextBlock Text="{Binding LayerPixelCountStr, StringFormat=Pixels: \{0\}}"/>
+ </StackPanel>
+
+ <Button
+ ToolTip.Tip="Object volume bounds for current layer, position and size. Click: go to region"
+ >
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
+ <Image Source="/Assets/Icons/expand-16x16.png"/>
+ <TextBlock Text="{Binding LayerBoundsStr, StringFormat=Bounds: \{0\}}"/>
+ </StackPanel>
+ </Button>
+
+ <Button
+ ToolTip.Tip="Region of interest selection over layer. (NS): Not selected
+ Click: go to region | SHIFT + click: Clear ROI"
+ >
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
+ <Image Source="/Assets/Icons/object-group-16x16.png"/>
+ <TextBlock Text="{Binding LayerROIStr, StringFormat=ROI: \{0\}}"/>
+ </StackPanel>
+ </Button>
+
+ </StackPanel>
+
+ <StackPanel Margin="0,5,0,0" VerticalAlignment="Center" HorizontalAlignment="Right" Grid.Row="2" Orientation="Horizontal" Spacing="5">
+
+ <Button
+ IsEnabled="{Binding LayerPixelPicker.IsSet}"
+ ToolTip.Tip="Pixel picker: Use CONTROL and over a pixel to get his position and brightness. Click: Center at position" >
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
+ <Image Source="/Assets/Icons/map-marker-16x16.png"/>
+ <TextBlock Text="{Binding LayerPixelPicker}"/>
+ </StackPanel>
+ </Button>
+
+ <Button
+ ToolTip.Tip="Layer image zoom level, use mouse scroll to zoom in/out into image. Ctrl + 0 OR double right click to scale to fit"
+ >
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
+ <Image Source="/Assets/Icons/search-16x16.png"/>
+ <TextBlock Text="{Binding LayerZoomStr, StringFormat=Zoom: [ \{0\} ]}"/>
+ </StackPanel>
+ </Button>
+
+ <Button
+ ToolTip.Tip="Layer Resolution. Click: Zoom to fit"
+ Command="{Binding ZoomToFit}"
+ >
+ <StackPanel VerticalAlignment="Center" Orientation="Horizontal" Spacing="5">
+ <Image Source="/Assets/Icons/expand-16x16.png"/>
+ <TextBlock Text="{Binding LayerResolutionStr}"/>
+ </StackPanel>
+ </Button>
+
+
+ <TextBlock
+ ToolTip.Tip="Layer preview computation time."
+ VerticalAlignment="Center" Text="{Binding ShowLayerRenderMs, StringFormat=\{0\}ms}"/>
</StackPanel>
</Grid>
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index b61abb5..39c9101 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -6,22 +6,27 @@
* of this license document, but changing it is not allowed.
*/
using System;
+using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Runtime.CompilerServices;
+using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
+using Avalonia.Input;
using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
-using Emgu.CV.Util;
using UVtools.Core;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
+using UVtools.Core.PixelEditor;
using UVtools.WPF.Controls;
using UVtools.WPF.Extensions;
using UVtools.WPF.Structures;
@@ -87,15 +92,28 @@ namespace UVtools.WPF
#endregion
#region Controls
+
+ public ProgressWindow ProgressWindow = new ProgressWindow();
+
+ //private ProgressWindow _progressWindow;
public AdvancedImageBox LayerImageBox;
public SliderEx LayerSlider;
public Panel LayerNavigationTooltipPanel;
public Border LayerNavigationTooltipBorder;
+
+ #region DataSets
+ public ObservableCollection<LogItem> Logs { get; } = new ObservableCollection<LogItem>();
+ public ObservableCollection<LayerIssue> Issues { get; } = new ObservableCollection<LayerIssue>();
+ public ObservableCollection<PixelOperation> Drawings { get; } = new ObservableCollection<PixelOperation>();
+ #endregion
+
#endregion
#region Members
private uint _actualLayer;
private bool _isGUIEnabled = true;
+ private bool _canSave;
+ private bool _isVerbose;
private bool _showLayerImageRotated;
private bool _showLayerImageDifference;
private bool _showLayerImageIssues = true;
@@ -113,7 +131,22 @@ namespace UVtools.WPF
public bool IsGUIEnabled
{
get => _isGUIEnabled;
- set => SetProperty(ref _isGUIEnabled, value);
+ set
+ {
+ if (!SetProperty(ref _isGUIEnabled, value)) return;
+ if (!_isGUIEnabled) return;
+ if (ProgressWindow.IsActive)
+ {
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ ProgressWindow.Close();
+ ProgressWindow.Dispose();
+ ProgressWindow = new ProgressWindow();
+ });
+ }
+
+
+ }
}
public bool IsFileLoaded
@@ -122,6 +155,18 @@ namespace UVtools.WPF
set => OnPropertyChanged();
}
+ public bool CanSave
+ {
+ get => _canSave;
+ set => SetProperty(ref _canSave, value);
+ }
+
+ public bool IsVerbose
+ {
+ get => _isVerbose;
+ set => SetProperty(ref _isVerbose, value);
+ }
+
public bool ShowLayerImageRotated
{
get => _showLayerImageRotated;
@@ -233,6 +278,21 @@ namespace UVtools.WPF
public bool CanGoUp => _actualLayer < SliderMaximumValue;
public bool CanGoDown => _actualLayer > 0;
+ public string LayerPixelCountStr
+ {
+ get
+ {
+ if (!LayerCache.IsCached) return "0";
+ var pixelPercent =
+ Math.Round(
+ LayerCache.Layer.NonZeroPixelCount * 100.0 / (SlicerFile.ResolutionX * SlicerFile.ResolutionY), 2);
+ return $"{LayerCache.Layer.NonZeroPixelCount} ({pixelPercent}%)";
+ }
+ }
+
+ public string LayerBoundsStr => LayerCache.Layer is null ? "NS" : LayerCache.Layer.BoundingRectangle.ToString();
+ public string LayerROIStr => LayerCache.Layer is null ? "NS" : "TODO";
+
public long ShowLayerRenderMs
{
get => _showLayerRenderMs;
@@ -240,7 +300,13 @@ namespace UVtools.WPF
}
#endregion
- private uint ActualLayer
+ public PixelPicker LayerPixelPicker { get; } = new PixelPicker();
+
+ public string LayerZoomStr => $"{LayerImageBox.Zoom / 100}x"+
+ (AppSettings.LockedZoomLevel == LayerImageBox.Zoom ? "🔒" : string.Empty);
+ public string LayerResolutionStr => SlicerFile?.Resolution.ToString() ?? "Unloaded";
+
+ public uint ActualLayer
{
get => _actualLayer;
set
@@ -250,6 +316,8 @@ namespace UVtools.WPF
OnPropertyChanged(nameof(CanGoUp));
OnPropertyChanged(nameof(ActualLayerTooltip));
OnPropertyChanged(nameof(LayerNavigationTooltipMargin));
+ OnPropertyChanged(nameof(LayerPixelCountStr));
+ OnPropertyChanged(nameof(LayerBoundsStr));
ShowLayer();
}
}
@@ -259,7 +327,7 @@ namespace UVtools.WPF
get
{
double top = 0;
- if (LayerSlider != null)
+ if (LayerSlider?.Track != null)
{
double trackerPos = LayerSlider.Track.Thumb.Bounds.Height / 2 + LayerSlider.Track.Thumb.Bounds.Top;
double halfTooltipHeight = LayerNavigationTooltipBorder.Bounds.Height / 2;
@@ -279,19 +347,12 @@ namespace UVtools.WPF
public LayerCache LayerCache = new LayerCache();
-
#endregion
#region Constructors
public MainWindow()
{
- _showLayerImageDifference = Settings.LayerPreview.ShowLayerDifference;
- _showLayerOutlinePrintVolumeBoundary = Settings.LayerPreview.VolumeBoundsOutline;
- _showLayerOutlineLayerBoundary = Settings.LayerPreview.LayerBoundsOutline;
- _showLayerOutlineHollowAreas = Settings.LayerPreview.HollowOutline;
-
- DataContext = this;
InitializeComponent();
#if DEBUG
//this.AttachDevTools();
@@ -301,6 +362,12 @@ namespace UVtools.WPF
LayerSlider = this.FindControl<SliderEx>("Layer.Navigation.Slider");
LayerNavigationTooltipPanel = this.FindControl<Panel>("Layer.Navigation.Tooltip.Panel");
LayerNavigationTooltipBorder = this.FindControl<Border>("Layer.Navigation.Tooltip.Border");
+
+ _showLayerImageDifference = Settings.LayerPreview.ShowLayerDifference;
+ _showLayerOutlinePrintVolumeBoundary = Settings.LayerPreview.VolumeBoundsOutline;
+ _showLayerOutlineLayerBoundary = Settings.LayerPreview.LayerBoundsOutline;
+ _showLayerOutlineHollowAreas = Settings.LayerPreview.HollowOutline;
+
/*LayerSlider.PropertyChanged += (sender, args) =>
{
Debug.WriteLine(args.Property.Name);
@@ -311,8 +378,22 @@ namespace UVtools.WPF
}
};*/
PropertyChanged += OnPropertyChanged;
+ DataContext = this;
+ AddHandler(DragDrop.DropEvent, (sender, e) =>
+ {
+ ProcessFiles(e.Data.GetFileNames().ToArray());
+ });
+
+ AddLog($"{About.Software} start");
}
+ public override void Show()
+ {
+ base.Show();
+ ProcessFiles(Program.Args);
+ }
+
+
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Debug.WriteLine(e.PropertyName);
@@ -332,14 +413,28 @@ namespace UVtools.WPF
#region Events
- public async void MenuFileOpenClicked()
+ public void MenuFileOpenClicked() => OpenFile();
+ public void MenuFileOpenNewWindowClicked() => OpenFile(true);
+
+ public async void OpenFile(bool newWindow = false)
{
var dialog = new OpenFileDialog
{
- AllowMultiple = false,
+ AllowMultiple = true,
+
};
var files = await dialog.ShowAsync(this);
- ProcessFiles(files);
+ ProcessFiles(files, newWindow);
+ }
+
+ public void CloseFile()
+ {
+ if (SlicerFile is null) return;
+ SlicerFile?.Dispose();
+ App.SlicerFile = null;
+ LayerCache.Clear();
+ LayerImageBox.Image = null;
+ ResetDataContext();
}
public async void MenuFileSettingsClicked()
@@ -347,6 +442,7 @@ namespace UVtools.WPF
SettingsWindow settingsWindow = new SettingsWindow();
await settingsWindow.ShowDialog(this);
}
+
#endregion
#region Methods
@@ -361,58 +457,63 @@ namespace UVtools.WPF
ProcessFile(files[i]);
continue;
}
+
+ App.NewInstance(files[i]);
+
}
}
- void ReloadFile(uint actualLayer = 0)
+ void ReloadFile() => ReloadFile(_actualLayer);
+
+ void ReloadFile(uint actualLayer)
{
if (App.SlicerFile is null) return;
- ProcessFile(App.SlicerFile.FileFullPath, actualLayer);
+ ProcessFile(SlicerFile.FileFullPath, _actualLayer);
}
- void ProcessFile(string fileName, uint actualLayer = 0)
+ async void ProcessFile(string fileName, uint actualLayer = 0)
{
+ if (!File.Exists(fileName)) return;
+ CloseFile();
var fileNameOnly = Path.GetFileName(fileName);
App.SlicerFile = FileFormat.FindByExtension(fileName, true, true);
- if (App.SlicerFile is null) return;
+ if (SlicerFile is null) return;
+ ProgressWindow.SetTitle($"Opening: {fileNameOnly}");
IsGUIEnabled = false;
-
var task = Task.Factory.StartNew(() =>
{
try
{
- App.SlicerFile.Decode(fileName);
+ SlicerFile.Decode(fileName, ProgressWindow.RestartProgress());
}
catch (OperationCanceledException)
{
- App.SlicerFile.Clear();
+ SlicerFile.Clear();
}
finally
{
- /* Invoke((MethodInvoker)delegate
- {
- // Running on the UI thread
- EnableGUI(true);
- });*/
+ IsGUIEnabled = true;
}
});
//ProgressWindow progressWindow = new ProgressWindow();
//progressWindow.ShowDialog(this);
- task.Wait();
+ await ProgressWindow.ShowDialog(this);
- var mat = App.SlicerFile[0].LayerMat;
+ /*var mat = App.SlicerFile[0].LayerMat;
var matRgb = App.SlicerFile[0].BrgMat;
Debug.WriteLine("4K grayscale - BGRA convertion:");
var bitmap = mat.ToBitmap();
Debug.WriteLine("4K BGR - BGRA convertion:");
var bitmapRgb = matRgb.ToBitmap();
- LayerImageBox.Image = bitmapRgb;
+ LayerImageBox.Image = bitmapRgb;*/
+
+ _actualLayer = actualLayer;
+ ShowLayer();
- IsGUIEnabled = true;
ResetDataContext();
}
@@ -444,6 +545,11 @@ namespace UVtools.WPF
ActualLayer = SliderMaximumValue;
}
+ public void ZoomToFit()
+ {
+ LayerImageBox.ZoomToFit();
+ }
+
/// <summary>
/// Shows a layer number
/// </summary>
@@ -453,17 +559,11 @@ namespace UVtools.WPF
Debug.WriteLine($"Showing layer: {_actualLayer}");
-
- //AddLogVerbose($"Show Layer: {layerNum}");
-
-
Stopwatch watch = Stopwatch.StartNew();
- var layer = SlicerFile[_actualLayer];
+ LayerCache.Layer = SlicerFile[_actualLayer];
try
{
- LayerCache.Image = layer.LayerMat;
-
//var imageSpan = LayerCache.Image.GetPixelSpan<byte>();
//var imageBgrSpan = LayerCache.ImageBgr.GetPixelSpan<byte>();
@@ -805,6 +905,7 @@ namespace UVtools.WPF
watch.Stop();
ShowLayerRenderMs = watch.ElapsedMilliseconds;
+ AddLogVerbose($"Show Layer: {_actualLayer}", watch.Elapsed.TotalSeconds);
}
catch (Exception e)
{
@@ -812,6 +913,25 @@ namespace UVtools.WPF
}
}
+
+ public void AddLog(LogItem log)
+ {
+ log.Index = Logs.Count;
+ Logs.Insert(0, log);
+ //lbLogOperations.Text = $"Operations: {count}";
+ }
+
+ public void AddLog(string description, double elapsedTime = 0) =>
+ AddLog(new LogItem(Logs.Count, description, elapsedTime));
+
+
+ public void AddLogVerbose(string description, double elapsedTime = 0)
+ {
+ if (!_isVerbose) return;
+ AddLog(description, elapsedTime);
+ Debug.WriteLine(description);
+ }
+
#endregion
}
}
diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs
index ea81577..b3f91bb 100644
--- a/UVtools.WPF/Program.cs
+++ b/UVtools.WPF/Program.cs
@@ -1,14 +1,21 @@
-using Avalonia;
+using System;
+using Avalonia;
namespace UVtools.WPF
{
- class Program
+ public static class Program
{
+ public static string[] Args;
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
- public static void Main(string[] args) => BuildAvaloniaApp()
- .StartWithClassicDesktopLifetime(args);
+ [STAThread]
+ public static void Main(string[] args)
+ {
+ Args = args;
+ BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+ }
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
diff --git a/UVtools.WPF/Structures/LogItem.cs b/UVtools.WPF/Structures/LogItem.cs
new file mode 100644
index 0000000..4dc6872
--- /dev/null
+++ b/UVtools.WPF/Structures/LogItem.cs
@@ -0,0 +1,61 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+using System;
+using UVtools.Core.Objects;
+
+namespace UVtools.WPF.Structures
+{
+ public sealed class LogItem : BindableBase
+ {
+ private int _index;
+ private string _startTime;
+ private double _elapsedTime;
+ private string _description;
+
+ public int Index
+ {
+ get => _index;
+ set => SetProperty(ref _index, value);
+ }
+
+ public string StartTime
+ {
+ get => _startTime;
+ set => SetProperty(ref _startTime, value);
+ }
+
+ public double ElapsedTime
+ {
+ get => _elapsedTime;
+ set => SetProperty(ref _elapsedTime, Math.Round(value, 2));
+ }
+
+ public string Description
+ {
+ get => _description;
+ set => SetProperty(ref _description, value);
+ }
+
+ public LogItem(int index, string description, double elapsedTime = 0)
+ {
+ _index = index;
+ _description = description;
+ ElapsedTime = elapsedTime;
+ _startTime = DateTime.Now.ToString("HH:mm:ss");
+ }
+
+ public LogItem(string description = null, uint elapsedTime = 0) : this(0, description, elapsedTime)
+ { }
+
+ public override string ToString()
+ {
+ return Description;
+ }
+ }
+}
diff --git a/UVtools.WPF/Structures/PixelPicker.cs b/UVtools.WPF/Structures/PixelPicker.cs
new file mode 100644
index 0000000..f011954
--- /dev/null
+++ b/UVtools.WPF/Structures/PixelPicker.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Text;
+using Avalonia;
+using ReactiveUI;
+
+namespace UVtools.WPF.Structures
+{
+ public class PixelPicker : ReactiveObject
+ {
+ private bool _isSet;
+ private Point _location = new Point(0,0);
+ private byte _brightness;
+
+ public bool IsSet
+ {
+ get => _isSet;
+ private set => this.RaiseAndSetIfChanged(ref _isSet, value);
+ }
+
+ public Point Location
+ {
+ get => _location;
+ private set => this.RaiseAndSetIfChanged(ref _location, value);
+ }
+
+ public byte Brightness
+ {
+ get => _brightness;
+ private set => this.RaiseAndSetIfChanged(ref _brightness, value);
+ }
+
+ public void Set(Point location, byte brightness)
+ {
+ Location = location;
+ Brightness = brightness;
+ IsSet = true;
+ }
+
+ public override string ToString()
+ {
+ return $"{{X={_location.X}, Y={_location.Y}, B={_brightness}}}";
+ }
+ }
+}
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index 6d7eac4..8e47665 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -22,6 +22,7 @@
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.0-preview5" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
+ <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.0-preview5" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.0-preview5" />
<PackageReference Include="Avalonia.ThemeManager" Version="0.10.0-preview3" />
<PackageReference Include="Dock.Avalonia" Version="0.10.0-preview5" />
diff --git a/UVtools.WPF/Windows/ProgressWindow.axaml b/UVtools.WPF/Windows/ProgressWindow.axaml
index fbcc4a2..435bd20 100644
--- a/UVtools.WPF/Windows/ProgressWindow.axaml
+++ b/UVtools.WPF/Windows/ProgressWindow.axaml
@@ -14,12 +14,21 @@
Icon="/Assets/Icons/UVtools.ico"
>
<StackPanel Orientation="Vertical">
- <TextBlock Margin="10" Text="Title"/>
- <TextBlock Margin="10,0,10,10" Text="Elapsed Time: "/>
- <TextBlock Margin="10,0,10,10" Text="Progress" HorizontalAlignment="Center"/>
+ <TextBlock Margin="10" Text="{Binding Title}"/>
+ <TextBlock Margin="10,0,10,10" Text="{Binding ElapsedTimeStr, StringFormat=Elapsed Time: \{0\}}"/>
+ <TextBlock Margin="10,0,10,10" Text="{Binding Description}" HorizontalAlignment="Center"/>
<Grid RowDefinitions="30" ColumnDefinitions="*,Auto">
- <ProgressBar Grid.Column="0" ShowProgressText="True"/>
- <Button Grid.Column="1" Padding="30,0" IsCancel="True">Cancel</Button>
+ <ProgressBar
+ Minimum="0"
+ Maximum="100"
+ IsIndeterminate="{Binding IsIndeterminate}"
+ Value="{Binding ProgressPercent}" Grid.Column="0" ShowProgressText="True"/>
+ <Button
+ IsEnabled="{Binding CanCancel}"
+ Command="{Binding TokenSource.Cancel}"
+ Grid.Column="1"
+ Padding="30,0"
+ IsCancel="True">Cancel</Button>
</Grid>
</StackPanel>
</Window>
diff --git a/UVtools.WPF/Windows/ProgressWindow.axaml.cs b/UVtools.WPF/Windows/ProgressWindow.axaml.cs
index b9744e6..953340a 100644
--- a/UVtools.WPF/Windows/ProgressWindow.axaml.cs
+++ b/UVtools.WPF/Windows/ProgressWindow.axaml.cs
@@ -5,22 +5,79 @@
* Everyone is permitted to copy and distribute verbatim copies
* of this license document, but changing it is not allowed.
*/
+
+using System;
+using System.Diagnostics;
+using System.Timers;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+using UVtools.Core.Operations;
+using UVtools.WPF.Structures;
namespace UVtools.WPF.Windows
{
- public class ProgressWindow : Window
+ public class ProgressWindow : Window, IDisposable
{
+ public Stopwatch StopWatch { get; } = new Stopwatch();
+ public OperationProgress Progress { get; } = new OperationProgress();
+
+ private LogItem _logItem = new LogItem();
+
+ private Timer _timer;
+
+ public bool CanCancel => Progress?.CanCancel ?? false;
+
public ProgressWindow()
{
InitializeComponent();
+ DataContext = Progress;
+ _timer = new Timer(100) {AutoReset = true};
+ _timer.Elapsed += (sender, args) =>
+ {
+ Progress.TriggerRefresh();
+ };
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
-
+ }
+
+ protected override void OnClosed(EventArgs e)
+ {
+ base.OnClosed(e);
+ _timer.Stop();
+ Progress.StopWatch.Stop();
+ _logItem.ElapsedTime = Math.Round(Progress.StopWatch.Elapsed.TotalSeconds, 2);
+ App.MainWindow.AddLog(_logItem);
+ }
+
+ public void SetTitle(string title)
+ {
+ Progress.Title = title;
+ _logItem.Description = title;
+ }
+
+ public OperationProgress RestartProgress(bool canCancel = true)
+ {
+ Progress.CanCancel = canCancel;
+ Progress.StopWatch.Restart();
+
+ if (!_timer.Enabled)
+ {
+ _timer.Enabled = true;
+ _timer.Start();
+ }
+
+ Dispatcher.UIThread.InvokeAsync(() => DataContext = Progress);
+ return Progress;
+ }
+
+ public void Dispose()
+ {
+ _timer.Close();
+ _timer.Dispose();
}
}
}