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

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2021-02-21 06:38:37 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2021-02-21 06:38:37 +0300
commit4ec98cfab14f4e2c8cd1ba246c85e75ea67d1934 (patch)
treee051c435005e44a6ce4ed053b31b4fe2644f42cd
parent9dffbd77b957c95c475ea35ad20d4410fa0f2378 (diff)
v2.5.0v2.5.0
* (Add) Help - Material manager (F10): Allow to manage material stock and costs with statistic over time * (Add) File - I printed this file (CTRL + P): Allow to select a material and consume resin from stock and print time from the loaded file
-rw-r--r--CHANGELOG.md5
-rw-r--r--UVtools.Core/Managers/MaterialManager.cs271
-rw-r--r--UVtools.Core/Objects/Material.cs99
-rw-r--r--UVtools.Core/Operations/OperationIPrintedThisFile.cs138
-rw-r--r--UVtools.Core/UVtools.Core.csproj4
-rw-r--r--UVtools.WPF/App.axaml.cs4
-rw-r--r--UVtools.WPF/Assets/Icons/flask-16x16.pngbin0 -> 162 bytes
-rw-r--r--UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml53
-rw-r--r--UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml.cs21
-rw-r--r--UVtools.WPF/MainWindow.axaml21
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs17
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj7
-rw-r--r--UVtools.WPF/Windows/MaterialManagerWindow.axaml254
-rw-r--r--UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs97
-rw-r--r--UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml (renamed from UVtools.WPF/Windows/PrusaSlicerManager.axaml)2
-rw-r--r--UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml.cs (renamed from UVtools.WPF/Windows/PrusaSlicerManager.axaml.cs)4
16 files changed, 967 insertions, 30 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9999cb..38ceaba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## 21/02/2021 - v2.5.0
+
+* (Add) Help - Material manager (F10): Allow to manage material stock and costs with statistic over time
+* (Add) File - I printed this file (CTRL + P): Allow to select a material and consume resin from stock and print time from the loaded file
+
## 19/02/2021 - v2.4.9
* **(Fix) PhotonWorkshop files: (#149)**
diff --git a/UVtools.Core/Managers/MaterialManager.cs b/UVtools.Core/Managers/MaterialManager.cs
new file mode 100644
index 0000000..bd0446b
--- /dev/null
+++ b/UVtools.Core/Managers/MaterialManager.cs
@@ -0,0 +1,271 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Xml.Serialization;
+using UVtools.Core.Objects;
+
+namespace UVtools.Core.Managers
+{
+ public class MaterialManager : BindableBase, IList<Material>
+ {
+ #region Settings
+
+ public static string FilePath;
+ #endregion
+
+ #region Singleton
+
+ private static Lazy<MaterialManager> _instanceHolder =
+ new(() => new MaterialManager());
+
+ /// <summary>
+ /// Instance of <see cref="UserSettings"/> (singleton)
+ /// </summary>
+ public static MaterialManager Instance => _instanceHolder.Value;
+
+ //public static List<Operation> Operations => _instance.Operations;
+ #endregion
+
+ #region Members
+
+ private ObservableCollection<Material> _materials = new();
+
+ #endregion
+
+ #region Properties
+
+ public ObservableCollection<Material> Materials
+ {
+ get => _materials;
+ set => RaiseAndSetIfChanged(ref _materials, value);
+ }
+
+ /// <summary>
+ /// Gets the total number of bottles in stock
+ /// </summary>
+ public int BottlesInStock => this.Sum(material => material.BottlesInStock);
+
+ /// <summary>
+ /// Gets the total number of bottles ever owned
+ /// </summary>
+ public int OwnedBottles => this.Sum(material => material.OwnedBottles);
+
+ /// <summary>
+ /// Gets the total of consumed volume in milliliters
+ /// </summary>
+ public decimal ConsumedVolume => this.Sum(material => material.ConsumedVolume);
+
+ /// <summary>
+ /// Gets the total of consumed volume in liters
+ /// </summary>
+ public decimal ConsumedVolumeLiters => this.Sum(material => material.ConsumedVolumeLiters);
+
+ /// <summary>
+ /// Gets the total volume in stock in milliliters
+ /// </summary>
+ public decimal VolumeInStock => this.Sum(material => material.VolumeInStock);
+
+ /// <summary>
+ /// Gets the total volume in stock in liters
+ /// </summary>
+ public decimal VolumeInStockLiters => VolumeInStock / 1000;
+
+ /// <summary>
+ /// Gets the total costs
+ /// </summary>
+ public decimal TotalCost => this.Sum(material => material.TotalCost);
+
+ /// <summary>
+ /// Gets the total print time in hours
+ /// </summary>
+ public double PrintTime => this.Sum(material => material.PrintTime);
+
+ /// <summary>
+ /// Gets the total print time
+ /// </summary>
+ public TimeSpan PrintTimeSpan => TimeSpan.FromHours(PrintTime);
+
+ #endregion
+
+ #region Constructor
+ private MaterialManager()
+ {
+ }
+ #endregion
+
+ #region Methods
+ public Material this[int index]
+ {
+ get => _materials[index];
+ set => _materials[index] = value;
+ }
+
+ public Material this[uint index]
+ {
+ get => _materials[(int) index];
+ set => _materials[(int) index] = value;
+ }
+
+ public Material this[Material material]
+ {
+ get
+ {
+ var index = IndexOf(material);
+ return index < 0 ? this[index] : null;
+ }
+ set
+ {
+ var index = IndexOf(material);
+ if(index >= 0) this[index] = value;
+ }
+ }
+
+ public IEnumerator<Material> GetEnumerator() => _materials.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ public void Add(Material item)
+ {
+ _materials.Add(item);
+ RaisePropertiesChanged();
+ }
+
+ public void Add(Material item, bool save)
+ {
+ Add(item);
+ if (save) Save();
+ }
+
+ public void Clear()
+ {
+ _materials.Clear();
+ RaisePropertiesChanged();
+ }
+
+ public void Clear(bool save)
+ {
+ Clear();
+ if(save) Save();
+ }
+
+
+ public bool Contains(Material item) => _materials.Contains(item);
+
+ public void CopyTo(Material[] array, int arrayIndex)
+ {
+ _materials.CopyTo(array, arrayIndex);
+ RaisePropertiesChanged();
+ }
+
+ public bool Remove(Material item)
+ {
+ if (_materials.Remove(item))
+ {
+ RaisePropertiesChanged();
+ return true;
+ }
+
+ return false;
+ }
+
+ public void Remove(Material item, bool save)
+ {
+ Remove(item);
+ if (save) Save();
+ }
+
+ public int Count => _materials.Count;
+ public bool IsReadOnly => false;
+ public int IndexOf(Material item) => _materials.IndexOf(item);
+
+ public void Insert(int index, Material item)
+ {
+ _materials.Insert(index, item);
+ RaisePropertiesChanged();
+ }
+
+ public void Insert(int index, Material item, bool save)
+ {
+ Insert(index, item);
+ if (save) Save();
+ }
+
+ public void RemoveAt(int index)
+ {
+ _materials.RemoveAt(index);
+ RaisePropertiesChanged();
+ }
+
+ public void RemoveAt(int index, bool save)
+ {
+ RemoveAt(index);
+ if (save) Save();
+ }
+
+ public void RaisePropertiesChanged()
+ {
+ RaisePropertyChanged(nameof(BottlesInStock));
+ RaisePropertyChanged(nameof(OwnedBottles));
+ RaisePropertyChanged(nameof(ConsumedVolume));
+ RaisePropertyChanged(nameof(ConsumedVolumeLiters));
+ RaisePropertyChanged(nameof(VolumeInStock));
+ RaisePropertyChanged(nameof(VolumeInStockLiters));
+ RaisePropertyChanged(nameof(TotalCost));
+ RaisePropertyChanged(nameof(PrintTime));
+ RaisePropertyChanged(nameof(PrintTimeSpan));
+ RaisePropertyChanged(nameof(Count));
+ }
+
+ /// <summary>
+ /// Load settings from file
+ /// </summary>
+ public static void Load()
+ {
+ if (string.IsNullOrWhiteSpace(FilePath) || !File.Exists(FilePath))
+ {
+ return;
+ }
+
+ var serializer = new XmlSerializer(Instance.GetType());
+ try
+ {
+ using var myXmlReader = new StreamReader(FilePath);
+ var instance = (MaterialManager)serializer.Deserialize(myXmlReader);
+ _instanceHolder = new Lazy<MaterialManager>(() => instance);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e.ToString());
+ }
+ }
+
+ /// <summary>
+ /// Save settings to file
+ /// </summary>
+ public static void Save()
+ {
+ var serializer = new XmlSerializer(Instance.GetType());
+ try
+ {
+ using var myXmlWriter = new StreamWriter(FilePath);
+ serializer.Serialize(myXmlWriter, Instance);
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e.ToString());
+ }
+ }
+
+ #endregion
+
+ public void SortByName()
+ {
+ var materials = _materials.ToList();
+ materials.Sort((material, material1) => string.Compare(material.Name, material1.Name, StringComparison.Ordinal));
+ Materials = new ObservableCollection<Material>(materials);
+ }
+ }
+} \ No newline at end of file
diff --git a/UVtools.Core/Objects/Material.cs b/UVtools.Core/Objects/Material.cs
index fed7392..84f39fa 100644
--- a/UVtools.Core/Objects/Material.cs
+++ b/UVtools.Core/Objects/Material.cs
@@ -13,17 +13,17 @@ namespace UVtools.Core.Objects
/// <summary>
/// Represents a material to feed in the printer
/// </summary>
- public class Material : BindableBase
+ public class Material : BindableBase, ICloneable
{
#region Members
private string _name;
- private decimal _bottleVolume = 1000;
+ private uint _bottleVolume = 1000;
private decimal _density = 1;
private decimal _bottleCost = 30;
private int _bottlesInStock = 1;
private decimal _bottleRemainingVolume = 1000;
- private decimal _totalConsumedVolume;
- private double _totalPrintTime;
+ private decimal _consumedVolume;
+ private double _printTime;
#endregion
@@ -37,14 +37,16 @@ namespace UVtools.Core.Objects
/// <summary>
/// Gets or sets the bottle volume in milliliters
/// </summary>
- public decimal BottleVolume
+ public uint BottleVolume
{
get => _bottleVolume;
set
{
if(!RaiseAndSetIfChanged(ref _bottleVolume, value)) return;
RaisePropertyChanged(nameof(BottleWeight));
- RaisePropertyChanged(nameof(TotalConsumedBottles));
+ RaisePropertyChanged(nameof(ConsumedBottles));
+ RaisePropertyChanged(nameof(TotalCost));
+ RaisePropertyChanged(nameof(VolumeInStock));
}
}
@@ -72,16 +74,28 @@ namespace UVtools.Core.Objects
public decimal BottleCost
{
get => _bottleCost;
- set => RaiseAndSetIfChanged(ref _bottleCost, value);
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _bottleCost, value)) return;
+ RaisePropertyChanged(nameof(TotalCost));
+ }
}
+ public decimal TotalCost => OwnedBottles * _bottleCost;
+
/// <summary>
/// Gets or sets the number of bottles in stock
/// </summary>
public int BottlesInStock
{
get => _bottlesInStock;
- set => RaiseAndSetIfChanged(ref _bottlesInStock, value);
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _bottlesInStock, value)) return;
+ RaisePropertyChanged(nameof(OwnedBottles));
+ RaisePropertyChanged(nameof(TotalCost));
+ RaisePropertyChanged(nameof(VolumeInStock));
+ }
}
/// <summary>
@@ -89,47 +103,75 @@ namespace UVtools.Core.Objects
/// </summary>
public decimal BottleRemainingVolume
{
- get => _bottleRemainingVolume;
- set => RaiseAndSetIfChanged(ref _bottleRemainingVolume, value);
+ get => Math.Round(_bottleRemainingVolume, 2);
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _bottleRemainingVolume, value)) return;
+ RaisePropertyChanged(nameof(VolumeInStock));
+ }
}
/// <summary>
+ /// Gets the total available volume in stock in milliliters
+ /// </summary>
+ public decimal VolumeInStock => _bottlesInStock * _bottleVolume - (_bottleVolume - _bottleRemainingVolume);
+
+ /// <summary>
/// Gets the number of consumed bottles
/// </summary>
- public uint TotalConsumedBottles => (uint) Math.Floor(_totalConsumedVolume / _bottleVolume);
+ public uint ConsumedBottles => (uint) Math.Floor(_consumedVolume / _bottleVolume);
+
+ /// <summary>
+ /// Gets the total number of owned bottles
+ /// </summary>
+ public int OwnedBottles => (int) (_bottlesInStock + ConsumedBottles);
/// <summary>
/// Gets or sets the total number of consumed volume in milliliters
/// </summary>
- public decimal TotalConsumedVolume
+ public decimal ConsumedVolume
{
- get => _totalConsumedVolume;
- set => RaiseAndSetIfChanged(ref _totalConsumedVolume, value);
+ get => _consumedVolume;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _consumedVolume, value)) return;
+ RaisePropertyChanged(nameof(ConsumedVolumeLiters));
+ }
}
/// <summary>
+ /// Gets total number of consumed volume in liters
+ /// </summary>
+ public decimal ConsumedVolumeLiters => ConsumedVolume / 1000;
+
+ /// <summary>
/// Gets or sets the total print time using with material in hours
/// </summary>
- public double TotalPrintTime
+ public double PrintTime
{
- get => _totalPrintTime;
- set => RaiseAndSetIfChanged(ref _totalPrintTime, value);
+ get => _printTime;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _printTime, value)) return;
+ RaisePropertyChanged(nameof(PrintTimeSpan));
+ }
}
- public TimeSpan TotalPrintTimeSpan => TimeSpan.FromHours(_totalPrintTime);
+ public TimeSpan PrintTimeSpan => TimeSpan.FromHours(_printTime);
#endregion
#region Constructors
public Material() { }
- public Material(string name, decimal bottleVolume = 1000, decimal density = 1, decimal bottleCost = 30, int bottlesInStock = 1)
+ public Material(string name, uint bottleVolume = 1000, decimal density = 1, decimal bottleCost = 30, int bottlesInStock = 1)
{
_name = name;
_bottleVolume = bottleVolume;
_density = density;
_bottleCost = bottleCost;
_bottlesInStock = bottlesInStock;
+ _bottleRemainingVolume = bottleVolume;
}
#endregion
@@ -155,7 +197,17 @@ namespace UVtools.Core.Objects
public override string ToString()
{
- return $"{nameof(Name)}: {Name}, {nameof(BottleVolume)}: {BottleVolume}ml, {nameof(BottleWeight)}: {BottleWeight}g, {nameof(Density)}: {Density}g/ml, {nameof(BottleCost)}: {BottleCost}, {nameof(BottlesInStock)}: {BottlesInStock}, {nameof(BottleRemainingVolume)}: {BottleRemainingVolume}ml, {nameof(TotalConsumedBottles)}: {TotalConsumedBottles}, {nameof(TotalConsumedVolume)}: {TotalConsumedVolume}ml, {nameof(TotalPrintTime)}: {TotalPrintTime:F4}h";
+ return $"{_name} ({_bottleRemainingVolume}/{VolumeInStock}ml)";
+ }
+
+ public object Clone()
+ {
+ return MemberwiseClone();
+ }
+
+ public Material CloneMaterial()
+ {
+ return (Material)Clone();
}
#endregion
@@ -200,8 +252,9 @@ namespace UVtools.Core.Objects
}
BottlesInStock -= consumedBottles;
+ ConsumedVolume += volume;
- AddPrintTime(printSeconds);
+ AddPrintTimeSeconds(printSeconds);
return _bottlesInStock > 0;
}
@@ -210,10 +263,10 @@ namespace UVtools.Core.Objects
/// Add print time with this material
/// </summary>
/// <param name="seconds">Seconds to add</param>
- public void AddPrintTime(double seconds)
+ public void AddPrintTimeSeconds(double seconds)
{
if (seconds <= 0) return;
- TotalPrintTime += seconds / 60 / 60;
+ PrintTime += seconds / 60 / 60;
}
#endregion
}
diff --git a/UVtools.Core/Operations/OperationIPrintedThisFile.cs b/UVtools.Core/Operations/OperationIPrintedThisFile.cs
new file mode 100644
index 0000000..851c4d1
--- /dev/null
+++ b/UVtools.Core/Operations/OperationIPrintedThisFile.cs
@@ -0,0 +1,138 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+using System;
+using System.Text;
+using UVtools.Core.FileFormats;
+using UVtools.Core.Managers;
+using UVtools.Core.Objects;
+
+namespace UVtools.Core.Operations
+{
+ [Serializable]
+ public class OperationIPrintedThisFile : Operation
+ {
+ #region Members
+ private decimal _volume;
+ private float _printTime;
+ private Material _materialItem;
+
+ #endregion
+
+ #region Overrides
+
+ public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None;
+
+ public override bool CanROI => false;
+ public override bool CanHaveProfiles => false;
+ public override string ButtonOkText => "Consume";
+
+ public override string Title => "I printed this file";
+ public override string Description => "Select a material and consume resin from stock and print time.";
+
+ public override string ConfirmationText =>
+ $"consume {_volume}ml and {PrintTimeHours}h on:\n{_materialItem} ?";
+
+ public override string ProgressTitle =>
+ $"Consuming";
+
+ public override string ProgressAction => "Consumed";
+
+ public override StringTag Validate(params object[] parameters)
+ {
+ var sb = new StringBuilder();
+
+ if (_materialItem is null)
+ {
+ sb.AppendLine("You must select an material.");
+ }
+ if (_volume <= 0)
+ {
+ sb.AppendLine("Volume must be higher than 0ml.");
+ }
+ if (_printTime <= 0)
+ {
+ sb.AppendLine("Print time must be higher than 0s.");
+ }
+
+ return new StringTag(sb.ToString());
+ }
+
+ public override string ToString()
+ {
+ var result = $"{_volume}ml {PrintTimeHours}h";
+ if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
+ return result;
+ }
+ #endregion
+
+ #region Properties
+
+ public Material MaterialItem
+ {
+ get => _materialItem;
+ set => RaiseAndSetIfChanged(ref _materialItem, value);
+ }
+
+ public decimal Volume
+ {
+ get => _volume;
+ set => RaiseAndSetIfChanged(ref _volume, value);
+ }
+
+ public float PrintTime
+ {
+ get => _printTime;
+ set
+ {
+ if(!RaiseAndSetIfChanged(ref _printTime, value)) return;
+ RaisePropertyChanged(nameof(PrintTimeHours));
+ }
+ }
+
+ public float PrintTimeHours => _printTime / 60 / 60;
+
+ public MaterialManager Manager => MaterialManager.Instance;
+
+ #endregion
+
+ #region Constructor
+
+ public OperationIPrintedThisFile() { }
+
+ public OperationIPrintedThisFile(FileFormat slicerFile) : base(slicerFile) { }
+
+ public override void InitWithSlicerFile()
+ {
+ base.InitWithSlicerFile();
+ _volume = (decimal) SlicerFile.MaterialMilliliters;
+ _printTime = SlicerFile.PrintTime;
+ MaterialManager.Load();
+ }
+
+ #endregion
+
+ #region Methods
+
+ protected override bool ExecuteInternally(OperationProgress progress)
+ {
+ if (MaterialItem is null) return !progress.Token.IsCancellationRequested;
+
+ MaterialItem.Consume(_volume, _printTime);
+
+ MaterialManager.Save();
+ return !progress.Token.IsCancellationRequested;
+ }
+
+ #endregion
+
+ #region Equality
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index 37302e0..9c4fcf6 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
- <Version>2.4.9</Version>
+ <Version>2.5.0</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
@@ -44,7 +44,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="BinarySerializer" Version="8.5.3" />
+ <PackageReference Include="BinarySerializer" Version="8.6.0" />
<PackageReference Include="Emgu.CV" Version="4.5.1.4349" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Memory" Version="4.5.4" />
diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs
index 219a7db..7a454a1 100644
--- a/UVtools.WPF/App.axaml.cs
+++ b/UVtools.WPF/App.axaml.cs
@@ -22,6 +22,7 @@ using Avalonia.ThemeManager;
using Emgu.CV;
using UVtools.Core;
using UVtools.Core.FileFormats;
+using UVtools.Core.Managers;
using UVtools.WPF.Extensions;
using UVtools.WPF.Structures;
@@ -52,6 +53,9 @@ namespace UVtools.WPF
OperationProfiles.Load();
+ MaterialManager.FilePath = Path.Combine(UserSettings.SettingsFolder, "materials.xml");
+ MaterialManager.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")
diff --git a/UVtools.WPF/Assets/Icons/flask-16x16.png b/UVtools.WPF/Assets/Icons/flask-16x16.png
new file mode 100644
index 0000000..4cd3c0a
--- /dev/null
+++ b/UVtools.WPF/Assets/Icons/flask-16x16.png
Binary files differ
diff --git a/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml b/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml
new file mode 100644
index 0000000..7d2e04f
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml
@@ -0,0 +1,53 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+ x:Class="UVtools.WPF.Controls.Tools.ToolIPrintedThisFileControl">
+
+ <Grid RowDefinitions="Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,10,Auto">
+ <TextBlock Grid.Row="0" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="The material you want to consume from (remaining in current bottle / total in stock)"
+ Text="Material:"/>
+ <ComboBox Grid.Row="0" Grid.Column="2"
+ HorizontalAlignment="Stretch"
+ MinWidth="600"
+ Items="{Binding Operation.Manager}"
+ SelectedItem="{Binding Operation.MaterialItem}"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Volume:"/>
+
+ <StackPanel Grid.Row="2" Grid.Column="2"
+ Orientation="Horizontal" Spacing="5">
+ <NumericUpDown Minimum="1"
+ Maximum="1000000"
+ Increment="1"
+ MinWidth="200"
+ Value="{Binding Operation.Volume}"/>
+ <TextBlock VerticalAlignment="Center" Text="ml"/>
+
+ </StackPanel>
+
+
+ <TextBlock Grid.Row="4" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Print time:"/>
+
+ <StackPanel Grid.Row="4" Grid.Column="2"
+ Orientation="Horizontal" Spacing="5">
+ <NumericUpDown Minimum="1"
+ Maximum="100000000"
+ Increment="1"
+ MinWidth="200"
+ FormatString="F2"
+ Value="{Binding Operation.PrintTime}"/>
+ <TextBlock VerticalAlignment="Center"
+ Text="{Binding Operation.PrintTimeHours, StringFormat=s / {0:N4}h}"/>
+ </StackPanel>
+ </Grid>
+
+</UserControl>
diff --git a/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml.cs
new file mode 100644
index 0000000..91ed2f1
--- /dev/null
+++ b/UVtools.WPF/Controls/Tools/ToolIPrintedThisFileControl.axaml.cs
@@ -0,0 +1,21 @@
+using Avalonia.Markup.Xaml;
+using UVtools.Core.Operations;
+
+namespace UVtools.WPF.Controls.Tools
+{
+ public class ToolIPrintedThisFileControl : ToolControl
+ {
+ public OperationIPrintedThisFile Operation => BaseOperation as OperationIPrintedThisFile;
+
+ public ToolIPrintedThisFileControl()
+ {
+ InitializeComponent();
+ BaseOperation = new OperationIPrintedThisFile(SlicerFile);
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index 93d132f..2229ca7 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -72,6 +72,15 @@
</MenuItem>
<Separator/>
+
+ <MenuItem
+ Header="I _printed this file" HotKey="Ctrl+P" InputGesture="Ctrl+P"
+ IsEnabled="{Binding IsFileLoaded}"
+ Command="{Binding IPrintedThisFile}">
+ <MenuItem.Icon>
+ <Image Source="\Assets\Icons\flask-16x16.png"/>
+ </MenuItem.Icon>
+ </MenuItem>
<MenuItem
Name="MainMenu.File.Extract"
@@ -183,12 +192,24 @@
<Separator/>
<MenuItem
+ Header="_Material manager"
+ HotKey="F10"
+ InputGesture="F10"
+ Command="{Binding MenuHelpMaterialManagerClicked}">
+ <MenuItem.Icon>
+ <Image Source="\Assets\Icons\flask-16x16.png"/>
+ </MenuItem.Icon>
+ </MenuItem>
+
+ <MenuItem
Header="_Install profiles into PrusaSlicer"
Command="{Binding MenuHelpInstallProfilesClicked}">
<MenuItem.Icon>
<Image Source="\Assets\Icons\CNCMachine-16x16.png"/>
</MenuItem.Icon>
</MenuItem>
+
+ <Separator/>
<MenuItem
Header="_Open settings folder"
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index 90882ee..8553b3b 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -805,6 +805,11 @@ namespace UVtools.WPF
App.StartProcess(UserSettings.SettingsFolder);
}
+ private async void MenuHelpMaterialManagerClicked()
+ {
+ await new MaterialManagerWindow().ShowDialog(this);
+ }
+
public async void MenuHelpInstallProfilesClicked()
{
var PEFolder = App.GetPrusaSlicerDirectory();
@@ -818,7 +823,7 @@ namespace UVtools.WPF
"Unable to detect PrusaSlicer") == ButtonResult.Yes) App.OpenBrowser("https://www.prusa3d.com/prusaslicer/");
return;
}
- await new PrusaSlicerManager().ShowDialog(this);
+ await new PrusaSlicerManagerWindow().ShowDialog(this);
}
public async void MenuNewVersionClicked()
@@ -1149,6 +1154,8 @@ namespace UVtools.WPF
ProgressWindow = new ProgressWindow(title);
}
+
+
private async void ConvertToOnTapped(object? sender, RoutedEventArgs e)
{
if (sender is not MenuItem item) return;
@@ -1295,6 +1302,11 @@ namespace UVtools.WPF
return task;
}
+ public async void IPrintedThisFile()
+ {
+ await ShowRunOperation(typeof(OperationIPrintedThisFile));
+ }
+
public async void ExtractFile()
{
if (!IsFileLoaded) return;
@@ -1413,6 +1425,9 @@ namespace UVtools.WPF
CanSave = true;
return true;
+ case OperationIPrintedThisFile operation:
+ operation.Execute();
+ return true;
case OperationRepairLayers operation:
if (Issues is null)
{
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index 65e1c9a..2a353c6 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -12,7 +12,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
- <Version>2.4.9</Version>
+ <Version>2.5.0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -75,4 +75,9 @@
</AvaloniaResource>
<AvaloniaResource Include="Assets\Icons\*" />
</ItemGroup>
+ <ItemGroup>
+ <Compile Update="Windows\PrusaSlicerManagerWindow.axaml.cs">
+ <DependentUpon>PrusaSlicerManagerWindow.axaml</DependentUpon>
+ </Compile>
+ </ItemGroup>
</Project>
diff --git a/UVtools.WPF/Windows/MaterialManagerWindow.axaml b/UVtools.WPF/Windows/MaterialManagerWindow.axaml
new file mode 100644
index 0000000..52908af
--- /dev/null
+++ b/UVtools.WPF/Windows/MaterialManagerWindow.axaml
@@ -0,0 +1,254 @@
+<controls: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:controls="clr-namespace:UVtools.WPF.Controls"
+ mc:Ignorable="d" d:DesignWidth="1000" d:DesignHeight="450"
+ x:Class="UVtools.WPF.Windows.MaterialManagerWindow"
+ Title="Material manager"
+ MinWidth="900"
+ MinHeight="600"
+ WindowStartupLocation="CenterOwner"
+ Icon="/Assets/Icons/UVtools.ico">
+
+ <Grid RowDefinitions="Auto,Auto,Auto,*">
+ <Border Grid.Row="0" Padding="5">
+ <Expander IsExpanded="True">
+ <Expander.Header>
+ <TextBlock Text="Global statistics"
+ FontWeight="Bold"
+ Cursor="Hand"/>
+ </Expander.Header>
+
+ <StackPanel>
+ <!--<Button VerticalAlignment="Center"
+ Command="{Binding RefreshStatistics}">
+ <StackPanel Orientation="Horizontal" Spacing="10">
+ <Image Source="/Assets/Icons/refresh-16x16.png"/>
+ <TextBlock Text="Refresh statistics"/>
+ </StackPanel>
+ </Button>
+ !-->
+
+ <WrapPanel Orientation="Horizontal">
+ <StackPanel Orientation="Horizontal">
+ <TextBlock Text="Bottles in stock:" VerticalAlignment="Center" FontWeight="Bold"/>
+ <TextBox Classes="TransparentReadOnly"
+ Text="{Binding Manager.BottlesInStock, StringFormat=\{0:N0\}}"/>
+ </StackPanel>
+
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock Text="Owned bottles:" VerticalAlignment="Center" FontWeight="Bold"/>
+ <TextBox Classes="TransparentReadOnly"
+ Text="{Binding Manager.OwnedBottles, StringFormat=\{0:N0\}}"/>
+ </StackPanel>
+
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock Text="Consumed volume:" VerticalAlignment="Center" FontWeight="Bold"/>
+ <TextBox Classes="TransparentReadOnly"
+ Text="{Binding Manager.ConsumedVolumeLiters, StringFormat=\{0:N4\}}"/>
+ <TextBlock Text="liters" VerticalAlignment="Center"/>
+ </StackPanel>
+
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock Text="Volume in stock:" VerticalAlignment="Center" FontWeight="Bold"/>
+ <TextBox Classes="TransparentReadOnly"
+ Text="{Binding Manager.VolumeInStockLiters, StringFormat=\{0:N4\}}"/>
+ <TextBlock Text="liters" VerticalAlignment="Center"/>
+ </StackPanel>
+
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock Text="Spent:" VerticalAlignment="Center" FontWeight="Bold"/>
+ <TextBox Classes="TransparentReadOnly"
+ Text="{Binding Manager.TotalCost, StringFormat=\{0:N2\}}"/>
+ </StackPanel>
+
+ <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
+ <TextBlock Text="Print time:" VerticalAlignment="Center" FontWeight="Bold"/>
+ <TextBox Classes="TransparentReadOnly"
+ Text="{Binding Manager.PrintTimeSpan.TotalDays, StringFormat=\{0:N4\}}"/>
+ <TextBlock Text="days" VerticalAlignment="Center"/>
+ </StackPanel>
+ </WrapPanel>
+
+ </StackPanel>
+
+ </Expander>
+ </Border>
+
+
+ <Border Grid.Row="1" BorderBrush="Black" BorderThickness="1" Padding="5">
+ <Expander IsExpanded="False">
+ <Expander.Header>
+ <TextBlock Text="Add new material"
+ FontWeight="Bold"
+ Cursor="Hand"/>
+ </Expander.Header>
+
+ <Grid
+ RowDefinitions="Auto,10,Auto,10,Auto,10,Auto"
+ ColumnDefinitions="Auto,10,150,5,Auto,20,Auto,10,Auto,5,Auto,20,Auto,10,150,5,Auto,20,Auto,10,Auto,5,Auto">
+
+ <TextBlock Grid.Row="0" Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Name:"/>
+
+ <TextBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="17"
+ HorizontalAlignment="Stretch"
+ Watermark="A descriptive material name, eg: Epax Hard Grey"
+ Text="{Binding Material.Name}"/>
+
+ <Button
+ Grid.Row="0" Grid.Column="20" Grid.ColumnSpan="3"
+ VerticalContentAlignment="Center"
+ HorizontalContentAlignment="Stretch"
+ VerticalAlignment="Stretch"
+ Command="{Binding AddNewMaterial}">
+ <StackPanel Orientation="Horizontal" Spacing="10">
+ <Image Source="/Assets/Icons/plus-16x16.png"/>
+ <TextBlock
+ VerticalAlignment="Center"
+ Text="Add new material"/>
+ </StackPanel>
+ </Button>
+
+ <TextBlock Grid.Row="2" Grid.Column="0"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Bottle material volume"
+ Text="Volume:"/>
+
+ <NumericUpDown Grid.Row="2" Grid.Column="2"
+ Minimum="100"
+ Maximum="100000"
+ Increment="100"
+ Value="{Binding Material.BottleVolume}"
+ />
+ <TextBlock Grid.Row="2" Grid.Column="4"
+ VerticalAlignment="Center"
+ Text="ml"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="6"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Used to calculate weight and cost"
+ Text="Density:"/>
+
+ <NumericUpDown Grid.Row="2" Grid.Column="8"
+ Minimum="0.1"
+ Maximum="10"
+ Increment="0.1"
+ Value="{Binding Material.Density}"/>
+ <TextBlock Grid.Row="2" Grid.Column="10"
+ VerticalAlignment="Center"
+ Text="g/ml"/>
+
+ <TextBlock Grid.Row="2" Grid.Column="12"
+ VerticalAlignment="Center"
+ ToolTip.Tip="One bottle unit cost"
+ Text="Cost:"/>
+
+ <NumericUpDown Grid.Row="2" Grid.Column="14"
+ Minimum="1"
+ Maximum="100000"
+ Increment="1"
+ Value="{Binding Material.BottleCost}"/>
+ <TextBlock Grid.Row="2" Grid.Column="16"
+ VerticalAlignment="Center"
+ Text=""/>
+
+ <TextBlock Grid.Row="2" Grid.Column="18"
+ VerticalAlignment="Center"
+ ToolTip.Tip="Number of bottles in stock"
+ Text="Stock:"/>
+
+ <NumericUpDown Grid.Row="2" Grid.Column="20"
+ Minimum="1"
+ Maximum="100000"
+ Increment="1"
+ Value="{Binding Material.BottlesInStock}"/>
+ <TextBlock Grid.Row="2" Grid.Column="22"
+ VerticalAlignment="Center"
+ Text="units"/>
+ </Grid>
+ </Expander>
+ </Border>
+
+ <TextBlock Grid.Row="2"
+ VerticalAlignment="Center" FontWeight="Bold"
+ Margin="10,0,0,0"
+ Text="{Binding Manager.Count, StringFormat=Materials: {0}}"/>
+
+ <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="1">
+
+ <Button VerticalAlignment="Center"
+ IsEnabled="{Binding #MaterialsTable.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}"
+ Command="{Binding RemoveSelectedMaterials}">
+ <StackPanel Orientation="Horizontal" Spacing="10">
+ <Image Source="/Assets/Icons/trash-16x16.png"/>
+ <TextBlock Text="Remove selected materials"/>
+ </StackPanel>
+ </Button>
+
+ <Button VerticalAlignment="Center"
+ IsEnabled="{Binding Manager.Count}"
+ Command="{Binding ClearMaterials}">
+ <StackPanel Orientation="Horizontal" Spacing="10">
+ <Image Source="/Assets/Icons/delete-16x16.png"/>
+ <TextBlock Text="{Binding Manager.Count, StringFormat=Clear {0} materials}"/>
+ </StackPanel>
+ </Button>
+
+ </StackPanel>
+
+
+
+ <DataGrid Grid.Row="3"
+ Name="MaterialsTable"
+ CanUserReorderColumns="True"
+ CanUserResizeColumns="True"
+ CanUserSortColumns="True"
+ GridLinesVisibility="Horizontal"
+ ClipboardCopyMode="IncludeHeader"
+ VerticalAlignment="Stretch"
+ Items="{Binding Manager.Materials}">
+ <DataGrid.Columns>
+ <DataGridTextColumn Header="Name"
+ Binding="{Binding Name}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Volume (ml)"
+ Binding="{Binding BottleVolume}"
+ Width="Auto" />
+ <!--
+ <DataGridTextColumn Header="Density (g/ml)"
+ Binding="{Binding Density}"
+ Width="Auto" />!-->
+ <DataGridTextColumn Header="Cost"
+ Binding="{Binding BottleCost}"
+ Width="Auto" />
+ <DataGridTextColumn Header="In stock"
+ Binding="{Binding BottlesInStock}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Consumed bottles"
+ IsReadOnly="True"
+ Binding="{Binding ConsumedBottles, StringFormat=\{0:N0\}}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Spent"
+ IsReadOnly="True"
+ Binding="{Binding TotalCost, StringFormat=\{0:N2\}}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Remaining volume (ml)"
+ Binding="{Binding BottleRemainingVolume}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Consumed volume (l)"
+ IsReadOnly="True"
+ Binding="{Binding ConsumedVolumeLiters, StringFormat=\{0:N3\}}"
+ Width="Auto" />
+ <DataGridTextColumn Header="Print time (days)"
+ IsReadOnly="True"
+ Binding="{Binding PrintTimeSpan.TotalDays, StringFormat=\{0:N4\}}"
+ Width="Auto" />
+ </DataGrid.Columns>
+
+ </DataGrid>
+ </Grid>
+
+</controls:WindowEx>
diff --git a/UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs b/UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs
new file mode 100644
index 0000000..a519d34
--- /dev/null
+++ b/UVtools.WPF/Windows/MaterialManagerWindow.axaml.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using DynamicData;
+using MessageBox.Avalonia.Enums;
+using UVtools.Core.Managers;
+using UVtools.Core.Objects;
+using UVtools.WPF.Controls;
+using UVtools.WPF.Extensions;
+
+namespace UVtools.WPF.Windows
+{
+ public class MaterialManagerWindow : WindowEx
+ {
+ private Material _material = new();
+ private readonly DataGrid _grid;
+ public MaterialManager Manager => MaterialManager.Instance;
+
+ public Material Material
+ {
+ get => _material;
+ set => RaiseAndSetIfChanged(ref _material, value);
+ }
+
+ public MaterialManagerWindow()
+ {
+ InitializeComponent();
+#if DEBUG
+ this.AttachDevTools();
+#endif
+
+ _grid = this.FindControl<DataGrid>("MaterialsTable");
+
+ MaterialManager.Load(); // Reload
+
+ DataContext = this;
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ protected override void OnClosed(EventArgs e)
+ {
+ base.OnClosed(e);
+ MaterialManager.Save(); // Apply changes
+ }
+
+ public void RefreshStatistics()
+ {
+ Manager.RaisePropertiesChanged();
+ }
+
+ public async void AddNewMaterial()
+ {
+ if (string.IsNullOrWhiteSpace(Material.Name))
+ {
+ await this.MessageBoxError("Material name can't be empty");
+ return;
+ }
+
+ if (Manager.Contains(Material))
+ {
+ await this.MessageBoxError("A material with same name already exists.");
+ return;
+ }
+
+ Material.BottleRemainingVolume = Material.BottleVolume;
+
+ if (await this.MessageBoxQuestion("Are you sure you want to add the following material:\n" +
+ $"{Material}") != ButtonResult.Yes) return;
+
+ Manager.Add(Material);
+ Manager.SortByName();
+ MaterialManager.Save();
+ Material = new();
+ }
+
+ public async void RemoveSelectedMaterials()
+ {
+ if (_grid.SelectedItems.Count <= 0) return;
+ if (await this.MessageBoxQuestion($"Are you sure you want to remove {_grid.SelectedItems.Count} materials?") != ButtonResult.Yes) return;
+ Manager.RemoveMany(_grid.SelectedItems.Cast<Material>());
+ MaterialManager.Save();
+ }
+
+ public async void ClearMaterials()
+ {
+ if (Manager.Count == 0) return;
+ if (await this.MessageBoxQuestion($"Are you sure you want to clear {Manager.Count} materials?") != ButtonResult.Yes) return;
+ Manager.Clear(true);
+ }
+ }
+}
diff --git a/UVtools.WPF/Windows/PrusaSlicerManager.axaml b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml
index e9d6b1c..72184b1 100644
--- a/UVtools.WPF/Windows/PrusaSlicerManager.axaml
+++ b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml
@@ -4,7 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:UVtools.WPF.Controls"
mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
- x:Class="UVtools.WPF.Windows.PrusaSlicerManager"
+ x:Class="UVtools.WPF.Windows.PrusaSlicerManagerWindow"
Title="Install profiles into PrusaSlicer"
Width="900"
MinWidth="900"
diff --git a/UVtools.WPF/Windows/PrusaSlicerManager.axaml.cs b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml.cs
index ae24b3c..4b30c9c 100644
--- a/UVtools.WPF/Windows/PrusaSlicerManager.axaml.cs
+++ b/UVtools.WPF/Windows/PrusaSlicerManagerWindow.axaml.cs
@@ -10,11 +10,11 @@ using UVtools.WPF.Structures;
namespace UVtools.WPF.Windows
{
- public class PrusaSlicerManager : WindowEx
+ public class PrusaSlicerManagerWindow : WindowEx
{
public PEProfileFolder[] Profiles { get;}
- public PrusaSlicerManager()
+ public PrusaSlicerManagerWindow()
{
InitializeComponent();
Profiles = new[]