/*
* 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 Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Timers;
using UVtools.Core.EmguCV;
using UVtools.Core.Extensions;
using UVtools.Core.GCode;
using UVtools.Core.Layers;
using UVtools.Core.Managers;
using UVtools.Core.Objects;
using UVtools.Core.Operations;
using UVtools.Core.PixelEditor;
using Range = System.Range;
namespace UVtools.Core.FileFormats;
///
/// Slicer representation
///
public abstract class FileFormat : BindableBase, IDisposable, IEquatable, IList
{
#region Constants
///
/// Gets the decimal precision for display properties
///
public const byte DisplayFloatPrecision = 3;
public const SpeedUnit CoreSpeedUnit = SpeedUnit.MillimetersPerMinute;
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.05f;
public const ushort DefaultBottomLayerCount = 4;
public const ushort DefaultTransitionLayerCount = 0;
public const float DefaultBottomExposureTime = 30;
public const float DefaultExposureTime = 3;
public const float DefaultBottomLiftHeight = 5;
public const float DefaultLiftHeight = 5;
public const float DefaultBottomLiftSpeed = 100;
public const float DefaultLiftSpeed = 100;
public const float DefaultBottomLiftHeight2 = 0;
public const float DefaultLiftHeight2 = 0;
public const float DefaultBottomLiftSpeed2 = 300;
public const float DefaultLiftSpeed2 = 300;
public const float DefaultBottomRetractSpeed = 100;
public const float DefaultRetractSpeed = 100;
public const float DefaultBottomRetractHeight2 = 0;
public const float DefaultRetractHeight2 = 0;
public const float DefaultBottomRetractSpeed2 = 80;
public const float DefaultRetractSpeed2 = 80;
public const byte DefaultBottomLightPWM = 255;
public const byte DefaultLightPWM = 255;
public const string DefaultMachineName = "Unknown";
public const byte MaximumAntiAliasing = 16;
public const float MinimumLayerHeight = 0.01f;
public const float MaximumLayerHeight = 0.20f;
private const ushort QueueTimerPrintTime = 250; // ms
public const string DATATYPE_PNG = "PNG";
public const string DATATYPE_JPG = "JPG";
public const string DATATYPE_JPEG = "JPEG";
public const string DATATYPE_JP2 = "JP2";
public const string DATATYPE_BMP = "BMP";
public const string DATATYPE_TIF = "TIF";
public const string DATATYPE_TIFF = "TIFF";
public const string DATATYPE_PPM = "PPM";
public const string DATATYPE_PMG = "PMG";
public const string DATATYPE_SR = "SR";
public const string DATATYPE_RAS = "RAS";
public const string DATATYPE_RGB555 = "RGB555";
public const string DATATYPE_RGB565 = "RGB565";
public const string DATATYPE_RGB555_BE = "RGB555-BE";
public const string DATATYPE_RGB565_BE = "RGB565-BE";
public const string DATATYPE_RGB888 = "RGB888";
public const string DATATYPE_BGR555 = "BGR555";
public const string DATATYPE_BGR565 = "BGR565";
public const string DATATYPE_BGR555_BE = "BGR555-BE";
public const string DATATYPE_BGR565_BE = "BGR565-BE";
public const string DATATYPE_BGR888 = "BGR888";
#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
}
public enum TransitionLayerTypes : byte
{
///
/// Firmware transition layers are handled by printer firmware
///
Firmware,
///
/// Software transition layers are handled by software and written on layer data
///
Software
}
///
/// File decode type
///
public enum FileDecodeType : byte
{
///
/// Decodes all the file information and caches layer images
///
Full,
///
/// Decodes only the information in the file and thumbnails, no layer image is read nor cached, fast
///
Partial,
}
///
/// Image data type
///
public enum FileImageType : byte
{
Custom,
Png8,
Png24,
Png32,
///
/// eg: Nova Bene4
///
Png24BgrAA,
///
/// eg: Uniformation GKone
///
Png24RgbAA,
}
#endregion
#region Sub Classes
///
/// Available Print Parameters to modify
///
public class PrintParameterModifier
{
#region Instances
public static PrintParameterModifier PositionZ { get; } = new ("Position Z", "Absolute Z position", "mm", 0, 100000, 0.01, Layer.HeightPrecision);
public static PrintParameterModifier BottomLayerCount { get; } = new ("Bottom layer count", "Number of bottom/burn-in layers", "layers", 0, ushort.MaxValue, 1, 0);
public static PrintParameterModifier TransitionLayerCount { get; } = new ("Transition layer count", "Number of fade/transition layers", "layers",0, ushort.MaxValue, 1, 0);
public static PrintParameterModifier BottomLightOffDelay { get; } = new("Bottom light-off seconds", "Total motor movement time + rest time to wait before cure a new bottom layer", "s");
public static PrintParameterModifier LightOffDelay { get; } = new("Light-off seconds", "Total motor movement time + rest time to wait before cure a new layer", "s");
public static PrintParameterModifier BottomWaitTimeBeforeCure { get; } = new ("Bottom wait before cure", "Time to wait/rest before cure a new bottom layer\nChitubox: Rest after retract\nLychee: Wait before print", "s");
public static PrintParameterModifier WaitTimeBeforeCure { get; } = new ("Wait before cure", "Time to wait/rest before cure a new layer\nChitubox: Rest after retract\nLychee: Wait before print", "s");
public static PrintParameterModifier BottomExposureTime { get; } = new ("Bottom exposure time", "Bottom layers exposure time", "s", 0.1M);
public static PrintParameterModifier ExposureTime { get; } = new ("Exposure time", "Normal layers exposure time", "s", 0.1M);
public static PrintParameterModifier BottomWaitTimeAfterCure { get; } = new("Bottom wait after cure", "Time to wait/rest after cure a new bottom layer\nChitubox: Rest before lift\nLychee: Wait after print", "s");
public static PrintParameterModifier WaitTimeAfterCure { get; } = new("Wait after cure", "Time to wait/rest after cure a new bottom layer\nChitubox: Rest before lift\nLychee: Wait after print", "s");
public static PrintParameterModifier BottomLiftHeight { get; } = new ("Bottom lift height", "Bottom lift/peel height between layers", "mm");
public static PrintParameterModifier LiftHeight { get; } = new ("Lift height", @"Lift/peel height between layers", "mm");
public static PrintParameterModifier BottomLiftSpeed { get; } = new ("Bottom lift speed", "Lift speed of bottom layers", "mm/min", 10, 5000, 5);
public static PrintParameterModifier LiftSpeed { get; } = new ("Lift speed", "Lift speed of normal layers", "mm/min", 10, 5000, 5);
public static PrintParameterModifier BottomLiftHeight2 { get; } = new("2) Bottom lift height", "Bottom second lift/peel height between layers", "mm");
public static PrintParameterModifier LiftHeight2 { get; } = new("2) Lift height", @"Second lift/peel height between layers", "mm");
public static PrintParameterModifier BottomLiftSpeed2 { get; } = new("2) Bottom lift speed", "Lift speed of bottom layers for the second lift sequence (TSMC)", "mm/min", 0, 5000, 5);
public static PrintParameterModifier LiftSpeed2 { get; } = new("2) Lift speed", "Lift speed of normal layers for the second lift sequence (TSMC)", "mm/min", 0, 5000, 5);
public static PrintParameterModifier BottomWaitTimeAfterLift { get; } = new("Bottom wait after lift", "Time to wait/rest after a lift/peel sequence at bottom layers\nChitubox: Rest after lift\nLychee: Wait after lift", "s");
public static PrintParameterModifier WaitTimeAfterLift { get; } = new("Wait after lift", "Time to wait/rest after a lift/peel sequence at layers\nChitubox: Rest after lift\nLychee: Wait after lift", "s");
public static PrintParameterModifier BottomRetractSpeed { get; } = new ("Bottom retract speed", "Bottom down speed from lift height to next layer cure position", "mm/min", 10, 5000, 5);
public static PrintParameterModifier RetractSpeed { get; } = new ("Retract speed", "Down speed from lift height to next layer cure position", "mm/min", 10, 5000, 5);
public static PrintParameterModifier BottomRetractHeight2 { get; } = new("2) Bottom retract height", "Slow retract height of bottom layers (TSMC)", "mm");
public static PrintParameterModifier RetractHeight2 { get; } = new("2) Retract height", "Slow retract height of normal layers (TSMC)", "mm");
public static PrintParameterModifier BottomRetractSpeed2 { get; } = new("2) Bottom retract speed", "Slow retract speed of bottom layers (TSMC)", "mm/min", 0, 5000, 5);
public static PrintParameterModifier RetractSpeed2 { get; } = new("2) Retract speed", "Slow retract speed of normal layers (TSMC)", "mm/min", 0, 5000, 5);
public static PrintParameterModifier BottomLightPWM { get; } = new ("Bottom light PWM", "UV LED power for bottom layers", "☀", 1, byte.MaxValue, 5, 0);
public static PrintParameterModifier LightPWM { get; } = new ("Light PWM", "UV LED power for layers", "☀", 1, byte.MaxValue, 5, 0);
/*public static PrintParameterModifier[] Parameters = {
BottomLayerCount,
BottomWaitTimeBeforeCure,
WaitTimeBeforeCure,
BottomExposureTime,
ExposureTime,
BottomWaitTimeAfterCure,
WaitTimeAfterCure,
BottomLightOffDelay,
LightOffDelay,
BottomLiftHeight,
BottomLiftSpeed,
LiftHeight,
LiftSpeed,
BottomWaitTimeAfterLift,
WaitTimeAfterLift,
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 incrementing value for the dropdown
///
public double Increment { get; set; } = 1;
///
/// 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, double increment = 0.5, byte decimalPlates = 2)
{
Name = name;
Description = description ?? $"Modify '{name}'";
ValueUnit = valueUnit ?? string.Empty;
Minimum = minimum;
Maximum = maximum;
Increment = decimalPlates == 0 ? Math.Max(1, increment) : increment;
DecimalPlates = decimalPlates;
}
#endregion
#region Overrides
protected bool Equals(PrintParameterModifier other)
{
return Name == other.Name;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((PrintParameterModifier) obj);
}
public override int GetHashCode()
{
return (Name != null ? Name.GetHashCode() : 0);
}
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 CTBEncryptedFile(), // encrypted ctb
new PhotonSFile(), // photons
new PHZFile(), // phz
new PhotonWorkshopFile(), // PSW
new CWSFile(), // CWS
new AnetFile(), // Anet N4, N7
new LGSFile(), // LGS, LGS30
new VDAFile(), // VDA
new VDTFile(), // VDT
//new CXDLPv1File(), // Creality Box v1
new CXDLPFile(), // Creality Box
new FDGFile(), // fdg
new ZCodeFile(), // zcode
new JXSFile(), // jxs
new ZCodexFile(), // zcodex
new MDLPFile(), // MKS v1
new GR1File(), // GR1 Workshop
new FlashForgeSVGXFile(), // SVGX
new OSLAFile(), // OSLA
new OSFFile(), // OSF
new UVJFile(), // UVJ
new GenericZIPFile(), // Generic zip files
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("All slicer files", new List())
};
foreach (var format in AvailableFormats)
{
foreach (var fileExtension in format.FileExtensions)
{
if(!fileExtension.IsVisibleOnFileFilters) continue;
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
///
/// name 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? FindByExtensionOrFilePath(string extensionOrFilePath, bool createNewInstance = false) =>
FindByExtensionOrFilePath(extensionOrFilePath, out _, createNewInstance);
///
/// Find by an extension
///
/// name to find
/// Number of file formats sharing the input extension
/// 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? FindByExtensionOrFilePath(string extensionOrFilePath, out byte fileFormatsSharingExt, bool createNewInstance = false)
{
fileFormatsSharingExt = 0;
if (string.IsNullOrWhiteSpace(extensionOrFilePath)) return null;
bool isFilePath = false;
// Test for ext first
var fileFormats = AvailableFormats.Where(fileFormat => fileFormat.IsExtensionValid(extensionOrFilePath)).ToArray();
fileFormatsSharingExt = (byte)fileFormats.Length;
if (fileFormats.Length == 0) // Extension not found, can be filepath, try to find it
{
GetFileNameStripExtensions(extensionOrFilePath, out var extension);
if (string.IsNullOrWhiteSpace(extension)) return null;
fileFormats = AvailableFormats.Where(fileFormat => fileFormat.IsExtensionValid(extension)).ToArray();
if (fileFormats.Length == 0) return null;
isFilePath = true; // Was a file path
}
if (fileFormats.Length == 1 || !isFilePath)
{
return createNewInstance
? Activator.CreateInstance(fileFormats[0].GetType()) as FileFormat
: fileFormats[0];
}
// Multiple instances using Check for valid candidate
foreach (var fileFormat in fileFormats)
{
if (fileFormat.CanProcess(extensionOrFilePath))
{
return createNewInstance
? Activator.CreateInstance(fileFormat.GetType()) as FileFormat
: fileFormat;
}
}
// Try this in a far and not probable attempt
return createNewInstance
? Activator.CreateInstance(fileFormats[0].GetType()) as FileFormat
: fileFormats[0];
}
///
/// Find by an type name
///
/// Type name 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(string type, bool createNewInstance = false)
{
if (!type.EndsWith("File"))
{
type += "File";
}
var fileFormat = AvailableFormats.FirstOrDefault(format => string.Equals(format.GetType().Name, type, StringComparison.OrdinalIgnoreCase));
if (fileFormat is null) return null;
return createNewInstance
? Activator.CreateInstance(fileFormat.GetType()) as FileFormat
: fileFormat;
//return (from t in AvailableFormats where type == t.GetType() select createNewInstance ? (FileFormat) Activator.CreateInstance(type) : t).FirstOrDefault();
}
///
/// Find by any means (type name, extension, filepath)
///
/// Name 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? FindByAnyMeans(string name, bool createNewInstance = false)
{
return FindByType(name, true)
?? FindByExtensionOrFilePath(name, true);
}
///
/// 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)
{
var fileFormat = AvailableFormats.FirstOrDefault(format => format.GetType() == type);
if (fileFormat is null) return null;
return createNewInstance
? Activator.CreateInstance(type) as FileFormat
: fileFormat;
//return (from t in AvailableFormats where type == t.GetType() select createNewInstance ? (FileFormat) Activator.CreateInstance(type) : t).FirstOrDefault();
}
public static FileExtension? FindExtension(string extension)
{
return AvailableFormats.SelectMany(format => format.FileExtensions).FirstOrDefault(ext => ext.Equals(extension));
}
public static IEnumerable FindExtensions(string extension)
{
return AvailableFormats.SelectMany(format => format.FileExtensions).Where(ext => ext.Equals(extension));
}
public static string? GetFileNameStripExtensions(string? filepath)
{
if (filepath is null) return null;
//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);
}
public static FileFormat? Open(string fileFullPath, FileDecodeType decodeType, OperationProgress? progress = null)
{
var slicerFile = FindByExtensionOrFilePath(fileFullPath, true);
if (slicerFile is null) return null;
slicerFile.Decode(fileFullPath, decodeType, progress);
return slicerFile;
}
public static FileFormat? Open(string fileFullPath, OperationProgress? progress = null) =>
Open(fileFullPath, FileDecodeType.Full, progress);
public static Task OpenAsync(string fileFullPath, FileDecodeType decodeType, OperationProgress? progress = null)
=> Task.Run(() => Open(fileFullPath, decodeType, progress), progress?.Token ?? default);
public static Task OpenAsync(string fileFullPath, OperationProgress? progress = null) => OpenAsync(fileFullPath, FileDecodeType.Full, progress);
public static float RoundDisplaySize(float value) => (float) Math.Round(value, DisplayFloatPrecision);
public static double RoundDisplaySize(double value) => Math.Round(value, DisplayFloatPrecision);
public static decimal RoundDisplaySize(decimal value) => (decimal) Math.Round(value, DisplayFloatPrecision);
///
/// Copy parameters from one file to another
///
/// From source file
/// To target file
/// Number of affected parameters
public static uint CopyParameters(FileFormat from, FileFormat to)
{
if (ReferenceEquals(from, to)) return 0;
if (from.PrintParameterModifiers is null || to.PrintParameterModifiers is null) return 0;
uint count = 0;
to.RefreshPrintParametersModifiersValues();
var targetPrintModifier = to.PrintParameterModifiers.ToArray();
from.RefreshPrintParametersModifiersValues();
foreach (var sourceModifier in from.PrintParameterModifiers)
{
if (!targetPrintModifier.Contains(sourceModifier)) continue;
var fromValueObj = from.GetValueFromPrintParameterModifier(sourceModifier);
var toValueObj = to.GetValueFromPrintParameterModifier(sourceModifier);
if (fromValueObj is null || toValueObj is null) continue;
var fromValue = System.Convert.ToDecimal(fromValueObj);
var toValue = System.Convert.ToDecimal(toValueObj);
if (fromValue != toValue)
{
to.SetValueFromPrintParameterModifier(sourceModifier, fromValue);
count++;
}
}
return count;
}
///
/// Compress a layer from a
///
/// to compress
/// Compressed byte array
public static byte[] CompressLayer(Stream input)
{
return CompressLayer(input.ToArray());
}
///
/// Compress a layer from a byte array
///
/// byte array to compress
/// Compressed byte array
public static byte[] CompressLayer(byte[] input)
{
return input;
/*using (MemoryStream output = new MemoryStream())
{
using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal))
{
dstream.Write(input, 0, input.Length);
}
return output.ToArray();
}*/
}
///
/// Decompress a layer from a byte array
///
/// byte array to decompress
/// Decompressed byte array
public static byte[] DecompressLayer(byte[] input)
{
return input;
/*using (MemoryStream ms = new MemoryStream(input))
{
using (MemoryStream output = new MemoryStream())
{
using (DeflateStream dstream = new DeflateStream(ms, CompressionMode.Decompress))
{
dstream.CopyTo(output);
}
return output.ToArray();
}
}*/
}
public static byte[] EncodeImage(string dataType, Mat mat)
{
dataType = dataType.ToUpperInvariant();
if (dataType
is DATATYPE_PNG
or DATATYPE_JPG
or DATATYPE_JPEG
or DATATYPE_JP2
or DATATYPE_BMP
or DATATYPE_TIF
or DATATYPE_TIFF
or DATATYPE_PPM
or DATATYPE_PMG
or DATATYPE_SR
or DATATYPE_RAS
)
{
return CvInvoke.Imencode($".{dataType.ToLowerInvariant()}", mat);
}
if (dataType
is DATATYPE_RGB555
or DATATYPE_RGB565
or DATATYPE_RGB555_BE
or DATATYPE_RGB565_BE
or DATATYPE_RGB888
or DATATYPE_BGR555
or DATATYPE_BGR565
or DATATYPE_BGR555_BE
or DATATYPE_BGR565_BE
or DATATYPE_BGR888
)
{
var bytesPerPixel = dataType is "RGB888" or "BGR888" ? 3 : 2;
var bytes = new byte[mat.Width * mat.Height * bytesPerPixel];
uint index = 0;
var span = mat.GetDataByteSpan();
for (int i = 0; i < span.Length;)
{
byte b = span[i++];
byte g;
byte r;
if (mat.NumberOfChannels == 1) // 8 bit safe-guard
{
r = g = b;
}
else
{
g = span[i++];
r = span[i++];
}
if (mat.NumberOfChannels == 4) i++; // skip alpha
switch (dataType)
{
case DATATYPE_RGB555:
var rgb555 = (ushort)(((r & 0b11111000) << 7) | ((g & 0b11111000) << 2) | (b >> 3));
BitExtensions.ToBytesLittleEndian(rgb555, bytes, index);
index += 2;
break;
case DATATYPE_RGB565:
var rgb565 = (ushort)(((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3));
BitExtensions.ToBytesLittleEndian(rgb565, bytes, index);
index += 2;
break;
case DATATYPE_RGB555_BE:
var rgb555Be = (ushort)(((r & 0b11111000) << 7) | ((g & 0b11111000) << 2) | (b >> 3));
BitExtensions.ToBytesBigEndian(rgb555Be, bytes, index);
index += 2;
break;
case DATATYPE_RGB565_BE:
var rgb565Be = (ushort)(((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3));
BitExtensions.ToBytesBigEndian(rgb565Be, bytes, index);
index += 2;
break;
case DATATYPE_RGB888:
bytes[index++] = r;
bytes[index++] = g;
bytes[index++] = b;
break;
case DATATYPE_BGR555:
var bgr555 = (ushort)(((b & 0b11111000) << 7) | ((g & 0b11111000) << 2) | (r >> 3));
BitExtensions.ToBytesLittleEndian(bgr555, bytes, index);
index += 2;
break;
case DATATYPE_BGR565:
var bgr565 = (ushort)(((b & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (r >> 3));
BitExtensions.ToBytesLittleEndian(bgr565, bytes, index);
index += 2;
break;
case DATATYPE_BGR555_BE:
var bgr555Be = (ushort)(((b & 0b11111000) << 7) | ((g & 0b11111000) << 2) | (r >> 3));
BitExtensions.ToBytesBigEndian(bgr555Be, bytes, index);
index += 2;
break;
case DATATYPE_BGR565_BE:
var bgr565Be = (ushort)(((b & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (r >> 3));
BitExtensions.ToBytesBigEndian(bgr565Be, bytes, index);
index += 2;
break;
case DATATYPE_BGR888:
bytes[index++] = b;
bytes[index++] = g;
bytes[index++] = r;
break;
}
}
return bytes;
}
throw new NotSupportedException($"The encode type: {dataType} is not supported.");
}
public static Mat DecodeImage(string dataType, byte[] bytes, Size resolution)
{
if (dataType
is DATATYPE_PNG
or DATATYPE_JPG
or DATATYPE_JPEG
or DATATYPE_JP2
or DATATYPE_BMP
or DATATYPE_TIF
or DATATYPE_TIFF
or DATATYPE_PPM
or DATATYPE_PMG
or DATATYPE_SR
or DATATYPE_RAS
)
{
var mat = new Mat();
CvInvoke.Imdecode(bytes, ImreadModes.AnyColor, mat);
return mat;
}
if (dataType
is DATATYPE_RGB555
or DATATYPE_RGB565
or DATATYPE_RGB555_BE
or DATATYPE_RGB565_BE
or DATATYPE_RGB888
or DATATYPE_BGR555
or DATATYPE_BGR565
or DATATYPE_BGR555_BE
or DATATYPE_BGR565_BE
or DATATYPE_BGR888
)
{
var mat = new Mat(resolution, DepthType.Cv8U, 3);
var span = mat.GetDataByteSpan();
var pixel = 0;
int i = 0;
while (i < bytes.Length && pixel < span.Length)
{
switch (dataType)
{
case DATATYPE_RGB555:
ushort rgb555 = BitExtensions.ToUShortLittleEndian(bytes, i);
// 0b0rrrrrgggggbbbbb
span[pixel++] = (byte)((rgb555 & 0b00000000_00011111) << 3); // b
span[pixel++] = (byte)((rgb555 & 0b00000011_11100000) >> 2); // g
span[pixel++] = (byte)((rgb555 & 0b01111100_00000000) >> 7); // r
/*span[pixel++] = (byte)((rgb555 << 3) & 0b11111000); // b
span[pixel++] = (byte)((rgb555 >> 2) & 0b11111000); // g
span[pixel++] = (byte)((rgb555 >> 7) & 0b11111000); // r*/
i += 2;
break;
case DATATYPE_RGB565:
// 0brrrrrggggggbbbbb
ushort rgb565 = BitExtensions.ToUShortLittleEndian(bytes, i);
span[pixel++] = (byte)((rgb565 & 0b00000000_00011111) << 3); // b
span[pixel++] = (byte)((rgb565 & 0b00000111_11100000) >> 3); // g
span[pixel++] = (byte)((rgb565 & 0b11111000_00000000) >> 8); // r
i += 2;
break;
case DATATYPE_RGB555_BE:
ushort rgb555Be = BitExtensions.ToUShortBigEndian(bytes, i);
span[pixel++] = (byte)((rgb555Be & 0b00000000_00011111) << 3); // b
span[pixel++] = (byte)((rgb555Be & 0b00000011_11100000) >> 2); // g
span[pixel++] = (byte)((rgb555Be & 0b01111100_00000000) >> 7); // r
i += 2;
break;
case DATATYPE_RGB565_BE:
ushort rgb565Be = BitExtensions.ToUShortBigEndian(bytes, i);
span[pixel++] = (byte)((rgb565Be & 0b00000000_00011111) << 3); // b
span[pixel++] = (byte)((rgb565Be & 0b00000111_11100000) >> 3); // g
span[pixel++] = (byte)((rgb565Be & 0b11111000_00000000) >> 8); // r
i += 2;
break;
case DATATYPE_RGB888:
span[pixel++] = bytes[i + 2]; // b
span[pixel++] = bytes[i + 1]; // g
span[pixel++] = bytes[i]; // r
i += 3;
break;
case DATATYPE_BGR555:
ushort bgr555 = BitExtensions.ToUShortLittleEndian(bytes, i);
span[pixel++] = (byte)((bgr555 & 0b01111100_00000000) >> 7); // b
span[pixel++] = (byte)((bgr555 & 0b00000011_11100000) >> 2); // g
span[pixel++] = (byte)((bgr555 & 0b00000000_00011111) << 3); // r
i += 2;
break;
case DATATYPE_BGR565:
ushort bgr565 = BitExtensions.ToUShortLittleEndian(bytes, i);
span[pixel++] = (byte)((bgr565 & 0b11111000_00000000) >> 8); // b
span[pixel++] = (byte)((bgr565 & 0b00000111_11100000) >> 3); // g
span[pixel++] = (byte)((bgr565 & 0b00000000_00011111) << 3); // r
i += 2;
break;
case DATATYPE_BGR555_BE:
ushort bgr555Be = BitExtensions.ToUShortBigEndian(bytes, i);
span[pixel++] = (byte)((bgr555Be & 0b01111100_00000000) >> 7); // b
span[pixel++] = (byte)((bgr555Be & 0b00000011_11100000) >> 2); // g
span[pixel++] = (byte)((bgr555Be & 0b00000000_00011111) << 3); // r
i += 2;
break;
case DATATYPE_BGR565_BE:
ushort bgr565Be = BitExtensions.ToUShortBigEndian(bytes, i);
span[pixel++] = (byte)((bgr565Be & 0b11111000_00000000) >> 8); // b
span[pixel++] = (byte)((bgr565Be & 0b00000111_11100000) >> 3); // g
span[pixel++] = (byte)((bgr565Be & 0b00000000_00011111) << 3); // r
i += 2;
break;
case DATATYPE_BGR888:
span[pixel++] = bytes[i]; // b
span[pixel++] = bytes[i + 1]; // g
span[pixel++] = bytes[i + 2]; // r
i += 3;
break;
}
}
for (; pixel < span.Length; pixel++) // Fill leftovers
{
span[pixel] = 0;
}
return mat;
}
throw new NotSupportedException($"The decode type: {dataType} is not supported.");
}
public static Mat DecodeImage(string dataType, byte[] bytes, uint resolutionX = 0, uint resolutionY = 0)
=> DecodeImage(dataType, bytes, new Size((int)resolutionX, (int)resolutionY));
public static void MutateGetVarsIterationChamfer(uint startLayerIndex, uint endLayerIndex, int iterationsStart, int iterationsEnd, ref bool isFade, out float iterationSteps, out int maxIteration)
{
iterationSteps = 0;
maxIteration = 0;
isFade = isFade && startLayerIndex != endLayerIndex && iterationsStart != iterationsEnd;
if (!isFade) return;
iterationSteps = Math.Abs((iterationsStart - (float)iterationsEnd) / ((float)endLayerIndex - startLayerIndex));
maxIteration = Math.Max(iterationsStart, iterationsEnd);
}
public static int MutateGetIterationVar(bool isFade, int iterationsStart, int iterationsEnd, float iterationSteps, int maxIteration, uint startLayerIndex, uint layerIndex)
{
if (!isFade) return iterationsStart;
// calculate iterations based on range
int iterations = (int)(iterationsStart < iterationsEnd
? iterationsStart + (layerIndex - startLayerIndex) * iterationSteps
: iterationsStart - (layerIndex - startLayerIndex) * iterationSteps);
// constrain
return Math.Min(Math.Max(0, iterations), maxIteration);
}
public static int MutateGetIterationChamfer(uint layerIndex, uint startLayerIndex, uint endLayerIndex, int iterationsStart,
int iterationsEnd, bool isFade)
{
MutateGetVarsIterationChamfer(startLayerIndex, endLayerIndex, iterationsStart, iterationsEnd, ref isFade,
out float iterationSteps, out int maxIteration);
return MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, layerIndex);
}
#endregion
#region Members
public object Mutex = new();
protected Layer[] _layers = Array.Empty();
private bool _haveModifiedLayers;
private uint _version;
private uint _resolutionX;
private uint _resolutionY;
private float _displayWidth;
private float _displayHeight;
private byte _antiAliasing = 1;
private ushort _bottomLayerCount = DefaultBottomLayerCount;
private ushort _transitionLayerCount = DefaultTransitionLayerCount;
private float _bottomLightOffDelay;
private float _lightOffDelay;
private float _bottomWaitTimeBeforeCure;
private float _waitTimeBeforeCure;
private float _bottomExposureTime = DefaultBottomExposureTime;
private float _exposureTime = DefaultExposureTime;
private float _bottomWaitTimeAfterCure;
private float _waitTimeAfterCure;
private float _bottomLiftHeight = DefaultBottomLiftHeight;
private float _liftHeight = DefaultLiftHeight;
private float _bottomLiftSpeed = DefaultBottomLiftSpeed;
private float _liftSpeed = DefaultLiftSpeed;
private float _bottomLiftHeight2 = DefaultBottomLiftHeight2;
private float _liftHeight2 = DefaultLiftHeight2;
private float _bottomLiftSpeed2 = DefaultBottomLiftSpeed2;
private float _liftSpeed2 = DefaultLiftSpeed2;
private float _bottomWaitTimeAfterLift;
private float _waitTimeAfterLift;
private float _bottomRetractHeight2 = DefaultBottomRetractHeight2;
private float _retractHeight2 = DefaultRetractHeight2;
private float _bottomRetractSpeed2 = DefaultBottomRetractSpeed2;
private float _retractSpeed2 = DefaultRetractSpeed2;
private float _bottomRetractSpeed = DefaultBottomRetractSpeed;
private float _retractSpeed = DefaultRetractSpeed;
private byte _bottomLightPwm = DefaultBottomLightPWM;
private byte _lightPwm = DefaultLightPWM;
private float _printTime;
private float _materialMilliliters;
private float _machineZ;
private string _machineName = "Unknown";
private string? _materialName;
private float _materialGrams;
private float _materialCost;
private bool _suppressRebuildGCode;
private Rectangle _boundingRectangle = Rectangle.Empty;
private readonly Timer _queueTimerPrintTime = new(QueueTimerPrintTime){AutoReset = false};
#endregion
#region Properties
public FileDecodeType DecodeType { get; private set; } = FileDecodeType.Full;
///
/// Gets the file format type
///
public abstract FileFormatType FileType { get; }
///
/// Gets the layer image data type used on this file format
///
public virtual FileImageType LayerImageType => FileType == FileFormatType.Archive ? FileImageType.Png8 : FileImageType.Custom;
///
/// Gets the group name under convert menu to group all extensions, set to null to not group items
///
public virtual string? ConvertMenuGroup => null;
///
/// Gets the valid file extensions for this
///
public abstract FileExtension[] FileExtensions { get; }
///
/// The speed unit used by this file format in his internal data
///
public virtual SpeedUnit FormatSpeedUnit => CoreSpeedUnit;
///
/// Gets the available
///
public virtual PrintParameterModifier[]? PrintParameterModifiers => null;
///
/// Gets the available per layer
///
public virtual PrintParameterModifier[]? PrintParameterPerLayerModifiers => 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 layer parameters
///
///
/// True if exists, otherwise false
public bool HaveLayerParameterModifier(PrintParameterModifier modifier) =>
SupportPerLayerSettings && 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 temporary output file path to use on save and encode
///
public string TemporaryOutputFileFullPath => $"{FileFullPath}{TemporaryFileAppend}";
///
/// Gets the input file path loaded into this
///
public string? FileFullPath { get; set; }
public string? DirectoryPath => Path.GetDirectoryName(FileFullPath);
public string? Filename => Path.GetFileName(FileFullPath);
///
/// Returns the file extension. The returned value includes the period (".") character of the
/// extension except when you have a terminal period when you get string.Empty, such as ".exe" or ".cpp".
/// The returned value is null if the given path is null or empty if the given path does not include an
/// extension.
///
public string? FileExtension => Path.GetExtension(FileFullPath);
public string? FilenameNoExt => GetFileNameStripExtensions(FileFullPath);
///
/// Gets the available versions to set in this file format
///
public virtual uint[] AvailableVersions => Array.Empty();
///
/// Gets the amount of available versions in this file format
///
public virtual byte AvailableVersionsCount => (byte)AvailableVersions.Length;
///
/// Gets the available versions to set in this file format for the given extension
///
/// Extension name, with or without dot (.)
///
public virtual uint[] GetAvailableVersionsForExtension(string? extension) => AvailableVersions;
///
/// Gets the available versions to set in this file format for the given file name
///
///
///
public uint[] GetAvailableVersionsForFileName(string? fileName) => GetAvailableVersionsForExtension(Path.GetExtension(fileName));
///
/// Gets the default version to use in this file when not setting the version
///
public virtual uint DefaultVersion => 0;
///
/// Gets or sets the version of this file format
///
public virtual uint Version
{
get => _version;
set
{
if (AvailableVersions.Length > 0 && !AvailableVersions.Contains(value))
{
throw new VersionNotFoundException($"Version {value} not known for this file format");
}
RequireFullEncode = true;
RaiseAndSetIfChanged(ref _version, value);
}
}
///
/// Gets the thumbnails count present in this file format
///
public byte ThumbnailsCount => (byte)(ThumbnailsOriginalSize?.Length ?? 0);
///
/// 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 || thumbnail.IsEmpty) continue;
count++;
}
return count;
}
}
///
/// Gets the original thumbnail sizes
///
public virtual Size[]? ThumbnailsOriginalSize => null;
///
/// Gets the thumbnails for this
///
public Mat?[] Thumbnails { get; init; }
public IssueManager IssueManager { get; }
///
/// Layers List
///
public Layer[] Layers
{
get => _layers;
set
{
if (value is null)
{
throw new ArgumentNullException(nameof(value));
}
//if (ReferenceEquals(_layers, value)) return;
var rebuildProperties = false;
var oldLayerCount = LayerCount;
var oldLayers = _layers;
_layers = value;
BoundingRectangle = Rectangle.Empty;
if (LayerCount != oldLayerCount)
{
LayerCount = LayerCount;
}
RequireFullEncode = true;
PrintHeight = PrintHeight;
UpdatePrintTime();
if (LayerCount > 0)
{
//SetAllIsModified(true);
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) // Forced sanitize
{
if (_layers[layerIndex] is null) continue;
_layers[layerIndex].Index = layerIndex;
_layers[layerIndex].SlicerFile = this;
if (layerIndex >= oldLayerCount || layerIndex < oldLayerCount && !_layers[layerIndex].Equals(oldLayers[layerIndex]))
{
// Marks as modified only if layer image changed on this index
_layers[layerIndex].IsModified = true;
}
}
if (LayerCount != oldLayerCount && !SuppressRebuildProperties && LastLayer is not null)
{
RebuildLayersProperties();
rebuildProperties = true;
}
}
if (!rebuildProperties)
{
MaterialMilliliters = -1;
RebuildGCode();
}
RaisePropertyChanged();
}
}
///
/// First layer index, this is always 0
///
public const uint FirstLayerIndex = 0;
///
/// Gets the last layer index
///
public uint LastLayerIndex => LayerCount > 0 ? LayerCount - 1 : 0;
///
/// Gets the first layer
///
public Layer? FirstLayer => LayerCount > 0 ? this[0] : null;
///
/// Gets the last bottom layer
///
public Layer? LastBottomLayer => this.LastOrDefault(layer => layer.IsBottomLayer);
///
/// Gets the first normal layer
///
public Layer? FirstNormalLayer => this.FirstOrDefault(layer => layer.IsNormalLayer);
///
/// Gets the last layer
///
public Layer? LastLayer => LayerCount > 0 ? this[^1] : null;
///
/// Gets the smallest bottom layer using the pixel count
///
public Layer? SmallestBottomLayer => this.Where(layer => layer is {IsBottomLayer: true, IsEmpty: false}).MinBy(layer => layer.NonZeroPixelCount);
///
/// Gets the largest bottom layer using the pixel count
///
public Layer? LargestBottomLayer => this.Where(layer => layer is {IsBottomLayer: true, IsEmpty: false}).MaxBy(layer => layer.NonZeroPixelCount);
///
/// Gets the smallest normal layer using the pixel count
///
public Layer? SmallestNormalLayer => this.Where(layer => layer is {IsNormalLayer: true, IsEmpty: false}).MinBy(layer => layer.NonZeroPixelCount);
///
/// Gets the largest layer using the pixel count
///
public Layer? LargestNormalLayer => this.Where(layer => layer is {IsNormalLayer: true, IsEmpty: false}).MaxBy(layer => layer.NonZeroPixelCount);
///
/// Gets the smallest normal layer using the pixel count
///
public Layer? SmallestLayer => this.Where(layer => !layer.IsEmpty).MinBy(layer => layer.NonZeroPixelCount);
///
/// Gets the largest layer using the pixel count
///
public Layer? LargestLayer => this.MaxBy(layer => layer.NonZeroPixelCount);
public Layer? GetSmallestLayerBetween(uint layerStartIndex, uint layerEndIndex)
{
return this.Where((layer, index) => !layer.IsEmpty && index >= layerStartIndex && index <= layerEndIndex).MinBy(layer => layer.NonZeroPixelCount);
}
public Layer? GetLargestLayerBetween(uint layerStartIndex, uint layerEndIndex)
{
return this.Where((layer, index) => !layer.IsEmpty && index >= layerStartIndex && index <= layerEndIndex).MaxBy(layer => layer.NonZeroPixelCount);
}
///
/// Gets all bottom layers
///
///
public IEnumerable BottomLayers => this.Where(layer => layer.IsBottomLayer);
///
/// Gets all normal layers
///
///
public IEnumerable NormalLayers => this.Where(layer => layer.IsNormalLayer);
///
/// Gets all transition layers
///
///
public IEnumerable TransitionLayers => this.Where(layer => layer.IsTransitionLayer);
///
/// Gets all layers that use TSMC values
///
///
public IEnumerable TsmcLayers => this.Where(layer => layer.IsUsingTSMC);
///
/// Gets all layers on same position but exclude the first layer on that position
///
public IEnumerable SamePositionedLayers
{
get
{
for (int layerIndex = 1; layerIndex < LayerCount; layerIndex++)
{
var layer = this[layerIndex];
if (this[layerIndex - 1].PositionZ != layer.PositionZ) continue;
yield return layer;
}
}
}
public IEnumerable GetDistinctLayersByPositionZ(uint layerIndexStart = 0) =>
GetDistinctLayersByPositionZ(layerIndexStart, LastLayerIndex);
public IEnumerable GetDistinctLayersByPositionZ(uint layerIndexStart, uint layerIndexEnd)
{
return layerIndexEnd - layerIndexStart >= LastLayerIndex
? this.DistinctBy(layer => layer.PositionZ)
: this.Where((_, layerIndex) => layerIndex >= layerIndexStart && layerIndex <= layerIndexEnd).DistinctBy(layer => layer.PositionZ);
}
public IEnumerable GetLayersFromHeightRange(float startPositionZ, float endPositionZ)
{
return this.Where(layer => layer.PositionZ >= startPositionZ && layer.PositionZ <= endPositionZ);
}
public IEnumerable GetLayersFromHeightRange(float endPositionZ)
{
return this.Where(layer => layer.PositionZ <= endPositionZ);
}
///
/// True if all layers are using same value parameters as global settings, otherwise false
///
public bool AllLayersAreUsingGlobalParameters => this.All(layer => layer.IsUsingGlobalParameters);
///
/// True if any layer is using TSMC, otherwise false when none of layers is using TSMC
///
public bool AnyLayerIsUsingTSMC => this.Any(layer => layer.IsUsingTSMC);
///
/// True if the file global property is using TSMC, otherwise false when not using
///
public bool IsUsingTSMC => (CanUseAnyLiftHeight2 || CanUseAnyRetractHeight2) && (BottomLiftHeight2 > 0 || BottomRetractHeight2 > 0 || LiftHeight2 > 0 || RetractHeight2 > 0);
///
/// Gets if any layer got modified, otherwise false
/// Sets all layers `IsModified` flag
///
public bool IsModified
{
get
{
for (uint i = 0; i < LayerCount; i++)
{
if (this[i].IsModified) return true;
}
return false;
}
set
{
for (uint i = 0; i < LayerCount; i++)
{
if (this[i] is null) continue;
this[i].IsModified = value;
}
}
}
///
/// Gets the bounding rectangle of the model
///
public Rectangle BoundingRectangle
{
get => GetBoundingRectangle();
set
{
RaiseAndSetIfChanged(ref _boundingRectangle, value);
RaisePropertyChanged(nameof(BoundingRectangleMillimeters));
}
}
///
/// Gets the bounding rectangle of the object in millimeters
///
public RectangleF BoundingRectangleMillimeters
{
get
{
var pixelSize = PixelSize;
return new RectangleF(
(float)Math.Round(_boundingRectangle.X * pixelSize.Width, 2),
(float)Math.Round(_boundingRectangle.Y * pixelSize.Height, 2),
(float)Math.Round(_boundingRectangle.Width * pixelSize.Width, 2),
(float)Math.Round(_boundingRectangle.Height * pixelSize.Height, 2));
}
}
///
/// Gets or sets if modifications require a full encode to save
///
public bool RequireFullEncode
{
get => _haveModifiedLayers || IsModified;
set => RaiseAndSetIfChanged(ref _haveModifiedLayers, value);
}
///
/// Gets the image width and height 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 virtual uint ResolutionX
{
get => _resolutionX;
set
{
if(!RaiseAndSetIfChanged(ref _resolutionX, value)) return;
RaisePropertyChanged(nameof(Resolution));
RaisePropertyChanged(nameof(ResolutionRectangle));
RaisePropertyChanged(nameof(DisplayPixelCount));
RaisePropertyChanged(nameof(DisplayAspectRatio));
RaisePropertyChanged(nameof(DisplayAspectRatioStr));
RaisePropertyChanged(nameof(IsDisplayPortrait));
RaisePropertyChanged(nameof(IsDisplayLandscape));
RaisePropertyChanged(nameof(Xppmm));
RaisePropertyChanged(nameof(PixelWidth));
RaisePropertyChanged(nameof(PixelWidthMicrons));
NotifyAspectChange();
}
}
///
/// Gets the image height resolution
///
public virtual uint ResolutionY
{
get => _resolutionY;
set
{
if(!RaiseAndSetIfChanged(ref _resolutionY, value)) return;
RaisePropertyChanged(nameof(Resolution));
RaisePropertyChanged(nameof(ResolutionRectangle));
RaisePropertyChanged(nameof(DisplayPixelCount));
RaisePropertyChanged(nameof(DisplayAspectRatio));
RaisePropertyChanged(nameof(DisplayAspectRatioStr));
RaisePropertyChanged(nameof(IsDisplayPortrait));
RaisePropertyChanged(nameof(IsDisplayLandscape));
RaisePropertyChanged(nameof(Yppmm));
RaisePropertyChanged(nameof(PixelHeight));
RaisePropertyChanged(nameof(PixelHeightMicrons));
NotifyAspectChange();
}
}
///
/// Gets an rectangle that starts at 0,0 and goes up to
///
public Rectangle ResolutionRectangle => new(Point.Empty, Resolution);
///
/// Gets the display total number of pixels ( * )
///
public uint DisplayPixelCount => ResolutionX * ResolutionY;
///
/// Gets the size of display in millimeters
///
public SizeF Display
{
get => new(DisplayWidth, DisplayHeight);
set
{
DisplayWidth = RoundDisplaySize(value.Width);
DisplayHeight = RoundDisplaySize(value.Height);
RaisePropertyChanged();
}
}
///
/// Gets or sets the display width in millimeters
///
public virtual float DisplayWidth
{
get => _displayWidth;
set
{
if (!RaiseAndSetIfChanged(ref _displayWidth, RoundDisplaySize(value))) return;
RaisePropertyChanged(nameof(Display));
RaisePropertyChanged(nameof(DisplayDiagonal));
RaisePropertyChanged(nameof(DisplayDiagonalInches));
RaisePropertyChanged(nameof(Xppmm));
RaisePropertyChanged(nameof(PixelWidth));
RaisePropertyChanged(nameof(PixelWidthMicrons));
NotifyAspectChange();
}
}
///
/// Gets or sets the display height in millimeters
///
public virtual float DisplayHeight
{
get => _displayHeight;
set
{
if(!RaiseAndSetIfChanged(ref _displayHeight, RoundDisplaySize(value))) return;
RaisePropertyChanged(nameof(Display));
RaisePropertyChanged(nameof(DisplayDiagonal));
RaisePropertyChanged(nameof(DisplayDiagonalInches));
RaisePropertyChanged(nameof(Yppmm));
RaisePropertyChanged(nameof(PixelHeight));
RaisePropertyChanged(nameof(PixelHeightMicrons));
NotifyAspectChange();
}
}
///
/// Gets the display diagonal in millimeters
///
public float DisplayDiagonal => (float)Math.Round(Math.Sqrt(Math.Pow(DisplayWidth, 2) + Math.Pow(DisplayHeight, 2)), 2);
///
/// Gets the display diagonal in inch's
///
public float DisplayDiagonalInches => (float)Math.Round(Math.Sqrt(Math.Pow(DisplayWidth, 2) + Math.Pow(DisplayHeight, 2)) * UnitExtensions.MillimeterToInch, 2);
///
/// Gets the display ratio
///
public Size DisplayAspectRatio
{
get
{
var gcd = MathExtensions.GCD(ResolutionX, ResolutionY);
return new((int)(ResolutionX / gcd), (int)(ResolutionY / gcd));
}
}
public string DisplayAspectRatioStr
{
get
{
var aspect = DisplayAspectRatio;
return $"{aspect.Width}:{aspect.Height}";
}
}
///
/// Gets or sets if images need to be mirrored on lcd to print on the correct orientation
///
public virtual FlipDirection DisplayMirror { get; set; } = FlipDirection.None;
///
/// Gets if the display is in portrait mode
///
public bool IsDisplayPortrait => ResolutionY > ResolutionX;
///
/// Gets if the display is in landscape mode
///
public bool IsDisplayLandscape => !IsDisplayPortrait;
///
/// Gets or sets the maximum printer build Z volume
///
public virtual float MachineZ
{
get => _machineZ > 0 ? _machineZ : PrintHeight;
set => RaiseAndSetIfChanged(ref _machineZ, value);
}
///
/// Gets or sets the pixels per mm on X direction
///
public virtual float Xppmm => ResolutionX > 0 && DisplayWidth > 0 ? ResolutionX / DisplayWidth : 0;
///
/// Gets or sets the pixels per mm on Y direction
///
public virtual float Yppmm => ResolutionY > 0 && DisplayHeight > 0 ? ResolutionY / DisplayHeight : 0;
///
/// Gets or sets the pixels per mm
///
public SizeF Ppmm => new(Xppmm, Yppmm);
///
/// Gets the maximum (Width or Height) pixels per mm
///
public float PpmmMax => Ppmm.Max();
///
/// Gets the pixel width in millimeters
///
public float PixelWidth => DisplayWidth > 0 && ResolutionX > 0 ? (float) Math.Round(DisplayWidth / ResolutionX, 3) : 0;
///
/// Gets the pixel height in millimeters
///
public float PixelHeight => DisplayHeight > 0 && ResolutionY > 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 && ResolutionX > 0 ? (float)Math.Round(DisplayWidth / ResolutionX * 1000, 3) : 0;
///
/// Gets the pixel height in microns
///
public float PixelHeightMicrons => DisplayHeight > 0 && ResolutionY > 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();
///
/// Gets the file volume (XYZ) in mm^3
///
public float Volume => (float)Math.Round(this.Sum(layer => layer.GetVolume()), 3);
///
/// Checks if this file have AntiAliasing
///
public bool HaveAntiAliasing => AntiAliasing > 1;
///
/// Gets if the AntiAliasing is emulated/fake with fractions of the time or if is real grey levels
///
public virtual bool IsAntiAliasingEmulated => false;
///
/// Gets or sets the AntiAliasing level
///
public virtual byte AntiAliasing
{
get => _antiAliasing;
set => RaiseAndSet(ref _antiAliasing, value);
}
///
/// 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 : LastLayer?.PositionZ ?? 0;
set => RaisePropertyChanged();
}
///
/// Checks if this file format supports per layer settings
///
public bool SupportPerLayerSettings => PrintParameterPerLayerModifiers is not null && PrintParameterPerLayerModifiers.Length > 0;
public bool IsReadOnly => false;
public int Count => _layers?.Length ?? 0;
///
/// Gets or sets the layer count
///
public virtual uint LayerCount
{
get => (uint)Count;
set {
RaisePropertyChanged();
RaisePropertyChanged(nameof(NormalLayerCount));
RaisePropertyChanged(nameof(TransitionLayersRepresentation));
}
}
///
/// Return the number of digits on the layer count number, eg: 123 layers = 3 digits
///
public byte LayerDigits => (byte)LayerCount.DigitCount();
///
/// Gets or sets the total height for the bottom layers in millimeters
///
public float BottomLayersHeight
{
get => LastBottomLayer?.PositionZ ?? 0;
set => BottomLayerCount = (ushort)Math.Ceiling(value / LayerHeight);
}
#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));
RaisePropertyChanged(nameof(TransitionLayersRepresentation));
}
}
///
/// Gets the transition layer type
///
public virtual TransitionLayerTypes TransitionLayerType => TransitionLayerTypes.Software;
///
/// Gets or sets the number of transition layers
///
public virtual ushort TransitionLayerCount
{
get => _transitionLayerCount;
set
{
RaiseAndSet(ref _transitionLayerCount, (ushort)Math.Min(value, MaximumPossibleTransitionLayerCount));
RaisePropertyChanged(nameof(HaveTransitionLayers));
RaisePropertyChanged(nameof(TransitionLayersRepresentation));
}
}
///
/// Gets if have transition layers
///
public bool HaveTransitionLayers => _transitionLayerCount > 0;
///
/// Gets the maximum transition layers this layer collection supports
///
public uint MaximumPossibleTransitionLayerCount
{
get
{
if (BottomLayerCount == 0) return 0;
if (_layers is null) return uint.MaxValue;
int layerCount = (int)LayerCount - BottomLayerCount - 1;
if (layerCount <= 0) return 0;
return (uint)layerCount;
}
}
///
/// Gets the number of normal layer count
///
public uint NormalLayerCount => LayerCount - BottomLayerCount;
///
/// Gets or sets the bottom layer off time in seconds
///
public virtual float BottomLightOffDelay
{
get => _bottomLightOffDelay;
set
{
RaiseAndSet(ref _bottomLightOffDelay, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(LightOffDelayRepresentation));
}
}
///
/// Gets or sets the layer off time in seconds
///
public virtual float LightOffDelay
{
get => _lightOffDelay;
set
{
RaiseAndSet(ref _lightOffDelay, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(LightOffDelayRepresentation));
}
}
///
/// Gets or sets the bottom time in seconds to wait before cure the layer
///
public virtual float BottomWaitTimeBeforeCure
{
get => _bottomWaitTimeBeforeCure;
set
{
RaiseAndSet(ref _bottomWaitTimeBeforeCure, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(WaitTimeRepresentation));
}
}
///
/// Gets or sets the time in seconds to wait after cure the layer
///
public virtual float WaitTimeBeforeCure
{
get => _waitTimeBeforeCure;
set
{
RaiseAndSet(ref _waitTimeBeforeCure, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(WaitTimeRepresentation));
}
}
///
/// Gets or sets the initial exposure time for in seconds
///
public virtual float BottomExposureTime
{
get => _bottomExposureTime;
set
{
RaiseAndSet(ref _bottomExposureTime, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(ExposureRepresentation));
RaisePropertyChanged(nameof(TransitionLayersRepresentation));
}
}
///
/// Gets or sets the normal layer exposure time in seconds
///
public virtual float ExposureTime
{
get => _exposureTime;
set
{
RaiseAndSet(ref _exposureTime, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(ExposureRepresentation));
RaisePropertyChanged(nameof(TransitionLayersRepresentation));
}
}
///
/// Gets or sets the bottom time in seconds to wait after cure the layer
///
public virtual float BottomWaitTimeAfterCure
{
get => _bottomWaitTimeAfterCure;
set
{
RaiseAndSet(ref _bottomWaitTimeAfterCure, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(WaitTimeRepresentation));
}
}
///
/// Gets or sets the time in seconds to wait after cure the layer
///
public virtual float WaitTimeAfterCure
{
get => _waitTimeAfterCure;
set
{
RaiseAndSet(ref _waitTimeAfterCure, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(WaitTimeRepresentation));
}
}
///
/// Gets: Total bottom lift height (lift1 + lift2)
/// Sets: Bottom lift1 with value and lift2 with 0
///
public float BottomLiftHeightTotal
{
get => (float)Math.Round(BottomLiftHeight + BottomLiftHeight2, 2);
set
{
BottomLiftHeight = (float)Math.Round(value, 2);
BottomLiftHeight2 = 0;
}
}
///
/// Gets: Total lift height (lift1 + lift2)
/// Sets: Lift1 with value and lift2 with 0
///
public float LiftHeightTotal
{
get => (float)Math.Round(LiftHeight + LiftHeight2, 2);
set
{
LiftHeight = (float)Math.Round(value, 2);
LiftHeight2 = 0;
}
}
///
/// Gets or sets the bottom lift height in mm
///
public virtual float BottomLiftHeight
{
get => _bottomLiftHeight;
set
{
RaiseAndSet(ref _bottomLiftHeight, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(BottomLiftHeightTotal));
RaisePropertyChanged(nameof(LiftRepresentation));
BottomRetractHeight2 = BottomRetractHeight2; // Sanitize
}
}
///
/// Gets or sets the bottom lift speed in mm/min
///
public virtual float BottomLiftSpeed
{
get => _bottomLiftSpeed;
set
{
RaiseAndSet(ref _bottomLiftSpeed, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(LiftRepresentation));
}
}
///
/// Gets or sets the lift height in mm
///
public virtual float LiftHeight
{
get => _liftHeight;
set
{
RaiseAndSet(ref _liftHeight, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(LiftHeightTotal));
RaisePropertyChanged(nameof(LiftRepresentation));
RetractHeight2 = RetractHeight2; // Sanitize
}
}
///
/// Gets or sets the speed in mm/min
///
public virtual float LiftSpeed
{
get => _liftSpeed;
set
{
RaiseAndSet(ref _liftSpeed, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(LiftRepresentation));
}
}
///
/// Gets or sets the second bottom lift height in mm
///
public virtual float BottomLiftHeight2
{
get => _bottomLiftHeight2;
set
{
RaiseAndSet(ref _bottomLiftHeight2, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(BottomLiftHeightTotal));
RaisePropertyChanged(nameof(LiftRepresentation));
BottomRetractHeight2 = BottomRetractHeight2; // Sanitize
}
}
///
/// Gets or sets the second bottom lift speed in mm/min
///
public virtual float BottomLiftSpeed2
{
get => _bottomLiftSpeed2;
set
{
RaiseAndSet(ref _bottomLiftSpeed2, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(LiftRepresentation));
}
}
///
/// Gets or sets the second lift height in mm (This is the closer to fep retract)
///
public virtual float LiftHeight2
{
get => _liftHeight2;
set
{
RaiseAndSet(ref _liftHeight2, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(LiftHeightTotal));
RaisePropertyChanged(nameof(LiftRepresentation));
RetractHeight2 = RetractHeight2; // Sanitize
}
}
///
/// Gets or sets the second speed in mm/min (This is the closer to fep retract)
///
public virtual float LiftSpeed2
{
get => _liftSpeed2;
set
{
RaiseAndSet(ref _liftSpeed2, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(LiftRepresentation));
}
}
///
/// Gets or sets the bottom time in seconds to wait after lift / before retract
///
public virtual float BottomWaitTimeAfterLift
{
get => _bottomWaitTimeAfterLift;
set
{
RaiseAndSet(ref _bottomWaitTimeAfterLift, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(WaitTimeRepresentation));
}
}
///
/// Gets or sets the time in seconds to wait after lift / before retract
///
public virtual float WaitTimeAfterLift
{
get => _waitTimeAfterLift;
set
{
RaiseAndSet(ref _waitTimeAfterLift, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(WaitTimeRepresentation));
}
}
///
/// Gets: Total bottom retract height (retract1 + retract2) alias of
///
public float BottomRetractHeightTotal => BottomLiftHeightTotal;
///
/// Gets: Total retract height (retract1 + retract2) alias of
///
public float RetractHeightTotal => LiftHeightTotal;
///
/// Gets the bottom retract height in mm
///
public float BottomRetractHeight => (float)Math.Round(BottomLiftHeightTotal - BottomRetractHeight2, 2);
///
/// Gets the speed in mm/min for the bottom retracts
///
public virtual float BottomRetractSpeed
{
get => _bottomRetractSpeed;
set
{
RaiseAndSet(ref _bottomRetractSpeed, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(RetractRepresentation));
}
}
///
/// Gets the retract height in mm
///
public float RetractHeight => (float)Math.Round(LiftHeightTotal - RetractHeight2, 2);
///
/// Gets the speed in mm/min for the retracts
///
public virtual float RetractSpeed
{
get => _retractSpeed;
set
{
RaiseAndSet(ref _retractSpeed, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(RetractRepresentation));
}
}
///
/// Gets or sets the second bottom retract height in mm
///
public virtual float BottomRetractHeight2
{
get => _bottomRetractHeight2;
set
{
value = Math.Clamp((float)Math.Round(value, 2), 0, BottomRetractHeightTotal);
RaiseAndSet(ref _bottomRetractHeight2, value);
RaisePropertyChanged(nameof(BottomRetractHeight));
RaisePropertyChanged(nameof(BottomRetractHeightTotal));
RaisePropertyChanged(nameof(RetractRepresentation));
}
}
///
/// Gets the speed in mm/min for the retracts
///
public virtual float BottomRetractSpeed2
{
get => _bottomRetractSpeed2;
set
{
RaiseAndSet(ref _bottomRetractSpeed2, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(RetractRepresentation));
}
}
///
/// Gets or sets the second retract height in mm
///
public virtual float RetractHeight2
{
get => _retractHeight2;
set
{
value = Math.Clamp((float)Math.Round(value, 2), 0, RetractHeightTotal);
RaiseAndSet(ref _retractHeight2, value);
RaisePropertyChanged(nameof(RetractHeight));
RaisePropertyChanged(nameof(RetractHeightTotal));
RaisePropertyChanged(nameof(RetractRepresentation));
}
}
///
/// Gets the speed in mm/min for the retracts
///
public virtual float RetractSpeed2
{
get => _retractSpeed2;
set
{
RaiseAndSet(ref _retractSpeed2, (float)Math.Round(value, 2));
RaisePropertyChanged(nameof(RetractRepresentation));
}
}
///
/// 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);
}
///
/// Gets the minimum used speed for bottom layers in mm/min
///
public float MinimumBottomSpeed
{
get
{
float speed = float.MaxValue;
if (BottomLiftSpeed > 0) speed = Math.Min(speed, BottomLiftSpeed);
if (CanUseBottomLiftSpeed2 && BottomLiftSpeed2 > 0) speed = Math.Min(speed, BottomLiftSpeed2);
if (CanUseBottomRetractSpeed && BottomRetractSpeed > 0) speed = Math.Min(speed, BottomRetractSpeed);
if (CanUseBottomRetractSpeed2 && BottomRetractSpeed2 > 0) speed = Math.Min(speed, BottomRetractSpeed2);
if (Math.Abs(speed - float.MaxValue) < 0.01) return 0;
return speed;
}
}
///
/// Gets the minimum used speed for normal bottom layers in mm/min
///
public float MinimumNormalSpeed
{
get
{
float speed = float.MaxValue;
if (LiftSpeed > 0) speed = Math.Min(speed, LiftSpeed);
if (CanUseLiftSpeed2 && LiftSpeed2 > 0) speed = Math.Min(speed, LiftSpeed2);
if (CanUseRetractSpeed && RetractSpeed > 0) speed = Math.Min(speed, RetractSpeed);
if (CanUseRetractSpeed2 && RetractSpeed2 > 0) speed = Math.Min(speed, RetractSpeed2);
if (Math.Abs(speed - float.MaxValue) < 0.01) return 0;
return speed;
}
}
///
/// Gets the minimum used speed in mm/min
///
public float MinimumSpeed
{
get
{
var bottomSpeed = MinimumBottomSpeed;
var normalSpeed = MinimumNormalSpeed;
if (bottomSpeed <= 0) return normalSpeed;
if (normalSpeed <= 0) return bottomSpeed;
return Math.Min(bottomSpeed, normalSpeed);
}
}
///
/// Gets the maximum used speed for bottom layers in mm/min
///
public float MaximumBottomSpeed
{
get
{
float speed = BottomLiftSpeed;
if (CanUseBottomLiftSpeed2) speed = Math.Max(speed, BottomLiftSpeed2);
if (CanUseBottomRetractSpeed) speed = Math.Max(speed, BottomRetractSpeed);
if (CanUseBottomRetractSpeed2) speed = Math.Max(speed, BottomRetractSpeed2);
return speed;
}
}
///
/// Gets the maximum used speed for normal bottom layers in mm/min
///
public float MaximumNormalSpeed
{
get
{
float speed = LiftSpeed;
if (CanUseLiftSpeed2) speed = Math.Max(speed, LiftSpeed2);
if (CanUseRetractSpeed) speed = Math.Max(speed, RetractSpeed);
if (CanUseRetractSpeed2) speed = Math.Max(speed, RetractSpeed2);
return speed;
}
}
///
/// Gets the maximum used speed in mm/min
///
public float MaximumSpeed => Math.Max(MaximumBottomSpeed, MaximumNormalSpeed);
public bool CanUseBottomLayerCount => HavePrintParameterModifier(PrintParameterModifier.BottomLayerCount);
public bool CanUseTransitionLayerCount => HavePrintParameterModifier(PrintParameterModifier.TransitionLayerCount);
public bool CanUseBottomLightOffDelay => HavePrintParameterModifier(PrintParameterModifier.BottomLightOffDelay);
public bool CanUseLightOffDelay => HavePrintParameterModifier(PrintParameterModifier.LightOffDelay);
public bool CanUseAnyLightOffDelay => CanUseBottomLightOffDelay || CanUseLightOffDelay;
public bool CanUseBottomWaitTimeBeforeCure => HavePrintParameterModifier(PrintParameterModifier.BottomWaitTimeBeforeCure);
public bool CanUseWaitTimeBeforeCure => HavePrintParameterModifier(PrintParameterModifier.WaitTimeBeforeCure);
public bool CanUseAnyWaitTimeBeforeCure => CanUseBottomWaitTimeBeforeCure || CanUseWaitTimeBeforeCure;
public bool CanUseBottomExposureTime => HavePrintParameterModifier(PrintParameterModifier.BottomExposureTime);
public bool CanUseExposureTime => HavePrintParameterModifier(PrintParameterModifier.ExposureTime);
public bool CanUseAnyExposureTime => CanUseBottomExposureTime || CanUseExposureTime;
public bool CanUseBottomWaitTimeAfterCure => HavePrintParameterModifier(PrintParameterModifier.BottomWaitTimeAfterCure);
public bool CanUseWaitTimeAfterCure => HavePrintParameterModifier(PrintParameterModifier.WaitTimeAfterCure);
public bool CanUseAnyWaitTimeAfterCure => CanUseBottomWaitTimeAfterCure || CanUseWaitTimeAfterCure;
public bool CanUseBottomLiftHeight => HavePrintParameterModifier(PrintParameterModifier.BottomLiftHeight);
public bool CanUseLiftHeight => HavePrintParameterModifier(PrintParameterModifier.LiftHeight);
public bool CanUseAnyLiftHeight => CanUseBottomLiftHeight || CanUseLiftHeight;
public bool CanUseBottomLiftSpeed => HavePrintParameterModifier(PrintParameterModifier.BottomLiftSpeed);
public bool CanUseLiftSpeed => HavePrintParameterModifier(PrintParameterModifier.LiftSpeed);
public bool CanUseAnyLiftSpeed => CanUseBottomLiftSpeed || CanUseLiftSpeed;
public bool CanUseBottomLiftHeight2 => HavePrintParameterModifier(PrintParameterModifier.BottomLiftHeight2);
public bool CanUseLiftHeight2 => HavePrintParameterModifier(PrintParameterModifier.LiftHeight2);
public bool CanUseAnyLiftHeight2 => CanUseBottomLiftHeight2 || CanUseLiftHeight2;
public bool CanUseBottomLiftSpeed2 => HavePrintParameterModifier(PrintParameterModifier.BottomLiftSpeed2);
public bool CanUseLiftSpeed2 => HavePrintParameterModifier(PrintParameterModifier.LiftSpeed2);
public bool CanUseAnyLiftSpeed2 => CanUseBottomLiftSpeed2 || CanUseLiftSpeed2;
public bool CanUseBottomWaitTimeAfterLift => HavePrintParameterModifier(PrintParameterModifier.BottomWaitTimeAfterLift);
public bool CanUseWaitTimeAfterLift => HavePrintParameterModifier(PrintParameterModifier.WaitTimeAfterLift);
public bool CanUseAnyWaitTimeAfterLift => CanUseBottomWaitTimeAfterLift || CanUseWaitTimeAfterLift;
public bool CanUseBottomRetractSpeed => HavePrintParameterModifier(PrintParameterModifier.BottomRetractSpeed);
public bool CanUseRetractSpeed => HavePrintParameterModifier(PrintParameterModifier.RetractSpeed);
public bool CanUseAnyRetractSpeed => CanUseBottomRetractSpeed || CanUseRetractSpeed;
public bool CanUseBottomRetractHeight2 => HavePrintParameterModifier(PrintParameterModifier.BottomRetractHeight2);
public bool CanUseRetractHeight2 => HavePrintParameterModifier(PrintParameterModifier.RetractHeight2);
public bool CanUseAnyRetractHeight2 => CanUseBottomRetractHeight2 || CanUseRetractHeight2;
public bool CanUseBottomRetractSpeed2 => HavePrintParameterModifier(PrintParameterModifier.BottomRetractSpeed2);
public bool CanUseRetractSpeed2 => HavePrintParameterModifier(PrintParameterModifier.RetractSpeed2);
public bool CanUseAnyRetractSpeed2 => CanUseBottomRetractSpeed2 || CanUseRetractSpeed2;
public bool CanUseAnyWaitTime => CanUseBottomWaitTimeBeforeCure || CanUseBottomWaitTimeAfterCure || CanUseBottomWaitTimeAfterLift ||
CanUseWaitTimeBeforeCure || CanUseWaitTimeAfterCure || CanUseWaitTimeAfterLift;
public bool CanUseBottomLightPWM => HavePrintParameterModifier(PrintParameterModifier.BottomLightPWM);
public bool CanUseLightPWM => HavePrintParameterModifier(PrintParameterModifier.LightPWM);
public bool CanUseAnyLightPWM => CanUseBottomLightPWM || CanUseLightPWM;
public bool CanUseLayerPositionZ => HaveLayerParameterModifier(PrintParameterModifier.PositionZ);
public bool CanUseLayerWaitTimeBeforeCure => HaveLayerParameterModifier(PrintParameterModifier.WaitTimeBeforeCure);
public bool CanUseLayerExposureTime => HaveLayerParameterModifier(PrintParameterModifier.ExposureTime);
public bool CanUseLayerWaitTimeAfterCure => HaveLayerParameterModifier(PrintParameterModifier.WaitTimeAfterCure);
public bool CanUseLayerLiftHeight => HaveLayerParameterModifier(PrintParameterModifier.LiftHeight);
public bool CanUseLayerLiftSpeed => HaveLayerParameterModifier(PrintParameterModifier.LiftSpeed);
public bool CanUseLayerLiftHeight2 => HaveLayerParameterModifier(PrintParameterModifier.LiftHeight2);
public bool CanUseLayerLiftSpeed2 => HaveLayerParameterModifier(PrintParameterModifier.LiftSpeed2);
public bool CanUseLayerWaitTimeAfterLift => HaveLayerParameterModifier(PrintParameterModifier.WaitTimeAfterLift);
public bool CanUseLayerRetractSpeed => HaveLayerParameterModifier(PrintParameterModifier.RetractSpeed);
public bool CanUseLayerRetractHeight2 => HaveLayerParameterModifier(PrintParameterModifier.RetractHeight2);
public bool CanUseLayerRetractSpeed2 => HaveLayerParameterModifier(PrintParameterModifier.RetractSpeed2);
public bool CanUseLayerLightOffDelay => HaveLayerParameterModifier(PrintParameterModifier.LightOffDelay);
public bool CanUseLayerAnyWaitTimeBeforeCure => CanUseLayerWaitTimeBeforeCure || CanUseLayerLightOffDelay;
public bool CanUseLayerLightPWM => HaveLayerParameterModifier(PrintParameterModifier.LightPWM);
public string TransitionLayersRepresentation
{
get
{
var str = TransitionLayerCount.ToString(CultureInfo.InvariantCulture);
if (!CanUseTransitionLayerCount)
{
return str;
}
if (TransitionLayerCount > 0)
{
var decrement = ParseTransitionStepTimeFromLayers();
if (decrement != 0)
{
str += $"/{-decrement}s";
}
}
return str;
}
}
public string ExposureRepresentation
{
get
{
var str = string.Empty;
if (CanUseBottomExposureTime)
{
str += BottomExposureTime.ToString(CultureInfo.InvariantCulture);
}
if (CanUseExposureTime)
{
if (!string.IsNullOrEmpty(str)) str += '/';
str += ExposureTime.ToString(CultureInfo.InvariantCulture);
}
if (!string.IsNullOrEmpty(str)) str += 's';
return str;
}
}
public string LiftRepresentation
{
get
{
var str = string.Empty;
var haveBottomLiftHeight = CanUseBottomLiftHeight;
var haveLiftHeight = CanUseLiftHeight;
var haveBottomLiftHeight2 = CanUseBottomLiftHeight2;
var haveLiftHeight2 = CanUseLiftHeight2;
var haveBottomLiftSpeed2 = CanUseBottomLiftSpeed2;
var haveLiftSpeed2 = CanUseLiftSpeed2;
if (!haveBottomLiftHeight && !haveLiftHeight && !haveBottomLiftHeight2 && !haveLiftHeight2) return str;
// Sequence 1
if (haveBottomLiftHeight)
{
str += BottomLiftHeight.ToString(CultureInfo.InvariantCulture);
if (haveBottomLiftHeight2 && BottomLiftHeight2 > 0)
{
str += $"+{BottomLiftHeight2.ToString(CultureInfo.InvariantCulture)}";
}
}
if (haveLiftHeight)
{
if (!string.IsNullOrEmpty(str)) str += '/';
str += LiftHeight.ToString(CultureInfo.InvariantCulture);
if (haveLiftHeight2 && LiftHeight2 > 0)
{
str += $"+{LiftHeight2.ToString(CultureInfo.InvariantCulture)}";
}
}
if (string.IsNullOrEmpty(str)) return str;
str += "mm @ ";
var haveBottomLiftSpeed = CanUseBottomLiftSpeed;
var haveLiftSpeed = CanUseLiftSpeed;
if (haveBottomLiftSpeed)
{
str += BottomLiftSpeed.ToString(CultureInfo.InvariantCulture);
if (haveBottomLiftSpeed2 && haveBottomLiftHeight2 && BottomLiftHeight2 > 0)
{
str += $"+{BottomLiftSpeed2.ToString(CultureInfo.InvariantCulture)}";
}
}
if (haveLiftSpeed)
{
if (haveBottomLiftSpeed) str += '/';
str += LiftSpeed.ToString(CultureInfo.InvariantCulture);
if (haveLiftSpeed2 && haveLiftHeight2 && LiftHeight2 > 0)
{
str += $"+{LiftSpeed2.ToString(CultureInfo.InvariantCulture)}";
}
}
str += "mm/min";
/*// Sequence 2
if (haveBottomLiftHeight2)
{
str += $"\n2th: {BottomLiftHeight2.ToString(CultureInfo.InvariantCulture)}";
}
if (haveLiftHeight2)
{
str += str.EndsWith("mm/min") ? "\n2th: " : '/';
str += LiftHeight2.ToString(CultureInfo.InvariantCulture);
}
if (str.EndsWith("mm/min")) return str;
str += "mm @ ";
var haveBottomLiftSpeed2 = CanUseBottomLiftSpeed2;
var haveLiftSpeed2 = CanUseLiftSpeed2;
if (haveBottomLiftSpeed2)
{
str += BottomLiftSpeed2.ToString(CultureInfo.InvariantCulture);
}
if (haveLiftSpeed2)
{
if (haveBottomLiftSpeed2) str += '/';
str += LiftSpeed2.ToString(CultureInfo.InvariantCulture);
}
str += "mm/min";*/
return str;
}
}
public string RetractRepresentation
{
get
{
var str = string.Empty;
var haveBottomRetractHeight = CanUseLiftHeight;
var haveRetractHeight = CanUseBottomLiftHeight;
var haveBottomRetractSpeed = CanUseBottomRetractSpeed;
var haveRetractSpeed = CanUseRetractSpeed;
var haveBottomRetractHeight2 = CanUseBottomRetractHeight2;
var haveRetractHeight2 = CanUseRetractHeight2;
var haveBottomRetractSpeed2 = CanUseBottomRetractSpeed2;
var haveRetractSpeed2 = CanUseRetractSpeed2;
if (!haveBottomRetractSpeed && !haveRetractSpeed && !haveBottomRetractHeight2 && !haveRetractHeight2) return str;
// Sequence 1
if (haveBottomRetractHeight)
{
str += BottomRetractHeight.ToString(CultureInfo.InvariantCulture);
if (haveBottomRetractHeight2 && BottomRetractHeight2 > 0)
{
str += $"+{BottomRetractHeight2.ToString(CultureInfo.InvariantCulture)}";
}
}
if (haveRetractHeight)
{
if (!string.IsNullOrEmpty(str)) str += '/';
str += RetractHeight.ToString(CultureInfo.InvariantCulture);
if (haveRetractHeight2 && RetractHeight2 > 0)
{
str += $"+{RetractHeight2.ToString(CultureInfo.InvariantCulture)}";
}
}
if (string.IsNullOrEmpty(str)) return str;
str += "mm @ ";
if (haveBottomRetractSpeed)
{
str += BottomRetractSpeed.ToString(CultureInfo.InvariantCulture);
if (haveBottomRetractSpeed2 && haveBottomRetractHeight2 && BottomRetractHeight2 > 0)
{
str += $"+{BottomRetractSpeed2.ToString(CultureInfo.InvariantCulture)}";
}
}
if (haveRetractSpeed)
{
if (haveBottomRetractSpeed) str += '/';
str += RetractSpeed.ToString(CultureInfo.InvariantCulture);
if (haveRetractSpeed2 && haveRetractHeight2 && RetractHeight2 > 0)
{
str += $"+{RetractSpeed2.ToString(CultureInfo.InvariantCulture)}";
}
}
str += "mm/min";
// Sequence 2
/*if (haveBottomRetractHeight2)
{
str += $"\n2th: {BottomRetractHeight2.ToString(CultureInfo.InvariantCulture)}";
}
if (haveRetractHeight2)
{
str += str.EndsWith("mm/min") ? "\n2th: " : '/';
str += RetractHeight2.ToString(CultureInfo.InvariantCulture);
}
if (str.EndsWith("mm/min")) return str;
str += "mm @ ";
if (haveBottomRetractSpeed2)
{
str += BottomRetractSpeed2.ToString(CultureInfo.InvariantCulture);
}
if (haveRetractSpeed2)
{
if (haveBottomRetractSpeed2) str += '/';
str += RetractSpeed2.ToString(CultureInfo.InvariantCulture);
}
str += "mm/min";*/
return str;
}
}
public string LightOffDelayRepresentation
{
get
{
var str = string.Empty;
if (CanUseBottomLightOffDelay)
{
str += BottomLightOffDelay.ToString(CultureInfo.InvariantCulture);
}
if (CanUseLightOffDelay)
{
if (!string.IsNullOrEmpty(str)) str += '/';
str += LightOffDelay.ToString(CultureInfo.InvariantCulture);
}
if (!string.IsNullOrEmpty(str)) str += 's';
return str;
}
}
public string WaitTimeRepresentation
{
get
{
var str = string.Empty;
if (CanUseBottomWaitTimeBeforeCure || CanUseBottomWaitTimeAfterCure || CanUseBottomWaitTimeAfterLift)
{
str += $"{BottomWaitTimeBeforeCure}/{BottomWaitTimeAfterCure}/{BottomWaitTimeAfterLift}s";
}
if (!string.IsNullOrEmpty(str)) str += "|";
if (CanUseWaitTimeBeforeCure || CanUseWaitTimeAfterCure || CanUseWaitTimeAfterLift)
{
str += $"{WaitTimeBeforeCure}/{WaitTimeAfterCure}/{WaitTimeAfterLift}s";
}
return str;
}
}
public IEnumerable> BatchLayersIndexes(int batchSize = 0)
{
if (batchSize <= 0) batchSize = Environment.ProcessorCount * 10;
return Enumerable.Range(0, (int) LayerCount).Chunk(batchSize);
}
public IEnumerable> BatchLayers(int batchSize = 0)
{
if (batchSize <= 0) batchSize = Environment.ProcessorCount * 10;
return this.Chunk(batchSize);
}
#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));
RaisePropertyChanged(nameof(DisplayTotalOnTime));
RaisePropertyChanged(nameof(DisplayTotalOnTimeString));
RaisePropertyChanged(nameof(DisplayTotalOffTime));
RaisePropertyChanged(nameof(DisplayTotalOffTimeString));
}
}
///
/// Gets the calculated estimate print time in seconds
///
public float PrintTimeComputed
{
get
{
if (LayerCount == 0) return 0;
float time = ExtraPrintTime;
bool computeGeneral = _layers is null;
if (!computeGeneral)
{
foreach (var layer in this)
{
if (layer is null)
{
computeGeneral = true;
break;
}
var motorTime = layer.CalculateMotorMovementTime();
time += layer.WaitTimeBeforeCure + layer.ExposureTime + layer.WaitTimeAfterCure + layer.WaitTimeAfterLift;
if (SupportsGCode)
{
time += motorTime;
if (layer.WaitTimeBeforeCure <= 0)
{
time += layer.LightOffDelay;
}
}
else
{
time += motorTime > layer.LightOffDelay ? motorTime : layer.LightOffDelay;
}
/*if (lightOffDelay >= layer.LightOffDelay)
time += lightOffDelay;
else
time += layer.LightOffDelay;*/
}
}
if (computeGeneral)
{
var bottomMotorTime = CalculateMotorMovementTime(true);
var motorTime = CalculateMotorMovementTime(false);
time = ExtraPrintTime +
BottomLightOffDelay * BottomLayerCount +
LightOffDelay * NormalLayerCount +
BottomWaitTimeBeforeCure * BottomLayerCount +
WaitTimeBeforeCure * NormalLayerCount +
BottomExposureTime * BottomLayerCount +
ExposureTime * NormalLayerCount +
BottomWaitTimeAfterCure * BottomLayerCount +
WaitTimeAfterCure * NormalLayerCount +
BottomWaitTimeAfterLift * BottomLayerCount +
WaitTimeAfterLift * NormalLayerCount;
if (SupportsGCode)
{
time += bottomMotorTime * BottomLayerCount + motorTime * NormalLayerCount;
if (BottomWaitTimeBeforeCure <= 0)
{
time += BottomLightOffDelay * BottomLayerCount;
}
if (WaitTimeBeforeCure <= 0)
{
time += LightOffDelay * NormalLayerCount;
}
}
else
{
time += motorTime > BottomLightOffDelay ? bottomMotorTime * BottomLayerCount : BottomLightOffDelay * BottomLayerCount;
time += motorTime > LightOffDelay ? motorTime * NormalLayerCount : LightOffDelay * 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
{
get
{
var printTime = PrintTime;
return TimeSpan.FromSeconds(float.IsPositiveInfinity(printTime) || float.IsNaN(printTime) ? 0 : printTime).ToString("hh\\hmm\\m");
}
}
///
/// Gets the total time in seconds the display will remain on exposing the layers during the print
///
public float DisplayTotalOnTime => (float)Math.Round(this.Where(layer => layer is not null).Sum(layer => layer.ExposureTime), 2);
///
/// Gets the total time formatted in hours, minutes and seconds the display will remain on exposing the layers during the print
///
public string DisplayTotalOnTimeString => TimeSpan.FromSeconds(DisplayTotalOnTime).ToString("hh\\hmm\\mss\\s");
///
/// Gets the total time in seconds the display will remain off during the print.
/// This is the difference between and
///
public float DisplayTotalOffTime
{
get
{
var printTime = PrintTime;
if (float.IsPositiveInfinity(printTime) || float.IsNaN(printTime)) return float.NaN;
var value = (float) Math.Round(PrintTime - DisplayTotalOnTime, 2);
return value <= 0 ? float.NaN : value;
}
}
///
/// Gets the total time formatted in hours, minutes and seconds the display will remain off during the print.
/// This is the difference between and
///
public string DisplayTotalOffTimeString
{
get
{
var time = DisplayTotalOffTime;
return TimeSpan.FromSeconds(float.IsPositiveInfinity(time) || float.IsNaN(time) ? 0 : time).ToString("hh\\hmm\\mss\\s");
}
}
///
/// Gets the starting material milliliters when the file was loaded
///
public float StartingMaterialMilliliters { get; private set; }
///
/// Gets the estimate used material in ml
///
public virtual float MaterialMilliliters {
get => _materialMilliliters;
set
{
if (value <= 0) // Recalculate
{
value = (float)Math.Round(this.Where(layer => layer is not null).Sum(layer => layer.MaterialMilliliters), 3);
}
else // Set from value
{
value = (float)Math.Round(value, 3);
}
if(!RaiseAndSetIfChanged(ref _materialMilliliters, value)) return;
if (StartingMaterialMilliliters > 0 && StartingMaterialCost > 0)
{
MaterialCost = GetMaterialCostPer(_materialMilliliters);
}
//RaisePropertyChanged(nameof(MaterialCost));
}
}
//public float MaterialMillilitersComputed =>
///
/// Gets the estimate material in grams
///
public virtual float MaterialGrams
{
get => _materialGrams;
set => RaiseAndSetIfChanged(ref _materialGrams, (float)Math.Round(value, 3));
}
///
/// Gets the starting material cost when the file was loaded
///
public float StartingMaterialCost { get; private set; }
///
/// Gets the estimate material cost
///
public virtual float MaterialCost
{
get => _materialCost;
set => RaiseAndSetIfChanged(ref _materialCost, (float)Math.Round(value, 3));
}
///
/// Gets the material cost per one milliliter
///
public float MaterialMilliliterCost => StartingMaterialMilliliters > 0 ? StartingMaterialCost / StartingMaterialMilliliters : 0;
public float GetMaterialCostPer(float milliliters, byte roundDigits = 3) => (float)Math.Round(MaterialMilliliterCost * milliliters, roundDigits);
///
/// 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; }
///
/// Gets the GCode, returns null if not supported
///
public string? GCodeStr
{
get => GCode?.ToString();
set
{
if (GCode is null) return;
GCode.Clear();
if (!string.IsNullOrWhiteSpace(value))
{
GCode.Append(value);
}
RaisePropertyChanged();
}
}
///
/// Gets if this file format supports gcode
///
public virtual bool SupportsGCode => GCode is not null;
///
/// Gets if this file have available gcode to read
///
public bool HaveGCode => SupportsGCode && !GCode!.IsEmpty;
///
/// Disable or enable the gcode auto rebuild when needed, set this to false to manually write your own gcode
///
public bool SuppressRebuildGCode
{
get => _suppressRebuildGCode;
set => RaiseAndSetIfChanged(ref _suppressRebuildGCode, value);
}
///
/// Get all configuration objects with properties and values
///
public virtual object[] Configs => Array.Empty