diff options
Diffstat (limited to 'UVtools.Core/FileFormats/PWSFile.cs')
-rw-r--r-- | UVtools.Core/FileFormats/PWSFile.cs | 1349 |
1 files changed, 1349 insertions, 0 deletions
diff --git a/UVtools.Core/FileFormats/PWSFile.cs b/UVtools.Core/FileFormats/PWSFile.cs new file mode 100644 index 0000000..c4c4fce --- /dev/null +++ b/UVtools.Core/FileFormats/PWSFile.cs @@ -0,0 +1,1349 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Threading.Tasks; +using BinarySerialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class PWSFile : FileFormat + { + #region Constants + public const byte MarkSize = 12; + public const byte RLE1EncodingLimit = 0x7d; // 125; + public const ushort RLE4EncodingLimit = 0xfff; // 4095; + + // CRC-16-ANSI (aka CRC-16-IMB) Polynomial: x^16 + x^15 + x^2 + 1 + public static readonly int[] CRC16Table = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040, + }; + + #endregion + + #region Enums + public enum LayerRleFormat + { + PWS, + PW0 + } + #endregion + + #region Sub Classes + + #region FileMark + public class FileMark + { + public const string SectionMarkFile = "ANYCUBIC"; + + private string _mark = SectionMarkFile; + /// <summary> + /// Gets the file mark placeholder + /// Fixed to "ANYCUBIC" + /// </summary> + [FieldOrder(0)] + [FieldLength(MarkSize)] + public string Mark + { + get => _mark; + set => _mark = value.TrimEnd('\0'); + } + + /// <summary> + /// Gets the file format version + /// </summary> + [FieldOrder(1)] public uint Version { get; set; } = 1; + + /// <summary> + /// Gets the area num + /// </summary> + [FieldOrder(2)] public uint AreaNum { get; set; } = 4; + + /// <summary> + /// Gets the header start address + /// </summary> + [FieldOrder(3)] public uint HeaderAddress { get; set; } + + [FieldOrder(4)] public uint Offset1 { get; set; } + + /// <summary> + /// Gets the preview start offset + /// </summary> + [FieldOrder(5)] public uint PreviewAddress { get; set; } + + [FieldOrder(6)] public uint Offset2 { get; set; } + + /// <summary> + /// Gets the layer definition start address + /// </summary> + [FieldOrder(7)] public uint LayerDefinitionAddress { get; set; } + + [FieldOrder(8)] public uint Offset3 { get; set; } + + /// <summary> + /// Gets layer image start address + /// </summary> + [FieldOrder(9)] public uint LayerImageAddress { get; set; } + + public override string ToString() + { + return $"{nameof(Mark)}: {Mark}, {nameof(Version)}: {Version}, {nameof(AreaNum)}: {AreaNum}, {nameof(HeaderAddress)}: {HeaderAddress}, {nameof(Offset1)}: {Offset1}, {nameof(PreviewAddress)}: {PreviewAddress}, {nameof(Offset2)}: {Offset2}, {nameof(LayerDefinitionAddress)}: {LayerDefinitionAddress}, {nameof(Offset3)}: {Offset3}, {nameof(LayerImageAddress)}: {LayerImageAddress}"; + } + } + #endregion + + #region Section + + public class Section + { + private string _mark; + + /// <summary> + /// Gets the section mark placeholder + /// </summary> + [FieldOrder(0)] + [FieldLength(MarkSize)] + public string Mark + { + get => _mark; + set => _mark = value.TrimEnd('\0'); + } + + /// <summary> + /// Gets the length of this section + /// </summary> + [FieldOrder(1)] public uint Length { get; set; } + + public Section() { } + + public Section(string mark, object obj) : this(mark, (uint)Helpers.Serializer.SizeOf(obj)) { } + + public Section(string mark, uint length = 0) + { + Mark = mark; + Length = length; + } + + + public void Validate(string mark, object obj = null) + { + Validate(mark, 0u, obj); + } + + public void Validate(string mark, uint length, object obj = null) + { + if (!Mark.Equals(mark)) + { + throw new FileLoadException($"'{Mark}' section expected, but got '{mark}'"); + } + + if (!ReferenceEquals(obj, null)) + { + length += (uint)Helpers.Serializer.SizeOf(obj); + } + + if (length > 0 && Length != length) + { + throw new FileLoadException($"{Mark} section bytes: expected {Length}, got {length}, difference: {(int)Length - length}"); + } + } + + public override string ToString() => $"{{{nameof(Mark)}: {Mark}, {nameof(Length)}: {Length}}}"; + } + + #endregion + + #region Header + public class Header + { + public const string SectionMark = "HEADER"; + + [Ignore] public Section Section { get; set; } + [FieldOrder(0)] public float PixelSize { get; set; } + [FieldOrder(1)] public float LayerHeight { get; set; } + [FieldOrder(2)] public float LayerExposureTime { get; set; } + [FieldOrder(3)] public float LayerOffTime { get; set; } = 1; + [FieldOrder(4)] public float BottomExposureSeconds { get; set; } + [FieldOrder(5)] public float BottomLayersCount { get; set; } + [FieldOrder(6)] public float LiftHeight { get; set; } = 6; + + /// <summary> + /// Gets the lift speed in mm/s + /// </summary> + [FieldOrder(7)] public float LiftSpeed { get; set; } = 3; // mm/s + + /// <summary> + /// Gets the retract speed in mm/s + /// </summary> + [FieldOrder(8)] public float RetractSpeed { get; set; } = 3; // mm/s + [FieldOrder(9)] public float Volume { get; set; } + [FieldOrder(10)] public uint AntiAliasing { get; set; } = 1; + [FieldOrder(11)] public uint ResolutionX { get; set; } + [FieldOrder(12)] public uint ResolutionY { get; set; } + [FieldOrder(13)] public float Weight { get; set; } + [FieldOrder(14)] public float Price { get; set; } + [FieldOrder(15)] public uint ResinType { get; set; } // 0x24 ? + [FieldOrder(16)] public uint PerLayerOverride { get; set; } // bool + [FieldOrder(17)] public uint Offset1 { get; set; } + [FieldOrder(18)] public uint Offset2 { get; set; } + [FieldOrder(19)] public uint Offset3 { get; set; } + + public Header() + { + Section = new Section(SectionMark, this); + } + + public override string ToString() => $"{nameof(Section)}: {Section}, {nameof(PixelSize)}: {PixelSize}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(LayerExposureTime)}: {LayerExposureTime}, {nameof(LayerOffTime)}: {LayerOffTime}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {nameof(BottomLayersCount)}: {BottomLayersCount}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(Volume)}: {Volume}, {nameof(AntiAliasing)}: {AntiAliasing}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(Weight)}: {Weight}, {nameof(Price)}: {Price}, {nameof(ResinType)}: {ResinType}, {nameof(PerLayerOverride)}: {PerLayerOverride}, {nameof(Offset1)}: {Offset1}, {nameof(Offset2)}: {Offset2}, {nameof(Offset3)}: {Offset3}"; + + public void Validate() + { + Section.Validate(SectionMark, this); + } + } + + #endregion + + #region Preview + + /// <summary> + /// The files contain two preview images. + /// These are shown on the printer display when choosing which file to print, sparing the poor printer from needing to render a 3D image from scratch. + /// </summary> + public class Preview + { + public const string SectionMark = "PREVIEW"; + [Ignore] public Section Section { get; set; } + + /// <summary> + /// Gets the image width, in pixels. + /// </summary> + [FieldOrder(1)] public uint Width { get; set; } = 224; + + /// <summary> + /// Gets the resolution of the image, in dpi. + /// </summary> + [FieldOrder(2)] public uint Resolution { get; set; } = 42; + + /// <summary> + /// Gets the image height, in pixels. + /// </summary> + [FieldOrder(3)] public uint Height { get; set; } = 168; + + // little-endian 16bit colors, RGB 565 encoded. + //[FieldOrder(4)] + //[FieldLength("Section.Length")] + [Ignore] + public byte[] Data { get; set; } + + public Preview() + { + Section = new Section(SectionMark, this); + } + + public Mat Decode(bool consumeData = true) + { + Mat image = new Mat(new Size((int) Width, (int) Height), DepthType.Cv8U, 3); + var span = image.GetPixelSpan<byte>(); + + int pixel = 0; + for (uint i = 0; i < Data.Length; i += 2) + { + ushort color16 = (ushort)(Data[i] + (Data[i+1] << 8)); + var r =(color16 >> 11) & 0x1f; + var g = (color16 >> 5) & 0x3f; + var b = (color16 >> 0) & 0x1f; + + /*span[pixel++] = new Rgba32( + (byte)((r << 3) | (r & 0x7)), + (byte)((g << 2) | (g & 0x3)), + (byte)((b << 3) | (b & 0x7)) + );*/ + span[pixel++] = (byte) ((b << 3) | (b & 0x7)); + span[pixel++] = (byte) ((g << 2) | (g & 0x3)); + span[pixel++] = (byte) ((r << 3) | (r & 0x7)); + } + + if (consumeData) + Data = null; + + return image; + } + + public static Preview Encode(Mat image) + { + var span = image.GetPixelSpan<byte>(); + + Preview preview = new Preview + { + Width = (uint) image.Width, + Height = (uint) image.Height, + Resolution = 42, + Data = new byte[image.Width * image.Height * 2] + }; + + int i = 0; + for (int pixel = 0; pixel < span.Length; pixel += image.NumberOfChannels) + { + // BGR + int b = span[pixel] >> 3; + int g = span[pixel+1] >> 2; + int r = span[pixel+2] >> 3; + + + ushort color = (ushort) ((r << 11) | (g << 5) | (b << 0)); + + preview.Data[i++] = (byte) color; + preview.Data[i++] = (byte) (color >> 8); + } + + preview.Section.Length += (uint) preview.Data.Length; + return preview; + } + + public override string ToString() + { + return $"{nameof(Section)}: {Section}, {nameof(Width)}: {Width}, {nameof(Resolution)}: {Resolution}, {nameof(Height)}: {Height}, {nameof(Data)}: {Data}"; + } + + public void Validate(uint size) + { + Section.Validate(SectionMark, size, this); + } + } + + #endregion + + #region Layer + + public class LayerData + { + /// <summary> + /// Gets the layer image offset to encoded layer data, and its length in bytes. + /// </summary> + [FieldOrder(0)] + public uint DataAddress { get; set; } + + /// <summary> + /// Gets the layer image length in bytes. + /// </summary> + [FieldOrder(1)] + public uint DataLength { get; set; } + + [FieldOrder(2)] public float LiftHeight { get; set; } + + [FieldOrder(3)] public float LiftSpeed { get; set; } + + /// <summary> + /// Gets the exposure time for this layer, in seconds. + /// </summary> + [FieldOrder(4)] + public float LayerExposure { get; set; } + + /// <summary> + /// Gets the build platform Z position for this layer, measured in millimeters. + /// </summary> + [FieldOrder(5)] + public float LayerPositionZ { get; set; } + + [FieldOrder(6)] public float Offset1 { get; set; } + [FieldOrder(7)] public float Offset2 { get; set; } + + [Ignore] public byte[] EncodedRle { get; set; } + [Ignore] public PWSFile Parent { get; set; } + + public LayerData() + { + } + + public LayerData(PWSFile parent, uint layerIndex) + { + Parent = parent; + LiftHeight = Parent.HeaderSettings.LiftHeight; + LiftSpeed = Parent.HeaderSettings.LiftSpeed; + + LayerPositionZ = parent[layerIndex].PositionZ; + LayerExposure = parent[layerIndex].ExposureTime; + } + + public Mat Decode(bool consumeData = true) + { + var result = Parent.LayerFormat == LayerRleFormat.PWS ? DecodePWS() : DecodePW0(); + if (consumeData) + EncodedRle = null; + + return result; + } + + public byte[] Encode(Mat image) + { + EncodedRle = Parent.LayerFormat == LayerRleFormat.PWS ? EncodePWS(image) : EncodePW0(image); + return EncodedRle; + } + + private Mat DecodePWS() + { + var image = new Mat(new Size((int) Parent.ResolutionX, (int) Parent.ResolutionY), DepthType.Cv8U, 1); + var span = image.GetPixelSpan<byte>(); + + int index = 0; + for (byte bit = 0; bit < Parent.AntiAliasing; bit++) + { + byte bitValue = (byte)(byte.MaxValue / ((1 << Parent.AntiAliasing) - 1) * (1 << bit)); + + int pixel = 0; + for (; index < EncodedRle.Length; index++) + { + // Lower 7 bits is the repeat count for the bit (0..127) + int reps = EncodedRle[index] & 0x7f; + + // We only need to set the non-zero pixels + // High bit is on for white, off for black + if ((EncodedRle[index] & 0x80) != 0) + { + for (int i = 0; i < reps; i++) + { + span[pixel + i] |= bitValue; + } + } + + pixel += reps; + + if (pixel == span.Length) + { + index++; + break; + } + + if (pixel > span.Length) + { + image.Dispose(); + throw new FileLoadException("Error image ran off the end"); + } + } + } + + return image; + } + + public byte[] EncodePWS(Mat image) + { + List<byte> rawData = new List<byte>(); + var span = image.GetPixelSpan<byte>(); + + bool obit; + int rep; + + void AddRep() + { + if (rep <= 0) return; + + byte by = (byte)rep; + + if (obit) + { + by |= 0x80; + //bitsOn += uint(rep) + } + + rawData.Add(by); + } + + for (byte aalevel = 0; aalevel < Parent.AntiAliasing; aalevel++) + { + obit = false; + rep = 0; + + + for (int pixel = 0; pixel < span.Length; pixel++) + { + var nbit = (span[pixel] & (1 << (8 - Parent.AntiAliasing + aalevel))) != 0; + + if (nbit == obit) + { + rep++; + + if (rep == RLE1EncodingLimit) + { + AddRep(); + rep = 0; + } + } + else + { + AddRep(); + obit = nbit; + rep = 1; + } + } + + // Collect stragglers + AddRep(); + } + + DataLength = (uint) rawData.Count; + + return rawData.ToArray(); + } + + private Mat DecodePW0() + { + var image = new Mat(new Size((int)Parent.ResolutionX, (int)Parent.ResolutionY), DepthType.Cv8U, 1); + var span = image.GetPixelSpan<byte>(); + + uint n = 0; + for (int index = 0; index < EncodedRle.Length; index++) + { + byte b = EncodedRle[index]; + int code = (b >> 4); + uint reps = (uint) (b & 0xf); + byte color; + switch (code) + { + case 0x0: + color = 0x00; + index++; + reps = reps * 256 + EncodedRle[index]; + break; + case 0xf: + color = 0xff; + index++; + reps = reps * 256 + EncodedRle[index]; + break; + default: + color = (byte) ((code << 4) | code); + break; + } + + color &= 0xff; + + // We only need to set the non-zero pixels + if (color != 0) + { + for (int i = 0; i < reps; i++) + { + span[(int) (n + i)] |= color; + } + } + + n += reps; + + + if (n == span.Length) + { + //index++; + break; + } + + if (n > span.Length) + { + image.Dispose(); + throw new FileLoadException($"Error image ran off the end: {n - reps}({reps}) of {span.Length}"); + } + } + + if (n != span.Length) + { + image.Dispose(); + throw new FileLoadException($"Error image ended short: {n} of {span.Length}"); + } + + return image; + } + + public byte[] EncodePW0(Mat image) + { + List<byte> rawData = new List<byte>(); + var span = image.GetPixelSpan<byte>(); + + int lastColor = -1; + int reps = 0; + + void PutReps() + { + while (reps > 0) + { + int done = reps; + + if (lastColor == 0 || lastColor == 0xf) + { + if (done > RLE4EncodingLimit) + { + done = RLE4EncodingLimit; + } + //more:= []byte{ 0, 0} + //binary.BigEndian.PutUint16(more, uint16(done | (color << 12))) + + //rle = append(rle, more...) + + ushort more = (ushort)(done | (lastColor << 12)); + rawData.Add((byte)(more >> 8)); + rawData.Add((byte)more); + } + else + { + if (done > 0xf) + { + done = 0xf; + } + rawData.Add((byte)(done | lastColor << 4)); + } + + reps -= done; + } + } + + for (int i = 0; i < span.Length; i++) + { + int color = span[i] >> 4; + + if (color == lastColor) + { + reps++; + } + else + { + PutReps(); + lastColor = color; + reps = 1; + } + } + + PutReps(); + + EncodedRle = rawData.ToArray(); + DataLength = (uint)rawData.Count; + + ushort crc = CRCRle4(EncodedRle); + rawData.Add((byte)(crc >> 8)); + rawData.Add((byte)crc); + + return EncodedRle; + } + + public static ushort CRCRle4(byte[] data) + { + ushort crc16 = 0; + for (int i = 0; i < data.Length; i++) + { + crc16 = (ushort) ((crc16 << 8) ^ CRC16Table[((crc16 >> 8) ^ CRC16Table[data[i]]) & 0xff]); + + } + + crc16 = (ushort) ((CRC16Table[crc16 & 0xff] * 0x100) + CRC16Table[(crc16 >> 8) & 0xff]); + + return crc16; + } + + public ushort CRCEncodedRle() + { + return CRCRle4(EncodedRle); + } + } + + #endregion + + #region LayerDefinition + public class LayerDefinition + { + public const string SectionMark = "LAYERDEF"; + + [Ignore] public Section Section { get; set; } = new Section(SectionMark); + + [FieldOrder(0)] public uint LayersCount { get; set; } + + [Ignore] public LayerData[] Layers; + + public LayerDefinition() + { + Section = new Section(SectionMark, this); + } + + public LayerDefinition(uint layersCount) : this() + { + LayersCount = layersCount; + Layers = new LayerData[layersCount]; + } + + [Ignore] + public LayerData this[uint index] + { + get => Layers[index]; + set => Layers[index] = value; + } + + [Ignore] + public LayerData this[int index] + { + get => Layers[index]; + set => Layers[index] = value; + } + + public void Validate() + { + Section.Validate(SectionMark, (uint)(LayersCount * Helpers.Serializer.SizeOf(new LayerData())), this); + } + + public override string ToString() => $"{nameof(Section)}: {Section}, {nameof(LayersCount)}: {LayersCount}"; + } + #endregion + + #endregion + + #region Properties + + public FileMark FileMarkSettings { get; protected internal set; } = new FileMark(); + + public Header HeaderSettings { get; protected internal set; } = new Header(); + + public Preview PreviewSettings { get; protected internal set; } = new Preview(); + + public LayerDefinition LayersDefinition { get; private set; } = new LayerDefinition(); + + public Dictionary<string, LayerData> LayersHash { get; } = new Dictionary<string, LayerData>(); + + public override FileFormatType FileType => FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = { + new FileExtension("pws", "Photon Workshop PWS Files"), + new FileExtension("pw0", "Photon Workshop PW0 Files") + }; + + public override Type[] ConvertToFormats { get; } = + { + //typeof(ChituboxZipFile) + //typeof(PHZFile), + typeof(PWSFile), + typeof(CWSFile), + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = + { + PrintParameterModifier.InitialLayerCount, + PrintParameterModifier.InitialExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + //PrintParameterModifier.BottomLayerOffTime, + PrintParameterModifier.LayerOffTime, + //PrintParameterModifier.BottomLiftHeight, + //PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + }; + + public override byte ThumbnailsCount { get; } = 1; + + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(224, 168)}; + + public override uint ResolutionX => HeaderSettings.ResolutionX; + + public override uint ResolutionY => HeaderSettings.ResolutionY; + public override byte AntiAliasing => (byte) HeaderSettings.AntiAliasing; + + public override float LayerHeight => HeaderSettings.LayerHeight; + + public override ushort InitialLayerCount => (ushort)HeaderSettings.BottomLayersCount; + + public override float InitialExposureTime => HeaderSettings.BottomExposureSeconds; + + public override float LayerExposureTime => HeaderSettings.LayerExposureTime; + public override float LiftHeight => HeaderSettings.LiftHeight; + public override float LiftSpeed => HeaderSettings.LiftSpeed * 60; + public override float RetractSpeed => HeaderSettings.RetractSpeed * 60; + + public override float PrintTime => 0; + + public override float UsedMaterial => HeaderSettings.Volume; + + public override float MaterialCost => HeaderSettings.Price; + + public override string MaterialName => null; + public override string MachineName => LayerFormat == LayerRleFormat.PWS ? "AnyCubic Photon S" : "AnyCubic Photon Zero"; + + public override object[] Configs => new object[] { FileMarkSettings, HeaderSettings, PreviewSettings, LayersDefinition }; + + public LayerRleFormat LayerFormat => FileFullPath.EndsWith(".pws") ? LayerRleFormat.PWS : LayerRleFormat.PW0; + + #endregion + + #region Constructors + public PWSFile() + { + } + #endregion + + #region Methods + public override void Clear() + { + base.Clear(); + + LayersDefinition = null; + } + + public override void Encode(string fileFullPath, OperationProgress progress = null) + { + base.Encode(fileFullPath, progress); + LayersHash.Clear(); + + LayersDefinition = new LayerDefinition(LayerCount); + + uint currentOffset = FileMarkSettings.HeaderAddress = (uint) Helpers.Serializer.SizeOf(FileMarkSettings); + using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) + { + outputFile.Seek((int) currentOffset, SeekOrigin.Begin); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, HeaderSettings.Section); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + if (CreatedThumbnailsCount > 0) + { + FileMarkSettings.PreviewAddress = currentOffset; + Preview preview = Preview.Encode(Thumbnails[0]); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, preview.Section); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, preview); + currentOffset += outputFile.WriteBytes(preview.Data); + } + + FileMarkSettings.LayerDefinitionAddress = currentOffset; + + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + LayerData layer = new LayerData(this, (uint) layerIndex); + using (var image = this[layerIndex].LayerMat) + { + layer.Encode(image); + LayersDefinition.Layers[layerIndex] = layer; + } + lock (progress.Mutex) + { + progress++; + } + }); + + LayersDefinition.Section.Length += (uint)Helpers.Serializer.SizeOf(LayersDefinition[0]) * LayerCount; + currentOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinition.Section); + uint offsetLayerRle = FileMarkSettings.LayerImageAddress = currentOffset + LayersDefinition.Section.Length; + + currentOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinition); + + progress.Reset(OperationProgress.StatusWritingFile, LayerCount); + + foreach (var layer in LayersDefinition.Layers) + { + progress.Token.ThrowIfCancellationRequested(); + string hash = Helpers.ComputeSHA1Hash(layer.EncodedRle); + + if (LayersHash.TryGetValue(hash, out var layerDataHash)) + { + layer.DataAddress = layerDataHash.DataAddress; + layer.DataLength = (uint)layerDataHash.EncodedRle.Length; + } + else + { + LayersHash.Add(hash, layer); + + layer.DataAddress = offsetLayerRle; + + outputFile.Seek(offsetLayerRle, SeekOrigin.Begin); + offsetLayerRle += Helpers.SerializeWriteFileStream(outputFile, layer.EncodedRle); + } + + outputFile.Seek(currentOffset, SeekOrigin.Begin); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, layer); + + progress++; + } + + // Rewind + outputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, FileMarkSettings); + } + } + + public override void Decode(string fileFullPath, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) + { + + //HeaderSettings = Helpers.ByteToType<CbddlpFile.Header>(InputFile); + //HeaderSettings = Helpers.Serializer.Deserialize<Header>(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); + FileMarkSettings = Helpers.Deserialize<FileMark>(inputFile); + + Debug.Write("FileMark -> "); + Debug.WriteLine(FileMarkSettings); + + if (!FileMarkSettings.Mark.Equals(FileMark.SectionMarkFile)) + { + throw new FileLoadException( + $"Invalid Filemark {FileMarkSettings.Mark}, expected {FileMark.SectionMarkFile}", fileFullPath); + } + + if (FileMarkSettings.Version != 1) + { + throw new FileLoadException($"Invalid Version {FileMarkSettings.Version}, expected 1", + fileFullPath); + } + + FileFullPath = fileFullPath; + + inputFile.Seek(FileMarkSettings.HeaderAddress, SeekOrigin.Begin); + //Section sectionHeader = Helpers.Deserialize<Section>(inputFile); + //Debug.Write("SectionHeader -> "); + //Debug.WriteLine(sectionHeader); + + var section = Helpers.Deserialize<Section>(inputFile); + HeaderSettings = Helpers.Deserialize<Header>(inputFile); + HeaderSettings.Section = section; + + + Debug.Write("Header -> "); + Debug.WriteLine(HeaderSettings); + + HeaderSettings.Validate(); + + if (FileMarkSettings.PreviewAddress > 0) + { + inputFile.Seek(FileMarkSettings.PreviewAddress, SeekOrigin.Begin); + + section = Helpers.Deserialize<Section>(inputFile); + PreviewSettings = Helpers.Deserialize<Preview>(inputFile); + PreviewSettings.Section = section; + Debug.Write("Preview -> "); + Debug.WriteLine(PreviewSettings); + + uint datasize = PreviewSettings.Width * PreviewSettings.Height * 2; + PreviewSettings.Validate(datasize); + + PreviewSettings.Data = new byte[datasize]; + inputFile.ReadBytes(PreviewSettings.Data); + + Thumbnails[0] = PreviewSettings.Decode(true); + } + + inputFile.Seek(FileMarkSettings.LayerDefinitionAddress, SeekOrigin.Begin); + + section = Helpers.Deserialize<Section>(inputFile); + LayersDefinition = Helpers.Deserialize<LayerDefinition>(inputFile); + LayersDefinition.Section = section; + Debug.Write("LayersDefinition -> "); + Debug.WriteLine(LayersDefinition); + + LayerManager = new LayerManager(LayersDefinition.LayersCount); + LayersDefinition.Layers = new LayerData[LayerCount]; + + + LayersDefinition.Validate(); + + for (int i = 0; i < LayerCount; i++) + { + LayersDefinition[i] = Helpers.Deserialize<LayerData>(inputFile); + LayersDefinition[i].Parent = this; + } + + progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); + + for (int i = 0; i < LayerCount; i++) + { + var layer = LayersDefinition[i]; + inputFile.Seek(layer.DataAddress, SeekOrigin.Begin); + layer.EncodedRle = new byte[layer.DataLength]; + inputFile.ReadBytes(layer.EncodedRle); + + progress++; + progress.Token.ThrowIfCancellationRequested(); + } + + progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) + { + return; + } + + using (var image = LayersDefinition[(uint) layerIndex].Decode()) + { + this[layerIndex] = new Layer((uint) layerIndex, image) + { + PositionZ = LayersDefinition[(uint)layerIndex].LayerPositionZ, + ExposureTime = LayersDefinition[(uint)layerIndex].LayerExposure + }; + } + + lock (progress.Mutex) + { + progress++; + } + }); + } + + progress.Token.ThrowIfCancellationRequested(); + } + + public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) + { + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return HeaderSettings.LayerOffTime; + + var baseValue = base.GetValueFromPrintParameterModifier(modifier); + return baseValue; + } + + public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) + { + void UpdateLayers() + { + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + // Bottom : others + + this[layerIndex].ExposureTime = + LayersDefinition[layerIndex].LayerExposure = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomExposureSeconds, HeaderSettings.LayerExposureTime); + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + HeaderSettings.BottomLayersCount = + HeaderSettings.BottomLayersCount = value.Convert<uint>(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + HeaderSettings.BottomExposureSeconds = value.Convert<float>(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + HeaderSettings.LayerExposureTime = value.Convert<float>(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) + { + HeaderSettings.LayerOffTime = value.Convert<float>(); + UpdateLayers(); + return true; + } + + + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + { + HeaderSettings.LiftHeight = value.Convert<float>(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + { + HeaderSettings.LiftSpeed = value.Convert<float>() / 60f; + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + { + HeaderSettings.RetractSpeed = value.Convert<float>() / 60f; + UpdateLayers(); + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (LayerManager.IsModified) + { + if (!string.IsNullOrEmpty(filePath)) + { + FileFullPath = filePath; + } + Encode(FileFullPath, progress); + return; + } + + + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + } + + using (var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) + { + outputFile.Seek(FileMarkSettings.HeaderAddress+Helpers.Serializer.SizeOf(HeaderSettings.Section), SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + + outputFile.Seek(FileMarkSettings.LayerDefinitionAddress + Helpers.Serializer.SizeOf(HeaderSettings.Section) + Helpers.Serializer.SizeOf(LayersDefinition), SeekOrigin.Begin); + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + Helpers.SerializeWriteFileStream(outputFile, LayersDefinition[layerIndex]); + } + } + + //Decode(FileFullPath, progress); + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + if (to == typeof(PWSFile)) + { + if (Path.GetExtension(FileFullPath).Equals(Path.GetExtension(fileFullPath))) + { + return false; + } + PWSFile file = new PWSFile + { + LayerManager = LayerManager, + HeaderSettings = + { + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + LayerHeight = LayerHeight, + LayerExposureTime = LayerExposureTime, + LiftHeight = LiftHeight, + LiftSpeed = LiftSpeed / 60, + RetractSpeed = RetractSpeed / 60, + LayerOffTime = HeaderSettings.LayerOffTime, + BottomLayersCount = InitialLayerCount, + BottomExposureSeconds = InitialExposureTime, + Price = MaterialCost, + Volume = UsedMaterial, + Weight = HeaderSettings.Weight, + AntiAliasing = ValidateAntiAliasingLevel() + } + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + /*if (to == typeof(PHZFile)) + { + PHZFile file = new PHZFile + { + LayerManager = LayerManager + }; + + + file.HeaderSettings.Version = 2; + file.HeaderSettings.BedSizeX = HeaderSettings.BedSizeX; + file.HeaderSettings.BedSizeY = HeaderSettings.BedSizeY; + file.HeaderSettings.BedSizeZ = HeaderSettings.BedSizeZ; + file.HeaderSettings.OverallHeightMilimeter = TotalHeight; + file.HeaderSettings.BottomExposureSeconds = InitialExposureTime; + file.HeaderSettings.BottomLayersCount = InitialLayerCount; + file.HeaderSettings.BottomLightPWM = HeaderSettings.BottomLightPWM; + file.HeaderSettings.LayerCount = LayerCount; + file.HeaderSettings.LayerExposureSeconds = LayerExposureTime; + file.HeaderSettings.LayerHeightMilimeter = LayerHeight; + file.HeaderSettings.LayerOffTime = HeaderSettings.LayerOffTime; + file.HeaderSettings.LightPWM = HeaderSettings.LightPWM; + file.HeaderSettings.PrintTime = HeaderSettings.PrintTime; + file.HeaderSettings.ProjectorType = HeaderSettings.ProjectorType; + file.HeaderSettings.ResolutionX = ResolutionX; + file.HeaderSettings.ResolutionY = ResolutionY; + + file.HeaderSettings.BottomLayerCount = InitialLayerCount; + file.HeaderSettings.BottomLiftHeight = PrintParametersSettings.BottomLiftHeight; + file.HeaderSettings.BottomLiftSpeed = PrintParametersSettings.BottomLiftSpeed; + file.HeaderSettings.BottomLightOffDelay = PrintParametersSettings.BottomLightOffDelay; + file.HeaderSettings.CostDollars = MaterialCost; + file.HeaderSettings.LiftHeight = PrintParametersSettings.LiftHeight; + file.HeaderSettings.LiftingSpeed = PrintParametersSettings.LiftingSpeed; + file.HeaderSettings.LayerOffTime = HeaderSettings.LayerOffTime; + file.HeaderSettings.RetractSpeed = PrintParametersSettings.RetractSpeed; + file.HeaderSettings.VolumeMl = UsedMaterial; + file.HeaderSettings.WeightG = PrintParametersSettings.WeightG; + + file.HeaderSettings.MachineName = MachineName; + file.HeaderSettings.MachineNameSize = (uint) MachineName.Length; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ZCodexFile)) + { + TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); + ZCodexFile file = new ZCodexFile + { + ResinMetadataSettings = new ZCodexFile.ResinMetadata + { + MaterialId = 2, + Material = MaterialName, + AdditionalSupportLayerTime = 0, + BottomLayersNumber = InitialLayerCount, + BottomLayersTime = (uint)(InitialExposureTime * 1000), + LayerTime = (uint)(LayerExposureTime * 1000), + DisableSettingsChanges = false, + LayerThickness = LayerHeight, + PrintTime = (uint)PrintTime, + TotalLayersCount = LayerCount, + TotalMaterialVolumeUsed = UsedMaterial, + TotalMaterialWeightUsed = UsedMaterial, + }, + UserSettings = new ZCodexFile.UserSettingsdata + { + Printer = MachineName, + BottomLayersCount = InitialLayerCount, + PrintTime = $"{ts.Hours}h {ts.Minutes}m", + LayerExposureTime = (uint)(LayerExposureTime * 1000), + BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), + MaterialId = 2, + LayerThickness = $"{LayerHeight} mm", + AntiAliasing = 0, + CrossSupportEnabled = 1, + ExposureOffTime = (uint) HeaderSettings.LayerOffTime, + HollowEnabled = 0, + HollowThickness = 0, + InfillDensity = 0, + IsAdvanced = 0, + MaterialType = MaterialName, + MaterialVolume = UsedMaterial, + MaxLayer = LayerCount - 1, + ModelLiftEnabled = 0, + ModelLiftHeight = 0, + RaftEnabled = 0, + RaftHeight = 0, + RaftOffset = 0, + SupportAdditionalExposureEnabled = 0, + SupportAdditionalExposureTime = 0, + XCorrection = 0, + YCorrection = 0, + ZLiftDistance = PrintParametersSettings.LiftHeight, + ZLiftFeedRate = PrintParametersSettings.LiftingSpeed, + ZLiftRetractRate = PrintParametersSettings.RetractSpeed, + }, + ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata + { + PrintTime = (uint)PrintTime, + PrinterName = MachineName, + Materials = new List<ZCodexFile.ZCodeMetadata.MaterialsData> + { + new ZCodexFile.ZCodeMetadata.MaterialsData + { + Name = MaterialName, + ExtruderType = "MAIN", + Id = 0, + Usage = 0, + Temperature = 0 + } + }, + }, + LayerManager = LayerManager + }; + + float usedMaterial = UsedMaterial / LayerCount; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData + { + Layer = layerIndex, + UsedMaterialVolume = usedMaterial + }); + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + return true; + } + */ + + if (to == typeof(CWSFile)) + { + CWSFile defaultFormat = (CWSFile)FindByType(typeof(CWSFile)); + CWSFile file = new CWSFile { LayerManager = LayerManager }; + + //file.SliceSettings.Xppm = file.OutputSettings.PixPermmX = defaultFormat.OutputSettings. + //file.SliceSettings.Yppm = file.OutputSettings.PixPermmY = (float)Math.Round(ResolutionY / HeaderSettings.BedSizeY, 3); + file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort)ResolutionX; + file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort)ResolutionY; + file.SliceSettings.Thickness = file.OutputSettings.LayerThickness = LayerHeight; + file.SliceSettings.LayersNum = file.OutputSettings.LayersNum = LayerCount; + file.SliceSettings.HeadLayersNum = file.OutputSettings.NumberBottomLayers = InitialLayerCount; + file.SliceSettings.LayersExpoMs = file.OutputSettings.LayerTime = (uint)LayerExposureTime * 1000; + file.SliceSettings.HeadLayersExpoMs = file.OutputSettings.BottomLayersTime = (uint)InitialExposureTime * 1000; + file.SliceSettings.WaitBeforeExpoMs = (uint)(HeaderSettings.LayerOffTime * 1000); + file.SliceSettings.LiftDistance = file.OutputSettings.LiftDistance = LiftHeight; + file.SliceSettings.LiftUpSpeed = file.OutputSettings.ZLiftFeedRate = LiftSpeed; + file.SliceSettings.LiftDownSpeed = file.OutputSettings.ZLiftRetractRate = RetractSpeed; + file.SliceSettings.LiftWhenFinished = defaultFormat.SliceSettings.LiftWhenFinished; + + file.OutputSettings.BlankingLayerTime = (uint)(HeaderSettings.LayerOffTime * 1000); + //file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.OutlineWidthInset = 0; + //file.OutputSettings.OutlineWidthOutset = 0; + file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.TiltValue = 0; + //file.OutputSettings.UseMainliftGCodeTab = false; + //file.OutputSettings.AntiAliasing = 0; + //file.OutputSettings.AntiAliasingValue = 0; + //file.OutputSettings.FlipX = HeaderSettings. != 0; + //file.OutputSettings.FlipY = file.OutputSettings.FlipX; + file.OutputSettings.AntiAliasingValue = ValidateAntiAliasingLevel(); + file.OutputSettings.AntiAliasing = file.OutputSettings.AntiAliasingValue > 1; + + file.Encode(fileFullPath, progress); + + return true; + } + + return false; + } + #endregion + } +} |