* (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
This commit is contained in:
Tiago Conceição
2021-02-21 03:38:37 +00:00
parent 9dffbd77b9
commit 4ec98cfab1
16 changed files with 967 additions and 30 deletions
+5
View File
@@ -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)**
+271
View File
@@ -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);
}
}
}
+76 -23
View File
@@ -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
}
@@ -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
}
}
+2 -2
View File
@@ -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" />
+4
View File
@@ -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")
Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

@@ -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>
@@ -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);
}
}
}
+21
View File
@@ -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"
@@ -182,6 +191,16 @@
<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}">
@@ -189,6 +208,8 @@
<Image Source="\Assets\Icons\CNCMachine-16x16.png"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem
Header="_Open settings folder"
+16 -1
View File
@@ -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)
{
+6 -1
View File
@@ -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>
@@ -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>
@@ -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);
}
}
}
@@ -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"
@@ -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[]