diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-04-14 08:08:37 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2021-04-14 08:08:37 +0300 |
commit | 4dae750e83987fde8b34087d26fc75407e88f55a (patch) | |
tree | 83abb7b90ea50e4402ba3b59955fa76f695d07cc /UVtools.Core | |
parent | f914d77538c693155de9333d746d29d6c9958178 (diff) |
v2.9.0v2.9.0
* **File formats:**
* Add Voxeldance Tango (VDT)
* Add Makerbase MKS-DLP (MDLPv1)
* Add GR1 Workshop (GR1)
* Add Creality CXDLP (CXDLP)
* When decoding a file and have a empty resolution (Width: 0 or Height: 0) it will auto fix it by get and set the first layer image resolution to the file
* Fix when decoding a file it was already set to require a full encode, preventing fast saves on print parameters edits
* **GUI:**
* When file resolution dismatch from layer resolution, it is now possible to auto fix it by set the layer resolution to the file
* When loading a file with auto scan for issues disabled it will force auto scan for issues types that are instant to check (print height and empty layers), if any exists it will auto select issues tab
* **(Add) PrusaSlicer printers:**
* Creality HALOT-SKY CL-89
* Creality HALOT-SKY CL-60
* (Improvement) Tool - Adjust layer height: Improve the performance when multiplying layers / go with higher layer height
* (Fix) PrusaSlicer Printer - Wanhao D7: Change the auto convertion format from .zip to .xml.cws
Diffstat (limited to 'UVtools.Core')
26 files changed, 2477 insertions, 374 deletions
diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index 48a9da8..9ea852a 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -18,12 +18,12 @@ namespace UVtools.Core.Extensions { public static class EmguExtensions { - public static readonly MCvScalar WhiteByte = new MCvScalar(255); - public static readonly MCvScalar White3Byte = new MCvScalar(255, 255, 255); - public static readonly MCvScalar BlackByte = new MCvScalar(0); - public static readonly MCvScalar Black3Byte = new MCvScalar(0, 0, 0); - public static readonly MCvScalar Transparent4Byte = new MCvScalar(0, 0, 0, 0); - public static readonly MCvScalar Black4Byte = new MCvScalar(0, 0, 0, 255); + public static readonly MCvScalar WhiteByte = new(255); + public static readonly MCvScalar White3Byte = new(255, 255, 255); + public static readonly MCvScalar BlackByte = new(0); + public static readonly MCvScalar Black3Byte = new(0, 0, 0); + public static readonly MCvScalar Transparent4Byte = new(0, 0, 0, 0); + public static readonly MCvScalar Black4Byte = new(0, 0, 0, 255); public static unsafe byte* GetBytePointer(this Mat mat) { @@ -64,10 +64,16 @@ namespace UVtools.Core.Extensions public static unsafe Span<T> GetPixelRowSpan<T>(this Mat mat, int y, int length = 0, int offset = 0) { - return new(IntPtr.Add(mat.DataPointer, y * mat.Step + offset).ToPointer(), length == 0 ? mat.Step : length); + return new(IntPtr.Add(mat.DataPointer, y * mat.Step + offset).ToPointer(), length <= 0 ? mat.Step : length); //return mat.GetPixelSpan<T>().Slice(offset, mat.Step); } + public static unsafe Span<T> GetPixelColSpan<T>(this Mat mat, int x, int length = 0, int offset = 0) + { + var colMat = mat.Col(x); + return new(IntPtr.Add(mat.DataPointer, offset).ToPointer(), length <= 0 ? mat.Height : length); + } + /// <summary> /// Gets if a <see cref="Mat"/> is all zeroed /// </summary> diff --git a/UVtools.Core/Extensions/SizeExtensions.cs b/UVtools.Core/Extensions/SizeExtensions.cs index f170e12..e16893c 100644 --- a/UVtools.Core/Extensions/SizeExtensions.cs +++ b/UVtools.Core/Extensions/SizeExtensions.cs @@ -47,6 +47,16 @@ namespace UVtools.Core.Extensions public static Size Inflate(this Size size, int width, int height) => new (size.Width + width, size.Height + height); /// <summary> + /// Gets if this size have a zero value on width or height + /// </summary> + /// <param name="size"></param> + /// <returns></returns> + public static bool HaveZero(this Size size) + { + return size.Width <= 0 && size.Height <= 0; + } + + /// <summary> /// Exchange width with height /// </summary> /// <param name="size"></param> @@ -74,6 +84,16 @@ namespace UVtools.Core.Extensions return round >= 0 ? (float) Math.Round(rect.Width * rect.Height, round) : rect.Width * rect.Height; } + /// <summary> + /// Gets if this size have a zero value on width or height + /// </summary> + /// <param name="size"></param> + /// <returns></returns> + public static bool HaveZero(this SizeF size) + { + return size.Width <= 0 && size.Height <= 0; + } + public static float Area(this SizeF size, int round = -1) { return round >= 0 ? (float)Math.Round(size.Width * size.Height, round) : size.Width * size.Height; diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs index 3f6e1d7..f541172 100644 --- a/UVtools.Core/FileFormats/CWSFile.cs +++ b/UVtools.Core/FileFormats/CWSFile.cs @@ -335,8 +335,6 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LightPWM, }; - public override byte ThumbnailsCount { get; } = 0; - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = null; public override uint ResolutionX @@ -383,10 +381,10 @@ namespace UVtools.Core.FileFormats } } - public override float MaxPrintHeight + public override float MachineZ { - get => OutputSettings.PlatformZSize > 0 ? OutputSettings.PlatformZSize : base.MaxPrintHeight; - set => base.MaxPrintHeight = OutputSettings.PlatformZSize = (float)Math.Round(value, 2); + get => OutputSettings.PlatformZSize > 0 ? OutputSettings.PlatformZSize : base.MachineZ; + set => base.MachineZ = OutputSettings.PlatformZSize = (float)Math.Round(value, 2); } public override bool MirrorDisplay diff --git a/UVtools.Core/FileFormats/CXDLPFile.cs b/UVtools.Core/FileFormats/CXDLPFile.cs new file mode 100644 index 0000000..2e680a3 --- /dev/null +++ b/UVtools.Core/FileFormats/CXDLPFile.cs @@ -0,0 +1,703 @@ +/* + * 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. + */ + +// https://github.com/cbiffle/catibo/blob/master/doc/cbddlp-ctb.adoc + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BinarySerialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class CXDLPFile : FileFormat + { + #region Constants + private const byte HEADER_SIZE = 9; // CXSW3DV2 + private const string HEADER_VALUE = "CXSW3DV2"; + + + private const uint SlicerInfoAddress = 4 + HEADER_SIZE + 6 + 290 * 290 * 4 + 116 * 116 * 2 + 6; + #endregion + + #region Sub Classes + #region Header + public sealed class Header + { + /// <summary> + /// Gets the size of the header + /// </summary> + [FieldOrder(0)] + [FieldEndianness(Endianness.Big)] + public uint HeaderSize { get; set; } = HEADER_SIZE; + + /// <summary> + /// Gets the header name + /// </summary> + [FieldOrder(1)] + [FieldLength(HEADER_SIZE)] + [SerializeAs(SerializedType.TerminatedString)] + public string HeaderValue { get; set; } = HEADER_VALUE; + + /// <summary> + /// Gets the number of records in the layer table + /// </summary> + [FieldOrder(2)] + [FieldEndianness(Endianness.Big)] + public ushort LayerCount { get; set; } + + /// <summary> + /// Gets the printer resolution along X axis, in pixels. This information is critical to correctly decoding layer images. + /// </summary> + [FieldOrder(3)] + [FieldEndianness(Endianness.Big)] + public ushort ResolutionX { get; set; } + + /// <summary> + /// Gets the printer resolution along Y axis, in pixels. This information is critical to correctly decoding layer images. + /// </summary> + [FieldOrder(4)] + [FieldEndianness(Endianness.Big)] + public ushort ResolutionY { get; set; } + + public void Validate() + { + if (HeaderSize != HEADER_SIZE || HeaderValue != HEADER_VALUE) + { + throw new FileLoadException("Not a valid CXDLP file!"); + } + } + } + + #endregion + + #region SlicerInfo + // Address: 363337 + public sealed class SlicerInfo + { + [FieldOrder(0)] + [FieldEndianness(Endianness.Big)] + public uint DisplayWidthDataSize { get; set; } = 20; + + [FieldOrder(1)] + [FieldLength(nameof(DisplayWidthDataSize))] + public byte[] DisplayWidth { get; set; } + + [FieldOrder(2)] + [FieldEndianness(Endianness.Big)] + public uint DisplayHeightDataSize { get; set; } = 20; + + [FieldOrder(3)] + [FieldLength(nameof(DisplayHeightDataSize))] + public byte[] DisplayHeight { get; set; } + + [FieldOrder(4)] + [FieldEndianness(Endianness.Big)] + public uint LayerHeightDataSize { get; set; } = 16; + + [FieldOrder(5)] + [FieldLength(nameof(LayerHeightDataSize))] + public byte[] LayerHeight { get; set; } + + [FieldOrder(6)] + [FieldEndianness(Endianness.Big)] + public ushort ExposureTime { get; set; } + + [FieldOrder(7)] + [FieldEndianness(Endianness.Big)] + public ushort LightOffDelay { get; set; } + + [FieldOrder(8)] + [FieldEndianness(Endianness.Big)] + public ushort BottomExposureTime { get; set; } + + [FieldOrder(9)] + [FieldEndianness(Endianness.Big)] + public ushort BottomLayers { get; set; } + + [FieldOrder(10)] + [FieldEndianness(Endianness.Big)] + public ushort BottomLiftHeight { get; set; } + + [FieldOrder(11)] + [FieldEndianness(Endianness.Big)] + public ushort BottomLiftSpeed { get; set; } + + [FieldOrder(12)] + [FieldEndianness(Endianness.Big)] + public ushort LiftHeight { get; set; } + + [FieldOrder(13)] + [FieldEndianness(Endianness.Big)] + public ushort LiftSpeed { get; set; } + + [FieldOrder(14)] + [FieldEndianness(Endianness.Big)] + public ushort RetractSpeed { get; set; } + + [FieldOrder(15)] + [FieldEndianness(Endianness.Big)] + public ushort BottomLightPWM { get; set; } = 255; + + [FieldOrder(16)] + [FieldEndianness(Endianness.Big)] + public ushort LightPWM { get; set; } = 255; + } + #endregion + + #region Layer Def + + public sealed class PreLayer + { + [FieldOrder(0)] + [FieldEndianness(Endianness.Big)] + public uint Unknown { get; set; } + + public PreLayer() + { + } + + public PreLayer(uint unknown) + { + Unknown = unknown; + } + } + + public sealed class LayerDef + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public uint Unknown { get; set; } + [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public uint LineCount { get; set; } + [FieldOrder(2)] [FieldCount(nameof(LineCount))] public LayerLine[] Lines { get; set; } + [FieldOrder(3)] public PageBreak PageBreak { get; set; } = new(); + + public LayerDef() { } + + public LayerDef(uint unknown, uint lineCount, LayerLine[] lines) + { + Unknown = unknown; + LineCount = lineCount; + Lines = lines; + } + } + + public sealed class LayerLine + { + public const byte CoordinateCount = 5; + [FieldOrder(0)] [FieldCount(CoordinateCount)] public byte[] Coordinates { get; set; } = new byte[CoordinateCount]; + //[FieldOrder(0)] [FieldEndianness(Endianness.Big)] [FieldBitLength(13)] public ushort StartY { get; set; } + //[FieldOrder(1)] [FieldEndianness(Endianness.Big)] [FieldBitLength(13)] public ushort EndY { get; set; } + //[FieldOrder(2)] [FieldEndianness(Endianness.Big)] [FieldBitLength(14)] public ushort StartX { get; set; } + [FieldOrder(1)] public byte Gray { get; set; } + + [Ignore] public ushort StartY => (ushort) ((((Coordinates[0] << 8) + Coordinates[1]) >> 3) & 0x1FFF); // 13 bits + + [Ignore] public ushort EndY => (ushort)((((Coordinates[1] << 16) + (Coordinates[2] << 8) + Coordinates[3]) >> 6) & 0x1FFF); // 13 bits + + [Ignore] public ushort StartX => (ushort)(((Coordinates[3] << 8) + Coordinates[4]) & 0x3FFF); // 14 bits + [Ignore] public ushort Length => (ushort) (EndY - StartY); + + public LayerLine() { } + + public LayerLine(ushort startY, ushort endY, ushort startX, byte gray) + { + Coordinates[0] = (byte) ((startY >> 5) & 0xFF); + Coordinates[1] = (byte) (((startY << 3) + (endY >> 10)) & 0xFF); + Coordinates[2] = (byte) ((endY >> 2) & 0xFF); + Coordinates[3] = (byte)(((endY << 6) + (startX >> 8)) & 0xFF); + Coordinates[4] = (byte) startX; + /*StartY = startY; + EndY = endY; + StartX = startX;*/ + Gray = gray; + } + } + + public sealed class PageBreak + { + [FieldOrder(0)] public byte Line { get; set; } = 0x0D; + [FieldOrder(1)] public byte Break { get; set; } = 0x0A; + } + + #endregion + + #region Footer + public sealed class Footer + { + /// <summary> + /// Gets the size of the header + /// </summary> + [FieldOrder(0)] + [FieldEndianness(Endianness.Big)] + public uint FooterSize { get; set; } = HEADER_SIZE; + + /// <summary> + /// Gets the header name + /// </summary> + [FieldOrder(1)] + [FieldLength(HEADER_SIZE)] + [SerializeAs(SerializedType.TerminatedString)] + public string FooterValue { get; set; } = HEADER_VALUE; + + [FieldOrder(2)] + public uint Unknown { get; set; } = 7; + + public void Validate() + { + if (FooterSize != HEADER_SIZE || FooterValue != HEADER_VALUE) + { + throw new FileLoadException("Not a valid CXDLP file!"); + } + } + } + #endregion + + #endregion + + #region Properties + + public Header HeaderSettings { get; protected internal set; } = new(); + public SlicerInfo SlicerInfoSettings { get; protected internal set; } = new(); + public Footer FooterSettings { get; protected internal set; } = new(); + + public override FileFormatType FileType => FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = { + new("cxdlp", "Creality CXDLP"), + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = + { + PrintParameterModifier.BottomLayerCount, + PrintParameterModifier.BottomExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + PrintParameterModifier.BottomLiftHeight, + PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + PrintParameterModifier.LightOffDelay, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM, + }; + + public override Size[] ThumbnailsOriginalSize { get; } = + { + new(116, 116), + new(290, 290), + new(290, 290) + }; + + public override uint ResolutionX + { + get => HeaderSettings.ResolutionX; + set + { + HeaderSettings.ResolutionX = (ushort) value; + RaisePropertyChanged(); + } + } + + public override uint ResolutionY + { + get => HeaderSettings.ResolutionY; + set + { + HeaderSettings.ResolutionY = (ushort) value; + RaisePropertyChanged(); + } + } + + public override float DisplayWidth + { + get => float.Parse(SlicerInfoSettings.DisplayWidth.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.DisplayWidthDataSize = (uint)(str.Length * 2); + var data = new byte[SlicerInfoSettings.DisplayWidthDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.DisplayWidth = data; + RaisePropertyChanged(); + } + } + + public override float DisplayHeight + { + get => float.Parse(SlicerInfoSettings.DisplayHeight.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.DisplayHeightDataSize = (uint)(str.Length * 2); + var data = new byte[SlicerInfoSettings.DisplayHeightDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.DisplayHeight = data; + RaisePropertyChanged(); + } + } + + public override byte AntiAliasing + { + get => 8; + set {} + } + + public override float LayerHeight + { + get => float.Parse(SlicerInfoSettings.LayerHeight.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Layer.RoundHeight(value).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.LayerHeightDataSize = (uint)(str.Length * 2); + var data = new byte[SlicerInfoSettings.LayerHeightDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.LayerHeight = data; + RaisePropertyChanged(); + } + } + + public override uint LayerCount + { + get => base.LayerCount; + set => base.LayerCount = HeaderSettings.LayerCount = (ushort) base.LayerCount; + } + + public override ushort BottomLayerCount + { + get => SlicerInfoSettings.BottomLayers; + set => base.BottomLayerCount = SlicerInfoSettings.BottomLayers = value; + } + + public override float BottomExposureTime + { + get => SlicerInfoSettings.BottomExposureTime; + set => base.BottomExposureTime = SlicerInfoSettings.BottomExposureTime = (ushort) value; + } + + public override float ExposureTime + { + get => SlicerInfoSettings.ExposureTime; + set => base.ExposureTime = SlicerInfoSettings.ExposureTime = (ushort) value; + } + + public override float BottomLiftHeight + { + get => SlicerInfoSettings.BottomLiftHeight; + set => base.BottomLiftHeight = SlicerInfoSettings.BottomLiftHeight = (ushort) value; + } + + public override float LiftHeight + { + get => SlicerInfoSettings.LiftHeight; + set => base.LiftHeight = SlicerInfoSettings.LiftHeight = (ushort)value; + } + + public override float BottomLiftSpeed + { + get => SlicerInfoSettings.BottomLiftSpeed; + set => base.BottomLiftSpeed = SlicerInfoSettings.BottomLiftSpeed = (ushort)value; + } + + public override float LiftSpeed + { + get => SlicerInfoSettings.LiftSpeed; + set => base.LiftSpeed = SlicerInfoSettings.LiftSpeed = (ushort)value; + } + + public override float RetractSpeed + { + get => SlicerInfoSettings.RetractSpeed; + set => base.RetractSpeed = SlicerInfoSettings.RetractSpeed = (ushort)value; + } + + public override float BottomLightOffDelay => SlicerInfoSettings.LightOffDelay; + + public override float LightOffDelay + { + get => SlicerInfoSettings.LightOffDelay; + set => base.LightOffDelay = SlicerInfoSettings.LightOffDelay = (ushort)value; + } + + public override byte BottomLightPWM + { + get => (byte) SlicerInfoSettings.BottomLightPWM; + set => base.BottomLightPWM = (byte) (SlicerInfoSettings.BottomLightPWM = value); + } + + public override byte LightPWM + { + get => (byte)SlicerInfoSettings.LightPWM; + set => base.LightPWM = (byte) (SlicerInfoSettings.LightPWM = value); + } + + public override object[] Configs => new object[] { HeaderSettings, SlicerInfoSettings, FooterSettings }; + + #endregion + + #region Constructors + #endregion + + #region Methods + + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) + { + using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write); + + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + var pageBreak = new PageBreak(); + + byte[][] previews = new byte[ThumbnailsOriginalSize.Length][]; + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + previews[i] = new byte[ThumbnailsOriginalSize[i].Area() * 2]; + } + // Previews + Parallel.For(0, previews.Length, previewIndex => + { + if (progress.Token.IsCancellationRequested) return; + if (Thumbnails[previewIndex] is null) return; + var span = Thumbnails[previewIndex].GetPixelSpanByte(); + int index = 0; + for (int i = 0; i < span.Length; i += 3) + { + byte b = span[i]; + byte g = span[i + 1]; + byte r = span[i + 2]; + + ushort rgb15 = (ushort)(((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0)); + + previews[previewIndex][index++] = (byte)(rgb15 >> 8); + previews[previewIndex][index++] = (byte)(rgb15 & 0xff); + } + + if (index != previews[previewIndex].Length) + { + throw new FileLoadException($"Preview encode incomplete encode, expected: {previews[previewIndex].Length}, encoded: {index}"); + } + }); + + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + Helpers.SerializeWriteFileStream(outputFile, previews[i]); + Helpers.SerializeWriteFileStream(outputFile, pageBreak); + } + Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + + progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); + var preLayers = new PreLayer[LayerCount]; + var layerDefs = new LayerDef[LayerCount]; + + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + var layer = this[layerIndex]; + preLayers[layerIndex] = new(layer.NonZeroPixelCount); + } + Helpers.SerializeWriteFileStream(outputFile, preLayers); + Helpers.SerializeWriteFileStream(outputFile, pageBreak); + + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + List<LayerLine> layerLines = new(); + var layer = this[layerIndex]; + using var mat = layer.LayerMat; + var span = mat.GetPixelSpanByte(); + + for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++) + { + int y = layer.BoundingRectangle.Y; + int startY = -1; + byte lastColor = 0; + for (; y < layer.BoundingRectangle.Bottom; y++) + { + int pos = mat.GetPixelPos(x, y); + byte color = span[pos]; + + if (lastColor == color && color != 0) continue; + + if (startY >= 0) + { + layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor)); + } + + startY = color == 0 ? -1 : y; + + lastColor = color; + } + + if (startY >= 0) + { + layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor)); + } + } + + layerDefs[layerIndex] = new LayerDef(layer.NonZeroPixelCount, (uint)layerLines.Count, layerLines.ToArray()); + + progress.LockAndIncrement(); + }); + + progress.Reset(OperationProgress.StatusWritingFile, LayerCount); + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + Helpers.SerializeWriteFileStream(outputFile, layerDefs[layerIndex]); + progress++; + } + + Helpers.SerializeWriteFileStream(outputFile, FooterSettings); + + Debug.WriteLine("Encode Results:"); + Debug.WriteLine(HeaderSettings); + Debug.WriteLine("-End-"); + } + + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) + { + using var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read); + HeaderSettings = Helpers.Deserialize<Header>(inputFile); + HeaderSettings.Validate(); + + byte[][] previews = new byte[ThumbnailsOriginalSize.Length][]; + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + previews[i] = new byte[ThumbnailsOriginalSize[i].Area() * 2]; + inputFile.ReadBytes(previews[i]); + inputFile.Seek(2, SeekOrigin.Current); + } + + Parallel.For(0, previews.Length, previewIndex => + { + var mat = new Mat(ThumbnailsOriginalSize[previewIndex], DepthType.Cv8U, 3); + var span = mat.GetPixelSpanByte(); + + int spanIndex = 0; + for (int i = 0; i < previews[previewIndex].Length; i += 2) + { + ushort rgb15 = (ushort)((ushort)(previews[previewIndex][i + 0] << 8) | previews[previewIndex][i + 1]); + byte r = (byte)((rgb15 >> 11) << 3); + byte g = (byte)((rgb15 >> 5) << 2); + byte b = (byte)((rgb15 >> 0) << 3); + + span[spanIndex++] = b; + span[spanIndex++] = g; + span[spanIndex++] = r; + } + + Thumbnails[previewIndex] = mat; + }); + + + SlicerInfoSettings = Helpers.Deserialize<SlicerInfo>(inputFile); + + LayerManager.Init(HeaderSettings.LayerCount); + progress.ItemCount = LayerCount; + var preLayers = new PreLayer[LayerCount]; + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + preLayers[layerIndex] = Helpers.Deserialize<PreLayer>(inputFile); + } + + inputFile.Seek(2, SeekOrigin.Current); + var layerDefs = new LayerDef[LayerCount]; + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + layerDefs[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile); + progress++; + } + + /*using TextWriter file = new StreamWriter("D:\\dump.txt"); + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + file.WriteLine($"Layer: {layerIndex}"); + for (int lineIndex = 0; lineIndex < layerDefs[layerIndex].LineCount; lineIndex++) + { + file.WriteLine($"\tLine: {lineIndex}"); + var line = layerDefs[layerIndex].Lines[lineIndex]; + file.WriteLine($"\t\tb1: {line.Coordinates[0]}"); + file.WriteLine($"\t\tb2: {line.Coordinates[1]}"); + file.WriteLine($"\t\tb3: {line.Coordinates[2]}"); + file.WriteLine($"\t\tb4: {line.Coordinates[3]}"); + file.WriteLine($"\t\tb5: {line.Coordinates[4]}"); + file.WriteLine($"\t\tstartY: {line.StartY}"); + file.WriteLine($"\t\tendY: {line.EndY}"); + file.WriteLine($"\t\tstartX: {line.StartX}"); + file.WriteLine($"\t\tgray: {line.Gray}"); + } + } + file.Close();*/ + + progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using var mat = EmguExtensions.InitMat(Resolution); + foreach (var line in layerDefs[layerIndex].Lines) + { + CvInvoke.Line(mat, new Point(line.StartX, line.StartY), new Point(line.StartX, line.EndY), new MCvScalar(line.Gray)); + } + + this[layerIndex] = new Layer((uint)layerIndex, mat, this); + progress.LockAndIncrement(); + }); + + FooterSettings = Helpers.Deserialize<Footer>(inputFile); + FooterSettings.Validate(); + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (RequireFullEncode) + { + 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(SlicerInfoAddress, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + } + + #endregion + } +} diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs index f317809..ab86e58 100644 --- a/UVtools.Core/FileFormats/ChituboxFile.cs +++ b/UVtools.Core/FileFormats/ChituboxFile.cs @@ -904,7 +904,7 @@ namespace UVtools.Core.FileFormats #endregion - #region KeyRing + #region KeyRing public class KeyRing { @@ -1026,9 +1026,11 @@ namespace UVtools.Core.FileFormats - public override byte ThumbnailsCount { get; } = 2; - - public override Size[] ThumbnailsOriginalSize { get; } = {new Size(400, 300), new Size(200, 125)}; + public override Size[] ThumbnailsOriginalSize { get; } = + { + new(400, 300), + new(200, 125) + }; public override uint ResolutionX { @@ -1070,10 +1072,10 @@ namespace UVtools.Core.FileFormats } } - public override float MaxPrintHeight + public override float MachineZ { - get => HeaderSettings.BedSizeZ > 0 ? HeaderSettings.BedSizeZ : base.MaxPrintHeight; - set => base.MaxPrintHeight = HeaderSettings.BedSizeZ = (float)Math.Round(value, 2); + get => HeaderSettings.BedSizeZ > 0 ? HeaderSettings.BedSizeZ : base.MachineZ; + set => base.MachineZ = HeaderSettings.BedSizeZ = (float)Math.Round(value, 2); } public override bool MirrorDisplay diff --git a/UVtools.Core/FileFormats/ChituboxZipFile.cs b/UVtools.Core/FileFormats/ChituboxZipFile.cs index ed8a707..4456db2 100644 --- a/UVtools.Core/FileFormats/ChituboxZipFile.cs +++ b/UVtools.Core/FileFormats/ChituboxZipFile.cs @@ -106,9 +106,11 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LightPWM, }; - public override byte ThumbnailsCount { get; } = 2; - - public override Size[] ThumbnailsOriginalSize { get; } = {new Size(954, 850), new Size(168, 150)}; + public override Size[] ThumbnailsOriginalSize { get; } = + { + new(954, 850), + new(168, 150) + }; public override uint ResolutionX { @@ -150,10 +152,10 @@ namespace UVtools.Core.FileFormats } } - public override float MaxPrintHeight + public override float MachineZ { - get => HeaderSettings.MachineZ > 0 ? HeaderSettings.MachineZ : base.MaxPrintHeight; - set => base.MaxPrintHeight = HeaderSettings.MachineZ = (float)Math.Round(value, 2); + get => HeaderSettings.MachineZ > 0 ? HeaderSettings.MachineZ : base.MachineZ; + set => base.MachineZ = HeaderSettings.MachineZ = (float)Math.Round(value, 2); } public override bool MirrorDisplay diff --git a/UVtools.Core/FileFormats/FDGFile.cs b/UVtools.Core/FileFormats/FDGFile.cs index 9aa0eda..71fc401 100644 --- a/UVtools.Core/FileFormats/FDGFile.cs +++ b/UVtools.Core/FileFormats/FDGFile.cs @@ -697,9 +697,11 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LightOffDelay, }; - public override byte ThumbnailsCount { get; } = 2; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(400, 300), new System.Drawing.Size(200, 125)}; + public override Size[] ThumbnailsOriginalSize { get; } = + { + new(400, 300), + new(200, 125) + }; public override uint ResolutionX { @@ -742,10 +744,10 @@ namespace UVtools.Core.FileFormats } } - public override float MaxPrintHeight + public override float MachineZ { - get => HeaderSettings.BedSizeZ > 0 ? HeaderSettings.BedSizeZ : base.MaxPrintHeight; - set => base.MaxPrintHeight = HeaderSettings.BedSizeZ = (float)Math.Round(value, 2); + get => HeaderSettings.BedSizeZ > 0 ? HeaderSettings.BedSizeZ : base.MachineZ; + set => base.MachineZ = HeaderSettings.BedSizeZ = (float)Math.Round(value, 2); } public override bool MirrorDisplay diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index d5cdbbf..2fe8a04 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -211,8 +211,11 @@ namespace UVtools.Core.FileFormats new CWSFile(), // CWS new ZCodeFile(), // zcode new ZCodexFile(), // zcodex - //new MakerbaseFile(), // MKS + new MDLPFile(), // MKS v1 + new GR1File(), // GR1 Workshop + new CXDLPFile(), // Creality Box new LGSFile(), // LGS, LGS30 + new VDTFile(), // VDT new UVJFile(), // UVJ new ImageFile(), // images }; @@ -237,7 +240,7 @@ namespace UVtools.Core.FileFormats { var result = new List<KeyValuePair<string, List<string>>> { - new KeyValuePair<string, List<string>>("All slicer files", new List<string>()) + new("All slicer files", new List<string>()) }; for (int i = 0; i < AvailableFormats.Length; i++) @@ -287,10 +290,14 @@ namespace UVtools.Core.FileFormats /// <returns><see cref="FileFormat"/> object or null if not found</returns> public static FileFormat FindByExtension(string extension, bool isFilePath = false, bool createNewInstance = false) { - return (from fileFormat in AvailableFormats where fileFormat.IsExtensionValid(extension, isFilePath) select createNewInstance ? (FileFormat) Activator.CreateInstance(fileFormat.GetType()) : fileFormat).FirstOrDefault(); + if (isFilePath) + { + GetFileNameStripExtensions(extension, out extension); + } + return (from fileFormat in AvailableFormats where fileFormat.IsExtensionValid(extension) select createNewInstance ? (FileFormat) Activator.CreateInstance(fileFormat.GetType()) : fileFormat).FirstOrDefault(); } - public static FileExtension FindExtension(string extension, bool isFilePath = false, bool createNewInstance = false) + public static FileExtension FindExtension(string extension) { return AvailableFormats.SelectMany(format => format.FileExtensions).FirstOrDefault(ext => ext.Equals(extension)); } @@ -324,7 +331,7 @@ namespace UVtools.Core.FileFormats private LayerManager _layerManager; private float _printTime; private float _materialMilliliters; - private float _maxPrintHeight; + private float _machineZ; private ushort _bottomLayerCount = DefaultBottomLayerCount; private float _bottomExposureTime = DefaultBottomExposureTime; private float _exposureTime = DefaultExposureTime; @@ -444,7 +451,7 @@ namespace UVtools.Core.FileFormats /// <summary> /// Gets the thumbnails count present in this file format /// </summary> - public abstract byte ThumbnailsCount { get; } + public byte ThumbnailsCount => (byte)(ThumbnailsOriginalSize?.Length ?? 0); /// <summary> /// Gets the number of created thumbnails @@ -594,10 +601,10 @@ namespace UVtools.Core.FileFormats /// <summary> /// Gets or sets the maximum printer build Z volume /// </summary> - public virtual float MaxPrintHeight + public virtual float MachineZ { - get => _maxPrintHeight > 0 ? _maxPrintHeight : PrintHeight; - set => RaiseAndSetIfChanged(ref _maxPrintHeight, value); + get => _machineZ > 0 ? _machineZ : PrintHeight; + set => RaiseAndSetIfChanged(ref _machineZ, value); } /// <summary> @@ -727,7 +734,7 @@ namespace UVtools.Core.FileFormats /// <summary> /// Checks if this file format supports per layer settings /// </summary> - public virtual bool SupportPerLayerSettings => PrintParameterPerLayerModifiers is not null && PrintParameterPerLayerModifiers.Length > 0; + public bool SupportPerLayerSettings => PrintParameterPerLayerModifiers is not null && PrintParameterPerLayerModifiers.Length > 0; /// <summary> /// Gets or sets the layer count @@ -1161,7 +1168,10 @@ namespace UVtools.Core.FileFormats /// <returns>True if valid, otherwise false</returns> public bool IsExtensionValid(string extension, bool isFilePath = false) { - extension = isFilePath ? Path.GetExtension(extension)?.Remove(0, 1) : extension; + if (isFilePath) + { + GetFileNameStripExtensions(extension, out extension); + } return FileExtensions.Any(fileExtension => fileExtension.Equals(extension)); } @@ -1352,7 +1362,28 @@ namespace UVtools.Core.FileFormats "Lower and fix your layer height on slicer to avoid precision errors.", fileFullPath); } - if (_layerManager.Sanitize()) + bool reSaveFile = false; + + if(ResolutionX == 0 || ResolutionY == 0) + { + var layer = FirstLayer; + if (layer is not null) + { + using var mat = layer.LayerMat; + + if (mat.Size.HaveZero()) + { + throw new FileLoadException($"File resolution ({Resolution}) is invalid and can't be auto fixed due invalid layers with same problem ({mat.Size}).", fileFullPath); + } + + Resolution = mat.Size; + reSaveFile = true; + } + } + + reSaveFile |= _layerManager.Sanitize(); + + if (reSaveFile) { Save(progress); } @@ -1815,7 +1846,7 @@ namespace UVtools.Core.FileFormats slicerFile.ResolutionY = ResolutionY; slicerFile.DisplayWidth = DisplayWidth; slicerFile.DisplayHeight = DisplayHeight; - slicerFile.MaxPrintHeight = MaxPrintHeight; + slicerFile.MachineZ = MachineZ; slicerFile.MirrorDisplay = MirrorDisplay; slicerFile.BottomExposureTime = BottomExposureTime; slicerFile.ExposureTime = ExposureTime; diff --git a/UVtools.Core/FileFormats/GR1File.cs b/UVtools.Core/FileFormats/GR1File.cs new file mode 100644 index 0000000..74b4019 --- /dev/null +++ b/UVtools.Core/FileFormats/GR1File.cs @@ -0,0 +1,531 @@ +/* + * 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. + */ + +// https://github.com/cbiffle/catibo/blob/master/doc/cbddlp-ctb.adoc + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +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 GR1File : FileFormat + { + #region Constants + + private const uint SlicerInfoAddress = 4 + 7 + 290 * 290 * 2 + 116 * 116 * 2 + 4; + #endregion + + #region Sub Classes + + #region Header + + public sealed class Header + { + public const byte HEADER_SIZE = 7; + public const string HEADER_VALUE = "MKSDLP"; + + [FieldOrder(0)] + [FieldEndianness(Endianness.Big)] + public uint HeaderSize { get; set; } = HEADER_SIZE; + + /// <summary> + /// Gets the file tag = MKSDLP + /// </summary> + [FieldOrder(1)] + [FieldLength(HEADER_SIZE)] + [SerializeAs(SerializedType.TerminatedString)] + public string HeaderValue { get; set; } = HEADER_VALUE; + } + #endregion + + #region SlicerInfo + + public sealed class SlicerInfo + { + // 290 * 290 * 2 + 116 * 116 * 2 + 4 + //[FieldOrder(0)] [FieldLength(195116)] public byte[] PreviewData { get; set; } + + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public ushort LayerCount { get; set; } + [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public ushort ResolutionX { get; set; } + [FieldOrder(2)] [FieldEndianness(Endianness.Big)] public ushort ResolutionY { get; set; } + [FieldOrder(3)] [FieldEndianness(Endianness.Big)] public uint DisplayWidthDataSize { get; set; } = 6; + [FieldOrder(4)] [FieldLength(nameof(DisplayWidthDataSize))] public byte[] DisplayWidth { get; set; } + [FieldOrder(5)] [FieldEndianness(Endianness.Big)] public uint DisplayHeightDataSize { get; set; } = 6; + [FieldOrder(6)] [FieldLength(nameof(DisplayHeightDataSize))] public byte[] DisplayHeight { get; set; } + + [FieldOrder(7)] [FieldEndianness(Endianness.Big)] public uint LayerHeightDataSize { get; set; } = 6; + [FieldOrder(8)] [FieldLength(nameof(LayerHeightDataSize))] public byte[] LayerHeight { get; set; } + [FieldOrder(9)] [FieldEndianness(Endianness.Big)] public ushort ExposureTime { get; set; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort LightOffDelay { get; set; } + [FieldOrder(11)] [FieldEndianness(Endianness.Big)] public ushort BottomExposureTime { get; set; } + [FieldOrder(12)] [FieldEndianness(Endianness.Big)] public ushort BottomLayers { get; set; } + [FieldOrder(13)] [FieldEndianness(Endianness.Big)] public ushort BottomLiftHeight { get; set; } + [FieldOrder(14)] [FieldEndianness(Endianness.Big)] public ushort BottomLiftSpeed { get; set; } + [FieldOrder(15)] [FieldEndianness(Endianness.Big)] public ushort LiftHeight { get; set; } + [FieldOrder(16)] [FieldEndianness(Endianness.Big)] public ushort LiftSpeed { get; set; } + [FieldOrder(17)] [FieldEndianness(Endianness.Big)] public ushort RetractSpeed { get; set; } + [FieldOrder(18)] [FieldEndianness(Endianness.Big)] public ushort BottomLightPWM { get; set; } + [FieldOrder(19)] [FieldEndianness(Endianness.Big)] public ushort LightPWM { get; set; } + } + #endregion + + #region LayerDef + + public sealed class LayerDef + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public uint LineCount { get; set; } + [FieldOrder(1)] [FieldCount(nameof(LineCount))] public LayerLine[] Lines { get; set; } + [FieldOrder(2)] public PageBreak PageBreak { get; set; } = new(); + + public LayerDef() { } + + public LayerDef(uint lineCount, LayerLine[] lines) + { + LineCount = lineCount; + Lines = lines; + } + } + + public sealed class LayerLine + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public ushort StartY { get; set; } + [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public ushort EndY { get; set; } + [FieldOrder(2)] [FieldEndianness(Endianness.Big)] public ushort StartX { get; set; } + //[FieldOrder(3)] [FieldEndianness(Endianness.Big)] public byte Gray { get; set; } + + + public LayerLine() + { } + + public LayerLine(ushort startY, ushort endY, ushort startX) + { + StartY = startY; + EndY = endY; + StartX = startX; + } + } + + public sealed class PageBreak + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public byte Line { get; set; } = 0x0D; + [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public byte Break { get; set; } = 0x0A; + } + + #endregion + #endregion + + #region Properties + + public Header HeaderSettings { get; protected internal set; } = new(); + public SlicerInfo SlicerInfoSettings { get; protected internal set; } = new(); + public override FileFormatType FileType => FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = { + new ("gr1", "GR1 Workshop") + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = + { + PrintParameterModifier.BottomLayerCount, + PrintParameterModifier.BottomExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + PrintParameterModifier.LightOffDelay, + PrintParameterModifier.BottomLiftHeight, + PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM, + }; + + public override Size[] ThumbnailsOriginalSize { get; } = + { + new (116, 116), + new (290, 290) + }; + + public override uint ResolutionX + { + get => SlicerInfoSettings.ResolutionX; + set + { + SlicerInfoSettings.ResolutionX = (ushort) value; + RaisePropertyChanged(); + } + } + + public override uint ResolutionY + { + get => SlicerInfoSettings.ResolutionY; + set + { + SlicerInfoSettings.ResolutionY = (ushort) value; + RaisePropertyChanged(); + } + } + + public override float DisplayWidth + { + get => float.Parse(SlicerInfoSettings.DisplayWidth.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.DisplayWidthDataSize = (uint) (str.Length * 2); + var data = new byte[SlicerInfoSettings.DisplayWidthDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.DisplayWidth = data; + RaisePropertyChanged(); + } + } + + public override float DisplayHeight + { + get => float.Parse(SlicerInfoSettings.DisplayHeight.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.DisplayHeightDataSize = (uint)(str.Length * 2); + var data = new byte[SlicerInfoSettings.DisplayHeightDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.DisplayHeight = data; + RaisePropertyChanged(); + } + } + + public override bool MirrorDisplay { get; set; } + + public override byte AntiAliasing + { + get => 1; + set { } + } + + public override float LayerHeight + { + get => float.Parse(SlicerInfoSettings.LayerHeight.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Layer.RoundHeight(value).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.LayerHeightDataSize = (uint)(str.Length * 2); + var data = new byte[SlicerInfoSettings.LayerHeightDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.LayerHeight = data; + RaisePropertyChanged(); + } + } + + public override uint LayerCount + { + get => base.LayerCount; + set => base.LayerCount = SlicerInfoSettings.LayerCount = (ushort)base.LayerCount; + } + + public override ushort BottomLayerCount + { + get => SlicerInfoSettings.BottomLayers; + set => base.BottomLayerCount = SlicerInfoSettings.BottomLayers = value; + } + + public override float BottomExposureTime + { + get => SlicerInfoSettings.BottomExposureTime; + set => base.BottomExposureTime = SlicerInfoSettings.BottomExposureTime = (ushort)value; + } + + public override float ExposureTime + { + get => SlicerInfoSettings.ExposureTime; + set => base.ExposureTime = SlicerInfoSettings.ExposureTime = (ushort)value; + } + + public override float BottomLiftHeight + { + get => SlicerInfoSettings.BottomLiftHeight; + set => base.BottomLiftHeight = SlicerInfoSettings.BottomLiftHeight = (ushort)value; + } + + public override float LiftHeight + { + get => SlicerInfoSettings.LiftHeight; + set => base.LiftHeight = SlicerInfoSettings.BottomLiftHeight = (ushort)value; + } + + public override float BottomLiftSpeed + { + get => SlicerInfoSettings.BottomLiftSpeed; + set => base.BottomLiftSpeed = SlicerInfoSettings.BottomLiftSpeed = (ushort)value; + } + + public override float LiftSpeed + { + get => SlicerInfoSettings.LiftSpeed; + set => base.LiftSpeed = SlicerInfoSettings.LiftSpeed = (ushort)value; + } + + public override float RetractSpeed + { + get => SlicerInfoSettings.RetractSpeed; + set => base.RetractSpeed = SlicerInfoSettings.RetractSpeed = (ushort)value; + } + + public override float BottomLightOffDelay => SlicerInfoSettings.LightOffDelay; + + public override float LightOffDelay + { + get => SlicerInfoSettings.LightOffDelay; + set => base.LightOffDelay = SlicerInfoSettings.LightOffDelay = (ushort)value; + } + + public override byte BottomLightPWM + { + get => (byte)SlicerInfoSettings.BottomLightPWM; + set => base.BottomLightPWM = (byte)(SlicerInfoSettings.BottomLightPWM = value); + } + + public override byte LightPWM + { + get => (byte)SlicerInfoSettings.LightPWM; + set => base.LightPWM = (byte)(SlicerInfoSettings.LightPWM = value); + } + + public override object[] Configs => new[] { (object)HeaderSettings, SlicerInfoSettings }; + + #endregion + + #region Constructors + + #endregion + + #region Methods + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) + { + using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write); + var pageBreak = new PageBreak(); + + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + byte[][] previews = new byte[ThumbnailsOriginalSize.Length][]; + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + previews[i] = new byte[ThumbnailsOriginalSize[i].Area() * 2]; + } + // Previews + Parallel.For(0, previews.Length, previewIndex => + { + if (progress.Token.IsCancellationRequested) return; + if (Thumbnails[previewIndex] is null) return; + var span = Thumbnails[previewIndex].GetPixelSpanByte(); + int index = 0; + for (int i = 0; i < span.Length; i += 3) + { + byte b = span[i]; + byte g = span[i + 1]; + byte r = span[i + 2]; + + ushort rgb15 = (ushort)(((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0)); + + previews[previewIndex][index++] = (byte)(rgb15 >> 8); + previews[previewIndex][index++] = (byte)(rgb15 & 0xff); + } + + if (index != previews[previewIndex].Length) + { + throw new FileLoadException($"Preview encode incomplete encode, expected: {previews[previewIndex].Length}, encoded: {index}"); + } + }); + + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + Helpers.SerializeWriteFileStream(outputFile, previews[i]); + Helpers.SerializeWriteFileStream(outputFile, pageBreak); + } + Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + + progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); + var layerDefs = new LayerDef[LayerCount]; + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + List<LayerLine> layerLines = new(); + var layer = this[layerIndex]; + using var mat = layer.LayerMat; + var span = mat.GetPixelSpanByte(); + + for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++) + { + int y = layer.BoundingRectangle.Y; + int startY = -1; + for (; y < layer.BoundingRectangle.Bottom; y++) + { + int pos = mat.GetPixelPos(x, y); + if (span[pos] < 128) // Black pixel + { + if(startY == -1) continue; // Keep ignoring + layerLines.Add(new LayerLine((ushort) startY, (ushort) (y-1), (ushort) x)); + startY = -1; + } + else // White pixel + { + if (startY >= 0) continue; // Keep sum + startY = y; + } + } + + if (startY >= 0) + { + layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort) x)); + } + } + + layerDefs[layerIndex] = new LayerDef((uint) layerLines.Count, layerLines.ToArray()); + + progress.LockAndIncrement(); + }); + + progress.Reset(OperationProgress.StatusWritingFile, LayerCount); + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + Helpers.SerializeWriteFileStream(outputFile, layerDefs[layerIndex]); + progress++; + } + + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + Debug.WriteLine("Encode Results:"); + Debug.WriteLine(HeaderSettings); + Debug.WriteLine("-End-"); + } + + + + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) + { + using var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read); + HeaderSettings = Helpers.Deserialize<Header>(inputFile); + if (HeaderSettings.HeaderValue != Header.HEADER_VALUE) + { + throw new FileLoadException("Not a valid Makerbase file!", fileFullPath); + } + + byte[][] previews = new byte[ThumbnailsOriginalSize.Length][]; + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + previews[i] = new byte[ThumbnailsOriginalSize[i].Area() * 2]; + inputFile.ReadBytes(previews[i]); + inputFile.Seek(2, SeekOrigin.Current); + } + + Parallel.For(0, previews.Length, previewIndex => + { + var mat = new Mat(ThumbnailsOriginalSize[previewIndex], DepthType.Cv8U, 3); + var span = mat.GetPixelSpanByte(); + + int spanIndex = 0; + for (int i = 0; i < previews[previewIndex].Length; i += 2) + { + ushort rgb15 = (ushort)((ushort)(previews[previewIndex][i + 0] << 8) | previews[previewIndex][i + 1]); + byte r = (byte)((rgb15 >> 11) << 3); + byte g = (byte)((rgb15 >> 5) << 2); + byte b = (byte)((rgb15 >> 0) << 3); + + span[spanIndex++] = b; + span[spanIndex++] = g; + span[spanIndex++] = r; + } + + Thumbnails[previewIndex] = mat; + }); + + + SlicerInfoSettings = Helpers.Deserialize<SlicerInfo>(inputFile); + + LayerManager.Init(SlicerInfoSettings.LayerCount); + progress.ItemCount = LayerCount; + LayerDef[] layerDefs = new LayerDef[LayerCount]; + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + layerDefs[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile); + progress++; + } + + progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using var mat = EmguExtensions.InitMat(Resolution); + foreach (var line in layerDefs[layerIndex].Lines) + { + CvInvoke.Line(mat, new Point(line.StartX, line.StartY), new Point(line.StartX, line.EndY), EmguExtensions.WhiteByte); + } + + this[layerIndex] = new Layer((uint) layerIndex, mat, this); + progress.LockAndIncrement(); + }); + + HeaderSettings = Helpers.Deserialize<Header>(inputFile); + if (HeaderSettings.HeaderValue != Header.HEADER_VALUE) + { + throw new FileLoadException("Not a valid Makerbase file!", fileFullPath); + } + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (RequireFullEncode) + { + 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(SlicerInfoAddress, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + } + + #endregion + } +} diff --git a/UVtools.Core/FileFormats/ImageFile.cs b/UVtools.Core/FileFormats/ImageFile.cs index 668e2a8..42c4323 100644 --- a/UVtools.Core/FileFormats/ImageFile.cs +++ b/UVtools.Core/FileFormats/ImageFile.cs @@ -20,8 +20,13 @@ namespace UVtools.Core.FileFormats new ("tga", "TGA"), }; public override PrintParameterModifier[] PrintParameterModifiers { get; } = null; - public override byte ThumbnailsCount { get; } = 4; - public override Size[] ThumbnailsOriginalSize { get; } = null; + + public override Size[] ThumbnailsOriginalSize { get; } = { + Size.Empty, + Size.Empty, + Size.Empty, + Size.Empty + }; public override uint ResolutionX { get => (uint) ImageMat.Width; diff --git a/UVtools.Core/FileFormats/LGSFile.cs b/UVtools.Core/FileFormats/LGSFile.cs index 03143e3..cb3d38d 100644 --- a/UVtools.Core/FileFormats/LGSFile.cs +++ b/UVtools.Core/FileFormats/LGSFile.cs @@ -225,9 +225,7 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LightOffDelay, }; - public override byte ThumbnailsCount { get; } = 1; - - public override Size[] ThumbnailsOriginalSize { get; } = {new Size(120, 150)}; + public override Size[] ThumbnailsOriginalSize { get; } = {new(120, 150)}; public override uint ResolutionX { diff --git a/UVtools.Core/FileFormats/MDLPFile.cs b/UVtools.Core/FileFormats/MDLPFile.cs new file mode 100644 index 0000000..784ad28 --- /dev/null +++ b/UVtools.Core/FileFormats/MDLPFile.cs @@ -0,0 +1,493 @@ +/* + * 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. + */ + +// https://github.com/cbiffle/catibo/blob/master/doc/cbddlp-ctb.adoc + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +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 MDLPFile : FileFormat + { + #region Constants + + private const uint SlicerInfoAddress = 4 + 7 + 290 * 290 * 2 + 116 * 116 * 2 + 4; + #endregion + + #region Sub Classes + + #region Header + + public sealed class Header + { + public const byte HEADER_SIZE = 7; + public const string HEADER_VALUE = "MKSDLP"; + + [FieldOrder(0)] + [FieldEndianness(Endianness.Big)] + public uint HeaderSize { get; set; } = HEADER_SIZE; + + /// <summary> + /// Gets the file tag = MKSDLP + /// </summary> + [FieldOrder(1)] + [FieldLength(HEADER_SIZE)] + [SerializeAs(SerializedType.TerminatedString)] + public string HeaderValue { get; set; } = HEADER_VALUE; + + public void Validate() + { + if (HeaderSize != HEADER_SIZE || HeaderValue != HEADER_VALUE) + { + throw new FileLoadException("Not a valid Makerbase mdlp file!"); + } + } + } + + public sealed class SlicerInfo + { + // 290 * 290 * 2 + 116 * 116 * 2 + 4 + //[FieldOrder(0)] [FieldLength(195116)] public byte[] PreviewData { get; set; } + + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public ushort LayerCount { get; set; } + [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public ushort ResolutionX { get; set; } + [FieldOrder(2)] [FieldEndianness(Endianness.Big)] public ushort ResolutionY { get; set; } + [FieldOrder(3)] [FieldEndianness(Endianness.Big)] public uint DisplayWidthDataSize { get; set; } = 6; + [FieldOrder(4)] [FieldLength(nameof(DisplayWidthDataSize))] public byte[] DisplayWidth { get; set; } + [FieldOrder(5)] [FieldEndianness(Endianness.Big)] public uint DisplayHeightDataSize { get; set; } = 6; + [FieldOrder(6)] [FieldLength(nameof(DisplayHeightDataSize))] public byte[] DisplayHeight { get; set; } + + [FieldOrder(7)] [FieldEndianness(Endianness.Big)] public uint LayerHeightDataSize { get; set; } = 6; + [FieldOrder(8)] [FieldLength(nameof(LayerHeightDataSize))] public byte[] LayerHeight { get; set; } + [FieldOrder(9)] [FieldEndianness(Endianness.Big)] public ushort ExposureTime { get; set; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort LightOffDelay { get; set; } + [FieldOrder(11)] [FieldEndianness(Endianness.Big)] public ushort BottomExposureTime { get; set; } + [FieldOrder(12)] [FieldEndianness(Endianness.Big)] public ushort BottomLayers { get; set; } + /* [FieldOrder(13)] [FieldEndianness(Endianness.Big)] public ushort BottomLiftHeight { get; set; } + [FieldOrder(14)] [FieldEndianness(Endianness.Big)] public ushort BottomLiftSpeed { get; set; } + [FieldOrder(15)] [FieldEndianness(Endianness.Big)] public ushort LiftHeight { get; set; } + [FieldOrder(16)] [FieldEndianness(Endianness.Big)] public ushort LiftSpeed { get; set; } + [FieldOrder(17)] [FieldEndianness(Endianness.Big)] public ushort RetractSpeed { get; set; } + [FieldOrder(18)] [FieldEndianness(Endianness.Big)] public ushort BottomLightPWM { get; set; } + [FieldOrder(19)] [FieldEndianness(Endianness.Big)] public ushort LightPWM { get; set; }*/ + } + #endregion + + #region LayerDef + + public sealed class LayerDef + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public uint LineCount { get; set; } + [FieldOrder(1)] [FieldCount(nameof(LineCount))] public LayerLine[] Lines { get; set; } + [FieldOrder(2)] public PageBreak PageBreak { get; set; } = new(); + + + public LayerDef(uint lineCount, LayerLine[] lines) + { + LineCount = lineCount; + Lines = lines; + } + } + + public sealed class LayerLine + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public ushort StartY { get; set; } + [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public ushort EndY { get; set; } + [FieldOrder(2)] [FieldEndianness(Endianness.Big)] public ushort StartX { get; set; } + + + public LayerLine() + { } + + public LayerLine(ushort startY, ushort endY, ushort startX) + { + StartY = startY; + EndY = endY; + StartX = startX; + } + } + + public sealed class PageBreak + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public byte Line { get; set; } = 0x0D; + [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public byte Break { get; set; } = 0x0A; + } + + #endregion + #endregion + + #region Properties + + public Header HeaderSettings { get; protected internal set; } = new(); + public SlicerInfo SlicerInfoSettings { get; protected internal set; } = new(); + public override FileFormatType FileType => FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = { + new ("mdlp", "Makerbase MDLP v1"), + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = + { + PrintParameterModifier.BottomLayerCount, + PrintParameterModifier.BottomExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + //PrintParameterModifier.BottomLightOffDelay, + PrintParameterModifier.LightOffDelay, + /*PrintParameterModifier.BottomLiftHeight, + PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM,*/ + }; + + public override Size[] ThumbnailsOriginalSize { get; } = + { + new (116, 116), + new (290, 290) + }; + + public override uint ResolutionX + { + get => SlicerInfoSettings.ResolutionX; + set + { + SlicerInfoSettings.ResolutionX = (ushort) value; + RaisePropertyChanged(); + } + } + + public override uint ResolutionY + { + get => SlicerInfoSettings.ResolutionY; + set + { + SlicerInfoSettings.ResolutionY = (ushort) value; + RaisePropertyChanged(); + } + } + + public override float DisplayWidth + { + get => float.Parse(SlicerInfoSettings.DisplayWidth.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.DisplayWidthDataSize = (uint) (str.Length * 2); + var data = new byte[SlicerInfoSettings.DisplayWidthDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.DisplayWidth = data; + RaisePropertyChanged(); + } + } + + public override float DisplayHeight + { + get => float.Parse(SlicerInfoSettings.DisplayHeight.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.DisplayHeightDataSize = (uint)(str.Length * 2); + var data = new byte[SlicerInfoSettings.DisplayHeightDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.DisplayHeight = data; + RaisePropertyChanged(); + } + } + + public override bool MirrorDisplay { get; set; } + + public override byte AntiAliasing + { + get => 1; + set { } + } + + public override float LayerHeight + { + get => float.Parse(SlicerInfoSettings.LayerHeight.Where(b => b != 0).Aggregate(string.Empty, (current, b) => current + System.Convert.ToChar(b))); + set + { + string str = Layer.RoundHeight(value).ToString(CultureInfo.InvariantCulture); + SlicerInfoSettings.LayerHeightDataSize = (uint)(str.Length * 2); + var data = new byte[SlicerInfoSettings.LayerHeightDataSize]; + for (var i = 0; i < str.Length; i++) + { + data[i * 2] = System.Convert.ToByte(str[i]); + } + + SlicerInfoSettings.LayerHeight = data; + RaisePropertyChanged(); + } + } + + public override uint LayerCount + { + get => base.LayerCount; + set => base.LayerCount = SlicerInfoSettings.LayerCount = (ushort)base.LayerCount; + } + + public override ushort BottomLayerCount + { + get => SlicerInfoSettings.BottomLayers; + set => base.BottomLayerCount = SlicerInfoSettings.BottomLayers = value; + } + + public override float BottomExposureTime + { + get => SlicerInfoSettings.BottomExposureTime; + set => base.BottomExposureTime = SlicerInfoSettings.BottomExposureTime = (ushort)value; + } + + public override float ExposureTime + { + get => SlicerInfoSettings.ExposureTime; + set => base.ExposureTime = SlicerInfoSettings.ExposureTime = (ushort)value; + } + + public override float BottomLightOffDelay + { + get => LightOffDelay; + set { } + } + + public override float LightOffDelay + { + get => SlicerInfoSettings.LightOffDelay; + set => base.LightOffDelay = SlicerInfoSettings.LightOffDelay = (ushort)value; + } + + public override object[] Configs => new[] { (object)HeaderSettings, SlicerInfoSettings }; + + #endregion + + #region Constructors + + #endregion + + #region Methods + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) + { + using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write); + var pageBreak = new PageBreak(); + + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + byte[][] previews = new byte[2][]; + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + previews[i] = new byte[ThumbnailsOriginalSize[i].Area() * 2]; + } + // Previews + Parallel.For(0, previews.Length, previewIndex => + { + if (progress.Token.IsCancellationRequested) return; + if (Thumbnails[previewIndex] is null) return; + var span = Thumbnails[previewIndex].GetPixelSpanByte(); + int index = 0; + for (int i = 0; i < span.Length; i += 3) + { + byte b = span[i]; + byte g = span[i + 1]; + byte r = span[i + 2]; + + ushort rgb15 = (ushort)(((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0)); + + previews[previewIndex][index++] = (byte)(rgb15 >> 8); + previews[previewIndex][index++] = (byte)(rgb15 & 0xff); + } + + if (index != previews[previewIndex].Length) + { + throw new FileLoadException($"Preview encode incomplete encode, expected: {previews[previewIndex].Length}, encoded: {index}"); + } + }); + + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + Helpers.SerializeWriteFileStream(outputFile, previews[i]); + Helpers.SerializeWriteFileStream(outputFile, pageBreak); + } + Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + + progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); + var layerDefs = new LayerDef[LayerCount]; + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + List<LayerLine> layerLines = new(); + var layer = this[layerIndex]; + using var mat = layer.LayerMat; + var span = mat.GetPixelSpanByte(); + + for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++) + { + int y = layer.BoundingRectangle.Y; + int startY = -1; + for (; y < layer.BoundingRectangle.Bottom; y++) + { + int pos = mat.GetPixelPos(x, y); + if (span[pos] < 128) // Black pixel + { + if(startY == -1) continue; // Keep ignoring + layerLines.Add(new LayerLine((ushort) startY, (ushort) (y-1), (ushort) x)); + startY = -1; + } + else // White pixel + { + if (startY >= 0) continue; // Keep sum + startY = y; + } + } + + if (startY >= 0) + { + layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort) x)); + } + } + + layerDefs[layerIndex] = new LayerDef((uint) layerLines.Count, layerLines.ToArray()); + + progress.LockAndIncrement(); + }); + + progress.Reset(OperationProgress.StatusWritingFile, LayerCount); + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + Helpers.SerializeWriteFileStream(outputFile, layerDefs[layerIndex]); + progress++; + } + + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + Debug.WriteLine("Encode Results:"); + Debug.WriteLine(HeaderSettings); + Debug.WriteLine("-End-"); + } + + + + protected override void DecodeInternally(string fileFullPath, OperationProgress 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)))); + HeaderSettings = Helpers.Deserialize<Header>(inputFile); + HeaderSettings.Validate(); + + byte[][] previews = new byte[ThumbnailsOriginalSize.Length][]; + for (int i = 0; i < ThumbnailsOriginalSize.Length; i++) + { + previews[i] = new byte[ThumbnailsOriginalSize[i].Area() * 2]; + inputFile.ReadBytes(previews[i]); + inputFile.Seek(2, SeekOrigin.Current); + } + + Parallel.For(0, previews.Length, previewIndex => + { + var mat = new Mat(ThumbnailsOriginalSize[previewIndex], DepthType.Cv8U, 3); + var span = mat.GetPixelSpanByte(); + + int spanIndex = 0; + for (int i = 0; i < previews[previewIndex].Length; i += 2) + { + ushort rgb15 = (ushort)((ushort)(previews[previewIndex][i + 0] << 8) | previews[previewIndex][i + 1]); + byte r = (byte)((rgb15 >> 11) << 3); + byte g = (byte)((rgb15 >> 5) << 2); + byte b = (byte)((rgb15 >> 0) << 3); + + span[spanIndex++] = b; + span[spanIndex++] = g; + span[spanIndex++] = r; + } + + Thumbnails[previewIndex] = mat; + }); + + + SlicerInfoSettings = Helpers.Deserialize<SlicerInfo>(inputFile); + + LayerManager.Init(SlicerInfoSettings.LayerCount); + progress.ItemCount = LayerCount; + LayerDef[] layerDefs = new LayerDef[LayerCount]; + for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + layerDefs[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile); + progress++; + } + + progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using var mat = EmguExtensions.InitMat(Resolution); + foreach (var line in layerDefs[layerIndex].Lines) + { + CvInvoke.Line(mat, new Point(line.StartX, line.StartY), new Point(line.StartX, line.EndY), EmguExtensions.WhiteByte); + } + + this[layerIndex] = new Layer((uint) layerIndex, mat, this); + progress.LockAndIncrement(); + }); + + HeaderSettings = Helpers.Deserialize<Header>(inputFile); + HeaderSettings.Validate(); + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (RequireFullEncode) + { + 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(SlicerInfoAddress, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + } + + #endregion + } +} diff --git a/UVtools.Core/FileFormats/MakerbaseFile.cs b/UVtools.Core/FileFormats/MakerbaseFile.cs deleted file mode 100644 index 96c49d1..0000000 --- a/UVtools.Core/FileFormats/MakerbaseFile.cs +++ /dev/null @@ -1,240 +0,0 @@ -/* - * 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. - */ - -// https://github.com/cbiffle/catibo/blob/master/doc/cbddlp-ctb.adoc - -using System; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using BinarySerialization; -using UVtools.Core.Operations; - -namespace UVtools.Core.FileFormats -{ - public class MakerbaseFile : FileFormat - { - #region Constants - private const uint MAGIC_CBDDLP = 0x12FD0019; - private const uint MAGIC_CBT = 0x12FD0086; - private const ushort REPEATRGB15MASK = 0x20; - - private const byte RLE8EncodingLimit = 0x7d; // 125; - private const ushort RLE16EncodingLimit = 0xFFF; - #endregion - - #region Sub Classes - - #region Header - public class Header - { - public const string TagValue = "MKSDLP"; - //[FieldOrder(0)] public uint Offset1 { get; set; } - - /// <summary> - /// Gets the file tag = MKSDLP - /// </summary> - //[SerializeAs(SerializedType.TerminatedString)] - [FieldOrder(0)] [FieldOffset(4)] [FieldLength(6)] public string Tag { get; set; } = TagValue; - - // 290 * 290 * 2 + 116 * 116 * 2 + 4 + 1 - [FieldOrder(1)] [FieldLength(195116)] public byte[] PreviewData { get; set; } - - [FieldOrder(2)] public ushort MaxSize { get; set; } - [FieldOrder(3)] public ushort ResolutionX { get; set; } - [FieldOrder(4)] public ushort ResolutionY { get; set; } - - - } - #endregion - - #endregion - - #region Properties - - public Header HeaderSettings { get; protected internal set; } = new Header(); - public override FileFormatType FileType => FileFormatType.Binary; - - public override FileExtension[] FileExtensions { get; } = { - new ("mdlp", "Makerbase MDLP Files"), - new ("gr1", "Workshop GR1 Files"), - }; - - public override PrintParameterModifier[] PrintParameterModifiers { get; } = - { - PrintParameterModifier.BottomLayerCount, - PrintParameterModifier.BottomExposureSeconds, - PrintParameterModifier.ExposureSeconds, - - PrintParameterModifier.BottomLightOffDelay, - PrintParameterModifier.LightOffDelay, - PrintParameterModifier.BottomLiftHeight, - PrintParameterModifier.BottomLiftSpeed, - PrintParameterModifier.LiftHeight, - PrintParameterModifier.LiftSpeed, - PrintParameterModifier.RetractSpeed, - - PrintParameterModifier.BottomLightPWM, - PrintParameterModifier.LightPWM, - }; - - public override byte ThumbnailsCount { get; } = 0; - - public override Size[] ThumbnailsOriginalSize { get; } = {new (290, 290), new (116, 116) }; - - public override uint ResolutionX - { - get => 0; - set - { - } - } - - public override uint ResolutionY - { - get => 0; - set { } - } - - public override float DisplayWidth { get; set; } - public override float DisplayHeight { get; set; } - public override bool MirrorDisplay { get; set; } - - public override byte AntiAliasing - { - get => 1; - set { } - } - - public override float LayerHeight - { - get => 0; - set { } - } - - public override uint LayerCount - { - set - { - - /*HeaderSettings.LayerCount = LayerCount; - HeaderSettings.OverallHeightMilimeter = TotalHeight;*/ - } - } - - public override ushort BottomLayerCount => 0; - - public override float BottomExposureTime => 0; - - public override float ExposureTime => 0; - public override float LiftHeight => 0; - public override float LiftSpeed => 0; - public override float RetractSpeed => 0; - - public override float PrintTime => 0; - - public override float MaterialMilliliters => 0; - - public override float MaterialCost => 0; - - public override string MaterialName => "Unknown"; - public override string MachineName => null; - - public override object[] Configs => new[] { (object)HeaderSettings }; - - #endregion - - #region Constructors - - #endregion - - #region Methods - protected override void EncodeInternally(string fileFullPath, OperationProgress progress) - { - uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); - using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) - { - - outputFile.Seek((int) currentOffset, SeekOrigin.Begin); - - - - } - - Debug.WriteLine("Encode Results:"); - Debug.WriteLine(HeaderSettings); - Debug.WriteLine("-End-"); - } - - - - protected override void DecodeInternally(string fileFullPath, OperationProgress 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)))); - HeaderSettings = Helpers.Deserialize<Header>(inputFile); - if (HeaderSettings.Tag != Header.TagValue) - { - throw new FileLoadException("Not a valid Makerbase file!", fileFullPath); - } - - /*var rng = 290 * 290 * 2 + 116 * 116 * 2 + 4; - byte[] buffer = new byte[rng]; - inputFile.Read(buffer, 0, rng);*/ - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - if (RequireFullEncode) - { - 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(0, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - - if (HeaderSettings.Version >= 2 && HeaderSettings.PrintParametersOffsetAddress > 0) - { - outputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, PrintParametersSettings); - Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); - } - - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) - { - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - outputFile.Seek(layerOffset, SeekOrigin.Begin); - layerOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinitions[aaIndex, layerIndex]); - } - } - }*/ - - //Decode(FileFullPath, progress); - } - - #endregion - } -} diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs index 2a6491f..3131b43 100644 --- a/UVtools.Core/FileFormats/PHZFile.cs +++ b/UVtools.Core/FileFormats/PHZFile.cs @@ -678,18 +678,18 @@ namespace UVtools.Core.FileFormats #region Properties - public Header HeaderSettings { get; protected internal set; } = new Header(); + public Header HeaderSettings { get; protected internal set; } = new(); public Preview[] Previews { get; protected internal set; } public LayerData[] LayersDefinitions { get; private set; } - public Dictionary<string, LayerData> LayersHash { get; } = new Dictionary<string, LayerData>(); + public Dictionary<string, LayerData> LayersHash { get; } = new (); public override FileFormatType FileType => FileFormatType.Binary; public override FileExtension[] FileExtensions { get; } = { - new FileExtension("phz", "Chitubox PHZ"), + new ("phz", "Chitubox PHZ"), }; public override PrintParameterModifier[] PrintParameterModifiers { get; } = @@ -715,9 +715,11 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LightOffDelay, }; - public override byte ThumbnailsCount { get; } = 2; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(400, 300), new System.Drawing.Size(200, 125)}; + public override Size[] ThumbnailsOriginalSize { get; } = + { + new(400, 300), + new(200, 125) + }; public override uint ResolutionX { @@ -760,9 +762,9 @@ namespace UVtools.Core.FileFormats } } - public override float MaxPrintHeight + public override float MachineZ { - get => HeaderSettings.BedSizeZ > 0 ? HeaderSettings.BedSizeZ : base.MaxPrintHeight; + get => HeaderSettings.BedSizeZ > 0 ? HeaderSettings.BedSizeZ : base.MachineZ; set { HeaderSettings.BedSizeZ = (float)Math.Round(value, 2); diff --git a/UVtools.Core/FileFormats/PhotonSFile.cs b/UVtools.Core/FileFormats/PhotonSFile.cs index d881a4f..e2391a7 100644 --- a/UVtools.Core/FileFormats/PhotonSFile.cs +++ b/UVtools.Core/FileFormats/PhotonSFile.cs @@ -220,9 +220,8 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.RetractSpeed, }; - public override byte ThumbnailsCount { get; } = 1; - public override Size[] ThumbnailsOriginalSize { get; } = {new Size(225, 168) }; + public override Size[] ThumbnailsOriginalSize { get; } = {new(225, 168) }; public override uint ResolutionX { diff --git a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs index 00e9670..4fbe61f 100644 --- a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs +++ b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs @@ -941,9 +941,7 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LiftSpeed, }; - public override byte ThumbnailsCount { get; } = 1; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new Size(224, 168)}; + public override Size[] ThumbnailsOriginalSize { get; } = {new(224, 168)}; public override uint ResolutionX { @@ -1014,7 +1012,7 @@ namespace UVtools.Core.FileFormats set { } } - public override float MaxPrintHeight + public override float MachineZ { get { diff --git a/UVtools.Core/FileFormats/SL1File.cs b/UVtools.Core/FileFormats/SL1File.cs index 05e32ae..d17adab 100644 --- a/UVtools.Core/FileFormats/SL1File.cs +++ b/UVtools.Core/FileFormats/SL1File.cs @@ -311,10 +311,11 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.ExposureSeconds, }; - public override byte ThumbnailsCount { get; } = 2; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = { new System.Drawing.Size(400, 400), new System.Drawing.Size(800, 480) }; - //public override Image<Rgba32>[] Thumbnails { get; set; } + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = + { + new(400, 400), + new(800, 480) + }; public override uint ResolutionX { @@ -356,7 +357,7 @@ namespace UVtools.Core.FileFormats } } - public override float MaxPrintHeight + public override float MachineZ { get => PrinterSettings.MaxPrintHeight; set diff --git a/UVtools.Core/FileFormats/UVJFile.cs b/UVtools.Core/FileFormats/UVJFile.cs index bc45523..2838811 100644 --- a/UVtools.Core/FileFormats/UVJFile.cs +++ b/UVtools.Core/FileFormats/UVJFile.cs @@ -161,9 +161,11 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LightPWM, }; - public override byte ThumbnailsCount { get; } = 2; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new(400, 400), new(800, 480) }; + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = + { + new(400, 400), + new(800, 480) + }; public override uint ResolutionX { @@ -340,39 +342,37 @@ namespace UVtools.Core.FileFormats }); } - using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) - { - outputFile.PutFileContent(FileConfigName, JsonConvert.SerializeObject(JsonSettings), ZipArchiveMode.Create); + using ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create); + outputFile.PutFileContent(FileConfigName, JsonConvert.SerializeObject(JsonSettings), ZipArchiveMode.Create); - if (CreatedThumbnailsCount > 0) - { - using var stream = outputFile.CreateEntry(FilePreviewTinyName).Open(); - using var vec = new VectorOfByte(); - CvInvoke.Imencode(".png", Thumbnails[0], vec); - stream.WriteBytes(vec.ToArray()); - stream.Close(); - } + if (CreatedThumbnailsCount > 0) + { + using var stream = outputFile.CreateEntry(FilePreviewTinyName).Open(); + using var vec = new VectorOfByte(); + CvInvoke.Imencode(".png", Thumbnails[0], vec); + stream.WriteBytes(vec.ToArray()); + stream.Close(); + } - if (CreatedThumbnailsCount > 1) - { - using Stream stream = outputFile.CreateEntry(FilePreviewHugeName).Open(); - using var vec = new VectorOfByte(); - CvInvoke.Imencode(".png", Thumbnails[1], vec); - stream.WriteBytes(vec.ToArray()); - stream.Close(); - } + if (CreatedThumbnailsCount > 1) + { + using Stream stream = outputFile.CreateEntry(FilePreviewHugeName).Open(); + using var vec = new VectorOfByte(); + CvInvoke.Imencode(".png", Thumbnails[1], vec); + stream.WriteBytes(vec.ToArray()); + stream.Close(); + } - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - progress.Token.ThrowIfCancellationRequested(); + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); - Layer layer = this[layerIndex]; + Layer layer = this[layerIndex]; - var layerimagePath = $"{FolderImageName}/{layerIndex:D8}.png"; - outputFile.PutFileContent(layerimagePath, layer.CompressedBytes, ZipArchiveMode.Create); + var layerimagePath = $"{FolderImageName}/{layerIndex:D8}.png"; + outputFile.PutFileContent(layerimagePath, layer.CompressedBytes, ZipArchiveMode.Create); - progress++; - } + progress++; } } @@ -418,7 +418,8 @@ namespace UVtools.Core.FileFormats entry = inputFile.GetEntry($"{FolderImageName}/{layerIndex:D8}.png"); if (entry is null) continue; - this[layerIndex] = new Layer(layerIndex, entry.Open(), LayerManager) + using var stream = entry.Open(); + this[layerIndex] = new Layer(layerIndex, stream, LayerManager) { PositionZ = JsonSettings.Layers.Count >= layerIndex ? JsonSettings.Layers[(int) layerIndex].Z : GetHeightFromLayer(layerIndex), LiftHeight = JsonSettings.Layers.Count >= layerIndex ? JsonSettings.Layers[(int)layerIndex].Exposure.LiftHeight : GetInitialLayerValueOrNormal(layerIndex, BottomLiftHeight, LiftHeight), diff --git a/UVtools.Core/FileFormats/VDTFile.cs b/UVtools.Core/FileFormats/VDTFile.cs new file mode 100644 index 0000000..5dfa02c --- /dev/null +++ b/UVtools.Core/FileFormats/VDTFile.cs @@ -0,0 +1,518 @@ +/* + * 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.IO; +using System.IO.Compression; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Util; +using Newtonsoft.Json; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class VDTFile : FileFormat + { + #region Constants + + private const string FileManifestName = "manifest.json"; + private static string[] FilePreviewNames = { + "Preview_Top.png", + "Preview_Top_48.png", + "Preview_Right.png", + "Preview_Right_48.png", + "Preview_Left.png", + "Preview_Left_48.png", + "Preview_Front.png", + "Preview_Front_48.png", + "Preview_FLT.png", + "Preview_FLT_48.png", + }; + #endregion + + #region Sub Classes + + public sealed class VDTManifest + { + [JsonProperty("application_name")] public string ApplicationName { get; set; } = "Voxeldance Tango"; + + [JsonProperty("application_version")] public string ApplicationVersion { get; set; } = "2.1.15.14"; + //2021-04-09 17:48:46 + [JsonProperty("create_datetime")] public string CreateDateTime { get; set; } = DateTime.Now.ToString("u"); + + [JsonProperty("file_version")] public byte FileVersion { get; set; } = 1; + + [JsonProperty("project_name")] public string ProjectName { get; set; } = "UVtools"; + + [JsonProperty("machine")] public VDTMachine Machine { get; set; } = new(); + [JsonProperty("advanced_parameters")] public VDTAdvancedParameters AdvancedParameters { get; set; } = new(); + [JsonProperty("resin")] public VDTResin Resin { get; set; } = new(); + [JsonProperty("print")] public VDTPrint Print { get; set; } = new(); + [JsonProperty("print_statistics")] public VDTPrintStatistics PrintStatistics { get; set; } = new(); + [JsonProperty("layers")] public VDTLayer[] Layers { get; set; } + } + + public sealed class VDTAdvancedParameters + { + [JsonProperty("antialasing_level")] public byte AntialasingLevel { get; set; } = 1; + [JsonProperty("grey_level")] public byte GreyLevel { get; set; } + [JsonProperty("image_blur_level")] public byte ImageBlurLevel { get; set; } + + [JsonProperty("bottom_light_pwm")] public byte BottomLightPWM { get; set; } = DefaultBottomLightPWM; + [JsonProperty("light_pwm")] public byte LightPWM { get; set; } = DefaultLightPWM; + + [JsonProperty("beam_compensation")] public float BeamCompensation { get; set; } + } + + public sealed class VDTMachine + { + [JsonProperty("name")] public string Name { get; set; } = "Unknown"; + [JsonProperty("type")] public string Type { get; set; } = "Default"; + [JsonProperty("lcd_width")] public float DisplayWidth { get; set; } + [JsonProperty("lcd_height")] public float DisplayHeight { get; set; } + [JsonProperty("z_height")] public float ZHeight { get; set; } + [JsonProperty("resolution_x")] public ushort ResolutionX { get; set; } + [JsonProperty("resolution_y")] public ushort ResolutionY { get; set; } + [JsonProperty("x_mirror")] public bool XMirror { get; set; } + [JsonProperty("y_mirror")] public bool YMirror { get; set; } + } + + public sealed class VDTResin + { + [JsonProperty("name")] public string Name { get; set; } + [JsonProperty("cost")] public float Cost { get; set; } + [JsonProperty("density")] public float Density { get; set; } = 1; + } + + public sealed class VDTPrint + { + [JsonProperty("layer_thickness")] public float LayerHeight { get; set; } + [JsonProperty("bottom_layers")] public ushort BottomLayers { get; set; } = DefaultBottomLayerCount; + [JsonProperty("bottom_exposure_time")] public float BottomExposureTime { get; set; } = DefaultBottomExposureTime; + [JsonProperty("exposure_time")] public float ExposureTime { get; set; } = DefaultExposureTime; + [JsonProperty("bottom_lift_distance")] public float BottomLiftHeight { get; set; } = DefaultBottomLiftHeight; + [JsonProperty("lift_distance")] public float LiftHeight { get; set; } = DefaultLiftHeight; + [JsonProperty("bottom_lift_speed")] public float BottomLiftSpeed { get; set; } = DefaultBottomLiftSpeed; + [JsonProperty("lift_speed")] public float LiftSpeed { get; set; } = DefaultLiftSpeed; + [JsonProperty("bottom_retract_speed")] public float BottomRetractSpeed { get; set; } = DefaultRetractSpeed; + [JsonProperty("retract_speed")] public float RetractSpeed { get; set; } = DefaultRetractSpeed; + [JsonProperty("bottom_light_off_delay")] public float BottomLightOffDelay { get; set; } = DefaultBottomLightOffDelay; + [JsonProperty("light_off_delay")] public float LightOffDelay { get; set; } = DefaultLightOffDelay; + } + + public sealed class VDTPrintStatistics + { + [JsonProperty("price")] public float Price { get; set; } + [JsonProperty("price_currency")] public string PriceCurrency { get; set; } = "€"; + [JsonProperty("estimated_time")] public uint EstimatedTime { get; set; } + [JsonProperty("volume")] public float Volume { get; set; } + [JsonProperty("weight")] public float Weight { get; set; } + } + + public sealed class VDTLayer + { + [JsonProperty("height")] public float PositionZ { get; set; } + [JsonProperty("exposure_time")] public float ExposureTime { get; set; } = DefaultExposureTime; + [JsonProperty("lift_distance")] public float LiftHeight { get; set; } = DefaultLiftHeight; + [JsonProperty("lift_speed")] public float LiftSpeed { get; set; } = DefaultLiftSpeed; + [JsonProperty("retract_speed")] public float RetractSpeed { get; set; } = DefaultRetractSpeed; + [JsonProperty("light_off_delay")] public float LightOffDelay { get; set; } = DefaultLightOffDelay; + [JsonProperty("light_pwm")] public byte LightPWM { get; set; } = DefaultLightPWM; + } + + #endregion + + #region Properties + public VDTManifest ManifestFile { get; set; } = new(); + + public override FileFormatType FileType => FileFormatType.Archive; + + public override FileExtension[] FileExtensions { get; } = { + new("vdt", "Voxeldance Tango VDT") + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = { + PrintParameterModifier.BottomLayerCount, + PrintParameterModifier.BottomExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + PrintParameterModifier.BottomLightOffDelay, + PrintParameterModifier.LightOffDelay, + PrintParameterModifier.BottomLiftHeight, + PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM, + }; + + public override PrintParameterModifier[] PrintParameterPerLayerModifiers { get; } = { + PrintParameterModifier.ExposureSeconds, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + PrintParameterModifier.LightOffDelay, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM, + }; + + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = + { + new(200, 200), + new(48, 48), + new(200, 200), + new(48, 48), + new(200, 200), + new(48, 48), + new(200, 200), + new(48, 48), + new(200, 200), + new(48, 48), + }; + + public override uint ResolutionX + { + get => ManifestFile.Machine.ResolutionX; + set + { + ManifestFile.Machine.ResolutionX = (ushort) value; + RaisePropertyChanged(); + } + } + + public override uint ResolutionY + { + get => ManifestFile.Machine.ResolutionY; + set + { + ManifestFile.Machine.ResolutionY = (ushort) value; + RaisePropertyChanged(); + } + } + + public override float DisplayWidth + { + get => ManifestFile.Machine.DisplayWidth; + set + { + ManifestFile.Machine.DisplayWidth = (float) Math.Round(value, 2); + RaisePropertyChanged(); + } + } + + public override float DisplayHeight + { + get => ManifestFile.Machine.DisplayHeight; + set + { + ManifestFile.Machine.DisplayHeight = (float)Math.Round(value, 2); + RaisePropertyChanged(); + } + } + + public override float MachineZ + { + get => ManifestFile.Machine.ZHeight > 0 ? ManifestFile.Machine.ZHeight : base.MachineZ; + set + { + ManifestFile.Machine.ZHeight = (float)Math.Round(value, 2); + RaisePropertyChanged(); + } + } + + public override bool MirrorDisplay => ManifestFile.Machine.XMirror || ManifestFile.Machine.YMirror; + + public override byte AntiAliasing + { + get => ManifestFile.AdvancedParameters.AntialasingLevel; + set + { + ManifestFile.AdvancedParameters.AntialasingLevel = value.Clamp(1, 16); + RaisePropertyChanged(); + } + + } + + public override float LayerHeight + { + get => ManifestFile.Print.LayerHeight; + set + { + ManifestFile.Print.LayerHeight = Layer.RoundHeight(value); + RaisePropertyChanged(); + } + } + + /* public override uint LayerCount + { + get => base.LayerCount; + set => base.LayerCount = base.LayerCount; + }*/ + + public override ushort BottomLayerCount + { + get => ManifestFile.Print.BottomLayers; + set => base.BottomLayerCount = ManifestFile.Print.BottomLayers = value; + } + + public override float BottomExposureTime + { + get => ManifestFile.Print.BottomExposureTime; + set => base.BottomExposureTime = ManifestFile.Print.BottomExposureTime = (float)Math.Round(value, 2); + } + + public override float ExposureTime + { + get => ManifestFile.Print.ExposureTime; + set => base.ExposureTime = ManifestFile.Print.ExposureTime = (float)Math.Round(value, 2); + } + + public override float BottomLightOffDelay + { + get => ManifestFile.Print.BottomLightOffDelay; + set => base.BottomLightOffDelay = ManifestFile.Print.BottomLightOffDelay = (float)Math.Round(value, 2); + } + + public override float LightOffDelay + { + get => ManifestFile.Print.LightOffDelay; + set => base.LightOffDelay = ManifestFile.Print.LightOffDelay = (float)Math.Round(value, 2); + } + + public override float BottomLiftHeight + { + get => ManifestFile.Print.BottomLiftHeight; + set => base.BottomLiftHeight = ManifestFile.Print.BottomLiftHeight = (float)Math.Round(value, 2); + } + + public override float LiftHeight + { + get => ManifestFile.Print.LiftHeight; + set => base.LiftHeight = ManifestFile.Print.LiftHeight = (float)Math.Round(value, 2); + } + + public override float BottomLiftSpeed + { + get => ManifestFile.Print.BottomLiftSpeed; + set => base.BottomLiftSpeed = ManifestFile.Print.BottomLiftSpeed = (float)Math.Round(value, 2); + } + + public override float LiftSpeed + { + get => ManifestFile.Print.LiftSpeed; + set => base.LiftSpeed = ManifestFile.Print.LiftSpeed = (float)Math.Round(value, 2); + } + + public override float RetractSpeed + { + get => ManifestFile.Print.RetractSpeed; + set => base.RetractSpeed = ManifestFile.Print.RetractSpeed = ManifestFile.Print.BottomRetractSpeed = (float)Math.Round(value, 2); + } + + public override byte BottomLightPWM + { + get => ManifestFile.AdvancedParameters.BottomLightPWM; + set => base.BottomLightPWM = ManifestFile.AdvancedParameters.BottomLightPWM = value; + } + + public override byte LightPWM + { + get => ManifestFile.AdvancedParameters.LightPWM; + set => base.LightPWM = ManifestFile.AdvancedParameters.LightPWM = value; + } + + public override float PrintTime + { + get => base.PrintTime; + set + { + base.PrintTime = value; + ManifestFile.PrintStatistics.EstimatedTime = (uint)base.PrintTime; + } + } + + public override float MaterialMilliliters + { + get => base.MaterialMilliliters; + set + { + base.MaterialMilliliters = value; + ManifestFile.PrintStatistics.Volume = base.MaterialMilliliters; + } + } + + public override float MaterialGrams + { + get => (float)Math.Round(ManifestFile.PrintStatistics.Weight, 3); + set => base.MaterialGrams = ManifestFile.PrintStatistics.Weight = (float)Math.Round(value, 3); + } + + public override string MaterialName + { + get => ManifestFile.Resin.Name; + set => base.MaterialName = ManifestFile.Resin.Name = value; + } + + public override float MaterialCost + { + get => (float)Math.Round(ManifestFile.Resin.Cost, 3); + set => base.MaterialCost = ManifestFile.Resin.Cost = (float)Math.Round(value, 3); + } + + public override string MachineName + { + get => ManifestFile.Machine.Name; + set => base.MachineName = ManifestFile.Machine.Name = value; + } + + public override object[] Configs => new[] {(object)ManifestFile, ManifestFile.Machine, ManifestFile.AdvancedParameters, ManifestFile.Resin, ManifestFile.Print, ManifestFile.PrintStatistics}; + #endregion + + #region Methods + + public void RebuildVDTLayers() + { + ManifestFile.CreateDateTime = DateTime.Now.ToString("u"); + var layers = new VDTLayer[LayerCount]; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + var layer = this[layerIndex]; + layers[layerIndex] = new VDTLayer + { + PositionZ = layer.PositionZ, + ExposureTime = layer.ExposureTime, + LiftHeight = layer.LiftHeight, + LiftSpeed = layer.LiftSpeed, + RetractSpeed = layer.RetractSpeed, + LightOffDelay = layer.LightOffDelay, + LightPWM = layer.LightPWM + }; + } + + ManifestFile.Layers = layers; + } + + protected override void EncodeInternally(string fileFullPath, OperationProgress progress) + { + // Redo layer data + RebuildVDTLayers(); + + using var outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create); + outputFile.PutFileContent(FileManifestName, JsonConvert.SerializeObject(ManifestFile), ZipArchiveMode.Create); + + if (CreatedThumbnailsCount > 0) + { + for (int i = 0; i < FilePreviewNames.Length; i++) + { + if(Thumbnails.Length <= i) break; + if(Thumbnails[i] is null) continue; + + using var stream = outputFile.CreateEntry(FilePreviewNames[i]).Open(); + using var vec = new VectorOfByte(); + CvInvoke.Imencode(".png", Thumbnails[i], vec); + stream.WriteBytes(vec.ToArray()); + stream.Close(); + } + } + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + + var layer = this[layerIndex]; + var layerImagePath = $"{layerIndex}.png"; + outputFile.PutFileContent(layerImagePath, layer.CompressedBytes, ZipArchiveMode.Create); + progress++; + } + } + + protected override void DecodeInternally(string fileFullPath, OperationProgress progress) + { + using (var inputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Read)) + { + var entry = inputFile.GetEntry(FileManifestName); + if (entry is null) + { + Clear(); + throw new FileLoadException($"{FileManifestName} not found", fileFullPath); + } + + ManifestFile = Helpers.JsonDeserializeObject<VDTManifest>(entry.Open()); + + LayerManager.Init((uint) ManifestFile.Layers.Length); + + for (int i = 0; i < FilePreviewNames.Length; i++) + { + if (Thumbnails.Length <= i) break; + + entry = inputFile.GetEntry(FilePreviewNames[i]); + if (entry is null) continue; + using var stream = entry.Open(); + Mat image = new(); + CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); + Thumbnails[i] = image; + stream.Close(); + } + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + var manifestLayer = ManifestFile.Layers[layerIndex]; + entry = inputFile.GetEntry($"{layerIndex}.png"); + if (entry is null) continue; + using var stream = entry.Open(); + this[layerIndex] = new Layer(layerIndex, stream, LayerManager) + { + PositionZ = manifestLayer.PositionZ, + ExposureTime = manifestLayer.ExposureTime, + LiftHeight = manifestLayer.LiftHeight, + LiftSpeed = manifestLayer.LiftSpeed, + RetractSpeed = manifestLayer.RetractSpeed, + LightOffDelay = manifestLayer.LightOffDelay, + LightPWM = manifestLayer.LightPWM, + }; + } + + progress.ProcessedItems++; + } + + LayerManager.GetBoundingRectangle(progress); + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (RequireFullEncode) + { + if (!string.IsNullOrEmpty(filePath)) + { + FileFullPath = filePath; + } + Encode(FileFullPath, progress); + return; + } + + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + + } + + RebuildVDTLayers(); + using var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update); + outputFile.PutFileContent(FileManifestName, JsonConvert.SerializeObject(ManifestFile), ZipArchiveMode.Update); + + //Decode(FileFullPath, progress); + } + #endregion + } +} diff --git a/UVtools.Core/FileFormats/ZCodeFile.cs b/UVtools.Core/FileFormats/ZCodeFile.cs index 4d42c1d..412adb5 100644 --- a/UVtools.Core/FileFormats/ZCodeFile.cs +++ b/UVtools.Core/FileFormats/ZCodeFile.cs @@ -22,7 +22,6 @@ using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.OpenSsl; using UVtools.Core.Extensions; using UVtools.Core.GCode; -using UVtools.Core.Objects; using UVtools.Core.Operations; namespace UVtools.Core.FileFormats @@ -190,7 +189,7 @@ namespace UVtools.Core.FileFormats public override FileFormatType FileType => FileFormatType.Archive; public override FileExtension[] FileExtensions { get; } = { - new("zcode", "UnizMaker ZCode files") + new("zcode", "UnizMaker ZCode") }; public override PrintParameterModifier[] PrintParameterModifiers { get; } = { @@ -219,8 +218,6 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LightPWM, }; - public override byte ThumbnailsCount { get; } = 1; - public override Size[] ThumbnailsOriginalSize { get; } = {new(640, 480)}; public override uint ResolutionX @@ -255,9 +252,9 @@ namespace UVtools.Core.FileFormats set => RaisePropertyChanged(); } - public override float MaxPrintHeight + public override float MachineZ { - get => ManifestFile.Device.MachineZ > 0 ? ManifestFile.Device.MachineZ : base.MaxPrintHeight; + get => ManifestFile.Device.MachineZ > 0 ? ManifestFile.Device.MachineZ : base.MachineZ; set { ManifestFile.Device.MachineZ = (ushort) value; diff --git a/UVtools.Core/FileFormats/ZCodexFile.cs b/UVtools.Core/FileFormats/ZCodexFile.cs index e0137a4..297ad99 100644 --- a/UVtools.Core/FileFormats/ZCodexFile.cs +++ b/UVtools.Core/FileFormats/ZCodexFile.cs @@ -182,8 +182,6 @@ namespace UVtools.Core.FileFormats PrintParameterModifier.LightPWM, }; - public override byte ThumbnailsCount { get; } = 1; - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new(320, 180)}; public override uint ResolutionX diff --git a/UVtools.Core/GCode/GCodeBuilder.cs b/UVtools.Core/GCode/GCodeBuilder.cs index c59bb56..2ac2609 100644 --- a/UVtools.Core/GCode/GCodeBuilder.cs +++ b/UVtools.Core/GCode/GCodeBuilder.cs @@ -545,7 +545,7 @@ namespace UVtools.Core.GCode lastZPosition = layer.PositionZ; } - float finalRaiseZPosition = slicerFile.MaxPrintHeight; + float finalRaiseZPosition = slicerFile.MachineZ; switch (GCodePositioningType) { diff --git a/UVtools.Core/Helpers.cs b/UVtools.Core/Helpers.cs index 5217f80..5ba461c 100644 --- a/UVtools.Core/Helpers.cs +++ b/UVtools.Core/Helpers.cs @@ -41,10 +41,8 @@ namespace UVtools.Core public static uint SerializeWriteFileStream(FileStream fs, object value, int offset = 0) { - using (MemoryStream stream = Serialize(value)) - { - return fs.WriteStream(stream, offset); - } + using MemoryStream stream = Serialize(value); + return fs.WriteStream(stream, offset); } public static T JsonDeserializeObject<T>(Stream stream) diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs index 1895bcd..84ab961 100644 --- a/UVtools.Core/Layer/LayerManager.cs +++ b/UVtools.Core/Layer/LayerManager.cs @@ -166,7 +166,7 @@ namespace UVtools.Core public void Clear() { //Layers = Array.Empty<Layer>(); - Layers = null; + _layers = null; } public bool Contains(Layer layer) @@ -869,9 +869,9 @@ namespace UVtools.Core return true; } - if (printHeightConfig.Enabled && SlicerFile.MaxPrintHeight > 0) + if (printHeightConfig.Enabled && SlicerFile.MachineZ > 0) { - float printHeightWithOffset = Layer.RoundHeight(SlicerFile.MaxPrintHeight + printHeightConfig.Offset); + float printHeightWithOffset = Layer.RoundHeight(SlicerFile.MachineZ + printHeightConfig.Offset); if (SlicerFile.PrintHeight > printHeightWithOffset) { foreach (var layer in this) diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs index a7fa89e..6b45374 100644 --- a/UVtools.Core/Operations/OperationLayerReHeight.cs +++ b/UVtools.Core/Operations/OperationLayerReHeight.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading.Tasks; using Emgu.CV; @@ -22,6 +23,8 @@ namespace UVtools.Core.Operations { #region Members private OperationLayerReHeightItem _item; + private bool _antiAliasing = true; + #endregion #region Overrides @@ -66,10 +69,23 @@ namespace UVtools.Core.Operations #endregion #region Properties + public OperationLayerReHeightItem Item { get => _item; - set => RaiseAndSetIfChanged(ref _item, value); + set + { + if(!RaiseAndSetIfChanged(ref _item, value)) return; + RaisePropertyChanged(nameof(AntiAliasing)); + } + } + + public bool CanAntiAliasing => _item?.IsMultiply ?? false; + + public bool AntiAliasing + { + get => _antiAliasing; + set => RaiseAndSetIfChanged(ref _antiAliasing, value); } @@ -135,19 +151,19 @@ namespace UVtools.Core.Operations #region Methods protected override bool ExecuteInternally(OperationProgress progress) { - progress.ItemCount = Item.LayerCount; + progress.ItemCount = _item.LayerCount; - var layers = new Layer[Item.LayerCount]; + var layers = new Layer[_item.LayerCount]; - uint newLayerIndex = 0; - for (uint layerIndex = 0; layerIndex < SlicerFile.LayerCount; layerIndex++) + if (_item.IsDivision) { - progress.Token.ThrowIfCancellationRequested(); - - var oldLayer = SlicerFile[layerIndex]; - if (Item.IsDivision) + uint newLayerIndex = 0; + for (uint layerIndex = 0; layerIndex < SlicerFile.LayerCount; layerIndex++) { - for (byte i = 0; i < Item.Modifier; i++) + progress.Token.ThrowIfCancellationRequested(); + + var oldLayer = SlicerFile[layerIndex]; + for (byte i = 0; i < _item.Modifier; i++) { var newLayer = oldLayer.Clone(); //newLayer.Index = newLayerIndex; @@ -157,26 +173,50 @@ namespace UVtools.Core.Operations progress++; } } - else + } + else + { + var layerIndexes = new uint[SlicerFile.LayerCount / _item.Modifier]; + for (uint i = 0; i < layerIndexes.Length; i++) { - using var mat = SlicerFile[layerIndex++].LayerMat; + layerIndexes[i] = i * _item.Modifier; + } + + Parallel.ForEach(layerIndexes, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + var oldLayer = SlicerFile[layerIndex]; + using var mat = oldLayer.LayerMat; + using var original = mat.Clone(); + for (byte i = 1; i < Item.Modifier; i++) { - using var nextMat = SlicerFile[layerIndex++].LayerMat; + using var nextMat = SlicerFile[layerIndex+i].LayerMat; CvInvoke.Add(mat, nextMat, mat); } + /*if (_antiAliasing) + { + CvInvoke.Subtract(mat, original, mat); + CvInvoke.PyrDown(mat, mat); + CvInvoke.PyrUp(mat, mat); + CvInvoke.Add(original, mat, mat); + }*/ + var newLayer = oldLayer.Clone(); //newLayer.Index = newLayerIndex; //newLayer.PositionZ = (float)(Item.LayerHeight * (newLayerIndex + 1)); newLayer.LayerMat = mat; - layers[newLayerIndex] = newLayer; - newLayerIndex++; - layerIndex--; - progress++; - } + layers[layerIndex / _item.Modifier] = newLayer; + + progress.LockAndIncrement(); + }); + + } + + SlicerFile.LayerHeight = (float)Item.LayerHeight; SlicerFile.LayerManager.Layers = layers; diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 01dd2dc..63a0232 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl> <Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description> - <Version>2.8.4</Version> + <Version>2.9.0</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> |