using BinarySerialization;
using Emgu.CV;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UVtools.Core.Exceptions;
using UVtools.Core.Extensions;
using UVtools.Core.Layers;
using UVtools.Core.Operations;
namespace UVtools.Core.FileFormats;
public sealed class GooFile : FileFormat
{
#region Constants
private const string FileVersion = "V3.0";
private static readonly byte[] FileMagic = {
0x07, 0x00, 0x00, 0x00,
0x44, 0x4C, 0x50, 0x00
};
private static byte[] Delimiter => new byte[]{ 0x0D, 0x0A };
private static byte LayerMagic => 0x55;
#endregion
#region Enums
public enum DelayModes : byte
{
///
/// Time with motor movement
///
LightOff = 0,
///
/// Absolute time to wait
///
WaitTime = 1
}
#endregion
#region Sub Classes
public class FileHeader
{
[FieldEndianness(Endianness.Big)] [FieldOrder(0)] [FieldLength(4)] public string Version { get; set; } = FileVersion;
[FieldOrder(1)] [FieldCount(8)] public byte[] Magic { get; set; } = FileMagic;
[FieldOrder(2)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string SoftwareName { get; set; } = About.Software;
[FieldOrder(3)] [FieldLength(24)] [SerializeAs(SerializedType.TerminatedString)] public string SoftwareVersion { get; set; } = About.VersionStr;
[FieldOrder(4)] [FieldLength(24)] [SerializeAs(SerializedType.TerminatedString)] public string FileCreateTime { get; set; } = DateTime.UtcNow.ToString("yyyy-mm-dd hh:mm:ss");
[FieldOrder(5)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string MachineName { get; set; } = DefaultMachineName;
[FieldOrder(6)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string MachineType { get; set; } = "DLP";
[FieldOrder(7)] [FieldLength(32)] [SerializeAs(SerializedType.TerminatedString)] public string ProfileName { get; set; } = About.Software;
[FieldEndianness(Endianness.Big)] [FieldOrder(8)] public ushort AntiAliasingLevel { get; set; } = 8;
[FieldEndianness(Endianness.Big)] [FieldOrder(9)] public ushort GreyLevel { get; set; } = 1;
[FieldEndianness(Endianness.Big)] [FieldOrder(10)] public ushort BlurLevel { get; set; } = 0;
[FieldOrder(11)] [FieldCount(116 * 116 * 2)] public byte[] SmallPreview565 { get; set; } = Array.Empty();
[FieldOrder(12)] [FieldCount(2)] public byte[] SmallPreviewDelimiter { get; set; } = Delimiter;
[FieldOrder(13)] [FieldCount(290 * 290 * 2)] public byte[] BigPreview565 { get; set; } = Array.Empty();
[FieldOrder(14)] [FieldCount(2)] public byte[] BigPreviewDelimiter { get; set; } = Delimiter;
[FieldEndianness(Endianness.Big)] [FieldOrder(15)] public uint LayerCount { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(16)] public ushort ResolutionX { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(17)] public ushort ResolutionY { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(18)] public bool MirrorX { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(19)] public bool MirrorY { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(20)] public float DisplayWidth { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(21)] public float DisplayHeight { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(22)] public float MachineZ { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(23)] public float LayerHeight { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(24)] public float ExposureTime { get; set; }
///
/// 0: Light off delay mode | 1:Wait time mode
///
[FieldEndianness(Endianness.Big)] [FieldOrder(25)] public DelayModes DelayMode { get; set; } = DelayModes.WaitTime;
[FieldEndianness(Endianness.Big)] [FieldOrder(26)] public float LightOffDelay { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(27)] public float BottomWaitTimeAfterCure { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(28)] public float BottomWaitTimeAfterLift { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(29)] public float BottomWaitTimeBeforeCure { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(30)] public float WaitTimeAfterCure { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(31)] public float WaitTimeAfterLift { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(32)] public float WaitTimeBeforeCure { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(33)] public float BottomExposureTime { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(34)] public uint BottomLayerCount { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(35)] public float BottomLiftHeight { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(36)] public float BottomLiftSpeed { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(37)] public float LiftHeight { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(38)] public float LiftSpeed { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(39)] public float BottomRetractHeight { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(40)] public float BottomRetractSpeed { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(41)] public float RetractHeight { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(42)] public float RetractSpeed { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(43)] public float BottomLiftHeight2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(44)] public float BottomLiftSpeed2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(45)] public float LiftHeight2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(46)] public float LiftSpeed2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(47)] public float BottomRetractHeight2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(48)] public float BottomRetractSpeed2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(49)] public float RetractHeight2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(50)] public float RetractSpeed2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(51)] public ushort BottomLightPWM { get; set; } = DefaultBottomLightPWM;
[FieldEndianness(Endianness.Big)] [FieldOrder(52)] public ushort LightPWM { get; set; } = DefaultLightPWM;
///
/// 0: Normal mode
/// 1: Advance mode, printing use the value of "Layer Definition Content"
///
[FieldEndianness(Endianness.Big)] [FieldOrder(53)] public bool PerLayerSettings { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(54)] public uint PrintTime { get; set; }
///
/// // The volume of all parts. unit: mm3
///
[FieldEndianness(Endianness.Big)] [FieldOrder(55)] public float Volume { get; set; }
///
/// The weight of all parts. unit: g
///
[FieldEndianness(Endianness.Big)] [FieldOrder(56)] public float MaterialGrams { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(57)] public float MaterialCost { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(58)] [FieldLength(8)] [SerializeAs(SerializedType.TerminatedString)] public string PriceCurrencySymbol { get; set; } = "$";
[FieldEndianness(Endianness.Big)] [FieldOrder(59)] public uint LayerDefAddress { get; set; } // 195477
///
/// 0:The range of pixel's gray value is from 0x0 ~ 0xf
/// 1:The range of pixel's gray value is from 0x0 ~ 0xff
///
[FieldEndianness(Endianness.Big)] [FieldOrder(60)] public byte GrayScaleLevel { get; set; } = 1;
[FieldEndianness(Endianness.Big)] [FieldOrder(61)] public ushort TransitionLayerCount { get; set; }
public override string ToString()
{
return $"{nameof(Version)}: {Version}, {nameof(Magic)}: {Magic}, {nameof(SoftwareName)}: {SoftwareName}, {nameof(SoftwareVersion)}: {SoftwareVersion}, {nameof(FileCreateTime)}: {FileCreateTime}, {nameof(MachineName)}: {MachineName}, {nameof(MachineType)}: {MachineType}, {nameof(ProfileName)}: {ProfileName}, {nameof(AntiAliasingLevel)}: {AntiAliasingLevel}, {nameof(GreyLevel)}: {GreyLevel}, {nameof(BlurLevel)}: {BlurLevel}, {nameof(SmallPreview565)}: {SmallPreview565}, {nameof(SmallPreviewDelimiter)}: {SmallPreviewDelimiter}, {nameof(BigPreview565)}: {BigPreview565}, {nameof(BigPreviewDelimiter)}: {BigPreviewDelimiter}, {nameof(LayerCount)}: {LayerCount}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(MirrorX)}: {MirrorX}, {nameof(MirrorY)}: {MirrorY}, {nameof(DisplayWidth)}: {DisplayWidth}, {nameof(DisplayHeight)}: {DisplayHeight}, {nameof(MachineZ)}: {MachineZ}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(DelayMode)}: {DelayMode}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(BottomWaitTimeAfterCure)}: {BottomWaitTimeAfterCure}, {nameof(BottomWaitTimeAfterLift)}: {BottomWaitTimeAfterLift}, {nameof(BottomWaitTimeBeforeCure)}: {BottomWaitTimeBeforeCure}, {nameof(WaitTimeAfterCure)}: {WaitTimeAfterCure}, {nameof(WaitTimeAfterLift)}: {WaitTimeAfterLift}, {nameof(WaitTimeBeforeCure)}: {WaitTimeBeforeCure}, {nameof(BottomExposureTime)}: {BottomExposureTime}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(BottomRetractHeight)}: {BottomRetractHeight}, {nameof(BottomRetractSpeed)}: {BottomRetractSpeed}, {nameof(RetractHeight)}: {RetractHeight}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(BottomLiftHeight2)}: {BottomLiftHeight2}, {nameof(BottomLiftSpeed2)}: {BottomLiftSpeed2}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(BottomRetractHeight2)}: {BottomRetractHeight2}, {nameof(BottomRetractSpeed2)}: {BottomRetractSpeed2}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(LightPWM)}: {LightPWM}, {nameof(PerLayerSettings)}: {PerLayerSettings}, {nameof(PrintTime)}: {PrintTime}, {nameof(Volume)}: {Volume}, {nameof(MaterialGrams)}: {MaterialGrams}, {nameof(MaterialCost)}: {MaterialCost}, {nameof(PriceCurrencySymbol)}: {PriceCurrencySymbol}, {nameof(LayerDefAddress)}: {LayerDefAddress}, {nameof(GrayScaleLevel)}: {GrayScaleLevel}, {nameof(TransitionLayerCount)}: {TransitionLayerCount}";
}
}
public class LayerDef
{
///
/// 0: reserve
/// 1: current layer pause printing
///
[FieldEndianness(Endianness.Big)] [FieldOrder(0)] public ushort Pause { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(1)] public float PausePositionZ { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(2)] public float PositionZ { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(3)] public float ExposureTime { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(4)] public float LightOffDelay { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(5)] public float WaitTimeAfterCure { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(6)] public float WaitTimeAfterLift { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(7)] public float WaitTimeBeforeCure { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(8)] public float LiftHeight { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(9)] public float LiftSpeed { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(10)] public float LiftHeight2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(11)] public float LiftSpeed2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(12)] public float RetractHeight { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(13)] public float RetractSpeed { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(14)] public float RetractHeight2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(15)] public float RetractSpeed2 { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(16)] public ushort LightPWM { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(17)] [FieldCount(2)] public byte[] DelimiterData { get; set; } = Delimiter;
[FieldEndianness(Endianness.Big)] [FieldOrder(18)] public uint DataLength { get; set; }
[Ignore] public GooFile? Parent { get; set; }
[Ignore] public byte[] EncodedRle { get; set; } = Array.Empty();
// DelimiterRLE
public LayerDef() { }
public LayerDef(GooFile parent, Layer layer)
{
Parent = parent;
PausePositionZ = parent.MachineZ;
SetFrom(layer);
}
public void SetFrom(Layer layer)
{
PositionZ = layer.PositionZ;
ExposureTime = layer.ExposureTime;
LightOffDelay = layer.LightOffDelay;
LiftHeight = layer.LiftHeight;
LiftSpeed = layer.LiftSpeed;
LiftHeight2 = layer.LiftHeight2;
LiftSpeed2 = layer.LiftSpeed2;
RetractSpeed = layer.RetractSpeed;
RetractHeight2 = layer.RetractHeight2;
RetractSpeed2 = layer.RetractSpeed2;
WaitTimeAfterCure = layer.WaitTimeAfterCure;
WaitTimeAfterLift = layer.WaitTimeAfterLift;
WaitTimeBeforeCure = layer.WaitTimeBeforeCure;
LightPWM = layer.LightPWM;
}
public void CopyTo(Layer layer)
{
layer.PositionZ = PositionZ;
layer.ExposureTime = ExposureTime;
layer.LightOffDelay = LightOffDelay;
layer.LiftHeight = LiftHeight;
layer.LiftSpeed = LiftSpeed;
layer.LiftHeight2 = LiftHeight2;
layer.LiftSpeed2 = LiftSpeed2;
layer.RetractSpeed = RetractSpeed;
layer.RetractHeight2 = RetractHeight2;
layer.RetractSpeed2 = RetractSpeed2;
layer.WaitTimeAfterCure = WaitTimeAfterCure;
layer.WaitTimeAfterLift = WaitTimeAfterLift;
layer.WaitTimeBeforeCure = WaitTimeBeforeCure;
layer.LightPWM = (byte)LightPWM;
}
public Mat DecodeImage(uint layerIndex, bool consumeRle = true)
{
var mat = EmguExtensions.InitMat(Parent!.Resolution);
if (DataLength <= 3) return mat;
if (EncodedRle![0] != LayerMagic) throw new MessageException($"RLE for layer {layerIndex} is corrupted, should start with {LayerMagic} but got {EncodedRle[0]}");
int pixel = 0;
var lastByteIndex = DataLength - 1;
byte color = 0;
byte checkSum = 0;
for (var i = 1; i < lastByteIndex; i++)
{
// Calculate checksum
unchecked
{
checkSum += EncodedRle[i];
}
}
checkSum = (byte)~checkSum;
if (EncodedRle[^1] != checkSum) throw new MessageException($"Decoded RLE for layer {layerIndex} is corrupted, expected checksum <{EncodedRle[^1]}>, got <{checkSum}>");
for (var i = 1; i < lastByteIndex; i++)
{
/* Byte0[7:6]: The type of chunk
* (0x0) 0 0 This chunk contain all 0x0 pixels
* (0x1) 0 1 This chunk contain the value of gray between 0x1 to 0xfe. The gray value is after byte0.
* (0x2) 1 0 This chunk contain the diff value from the previous pixel
* (0x3) 1 1 This chunk contain all 0xff pixels
*/
byte chunkType = (byte)(EncodedRle[i] >> 6);
int stride = 0;
int strideIndex0 = i;
int strideIndex1 = i + 1;
int strideIndex2 = i + 2;
int strideIndex3 = i + 3;
if (chunkType == 0x0) // 0 0
{
color = byte.MinValue;
}
else if (chunkType == 0x1) // 0 1
{
color = EncodedRle[++i];
strideIndex1++;
strideIndex2++;
strideIndex3++;
}
else if (chunkType == 0x2) // 1 0
{
/* When byte0[7:6] is [1:0], the meaning of byte0[5:4] follow below definition:
* 0 0 byte0[3:0] is the positive diff value. that's mean
current value subtract previous value is bigger
than 0. The range is from 0 to 15. 0x0 map to 0.
0xf map to 15.
* 0 1 byte0[3:0] is the positive diff value. And this
value's run-length represent by byte1[7:0]
* 1 0 byte0[3:0] is the negative diff value.that's mean
current value subtract previous value is smaller
than 0. The range is from 0 to 15. 0x0 map to 0.
0xf map to 15.
* 1 1 byte0[3:0] is the negative diff value. And this
value's run-length represent by byte1[7:0]
*/
byte diffType = (byte)(EncodedRle[i] >> 4 & 0x3);
byte diffValue = (byte)(EncodedRle[i] & 0xf);
if (diffType == 0x0)
{
color += diffValue;
stride = 1;
}
else if (diffType == 0x1)
{
color += diffValue;
stride = EncodedRle[++i];
}
else if (diffType == 0x2)
{
color -= diffValue;
stride = 1;
}
else if (diffType == 0x3)
{
color -= diffValue;
stride = EncodedRle[++i];
}
else
{
throw new ArgumentOutOfRangeException(nameof(diffType), $"Diff type {diffType:X} is out of range, can only go up to 0x3.");
}
}
else if (chunkType == 0x3) // 1 1
{
color = byte.MaxValue;
}
else
{
throw new ArgumentOutOfRangeException(nameof(chunkType), $"Chunk type {chunkType:X} is out of range, can only go up to 0x3.");
}
if (chunkType != 0x2)
{
/* Byte0[5:4]: The length of chunk except when byte0[7:6] is [1 0]
* (0x0) 0 0 4-bit run-length use byte0[3:0]
* (0x1) 0 1 The run-length consist by byte1[7:0] and byte0[3:0]
* (0x2) 1 0 The run-length consist by byte1[7:0], byte2[7:0] and byte0[3:0]
* (0x3) 1 1 The run-length consist by byte1[7:0], byte2[7:0], byte3[7:0] and byte0[3:0]
*/
byte chunkLength = (byte) (EncodedRle[strideIndex0] >> 4 & 0x3);
switch (chunkLength)
{
case 0x0:
stride = EncodedRle[strideIndex0] & 0xF;
break;
case 0x1:
stride = (EncodedRle[strideIndex1] << 4) + (EncodedRle[strideIndex0] & 0xF);
i += 1;
break;
case 0x2:
stride = (EncodedRle[strideIndex1] << 12) + (EncodedRle[strideIndex2] << 4) + (EncodedRle[strideIndex0] & 0xF);
i += 2;
break;
case 0x3:
stride = (EncodedRle[strideIndex1] << 20) + (EncodedRle[strideIndex2] << 12) + (EncodedRle[strideIndex3] << 4) + (EncodedRle[strideIndex0] & 0xF);
i += 3;
break;
default:
throw new ArgumentOutOfRangeException(nameof(chunkLength), $"Chunk length {chunkLength:X} is out of range, can only go up to 0x3.");
}
}
mat.FillSpan(ref pixel, stride, color);
}
if (consumeRle) EncodedRle = null!;
return mat;
}
public byte[] EncodeImage(Mat image, uint layerIndex)
{
List rle = new(){ LayerMagic };
byte previousColor = 0;
byte currentColor = 0;
uint stride = 0;
byte checkSum = 0;
var span = image.GetDataByteSpan();
void AddRep()
{
if (stride == 0)
{
return;
}
int firstByteIndex = rle.Count;
rle.Add(0);
// Difference mode
var differenceColor = (byte) Math.Abs(currentColor - previousColor);
if (stride <= byte.MaxValue && differenceColor <= 0xF)
{
rle[firstByteIndex] = (byte) (0x2 << 6 | (differenceColor & 0xF));
if (stride > 1)
{
rle[firstByteIndex] |= 0x1 << 4;
}
if (previousColor > currentColor)
{
rle[firstByteIndex] |= 0x1 << 5;
}
if (stride > 1)
{
rle.Add((byte) stride);
}
}
else
{
/*if (currentColor == byte.MinValue)
{
0 0 This chunk contain all 0x0 pixels
firstByte |= 0x00 << 6;
}*/
if (currentColor == byte.MaxValue)
{
// 1 1 This chunk contain all 0xff pixels
rle[firstByteIndex] |= 0x3 << 6;
}
else if (currentColor > byte.MinValue)
{
// 0 1 This chunk contain the value of gray between 0x1 to 0xfe. The gray value is after byte0.
rle[firstByteIndex] |= 0x1 << 6;
rle.Add(currentColor);
}
rle[firstByteIndex] |= (byte) (stride & 0xF);
if (stride <= 0xF)
{
return;
}
if (stride <= 0xFFF)
{
rle[firstByteIndex] |= 0x1 << 4;
rle.Add((byte) (stride >> 4));
return;
}
if (stride <= 0xFFFFF)
{
rle[firstByteIndex] |= 0x2 << 4;
rle.Add((byte) (stride >> 12));
rle.Add((byte) (stride >> 4));
return;
}
if (stride <= 0xFFFFFFF)
{
rle[firstByteIndex] |= 0x3 << 4;
rle.Add((byte) (stride >> 20));
rle.Add((byte) (stride >> 12));
rle.Add((byte) (stride >> 4));
return;
}
}
}
for (int i = 0; i < span.Length; i++)
{
if (currentColor == span[i])
{
stride++;
}
else
{
AddRep();
stride = 1;
previousColor = currentColor;
currentColor = span[i];
}
}
AddRep();
// Calculate checksum
for (int i = 1; i < rle.Count; i++)
{
unchecked
{
checkSum += rle[i];
}
}
rle.Add((byte) ~checkSum);
EncodedRle = rle.ToArray();
DataLength = (uint)EncodedRle.Length;
return EncodedRle;
}
public override string ToString()
{
return $"{nameof(Pause)}: {Pause}, {nameof(PausePositionZ)}: {PausePositionZ}, {nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(WaitTimeAfterCure)}: {WaitTimeAfterCure}, {nameof(WaitTimeAfterLift)}: {WaitTimeAfterLift}, {nameof(WaitTimeBeforeCure)}: {WaitTimeBeforeCure}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(RetractHeight)}: {RetractHeight}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}, {nameof(LightPWM)}: {LightPWM}, {nameof(DelimiterData)}: {DelimiterData}, {nameof(DataLength)}: {DataLength}, {nameof(Parent)}: {Parent}, {nameof(EncodedRle)}: {EncodedRle}";
}
}
public class FileFooter
{
[FieldOrder(0)] public byte Padding1 { get; set; }
[FieldOrder(1)] public byte Padding2 { get; set; }
[FieldOrder(2)] public byte Padding3 { get; set; }
[FieldOrder(3)] [FieldCount(8)] public byte[] Magic { get; set; } = FileMagic;
public override string ToString()
{
return $"{nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(Padding3)}: {Padding3}, {nameof(Magic)}: {Magic}";
}
}
#endregion
#region Properties
public override FileFormatType FileType => FileFormatType.Binary;
public override FileExtension[] FileExtensions { get; } = {
new(typeof(GooFile), "goo", "Elegoo GOO"),
};
public override Size[]? ThumbnailsOriginalSize { get; } =
{
new(116, 116),
new(290, 290)
};
public FileHeader Header { get; private set; } = new();
public LayerDef[]? LayersDefinition { get; private set; }
public FileFooter Footer { get; private set; } = new();
public override PrintParameterModifier[]? PrintParameterModifiers { get; } = {
PrintParameterModifier.BottomLayerCount,
PrintParameterModifier.TransitionLayerCount,
//PrintParameterModifier.BottomLightOffDelay,
PrintParameterModifier.LightOffDelay,
PrintParameterModifier.BottomWaitTimeBeforeCure,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.BottomExposureTime,
PrintParameterModifier.ExposureTime,
PrintParameterModifier.BottomWaitTimeAfterCure,
PrintParameterModifier.WaitTimeAfterCure,
PrintParameterModifier.BottomLiftHeight,
PrintParameterModifier.BottomLiftSpeed,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.BottomLiftHeight2,
PrintParameterModifier.BottomLiftSpeed2,
PrintParameterModifier.LiftHeight2,
PrintParameterModifier.LiftSpeed2,
PrintParameterModifier.BottomWaitTimeAfterLift,
PrintParameterModifier.WaitTimeAfterLift,
PrintParameterModifier.BottomRetractSpeed,
PrintParameterModifier.RetractSpeed,
PrintParameterModifier.BottomRetractHeight2,
PrintParameterModifier.BottomRetractSpeed2,
PrintParameterModifier.RetractHeight2,
PrintParameterModifier.RetractSpeed2,
PrintParameterModifier.BottomLightPWM,
PrintParameterModifier.LightPWM
};
public override PrintParameterModifier[]? PrintParameterPerLayerModifiers { get; } = {
PrintParameterModifier.PositionZ,
PrintParameterModifier.LightOffDelay,
PrintParameterModifier.WaitTimeBeforeCure,
PrintParameterModifier.ExposureTime,
PrintParameterModifier.WaitTimeAfterCure,
PrintParameterModifier.LiftHeight,
PrintParameterModifier.LiftSpeed,
PrintParameterModifier.LiftHeight2,
PrintParameterModifier.LiftSpeed2,
PrintParameterModifier.WaitTimeAfterLift,
PrintParameterModifier.RetractSpeed,
PrintParameterModifier.RetractHeight2,
PrintParameterModifier.RetractSpeed2,
PrintParameterModifier.LightPWM
};
public override uint ResolutionX
{
get => Header.ResolutionX;
set => base.ResolutionX = Header.ResolutionX = (ushort)value;
}
public override uint ResolutionY
{
get => Header.ResolutionY;
set => base.ResolutionY = Header.ResolutionY = (ushort)value;
}
public override float LayerHeight
{
get => Header.LayerHeight;
set
{
Header.LayerHeight = value;
RaisePropertyChanged();
}
}
public override float DisplayWidth
{
get => Header.DisplayWidth;
set => base.DisplayWidth = Header.DisplayWidth = RoundDisplaySize(value);
}
public override float DisplayHeight
{
get => Header.DisplayHeight;
set => base.DisplayHeight = Header.DisplayHeight = RoundDisplaySize(value);
}
public override float MachineZ
{
get => Header.MachineZ > 0 ? Header.MachineZ : base.MachineZ;
set => base.MachineZ = Header.MachineZ = (float)Math.Round(value, 2);
}
public override FlipDirection DisplayMirror
{
get
{
if (Header is {MirrorX: true, MirrorY: true}) return FlipDirection.Both;
if (Header.MirrorX) return FlipDirection.Horizontally;
if (Header.MirrorY) return FlipDirection.Vertically;
return FlipDirection.None;
}
set
{
switch (value)
{
case FlipDirection.None:
Header.MirrorX = false;
Header.MirrorY = false;
break;
case FlipDirection.Horizontally:
Header.MirrorX = true;
Header.MirrorY = false;
break;
case FlipDirection.Vertically:
Header.MirrorX = false;
Header.MirrorY = true;
break;
case FlipDirection.Both:
Header.MirrorX = true;
Header.MirrorY = true;
break;
default:
throw new ArgumentOutOfRangeException(nameof(value), value, null);
}
RaisePropertyChanged();
}
}
public override byte AntiAliasing
{
get => (byte)(Header.AntiAliasingLevel);
set => base.AntiAliasing = (byte)(Header.AntiAliasingLevel = value);
}
public override uint LayerCount
{
get => base.LayerCount;
set => base.LayerCount = Header.LayerCount = base.LayerCount;
}
public override ushort BottomLayerCount
{
get => (ushort) Header.BottomLayerCount;
set => base.BottomLayerCount = (ushort) (Header.BottomLayerCount = value);
}
public override TransitionLayerTypes TransitionLayerType => TransitionLayerTypes.Software;
public override ushort TransitionLayerCount
{
get => Header.TransitionLayerCount;
set => base.TransitionLayerCount = Header.TransitionLayerCount = (ushort) Math.Min(value, MaximumPossibleTransitionLayerCount);
}
public override float BottomLightOffDelay => Header.LightOffDelay;
public override float LightOffDelay
{
get => Header.LightOffDelay;
set
{
base.LightOffDelay = Header.LightOffDelay = (float)Math.Round(value, 2);
if (value > 0)
{
Header.DelayMode = DelayModes.LightOff;
}
}
}
public override float BottomWaitTimeBeforeCure
{
get => base.BottomWaitTimeBeforeCure > 0 ? base.BottomWaitTimeBeforeCure : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeBeforeCure ?? 0;
set
{
base.BottomWaitTimeBeforeCure = value;
Header.DelayMode = DelayModes.WaitTime;
}
}
public override float WaitTimeBeforeCure
{
get => Header.WaitTimeBeforeCure;
set
{
base.WaitTimeBeforeCure = Header.WaitTimeBeforeCure = Header.WaitTimeBeforeCure = (float)Math.Round(value, 2);
if (value > 0)
{
BottomLightOffDelay = 0;
LightOffDelay = 0;
}
Header.DelayMode = DelayModes.WaitTime;
}
}
public override float BottomExposureTime
{
get => Header.BottomExposureTime;
set => base.BottomExposureTime = Header.BottomExposureTime = (float)Math.Round(value, 2);
}
public override float BottomWaitTimeAfterCure
{
get => base.BottomWaitTimeAfterCure > 0 ? base.BottomWaitTimeAfterCure : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeAfterCure ?? 0;
set
{
base.BottomWaitTimeAfterCure = value;
Header.DelayMode = DelayModes.WaitTime;
}
}
public override float WaitTimeAfterCure
{
get => Header.WaitTimeAfterCure;
set
{
base.WaitTimeAfterCure = Header.WaitTimeAfterCure = (float)Math.Round(value, 2);
if (value > 0)
{
BottomLightOffDelay = 0;
LightOffDelay = 0;
}
Header.DelayMode = DelayModes.WaitTime;
}
}
public override float ExposureTime
{
get => Header.ExposureTime;
set => base.ExposureTime = Header.ExposureTime = (float)Math.Round(value, 2);
}
public override float BottomLiftHeight
{
get => Header.BottomLiftHeight;
set => base.BottomLiftHeight = Header.BottomLiftHeight = (float)Math.Round(value, 2);
}
public override float BottomLiftSpeed
{
get => Header.BottomLiftSpeed;
set => base.BottomLiftSpeed = Header.BottomLiftSpeed = (float)Math.Round(value, 2);
}
public override float LiftHeight
{
get => Header.LiftHeight;
set => base.LiftHeight = Header.LiftHeight = (float)Math.Round(value, 2);
}
public override float LiftSpeed
{
get => Header.LiftSpeed;
set => base.LiftSpeed = Header.LiftSpeed = (float)Math.Round(value, 2);
}
public override float BottomLiftHeight2
{
get => Header.BottomLiftHeight2;
set => base.BottomLiftHeight2 = Header.BottomLiftHeight2 = (float)Math.Round(value, 2);
}
public override float BottomLiftSpeed2
{
get => Header.BottomLiftSpeed2;
set => base.BottomLiftSpeed2 = Header.BottomLiftSpeed2 = (float)Math.Round(value, 2);
}
public override float LiftHeight2
{
get => Header.LiftHeight2;
set => base.LiftHeight2 = Header.LiftHeight2 = (float)Math.Round(value, 2);
}
public override float LiftSpeed2
{
get => Header.LiftSpeed2;
set => base.LiftSpeed2 = Header.LiftSpeed2 = (float)Math.Round(value, 2);
}
public override float BottomWaitTimeAfterLift
{
get => base.BottomWaitTimeAfterLift > 0 ? base.BottomWaitTimeAfterLift : this.FirstOrDefault(layer => layer is { IsBottomLayer: true, IsDummy: false })?.WaitTimeAfterLift ?? 0;
set
{
base.BottomWaitTimeAfterLift = value;
Header.DelayMode = DelayModes.WaitTime;
}
}
public override float WaitTimeAfterLift
{
get => Header.WaitTimeAfterLift;
set
{
base.WaitTimeAfterLift = Header.WaitTimeAfterLift = Header.WaitTimeAfterLift = (float)Math.Round(value, 2);
if (value > 0)
{
BottomLightOffDelay = 0;
LightOffDelay = 0;
}
Header.DelayMode = DelayModes.WaitTime;
}
}
public override float BottomRetractSpeed
{
get => Header.BottomRetractSpeed;
set => base.BottomRetractSpeed = Header.BottomRetractSpeed = (float)Math.Round(value, 2);
}
public override float RetractSpeed
{
get => Header.RetractSpeed;
set => base.RetractSpeed = Header.RetractSpeed = (float)Math.Round(value, 2);
}
public override float BottomRetractHeight2
{
get => Header.BottomRetractHeight2;
set
{
value = Math.Clamp((float)Math.Round(value, 2), 0, BottomRetractHeightTotal);
base.BottomRetractHeight2 = Header.BottomRetractHeight2 = value;
}
}
public override float BottomRetractSpeed2
{
get => Header.BottomRetractSpeed2;
set => base.BottomRetractSpeed2 = Header.BottomRetractSpeed2 = (float)Math.Round(value, 2);
}
public override float RetractHeight2
{
get => Header.RetractHeight2;
set
{
value = Math.Clamp((float)Math.Round(value, 2), 0, RetractHeightTotal);
base.RetractHeight2 = Header.RetractHeight2 = value;
}
}
public override float RetractSpeed2
{
get => Header.RetractSpeed2;
set => base.RetractSpeed2 = Header.RetractSpeed2 = (float)Math.Round(value, 2);
}
public override byte BottomLightPWM
{
get => (byte)Header.BottomLightPWM;
set => base.BottomLightPWM = (byte)(Header.BottomLightPWM = value);
}
public override byte LightPWM
{
get => (byte)Header.LightPWM;
set => base.LightPWM = (byte)(Header.LightPWM = value);
}
public override float PrintTime
{
get => base.PrintTime;
set
{
base.PrintTime = value;
Header.PrintTime = (uint)base.PrintTime;
}
}
public override string MachineName
{
get => Header.MachineName;
set => base.MachineName = Header.MachineName = value;
}
public override float MaterialGrams
{
get => Header.MaterialGrams;
set => base.MaterialGrams = Header.MaterialGrams = (float)Math.Round(value, 3);
}
public override float MaterialCost
{
get => (float)Math.Round(Header.MaterialCost, 3);
set => base.MaterialCost = Header.MaterialCost = (float)Math.Round(value, 3);
}
public override object[] Configs => new object[] { Header, Footer };
#endregion
#region Constructors
public GooFile()
{
}
#endregion
#region Methods
protected override void DecodeInternally(OperationProgress progress)
{
using var inputFile = new FileStream(FileFullPath!, FileMode.Open, FileAccess.Read);
Header = Helpers.Deserialize(inputFile);
Debug.WriteLine($"Header: {Header}");
var expectedMagic = FileMagic;
if (!Header.Magic.SequenceEqual(expectedMagic))
{
throw new FileLoadException("Not a valid GOO file! Magic value mismatch", FileFullPath);
}
progress.Reset(OperationProgress.StatusDecodePreviews, ThumbnailsCount);
Thumbnails[0] = DecodeImage(DATATYPE_RGB565_BE, Header.SmallPreview565, ThumbnailsOriginalSize![0]);
progress++;
Thumbnails[1] = DecodeImage(DATATYPE_RGB565_BE, Header.BigPreview565, ThumbnailsOriginalSize[1]);
progress++;
progress.Reset(OperationProgress.StatusDecodeLayers, Header.LayerCount);
Init(Header.LayerCount, DecodeType == FileDecodeType.Partial);
LayersDefinition = new LayerDef[LayerCount];
inputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
foreach (var batch in BatchLayersIndexes())
{
foreach (var layerIndex in batch)
{
progress.PauseOrCancelIfRequested();
LayersDefinition[layerIndex] = Helpers.Deserialize(inputFile);
LayersDefinition[layerIndex].Parent = this;
if (DecodeType == FileDecodeType.Full)
{
LayersDefinition[layerIndex].EncodedRle = inputFile.ReadBytes(LayersDefinition[layerIndex].DataLength);
}
else
{
inputFile.Seek(LayersDefinition[layerIndex].DataLength, SeekOrigin.Current);
}
inputFile.Seek(2, SeekOrigin.Current); // \n
Debug.WriteLine($"layer[{layerIndex}]: {LayersDefinition[layerIndex]}");
}
if (DecodeType == FileDecodeType.Full)
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
progress.PauseIfRequested();
var layerDef = LayersDefinition[layerIndex];
_layers[layerIndex] = new Layer((uint) layerIndex, layerDef.DecodeImage((uint) layerIndex), this);
layerDef.CopyTo(_layers[layerIndex]);
progress.LockAndIncrement();
});
}
}
Footer = Helpers.Deserialize(inputFile);
Debug.WriteLine($"Footer: {Footer}");
if (!Footer.Magic.SequenceEqual(expectedMagic))
{
throw new FileLoadException("Not a valid GOO file! Footer magic value mismatch", FileFullPath);
}
}
protected override void OnBeforeEncode(bool isPartialEncode)
{
Header.PerLayerSettings = UsingPerLayerSettings;
Header.Volume = Volume;
Header.MaterialGrams = MaterialMilliliters;
}
protected override void EncodeInternally(OperationProgress progress)
{
using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Create, FileAccess.Write);
progress.Reset(OperationProgress.StatusEncodePreviews, 2);
Mat?[] thumbnails = { GetThumbnail(true), GetThumbnail(false) };
Header.BigPreview565 = EncodeImage(DATATYPE_RGB565_BE, thumbnails[0]!);
progress++;
Header.SmallPreview565 = EncodeImage(DATATYPE_RGB565_BE, thumbnails[1]!);
progress++;
Header.LayerDefAddress = (uint)Helpers.Serializer.SizeOf(Header);
outputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
var layerData = new LayerDef[LayerCount];
var delimiter = Delimiter;
foreach (var batch in BatchLayersIndexes())
{
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
{
progress.PauseIfRequested();
using (var mat = this[layerIndex].LayerMat)
{
layerData[layerIndex] = new LayerDef(this, this[layerIndex]);
layerData[layerIndex].EncodeImage(mat, (uint) layerIndex);
}
progress.LockAndIncrement();
});
foreach (var layerIndex in batch)
{
progress.PauseOrCancelIfRequested();
outputFile.WriteSerialize(layerData[layerIndex]);
outputFile.WriteBytes(layerData[layerIndex].EncodedRle);
outputFile.WriteBytes(delimiter);
layerData[layerIndex].EncodedRle = null!; // Free
}
}
// Footer
outputFile.WriteSerialize(Footer);
// Header
outputFile.Seek(0, SeekOrigin.Begin);
outputFile.WriteSerialize(Header);
Debug.WriteLine("Encode Results:");
Debug.WriteLine(Header);
Debug.WriteLine(Footer);
Debug.WriteLine("-End-");
}
protected override void PartialSaveInternally(OperationProgress progress)
{
using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Open, FileAccess.Write);
outputFile.Seek(0, SeekOrigin.Begin);
outputFile.WriteSerialize(Header);
outputFile.Seek(Header.LayerDefAddress, SeekOrigin.Begin);
for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
LayersDefinition![layerIndex].SetFrom(this[layerIndex]);
outputFile.WriteSerialize(LayersDefinition[layerIndex]);
outputFile.Seek(LayersDefinition[layerIndex].DataLength + 2, SeekOrigin.Current);
}
}
#endregion
}