Files
Tiago Conceição 2c7ad09dc0 v3.13.1
- (Change) `Layer.IsBottomLayer` no longer calculate the value using the position of the layer, a new property `IsBottomLayerByHeight` is now used to get that result
- (Improvement) Tool - Double exposure: Increase the bottom layer count per cloned bottom layer
- (Improvement) Calibration - Exposure time finder: Set the absolute bottom layer count accordingly when also testing for bottom time
- (Improvement) Goo: Enforce Wait times or Light-off-delay flag based on property set
- (Fix) AnyCubic and Goo: `PerLayerSetting` flag was set inverted causing printer not to follow layer settings when it should and also the otherwise (#689)
- (Fix) Tool - Scripting: Prevent from reload UI multiple times when using profiles (#694)
2023-04-27 22:58:05 +01:00

1081 lines
45 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
{
/// <summary>
/// Time with motor movement
/// </summary>
LightOff = 0,
/// <summary>
/// Absolute time to wait
/// </summary>
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<byte>();
[FieldOrder(12)] [FieldCount(2)] public byte[] SmallPreviewDelimiter { get; set; } = Delimiter;
[FieldOrder(13)] [FieldCount(290 * 290 * 2)] public byte[] BigPreview565 { get; set; } = Array.Empty<byte>();
[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; }
/// <summary>
/// 0: Light off delay mode | 1Wait time mode
/// </summary>
[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;
/// <summary>
/// <para>0: Normal mode</para>
/// <para>1: Advance mode, printing use the value of "Layer Definition Content"</para>
/// </summary>
[FieldEndianness(Endianness.Big)] [FieldOrder(53)] public bool PerLayerSettings { get; set; }
[FieldEndianness(Endianness.Big)] [FieldOrder(54)] public uint PrintTime { get; set; }
/// <summary>
/// // The volume of all parts. unit: mm3
/// </summary>
[FieldEndianness(Endianness.Big)] [FieldOrder(55)] public float Volume { get; set; }
/// <summary>
/// The weight of all parts. unit: g
/// </summary>
[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
/// <summary>
/// <para>0The range of pixel's gray value is from 0x0 ~ 0xf</para>
/// <para>1The range of pixel's gray value is from 0x0 ~ 0xff</para>
/// </summary>
[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
{
/// <summary>
/// 0: reserve
/// 1: current layer pause printing
/// </summary>
[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<byte>();
// 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<byte> 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<FileHeader>(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<LayerDef>(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<FileFooter>(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
}