From 26e4810d6e1831ca86527156b793dde6d31b9ee5 Mon Sep 17 00:00:00 2001 From: aspadm Date: Sat, 31 Dec 2022 02:46:34 +0300 Subject: [PATCH 01/12] FileFormats: Add Anet N4 support Anet N4 is LCD printer similar to Anycubic Photon (same display parameters). The printer uses modified B9Creator job file (.b9j) with added preview image and print parameters. However, most of options are use integers, so it is impossible to set fractions of second for exposure. Format does not support anti-aliasing and curing/light offset delay. --- UVtools.Core/FileFormats/AnetN4File.cs | 552 +++++++++++++++++++++++++ UVtools.Core/FileFormats/FileFormat.cs | 1 + documentation/UVtools.Core.xml | 7 + 3 files changed, 560 insertions(+) create mode 100644 UVtools.Core/FileFormats/AnetN4File.cs diff --git a/UVtools.Core/FileFormats/AnetN4File.cs b/UVtools.Core/FileFormats/AnetN4File.cs new file mode 100644 index 0000000..787efbe --- /dev/null +++ b/UVtools.Core/FileFormats/AnetN4File.cs @@ -0,0 +1,552 @@ +/* + * 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 BinarySerialization; +using Emgu.CV; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Threading.Tasks; +using UVtools.Core.Converters; +using UVtools.Core.Extensions; +using UVtools.Core.Layers; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats; + +/// +/// This file format is based on B9Creator job file (.b9j) with defined version = 3 +/// and added several new fields (as example, preview image). +/// Some of the format features are not recommended to use (BaseLayersCount and FilledBaseLayersCount). +/// +public class AnetN4File : FileFormat +{ + #region Constants + + public const ushort RESOLUTION_X = 1440; + public const ushort RESOLUTION_Y = 2560; + + public const float DISPLAY_WIDTH = 68.04f; + public const float DISPLAY_HEIGHT = 120.96f; + public const float MACHINE_Z = 135f; + + #endregion + + #region Members + + private uint _resolutionX = RESOLUTION_X; + private uint _resolutionY = RESOLUTION_Y; + + // Printer uses incorrect BMP header for preview image so we need to use it as-is instead of generating. + private byte[] _bmpHeader = { 66, 77, 162, 4, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 40, 0, 0, 0, 4, 1, 0, 0, 140, 0, 0, 0, 1, 0, 16, 0, 3, 0, 0, 0, 96, 4, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 224, 7, 0, 0, 31, 0, 0, 0 }; + + #endregion + + #region Sub Classes + + #region Header + + public class Header + { + [FieldOrder(0)][FieldEndianness(Endianness.Big)] public int VersionLen { get; set; } + [FieldOrder(1)][FieldEncoding("UTF-16BE")][FieldLength(nameof(VersionLen))][SerializeAs(SerializedType.SizedString)] public string? Version { get; set; } = "3"; + [FieldOrder(2)][FieldEndianness(Endianness.Big)] public int NameLength { get; set; } + [FieldOrder(3)][FieldEncoding("UTF-16BE")][FieldLength(nameof(NameLength))][SerializeAs(SerializedType.SizedString)] public string? Name { get; set; } + [FieldOrder(4)][FieldEndianness(Endianness.Big)] public int DescriptionLength { get; set; } + [FieldOrder(5)][FieldEncoding("UTF-16BE")][FieldLength(nameof(DescriptionLength))][SerializeAs(SerializedType.SizedString)] public string? Description { get; set; } + [FieldOrder(6)][FieldEndianness(Endianness.Big)] public double XYPixelSize { get; set; } = 0.04725; // mm + [FieldOrder(7)][FieldEndianness(Endianness.Big)] public double LayerHeight { get; set; } // mm; from 0.03 to 0.08 + [FieldOrder(8)][FieldEndianness(Endianness.Big)] public uint BaseLayersCount { get; set; } = 0; // Number of extent filled additional first layers; do not use! + [FieldOrder(9)][FieldEndianness(Endianness.Big)] public uint FilledBaseLayersCount { get; set; } = 0; // Number of fully filled first layers inside BaseLayersCount; do not use! + [FieldOrder(10)][FieldEndianness(Endianness.Big)] public uint ExposureSeconds { get; set; } // from 3 to 25 + [FieldOrder(11)][FieldEndianness(Endianness.Big)] public uint BottomExposureSeconds { get; set; } // from 60 to 120 + [FieldOrder(12)][FieldEndianness(Endianness.Big)] public uint BottomLayerCount { get; set; } // from 2 to 10 + [FieldOrder(13)][FieldEndianness(Endianness.Big)] public uint LiftSpeed { get; set; } = (uint)Math.Ceiling(SpeedConverter.Convert(DefaultLiftSpeed, CoreSpeedUnit, SpeedUnit.MillimetersPerSecond)); // mm/s, from 1 to 10 + [FieldOrder(14)][FieldEndianness(Endianness.Big)] public uint LiftHeight { get; set; } = (uint)DefaultLiftHeight; // mm, from 3 to 10 + + [FieldOrder(15)][FieldEndianness(Endianness.Big)] public uint PreviewResolutionX { get; set; } = 260; + [FieldOrder(16)][FieldEndianness(Endianness.Big)] public uint PreviewResolutionY { get; set; } = 140; + [FieldOrder(17)][FieldEndianness(Endianness.Big)] public uint PreviewSize { get; set; } + [FieldOrder(18)][FieldEndianness(Endianness.Big)][FieldLength(nameof(PreviewSize))] public byte[]? PreviewContent { get; set; } // BMP image, BGR565 + [FieldOrder(19)][FieldEndianness(Endianness.Big)] public double VolumeMicroL { get; set; } // µl + [FieldOrder(20)][FieldEndianness(Endianness.Big)] public int EncodedPrintTime { get; set; } = 0; // s; for unknown reason always broken in original slicer + [FieldOrder(21)][FieldEndianness(Endianness.Big)] public uint LayersCount { get; set; } // Not include BaseLayers + + public override string ToString() + { + return $"{nameof(Version)}: {Version}, {nameof(Name)}: {Name}, {nameof(Description)}: {Description}, {nameof(XYPixelSize)}: {XYPixelSize}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(ExposureSeconds)}: {ExposureSeconds}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(VolumeMicroL)}: {VolumeMicroL}, {nameof(PreviewResolutionX)}: {PreviewResolutionX}, {nameof(PreviewResolutionY)}: {PreviewResolutionY}, {nameof(LayersCount)}: {LayersCount}"; + } + } + + #endregion + + #region LayerDef + + public class LayerDef + { + [FieldOrder(0)][FieldEndianness(Endianness.Big)] public uint WhitePixelsCount { get; set; } + // White pixels region (border including corner pixels) + [FieldOrder(1)][FieldEndianness(Endianness.Big)] public int XMin { get; set; } = 0; + [FieldOrder(2)][FieldEndianness(Endianness.Big)] public int YMin { get; set; } = 0; + [FieldOrder(3)][FieldEndianness(Endianness.Big)] public int XMax { get; set; } = RESOLUTION_X - 1; + [FieldOrder(4)][FieldEndianness(Endianness.Big)] public int YMax { get; set; } = RESOLUTION_Y - 1; + [FieldOrder(5)][FieldEndianness(Endianness.Big)] public uint BitsCount { get; set; } + [Ignore] + public uint RleBytesCount + { + get => (BitsCount + 7) >> 3; + set => BitsCount = value << 3; + } + + [Ignore] public byte[] EncodedRle { get; set; } = null!; + + public LayerDef() + { + } + + public LayerDef(Mat mat) + { + XMax = mat.Width - 1; + YMax = mat.Height - 1; + } + + public override string ToString() + { + return $"{nameof(WhitePixelsCount)}: {WhitePixelsCount}, {nameof(XMin)}: {XMin}, {nameof(YMin)}: {YMin}, {nameof(XMax)}: {XMax}, {nameof(YMax)}: {YMax}, {nameof(BitsCount)}: {BitsCount}, {nameof(RleBytesCount)}: {RleBytesCount}, {nameof(EncodedRle)}: {EncodedRle?.Length}"; + } + + public unsafe byte[] Encode(Mat mat) + { + uint computeRepeatsSize(uint repeats) + { + return (uint)Math.Ceiling(Math.Log2(repeats)); + } + + void setBits(List data, uint pos, uint value, uint count = 1) + { + if (data.Count * 8 < pos + count) + { + data.AddRange(new byte[(pos + count + 7 - data.Count * 8) / 8]); + } + + for (int off = (int)(pos + count - 1); off + 1 > pos; --off) + { + byte tmp = data[(int)off / 8]; + byte mask = (byte)(1 << ((int)off % 8)); + data[(int)off / 8] = ((int)value & 1) == 1 ? (byte)(tmp | mask) : (byte)(tmp & ~mask); + value >>= 1; + } + } + + if (mat.Width != RESOLUTION_X || mat.Height != RESOLUTION_Y) + { + throw new ArgumentException($"Anet N4 support only {RESOLUTION_X}x{RESOLUTION_Y} image, got {mat.Width}x{mat.Height}"); + } + + Rectangle rec = CvInvoke.BoundingRectangle(mat); + XMin = rec.Left; + XMax = rec.Right - 1; + YMin = rec.Top; + YMax = rec.Bottom - 1; + + WhitePixelsCount = 0; + + uint singleColorLenght = 0; + uint compressedPos = 33; + + List rawData = new(); + var spanMat = mat.GetBytePointer(); + + bool isWhitePrev = spanMat[0] > 127; + + setBits(rawData, 0, (uint)mat.Width, 16); + setBits(rawData, 16, (uint)mat.Height, 16); + setBits(rawData, 32, isWhitePrev ? 1u : 0u); + + for (int pixel_ind = 0; pixel_ind < mat.GetLength(); ++pixel_ind) + { + bool isWhiteCurrent = spanMat[pixel_ind] > 127; // No AA + + if (isWhiteCurrent) + { + WhitePixelsCount++; + } + + if (isWhiteCurrent == isWhitePrev) + { + singleColorLenght++; + } + + if (isWhiteCurrent != isWhitePrev || pixel_ind == mat.GetLength() - 1) + { + isWhitePrev = isWhiteCurrent; + var repeatsSize = computeRepeatsSize(singleColorLenght); + setBits(rawData, compressedPos, repeatsSize, 5); + setBits(rawData, compressedPos + 5, singleColorLenght, repeatsSize + 1); + compressedPos += 6 + repeatsSize; + singleColorLenght = 1; + } + } + + EncodedRle = rawData.ToArray(); + RleBytesCount = (uint)EncodedRle.Length; + BitsCount = compressedPos; + + return EncodedRle; + } + + public Mat Decode(bool consumeRle = true) + { + uint GetBits(uint pos, uint count = 1) + { + if (pos + count > BitsCount) + { + throw new IndexOutOfRangeException($"Trying to read {count} bits from pos {pos}, but total size is {BitsCount} bits."); + } + + uint res = 0; + for (uint i = pos; i < pos + count; ++i) + { + res <<= 1; + if ((EncodedRle[i >> 0x3] & (byte)(0x1u << (int)(i & 0x7u))) != 0) + { + res |= 1; + } + } + return res; + } + + if ((BitsCount + 7) / 8 != EncodedRle.Length) + { + throw new IndexOutOfRangeException($"Incorrect RLE data size {EncodedRle.Length * 8}, except {BitsCount} bits."); + } + + uint RleWidth = GetBits(0, 16); + uint RleHeight = GetBits(16, 16); + + var mat = EmguExtensions.InitMat(new Size((int)RleWidth, (int)RleHeight)); + var imageLength = mat.GetLength(); + + byte brightness = (byte)((GetBits(32) == 1) ? 0xff : 0x0); + + int pixelPos = 0; + uint bitPos = 33; + while (pixelPos < imageLength) + { + uint keySize = GetBits(bitPos, 5); + uint stripSize = GetBits(bitPos + 5, keySize + 1); + bitPos += keySize + 6; + mat.FillSpan(ref pixelPos, (int)stripSize, brightness); + brightness = (byte)~brightness; + } + + if (consumeRle) + EncodedRle = null!; + + return mat; + } + } + #endregion + + #endregion + + #region Properties + + public Header HeaderSettings { get; protected internal set; } = new(); + public override FileFormatType FileType => FileFormatType.Binary; + + public override string ConvertMenuGroup => "Anet"; + + public override FileExtension[] FileExtensions { get; } = { + new(typeof(AnetN4File), "N4", "Anet N4"), + }; + + public override SpeedUnit FormatSpeedUnit => SpeedUnit.MillimetersPerSecond; + + public override byte AntiAliasing => 1; // Format does not support antialiasing + + public override PrintParameterModifier[]? PrintParameterModifiers { get; } = + { + PrintParameterModifier.ExposureTime, + PrintParameterModifier.BottomExposureTime, + PrintParameterModifier.BottomLayerCount, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.LiftHeight, + }; + + public override Size[]? ThumbnailsOriginalSize { get; } = { new(260, 140) }; + + public override uint ResolutionX + { + get => _resolutionX; + set + { + if (!RaiseAndSetIfChanged(ref _resolutionX, value)) return; + HeaderSettings.XYPixelSize = PixelSizeMax; + } + } + + public override uint ResolutionY + { + get => _resolutionY; + set + { + if (!RaiseAndSetIfChanged(ref _resolutionY, value)) return; + HeaderSettings.XYPixelSize = PixelSizeMax; + } + } + + public override float DisplayWidth + { + get => DISPLAY_WIDTH; + set { } + } + + public override float DisplayHeight + { + get => DISPLAY_HEIGHT; + set { } + } + + public override FlipDirection DisplayMirror + { + get => FlipDirection.Horizontally; + set { } + } + + public override float LayerHeight + { + get => (float)Layer.RoundHeight(HeaderSettings.LayerHeight); + set + { + HeaderSettings.LayerHeight = Layer.RoundHeight(value); + RaisePropertyChanged(); + } + } + + public override float MachineZ + { + get => MACHINE_Z; + set { } + } + + public override uint LayerCount + { + get => base.LayerCount; + set => base.LayerCount = HeaderSettings.LayersCount = LayerCount; + } + + public override ushort BottomLayerCount + { + get => (ushort)HeaderSettings.BottomLayerCount; + set => base.BottomLayerCount = (ushort)(HeaderSettings.BottomLayerCount = value); + } + + public override float BottomWaitTimeBeforeCure + { + get => 0; + set + { + SetBottomLightOffDelay(0); + base.BottomWaitTimeBeforeCure = 0; + } + } + + public override float WaitTimeBeforeCure + { + get => 0; + set + { + SetNormalLightOffDelay(0); + base.WaitTimeBeforeCure = 0; + } + } + + public override float BottomExposureTime + { + get => HeaderSettings.BottomExposureSeconds; + set => base.BottomExposureTime = HeaderSettings.BottomExposureSeconds = (uint)Math.Round(value); + } + + public override float ExposureTime + { + get => HeaderSettings.ExposureSeconds; + set => base.ExposureTime = HeaderSettings.ExposureSeconds = (uint)Math.Round(value); + } + + public override float BottomLiftHeight => LiftHeight; + + public override float LiftHeight + { + get => HeaderSettings.LiftHeight; + set => base.LiftHeight = HeaderSettings.LiftHeight = (uint)Math.Round(value); + } + + public override float BottomLiftSpeed => LiftSpeed; + + public override float LiftSpeed + { + get => SpeedConverter.Convert(HeaderSettings.LiftSpeed, SpeedUnit.MillimetersPerSecond, CoreSpeedUnit); + set + { + HeaderSettings.LiftSpeed = (uint)Math.Round(SpeedConverter.Convert(value, CoreSpeedUnit, SpeedUnit.MillimetersPerSecond)); + base.LiftSpeed = SpeedConverter.Convert(HeaderSettings.LiftSpeed, SpeedUnit.MillimetersPerSecond, CoreSpeedUnit); + } + } + + public override float BottomRetractSpeed => LiftSpeed; + + public override float RetractSpeed => LiftSpeed; + + public override float PrintTime + { + get => base.PrintTime; + set + { + base.PrintTime = value; + HeaderSettings.EncodedPrintTime = (int)base.PrintTime; + } + } + + public override float MaterialMilliliters + { + get => base.MaterialMilliliters; + set + { + base.MaterialMilliliters = value; + HeaderSettings.VolumeMicroL = base.MaterialMilliliters * 1000.0; + } + } + + public override string MachineName => "Anet N4"; + + public override object[] Configs => new object[] { HeaderSettings }; + + #endregion + + #region Constructors + public AnetN4File() + { + } + #endregion + + #region Methods + + protected override void EncodeInternally(OperationProgress progress) + { + using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Create, FileAccess.Write); + + byte[] previewBuffer = new byte[72866]; + _bmpHeader.CopyTo(previewBuffer, 0); + EncodeImage(DATATYPE_BGR565, Thumbnails[0]!).CopyTo(previewBuffer, 66); + HeaderSettings.PreviewContent = previewBuffer; + + outputFile.WriteSerialize(HeaderSettings); + + progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); + var layerData = new LayerDef[LayerCount]; + + foreach (var batch in BatchLayersIndexes()) + { + Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex => + { + using (var mat = this[layerIndex].LayerMat) + { + layerData[layerIndex] = new LayerDef(mat); + layerData[layerIndex].Encode(mat); + } + progress.LockAndIncrement(); + }); + + foreach (var layerIndex in batch) + { + progress.ThrowIfCancellationRequested(); + + outputFile.WriteSerialize(layerData[layerIndex]); + outputFile.WriteBytes(layerData[layerIndex].EncodedRle); + + layerData[layerIndex].EncodedRle = null!; // Free + } + } + + outputFile.WriteSerialize(new UInt32()); // Supports count, always 0 + + Debug.WriteLine("Encode Results:"); + Debug.WriteLine(HeaderSettings); + Debug.WriteLine("-End-"); + } + + protected override void DecodeInternally(OperationProgress progress) + { + using var inputFile = new FileStream(FileFullPath!, FileMode.Open, FileAccess.Read); + HeaderSettings = Helpers.Deserialize
(inputFile); + if (HeaderSettings.Version == null || !HeaderSettings.Version.Equals("3")) + { + throw new FileLoadException("Not a valid N4 file: Version doesn't match", FileFullPath); + } + + if (HeaderSettings.PreviewSize != 72866 || HeaderSettings.PreviewContent == null) + { + throw new FileLoadException("Not a valid N4 file: incorrect preview format", FileFullPath); + } + + Thumbnails[0] = DecodeImage(DATATYPE_BGR565, HeaderSettings.PreviewContent[66..], ThumbnailsOriginalSize![0]); + + Debug.WriteLine(HeaderSettings); + + Init(HeaderSettings.LayersCount, DecodeType == FileDecodeType.Partial); + var layersDefinitions = new LayerDef[HeaderSettings.LayersCount]; + + progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + foreach (var batch in BatchLayersIndexes()) + { + foreach (var layerIndex in batch) + { + progress.ThrowIfCancellationRequested(); + + var layerDef = Helpers.Deserialize(inputFile); + layersDefinitions[layerIndex] = layerDef; + + if (DecodeType == FileDecodeType.Full) + { + layerDef.EncodedRle = inputFile.ReadBytes(layerDef.RleBytesCount); + } + else + { + inputFile.Seek(layerDef.RleBytesCount, SeekOrigin.Current); + } + + Debug.Write($"LAYER {layerIndex} -> "); + Debug.WriteLine(layerDef); + } + + if (DecodeType == FileDecodeType.Full) + { + Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex => + { + using var mat = layersDefinitions[layerIndex].Decode(); + _layers[layerIndex] = new Layer((uint)layerIndex, mat, this); + progress.LockAndIncrement(); + }); + } + } + + RebuildLayersProperties(); + } + + protected override void PartialSaveInternally(OperationProgress progress) + { + using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Open, FileAccess.Write); + outputFile.Seek(0, SeekOrigin.Begin); + outputFile.WriteSerialize(HeaderSettings); + } + + #endregion +} diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 8185462..37d4f6d 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -377,6 +377,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable Created + + + This file format is based on B9Creator job file (.b9j) with defined version = 3 + and added several new fields (as example, preview image). + Some of the format features are not recommended to use (BaseLayersCount and FilledBaseLayersCount). + + Gets a magic number identifying the file type. From da560dd684c00e8cd7128a4aad3bb984f2256ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 05:09:37 +0000 Subject: [PATCH 02/12] Add Anet N4 to machine presets --- UVtools.Core/Printer/Machine.cs | 2 ++ UVtools.Core/Printer/PrinterBrand.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/UVtools.Core/Printer/Machine.cs b/UVtools.Core/Printer/Machine.cs index 2eadd17..1cc2b88 100644 --- a/UVtools.Core/Printer/Machine.cs +++ b/UVtools.Core/Printer/Machine.cs @@ -212,6 +212,8 @@ namespace UVtools.Core.Printer new(PrinterBrand.Creality, "CT133 Pro", "CT133PRO", 3840, 2160, 293.76f, 165.24f, 300), new(PrinterBrand.Creality, "CT-005 Pro", "CT-005", 3840, 2400, 192, 120, 250),*/ + new(PrinterBrand.Anet, "Anet N4", "N4", 1440, 2560, 68.04f, 120.96f, 135f, FlipDirection.Horizontally), + new(PrinterBrand.AnyCubic, "AnyCubic Photon M3", "Photon M3", 4096, 2560, 163.84f, 102.40f, 180f, FlipDirection.Horizontally), new(PrinterBrand.AnyCubic, "AnyCubic Photon M3 Max", "Photon M3 Max", 6480, 3600, 298.08f, 165.60f, 300f, FlipDirection.Horizontally), new(PrinterBrand.AnyCubic, "AnyCubic Photon M3 Plus", "Photon M3 Plus", 5760, 3600, 198.15f, 123.84f, 245f, FlipDirection.Horizontally), diff --git a/UVtools.Core/Printer/PrinterBrand.cs b/UVtools.Core/Printer/PrinterBrand.cs index 3bfd459..92056f3 100644 --- a/UVtools.Core/Printer/PrinterBrand.cs +++ b/UVtools.Core/Printer/PrinterBrand.cs @@ -10,6 +10,7 @@ namespace UVtools.Core.Printer public enum PrinterBrand : byte { Generic, + Anet, AnyCubic, Creality, Elegoo, From 2f4bc251af64e53d7d62ebb10eda7b945ae53902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 17:45:47 +0000 Subject: [PATCH 03/12] Add n4.bt (010 template) --- Scripts/010 Editor/n4.bt | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Scripts/010 Editor/n4.bt diff --git a/Scripts/010 Editor/n4.bt b/Scripts/010 Editor/n4.bt new file mode 100644 index 0000000..7bc3bbf --- /dev/null +++ b/Scripts/010 Editor/n4.bt @@ -0,0 +1,57 @@ +//------------------------------------------------ +//--- 010 Editor v8.0.1 Binary Template +// +// File: Anet N4 +// Authors: Tiago Conceição +//------------------------------------------------ + +BigEndian(); + +typedef struct() { + uint32 WhitePixelsCount ; // 44944 + uint32 XMin ; + uint32 YMin ; + uint32 XMax ; + uint32 YMax ; + uint32 BitsCount ; + ubyte LayerRLE[(BitsCount + 7) >> 3] ; +} layerData; + +struct HEADER { + uint32 VersionLen ; // 2 + wchar_t Version[VersionLen/2] ; // 3 + uint32 NameLength ; + wchar_t FileName[NameLength/2] ; // File name without extension + uint32 DescriptionLength ; + wchar_t Description[DescriptionLength/2] ; // File name without extension + + double XYPixelSize ; // mm + double LayerHeight ; // mm; from 0.03 to 0.08 + + uint32 BaseLayersCount ; // Number of extent filled additional first layers; do not use! + uint32 FilledBaseLayersCount ; // Number of fully filled first layers inside BaseLayersCount; do not use! + + uint ExposureTime ; // from 3 to 25 + uint BottomExposureTime ; // from 60 to 120 + uint32 BottomLayerCount ; // from 2 to 10 + uint32 LiftSpeed ; // mm/s, from 1 to 10 + uint32 LiftHeight ; // mm, from 3 to 10 + + uint32 PreviewResolutionX ; // 260 + uint32 PreviewResolutionY ; // 140 + uint32 PreviewSize ; // 72866 + ubyte PreviewContent[PreviewSize] ; // BMP image, BGR565 + double VolumeMicroL ; // µl + uint32 EncodedPrintTime ; // s; for unknown reason always broken in original slicer + uint32 LayerCount ; +} header; + + +struct LAYERS { + local int i; + for( i = 0; i < header.LayerCount; i++ ){ + layerData lD(); + } +} layers; + +uint32 SupportsCount ; // Supports count, always 0 \ No newline at end of file From d5967947a29de49e9f6eecfbc3499c3fd34dff17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 17:53:22 +0000 Subject: [PATCH 04/12] Create Anet N4.ini --- PrusaSlicer/printer/Anet N4.ini | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 PrusaSlicer/printer/Anet N4.ini diff --git a/PrusaSlicer/printer/Anet N4.ini b/PrusaSlicer/printer/Anet N4.ini new file mode 100644 index 0000000..e3d696f --- /dev/null +++ b/PrusaSlicer/printer/Anet N4.ini @@ -0,0 +1,42 @@ +# generated by PrusaSlicer 2.5.0+win64 on 2022-12-31 at 17:52:32 UTC +absolute_correction = 0 +area_fill = 50 +bed_custom_model = +bed_custom_texture = +bed_shape = 0x0,68.04x0,68.04x120.96,0x120.96 +default_sla_material_profile = Prusa Orange Tough 0.05 +default_sla_print_profile = 0.05 Normal +display_height = 120.96 +display_mirror_x = 1 +display_mirror_y = 0 +display_orientation = landscape +display_pixels_x = 1440 +display_pixels_y = 2560 +display_width = 68.04 +elefant_foot_compensation = 0.2 +elefant_foot_min_width = 0.2 +fast_tilt_time = 5 +gamma_correction = 1 +high_viscosity_tilt_time = 10 +host_type = octoprint +inherits = Original Prusa SL1 +max_exposure_time = 120 +max_initial_exposure_time = 300 +max_print_height = 135 +min_exposure_time = 1 +min_initial_exposure_time = 1 +print_host = +printer_model = SL1 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\nPRINTER_VENDOR_ANET\nPRINTER_MODEL_N4\nFILEFORMAT_N4\n\nSTART_CUSTOM_VALUES\nLiftHeight_6\nLiftSpeed_60\nEND_CUSTOM_VALUES +printer_settings_id = +printer_technology = SLA +printer_variant = default +printer_vendor = +printhost_apikey = +printhost_cafile = +relative_correction = 1,1 +relative_correction_x = 1 +relative_correction_y = 1 +relative_correction_z = 1 +slow_tilt_time = 8 +thumbnails = 400x400,800x480 From 614f15991676e47c9085853871bac48c70f6b9ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 18:02:08 +0000 Subject: [PATCH 05/12] Update n4.bt --- Scripts/010 Editor/n4.bt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Scripts/010 Editor/n4.bt b/Scripts/010 Editor/n4.bt index 7bc3bbf..6268843 100644 --- a/Scripts/010 Editor/n4.bt +++ b/Scripts/010 Editor/n4.bt @@ -8,11 +8,11 @@ BigEndian(); typedef struct() { - uint32 WhitePixelsCount ; // 44944 - uint32 XMin ; - uint32 YMin ; - uint32 XMax ; - uint32 YMax ; + uint32 WhitePixelsCount ; // White pixels region (border including corner pixels) + uint32 XMin ; // Bounding rectangle X + uint32 YMin ; // Bounding rectangle Y + uint32 XMax ; // Bounding rectangle Right + uint32 YMax ; // Bounding rectangle Bottom uint32 BitsCount ; ubyte LayerRLE[(BitsCount + 7) >> 3] ; } layerData; From c648441a520267e658202b074183e74604eba398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 18:44:32 +0000 Subject: [PATCH 06/12] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8ea3d67..6271a78 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ But also, I need victims for test subject. Proceed at your own risk! - LGS120 (Longer Orange 120) - LGS4K (Longer Orange 4K & mono) - Flashforge SVGX +- Anet N4 - ZIP (Generic / Phrozen Zip) - VDA.ZIP (Voxeldance Additive) - VDT (Voxeldance Tango) From fa788efebb06e8e74f1c77c4ce97c2abb138bb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 18:44:51 +0000 Subject: [PATCH 07/12] Reorder --- UVtools.Core/FileFormats/FileFormat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 37d4f6d..7cb90ef 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -377,7 +377,6 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable Date: Sat, 31 Dec 2022 19:08:28 +0000 Subject: [PATCH 08/12] Remove antialiasing from slices --- PrusaSlicer/printer/Anet N4.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PrusaSlicer/printer/Anet N4.ini b/PrusaSlicer/printer/Anet N4.ini index e3d696f..c657ea3 100644 --- a/PrusaSlicer/printer/Anet N4.ini +++ b/PrusaSlicer/printer/Anet N4.ini @@ -1,4 +1,4 @@ -# generated by PrusaSlicer 2.5.0+win64 on 2022-12-31 at 17:52:32 UTC +# generated by PrusaSlicer 2.5.0+win64 on 2022-12-31 at 19:07:52 UTC absolute_correction = 0 area_fill = 50 bed_custom_model = @@ -16,7 +16,7 @@ display_width = 68.04 elefant_foot_compensation = 0.2 elefant_foot_min_width = 0.2 fast_tilt_time = 5 -gamma_correction = 1 +gamma_correction = 0 high_viscosity_tilt_time = 10 host_type = octoprint inherits = Original Prusa SL1 From 61c9c2724d48dc3722376b11d98e029d481586e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 19:18:46 +0000 Subject: [PATCH 09/12] Re-sort order --- UVtools.Core/FileFormats/FileFormat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 7cb90ef..597b8bd 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -385,8 +385,8 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable Date: Sat, 31 Dec 2022 19:21:33 +0000 Subject: [PATCH 10/12] Refactorings --- UVtools.Core/FileFormats/AnetN4File.cs | 226 ++++++++++++------------- 1 file changed, 111 insertions(+), 115 deletions(-) diff --git a/UVtools.Core/FileFormats/AnetN4File.cs b/UVtools.Core/FileFormats/AnetN4File.cs index 787efbe..e5c4315 100644 --- a/UVtools.Core/FileFormats/AnetN4File.cs +++ b/UVtools.Core/FileFormats/AnetN4File.cs @@ -26,7 +26,7 @@ namespace UVtools.Core.FileFormats; /// and added several new fields (as example, preview image). /// Some of the format features are not recommended to use (BaseLayersCount and FilledBaseLayersCount). /// -public class AnetN4File : FileFormat +public sealed class AnetN4File : FileFormat { #region Constants @@ -37,6 +37,11 @@ public class AnetN4File : FileFormat public const float DISPLAY_HEIGHT = 120.96f; public const float MACHINE_Z = 135f; + /// + /// Printer uses incorrect BMP header for preview image so we need to use it as-is instead of generating. + /// + private static readonly byte[] BmpHeader = { 66, 77, 162, 4, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 40, 0, 0, 0, 4, 1, 0, 0, 140, 0, 0, 0, 1, 0, 16, 0, 3, 0, 0, 0, 96, 4, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 224, 7, 0, 0, 31, 0, 0, 0 }; + #endregion #region Members @@ -44,8 +49,8 @@ public class AnetN4File : FileFormat private uint _resolutionX = RESOLUTION_X; private uint _resolutionY = RESOLUTION_Y; - // Printer uses incorrect BMP header for preview image so we need to use it as-is instead of generating. - private byte[] _bmpHeader = { 66, 77, 162, 4, 0, 0, 0, 0, 0, 0, 66, 0, 0, 0, 40, 0, 0, 0, 4, 1, 0, 0, 140, 0, 0, 0, 1, 0, 16, 0, 3, 0, 0, 0, 96, 4, 0, 0, 18, 11, 0, 0, 18, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 248, 0, 0, 224, 7, 0, 0, 31, 0, 0, 0 }; + private float _displayWidth = DISPLAY_WIDTH; + private float _displayHeight = DISPLAY_HEIGHT; #endregion @@ -55,33 +60,33 @@ public class AnetN4File : FileFormat public class Header { - [FieldOrder(0)][FieldEndianness(Endianness.Big)] public int VersionLen { get; set; } - [FieldOrder(1)][FieldEncoding("UTF-16BE")][FieldLength(nameof(VersionLen))][SerializeAs(SerializedType.SizedString)] public string? Version { get; set; } = "3"; - [FieldOrder(2)][FieldEndianness(Endianness.Big)] public int NameLength { get; set; } - [FieldOrder(3)][FieldEncoding("UTF-16BE")][FieldLength(nameof(NameLength))][SerializeAs(SerializedType.SizedString)] public string? Name { get; set; } - [FieldOrder(4)][FieldEndianness(Endianness.Big)] public int DescriptionLength { get; set; } - [FieldOrder(5)][FieldEncoding("UTF-16BE")][FieldLength(nameof(DescriptionLength))][SerializeAs(SerializedType.SizedString)] public string? Description { get; set; } + [FieldOrder(0)][FieldEndianness(Endianness.Big)] public uint VersionLength { get; set; } = 2; + [FieldOrder(1)][FieldEncoding("UTF-16BE")][FieldLength(nameof(VersionLength))] public string Version { get; set; } = "3"; + [FieldOrder(2)][FieldEndianness(Endianness.Big)] public uint NameLength { get; set; } = (uint)About.SoftwareWithVersion.Length * 2; + [FieldOrder(3)][FieldEncoding("UTF-16BE")][FieldLength(nameof(NameLength))] public string Name { get; set; } = About.SoftwareWithVersion; + [FieldOrder(4)][FieldEndianness(Endianness.Big)] public uint DescriptionLength { get; set; } = (uint)About.SoftwareWithVersion.Length * 2; + [FieldOrder(5)][FieldEncoding("UTF-16BE")][FieldLength(nameof(DescriptionLength))] public string Description { get; set; } = About.SoftwareWithVersion; [FieldOrder(6)][FieldEndianness(Endianness.Big)] public double XYPixelSize { get; set; } = 0.04725; // mm [FieldOrder(7)][FieldEndianness(Endianness.Big)] public double LayerHeight { get; set; } // mm; from 0.03 to 0.08 - [FieldOrder(8)][FieldEndianness(Endianness.Big)] public uint BaseLayersCount { get; set; } = 0; // Number of extent filled additional first layers; do not use! - [FieldOrder(9)][FieldEndianness(Endianness.Big)] public uint FilledBaseLayersCount { get; set; } = 0; // Number of fully filled first layers inside BaseLayersCount; do not use! - [FieldOrder(10)][FieldEndianness(Endianness.Big)] public uint ExposureSeconds { get; set; } // from 3 to 25 - [FieldOrder(11)][FieldEndianness(Endianness.Big)] public uint BottomExposureSeconds { get; set; } // from 60 to 120 - [FieldOrder(12)][FieldEndianness(Endianness.Big)] public uint BottomLayerCount { get; set; } // from 2 to 10 + [FieldOrder(8)][FieldEndianness(Endianness.Big)] public uint BaseLayersCount { get; set; } // Number of extent filled additional first layers; do not use! + [FieldOrder(9)][FieldEndianness(Endianness.Big)] public uint FilledBaseLayersCount { get; set; } // Number of fully filled first layers inside BaseLayersCount; do not use! + [FieldOrder(10)][FieldEndianness(Endianness.Big)] public uint ExposureTime { get; set; } = 6; // from 3 to 25 + [FieldOrder(11)][FieldEndianness(Endianness.Big)] public uint BottomExposureTime { get; set; } = 90; // from 60 to 120 + [FieldOrder(12)][FieldEndianness(Endianness.Big)] public uint BottomLayerCount { get; set; } = DefaultBottomLayerCount; // from 2 to 10 [FieldOrder(13)][FieldEndianness(Endianness.Big)] public uint LiftSpeed { get; set; } = (uint)Math.Ceiling(SpeedConverter.Convert(DefaultLiftSpeed, CoreSpeedUnit, SpeedUnit.MillimetersPerSecond)); // mm/s, from 1 to 10 [FieldOrder(14)][FieldEndianness(Endianness.Big)] public uint LiftHeight { get; set; } = (uint)DefaultLiftHeight; // mm, from 3 to 10 [FieldOrder(15)][FieldEndianness(Endianness.Big)] public uint PreviewResolutionX { get; set; } = 260; [FieldOrder(16)][FieldEndianness(Endianness.Big)] public uint PreviewResolutionY { get; set; } = 140; - [FieldOrder(17)][FieldEndianness(Endianness.Big)] public uint PreviewSize { get; set; } - [FieldOrder(18)][FieldEndianness(Endianness.Big)][FieldLength(nameof(PreviewSize))] public byte[]? PreviewContent { get; set; } // BMP image, BGR565 + [FieldOrder(17)][FieldEndianness(Endianness.Big)] public uint PreviewSize { get; set; } = 72866; + [FieldOrder(18)][FieldEndianness(Endianness.Big)][FieldLength(nameof(PreviewSize))] public byte[] PreviewContent { get; set; } = Array.Empty(); // BMP image, BGR565 [FieldOrder(19)][FieldEndianness(Endianness.Big)] public double VolumeMicroL { get; set; } // µl - [FieldOrder(20)][FieldEndianness(Endianness.Big)] public int EncodedPrintTime { get; set; } = 0; // s; for unknown reason always broken in original slicer + [FieldOrder(20)][FieldEndianness(Endianness.Big)] public uint EncodedPrintTime { get; set; } // s; for unknown reason always broken in original slicer [FieldOrder(21)][FieldEndianness(Endianness.Big)] public uint LayersCount { get; set; } // Not include BaseLayers public override string ToString() { - return $"{nameof(Version)}: {Version}, {nameof(Name)}: {Name}, {nameof(Description)}: {Description}, {nameof(XYPixelSize)}: {XYPixelSize}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(ExposureSeconds)}: {ExposureSeconds}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(VolumeMicroL)}: {VolumeMicroL}, {nameof(PreviewResolutionX)}: {PreviewResolutionX}, {nameof(PreviewResolutionY)}: {PreviewResolutionY}, {nameof(LayersCount)}: {LayersCount}"; + return $"{nameof(VersionLength)}: {VersionLength}, {nameof(Version)}: {Version}, {nameof(NameLength)}: {NameLength}, {nameof(Name)}: {Name}, {nameof(DescriptionLength)}: {DescriptionLength}, {nameof(Description)}: {Description}, {nameof(XYPixelSize)}: {XYPixelSize}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(BaseLayersCount)}: {BaseLayersCount}, {nameof(FilledBaseLayersCount)}: {FilledBaseLayersCount}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(BottomExposureTime)}: {BottomExposureTime}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(PreviewResolutionX)}: {PreviewResolutionX}, {nameof(PreviewResolutionY)}: {PreviewResolutionY}, {nameof(PreviewSize)}: {PreviewSize}, {nameof(PreviewContent)}: {PreviewContent}, {nameof(VolumeMicroL)}: {VolumeMicroL}, {nameof(EncodedPrintTime)}: {EncodedPrintTime}, {nameof(LayersCount)}: {LayersCount}"; } } @@ -91,10 +96,12 @@ public class AnetN4File : FileFormat public class LayerDef { + /// + /// White pixels region (border including corner pixels) + /// [FieldOrder(0)][FieldEndianness(Endianness.Big)] public uint WhitePixelsCount { get; set; } - // White pixels region (border including corner pixels) - [FieldOrder(1)][FieldEndianness(Endianness.Big)] public int XMin { get; set; } = 0; - [FieldOrder(2)][FieldEndianness(Endianness.Big)] public int YMin { get; set; } = 0; + [FieldOrder(1)][FieldEndianness(Endianness.Big)] public int XMin { get; set; } + [FieldOrder(2)][FieldEndianness(Endianness.Big)] public int YMin { get; set; } [FieldOrder(3)][FieldEndianness(Endianness.Big)] public int XMax { get; set; } = RESOLUTION_X - 1; [FieldOrder(4)][FieldEndianness(Endianness.Big)] public int YMax { get; set; } = RESOLUTION_Y - 1; [FieldOrder(5)][FieldEndianness(Endianness.Big)] public uint BitsCount { get; set; } @@ -110,11 +117,14 @@ public class AnetN4File : FileFormat public LayerDef() { } - - public LayerDef(Mat mat) + + public void SetFrom(Layer layer) { - XMax = mat.Width - 1; - YMax = mat.Height - 1; + WhitePixelsCount = layer.NonZeroPixelCount; // To be re-set latter while encoding + XMin = layer.BoundingRectangle.X; + YMin = layer.BoundingRectangle.Y; + XMax = layer.BoundingRectangle.Right; + YMax = layer.BoundingRectangle.Bottom; } public override string ToString() @@ -122,14 +132,14 @@ public class AnetN4File : FileFormat return $"{nameof(WhitePixelsCount)}: {WhitePixelsCount}, {nameof(XMin)}: {XMin}, {nameof(YMin)}: {YMin}, {nameof(XMax)}: {XMax}, {nameof(YMax)}: {YMax}, {nameof(BitsCount)}: {BitsCount}, {nameof(RleBytesCount)}: {RleBytesCount}, {nameof(EncodedRle)}: {EncodedRle?.Length}"; } - public unsafe byte[] Encode(Mat mat) + public byte[] Encode(Mat mat) { - uint computeRepeatsSize(uint repeats) + uint ComputeRepeatsSize(uint repeats) { return (uint)Math.Ceiling(Math.Log2(repeats)); } - void setBits(List data, uint pos, uint value, uint count = 1) + void SetBits(List data, uint pos, uint value, uint count = 1) { if (data.Count * 8 < pos + count) { @@ -138,9 +148,9 @@ public class AnetN4File : FileFormat for (int off = (int)(pos + count - 1); off + 1 > pos; --off) { - byte tmp = data[(int)off / 8]; - byte mask = (byte)(1 << ((int)off % 8)); - data[(int)off / 8] = ((int)value & 1) == 1 ? (byte)(tmp | mask) : (byte)(tmp & ~mask); + byte tmp = data[off / 8]; + byte mask = (byte)(1 << (off % 8)); + data[off / 8] = ((int)value & 1) == 1 ? (byte)(tmp | mask) : (byte)(tmp & ~mask); value >>= 1; } } @@ -150,29 +160,22 @@ public class AnetN4File : FileFormat throw new ArgumentException($"Anet N4 support only {RESOLUTION_X}x{RESOLUTION_Y} image, got {mat.Width}x{mat.Height}"); } - Rectangle rec = CvInvoke.BoundingRectangle(mat); - XMin = rec.Left; - XMax = rec.Right - 1; - YMin = rec.Top; - YMax = rec.Bottom - 1; - WhitePixelsCount = 0; - - uint singleColorLenght = 0; + uint singleColorLength = 0; uint compressedPos = 33; - List rawData = new(); - var spanMat = mat.GetBytePointer(); + var rawData = new List(); + var spanMat = mat.GetDataByteSpan(); bool isWhitePrev = spanMat[0] > 127; - setBits(rawData, 0, (uint)mat.Width, 16); - setBits(rawData, 16, (uint)mat.Height, 16); - setBits(rawData, 32, isWhitePrev ? 1u : 0u); + SetBits(rawData, 0, (uint)mat.Width, 16); + SetBits(rawData, 16, (uint)mat.Height, 16); + SetBits(rawData, 32, isWhitePrev ? 1u : 0u); - for (int pixel_ind = 0; pixel_ind < mat.GetLength(); ++pixel_ind) + for (int i = 0; i < spanMat.Length; i++) { - bool isWhiteCurrent = spanMat[pixel_ind] > 127; // No AA + bool isWhiteCurrent = spanMat[i] > 127; // No AA if (isWhiteCurrent) { @@ -181,17 +184,17 @@ public class AnetN4File : FileFormat if (isWhiteCurrent == isWhitePrev) { - singleColorLenght++; + singleColorLength++; } - if (isWhiteCurrent != isWhitePrev || pixel_ind == mat.GetLength() - 1) + if (isWhiteCurrent != isWhitePrev || i == spanMat.Length - 1) { isWhitePrev = isWhiteCurrent; - var repeatsSize = computeRepeatsSize(singleColorLenght); - setBits(rawData, compressedPos, repeatsSize, 5); - setBits(rawData, compressedPos + 5, singleColorLenght, repeatsSize + 1); + var repeatsSize = ComputeRepeatsSize(singleColorLength); + SetBits(rawData, compressedPos, repeatsSize, 5); + SetBits(rawData, compressedPos + 5, singleColorLength, repeatsSize + 1); compressedPos += 6 + repeatsSize; - singleColorLenght = 1; + singleColorLength = 1; } } @@ -212,7 +215,7 @@ public class AnetN4File : FileFormat } uint res = 0; - for (uint i = pos; i < pos + count; ++i) + for (uint i = pos; i < pos + count; i++) { res <<= 1; if ((EncodedRle[i >> 0x3] & (byte)(0x1u << (int)(i & 0x7u))) != 0) @@ -228,10 +231,10 @@ public class AnetN4File : FileFormat throw new IndexOutOfRangeException($"Incorrect RLE data size {EncodedRle.Length * 8}, except {BitsCount} bits."); } - uint RleWidth = GetBits(0, 16); - uint RleHeight = GetBits(16, 16); + uint rleWidth = GetBits(0, 16); + uint rleHeight = GetBits(16, 16); - var mat = EmguExtensions.InitMat(new Size((int)RleWidth, (int)RleHeight)); + var mat = EmguExtensions.InitMat(new Size((int)rleWidth, (int)rleHeight)); var imageLength = mat.GetLength(); byte brightness = (byte)((GetBits(32) == 1) ? 0xff : 0x0); @@ -247,8 +250,7 @@ public class AnetN4File : FileFormat brightness = (byte)~brightness; } - if (consumeRle) - EncodedRle = null!; + if (consumeRle) EncodedRle = null!; return mat; } @@ -259,18 +261,16 @@ public class AnetN4File : FileFormat #region Properties - public Header HeaderSettings { get; protected internal set; } = new(); + public Header HeaderSettings { get; private set; } = new(); public override FileFormatType FileType => FileFormatType.Binary; - public override string ConvertMenuGroup => "Anet"; - public override FileExtension[] FileExtensions { get; } = { - new(typeof(AnetN4File), "N4", "Anet N4"), + new(typeof(AnetN4File), "n4", "Anet N4"), }; public override SpeedUnit FormatSpeedUnit => SpeedUnit.MillimetersPerSecond; - public override byte AntiAliasing => 1; // Format does not support antialiasing + public override byte AntiAliasing => 1; // Format does not support anti-aliasing public override PrintParameterModifier[]? PrintParameterModifiers { get; } = { @@ -289,6 +289,7 @@ public class AnetN4File : FileFormat set { if (!RaiseAndSetIfChanged(ref _resolutionX, value)) return; + if(value != RESOLUTION_X) throw new ArgumentException($"Anet N4 support only {RESOLUTION_X}x{RESOLUTION_Y} image, got {ResolutionX}x{ResolutionY}"); HeaderSettings.XYPixelSize = PixelSizeMax; } } @@ -299,20 +300,29 @@ public class AnetN4File : FileFormat set { if (!RaiseAndSetIfChanged(ref _resolutionY, value)) return; + if (value != RESOLUTION_Y) throw new ArgumentException($"Anet N4 support only {RESOLUTION_X}x{RESOLUTION_Y} image, got {ResolutionX}x{ResolutionY}"); HeaderSettings.XYPixelSize = PixelSizeMax; } } public override float DisplayWidth { - get => DISPLAY_WIDTH; - set { } + get => _displayWidth; + set + { + if (!RaiseAndSetIfChanged(ref _displayWidth, value)) return; + HeaderSettings.XYPixelSize = PixelSizeMax; + } } public override float DisplayHeight { - get => DISPLAY_HEIGHT; - set { } + get => _displayHeight; + set + { + if (!RaiseAndSetIfChanged(ref _displayHeight, value)) return; + HeaderSettings.XYPixelSize = PixelSizeMax; + } } public override FlipDirection DisplayMirror @@ -323,20 +333,14 @@ public class AnetN4File : FileFormat public override float LayerHeight { - get => (float)Layer.RoundHeight(HeaderSettings.LayerHeight); + get => Layer.RoundHeight((float)HeaderSettings.LayerHeight); set { - HeaderSettings.LayerHeight = Layer.RoundHeight(value); + HeaderSettings.LayerHeight = Layer.RoundHeight((double)value); RaisePropertyChanged(); } } - public override float MachineZ - { - get => MACHINE_Z; - set { } - } - public override uint LayerCount { get => base.LayerCount; @@ -349,36 +353,16 @@ public class AnetN4File : FileFormat set => base.BottomLayerCount = (ushort)(HeaderSettings.BottomLayerCount = value); } - public override float BottomWaitTimeBeforeCure - { - get => 0; - set - { - SetBottomLightOffDelay(0); - base.BottomWaitTimeBeforeCure = 0; - } - } - - public override float WaitTimeBeforeCure - { - get => 0; - set - { - SetNormalLightOffDelay(0); - base.WaitTimeBeforeCure = 0; - } - } - public override float BottomExposureTime { - get => HeaderSettings.BottomExposureSeconds; - set => base.BottomExposureTime = HeaderSettings.BottomExposureSeconds = (uint)Math.Round(value); + get => HeaderSettings.BottomExposureTime; + set => base.BottomExposureTime = HeaderSettings.BottomExposureTime = (uint)Math.Round(value); } public override float ExposureTime { - get => HeaderSettings.ExposureSeconds; - set => base.ExposureTime = HeaderSettings.ExposureSeconds = (uint)Math.Round(value); + get => HeaderSettings.ExposureTime; + set => base.ExposureTime = HeaderSettings.ExposureTime = (uint)Math.Round(value); } public override float BottomLiftHeight => LiftHeight; @@ -411,7 +395,7 @@ public class AnetN4File : FileFormat set { base.PrintTime = value; - HeaderSettings.EncodedPrintTime = (int)base.PrintTime; + HeaderSettings.EncodedPrintTime = (uint)base.PrintTime; } } @@ -421,7 +405,7 @@ public class AnetN4File : FileFormat set { base.MaterialMilliliters = value; - HeaderSettings.VolumeMicroL = base.MaterialMilliliters * 1000.0; + HeaderSettings.VolumeMicroL = Math.Round(base.MaterialMilliliters * 1000.0, 3); } } @@ -434,18 +418,30 @@ public class AnetN4File : FileFormat #region Constructors public AnetN4File() { + MachineZ = MACHINE_Z; } #endregion #region Methods + protected override void OnBeforeEncode(bool isPartialEncode) + { + HeaderSettings.Name = FilenameNoExt!; + HeaderSettings.Description = $"{About.SoftwareWithVersion} @ {DateTime.Now}"; + } + protected override void EncodeInternally(OperationProgress progress) { using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Create, FileAccess.Write); - byte[] previewBuffer = new byte[72866]; - _bmpHeader.CopyTo(previewBuffer, 0); - EncodeImage(DATATYPE_BGR565, Thumbnails[0]!).CopyTo(previewBuffer, 66); + var previewBuffer = new byte[72866]; // 72866 + BmpHeader.CopyTo(previewBuffer, 0); + + if (CreatedThumbnailsCount > 0) + { + EncodeImage(DATATYPE_BGR565, Thumbnails[0]!).CopyTo(previewBuffer, BmpHeader.Length); + } + HeaderSettings.PreviewContent = previewBuffer; outputFile.WriteSerialize(HeaderSettings); @@ -457,11 +453,11 @@ public class AnetN4File : FileFormat { Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex => { - using (var mat = this[layerIndex].LayerMat) - { - layerData[layerIndex] = new LayerDef(mat); - layerData[layerIndex].Encode(mat); - } + var layer = this[layerIndex]; + using var mat = layer.LayerMat; + layerData[layerIndex] = new LayerDef(); + layerData[layerIndex].SetFrom(layer); + layerData[layerIndex].Encode(mat); progress.LockAndIncrement(); }); @@ -476,7 +472,7 @@ public class AnetN4File : FileFormat } } - outputFile.WriteSerialize(new UInt32()); // Supports count, always 0 + outputFile.WriteBytes(new byte[4]); // Supports count, always 0 Debug.WriteLine("Encode Results:"); Debug.WriteLine(HeaderSettings); @@ -487,17 +483,17 @@ public class AnetN4File : FileFormat { using var inputFile = new FileStream(FileFullPath!, FileMode.Open, FileAccess.Read); HeaderSettings = Helpers.Deserialize
(inputFile); - if (HeaderSettings.Version == null || !HeaderSettings.Version.Equals("3")) + if (HeaderSettings.Version is not "3") { - throw new FileLoadException("Not a valid N4 file: Version doesn't match", FileFullPath); + throw new FileLoadException($"Not a valid N4 file: Version doesn't match, got {HeaderSettings.Version} instead of 3)", FileFullPath); } - if (HeaderSettings.PreviewSize != 72866 || HeaderSettings.PreviewContent == null) + if (HeaderSettings.PreviewSize != 72866 || HeaderSettings.PreviewContent is null) { - throw new FileLoadException("Not a valid N4 file: incorrect preview format", FileFullPath); + throw new FileLoadException($"Not a valid N4 file: incorrect preview format ({HeaderSettings.PreviewSize})", FileFullPath); } - Thumbnails[0] = DecodeImage(DATATYPE_BGR565, HeaderSettings.PreviewContent[66..], ThumbnailsOriginalSize![0]); + Thumbnails[0] = DecodeImage(DATATYPE_BGR565, HeaderSettings.PreviewContent[BmpHeader.Length..], ThumbnailsOriginalSize![0]); Debug.WriteLine(HeaderSettings); From c03969f431baa7e30dbf40f16ed778c2a620c1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 19:21:39 +0000 Subject: [PATCH 11/12] Update FileFormats.1pj --- Scripts/010 Editor/FileFormats.1pj | 1 + 1 file changed, 1 insertion(+) diff --git a/Scripts/010 Editor/FileFormats.1pj b/Scripts/010 Editor/FileFormats.1pj index a4c81c5..a482fe8 100644 --- a/Scripts/010 Editor/FileFormats.1pj +++ b/Scripts/010 Editor/FileFormats.1pj @@ -12,6 +12,7 @@ gr1.bt lgs.bt mdlp.bt + n4.bt osf.bt osla.bt photons.bt From f07a69d987be1acb122b615422055fa5231e7eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 31 Dec 2022 19:21:44 +0000 Subject: [PATCH 12/12] Update UVtools.Core.xml --- documentation/UVtools.Core.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml index f049525..ea8a228 100644 --- a/documentation/UVtools.Core.xml +++ b/documentation/UVtools.Core.xml @@ -1378,6 +1378,16 @@ Some of the format features are not recommended to use (BaseLayersCount and FilledBaseLayersCount). + + + Printer uses incorrect BMP header for preview image so we need to use it as-is instead of generating. + + + + + White pixels region (border including corner pixels) + + Gets a magic number identifying the file type.