/* * GNU AFFERO GENERAL PUBLIC LICENSE * Version 3, 19 November 2007 * Copyright (C) 2007 Free Software Foundation, Inc. * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Emgu.CV; using Emgu.CV.CvEnum; using UVtools.Core.Extensions; using UVtools.Core.GCode; using UVtools.Core.Objects; using UVtools.Core.Operations; namespace UVtools.Core.FileFormats { /// /// Slicer representation /// public abstract class FileFormat : BindableBase, IDisposable, IEquatable, IEnumerable { #region Constants public const string TemporaryFileAppend = ".tmp"; public const ushort ExtraPrintTime = 300; private const string ExtractConfigFileName = "Configuration"; private const string ExtractConfigFileExtension = "ini"; public const float DefaultLayerHeight = 0.5f; public const ushort DefaultBottomLayerCount = 4; public const float DefaultBottomExposureTime = 30; public const float DefaultBottomLiftHeight = 5; public const float DefaultLiftHeight = 5; public const float DefaultBottomLiftSpeed = 100; public const float DefaultExposureTime = 3; public const float DefaultLiftSpeed = 100; public const float DefaultRetractSpeed = 100; public const float DefaultBottomLightOffDelay = 0; public const float DefaultLightOffDelay = 0; public const byte DefaultBottomLightPWM = 255; public const byte DefaultLightPWM = 255; public const float MinimumLayerHeight = 0.01f; public const float MaximumLayerHeight = 0.20f; #endregion #region Enums /// /// Enumeration of file format types /// public enum FileFormatType : byte { Archive, Binary } /// /// Enumeration of file thumbnail size types /// public enum FileThumbnailSize : byte { Small = 0, Large } #endregion #region Sub Classes /// /// Available Print Parameters to modify /// public class PrintParameterModifier { #region Instances public static PrintParameterModifier BottomLayerCount { get; } = new ("Bottom layer count", null, "layers",0, ushort.MaxValue, 0); public static PrintParameterModifier BottomExposureSeconds { get; } = new ("Bottom exposure time", null, "s", 0.1M, 1000, 2); public static PrintParameterModifier ExposureSeconds { get; } = new ("Exposure time", null, "s", 0.1M, 1000, 2); public static PrintParameterModifier BottomLightOffDelay { get; } = new ("Bottom light-off seconds", null, "s"); public static PrintParameterModifier LightOffDelay { get; } = new ("Light-off seconds", null, "s"); public static PrintParameterModifier BottomLiftHeight { get; } = new ("Bottom lift height", @"Modify 'Bottom lift height' millimeters between bottom layers", "mm", 1); public static PrintParameterModifier LiftHeight { get; } = new ("Lift height", @"Modify 'Lift height' millimeters between layers", "mm"); public static PrintParameterModifier BottomLiftSpeed { get; } = new ("Bottom lift Speed", @"Modify 'Bottom lift Speed' mm/min between bottom layers", "mm/min", 10); public static PrintParameterModifier LiftSpeed { get; } = new ("Lift speed", @"Modify 'Lift speed' mm/min between layers", "mm/min", 10, 5000, 2); public static PrintParameterModifier RetractSpeed { get; } = new ("Retract speed", @"Modify 'Retract speed' mm/min between layer", "mm/min", 10, 5000, 2); public static PrintParameterModifier BottomLightPWM { get; } = new ("Bottom light PWM", @"Modify 'Bottom light PWM' value", null, 1, byte.MaxValue, 0); public static PrintParameterModifier LightPWM { get; } = new ("Light PWM", @"Modify 'Light PWM' value", null, 1, byte.MaxValue, 0); public static PrintParameterModifier[] Parameters = { BottomLayerCount, BottomExposureSeconds, ExposureSeconds, BottomLightOffDelay, LightOffDelay, BottomLiftHeight, BottomLiftSpeed, LiftHeight, LiftSpeed, RetractSpeed, BottomLightPWM, LightPWM }; #endregion #region Properties /// /// Gets the name /// public string Name { get; } /// /// Gets the description /// public string Description { get; } /// /// Gets the value unit /// public string ValueUnit { get; } /// /// Gets the minimum value /// public decimal Minimum { get; } /// /// Gets the maximum value /// public decimal Maximum { get; } /// /// Gets the number of decimal plates /// public byte DecimalPlates { get; } /// /// Gets or sets the current / old value /// public decimal OldValue { get; set; } /// /// Gets or sets the new value /// public decimal NewValue { get; set; } public decimal Value { get => NewValue; set => OldValue = NewValue = value; } /// /// Gets if the value has changed /// public bool HasChanged => OldValue != NewValue; #endregion #region Constructor public PrintParameterModifier(string name, string description = null, string valueUnit = null, decimal minimum = 0, decimal maximum = 1000, byte decimalPlates = 2) { Name = name; Description = description ?? $"Modify '{name}'"; ValueUnit = valueUnit ?? string.Empty; Minimum = minimum; Maximum = maximum; DecimalPlates = decimalPlates; } #endregion #region Overrides public override string ToString() { return $"{nameof(Name)}: {Name}, {nameof(Description)}: {Description}, {nameof(ValueUnit)}: {ValueUnit}, {nameof(Minimum)}: {Minimum}, {nameof(Maximum)}: {Maximum}, {nameof(DecimalPlates)}: {DecimalPlates}, {nameof(OldValue)}: {OldValue}, {nameof(NewValue)}: {NewValue}, {nameof(HasChanged)}: {HasChanged}"; } #endregion } #endregion #region Static Methods /// /// Gets the available formats to process /// public static FileFormat[] AvailableFormats { get; } = { new SL1File(), // Prusa SL1 new ChituboxZipFile(), // Zip new ChituboxFile(), // cbddlp, cbt, photon new PhotonSFile(), // photons new PHZFile(), // phz new FDGFile(), // fdg new PhotonWorkshopFile(), // PSW new CWSFile(), // CWS new ZCodeFile(), // zcode new ZCodexFile(), // zcodex //new MakerbaseFile(), // MKS new LGSFile(), // LGS, LGS30 new UVJFile(), // UVJ new ImageFile(), // images }; public static string AllSlicerFiles => AvailableFormats.Aggregate("All slicer files|", (current, fileFormat) => current.EndsWith("|") ? $"{current}{fileFormat.FileFilterExtensionsOnly}" : $"{current}; {fileFormat.FileFilterExtensionsOnly}"); /// /// Gets all filters for open and save file dialogs /// public static string AllFileFilters => AllSlicerFiles + AvailableFormats.Aggregate(string.Empty, (current, fileFormat) => $"{current}|" + fileFormat.FileFilter); public static List>> AllFileFiltersAvalonia { get { var result = new List>> { new KeyValuePair>("All slicer files", new List()) }; for (int i = 0; i < AvailableFormats.Length; i++) { foreach (var fileExtension in AvailableFormats[i].FileExtensions) { result[0].Value.Add(fileExtension.Extension); result.Add(new KeyValuePair>(fileExtension.Description, new List { fileExtension.Extension })); } } return result; } } public static List AllFileExtensions { get { List extensions = new(); foreach (var slicerFile in AvailableFormats) { extensions.AddRange(slicerFile.FileExtensions); } return extensions; } } public static List AllFileExtensionsString => (from slicerFile in AvailableFormats from extension in slicerFile.FileExtensions select extension.Extension).ToList(); /// /// Gets the count of available file extensions /// public static byte FileExtensionsCount => AvailableFormats.Aggregate(0, (current, fileFormat) => (byte) (current + fileFormat.FileExtensions.Length)); /// /// Find by an extension /// /// Extension name to find /// True if is a file path rather than only a extension name /// True to create a new instance of found file format, otherwise will return a pre created one which should be used for read-only purpose /// object or null if not found public static FileFormat FindByExtension(string extension, bool isFilePath = false, bool createNewInstance = false) { return (from fileFormat in AvailableFormats where fileFormat.IsExtensionValid(extension, isFilePath) select createNewInstance ? (FileFormat) Activator.CreateInstance(fileFormat.GetType()) : fileFormat).FirstOrDefault(); } public static FileExtension FindExtension(string extension, bool isFilePath = false, bool createNewInstance = false) { return AvailableFormats.SelectMany(format => format.FileExtensions).FirstOrDefault(ext => ext.Equals(extension)); } /// /// Find by an type /// /// Type to find /// True to create a new instance of found file format, otherwise will return a pre created one which should be used for read-only purpose /// object or null if not found public static FileFormat FindByType(Type type, bool createNewInstance = false) { return (from t in AvailableFormats where type == t.GetType() select createNewInstance ? (FileFormat) Activator.CreateInstance(type) : t).FirstOrDefault(); } public static string GetFileNameStripExtensions(string filepath) { //if (file.EndsWith(TemporaryFileAppend)) file = Path.GetFileNameWithoutExtension(file); return PathExtensions.GetFileNameStripExtensions(filepath, AllFileExtensionsString.OrderByDescending(s => s.Length).ToList(), out _); } public static string GetFileNameStripExtensions(string filepath, out string strippedExtension) { //if (file.EndsWith(TemporaryFileAppend)) file = Path.GetFileNameWithoutExtension(file); return PathExtensions.GetFileNameStripExtensions(filepath, AllFileExtensionsString.OrderByDescending(s => s.Length).ToList(), out strippedExtension); } #endregion #region Members private bool _haveModifiedLayers; private LayerManager _layerManager; private float _printTime; private float _materialMilliliters; private float _maxPrintHeight; private ushort _bottomLayerCount = DefaultBottomLayerCount; private float _bottomExposureTime = DefaultBottomExposureTime; private float _exposureTime = DefaultExposureTime; private float _bottomLiftHeight = DefaultBottomLiftHeight; private float _liftHeight = DefaultLiftHeight; private float _bottomLiftSpeed = DefaultBottomLiftSpeed; private float _liftSpeed = DefaultLiftSpeed; private float _retractSpeed = DefaultRetractSpeed; private float _bottomLightOffDelay = DefaultBottomLightOffDelay; private float _lightOffDelay = DefaultLightOffDelay; private byte _bottomLightPwm = DefaultBottomLightPWM; private byte _lightPwm = DefaultLightPWM; private string _machineName = "Unknown"; private string _materialName; private float _materialGrams; private float _materialCost; #endregion #region Properties /// /// Gets the file format type /// public abstract FileFormatType FileType { get; } /// /// Gets the valid file extensions for this /// public abstract FileExtension[] FileExtensions { get; } /// /// Gets the available /// public abstract PrintParameterModifier[] PrintParameterModifiers { get; } /// /// Gets the available per layer /// public virtual PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = null; /// /// Checks if a exists on print parameters /// /// /// True if exists, otherwise false public bool HavePrintParameterModifier(PrintParameterModifier modifier) => PrintParameterModifiers is not null && PrintParameterModifiers.Contains(modifier); /// /// Checks if a exists on print parameters /// /// /// True if exists, otherwise false public bool HavePrintParameterPerLayerModifier(PrintParameterModifier modifier) => PrintParameterPerLayerModifiers is not null && PrintParameterPerLayerModifiers.Contains(modifier); /// /// Gets the file filter for open and save dialogs /// public string FileFilter { get { var result = string.Empty; foreach (var fileExt in FileExtensions) { if (!ReferenceEquals(result, string.Empty)) { result += '|'; } result += fileExt.Filter; } return result; } } /// /// Gets all valid file extensions for Avalonia file dialog /// public List>> FileFilterAvalonia => FileExtensions.Select(fileExt => new KeyValuePair>(fileExt.Description, new List {fileExt.Extension})).ToList(); /// /// Gets all valid file extensions in "*.extension1;*.extension2" format /// public string FileFilterExtensionsOnly { get { var result = string.Empty; foreach (var fileExt in FileExtensions) { if (!ReferenceEquals(result, string.Empty)) { result += "; "; } result += $"*.{fileExt.Extension}"; } return result; } } /// /// Gets or sets if change a global property should rebuild every layer data based on them /// public bool SuppressRebuildProperties { get; set; } /// /// Gets the input file path loaded into this /// public string FileFullPath { get; set; } /// /// Gets the thumbnails count present in this file format /// public abstract byte ThumbnailsCount { get; } /// /// Gets the number of created thumbnails /// public byte CreatedThumbnailsCount { get { if (Thumbnails is null) return 0; byte count = 0; foreach (var thumbnail in Thumbnails) { if (thumbnail is null) continue; count++; } return count; } } /// /// Gets the original thumbnail sizes /// public abstract Size[] ThumbnailsOriginalSize { get; } /// /// Gets the thumbnails for this /// public Mat[] Thumbnails { get; set; } /// /// Gets the cached layers into compressed bytes /// public LayerManager LayerManager { get => _layerManager; set { var oldLayerManager = _layerManager; if (!RaiseAndSetIfChanged(ref _layerManager, value) || value is null) return; if(!ReferenceEquals(this, _layerManager.SlicerFile)) // Auto fix parent slicer file { _layerManager.SlicerFile = this; } // Recalculate changes PrintHeight = PrintHeight; PrintTime = PrintTimeComputed; MaterialMilliliters = 0; if (oldLayerManager is null) return; // Init if (oldLayerManager.Count != LayerCount) { LayerCount = _layerManager.Count; if (SuppressRebuildProperties) return; if (LayerCount == 0 || this[LayerCount - 1] is null) return; // Not initialized LayerManager.RebuildLayersProperties(); } } } /// /// Gets the first layer /// public Layer FirstLayer => _layerManager?.FirstLayer; /// /// Gets the last layer /// public Layer LastLayer => _layerManager?.LastLayer; /// /// Gets the bounding rectangle of the object /// public Rectangle BoundingRectangle => _layerManager?.BoundingRectangle ?? Rectangle.Empty; /// /// Gets the bounding rectangle of the object in millimeters /// public RectangleF BoundingRectangleMillimeters => _layerManager?.BoundingRectangleMillimeters ?? Rectangle.Empty; /// /// Gets or sets if modifications require a full encode to save /// public bool RequireFullEncode { get => _haveModifiedLayers || LayerManager.IsModified; set => RaiseAndSetIfChanged(ref _haveModifiedLayers, value); } // => LayerManager.IsModified; /// /// Gets the image width resolution /// public Size Resolution { get => new((int)ResolutionX, (int)ResolutionY); set { ResolutionX = (uint) value.Width; ResolutionY = (uint) value.Height; RaisePropertyChanged(); } } /// /// Gets the image width resolution /// public abstract uint ResolutionX { get; set; } /// /// Gets the image height resolution /// public abstract uint ResolutionY { get; set; } /// /// Gets the size of display in millimeters /// public SizeF Display { get => new(DisplayWidth, DisplayHeight); set { DisplayWidth = value.Width; DisplayHeight = value.Height; RaisePropertyChanged(); } } /// /// Gets or sets the display width in millimeters /// public abstract float DisplayWidth { get; set; } /// /// Gets or sets the display height in millimeters /// public abstract float DisplayHeight { get; set; } /// /// Gets or sets if images need to be mirrored on lcd to print on the correct orientation /// public virtual bool MirrorDisplay { get; set; } /// /// Gets or sets the maximum printer build Z volume /// public virtual float MaxPrintHeight { get => _maxPrintHeight > 0 ? _maxPrintHeight : PrintHeight; set => RaiseAndSetIfChanged(ref _maxPrintHeight, value); } /// /// Gets or sets the pixels per mm on X direction /// public virtual float Xppmm { get => DisplayWidth > 0 ? ResolutionX / DisplayWidth : 0; set { RaisePropertyChanged(nameof(Xppmm)); RaisePropertyChanged(nameof(Ppmm)); } } /// /// Gets or sets the pixels per mm on Y direction /// public virtual float Yppmm { get => DisplayHeight > 0 ? ResolutionY / DisplayHeight : 0; set { RaisePropertyChanged(nameof(Yppmm)); RaisePropertyChanged(nameof(Ppmm)); } } /// /// Gets or sets the pixels per mm /// public SizeF Ppmm { get => new(Xppmm, Yppmm); set { Xppmm = value.Width; Yppmm = value.Height; } } /// /// Gets the pixel width in millimeters /// public float PixelWidth => DisplayWidth > 0 ? (float) Math.Round(DisplayWidth / ResolutionX, 3) : 0; /// /// Gets the pixel height in millimeters /// public float PixelHeight => DisplayHeight > 0 ? (float) Math.Round(DisplayHeight / ResolutionY, 3) : 0; /// /// Gets the pixel size in millimeters /// public SizeF PixelSize => new(PixelWidth, PixelHeight); /// /// Gets the maximum pixel between width and height in millimeters /// public float PixelSizeMax => PixelSize.Max(); /// /// Gets the pixel area in millimeters /// public float PixelArea => PixelSize.Area(); /// /// Gets the pixel width in microns /// public float PixelWidthMicrons => DisplayWidth > 0 ? (float)Math.Round(DisplayWidth / ResolutionX * 1000, 3) : 0; /// /// Gets the pixel height in microns /// public float PixelHeightMicrons => DisplayHeight > 0 ? (float)Math.Round(DisplayHeight / ResolutionY * 1000, 3) : 0; /// /// Gets the pixel size in microns /// public SizeF PixelSizeMicrons => new(PixelWidthMicrons, PixelHeightMicrons); /// /// Gets the maximum pixel between width and height in microns /// public float PixelSizeMicronsMax => PixelSizeMicrons.Max(); /// /// Gets the pixel area in millimeters /// public float PixelAreaMicrons => PixelSizeMicrons.Area(); /// /// Checks if this file have AntiAliasing /// public bool HaveAntiAliasing => AntiAliasing > 1; /// /// Gets or sets the AntiAliasing level /// public abstract byte AntiAliasing { get; set; } /// /// Gets Layer Height in mm /// public abstract float LayerHeight { get; set; } /// /// Gets Layer Height in um /// public ushort LayerHeightUm => (ushort) (LayerHeight * 1000); /// /// Gets or sets the print height in mm /// public virtual float PrintHeight { get => LayerCount == 0 ? 0 : this[LayerCount - 1]?.PositionZ ?? 0; set => RaisePropertyChanged(); } /// /// Gets the last layer index /// public uint LastLayerIndex => LayerCount - 1; /// /// Checks if this file format supports per layer settings /// public virtual bool SupportPerLayerSettings => PrintParameterPerLayerModifiers is not null && PrintParameterPerLayerModifiers.Length > 0; /// /// Gets or sets the layer count /// public virtual uint LayerCount { get => LayerManager?.Count ?? 0; set { RaisePropertyChanged(); RaisePropertyChanged(nameof(NormalLayerCount)); } } #region Universal Properties /// /// Gets or sets the number of initial layer count /// public virtual ushort BottomLayerCount { get => _bottomLayerCount; set { RaiseAndSet(ref _bottomLayerCount, value); RaisePropertyChanged(nameof(NormalLayerCount)); } } /// /// Gets the number of normal layer count /// public uint NormalLayerCount => LayerCount - BottomLayerCount; /// /// Gets or sets the initial exposure time for in seconds /// public virtual float BottomExposureTime { get => _bottomExposureTime; set => RaiseAndSet(ref _bottomExposureTime, value); } /// /// Gets or sets the normal layer exposure time in seconds /// public virtual float ExposureTime { get => _exposureTime; set => RaiseAndSet(ref _exposureTime, value); } /// /// Gets or sets the bottom lift height in mm /// public virtual float BottomLiftHeight { get => _bottomLiftHeight; set => RaiseAndSet(ref _bottomLiftHeight, value); } /// /// Gets or sets the lift height in mm /// public virtual float LiftHeight { get => _liftHeight; set => RaiseAndSet(ref _liftHeight, value); } /// /// Gets or sets the bottom lift speed in mm/min /// public virtual float BottomLiftSpeed { get => _bottomLiftSpeed; set => RaiseAndSet(ref _bottomLiftSpeed, value); } /// /// Gets or sets the speed in mm/min /// public virtual float LiftSpeed { get => _liftSpeed; set => RaiseAndSet(ref _liftSpeed, value); } /// /// Gets the speed in mm/min for the retracts /// public virtual float RetractSpeed { get => _retractSpeed; set => RaiseAndSet(ref _retractSpeed, value); } /// /// Gets or sets the bottom layer off time in seconds /// public virtual float BottomLightOffDelay { get => _bottomLightOffDelay; set => RaiseAndSet(ref _bottomLightOffDelay, value); } /// /// Gets or sets the layer off time in seconds /// public virtual float LightOffDelay { get => _lightOffDelay; set => RaiseAndSet(ref _lightOffDelay, value); } /// /// Gets or sets the bottom pwm value from 0 to 255 /// public virtual byte BottomLightPWM { get => _bottomLightPwm; set => RaiseAndSet(ref _bottomLightPwm, value); } /// /// Gets or sets the pwm value from 0 to 255 /// public virtual byte LightPWM { get => _lightPwm; set => RaiseAndSet(ref _lightPwm, value); } #endregion /// /// Gets the estimate print time in seconds /// public virtual float PrintTime { get { if (_printTime <= 0) { _printTime = PrintTimeComputed; } return _printTime; } set { if (value <= 0) { value = PrintTimeComputed; } if(!RaiseAndSetIfChanged(ref _printTime, value)) return; RaisePropertyChanged(nameof(PrintTimeHours)); RaisePropertyChanged(nameof(PrintTimeString)); } } /// /// Gets the calculated estimate print time in seconds /// public float PrintTimeComputed { get { if (LayerCount == 0) return 0; float time = ExtraPrintTime; bool computeGeneral = LayerManager is null; if (!computeGeneral) { foreach (var layer in this) { if (layer is null) { computeGeneral = true; break; } var lightOffDelay = OperationCalculator.LightOffDelayC.CalculateSeconds(layer.LiftHeight, layer.LiftSpeed, layer.RetractSpeed); time += layer.ExposureTime; if (lightOffDelay >= layer.LightOffDelay) time += lightOffDelay; else time += layer.LightOffDelay; } } if (computeGeneral) { time = ExtraPrintTime + BottomLightOffDelay * BottomLayerCount + LightOffDelay * NormalLayerCount + OperationCalculator.LightOffDelayC.CalculateSeconds(BottomLiftHeight, BottomLiftSpeed, RetractSpeed) * BottomLayerCount + OperationCalculator.LightOffDelayC.CalculateSeconds(LiftHeight, LiftSpeed, RetractSpeed) * NormalLayerCount; } return (float) Math.Round(time, 2); } } /// /// Gets the estimate print time in hours /// public float PrintTimeHours => (float) Math.Round(PrintTime / 3600, 2); /// /// Gets the estimate print time in hours and minutes formatted /// public string PrintTimeString => TimeSpan.FromSeconds(PrintTime).ToString("hh\\hmm\\m"); /// /// Gets the estimate used material in ml /// public virtual float MaterialMilliliters { get => _materialMilliliters; set { if (value <= 0) { value = (float)Math.Round(this.Where(layer => layer is not null).Sum(layer => layer.MaterialMilliliters), 3); ; } RaiseAndSetIfChanged(ref _materialMilliliters, value); } } //public float MaterialMillilitersComputed => /// /// Gets the estimate material in grams /// public virtual float MaterialGrams { get => _materialGrams; set => RaiseAndSetIfChanged(ref _materialGrams, value); } /// /// Gets the estimate material cost /// public virtual float MaterialCost { get => _materialCost; set => RaiseAndSetIfChanged(ref _materialCost, value); } /// /// Gets the material name /// public virtual string MaterialName { get => _materialName; set => RaiseAndSetIfChanged(ref _materialName, value); } /// /// Gets the machine name /// public virtual string MachineName { get => _machineName; set { if(!RaiseAndSetIfChanged(ref _machineName, value)) return; if(FileType == FileFormatType.Binary) RequireFullEncode = true; } } /// /// Gets the GCode, returns null if not supported /// public GCodeBuilder GCode { get; set; } = new(); /// /// Gets the GCode, returns null if not supported /// public string GCodeStr => GCode?.ToString(); /// /// Gets if this file have available gcode /// public bool HaveGCode => !GCode?.IsEmpty ?? false; /// /// Get all configuration objects with properties and values /// public abstract object[] Configs { get; } /// /// Gets if this file is valid to read /// public bool IsValid => FileFullPath is not null; #endregion #region Constructor protected FileFormat() { Thumbnails = new Mat[ThumbnailsCount]; PropertyChanged += OnPropertyChanged; } private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (SuppressRebuildProperties) return; if ( e.PropertyName == nameof(BottomLayerCount) || e.PropertyName == nameof(BottomExposureTime) || e.PropertyName == nameof(ExposureTime) || e.PropertyName == nameof(BottomLightOffDelay) || e.PropertyName == nameof(LightOffDelay) || e.PropertyName == nameof(BottomLiftHeight) || e.PropertyName == nameof(LiftHeight) || e.PropertyName == nameof(BottomLiftSpeed) || e.PropertyName == nameof(LiftSpeed) || e.PropertyName == nameof(RetractSpeed) || e.PropertyName == nameof(BottomLightPWM) || e.PropertyName == nameof(LightPWM) ) { if (LayerManager is not null) { LayerManager.RebuildLayersProperties(false, e.PropertyName); } if(e.PropertyName != nameof(BottomLightPWM) && e.PropertyName != nameof(LightPWM)) PrintTime = PrintTimeComputed; return; } } #endregion #region Indexers public Layer this[int index] { get => LayerManager[index]; set => LayerManager[index] = value; } public Layer this[uint index] { get => LayerManager[index]; set => LayerManager[index] = value; } public Layer this[long index] { get => LayerManager[index]; set => LayerManager[index] = value; } #endregion #region Numerators public IEnumerator GetEnumerator() { return ((IEnumerable)LayerManager.Layers).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region Overrides public override bool Equals(object obj) { return Equals(obj as FileFormat); } public bool Equals(FileFormat other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return FileFullPath == other.FileFullPath; } public override int GetHashCode() { return (FileFullPath != null ? FileFullPath.GetHashCode() : 0); } public void Dispose() { Clear(); } #endregion #region Methods /// /// Clears all definitions and properties, it also dispose valid candidates /// public virtual void Clear() { FileFullPath = null; LayerManager = null; GCode.Clear(); if (Thumbnails is not null) { for (int i = 0; i < ThumbnailsCount; i++) { Thumbnails[i]?.Dispose(); } } } /// /// Validate if a file is a valid /// /// Full file path public void FileValidation(string fileFullPath) { if (fileFullPath is null) throw new ArgumentNullException(nameof(FileFullPath), "fullFilePath can't be null."); if (!File.Exists(fileFullPath)) throw new FileNotFoundException("The specified file does not exists.", fileFullPath); if (IsExtensionValid(fileFullPath, true)) { return; } throw new FileLoadException($"The specified file is not valid.", fileFullPath); } /// /// Checks if a extension is valid under the /// /// Extension to check /// True if is a full file path, otherwise false for extension only /// True if valid, otherwise false public bool IsExtensionValid(string extension, bool isFilePath = false) { extension = isFilePath ? Path.GetExtension(extension)?.Remove(0, 1) : extension; return FileExtensions.Any(fileExtension => fileExtension.Equals(extension)); } /// /// Gets all valid file extensions in a specified format /// public string GetFileExtensions(string prepend = ".", string separator = ", ") { var result = string.Empty; foreach (var fileExt in FileExtensions) { if (!ReferenceEquals(result, string.Empty)) { result += separator; } result += $"{prepend}{fileExt.Extension}"; } return result; } /// /// Gets a thumbnail by it height or lower /// /// Max height allowed /// public Mat GetThumbnail(uint maxHeight = 400) { for (int i = 0; i < ThumbnailsCount; i++) { if(ReferenceEquals(Thumbnails[i], null)) continue; if (Thumbnails[i].Height <= maxHeight) return Thumbnails[i]; } return null; } /// /// Gets a thumbnail by the largest or smallest /// /// True to get the largest, otherwise false /// public Mat GetThumbnail(bool largest) { switch (CreatedThumbnailsCount) { case 0: return null; case 1: return Thumbnails[0]; default: if (largest) { return Thumbnails[0].Size.Area() >= Thumbnails[1].Size.Area() ? Thumbnails[0] : Thumbnails[1]; } else { return Thumbnails[0].Size.Area() <= Thumbnails[1].Size.Area() ? Thumbnails[0] : Thumbnails[1]; } } } /// /// Sets thumbnails from a list of thumbnails and clone them /// /// public void SetThumbnails(Mat[] images) { for (var i = 0; i < ThumbnailsCount; i++) { Thumbnails[i] = images[Math.Min(i, images.Length - 1)].Clone(); if (Thumbnails[i].Size != ThumbnailsOriginalSize[i]) { CvInvoke.Resize(Thumbnails[i], Thumbnails[i], ThumbnailsOriginalSize[i]); } } } /// /// Sets all thumbnails the same image /// /// Image to set public void SetThumbnails(Mat image) { for (var i = 0; i < ThumbnailsCount; i++) { Thumbnails[i] = image.Clone(); if (ThumbnailsOriginalSize is null || i >= ThumbnailsOriginalSize.Length) continue; if (Thumbnails[i].Size != ThumbnailsOriginalSize[i]) { CvInvoke.Resize(Thumbnails[i], Thumbnails[i], ThumbnailsOriginalSize[i]); } } } /// /// Sets a thumbnail from a disk file /// /// Thumbnail index /// public void SetThumbnail(int index, string filePath) { Thumbnails[index] = CvInvoke.Imread(filePath, ImreadModes.AnyColor); if (Thumbnails[index].Size != ThumbnailsOriginalSize[index]) { CvInvoke.Resize(Thumbnails[index], Thumbnails[index], ThumbnailsOriginalSize[index]); } } /// /// Encode to an output file /// /// Output file /// protected abstract void EncodeInternally(string fileFullPath, OperationProgress progress); /// /// Encode to an output file /// /// Output file /// public void Encode(string fileFullPath, OperationProgress progress = null) { progress ??= new OperationProgress(); progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); FileFullPath = fileFullPath; if (File.Exists(fileFullPath)) { File.Delete(fileFullPath); } for (var i = 0; i < Thumbnails.Length; i++) { if (Thumbnails[i] is null) continue; if(Thumbnails[i].Size == ThumbnailsOriginalSize[i]) continue; CvInvoke.Resize(Thumbnails[i], Thumbnails[i], new Size(ThumbnailsOriginalSize[i].Width, ThumbnailsOriginalSize[i].Height)); } EncodeInternally(fileFullPath, progress); LayerManager.Desmodify(); RequireFullEncode = false; } /// /// Decode a slicer file /// /// /// protected abstract void DecodeInternally(string fileFullPath, OperationProgress progress); /// /// Decode a slicer file /// /// /// public void Decode(string fileFullPath, OperationProgress progress = null) { Clear(); FileValidation(fileFullPath); FileFullPath = fileFullPath; progress ??= new OperationProgress(); progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); DecodeInternally(fileFullPath, progress); progress.Token.ThrowIfCancellationRequested(); // Sanitize for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { // Check for null layers if(this[layerIndex] is null) throw new FileLoadException($"Layer {layerIndex} was defined but doesn't contain a valid image.", fileFullPath); if(layerIndex <= 0) continue; // Check for bigger position z than it successor if(this[layerIndex-1].PositionZ > this[layerIndex].PositionZ) throw new FileLoadException($"Layer {layerIndex-1} ({this[layerIndex - 1].PositionZ}mm) have a higher Z position than the successor layer {layerIndex} ({this[layerIndex].PositionZ}mm).\n", fileFullPath); } // Fix 0mm positions at layer 0 if(this[0].PositionZ == 0) { for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { this[layerIndex].PositionZ += LayerHeight; } Save(progress); } } /// /// Extract contents to a folder /// /// Path to folder where content will be extracted /// /// /// public virtual void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true, OperationProgress progress = null) { progress ??= new OperationProgress(); progress.ItemName = OperationProgress.StatusExtracting; /*if (emptyFirst) { if (Directory.Exists(path)) { DirectoryInfo di = new DirectoryInfo(path); foreach (FileInfo file in di.GetFiles()) { file.Delete(); } foreach (DirectoryInfo dir in di.GetDirectories()) { dir.Delete(true); } } }*/ //if (!Directory.Exists(path)) //{ Directory.CreateDirectory(path); //} if (genericConfigExtract) { if (Configs is not null) { using TextWriter tw = new StreamWriter(Path.Combine(path, $"{ExtractConfigFileName}.{ExtractConfigFileExtension}"), false); foreach (var config in Configs) { var type = config.GetType(); tw.WriteLine($"[{type.Name}]"); foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (property.Name.Equals("Item")) continue; tw.WriteLine($"{property.Name} = {property.GetValue(config)}"); } tw.WriteLine(); } tw.Close(); } } if (genericLayersExtract) { if (LayerCount > 0) { using (TextWriter tw = new StreamWriter(Path.Combine(path, "Layers.ini"))) { for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) { var layer = this[layerIndex]; tw.WriteLine($"[{layerIndex}]"); tw.WriteLine($"{nameof(layer.NonZeroPixelCount)}: {layer.NonZeroPixelCount}"); tw.WriteLine($"{nameof(layer.BoundingRectangle)}: {layer.BoundingRectangle}"); tw.WriteLine($"{nameof(layer.IsBottomLayer)}: {layer.IsBottomLayer}"); tw.WriteLine($"{nameof(layer.LayerHeight)}: {layer.LayerHeight}"); tw.WriteLine($"{nameof(layer.PositionZ)}: {layer.PositionZ}"); tw.WriteLine($"{nameof(layer.ExposureTime)}: {layer.ExposureTime}"); if (HavePrintParameterPerLayerModifier(PrintParameterModifier.LightOffDelay)) tw.WriteLine($"{nameof(layer.LightOffDelay)}: {layer.LightOffDelay}"); if (HavePrintParameterPerLayerModifier(PrintParameterModifier.LiftHeight)) tw.WriteLine($"{nameof(layer.LiftHeight)}: {layer.LiftHeight}"); if (HavePrintParameterPerLayerModifier(PrintParameterModifier.LiftSpeed)) tw.WriteLine($"{nameof(layer.LiftSpeed)}: {layer.LiftSpeed}"); if (HavePrintParameterPerLayerModifier(PrintParameterModifier.RetractSpeed)) tw.WriteLine($"{nameof(layer.RetractSpeed)}: {layer.RetractSpeed}"); if (HavePrintParameterPerLayerModifier(PrintParameterModifier.LightPWM)) tw.WriteLine($"{nameof(layer.LightPWM)}: {layer.LightPWM}"); var materialMillilitersPercent = layer.MaterialMillilitersPercent; if (!float.IsNaN(materialMillilitersPercent)) { tw.WriteLine($"{nameof(layer.MaterialMilliliters)}: {layer.MaterialMilliliters}ml ({materialMillilitersPercent:F2}%)"); } tw.WriteLine(); } tw.Close(); } } } if (FileType == FileFormatType.Archive) { progress.CanCancel = false; ZipArchiveExtensions.ImprovedExtractToDirectory(FileFullPath, path, ZipArchiveExtensions.Overwrite.Always); return; } progress.ItemCount = LayerCount; if (genericLayersExtract) { uint i = 0; if (Thumbnails is not null) { foreach (var thumbnail in Thumbnails) { if (thumbnail is null) { continue; } thumbnail.Save(Path.Combine(path, $"Thumbnail{i}.png")); i++; } } if (LayerCount > 0) { Parallel.ForEach(this, (layer) => { if (progress.Token.IsCancellationRequested) return; var byteArr = layer.CompressedBytes; using (FileStream stream = File.Create(Path.Combine(path, layer.Filename), byteArr.Length)) { stream.Write(byteArr, 0, byteArr.Length); stream.Close(); lock (progress.Mutex) { progress++; } } }); } } } /// /// Get height in mm from layer height /// /// /// /// The height in mm public float GetHeightFromLayer(uint layerIndex, bool realHeight = true) { return Layer.RoundHeight((layerIndex + (realHeight ? 1 : 0)) * LayerHeight); } /// /// Gets the value for initial layer or normal layers based on layer index /// /// Type of value /// Layer index /// Initial value /// Normal value /// public T GetInitialLayerValueOrNormal(uint layerIndex, T initialLayerValue, T normalLayerValue) { return layerIndex < BottomLayerCount ? initialLayerValue : normalLayerValue; } /// /// Refresh print parameters globals with this file settings /// public void RefreshPrintParametersModifiersValues() { if (PrintParameterModifiers is null) return; if (PrintParameterModifiers.Contains(PrintParameterModifier.BottomLayerCount)) { PrintParameterModifier.BottomLayerCount.Value = BottomLayerCount; } if (PrintParameterModifiers.Contains(PrintParameterModifier.BottomExposureSeconds)) { PrintParameterModifier.BottomExposureSeconds.Value = (decimal) BottomExposureTime; } if (PrintParameterModifiers.Contains(PrintParameterModifier.ExposureSeconds)) { PrintParameterModifier.ExposureSeconds.Value = (decimal)ExposureTime; } if (PrintParameterModifiers.Contains(PrintParameterModifier.BottomLightOffDelay)) { PrintParameterModifier.BottomLightOffDelay.Value = (decimal)BottomLightOffDelay; } if (PrintParameterModifiers.Contains(PrintParameterModifier.LightOffDelay)) { PrintParameterModifier.LightOffDelay.Value = (decimal)LightOffDelay; } if (PrintParameterModifiers.Contains(PrintParameterModifier.BottomLiftHeight)) { PrintParameterModifier.BottomLiftHeight.Value = (decimal)BottomLiftHeight; } if (PrintParameterModifiers.Contains(PrintParameterModifier.LiftHeight)) { PrintParameterModifier.LiftHeight.Value = (decimal)LiftHeight; } if (PrintParameterModifiers.Contains(PrintParameterModifier.BottomLiftSpeed)) { PrintParameterModifier.BottomLiftSpeed.Value = (decimal)BottomLiftSpeed; } if (PrintParameterModifiers.Contains(PrintParameterModifier.LiftSpeed)) { PrintParameterModifier.LiftSpeed.Value = (decimal)LiftSpeed; } if (PrintParameterModifiers.Contains(PrintParameterModifier.RetractSpeed)) { PrintParameterModifier.RetractSpeed.Value = (decimal)RetractSpeed; } if (PrintParameterModifiers.Contains(PrintParameterModifier.BottomLightPWM)) { PrintParameterModifier.BottomLightPWM.Value = BottomLightPWM; } if (PrintParameterModifiers.Contains(PrintParameterModifier.LightPWM)) { PrintParameterModifier.LightPWM.Value = LightPWM; } } /// /// Refresh print parameters per layer globals with this file settings /// public void RefreshPrintParametersPerLayerModifiersValues(uint layerIndex) { if (PrintParameterPerLayerModifiers is null) return; var layer = this[layerIndex]; if (PrintParameterPerLayerModifiers.Contains(PrintParameterModifier.ExposureSeconds)) { PrintParameterModifier.ExposureSeconds.Value = (decimal)layer.ExposureTime; } if (PrintParameterPerLayerModifiers.Contains(PrintParameterModifier.LightOffDelay)) { PrintParameterModifier.LightOffDelay.Value = (decimal)layer.LightOffDelay; } if (PrintParameterPerLayerModifiers.Contains(PrintParameterModifier.LiftHeight)) { PrintParameterModifier.LiftHeight.Value = (decimal)layer.LiftHeight; } if (PrintParameterPerLayerModifiers.Contains(PrintParameterModifier.LiftSpeed)) { PrintParameterModifier.LiftSpeed.Value = (decimal)layer.LiftSpeed; } if (PrintParameterPerLayerModifiers.Contains(PrintParameterModifier.RetractSpeed)) { PrintParameterModifier.RetractSpeed.Value = (decimal)layer.RetractSpeed; } if (PrintParameterPerLayerModifiers.Contains(PrintParameterModifier.LightPWM)) { PrintParameterModifier.LightPWM.Value = layer.LightPWM; } } /// /// Gets the value attributed to /// /// Modifier to use /// A value public object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) { if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerCount)) return BottomLayerCount; if (ReferenceEquals(modifier, PrintParameterModifier.BottomExposureSeconds)) return BottomExposureTime; if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) return ExposureTime; if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightOffDelay)) return BottomLightOffDelay; if (ReferenceEquals(modifier, PrintParameterModifier.LightOffDelay)) return LightOffDelay; if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) return BottomLiftHeight; if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) return LiftHeight; if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) return BottomLiftSpeed; if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) return LiftSpeed; if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) return RetractSpeed; if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) return BottomLightPWM; if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) return LightPWM; return null; } /// /// Sets a property value attributed to /// /// Modifier to use /// Value to set /// True if set, otherwise false = not found public bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, decimal value) { if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerCount)) { BottomLayerCount = (ushort)value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.BottomExposureSeconds)) { BottomExposureTime = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) { ExposureTime = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightOffDelay)) { BottomLightOffDelay = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.LightOffDelay)) { LightOffDelay = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) { BottomLiftHeight = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) { LiftHeight = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) { BottomLiftSpeed = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) { LiftSpeed = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) { RetractSpeed = (float) value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) { BottomLightPWM = (byte)value; return true; } if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) { LightPWM = (byte)value; return true; } return false; } /// /// Sets properties from print parameters /// /// Number of affected parameters public byte SetValuesFromPrintParametersModifiers() { if (PrintParameterModifiers is null) return 0; byte changed = 0; foreach (var modifier in PrintParameterModifiers) { if(!modifier.HasChanged) continue; modifier.OldValue = modifier.NewValue; SetValueFromPrintParameterModifier(modifier, modifier.NewValue); changed++; } return changed; } /// /// Rebuilds GCode based on current settings /// public virtual void RebuildGCode() { if (GCode.IsEmpty) return; GCode.RebuildGCode(this); } /// /// Saves current configuration on input file /// /// public void Save(OperationProgress progress = null) { SaveAs(null, progress); } /// /// Saves current configuration on a copy /// /// File path to save copy as, use null to overwrite active file (Same as ) /// public abstract void SaveAs(string filePath = null, OperationProgress progress = null); /// /// Converts this file type to another file type /// /// Target file format /// Output path file /// /// The converted file if successful, otherwise null public virtual FileFormat Convert(Type to, string fileFullPath, OperationProgress progress = null) { if (!IsValid) return null; var found = AvailableFormats.Any(format => to == format.GetType()); if (!found) return null; progress ??= new OperationProgress("Converting"); var slicerFile = (FileFormat)Activator.CreateInstance(to); if (slicerFile is null) return null; slicerFile.SuppressRebuildProperties = true; slicerFile.LayerManager = _layerManager.Clone(); slicerFile.AntiAliasing = ValidateAntiAliasingLevel(); slicerFile.LayerCount = _layerManager.Count; slicerFile.BottomLayerCount = BottomLayerCount; slicerFile.LayerHeight = LayerHeight; slicerFile.ResolutionX = ResolutionX; slicerFile.ResolutionY = ResolutionY; slicerFile.DisplayWidth = DisplayWidth; slicerFile.DisplayHeight = DisplayHeight; slicerFile.MaxPrintHeight = MaxPrintHeight; slicerFile.MirrorDisplay = MirrorDisplay; slicerFile.BottomExposureTime = BottomExposureTime; slicerFile.ExposureTime = ExposureTime; slicerFile.BottomLiftHeight = BottomLiftHeight; slicerFile.LiftHeight = LiftHeight; slicerFile.BottomLiftSpeed = BottomLiftSpeed; slicerFile.LiftSpeed = LiftSpeed; slicerFile.RetractSpeed = RetractSpeed; slicerFile.BottomLightOffDelay = BottomLightOffDelay; slicerFile.LightOffDelay = LightOffDelay; slicerFile.BottomLightPWM = BottomLightPWM; slicerFile.LightPWM = LightPWM; slicerFile.MachineName = MachineName; slicerFile.MaterialName = MaterialName; slicerFile.MaterialMilliliters = MaterialMilliliters; slicerFile.MaterialGrams = MaterialGrams; slicerFile.MaterialCost = MaterialCost; slicerFile.Xppmm = Xppmm; slicerFile.Yppmm = Yppmm; slicerFile.PrintTime = PrintTime; slicerFile.PrintHeight = PrintHeight; slicerFile.SuppressRebuildProperties = false; slicerFile.SetThumbnails(Thumbnails); slicerFile.Encode(fileFullPath, progress); return slicerFile; } /// /// Converts this file type to another file type /// /// Target file format /// Output path file /// /// TThe converted file if successful, otherwise null public FileFormat Convert(FileFormat to, string fileFullPath, OperationProgress progress = null) => Convert(to.GetType(), fileFullPath, progress); /// /// Validate AntiAlias Level /// public byte ValidateAntiAliasingLevel() { if (AntiAliasing < 2) return 1; if(AntiAliasing % 2 != 0) throw new ArgumentException("AntiAliasing must be multiples of 2, otherwise use 0 or 1 to disable it", nameof(AntiAliasing)); return AntiAliasing; } #endregion } }