/* * GNU AFFERO GENERAL PUBLIC LICENSE * Version 3, 19 November 2007 * Copyright (C) 2007 Free Software Foundation, Inc. * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ using System; using System.Collections.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; namespace UVtools.Core { 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; /// /// Gets the file mark placeholder /// Fixed to "ANYCUBIC" /// [FieldOrder(0)] [FieldLength(MarkSize)] public string Mark { get => _mark; set => _mark = value.TrimEnd('\0'); } /// /// Gets the file format version /// [FieldOrder(1)] public uint Version { get; set; } = 1; /// /// Gets the area num /// [FieldOrder(2)] public uint AreaNum { get; set; } = 4; /// /// Gets the header start address /// [FieldOrder(3)] public uint HeaderAddress { get; set; } [FieldOrder(4)] public uint Offset1 { get; set; } /// /// Gets the preview start offset /// [FieldOrder(5)] public uint PreviewAddress { get; set; } [FieldOrder(6)] public uint Offset2 { get; set; } /// /// Gets the layer definition start address /// [FieldOrder(7)] public uint LayerDefinitionAddress { get; set; } [FieldOrder(8)] public uint Offset3 { get; set; } /// /// Gets layer image start address /// [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; /// /// Gets the section mark placeholder /// [FieldOrder(0)] [FieldLength(MarkSize)] public string Mark { get => _mark; set => _mark = value.TrimEnd('\0'); } /// /// Gets the length of this section /// [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; /// /// Gets the lift speed in mm/s /// [FieldOrder(7)] public float LiftSpeed { get; set; } = 3; // mm/s /// /// Gets the retract speed in mm/s /// [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 /// /// 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. /// public class Preview { public const string SectionMark = "PREVIEW"; [Ignore] public Section Section { get; set; } /// /// Gets the image width, in pixels. /// [FieldOrder(1)] public uint Width { get; set; } = 224; /// /// Gets the resolution of the image, in dpi. /// [FieldOrder(2)] public uint Resolution { get; set; } = 42; /// /// Gets the image height, in pixels. /// [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(); 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(); Preview preview = new Preview { Width = (uint) image.Width, Height = (uint) image.Height, Resolution = 42, Data = new byte[span.Length * 2] }; int pixel = 0; for (int i = 0; i < span.Length; i++) { // BGR int b = span[pixel++] >> 3; int g = span[pixel++] >> 2; int r = span[pixel++] >> 3; ushort color = (ushort) ((r << 11) | (g << 5) | (b << 0)); preview.Data[i * 2] = (byte) color; preview.Data[i * 2 + 1] = (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 { /// /// Gets the layer image offset to encoded layer data, and its length in bytes. /// [FieldOrder(0)] public uint DataAddress { get; set; } /// /// Gets the layer image length in bytes. /// [FieldOrder(1)] public uint DataLength { get; set; } [FieldOrder(2)] public float LiftHeight { get; set; } [FieldOrder(3)] public float LiftSpeed { get; set; } /// /// Gets the exposure time for this layer, in seconds. /// [FieldOrder(4)] public float LayerExposure { get; set; } /// /// Gets the build platform Z position for this layer, measured in millimeters. /// [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; LayerExposure = layerIndex < Parent.InitialLayerCount ? Parent.HeaderSettings.BottomExposureSeconds : Parent.HeaderSettings.LayerExposureTime; LayerPositionZ = Parent.GetHeightFromLayer(layerIndex); } 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(); 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 rawData = new List(); var span = image.GetPixelSpan(); 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(); 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 rawData = new List(); var span = image.GetPixelSpan(); 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 LayersHash { get; } = new Dictionary(); 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(ZCodexFile), }; 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) { base.Encode(fileFullPath); 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 => { LayerData layer = new LayerData(this, (uint) layerIndex); layer.Encode(this[layerIndex].LayerMat); LayersDefinition.Layers[layerIndex] = layer; }); 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); foreach (var layer in LayersDefinition.Layers) { 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); } // Rewind outputFile.Seek(0, SeekOrigin.Begin); Helpers.SerializeWriteFileStream(outputFile, FileMarkSettings); } } public override void Decode(string fileFullPath) { base.Decode(fileFullPath); var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read); //HeaderSettings = Helpers.ByteToType(InputFile); //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); FileMarkSettings = Helpers.Deserialize(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
(inputFile); //Debug.Write("SectionHeader -> "); //Debug.WriteLine(sectionHeader); var section = Helpers.Deserialize
(inputFile); HeaderSettings = Helpers.Deserialize
(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
(inputFile); PreviewSettings = Helpers.Deserialize(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
(inputFile); LayersDefinition = Helpers.Deserialize(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(inputFile); LayersDefinition[i].Parent = this; } 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); /*if (LayerFormat == LayerRleFormat.PW0) { var crcBytes = new byte[2]; inputFile.Read(crcBytes, 0, 2); ushort crcExpected = BitConverter.ToUInt16(crcBytes, 0); ushort crcEncodedRle = LayersDefinition.Layers[i].CRCEncodedRle(); if (crcExpected != crcEncodedRle) { Debug.WriteLine($"Error: Checksum expected {crcExpected}, got {crcEncodedRle}"); } }*/ } Parallel.For(0, LayerCount, layerIndex => { using (var image = LayersDefinition[(uint) layerIndex].Decode()) { this[layerIndex] = new Layer((uint) layerIndex, image); } }); } 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 LayersDefinition[layerIndex].LayerExposure = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomExposureSeconds : HeaderSettings.LayerExposureTime; } } if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) { HeaderSettings.BottomLayersCount = HeaderSettings.BottomLayersCount = value.Convert(); UpdateLayers(); return true; } if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) { HeaderSettings.BottomExposureSeconds = value.Convert(); UpdateLayers(); return true; } if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) { HeaderSettings.LayerExposureTime = value.Convert(); UpdateLayers(); return true; } if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) { HeaderSettings.LayerOffTime = value.Convert(); UpdateLayers(); return true; } if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) { HeaderSettings.LiftHeight = value.Convert(); UpdateLayers(); return true; } if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) { HeaderSettings.LiftSpeed = value.Convert() / 60f; UpdateLayers(); return true; } if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) { HeaderSettings.RetractSpeed = value.Convert() / 60f; UpdateLayers(); return true; } return false; } public override void SaveAs(string filePath = null) { if (LayerManager.IsModified) { if (!string.IsNullOrEmpty(filePath)) { FileFullPath = filePath; } Encode(FileFullPath); 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]); } outputFile.Close(); } //Decode(FileFullPath); } public override bool Convert(Type to, string fileFullPath) { /*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); 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 { 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); return true; } */ return false; } #endregion } }