diff options
-rw-r--r-- | CHANGELOG.md | 18 | ||||
-rw-r--r-- | CREDITS.md | 5 | ||||
-rw-r--r-- | PrusaSL1Reader/CbddlpFile.cs | 797 | ||||
-rw-r--r-- | PrusaSL1Reader/ChituboxFile.cs | 1413 | ||||
-rw-r--r-- | PrusaSL1Reader/FileFormat.cs | 95 | ||||
-rw-r--r-- | PrusaSL1Reader/Helpers.cs | 8 | ||||
-rw-r--r-- | PrusaSL1Reader/IFileFormat.cs | 43 | ||||
-rw-r--r-- | PrusaSL1Reader/PrusaSL1Reader.csproj | 6 | ||||
-rw-r--r-- | PrusaSL1Reader/SL1File.cs | 111 | ||||
-rw-r--r-- | PrusaSL1Reader/ZCodexFile.cs | 33 | ||||
-rw-r--r-- | PrusaSL1Viewer/FrmMain.Designer.cs | 229 | ||||
-rw-r--r-- | PrusaSL1Viewer/FrmMain.cs | 77 | ||||
-rw-r--r-- | PrusaSL1Viewer/FrmMain.resx | 85 | ||||
-rw-r--r-- | PrusaSL1Viewer/Images/Open-16x16.png | bin | 455 -> 326 bytes | |||
-rw-r--r-- | PrusaSL1Viewer/Program.cs | 11 | ||||
-rw-r--r-- | PrusaSL1Viewer/Properties/AssemblyInfo.cs | 4 | ||||
-rw-r--r-- | PrusaSL1Viewer/PrusaSL1Viewer.csproj | 3 | ||||
-rw-r--r-- | README.md | 1 |
18 files changed, 1882 insertions, 1057 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c2d0d03..fa72d81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 30/04/2020 - v0.3.1 - Beta + +* (Add) Thumbnails to converted photon and cbddlp files +* (Add) ctb file format +* (Add) Show possible extensions/files under "Convert To" menu +* (Add) Open new file in a new window without lose current work +* (Improvement) Rename and complete some Chitubox properties +* (Improvement) More completion of cbddlp file +* (Improvement) Optimized layer read from cbddlp file +* (Improvement) Add layer hash code to encoded Chitubox layers in order to optimize file size in case of repeated layer images +* (Improvement) GUI thumbnail preview now auto scale splitter height to a max of 400px when change thumbnail +* (Improvement) After convertion program prompt for open the result file in a new window +* (Change) Move layer rotate from view menu to layer menu +* (Change) Cbbdlp convertion name to Chitubox +* (Change) On covert, thumbnails now are resized to match exactly the target thumbnail size +* (Change) GUI will now show thumbnails from smaller to larger +* (Fixed) RetractFeedrate was incorrectly used instead of LiftFeedrate on Zcodex gcode + ## 27/04/2020 - v0.3.0 - Beta * (Add) zcodex file format @@ -9,6 +9,11 @@ * For information about cbddlp file format * https://github.com/Photonsters +### Cliff L. Biffle +* Catibo: read/write/analyze CTB, CBDDLP, and PHZ files +* For information about cbddlp and cbt file format +* https://github.com/cbiffle/catibo + ### Jason S. McMullan (ezrec) * For ideas and permission to use his project (uv3dp) diff --git a/PrusaSL1Reader/CbddlpFile.cs b/PrusaSL1Reader/CbddlpFile.cs deleted file mode 100644 index 0546e1d..0000000 --- a/PrusaSL1Reader/CbddlpFile.cs +++ /dev/null @@ -1,797 +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. - */ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using BinarySerialization; -using PrusaSL1Reader.Extensions; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace PrusaSL1Reader -{ - public class CbddlpFile : FileFormat - { - #region Constants - private const uint SPI_FILE_MAGIC_BASE = 0x12FD0019; - private const int SPECIAL_BIT = 1 << 1; - private const int SPECIAL_BIT_MASK = ~SPECIAL_BIT; - private const ushort REPEATRGB15MASK = (1 << 5); - - private const byte RLE8EncodingLimit = 125; - private const ushort RLE16EncodingLimit = 0x1000; - #endregion - - #region Sub Classes - #region Header - public class Header - { - - [FieldOrder(0)] public uint Magic { get; set; } // 00 - [FieldOrder(1)] public uint Version { get; set; } // 04 - [FieldOrder(2)] public float BedSizeX { get; set; } // 08 - [FieldOrder(3)] public float BedSizeY { get; set; } // 12 - [FieldOrder(4)] public float BedSizeZ { get; set; } // 20 - [FieldOrder(5)] public uint Unknown1 { get; set; } - [FieldOrder(6)] public uint Unknown2 { get; set; } - [FieldOrder(7)] public uint Unknown3 { get; set; } - [FieldOrder(8)] public float LayerHeightMilimeter { get; set; } // 20 - [FieldOrder(9)] public float LayerExposureSeconds { get; set; } // 24: Layer exposure (in seconds) - [FieldOrder(10)] public float BottomExposureSeconds { get; set; } // 28: Bottom layers exporsure (in seconds) - [FieldOrder(11)] public float LayerOffTime { get; set; } // 2c: Layer off time (in seconds) - [FieldOrder(12)] public uint BottomLayersCount { get; set; } // 30: Number of bottom layers - [FieldOrder(13)] public uint ResolutionX { get; set; } // 34: - [FieldOrder(14)] public uint ResolutionY { get; set; } // 38: - [FieldOrder(15)] public uint PreviewOneOffsetAddress { get; set; }// 3c: Offset of the high-res preview - [FieldOrder(16)] public uint LayersDefinitionOffsetAddress { get; set; }// 40: Offset of the layer definitions - [FieldOrder(17)] public uint LayerCount { get; set; } // 44: - [FieldOrder(18)] public uint PreviewTwoOffsetAddress { get; set; }// 48: Offset of the low-rew preview - [FieldOrder(19)] public uint PrintTime { get; set; } // 4c: In seconds - [FieldOrder(20)] public uint ProjectorType { get; set; } // 0 = CAST, 1 = LCD_X_MIRROR - [FieldOrder(21)] public uint PrintParametersOffsetAddress { get; set; } // 54: - [FieldOrder(22)] public uint PrintParametersSize { get; set; } // 58: - [FieldOrder(23)] public uint AntiAliasLevel { get; set; } // 5c: - [FieldOrder(24)] public ushort LightPWM { get; set; } // 60: - [FieldOrder(25)] public ushort BottomLightPWM { get; set; } // 62: - [FieldOrder(26)] public uint Padding1 { get; set; } - [FieldOrder(27)] public uint Padding2 { get; set; } - [FieldOrder(28)] public uint Padding3 { get; set; } - - public override string ToString() - { - return $"{nameof(Magic)}: {Magic}, {nameof(Version)}: {Version}, {nameof(BedSizeX)}: {BedSizeX}, {nameof(BedSizeY)}: {BedSizeY}, {nameof(BedSizeZ)}: {BedSizeZ}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(LayerHeightMilimeter)}: {LayerHeightMilimeter}, {nameof(LayerExposureSeconds)}: {LayerExposureSeconds}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {nameof(LayerOffTime)}: {LayerOffTime}, {nameof(BottomLayersCount)}: {BottomLayersCount}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(PreviewOneOffsetAddress)}: {PreviewOneOffsetAddress}, {nameof(LayersDefinitionOffsetAddress)}: {LayersDefinitionOffsetAddress}, {nameof(LayerCount)}: {LayerCount}, {nameof(PreviewTwoOffsetAddress)}: {PreviewTwoOffsetAddress}, {nameof(PrintTime)}: {PrintTime}, {nameof(ProjectorType)}: {ProjectorType}, {nameof(PrintParametersOffsetAddress)}: {PrintParametersOffsetAddress}, {nameof(PrintParametersSize)}: {PrintParametersSize}, {nameof(AntiAliasLevel)}: {AntiAliasLevel}, {nameof(LightPWM)}: {LightPWM}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(Padding3)}: {Padding3}"; - } - } - #endregion - - #region PrintParameters - public class PrintParameters - { - [FieldOrder(0)] public float BottomLiftHeight { get; set; } - [FieldOrder(1)] public float BottomLiftSpeed { get; set; } - [FieldOrder(2)] public float LiftHeight { get; set; } - [FieldOrder(3)] public float LiftingSpeed { get; set; } - [FieldOrder(4)] public float RetractSpeed { get; set; } - [FieldOrder(5)] public float VolumeMl { get; set; } - [FieldOrder(6)] public float WeightG { get; set; } - [FieldOrder(7)] public float CostDollars { get; set; } - [FieldOrder(8)] public float BottomLightOffDelay { get; set; } - [FieldOrder(9)] public float LightOffDelay { get; set; } - [FieldOrder(10)] public uint BottomLayerCount { get; set; } - [FieldOrder(11)] public uint P1 { get; set; } - [FieldOrder(12)] public uint P2 { get; set; } - [FieldOrder(13)] public uint P3 { get; set; } - [FieldOrder(14)] public uint P4 { get; set; } - - public override string ToString() - { - return $"{nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftingSpeed)}: {LiftingSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(VolumeMl)}: {VolumeMl}, {nameof(WeightG)}: {WeightG}, {nameof(CostDollars)}: {CostDollars}, {nameof(BottomLightOffDelay)}: {BottomLightOffDelay}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(P1)}: {P1}, {nameof(P2)}: {P2}, {nameof(P3)}: {P3}, {nameof(P4)}: {P4}"; - } - } - #endregion - - #region MachineInfo - - public class MachineInfo - { - [FieldOrder(0)] public uint d1 { get; set; } - [FieldOrder(1)] public uint d2 { get; set; } - [FieldOrder(2)] public uint d3 { get; set; } - [FieldOrder(3)] public uint d4 { get; set; } - [FieldOrder(4)] public uint d5 { get; set; } - [FieldOrder(5)] public uint d6 { get; set; } - [FieldOrder(6)] public uint d7 { get; set; } - [FieldOrder(7)] public uint MachineNameAddress { get; set; } - [FieldOrder(8)] public uint MachineNameSize { get; set; } - [FieldOrder(9)] public uint d8 { get; set; } - [FieldOrder(10)] public uint d9 { get; set; } - [FieldOrder(11)] public uint d10 { get; set; } - [FieldOrder(12)] public uint d11 { get; set; } - [FieldOrder(13)] public uint d12 { get; set; } - [FieldOrder(14)] public uint d13 { get; set; } - [FieldOrder(15)] public uint d14 { get; set; } - [FieldOrder(16)] public uint d15 { get; set; } - [FieldOrder(17)] public uint d16 { get; set; } - [FieldOrder(18)] public uint d17 { get; set; } - - [FieldOrder(19)] [FieldLength(nameof(MachineNameSize))] - public string MachineName { get; set; } - - public override string ToString() - { - return $"{nameof(d1)}: {d1}, {nameof(d2)}: {d2}, {nameof(d3)}: {d3}, {nameof(d4)}: {d4}, {nameof(d5)}: {d5}, {nameof(d6)}: {d6}, {nameof(d7)}: {d7}, {nameof(MachineNameAddress)}: {MachineNameAddress}, {nameof(MachineNameSize)}: {MachineNameSize}, {nameof(d8)}: {d8}, {nameof(d9)}: {d9}, {nameof(d10)}: {d10}, {nameof(d11)}: {d11}, {nameof(d12)}: {d12}, {nameof(d13)}: {d13}, {nameof(d14)}: {d14}, {nameof(d15)}: {d15}, {nameof(d16)}: {d16}, {nameof(d17)}: {d17}, {nameof(MachineName)}: {MachineName}"; - } - } - - #endregion - - #region Preview - public class Preview - { - [FieldOrder(0)] public uint ResolutionX { get; set; } - [FieldOrder(1)] public uint ResolutionY { get; set; } - [FieldOrder(2)] public uint ImageOffset { get; set; } - [FieldOrder(3)] public uint ImageLength { get; set; } - [FieldOrder(4)] public uint Unknown1 { get; set; } - [FieldOrder(5)] public uint Unknown2 { get; set; } - [FieldOrder(6)] public uint Unknown3 { get; set; } - [FieldOrder(7)] public uint Unknown4 { get; set; } - - public override string ToString() - { - return $"{nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(ImageOffset)}: {ImageOffset}, {nameof(ImageLength)}: {ImageLength}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; - } - } - - #endregion - - #region Layer - public class Layer - { - [FieldOrder(0)] public float LayerPositionZ { get; set; } - [FieldOrder(1)] public float LayerExposure { get; set; } - [FieldOrder(2)] public float LayerOffTimeSeconds { get; set; } - [FieldOrder(3)] public uint DataAddress { get; set; } - [FieldOrder(4)] public uint DataSize { get; set; } - [FieldOrder(5)] public uint Unknown1 { get; set; } - [FieldOrder(6)] public uint Unknown2 { get; set; } - [FieldOrder(7)] public uint Unknown3 { get; set; } - [FieldOrder(8)] public uint Unknown4 { get; set; } - - public override string ToString() - { - return $"{nameof(LayerPositionZ)}: {LayerPositionZ}, {nameof(LayerExposure)}: {LayerExposure}, {nameof(LayerOffTimeSeconds)}: {LayerOffTimeSeconds}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; - } - } - #endregion - - #endregion - - #region Properties - public FileStream InputFile { get; private set; } - public FileStream OutputFile { get; private set; } - public Header HeaderSettings { get; protected internal set; } - public PrintParameters PrintParametersSettings { get; protected internal set; } - - public MachineInfo MachineInfoSettings { get; protected internal set; } - - public Preview[] Previews { get; protected internal set; } - - public Layer[,] Layers { get; private set; } - - private uint CurrentOffset { get; set; } - private uint LayerDataCurrentOffset { get; set; } - - - public override FileFormatType FileType => FileFormatType.Binary; - - public override FileExtension[] FileExtensions { get; } = { - new FileExtension("cbddlp", "Chitubox DLP Files"), - new FileExtension("photon", "Photon Files"), - }; - - public override PrintParameterModifier[] PrintParameterModifiers { get; } = - { - PrintParameterModifier.InitialLayerCount, - PrintParameterModifier.InitialExposureSeconds, - PrintParameterModifier.ExposureSeconds, - - PrintParameterModifier.BottomLayerOffTime, - PrintParameterModifier.LayerOffTime, - PrintParameterModifier.BottomLiftHeight, - PrintParameterModifier.BottomLiftSpeed, - PrintParameterModifier.ZRetractHeight, - PrintParameterModifier.ZDetractSpeed, - PrintParameterModifier.ZRetractSpeed, - - PrintParameterModifier.BottomLightPWM, - PrintParameterModifier.LightPWM, - }; - - public override string FileFullPath { get; set; } - - public override byte ThumbnailsCount { get; } = 2; - - public override Image<Rgba32>[] Thumbnails { get; set; } - - public override uint ResolutionX => HeaderSettings.ResolutionX; - - public override uint ResolutionY => HeaderSettings.ResolutionY; - - public override float LayerHeight => HeaderSettings.LayerHeightMilimeter; - - public override uint LayerCount => HeaderSettings.LayerCount; - - public override ushort InitialLayerCount => (ushort)HeaderSettings.BottomLayersCount; - - public override float InitialExposureTime => HeaderSettings.BottomExposureSeconds; - - public override float LayerExposureTime => HeaderSettings.LayerExposureSeconds; - public override float ZRetractHeight => PrintParametersSettings.LiftHeight; - public override float ZRetractSpeed => PrintParametersSettings.RetractSpeed; - public override float ZDetractSpeed => PrintParametersSettings.LiftingSpeed; - - public override float PrintTime => HeaderSettings.PrintTime; - - public override float UsedMaterial => PrintParametersSettings.VolumeMl; - - public override float MaterialCost => PrintParametersSettings.CostDollars; - - public override string MaterialName => "Unknown"; - public override string MachineName => MachineInfoSettings.MachineName; - - public override object[] Configs => new[] { (object)HeaderSettings, PrintParametersSettings, MachineInfoSettings }; - #endregion - - #region Constructors - public CbddlpFile() - { - Previews = new Preview[ThumbnailsCount]; - } - #endregion - - #region Methods - public override void Clear() - { - base.Clear(); - - for (byte i = 0; i < ThumbnailsCount; i++) - { - Previews[i] = new Preview(); - } - - Layers = null; - - if (!ReferenceEquals(InputFile, null)) - { - InputFile.Close(); - InputFile.Dispose(); - } - - if (!ReferenceEquals(OutputFile, null)) - { - OutputFile.Close(); - OutputFile.Dispose(); - } - } - - public override void BeginEncode(string fileFullPath) - { - CurrentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); - Layers = new Layer[HeaderSettings.LayerCount, HeaderSettings.AntiAliasLevel]; - OutputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write); - - HeaderSettings.Magic = SPI_FILE_MAGIC_BASE; - //HeaderSettings.PreviewOneOffsetAddress = CurrentOffset; - HeaderSettings.PrintParametersSize = (uint)Helpers.Serializer.SizeOf(PrintParametersSettings); - - //CurrentOffset = Helpers.SerializeWriteFileStream(OutputFile, HeaderSettings); - - OutputFile.Seek((int)CurrentOffset, SeekOrigin.Begin); - - /*for (int i = 0; i < ThumbnailsCount; i++) - { - if (i == 1) - { - HeaderSettings.PreviewTwoOffsetAddress = CurrentOffset; - } - var image = Thumbnails[i]; - Preview preview = new Preview {ResolutionX = (uint)image.Width, ResolutionY = (uint)image.Height}; - List<byte> rawData = new List<byte>(); - - Rgba32 color; - byte nrOfColor = 0; - Rgba32? prevColor = null; - - for (int y = 0; y < image.Height; y++) - { - Span<Rgba32> pixelRowSpan = image.GetPixelRowSpan(y); - for (int x = 0; x < image.Width; x++) - { - color = pixelRowSpan[x]; - if (prevColor == null) prevColor = color; - bool isLastPixel = x == (image.Width - 1) && y == (image.Height - 1); - if (color == prevColor && nrOfColor < 0x0FFF && !isLastPixel) - { - nrOfColor++; - } - else - { - - byte R = prevColor.Value.R; - byte G = prevColor.Value.G; - byte B = prevColor.Value.B; - byte X = nrOfColor > 1 ? (byte)1 : (byte)0; - - - // build 2 or 4 bytes (depending on X - // The color (R,G,B) of a pixel spans 2 bytes (little endian) and - // each color component is 5 bits: RRRRR GGG GG X BBBBB - R = (byte)Math.Round(R / 255f * 31f); - G = (byte)Math.Round(G / 255f * 31f); - B = (byte)Math.Round(B / 255f * 31f); - - byte encValue0 = (byte)(R << 3 | G >> 2); - byte encValue1 = (byte) (((G & 0b00000011) << 6) | X << 5 | B); - rawData.Add(encValue0); - rawData.Add(encValue1); - if (X == 1) - { - nrOfColor--; - // write one less than nr of pixels - byte encValue2 = (byte)(nrOfColor >> 8); - byte encValue3 = (byte)(nrOfColor & 0b000000011111111); - // seems like nr bytes pixels have 0011 as start - encValue2 = (byte)(encValue2 | 0b00110000); - rawData.Add(encValue2); - rawData.Add(encValue3); - } - - prevColor = color; - nrOfColor = 1; - } - } - } - - - preview.ImageLength = (uint)rawData.Count; - CurrentOffset += (uint)Helpers.Serializer.SizeOf(preview); - preview.ImageOffset = CurrentOffset; - - Previews[i] = preview; - - Helpers.SerializeWriteFileStream(OutputFile, preview); - CurrentOffset += Helpers.SerializeWriteFileStream(OutputFile, rawData.ToArray()); - }*/ - - - if (HeaderSettings.Version == 2) - { - HeaderSettings.PrintParametersOffsetAddress = CurrentOffset; - - CurrentOffset += Helpers.SerializeWriteFileStream(OutputFile, PrintParametersSettings); - - MachineInfoSettings.MachineNameAddress = (uint)(CurrentOffset + Helpers.Serializer.SizeOf(MachineInfoSettings) - - MachineInfoSettings.MachineNameSize); - - - CurrentOffset += Helpers.SerializeWriteFileStream(OutputFile, MachineInfoSettings); - } - - HeaderSettings.LayersDefinitionOffsetAddress = CurrentOffset; - LayerDataCurrentOffset = CurrentOffset + (uint)Helpers.Serializer.SizeOf(new Layer()) * HeaderSettings.LayerCount * HeaderSettings.AntiAliasLevel; - } - - - public override void InsertLayerImageEncode(Image<Gray8> image, uint layerIndex) - { - Layer layer = new Layer(); - List<byte> rawData = new List<byte>(); - - byte color; - byte black = 0; - byte white = 1; - - byte nrOfColor = 0; - byte prevColor = byte.MaxValue; - - for (int y = 0; y < image.Height; y++) - { - Span<Gray8> pixelRowSpan = image.GetPixelRowSpan(y); - for (int x = 0; x < image.Width; x++) - { - color = pixelRowSpan[x].PackedValue < 128 ? black : white; - if (prevColor == byte.MaxValue) prevColor = color; - bool isLastPixel = x == (image.Width - 1) && y == (image.Height - 1); - - if (color == prevColor && nrOfColor < 0x7D && !isLastPixel) - { - nrOfColor++; - } - else - { - byte encValue = - (byte)((prevColor << 7) | - nrOfColor); // push color (B/W) to highest bit and repetitions to lowest 7 bits. - rawData.Add(encValue); - prevColor = color; - nrOfColor = 1; - } - } - } - - - //layer.DataAddress = CurrentOffset + (uint)Helpers.Serializer.SizeOf(layer); - layer.DataAddress = LayerDataCurrentOffset; - layer.DataSize = (uint)rawData.Count; - layer.LayerPositionZ = layerIndex * HeaderSettings.LayerHeightMilimeter; - layer.LayerOffTimeSeconds = layerIndex < HeaderSettings.BottomLayersCount ? PrintParametersSettings.BottomLightOffDelay : PrintParametersSettings.LightOffDelay; - layer.LayerExposure = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomExposureSeconds : HeaderSettings.LayerExposureSeconds; - Layers[layerIndex, 0] = layer; - - CurrentOffset += Helpers.SerializeWriteFileStream(OutputFile, layer); - - OutputFile.Seek(LayerDataCurrentOffset, SeekOrigin.Begin); - LayerDataCurrentOffset += Helpers.WriteFileStream(OutputFile, rawData.ToArray()); - OutputFile.Seek(CurrentOffset, SeekOrigin.Begin); - } - - public override void EndEncode() - { - - OutputFile.Seek(0, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(OutputFile, HeaderSettings); - - OutputFile.Close(); - OutputFile.Dispose(); - - Debug.WriteLine("Encode Results:"); - Debug.WriteLine(HeaderSettings); - Debug.WriteLine(Previews[0]); - Debug.WriteLine(Previews[1]); - Debug.WriteLine(PrintParametersSettings); - Debug.WriteLine(MachineInfoSettings); - Debug.WriteLine("-End-"); - } - - public override void Decode(string fileFullPath) - { - base.Decode(fileFullPath); - - 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.Magic != SPI_FILE_MAGIC_BASE) - { - throw new FileLoadException("Not a valid CBDDLP file!", fileFullPath); - } - - if (HeaderSettings.Version == 1 || HeaderSettings.AntiAliasLevel == 0) - { - HeaderSettings.AntiAliasLevel = 1; - } - - FileFullPath = fileFullPath; - - - - Debug.Write("Header -> "); - Debug.WriteLine(HeaderSettings); - - for (byte i = 0; i < ThumbnailsCount; i++) - { - uint offsetAddress = i == 0 ? HeaderSettings.PreviewOneOffsetAddress : HeaderSettings.PreviewTwoOffsetAddress; - if (offsetAddress == 0) continue; - - InputFile.Seek(offsetAddress, SeekOrigin.Begin); - Previews[i] = Helpers.Deserialize<Preview>(InputFile); - - Debug.Write($"Preview {i} -> "); - Debug.WriteLine(Previews[i]); - - Thumbnails[i] = new Image<Rgba32>((int)Previews[i].ResolutionX, (int)Previews[i].ResolutionY); - - InputFile.Seek(Previews[i].ImageOffset, SeekOrigin.Begin); - byte[] rawImageData = new byte[Previews[i].ImageLength]; - InputFile.Read(rawImageData, 0, (int)Previews[i].ImageLength); - int x = 0; - int y = 0; - for (int n = 0; n < Previews[i].ImageLength; n++) - { - uint dot = (uint)(rawImageData[n] & 0xFF | ((rawImageData[++n] & 0xFF) << 8)); - //uint color = ((dot & 0xF800) << 8) | ((dot & 0x07C0) << 5) | ((dot & 0x001F) << 3); - byte red = (byte)(((dot >> 11) & 0x1F) << 3); - byte green = (byte)(((dot >> 6) & 0x1F) << 3); - byte blue = (byte)((dot & 0x1F) << 3); - int repeat = 1; - if ((dot & 0x0020) == 0x0020) - { - repeat += rawImageData[++n] & 0xFF | ((rawImageData[++n] & 0x0F) << 8); - } - - - for (int j = 0; j < repeat; j++) - { - Thumbnails[i][x, y] = new Rgba32(red, green, blue, 255); - x++; - - if (x == Previews[i].ResolutionX) - { - x = 0; - y++; - } - } - } - } - - if (HeaderSettings.Version == 2 && HeaderSettings.PrintParametersOffsetAddress > 0) - { - InputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); - PrintParametersSettings = Helpers.Deserialize<PrintParameters>(InputFile); - Debug.Write("Print Parameters -> "); - Debug.WriteLine(PrintParametersSettings); - - MachineInfoSettings = Helpers.Deserialize<MachineInfo>(InputFile); - Debug.Write("Machine Info -> "); - Debug.WriteLine(MachineInfoSettings); - - /*InputFile.BaseStream.Seek(MachineInfoSettings.MachineNameAddress, SeekOrigin.Begin); - byte[] bytes = InputFile.ReadBytes((int)MachineInfoSettings.MachineNameSize); - MachineName = System.Text.Encoding.UTF8.GetString(bytes); - Debug.WriteLine($"{nameof(MachineName)}: {MachineName}");*/ - } - - Layers = new Layer[HeaderSettings.LayerCount, HeaderSettings.AntiAliasLevel]; - - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - - Image<Gray8> image = new Image<Gray8>((int)HeaderSettings.BedSizeX, (int)HeaderSettings.BedSizeY); - - - for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) - { - Debug.WriteLine($"-Image GROUP {aaIndex}-"); - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - InputFile.Seek(layerOffset, SeekOrigin.Begin); - Layer layer = Helpers.Deserialize<Layer>(InputFile); - Layers[layerIndex, aaIndex] = layer; - //Layers.Add(layer); - - layerOffset += (uint)Helpers.Serializer.SizeOf(layer); - Debug.Write($"LAYER {layerIndex} -> "); - Debug.WriteLine(layer); - } - } - } - - public override void Extract(string path, bool emptyFirst = true, bool genericConfigExtract = false, - bool genericLayersExtract = false) => - base.Extract(path, emptyFirst, true, true); - - public override Image<Gray8> GetLayerImage(uint layerIndex) - { - if (layerIndex >= HeaderSettings.LayerCount) - { - throw new IndexOutOfRangeException($"Layer {layerIndex} doesn't exists, out of bounds."); - } - - Image<Gray8> image = new Image<Gray8>((int)HeaderSettings.ResolutionX, (int)HeaderSettings.ResolutionY); - - for (uint aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) - { - Layer layer = Layers[layerIndex, aaIndex]; - InputFile.Seek(layer.DataAddress, SeekOrigin.Begin); - byte[] rawImageData = new byte[(int)layer.DataSize]; - InputFile.Read(rawImageData, 0, (int)layer.DataSize); - uint x = 0; - uint y = 0; - - foreach (byte rle in rawImageData) - { - // From each byte retrieve color (highest bit) and number of pixels of that color (lowest 7 bits) - uint length = (uint)(rle & 0x7F); // turn highest bit of - bool color = (rle & 0x80) == 0x80; // only read 1st bit - - uint x2 = Math.Min(x + length, HeaderSettings.ResolutionX); - - if (color) - { - var span = image.GetPixelRowSpan((int)y); - for (uint i = x; i < x2; i++) - { - span[(int)i] = Helpers.Gray8White; - } - } - - x += length; - - if (x >= HeaderSettings.ResolutionX) - { - length = x - HeaderSettings.ResolutionX; - x = 0; - y++; - x2 = x + length; - - if (color) - { - var span = image.GetPixelRowSpan((int)y); - for (uint i = x; i < x2; i++) - { - span[(int)i] = Helpers.Gray8White; - } - } - - x += length; - } - } - } - - return image; - } - - public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) - { - var baseValue = base.GetValueFromPrintParameterModifier(modifier); - if (!ReferenceEquals(baseValue, null)) return baseValue; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) return PrintParametersSettings.BottomLightOffDelay; - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return PrintParametersSettings.LightOffDelay; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) return PrintParametersSettings.BottomLiftHeight; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) return PrintParametersSettings.BottomLiftSpeed; - if (ReferenceEquals(modifier, PrintParameterModifier.ZRetractHeight)) return PrintParametersSettings.LiftHeight; - if (ReferenceEquals(modifier, PrintParameterModifier.ZDetractSpeed)) return PrintParametersSettings.LiftingSpeed; - if (ReferenceEquals(modifier, PrintParameterModifier.ZRetractSpeed)) return PrintParametersSettings.RetractSpeed; - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) return HeaderSettings.BottomLightPWM; - if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) return HeaderSettings.LightPWM; - - - - return null; - } - - public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) - { - void UpdateLayers() - { - for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) - { - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - // Bottom : others - Layers[layerIndex, aaIndex].LayerExposure = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomExposureSeconds : HeaderSettings.LayerExposureSeconds; - Layers[layerIndex, aaIndex].LayerOffTimeSeconds = layerIndex < HeaderSettings.BottomLayersCount ? PrintParametersSettings.BottomLightOffDelay : PrintParametersSettings.LightOffDelay; - } - } - } - - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - { - HeaderSettings.BottomLayersCount = - PrintParametersSettings.BottomLayerCount = value.Convert<uint>(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - { - HeaderSettings.BottomExposureSeconds = value.Convert<float>(); - UpdateLayers(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - { - HeaderSettings.LayerExposureSeconds = value.Convert<float>(); - UpdateLayers(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) - { - PrintParametersSettings.BottomLightOffDelay = value.Convert<float>(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) - { - HeaderSettings.LayerOffTime = - PrintParametersSettings.LightOffDelay = value.Convert<float>(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) - { - PrintParametersSettings.BottomLiftHeight = value.Convert<float>(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) - { - PrintParametersSettings.BottomLiftSpeed = value.Convert<float>(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.ZRetractHeight)) - { - PrintParametersSettings.LiftHeight = value.Convert<float>(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.ZDetractSpeed)) - { - PrintParametersSettings.LiftingSpeed = value.Convert<float>(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.ZRetractSpeed)) - { - PrintParametersSettings.RetractSpeed = value.Convert<float>(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) - { - HeaderSettings.BottomLightPWM = value.Convert<ushort>(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) - { - HeaderSettings.LightPWM = value.Convert<ushort>(); - return true; - } - - return false; - } - - public override void SaveAs(string filePath = null) - { - InputFile.Dispose(); - if (!string.IsNullOrEmpty(filePath)) - { - File.Copy(FileFullPath, filePath, true); - FileFullPath = filePath; - - } - - using (InputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) - { - - InputFile.Seek(0, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(InputFile, HeaderSettings); - - if (HeaderSettings.Version == 2 && HeaderSettings.PrintParametersOffsetAddress > 0) - { - InputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(InputFile, PrintParametersSettings); - Helpers.SerializeWriteFileStream(InputFile, MachineInfoSettings); - } - - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) - { - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - InputFile.Seek(layerOffset, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(InputFile, Layers[layerIndex, aaIndex]); - layerOffset += (uint)Helpers.Serializer.SizeOf(Layers[layerIndex, aaIndex]); - } - } - InputFile.Close(); - } - - Decode(FileFullPath); - } - - public override bool Convert(Type to, string fileFullPath) - { - throw new NotImplementedException(); - } - #endregion - } -} diff --git a/PrusaSL1Reader/ChituboxFile.cs b/PrusaSL1Reader/ChituboxFile.cs new file mode 100644 index 0000000..e0401b6 --- /dev/null +++ b/PrusaSL1Reader/ChituboxFile.cs @@ -0,0 +1,1413 @@ +/* + * 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.IO; +using BinarySerialization; +using PrusaSL1Reader.Extensions; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace PrusaSL1Reader +{ + public class ChituboxFile : FileFormat + { + #region Constants + private const uint MAGIC_CBDDLP = 0x12FD0019; + private const uint MAGIC_CBT = 0x12FD0086; + private const int SPECIAL_BIT = 1 << 1; + private const int SPECIAL_BIT_MASK = ~SPECIAL_BIT; + private const ushort REPEATRGB15MASK = 0x20; + + private const byte RLE8EncodingLimit = 125; + private const ushort RLE16EncodingLimit = 0x1000; + #endregion + + #region Sub Classes + #region Header + public class Header + { + + /// <summary> + /// Gets a magic number identifying the file type. + /// 0x12fd_0019 for cbddlp + /// 0x12fd_0086 for ctb + /// </summary> + [FieldOrder(0)] public uint Magic { get; set; } + + /// <summary> + /// Gets the software version + /// </summary> + [FieldOrder(1)] public uint Version { get; set; } = 2; + + /// <summary> + /// Gets dimensions of the printer’s X output volume, in millimeters. + /// </summary> + [FieldOrder(2)] public float BedSizeX { get; set; } + + /// <summary> + /// Gets dimensions of the printer’s Y output volume, in millimeters. + /// </summary> + [FieldOrder(3)] public float BedSizeY { get; set; } + + /// <summary> + /// Gets dimensions of the printer’s Z output volume, in millimeters. + /// </summary> + [FieldOrder(4)] public float BedSizeZ { get; set; } + + [FieldOrder(5)] public uint Unknown1 { get; set; } + [FieldOrder(6)] public uint Unknown2 { get; set; } + + /// <summary> + /// Gets the height of the model described by this file, in millimeters. + /// </summary> + [FieldOrder(7)] public float OverallHeightMilimeter { get; set; } + + /// <summary> + /// Gets the layer height setting used at slicing, in millimeters. Actual height used by the machine is in the layer table. + /// </summary> + [FieldOrder(8)] public float LayerHeightMilimeter { get; set; } + + /// <summary> + /// Gets the exposure time setting used at slicing, in seconds, for normal (non-bottom) layers, respectively. Actual time used by the machine is in the layer table. + /// </summary> + [FieldOrder(9)] public float LayerExposureSeconds { get; set; } + + /// <summary> + /// Gets the exposure time setting used at slicing, in seconds, for bottom layers. Actual time used by the machine is in the layer table. + /// </summary> + [FieldOrder(10)] public float BottomExposureSeconds { get; set; } + + /// <summary> + /// Gets the light off time setting used at slicing, for normal layers, in seconds. Actual time used by the machine is in the layer table. Note that light_off_time_s appears in both the file header and ExtConfig. + /// </summary> + [FieldOrder(11)] public float LayerOffTime { get; set; } = 1; + + /// <summary> + /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig.. + /// </summary> + [FieldOrder(12)] public uint BottomLayersCount { get; set; } = 10; + + /// <summary> + /// Gets the printer resolution along X axis, in pixels. This information is critical to correctly decoding layer images. + /// </summary> + [FieldOrder(13)] public uint ResolutionX { get; set; } + + /// <summary> + /// Gets the printer resolution along Y axis, in pixels. This information is critical to correctly decoding layer images. + /// </summary> + [FieldOrder(14)] public uint ResolutionY { get; set; } + + /// <summary> + /// Gets the file offsets of ImageHeader records describing the larger preview images. + /// </summary> + [FieldOrder(15)] public uint PreviewLargeOffsetAddress { get; set; } + + /// <summary> + /// Gets the file offset of a table of LayerHeader records giving parameters for each printed layer. + /// </summary> + [FieldOrder(16)] public uint LayersDefinitionOffsetAddress { get; set; } + + /// <summary> + /// Gets the number of records in the layer table for the first level set. In ctb files, that’s equivalent to the total number of records, but records may be multiplied in antialiased cbddlp files. + /// </summary> + [FieldOrder(17)] public uint LayerCount { get; set; } + + /// <summary> + /// Gets the file offsets of ImageHeader records describing the smaller preview images. + /// </summary> + [FieldOrder(18)] public uint PreviewSmallOffsetAddress { get; set; } + + /// <summary> + /// Gets the estimated duration of print, in seconds. + /// </summary> + [FieldOrder(19)] public uint PrintTime { get; set; } + + /// <summary> + /// Gets the records whether this file was generated assuming normal (0) or mirrored (1) image projection. LCD printers are "mirrored" for this purpose. + /// </summary> + [FieldOrder(20)] public uint ProjectorType { get; set; } + + /// <summary> + /// Gets the print parameters table offset + /// </summary> + [FieldOrder(21)] public uint PrintParametersOffsetAddress { get; set; } + + /// <summary> + /// Gets the print parameters table size in bytes. + /// </summary> + [FieldOrder(22)] public uint PrintParametersSize { get; set; } + + /// <summary> + /// Gets the number of times each layer image is repeated in the file. + /// This is used to implement antialiasing in cbddlp files. When greater than 1, + /// the layer table will actually contain layer_table_count * level_set_count entries. + /// See the section on antialiasing for details. + /// </summary> + [FieldOrder(23)] public uint AntiAliasLevel { get; set; } = 1; + + /// <summary> + /// Gets the PWM duty cycle for the UV illumination source on normal levels, respectively. + /// This appears to be an 8-bit quantity where 0xFF is fully on and 0x00 is fully off. + /// </summary> + [FieldOrder(24)] public ushort LightPWM { get; set; } = 255; + + /// <summary> + /// Gets the PWM duty cycle for the UV illumination source on bottom levels, respectively. + /// This appears to be an 8-bit quantity where 0xFF is fully on and 0x00 is fully off. + /// </summary> + [FieldOrder(25)] public ushort BottomLightPWM { get; set; } = 255; + + /// <summary> + /// Gets the key used to encrypt layer data, or 0 if encryption is not used. + /// </summary> + [FieldOrder(26)] public uint EncryptionKey { get; set; } + + /// <summary> + /// Gets the slicer tablet offset + /// </summary> + [FieldOrder(27)] public uint SlicerOffset { get; set; } + + /// <summary> + /// Gets the slicer table size in bytes + /// </summary> + [FieldOrder(28)] public uint SlicerSize { get; set; } + + public override string ToString() + { + return $"{nameof(Magic)}: {Magic}, {nameof(Version)}: {Version}, {nameof(BedSizeX)}: {BedSizeX}, {nameof(BedSizeY)}: {BedSizeY}, {nameof(BedSizeZ)}: {BedSizeZ}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(OverallHeightMilimeter)}: {OverallHeightMilimeter}, {nameof(LayerHeightMilimeter)}: {LayerHeightMilimeter}, {nameof(LayerExposureSeconds)}: {LayerExposureSeconds}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {nameof(LayerOffTime)}: {LayerOffTime}, {nameof(BottomLayersCount)}: {BottomLayersCount}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(PreviewLargeOffsetAddress)}: {PreviewLargeOffsetAddress}, {nameof(LayersDefinitionOffsetAddress)}: {LayersDefinitionOffsetAddress}, {nameof(LayerCount)}: {LayerCount}, {nameof(PreviewSmallOffsetAddress)}: {PreviewSmallOffsetAddress}, {nameof(PrintTime)}: {PrintTime}, {nameof(ProjectorType)}: {ProjectorType}, {nameof(PrintParametersOffsetAddress)}: {PrintParametersOffsetAddress}, {nameof(PrintParametersSize)}: {PrintParametersSize}, {nameof(AntiAliasLevel)}: {AntiAliasLevel}, {nameof(LightPWM)}: {LightPWM}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(EncryptionKey)}: {EncryptionKey}, {nameof(SlicerOffset)}: {SlicerOffset}, {nameof(SlicerSize)}: {SlicerSize}"; + } + } + #endregion + + #region PrintParameters + public class PrintParameters + { + /// <summary> + /// Gets the distance to lift the build platform away from the vat after bottom layers, in millimeters. + /// </summary> + [FieldOrder(0)] public float BottomLiftHeight { get; set; } = 5; + + /// <summary> + /// Gets the speed at which to lift the build platform away from the vat after bottom layers, in millimeters per minute. + /// </summary> + [FieldOrder(1)] public float BottomLiftSpeed { get; set; } = 300; + + /// <summary> + /// Gets the distance to lift the build platform away from the vat after normal layers, in millimeters. + /// </summary> + [FieldOrder(2)] public float LiftHeight { get; set; } = 5; + + /// <summary> + /// Gets the speed at which to lift the build platform away from the vat after normal layers, in millimeters per minute. + /// </summary> + [FieldOrder(3)] public float LiftingSpeed { get; set; } = 300; + + /// <summary> + /// Gets the speed to use when the build platform re-approaches the vat after lift, in millimeters per minute. + /// </summary> + [FieldOrder(4)] public float RetractSpeed { get; set; } = 300; + + /// <summary> + /// Gets the estimated required resin, measured in milliliters. The volume number is derived from the model. + /// </summary> + [FieldOrder(5)] public float VolumeMl { get; set; } + + /// <summary> + /// Gets the estimated grams, derived from volume using configured factors for density. + /// </summary> + [FieldOrder(6)] public float WeightG { get; set; } + + /// <summary> + /// Gets the estimated cost based on currency unit the user had configured. Derived from volume using configured factors for density and cost. + /// </summary> + [FieldOrder(7)] public float CostDollars { get; set; } + + /// <summary> + /// Gets the light off time setting used at slicing, for bottom layers, in seconds. Actual time used by the machine is in the layer table. Note that light_off_time_s appears in both the file header and ExtConfig. + /// </summary> + [FieldOrder(8)] public float BottomLightOffDelay { get; set; } = 1; + + /// <summary> + /// Gets the light off time setting used at slicing, for normal layers, in seconds. Actual time used by the machine is in the layer table. Note that light_off_time_s appears in both the file header and ExtConfig. + /// </summary> + [FieldOrder(9)] public float LightOffDelay { get; set; } = 1; + + /// <summary> + /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig. + /// </summary> + [FieldOrder(10)] public uint BottomLayerCount { get; set; } = 10; + [FieldOrder(11)] public uint Padding1 { get; set; } + [FieldOrder(12)] public uint Padding2 { get; set; } + [FieldOrder(13)] public uint Padding3 { get; set; } + [FieldOrder(14)] public uint Padding4 { get; set; } + + public override string ToString() + { + return $"{nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftingSpeed)}: {LiftingSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(VolumeMl)}: {VolumeMl}, {nameof(WeightG)}: {WeightG}, {nameof(CostDollars)}: {CostDollars}, {nameof(BottomLightOffDelay)}: {BottomLightOffDelay}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(Padding3)}: {Padding3}, {nameof(Padding4)}: {Padding4}"; + } + } + #endregion + + #region SlicerInfo + + public class SlicerInfo + { + [FieldOrder(0)] public uint Padding1 { get; set; } + [FieldOrder(1)] public uint Padding2 { get; set; } + [FieldOrder(2)] public uint Padding3 { get; set; } + [FieldOrder(3)] public uint Padding4 { get; set; } + [FieldOrder(4)] public uint Padding5 { get; set; } + [FieldOrder(5)] public uint Padding6 { get; set; } + [FieldOrder(6)] public uint Padding7 { get; set; } + + /// <summary> + /// Gets the machine name offset to a string naming the machine type, and its length in bytes. + /// </summary> + [FieldOrder(7)] public uint MachineNameAddress { get; set; } + + /// <summary> + /// Gets the machine size in bytes + /// </summary> + [FieldOrder(8)] public uint MachineNameSize { get; set; } + + /// <summary> + /// Gets the parameter used to control encryption. + /// Not totally understood. 0 for cbddlp files, 0xF for ctb files. + /// </summary> + [FieldOrder(9)] public uint EncryptionMode { get; set; } = 8; + + /// <summary> + /// Gets a number that increments with time or number of models sliced, or both. Zeroing it in output seems to have no effect. Possibly a user tracking bug. + /// </summary> + [FieldOrder(10)] public uint MysteriousId { get; set; } + + /// <summary> + /// Gets the user-selected antialiasing level. For cbddlp files this will match the level_set_count. For ctb files, this number is essentially arbitrary. + /// </summary> + [FieldOrder(11)] public uint AntialiasLevel { get; set; } = 1; + + /// <summary> + /// Gets a version of software that generated this file, encoded with major, minor, and patch release in bytes starting from the MSB down. + /// (No provision is made to name the software being used, so this assumes that only one software package can generate the files. + /// Probably best to hardcode it at 0x01060300.) + /// </summary> + [FieldOrder(12)] public uint SoftwareVersion { get; set; } = 0x01060300; + [FieldOrder(13)] public uint Unknown1 { get; set; } + [FieldOrder(14)] public uint Padding8 { get; set; } + [FieldOrder(15)] public uint Padding9 { get; set; } + [FieldOrder(16)] public uint Padding10 { get; set; } + [FieldOrder(17)] public uint Padding11 { get; set; } + [FieldOrder(18)] public uint Padding12 { get; set; } + + /// <summary> + /// Gets the machine name. string is not nul-terminated. + /// The character encoding is currently unknown — all observed files in the wild use 7-bit ASCII characters only. + /// Note that the machine type here is set in the software profile, and is not the name the user assigned to the machine. + /// </summary> + [FieldOrder(19)] [FieldLength(nameof(MachineNameSize))] + public string MachineName { get; set; } + + public override string ToString() + { + return $"{nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(Padding3)}: {Padding3}, {nameof(Padding4)}: {Padding4}, {nameof(Padding5)}: {Padding5}, {nameof(Padding6)}: {Padding6}, {nameof(Padding7)}: {Padding7}, {nameof(MachineNameAddress)}: {MachineNameAddress}, {nameof(MachineNameSize)}: {MachineNameSize}, {nameof(EncryptionMode)}: {EncryptionMode}, {nameof(MysteriousId)}: {MysteriousId}, {nameof(AntialiasLevel)}: {AntialiasLevel}, {nameof(SoftwareVersion)}: {SoftwareVersion}, {nameof(Unknown1)}: {Unknown1}, {nameof(Padding8)}: {Padding8}, {nameof(Padding9)}: {Padding9}, {nameof(Padding10)}: {Padding10}, {nameof(Padding11)}: {Padding11}, {nameof(Padding12)}: {Padding12}, {nameof(MachineName)}: {MachineName}"; + } + } + + #endregion + + #region Preview + /// <summary> + /// The files contain two preview images. + /// These are shown on the printer display when choosing which file to print, sparing the poor printer from needing to render a 3D image from scratch. + /// </summary> + public class Preview + { + /// <summary> + /// Gets the X dimension of the preview image, in pixels. + /// </summary> + [FieldOrder(0)] public uint ResolutionX { get; set; } + + /// <summary> + /// Gets the Y dimension of the preview image, in pixels. + /// </summary> + [FieldOrder(1)] public uint ResolutionY { get; set; } + + /// <summary> + /// Gets the image offset of the encoded data blob. + /// </summary> + [FieldOrder(2)] public uint ImageOffset { get; set; } + + /// <summary> + /// Gets the image length in bytes. + /// </summary> + [FieldOrder(3)] public uint ImageLength { get; set; } + + [FieldOrder(4)] public uint Unknown1 { get; set; } + [FieldOrder(5)] public uint Unknown2 { get; set; } + [FieldOrder(6)] public uint Unknown3 { get; set; } + [FieldOrder(7)] public uint Unknown4 { get; set; } + + public override string ToString() + { + return $"{nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(ImageOffset)}: {ImageOffset}, {nameof(ImageLength)}: {ImageLength}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; + } + } + + #endregion + + #region Layer + public class Layer + { + /// <summary> + /// Gets the build platform Z position for this layer, measured in millimeters. + /// </summary> + [FieldOrder(0)] public float LayerPositionZ { get; set; } + + /// <summary> + /// Gets the exposure time for this layer, in seconds. + /// </summary> + [FieldOrder(1)] public float LayerExposure { get; set; } + + /// <summary> + /// Gets how long to keep the light off after exposing this layer, in seconds. + /// </summary> + [FieldOrder(2)] public float LayerOffTimeSeconds { get; set; } + + /// <summary> + /// Gets the layer image offset to encoded layer data, and its length in bytes. + /// </summary> + [FieldOrder(3)] public uint DataAddress { get; set; } + + /// <summary> + /// Gets the layer image length in bytes. + /// </summary> + [FieldOrder(4)] public uint DataSize { get; set; } + [FieldOrder(5)] public uint Unknown1 { get; set; } + [FieldOrder(6)] public uint Unknown2 { get; set; } + [FieldOrder(7)] public uint Unknown3 { get; set; } + [FieldOrder(8)] public uint Unknown4 { get; set; } + + public override string ToString() + { + return $"{nameof(LayerPositionZ)}: {LayerPositionZ}, {nameof(LayerExposure)}: {LayerExposure}, {nameof(LayerOffTimeSeconds)}: {LayerOffTimeSeconds}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; + } + } + #endregion + + #region KeyRing + + public class KeyRing + { + public ulong Init { get; } + public ulong Key { get; private set; } + public uint Index { get; private set; } + + public KeyRing(uint seed, uint layerIndex) + { + Init = seed * 0x2d83cdac + 0xd8a83423; + Key = (layerIndex * 0x1e1530cd + 0xec3d47cd) * Init; + } + + public byte Next() + { + byte k = (byte)(Key >> (int)(8 * Index)); + + Index++; + + if ((Index & 3) == 0) + { + Key += Init; + Index = 0; + } + + return k; + } + + public List<byte> Read(List<byte> input) + { + List<byte> data = new List<byte>(input.Count); + for (int i = 0; i < input.Count; i++) + { + data[i] = (byte)(input[i] ^ Next()); + } + + return data; + } + + public byte[] Read(byte[] input) + { + byte[] data = new byte[input.Length]; + for (int i = 0; i < input.Length; i++) + { + data[i] = (byte)(input[i]^Next()); + } + return data; + } + } + + #endregion + + #endregion + + #region Properties + public FileStream InputFile { get; private set; } + public FileStream OutputFile { get; private set; } + public Header HeaderSettings { get; protected internal set; } = new Header(); + public PrintParameters PrintParametersSettings { get; protected internal set; } = new PrintParameters(); + + public SlicerInfo SlicerInfoSettings { get; protected internal set; } = new SlicerInfo(); + + public Preview[] Previews { get; protected internal set; } + + public Layer[,] Layers { get; private set; } + + public Dictionary<string, Layer> LayersHash { get; } = new Dictionary<string, Layer>(); + + private uint CurrentOffset { get; set; } + private uint LayerDataCurrentOffset { get; set; } + + + public override FileFormatType FileType => FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = { + new FileExtension("cbddlp", "Chitubox DLP Files"), + new FileExtension("ctb", "Chitubox CTB Files"), + new FileExtension("photon", "Photon Files"), + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = + { + PrintParameterModifier.InitialLayerCount, + PrintParameterModifier.InitialExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + PrintParameterModifier.BottomLayerOffTime, + PrintParameterModifier.LayerOffTime, + PrintParameterModifier.BottomLiftHeight, + PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM, + }; + + public override byte ThumbnailsCount { get; } = 2; + + public override Size[] ThumbnailsOriginalSize { get; } = {new Size(400, 300), new Size(200, 125)}; + + public override uint ResolutionX => HeaderSettings.ResolutionX; + + public override uint ResolutionY => HeaderSettings.ResolutionY; + + public override float LayerHeight => HeaderSettings.LayerHeightMilimeter; + + public override uint LayerCount => HeaderSettings.LayerCount; + + public override ushort InitialLayerCount => (ushort)HeaderSettings.BottomLayersCount; + + public override float InitialExposureTime => HeaderSettings.BottomExposureSeconds; + + public override float LayerExposureTime => HeaderSettings.LayerExposureSeconds; + public override float LiftHeight => PrintParametersSettings.LiftHeight; + public override float LiftSpeed => PrintParametersSettings.LiftingSpeed; + public override float RetractSpeed => PrintParametersSettings.RetractSpeed; + + public override float PrintTime => HeaderSettings.PrintTime; + + public override float UsedMaterial => (float) Math.Round(PrintParametersSettings.VolumeMl, 2); + + public override float MaterialCost => (float) Math.Round(PrintParametersSettings.CostDollars, 2); + + public override string MaterialName => "Unknown"; + public override string MachineName => SlicerInfoSettings.MachineName; + + public override object[] Configs => new[] { (object)HeaderSettings, PrintParametersSettings, SlicerInfoSettings }; + + public bool IsCbtFile => HeaderSettings.Magic == MAGIC_CBT; + #endregion + + #region Constructors + public ChituboxFile() + { + Previews = new Preview[ThumbnailsCount]; + } + #endregion + + #region Methods + public override void Clear() + { + base.Clear(); + + for (byte i = 0; i < ThumbnailsCount; i++) + { + Previews[i] = new Preview(); + } + + Layers = null; + + if (!ReferenceEquals(InputFile, null)) + { + InputFile.Close(); + InputFile.Dispose(); + } + + if (!ReferenceEquals(OutputFile, null)) + { + OutputFile.Close(); + OutputFile.Dispose(); + } + } + + public override void BeginEncode(string fileFullPath) + { + base.BeginEncode(fileFullPath); + LayersHash.Clear(); + + HeaderSettings.Magic = fileFullPath.EndsWith(".ctb") ? MAGIC_CBT : MAGIC_CBDDLP; + HeaderSettings.PrintParametersSize = (uint)Helpers.Serializer.SizeOf(PrintParametersSettings); + + if (IsCbtFile) + { + PrintParametersSettings.Padding4 = 0x1234; + SlicerInfoSettings.EncryptionMode = 0xf; + SlicerInfoSettings.Unknown1 = 0x200; + } + + CurrentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); + Layers = new Layer[HeaderSettings.LayerCount, HeaderSettings.AntiAliasLevel]; + OutputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write); + + OutputFile.Seek((int)CurrentOffset, SeekOrigin.Begin); + + List<byte> rawData = new List<byte>(); + ushort color15 = 0; + uint rep = 0; + + void rleRGB15() + { + switch (rep) + { + case 0: + return; + case 1: + rawData.Add((byte) (color15 & ~REPEATRGB15MASK)); + rawData.Add((byte) ((color15 & ~REPEATRGB15MASK) >> 8)); + break; + case 2: + for (int i = 0; i < 2; i++) + { + rawData.Add((byte)(color15 & ~REPEATRGB15MASK)); + rawData.Add((byte)((color15 & ~REPEATRGB15MASK) >> 8)); + } + break; + default: + rawData.Add((byte) (color15 | REPEATRGB15MASK)); + rawData.Add((byte) ((color15 | REPEATRGB15MASK) >> 8)); + rawData.Add((byte) ((rep - 1) | 0x3000)); + rawData.Add((byte) (((rep - 1) | 0x3000) >> 8)); + break; + } + } + + for (byte i = 0; i < ThumbnailsCount; i++) + { + var image = Thumbnails[i]; + + color15 = 0; + rep = 0; + rawData.Clear(); + + + for (int y = 0; y < image.Height; y++) + { + Span<Rgba32> pixelRowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < image.Width; x++) + { + + /*var ncolor15 = + (((pixelRowSpan[x].B >> (16 - 5)) & 0x1f)) + | (((pixelRowSpan[x].G >> (16 - 5)) & 0x1f) << 6) + | (((pixelRowSpan[x].R >> (16 - 5)) & 0x1f) << 11);*/ + + var ncolor15 = + (pixelRowSpan[x].B >> 3) + | ((pixelRowSpan[x].G >> 2) << 5) + | ((pixelRowSpan[x].R >> 3) << 11); + + if (ncolor15 == color15) + { + rep++; + if (rep == RLE16EncodingLimit) + { + rleRGB15(); + rep = 0; + } + } + else + { + rleRGB15(); + color15 = (ushort) ncolor15; + rep = 1; + } + } + } + + rleRGB15(); + + if(rawData.Count == 0) continue; + + if (i == (byte) FileThumbnailSize.Small) + { + HeaderSettings.PreviewSmallOffsetAddress = CurrentOffset; + } + else + { + HeaderSettings.PreviewLargeOffsetAddress = CurrentOffset; + } + + + + Preview preview = new Preview + { + ResolutionX = (uint) image.Width, + ResolutionY = (uint) image.Height, + ImageLength = (uint) rawData.Count, + }; + + CurrentOffset += (uint)Helpers.Serializer.SizeOf(preview); + preview.ImageOffset = CurrentOffset; + + Helpers.SerializeWriteFileStream(OutputFile, preview); + + CurrentOffset += (uint)rawData.Count; + OutputFile.Write(rawData.ToArray(), 0, rawData.Count); + } + + + /*for (int i = 0; i < ThumbnailsCount; i++) + { + if (i == 1) + { + HeaderSettings.PreviewSmallOffsetAddress = CurrentOffset; + } + var image = Thumbnails[i]; + Preview preview = new Preview {ResolutionX = (uint)image.Width, ResolutionY = (uint)image.Height}; + List<byte> rawData = new List<byte>(); + + Rgba32 color; + byte nrOfColor = 0; + Rgba32? prevColor = null; + + for (int y = 0; y < image.Height; y++) + { + Span<Rgba32> pixelRowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < image.Width; x++) + { + color = pixelRowSpan[x]; + if (prevColor == null) prevColor = color; + bool isLastPixel = x == (image.Width - 1) && y == (image.Height - 1); + if (color == prevColor && nrOfColor < 0x0FFF && !isLastPixel) + { + nrOfColor++; + } + else + { + + byte R = prevColor.Value.R; + byte G = prevColor.Value.G; + byte B = prevColor.Value.B; + byte X = nrOfColor > 1 ? (byte)1 : (byte)0; + + + // build 2 or 4 bytes (depending on X + // The color (R,G,B) of a pixel spans 2 bytes (little endian) and + // each color component is 5 bits: RRRRR GGG GG X BBBBB + R = (byte)Math.Round(R / 255f * 31f); + G = (byte)Math.Round(G / 255f * 31f); + B = (byte)Math.Round(B / 255f * 31f); + + byte encValue0 = (byte)(R << 3 | G >> 2); + byte encValue1 = (byte) (((G & 0b00000011) << 6) | X << 5 | B); + rawData.Add(encValue0); + rawData.Add(encValue1); + if (X == 1) + { + nrOfColor--; + // write one less than nr of pixels + byte encValue2 = (byte)(nrOfColor >> 8); + byte encValue3 = (byte)(nrOfColor & 0b000000011111111); + // seems like nr bytes pixels have 0011 as start + encValue2 = (byte)(encValue2 | 0b00110000); + rawData.Add(encValue2); + rawData.Add(encValue3); + } + + prevColor = color; + nrOfColor = 1; + } + } + } + + + preview.ImageLength = (uint)rawData.Count; + CurrentOffset += (uint)Helpers.Serializer.SizeOf(preview); + preview.ImageOffset = CurrentOffset; + + Previews[i] = preview; + + Helpers.SerializeWriteFileStream(OutputFile, preview); + CurrentOffset += Helpers.SerializeWriteFileStream(OutputFile, rawData.ToArray()); + }*/ + + + if (HeaderSettings.Version == 2) + { + HeaderSettings.PrintParametersOffsetAddress = CurrentOffset; + + CurrentOffset += Helpers.SerializeWriteFileStream(OutputFile, PrintParametersSettings); + + HeaderSettings.SlicerOffset = CurrentOffset; + HeaderSettings.SlicerSize = (uint) Helpers.Serializer.SizeOf(SlicerInfoSettings); + + SlicerInfoSettings.MachineNameAddress = (uint)(CurrentOffset + Helpers.Serializer.SizeOf(SlicerInfoSettings) - + SlicerInfoSettings.MachineNameSize); + + + CurrentOffset += Helpers.SerializeWriteFileStream(OutputFile, SlicerInfoSettings); + } + + HeaderSettings.LayersDefinitionOffsetAddress = CurrentOffset; + LayerDataCurrentOffset = CurrentOffset + (uint)Helpers.Serializer.SizeOf(new Layer()) * HeaderSettings.LayerCount * HeaderSettings.AntiAliasLevel; + } + + private List<byte> EncodeCbddlpImage(Image<Gray8> image) + { + List<byte> rawData = new List<byte>(); + + byte color; + byte black = 0; + byte white = 1; + + byte nrOfColor = 0; + byte prevColor = byte.MaxValue; + + for (int y = 0; y < image.Height; y++) + { + Span<Gray8> pixelRowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < image.Width; x++) + { + color = pixelRowSpan[x].PackedValue < 128 ? black : white; + if (prevColor == byte.MaxValue) prevColor = color; + bool isLastPixel = x == (image.Width - 1) && y == (image.Height - 1); + + if (color == prevColor && nrOfColor < 0x7D && !isLastPixel) + { + nrOfColor++; + } + else + { + byte encValue = (byte)((prevColor << 7) | nrOfColor); // push color (B/W) to highest bit and repetitions to lowest 7 bits. + rawData.Add(encValue); + prevColor = color; + nrOfColor = 1; + } + } + } + + return rawData; + } + + private List<byte> EncodeCbtImage(Image<Gray8> image, uint layerIndex) + { + List<byte> rawData = new List<byte>(); + byte color = byte.MaxValue >> 1; + uint stride = 0; + + void AddRep() + { + if (stride == 0) + { + return; + } + + if (stride > 1) + { + color |= 0x80; + } + rawData.Add(color); + + if (stride <= 1) + { + // no run needed + return; + } + + if (stride <= 0x7f) + { + rawData.Add((byte) stride); + return; + } + + if (stride <= 0x3fff) + { + rawData.Add((byte) ((stride >> 8) | 0x80)); + rawData.Add((byte)stride); + return; + } + + if (stride <= 0x1fffff) + { + rawData.Add((byte)((stride >> 16) | 0xc0)); + rawData.Add((byte)(stride >> 8)); + rawData.Add((byte)stride); + return; + } + + if (stride <= 0xfffffff) + { + rawData.Add((byte)((stride >> 24) | 0xe0)); + rawData.Add((byte)(stride >> 16)); + rawData.Add((byte)(stride >> 8)); + rawData.Add((byte)stride); + } + + } + + + for (int y = 0; y < image.Height; y++) + { + var pixelRowSpan = image.GetPixelRowSpan(y); + for (int x = 0; x < image.Width; x++) + { + var grey7 = (byte)(pixelRowSpan[x].PackedValue >> 1); + if (pixelRowSpan[x].PackedValue > 0) + { + + } + if (grey7 == color) + { + stride++; + } + else + { + AddRep(); + color = grey7; + stride = 1; + } + } + } + + AddRep(); + + if (HeaderSettings.EncryptionKey > 0) + { + List<byte> encodedData = new List<byte>(); + KeyRing kr = new KeyRing(HeaderSettings.EncryptionKey, layerIndex); + return kr.Read(rawData); + } + + return rawData; + } + + + public override void InsertLayerImageEncode(Image<Gray8> image, uint layerIndex) + { + Layer layer = new Layer(); + Layer layerHash = null; + List<byte> rawData = IsCbtFile ? EncodeCbtImage(image, layerIndex) : EncodeCbddlpImage(image); + + var byteArr = rawData.ToArray(); + + if (HeaderSettings.EncryptionKey == 0) + { + string hash = Helpers.ComputeSHA1Hash(byteArr); + if (!LayersHash.TryGetValue(hash, out layerHash)) + { + LayersHash.Add(hash, layer); + } + } + + + //layer.DataAddress = CurrentOffset + (uint)Helpers.Serializer.SizeOf(layer); + layer.DataAddress = layerHash?.DataAddress ?? LayerDataCurrentOffset; + layer.DataSize = layerHash?.DataSize ?? (uint)byteArr.Length; + layer.LayerPositionZ = layerIndex * HeaderSettings.LayerHeightMilimeter; + layer.LayerOffTimeSeconds = layerIndex < HeaderSettings.BottomLayersCount ? PrintParametersSettings.BottomLightOffDelay : PrintParametersSettings.LightOffDelay; + layer.LayerExposure = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomExposureSeconds : HeaderSettings.LayerExposureSeconds; + Layers[layerIndex, 0] = layer; + + CurrentOffset += Helpers.SerializeWriteFileStream(OutputFile, layer); + + if (!ReferenceEquals(layerHash, null)) return; + + OutputFile.Seek(LayerDataCurrentOffset, SeekOrigin.Begin); + LayerDataCurrentOffset += Helpers.WriteFileStream(OutputFile, byteArr); + OutputFile.Seek(CurrentOffset, SeekOrigin.Begin); + } + + public override void EndEncode() + { + + OutputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(OutputFile, HeaderSettings); + + OutputFile.Close(); + OutputFile.Dispose(); + + Debug.WriteLine("Encode Results:"); + Debug.WriteLine(HeaderSettings); + Debug.WriteLine(Previews[0]); + Debug.WriteLine(Previews[1]); + Debug.WriteLine(PrintParametersSettings); + Debug.WriteLine(SlicerInfoSettings); + Debug.WriteLine("-End-"); + } + + public override void Decode(string fileFullPath) + { + base.Decode(fileFullPath); + + 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.Magic != MAGIC_CBDDLP && HeaderSettings.Magic != MAGIC_CBT) + { + throw new FileLoadException("Not a valid CBDDLP nor CTB nor Photon file!", fileFullPath); + } + + if (HeaderSettings.Version == 1 || HeaderSettings.AntiAliasLevel == 0) + { + HeaderSettings.AntiAliasLevel = 1; + } + + FileFullPath = fileFullPath; + + + + Debug.Write("Header -> "); + Debug.WriteLine(HeaderSettings); + + for (byte i = 0; i < ThumbnailsCount; i++) + { + uint offsetAddress = i == 0 ? HeaderSettings.PreviewSmallOffsetAddress : HeaderSettings.PreviewLargeOffsetAddress; + if (offsetAddress == 0) continue; + + InputFile.Seek(offsetAddress, SeekOrigin.Begin); + Previews[i] = Helpers.Deserialize<Preview>(InputFile); + + Debug.Write($"Preview {i} -> "); + Debug.WriteLine(Previews[i]); + + Thumbnails[i] = new Image<Rgba32>((int)Previews[i].ResolutionX, (int)Previews[i].ResolutionY); + + InputFile.Seek(Previews[i].ImageOffset, SeekOrigin.Begin); + byte[] rawImageData = new byte[Previews[i].ImageLength]; + InputFile.Read(rawImageData, 0, (int)Previews[i].ImageLength); + int x = 0; + int y = 0; + for (int n = 0; n < Previews[i].ImageLength; n++) + { + uint dot = (uint)(rawImageData[n] & 0xFF | ((rawImageData[++n] & 0xFF) << 8)); + //uint color = ((dot & 0xF800) << 8) | ((dot & 0x07C0) << 5) | ((dot & 0x001F) << 3); + byte red = (byte)(((dot >> 11) & 0x1F) << 3); + byte green = (byte)(((dot >> 6) & 0x1F) << 3); + byte blue = (byte)((dot & 0x1F) << 3); + int repeat = 1; + if ((dot & 0x0020) == 0x0020) + { + repeat += rawImageData[++n] & 0xFF | ((rawImageData[++n] & 0x0F) << 8); + } + + + for (int j = 0; j < repeat; j++) + { + Thumbnails[i][x, y] = new Rgba32(red, green, blue, 255); + x++; + + if (x == Previews[i].ResolutionX) + { + x = 0; + y++; + } + } + } + } + + //if (HeaderSettings.Version == 2) + //{ + if (HeaderSettings.PrintParametersOffsetAddress > 0) + { + InputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); + PrintParametersSettings = Helpers.Deserialize<PrintParameters>(InputFile); + Debug.Write("Print Parameters -> "); + Debug.WriteLine(PrintParametersSettings); + + + } + + if (HeaderSettings.SlicerOffset > 0) + { + InputFile.Seek(HeaderSettings.SlicerOffset, SeekOrigin.Begin); + SlicerInfoSettings = Helpers.Deserialize<SlicerInfo>(InputFile); + Debug.Write("Slicer Info -> "); + Debug.WriteLine(SlicerInfoSettings); + } + + /*InputFile.BaseStream.Seek(MachineInfoSettings.MachineNameAddress, SeekOrigin.Begin); + byte[] bytes = InputFile.ReadBytes((int)MachineInfoSettings.MachineNameSize); + MachineName = System.Text.Encoding.UTF8.GetString(bytes); + Debug.WriteLine($"{nameof(MachineName)}: {MachineName}");*/ + //} + + Layers = new Layer[HeaderSettings.LayerCount, HeaderSettings.AntiAliasLevel]; + + uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + + Image<Gray8> image = new Image<Gray8>((int)HeaderSettings.BedSizeX, (int)HeaderSettings.BedSizeY); + + + for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) + { + Debug.WriteLine($"-Image GROUP {aaIndex}-"); + for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + InputFile.Seek(layerOffset, SeekOrigin.Begin); + Layer layer = Helpers.Deserialize<Layer>(InputFile); + Layers[layerIndex, aaIndex] = layer; + //Layers.Add(layer); + + layerOffset += (uint)Helpers.Serializer.SizeOf(layer); + Debug.Write($"LAYER {layerIndex} -> "); + Debug.WriteLine(layer); + } + } + } + + public override void Extract(string path, bool emptyFirst = true, bool genericConfigExtract = false, + bool genericLayersExtract = false) => + base.Extract(path, emptyFirst, true, true); + + private Image<Gray8> DecodeCbddlpImage(uint layerIndex) + { + Image<Gray8> image = new Image<Gray8>((int)HeaderSettings.ResolutionX, (int)HeaderSettings.ResolutionY); + + for (uint aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) + { + Layer layer = Layers[layerIndex, aaIndex]; + InputFile.Seek(layer.DataAddress, SeekOrigin.Begin); + byte[] rawImageData = new byte[(int)layer.DataSize]; + InputFile.Read(rawImageData, 0, (int)layer.DataSize); + uint x = 0; + uint y = 0; + + foreach (byte rle in rawImageData) + { + // From each byte retrieve color (highest bit) and number of pixels of that color (lowest 7 bits) + uint length = (uint)(rle & 0x7F); // turn highest bit of + bool color = (rle & 0x80) == 0x80; // only read 1st bit + + if (length == 0) + { + Debug.WriteLine("Corrupted RLE data."); + continue; + } + + if (!color) // Skip black pixels + { + uint x2 = x + length; + y += x2 / HeaderSettings.ResolutionX; + x = x2 % HeaderSettings.ResolutionX; + continue; + } + + var span = image.GetPixelRowSpan((int)y); + while(length-- > 0) + { + if (x >= HeaderSettings.ResolutionX) + { + y++; + x = 0; + span = image.GetPixelRowSpan((int)y); + } + + span[(int)x] = Helpers.Gray8White; + x++; + } + } + } + + return image; + } + + private Image<Gray8> DecodeCbtImage(uint layerIndex) + { + Image<Gray8> image = new Image<Gray8>((int)HeaderSettings.ResolutionX, (int)HeaderSettings.ResolutionY); + Layer layer = Layers[layerIndex, 0]; + + InputFile.Seek(layer.DataAddress, SeekOrigin.Begin); + byte[] encodedImageData = new byte[(int)layer.DataSize]; + InputFile.Read(encodedImageData, 0, (int)layer.DataSize); + uint x = 0; + uint y = 0; + + var rawImageData = encodedImageData; + + if (HeaderSettings.EncryptionKey > 0) + { + KeyRing kr = new KeyRing(HeaderSettings.EncryptionKey, layerIndex); + rawImageData = kr.Read(encodedImageData); + } + + for (var n = 0; n < rawImageData.Length; n++) + { + byte code = rawImageData[n]; + uint stride = 1; + + if ((code & 0x80) == 0x80) // It's a run + { + code &= 0x7f; // Get the run length + n++; + + var slen = rawImageData[n]; + + if ((slen & 0x80) == 0) + { + stride = slen; + } + else if ((slen & 0xc0) == 0x80) + { + stride = (uint) (((slen & 0x3f) << 8) + rawImageData[n + 1]); + n++; + } + else if((slen & 0xe0) == 0xc0) + { + stride = (uint) (((slen & 0x1f) << 16) + (rawImageData[n + 1] << 8) + rawImageData[n + 2]); + n += 2; + } + else if((slen & 0xf0) == 0xe0) + { + stride = (uint) (((slen & 0xf) << 24) + (rawImageData[n + 1] << 16) + (rawImageData[n + 2] << 8) + rawImageData[n + 3]); + + n += 3; + } + else + { + Debug.WriteLine("Corrupted RLE data"); + } + } + + // Bit extend from 7-bit to 8-bit greymap + if (code != 0) + { + code = (byte) ((code << 1) | 1); + } + + if(stride == 0) continue; // Nothing to do + + if (code == 0) // Ignore blacks, spare cycles + { + uint x2 = x + stride; + y += x2 / HeaderSettings.ResolutionX; + x = x2 % HeaderSettings.ResolutionX; + continue; + } + + var span = image.GetPixelRowSpan((int)y); + while (stride-- > 0) + { + if (x >= HeaderSettings.ResolutionX) + { + y++; + x = 0; + span = image.GetPixelRowSpan((int)y); + } + + span[(int) x].PackedValue = code; + + x++; + } + } + + return image; + } + + public override Image<Gray8> GetLayerImage(uint layerIndex) + { + if (layerIndex >= HeaderSettings.LayerCount) + { + throw new IndexOutOfRangeException($"Layer {layerIndex} doesn't exists, out of bounds."); + } + + return IsCbtFile ? DecodeCbtImage(layerIndex) : DecodeCbddlpImage(layerIndex); + } + + public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) + { + var baseValue = base.GetValueFromPrintParameterModifier(modifier); + if (!ReferenceEquals(baseValue, null)) return baseValue; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) return PrintParametersSettings.BottomLightOffDelay; + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return PrintParametersSettings.LightOffDelay; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) return PrintParametersSettings.BottomLiftHeight; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) return PrintParametersSettings.BottomLiftSpeed; + /*if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) return PrintParametersSettings.LiftHeight; + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) return PrintParametersSettings.LiftingSpeed; + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) return PrintParametersSettings.RetractSpeed;*/ + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) return HeaderSettings.BottomLightPWM; + if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) return HeaderSettings.LightPWM; + + + + return null; + } + + public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) + { + void UpdateLayers() + { + for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) + { + for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + // Bottom : others + Layers[layerIndex, aaIndex].LayerExposure = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomExposureSeconds : HeaderSettings.LayerExposureSeconds; + Layers[layerIndex, aaIndex].LayerOffTimeSeconds = layerIndex < HeaderSettings.BottomLayersCount ? PrintParametersSettings.BottomLightOffDelay : PrintParametersSettings.LightOffDelay; + } + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + HeaderSettings.BottomLayersCount = + PrintParametersSettings.BottomLayerCount = value.Convert<uint>(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + HeaderSettings.BottomExposureSeconds = value.Convert<float>(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + HeaderSettings.LayerExposureSeconds = value.Convert<float>(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) + { + PrintParametersSettings.BottomLightOffDelay = value.Convert<float>(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) + { + HeaderSettings.LayerOffTime = + PrintParametersSettings.LightOffDelay = value.Convert<float>(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) + { + PrintParametersSettings.BottomLiftHeight = value.Convert<float>(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) + { + PrintParametersSettings.BottomLiftSpeed = value.Convert<float>(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + { + PrintParametersSettings.LiftHeight = value.Convert<float>(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + { + PrintParametersSettings.LiftingSpeed = value.Convert<float>(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + { + PrintParametersSettings.RetractSpeed = value.Convert<float>(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) + { + HeaderSettings.BottomLightPWM = value.Convert<ushort>(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) + { + HeaderSettings.LightPWM = value.Convert<ushort>(); + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null) + { + InputFile.Dispose(); + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + + } + + using (InputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) + { + + InputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(InputFile, HeaderSettings); + + if (HeaderSettings.Version == 2 && HeaderSettings.PrintParametersOffsetAddress > 0) + { + InputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(InputFile, PrintParametersSettings); + Helpers.SerializeWriteFileStream(InputFile, SlicerInfoSettings); + } + + uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) + { + for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + InputFile.Seek(layerOffset, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(InputFile, Layers[layerIndex, aaIndex]); + layerOffset += (uint)Helpers.Serializer.SizeOf(Layers[layerIndex, aaIndex]); + } + } + InputFile.Close(); + } + + Decode(FileFullPath); + } + + public override bool Convert(Type to, string fileFullPath) + { + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/PrusaSL1Reader/FileFormat.cs b/PrusaSL1Reader/FileFormat.cs index c70fd8e..74c0fbe 100644 --- a/PrusaSL1Reader/FileFormat.cs +++ b/PrusaSL1Reader/FileFormat.cs @@ -6,12 +6,13 @@ * of this license document, but changing it is not allowed. */ using System; -using System.Dynamic; +using System.Drawing; using System.IO; using System.Linq; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; namespace PrusaSL1Reader { @@ -30,6 +31,15 @@ namespace PrusaSL1Reader Archive, Binary } + + /// <summary> + /// Enumeration of file thumbnail size types + /// </summary> + public enum FileThumbnailSize : byte + { + Small = 0, + Large + } #endregion #region Sub Classes @@ -48,9 +58,9 @@ namespace PrusaSL1Reader public static PrintParameterModifier LayerOffTime { get; } = new PrintParameterModifier("Layer Off Time", @"Modify 'Layer Off Time' seconds", "s"); public static PrintParameterModifier BottomLiftHeight { get; } = new PrintParameterModifier("Bottom Lift Height", @"Modify 'Bottom Lift Height' millimeters between bottom layers", "mm"); public static PrintParameterModifier BottomLiftSpeed { get; } = new PrintParameterModifier("Bottom Lift Speed", @"Modify 'Bottom Lift Speed' mm/min between bottom layers", "mm/min"); - public static PrintParameterModifier ZRetractHeight { get; } = new PrintParameterModifier("Z Retract Height", @"Modify 'Z Retract Height' millimeters between layers", "mm"); - public static PrintParameterModifier ZRetractSpeed { get; } = new PrintParameterModifier("Z Retract Speed", @"Modify 'Z Retract Speed' mm/min between layers", "mm/min", 10, 5000); - public static PrintParameterModifier ZDetractSpeed { get; } = new PrintParameterModifier("Z Detract Speed", @"Modify 'Z Detract Speed' mm/min between layers", "mm/min", 10, 5000); + public static PrintParameterModifier LiftHeight { get; } = new PrintParameterModifier("Lift Height", @"Modify 'Lift Height' millimeters between layers", "mm"); + public static PrintParameterModifier LiftSpeed { get; } = new PrintParameterModifier("Lift Speed", @"Modify 'Lift Speed' mm/min between layers", "mm/min", 10, 5000); + public static PrintParameterModifier RetractSpeed { get; } = new PrintParameterModifier("Retract Speed", @"Modify 'Retract Speed' mm/min between layers", "mm/min", 10, 5000); public static PrintParameterModifier BottomLightPWM { get; } = new PrintParameterModifier("Bottom Light PWM", @"Modify 'Bottom Light PWM' value", null, 50, byte.MaxValue); public static PrintParameterModifier LightPWM { get; } = new PrintParameterModifier("Light PWM", @"Modify 'Light PWM' value", null, 50, byte.MaxValue); @@ -116,7 +126,7 @@ namespace PrusaSL1Reader public static FileFormat[] AvaliableFormats { get; } = { new SL1File(), // Prusa SL1 - new CbddlpFile(), // cbddlp, photon + new ChituboxFile(), // cbddlp, cbt, photon new ZCodexFile(), // zcodex }; @@ -203,7 +213,7 @@ namespace PrusaSL1Reader } } - public abstract string FileFullPath { get; set; } + public string FileFullPath { get; set; } public abstract byte ThumbnailsCount { get; } @@ -223,7 +233,9 @@ namespace PrusaSL1Reader } } - public abstract Image<Rgba32>[] Thumbnails { get; set; } + public abstract Size[] ThumbnailsOriginalSize { get; } + + public Image<Rgba32>[] Thumbnails { get; set; } public abstract uint ResolutionX { get; } @@ -241,11 +253,11 @@ namespace PrusaSL1Reader public abstract float LayerExposureTime { get; } - public abstract float ZRetractHeight { get; } + public abstract float LiftHeight { get; } - public abstract float ZRetractSpeed { get; } + public abstract float RetractSpeed { get; } - public abstract float ZDetractSpeed { get; } + public abstract float LiftSpeed { get; } public abstract float PrintTime { get; } @@ -328,12 +340,62 @@ namespace PrusaSL1Reader return FileExtensions.Any(fileExtension => fileExtension.Extension.Equals(extension)); } + public string GetFileExtensions(string prepend = ".", string separator = ", ") + { + var result = string.Empty; + + foreach (var fileExt in FileExtensions) + { + if (!ReferenceEquals(result, string.Empty)) + { + result += separator; + } + result += $"{prepend}{fileExt.Extension}"; + } + + return result; + } + + public Image<Rgba32> GetThumbnail(uint maxHeight = 400) + { + for (int i = 0; i < ThumbnailsCount; i++) + { + if(ReferenceEquals(Thumbnails[i], null)) continue; + if (Thumbnails[i].Height <= maxHeight) return Thumbnails[i]; + } + + return null; + } + + public void SetThumbnails(Image<Rgba32>[] images) + { + for (var i = 0; i < ThumbnailsCount; i++) + { + Thumbnails[i] = images[Math.Min(i, images.Length - 1)].Clone(); + } + } + + public void SetThumbnails(Image<Rgba32> image) + { + for (var i = 0; i < ThumbnailsCount; i++) + { + Thumbnails[i] = image.Clone(); + } + } + public virtual void BeginEncode(string fileFullPath) { if (File.Exists(fileFullPath)) { File.Delete(fileFullPath); } + + for (var i = 0; i < Thumbnails.Length; i++) + { + if(ReferenceEquals(Thumbnails[i], null)) continue; + + Thumbnails[i].Mutate(x => x.Resize(ThumbnailsOriginalSize[i].Width, ThumbnailsOriginalSize[i].Height)); + } } public abstract void InsertLayerImageEncode(Image<Gray8> image, uint layerIndex); @@ -421,12 +483,13 @@ namespace PrusaSL1Reader if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) return LayerExposureTime; - if (ReferenceEquals(modifier, PrintParameterModifier.ZRetractHeight)) - return ZRetractHeight; - if (ReferenceEquals(modifier, PrintParameterModifier.ZRetractSpeed)) - return ZRetractSpeed; - if (ReferenceEquals(modifier, PrintParameterModifier.ZDetractSpeed)) - return ZDetractSpeed; + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + return LiftHeight; + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + return LiftSpeed; + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + return RetractSpeed; + return null; diff --git a/PrusaSL1Reader/Helpers.cs b/PrusaSL1Reader/Helpers.cs index f2f9396..22decff 100644 --- a/PrusaSL1Reader/Helpers.cs +++ b/PrusaSL1Reader/Helpers.cs @@ -6,7 +6,9 @@ * of this license document, but changing it is not allowed. */ +using System; using System.IO; +using System.Security.Cryptography; using BinarySerialization; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; @@ -86,5 +88,11 @@ namespace PrusaSL1Reader return WriteFileStream(fs, stream); } } + + public static SHA1CryptoServiceProvider SHA1 { get; } = new SHA1CryptoServiceProvider(); + public static string ComputeSHA1Hash(byte[] input) + { + return Convert.ToBase64String(SHA1.ComputeHash(input)); + } } } diff --git a/PrusaSL1Reader/IFileFormat.cs b/PrusaSL1Reader/IFileFormat.cs index 629d37f..99e0260 100644 --- a/PrusaSL1Reader/IFileFormat.cs +++ b/PrusaSL1Reader/IFileFormat.cs @@ -7,6 +7,7 @@ */ using System; +using System.Drawing; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; @@ -58,6 +59,11 @@ namespace PrusaSL1Reader /// Gets the number of created thumbnails /// </summary> byte CreatedThumbnailsCount { get; } + + /// <summary> + /// Gets the original thumbnail sizes + /// </summary> + Size[] ThumbnailsOriginalSize { get; } /// <summary> /// Gets the thumbnails for this <see cref="FileFormat"/> @@ -105,19 +111,19 @@ namespace PrusaSL1Reader float LayerExposureTime { get; } /// <summary> - /// Gets the height in mm to retract between layers + /// Gets the speed in mm/min for the detracts /// </summary> - float ZRetractHeight { get; } + float LiftSpeed { get; } /// <summary> - /// Gets the speed in mm/min for the retracts + /// Gets the height in mm to retract between layers /// </summary> - float ZRetractSpeed { get; } + float LiftHeight { get; } /// <summary> - /// Gets the speed in mm/min for the detracts + /// Gets the speed in mm/min for the retracts /// </summary> - float ZDetractSpeed { get; } + float RetractSpeed { get; } /// <summary> /// Gets the estimate print time in seconds @@ -182,6 +188,31 @@ namespace PrusaSL1Reader bool IsExtensionValid(string extension, bool isFilePath = false); /// <summary> + /// Gets all valid file extensions in a specified format + /// </summary> + + string GetFileExtensions(string prepend = ".", string separator = ", "); + + /// <summary> + /// Gets a thumbnail by it height or lower + /// </summary> + /// <param name="maxHeight">Max height allowed</param> + /// <returns></returns> + Image<Rgba32> GetThumbnail(uint maxHeight = 400); + + /// <summary> + /// Sets thumbnails from a list of thumbnails and clone them + /// </summary> + /// <param name="images"></param> + void SetThumbnails(Image<Rgba32>[] images); + + /// <summary> + /// Sets all thumbnails the same image + /// </summary> + /// <param name="images">Image to set</param> + void SetThumbnails(Image<Rgba32> images); + + /// <summary> /// Begin encode to an output file /// </summary> /// <param name="fileFullPath">Output file</param> diff --git a/PrusaSL1Reader/PrusaSL1Reader.csproj b/PrusaSL1Reader/PrusaSL1Reader.csproj index 2562899..144ecc5 100644 --- a/PrusaSL1Reader/PrusaSL1Reader.csproj +++ b/PrusaSL1Reader/PrusaSL1Reader.csproj @@ -7,9 +7,9 @@ <PackageProjectUrl>https://github.com/sn4k3/PrusaSL1Viewer</PackageProjectUrl> <PackageIcon></PackageIcon> <RepositoryUrl>https://github.com/sn4k3/PrusaSL1Viewer</RepositoryUrl> - <AssemblyVersion>0.3.0.0</AssemblyVersion> - <FileVersion>0.3.0.0</FileVersion> - <Version>0.3.0</Version> + <AssemblyVersion>0.3.1.0</AssemblyVersion> + <FileVersion>0.3.1.0</FileVersion> + <Version>0.3.1</Version> <Description>Open, view, edit, extract and convert DLP/SLA files generated from Slicers</Description> </PropertyGroup> diff --git a/PrusaSL1Reader/SL1File.cs b/PrusaSL1Reader/SL1File.cs index 1a0a877..da4d63a 100644 --- a/PrusaSL1Reader/SL1File.cs +++ b/PrusaSL1Reader/SL1File.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; using System.Globalization; using System.IO; using System.IO.Compression; @@ -284,11 +285,10 @@ namespace PrusaSL1Reader PrintParameterModifier.ExposureSeconds, }; - public override string FileFullPath { get; set; } - public override byte ThumbnailsCount { get; } = 2; - public override Image<Rgba32>[] Thumbnails { get; set; } + public override Size[] ThumbnailsOriginalSize { get; } = { new Size(400, 400), new Size(800, 480) }; + //public override Image<Rgba32>[] Thumbnails { get; set; } public override uint ResolutionX => PrinterSettings.DisplayPixelsX; @@ -304,11 +304,11 @@ namespace PrusaSL1Reader public override float LayerExposureTime => OutputConfigSettings.ExpTime; - public override float ZRetractHeight { get; } = 0; + public override float LiftHeight { get; } = 0; - public override float ZRetractSpeed { get; } = 0; + public override float LiftSpeed { get; } = 0; - public override float ZDetractSpeed { get; } = 0; + public override float RetractSpeed { get; } = 0; public override float PrintTime => OutputConfigSettings.PrintTime; @@ -433,7 +433,7 @@ namespace PrusaSL1Reader Statistics.ExecutionTime.Restart(); InputFile = ZipFile.OpenRead(FileFullPath); - byte thumbnailIndex = 0; + foreach (ZipArchiveEntry entity in InputFile.Entries) { if (entity.Name.EndsWith(".ini")) @@ -476,11 +476,17 @@ namespace PrusaSL1Reader { using (Stream stream = entity.Open()) { - Thumbnails[thumbnailIndex] = Image.Load<Rgba32>(stream); + var image = Image.Load<Rgba32>(stream); + byte thumbnailIndex = + (byte) (image.Width == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Width && + image.Height == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Height + ? FileThumbnailSize.Small + : FileThumbnailSize.Large); + Thumbnails[thumbnailIndex] = image; stream.Close(); } - thumbnailIndex++; + //thumbnailIndex++; continue; } @@ -589,52 +595,48 @@ namespace PrusaSL1Reader { if (!IsValid) return false; - if (to == typeof(CbddlpFile)) + if (to == typeof(ChituboxFile)) { - CbddlpFile file = new CbddlpFile - { - HeaderSettings = new CbddlpFile.Header - { - Version = 2, - AntiAliasLevel = LookupCustomValue<uint>("AntiAliasLevel", 1), - BedSizeX = PrinterSettings.DisplayWidth, - BedSizeY = PrinterSettings.DisplayHeight, - BedSizeZ = PrinterSettings.MaxPrintHeight, - BottomExposureSeconds = MaterialSettings.InitialExposureTime, - BottomLayersCount = PrintSettings.FadedLayers, - BottomLightPWM = LookupCustomValue<ushort>("BottomLightPWM", 255), // TODO - LayerCount = LayerCount, - LayerExposureSeconds = MaterialSettings.ExposureTime, - LayerHeightMilimeter = PrintSettings.LayerHeight, - LayerOffTime = LookupCustomValue<float>("LayerOffTime", 0), // TODO - LightPWM = LookupCustomValue<ushort>("LightPWM", 255), // TODO - PrintTime = (uint) OutputConfigSettings.PrintTime, - ProjectorType = PrinterSettings.DisplayMirrorX ? 1u : 0u, - ResolutionX = PrinterSettings.DisplayPixelsX, - ResolutionY = PrinterSettings.DisplayPixelsY, - }, - PrintParametersSettings = new CbddlpFile.PrintParameters - { - BottomLayerCount = PrintSettings.FadedLayers, - BottomLiftHeight = LookupCustomValue<float>("BottomLiftHeight", 5), // TODO - BottomLiftSpeed = LookupCustomValue<float>("BottomLiftSpeed", 60), // TODO - BottomLightOffDelay = LookupCustomValue<float>("BottomLightOffDelay", 0), // TODO - CostDollars = MaterialCost, - LiftHeight = LookupCustomValue<float>("LiftHeight", 5), // TODO, // TODO - LiftingSpeed = LookupCustomValue<float>("LiftingSpeed", 60), // TODO - LightOffDelay = LookupCustomValue<float>("LightOffDelay", 0), // TODO - RetractSpeed = LookupCustomValue<float>("RetractSpeed", 150), // TODO - VolumeMl = OutputConfigSettings.UsedMaterial, - WeightG = OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity - }, - Thumbnails = Thumbnails, - MachineInfoSettings = new CbddlpFile.MachineInfo() - { - MachineName = PrinterSettings.PrinterSettingsId, - MachineNameSize = (uint)PrinterSettings.PrinterSettingsId.Length + ChituboxFile file = new ChituboxFile(); + + + file.HeaderSettings.Version = 2; + file.HeaderSettings.BedSizeX = PrinterSettings.DisplayWidth; + file.HeaderSettings.BedSizeY = PrinterSettings.DisplayHeight; + file.HeaderSettings.BedSizeZ = PrinterSettings.MaxPrintHeight; + file.HeaderSettings.OverallHeightMilimeter = TotalHeight; + file.HeaderSettings.BottomExposureSeconds = MaterialSettings.InitialExposureTime; + file.HeaderSettings.BottomLayersCount = PrintSettings.FadedLayers; + file.HeaderSettings.BottomLightPWM = LookupCustomValue<ushort>("BottomLightPWM", file.HeaderSettings.BottomLightPWM); + file.HeaderSettings.LayerCount = LayerCount; + file.HeaderSettings.LayerExposureSeconds = MaterialSettings.ExposureTime; + file.HeaderSettings.LayerHeightMilimeter = PrintSettings.LayerHeight; + file.HeaderSettings.LayerOffTime = LookupCustomValue<float>("LayerOffTime", file.HeaderSettings.LayerOffTime); + file.HeaderSettings.LightPWM = LookupCustomValue<ushort>("LightPWM", file.HeaderSettings.LightPWM); + file.HeaderSettings.PrintTime = (uint) OutputConfigSettings.PrintTime; + file.HeaderSettings.ProjectorType = PrinterSettings.DisplayMirrorX ? 1u : 0u; + file.HeaderSettings.ResolutionX = PrinterSettings.DisplayPixelsX; + file.HeaderSettings.ResolutionY = PrinterSettings.DisplayPixelsY; + + //file.HeaderSettings.AntiAliasLevel = LookupCustomValue<uint>("AntiAliasLevel", 1); + + + file.PrintParametersSettings.BottomLayerCount = PrintSettings.FadedLayers; + file.PrintParametersSettings.BottomLiftHeight = LookupCustomValue<float>("BottomLiftHeight", file.PrintParametersSettings.BottomLiftHeight); + file.PrintParametersSettings.BottomLiftSpeed = LookupCustomValue<float>("BottomLiftSpeed", file.PrintParametersSettings.BottomLiftSpeed); + file.PrintParametersSettings.BottomLightOffDelay = LookupCustomValue<float>("BottomLightOffDelay", file.PrintParametersSettings.BottomLightOffDelay); + file.PrintParametersSettings.CostDollars = MaterialCost; + file.PrintParametersSettings.LiftHeight = LookupCustomValue<float>("LiftHeight", file.PrintParametersSettings.LiftHeight); + file.PrintParametersSettings.LiftingSpeed = LookupCustomValue<float>("LiftingSpeed", file.PrintParametersSettings.LiftingSpeed); + file.PrintParametersSettings.LightOffDelay = LookupCustomValue<float>("LightOffDelay", file.PrintParametersSettings.LightOffDelay); + file.PrintParametersSettings.RetractSpeed = LookupCustomValue<float>("RetractSpeed", file.PrintParametersSettings.RetractSpeed); + file.PrintParametersSettings.VolumeMl = OutputConfigSettings.UsedMaterial; + file.PrintParametersSettings.WeightG = (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 2); + + file.SetThumbnails(Thumbnails); + file.SlicerInfoSettings.MachineName = PrinterSettings.PrinterSettingsId; + file.SlicerInfoSettings.MachineNameSize = (uint) PrinterSettings.PrinterSettingsId.Length; - }, - }; if (LookupCustomValue<bool>("FLIP_XY", false, true)) { @@ -723,9 +725,10 @@ namespace PrusaSL1Reader } }, }, - Thumbnails = Thumbnails, }; + file.SetThumbnails(Thumbnails); + float usedMaterial = UsedMaterial / LayerCount; for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { diff --git a/PrusaSL1Reader/ZCodexFile.cs b/PrusaSL1Reader/ZCodexFile.cs index e69a39b..dbddadf 100644 --- a/PrusaSL1Reader/ZCodexFile.cs +++ b/PrusaSL1Reader/ZCodexFile.cs @@ -7,6 +7,7 @@ */ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.IO.Compression; using System.Runtime.CompilerServices; @@ -159,16 +160,14 @@ namespace PrusaSL1Reader PrintParameterModifier.ExposureSeconds, - PrintParameterModifier.ZRetractHeight, - PrintParameterModifier.ZRetractSpeed, - PrintParameterModifier.ZDetractSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.RetractSpeed, + PrintParameterModifier.LiftSpeed, }; - public override string FileFullPath { get; set; } - public override byte ThumbnailsCount { get; } = 1; - public override Image<Rgba32>[] Thumbnails { get; set; } + public override Size[] ThumbnailsOriginalSize { get; } = {new Size(320, 180)}; public override uint ResolutionX => 1440; @@ -183,9 +182,11 @@ namespace PrusaSL1Reader public override float InitialExposureTime => UserSettings.BottomLayerExposureTime / 1000; public override float LayerExposureTime => UserSettings.LayerExposureTime / 1000; - public override float ZRetractHeight => UserSettings.ZLiftDistance; - public override float ZRetractSpeed => (uint) UserSettings.ZLiftRetractRate; - public override float ZDetractSpeed => (uint) UserSettings.ZLiftFeedRate; + public override float LiftHeight => UserSettings.ZLiftDistance; + + public override float LiftSpeed => UserSettings.ZLiftFeedRate; + + public override float RetractSpeed => UserSettings.ZLiftRetractRate; public override float PrintTime => ResinMetadataSettings.PrintTime; @@ -399,21 +400,21 @@ namespace PrusaSL1Reader return true; } - if (ReferenceEquals(modifier, PrintParameterModifier.ZRetractHeight)) + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) { UserSettings.ZLiftDistance = value.Convert<float>(); UpdateGCode(); return true; } - if (ReferenceEquals(modifier, PrintParameterModifier.ZRetractSpeed)) + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) { - UserSettings.ZLiftRetractRate = value.Convert<float>(); + UserSettings.ZLiftFeedRate = value.Convert<float>(); UpdateGCode(); return true; } - if (ReferenceEquals(modifier, PrintParameterModifier.ZDetractSpeed)) + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) { - UserSettings.ZLiftFeedRate = value.Convert<float>(); + UserSettings.ZLiftRetractRate = value.Convert<float>(); UpdateGCode(); return true; } @@ -450,10 +451,10 @@ namespace PrusaSL1Reader private void UpdateGCode() { GCode = Regex.Replace(GCode, @"Z[+]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", - $"Z{UserSettings.ZLiftDistance} F{UserSettings.ZLiftRetractRate}"); + $"Z{UserSettings.ZLiftDistance} F{UserSettings.ZLiftFeedRate}"); GCode = Regex.Replace(GCode, @"Z-[-]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", - $"Z-{UserSettings.ZLiftDistance - LayerHeight} F{UserSettings.ZLiftFeedRate}"); + $"Z-{UserSettings.ZLiftDistance - LayerHeight} F{UserSettings.ZLiftRetractRate}"); } #endregion diff --git a/PrusaSL1Viewer/FrmMain.Designer.cs b/PrusaSL1Viewer/FrmMain.Designer.cs index cd4fd71..b8609b7 100644 --- a/PrusaSL1Viewer/FrmMain.Designer.cs +++ b/PrusaSL1Viewer/FrmMain.Designer.cs @@ -33,6 +33,7 @@ this.menu = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuFileOpen = new System.Windows.Forms.ToolStripMenuItem(); + this.menuFileOpenNewWindow = new System.Windows.Forms.ToolStripMenuItem(); this.menuFileReload = new System.Windows.Forms.ToolStripMenuItem(); this.menuFileSave = new System.Windows.Forms.ToolStripMenuItem(); this.menuFileSaveAs = new System.Windows.Forms.ToolStripMenuItem(); @@ -44,7 +45,6 @@ this.menuFileExit = new System.Windows.Forms.ToolStripMenuItem(); this.menuEdit = new System.Windows.Forms.ToolStripMenuItem(); this.viewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.menuViewRotateImage = new System.Windows.Forms.ToolStripMenuItem(); this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuAboutWebsite = new System.Windows.Forms.ToolStripMenuItem(); this.menuAboutDonate = new System.Windows.Forms.ToolStripMenuItem(); @@ -70,6 +70,14 @@ this.tsThumbnailsNext = new System.Windows.Forms.ToolStripButton(); this.tsThumbnailsExport = new System.Windows.Forms.ToolStripButton(); this.tsThumbnailsResolution = new System.Windows.Forms.ToolStripLabel(); + this.lvProperties = new System.Windows.Forms.ListView(); + this.lvChKey = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.lvChValue = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.tsProperties = new System.Windows.Forms.ToolStrip(); + this.tsPropertiesLabelCount = new System.Windows.Forms.ToolStripLabel(); + this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.tsPropertiesLabelGroups = new System.Windows.Forms.ToolStripLabel(); + this.tsPropertiesButtonSave = new System.Windows.Forms.ToolStripButton(); this.tbpGCode = new System.Windows.Forms.TabPage(); this.tbGCode = new System.Windows.Forms.TextBox(); this.tsGCode = new System.Windows.Forms.ToolStrip(); @@ -78,14 +86,7 @@ this.tsGcodeLabelChars = new System.Windows.Forms.ToolStripLabel(); this.tsGCodeButtonSave = new System.Windows.Forms.ToolStripButton(); this.imageList16x16 = new System.Windows.Forms.ImageList(this.components); - this.tsProperties = new System.Windows.Forms.ToolStrip(); - this.lvProperties = new System.Windows.Forms.ListView(); - this.lvChKey = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.lvChValue = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.tsPropertiesLabelCount = new System.Windows.Forms.ToolStripLabel(); - this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); - this.tsPropertiesLabelGroups = new System.Windows.Forms.ToolStripLabel(); - this.tsPropertiesButtonSave = new System.Windows.Forms.ToolStripButton(); + this.tsLayerImageRotate = new System.Windows.Forms.ToolStripButton(); this.menu.SuspendLayout(); this.mainTable.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); @@ -106,9 +107,9 @@ this.scLeft.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.pbThumbnail)).BeginInit(); this.tsThumbnails.SuspendLayout(); + this.tsProperties.SuspendLayout(); this.tbpGCode.SuspendLayout(); this.tsGCode.SuspendLayout(); - this.tsProperties.SuspendLayout(); this.SuspendLayout(); // // menu @@ -128,6 +129,7 @@ // this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.menuFileOpen, + this.menuFileOpenNewWindow, this.menuFileReload, this.menuFileSave, this.menuFileSaveAs, @@ -146,16 +148,26 @@ this.menuFileOpen.Image = global::PrusaSL1Viewer.Properties.Resources.Open_16x16; this.menuFileOpen.Name = "menuFileOpen"; this.menuFileOpen.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); - this.menuFileOpen.Size = new System.Drawing.Size(186, 22); + this.menuFileOpen.Size = new System.Drawing.Size(261, 22); this.menuFileOpen.Text = "&Open"; this.menuFileOpen.Click += new System.EventHandler(this.ItemClicked); // + // menuFileOpenNewWindow + // + this.menuFileOpenNewWindow.Image = global::PrusaSL1Viewer.Properties.Resources.Open_16x16; + this.menuFileOpenNewWindow.Name = "menuFileOpenNewWindow"; + this.menuFileOpenNewWindow.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + | System.Windows.Forms.Keys.O))); + this.menuFileOpenNewWindow.Size = new System.Drawing.Size(261, 22); + this.menuFileOpenNewWindow.Text = "Open in new window"; + this.menuFileOpenNewWindow.Click += new System.EventHandler(this.ItemClicked); + // // menuFileReload // this.menuFileReload.Image = global::PrusaSL1Viewer.Properties.Resources.File_Refresh_16x16; this.menuFileReload.Name = "menuFileReload"; this.menuFileReload.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.F5))); - this.menuFileReload.Size = new System.Drawing.Size(186, 22); + this.menuFileReload.Size = new System.Drawing.Size(261, 22); this.menuFileReload.Text = "&Reload"; this.menuFileReload.Click += new System.EventHandler(this.ItemClicked); // @@ -165,7 +177,7 @@ this.menuFileSave.Image = global::PrusaSL1Viewer.Properties.Resources.Save_16x16; this.menuFileSave.Name = "menuFileSave"; this.menuFileSave.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); - this.menuFileSave.Size = new System.Drawing.Size(186, 22); + this.menuFileSave.Size = new System.Drawing.Size(261, 22); this.menuFileSave.Text = "&Save"; this.menuFileSave.Click += new System.EventHandler(this.ItemClicked); // @@ -176,7 +188,7 @@ this.menuFileSaveAs.Name = "menuFileSaveAs"; this.menuFileSaveAs.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) | System.Windows.Forms.Keys.S))); - this.menuFileSaveAs.Size = new System.Drawing.Size(186, 22); + this.menuFileSaveAs.Size = new System.Drawing.Size(261, 22); this.menuFileSaveAs.Text = "Save As"; this.menuFileSaveAs.Click += new System.EventHandler(this.ItemClicked); // @@ -186,14 +198,14 @@ this.menuFileClose.Image = global::PrusaSL1Viewer.Properties.Resources.File_Close_16x16; this.menuFileClose.Name = "menuFileClose"; this.menuFileClose.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.W))); - this.menuFileClose.Size = new System.Drawing.Size(186, 22); + this.menuFileClose.Size = new System.Drawing.Size(261, 22); this.menuFileClose.Text = "&Close"; this.menuFileClose.Click += new System.EventHandler(this.ItemClicked); // // toolStripSeparator1 // this.toolStripSeparator1.Name = "toolStripSeparator1"; - this.toolStripSeparator1.Size = new System.Drawing.Size(183, 6); + this.toolStripSeparator1.Size = new System.Drawing.Size(258, 6); // // menuFileExtract // @@ -202,7 +214,7 @@ this.menuFileExtract.Name = "menuFileExtract"; this.menuFileExtract.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Alt) | System.Windows.Forms.Keys.E))); - this.menuFileExtract.Size = new System.Drawing.Size(186, 22); + this.menuFileExtract.Size = new System.Drawing.Size(261, 22); this.menuFileExtract.Text = "&Extract"; this.menuFileExtract.Click += new System.EventHandler(this.ItemClicked); // @@ -211,20 +223,20 @@ this.menuFileConvert.Enabled = false; this.menuFileConvert.Image = global::PrusaSL1Viewer.Properties.Resources.Convert_16x16; this.menuFileConvert.Name = "menuFileConvert"; - this.menuFileConvert.Size = new System.Drawing.Size(186, 22); + this.menuFileConvert.Size = new System.Drawing.Size(261, 22); this.menuFileConvert.Text = "&Convert To"; // // toolStripSeparator2 // this.toolStripSeparator2.Name = "toolStripSeparator2"; - this.toolStripSeparator2.Size = new System.Drawing.Size(183, 6); + this.toolStripSeparator2.Size = new System.Drawing.Size(258, 6); // // menuFileExit // this.menuFileExit.Image = global::PrusaSL1Viewer.Properties.Resources.Exit_16x16; this.menuFileExit.Name = "menuFileExit"; this.menuFileExit.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Alt | System.Windows.Forms.Keys.F4))); - this.menuFileExit.Size = new System.Drawing.Size(186, 22); + this.menuFileExit.Size = new System.Drawing.Size(261, 22); this.menuFileExit.Text = "&Exit"; this.menuFileExit.Click += new System.EventHandler(this.ItemClicked); // @@ -237,24 +249,10 @@ // // viewToolStripMenuItem // - this.viewToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuViewRotateImage}); this.viewToolStripMenuItem.Name = "viewToolStripMenuItem"; this.viewToolStripMenuItem.Size = new System.Drawing.Size(44, 20); this.viewToolStripMenuItem.Text = "&View"; - // - // menuViewRotateImage - // - this.menuViewRotateImage.Checked = true; - this.menuViewRotateImage.CheckOnClick = true; - this.menuViewRotateImage.CheckState = System.Windows.Forms.CheckState.Checked; - this.menuViewRotateImage.Image = global::PrusaSL1Viewer.Properties.Resources.Rotate_16x16; - this.menuViewRotateImage.Name = "menuViewRotateImage"; - this.menuViewRotateImage.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.R))); - this.menuViewRotateImage.Size = new System.Drawing.Size(205, 22); - this.menuViewRotateImage.Text = "&Rotate Image 90º"; - this.menuViewRotateImage.ToolTipText = "Auto rotate layer preview image at 90º (This can slow down the layer preview)"; - this.menuViewRotateImage.Click += new System.EventHandler(this.ItemClicked); + this.viewToolStripMenuItem.Visible = false; // // helpToolStripMenuItem // @@ -398,7 +396,8 @@ this.tsLayer.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; this.tsLayer.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.tsLayerImageExport, - this.tsLayerResolution}); + this.tsLayerResolution, + this.tsLayerImageRotate}); this.tsLayer.Location = new System.Drawing.Point(0, 0); this.tsLayer.Name = "tsLayer"; this.tsLayer.Size = new System.Drawing.Size(1095, 25); @@ -560,6 +559,76 @@ this.tsThumbnailsResolution.Text = "Resolution"; this.tsThumbnailsResolution.ToolTipText = "Thumbnail Resolution"; // + // lvProperties + // + this.lvProperties.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.lvChKey, + this.lvChValue}); + this.lvProperties.Dock = System.Windows.Forms.DockStyle.Fill; + this.lvProperties.FullRowSelect = true; + this.lvProperties.GridLines = true; + this.lvProperties.HideSelection = false; + this.lvProperties.Location = new System.Drawing.Point(0, 25); + this.lvProperties.Name = "lvProperties"; + this.lvProperties.Size = new System.Drawing.Size(380, 244); + this.lvProperties.TabIndex = 1; + this.lvProperties.UseCompatibleStateImageBehavior = false; + this.lvProperties.View = System.Windows.Forms.View.Details; + // + // lvChKey + // + this.lvChKey.Text = "Key"; + this.lvChKey.Width = 183; + // + // lvChValue + // + this.lvChValue.Text = "Value"; + this.lvChValue.Width = 205; + // + // tsProperties + // + this.tsProperties.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; + this.tsProperties.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.tsPropertiesLabelCount, + this.toolStripSeparator4, + this.tsPropertiesLabelGroups, + this.tsPropertiesButtonSave}); + this.tsProperties.Location = new System.Drawing.Point(0, 0); + this.tsProperties.Name = "tsProperties"; + this.tsProperties.Size = new System.Drawing.Size(380, 25); + this.tsProperties.TabIndex = 0; + this.tsProperties.Text = "Properties"; + // + // tsPropertiesLabelCount + // + this.tsPropertiesLabelCount.Name = "tsPropertiesLabelCount"; + this.tsPropertiesLabelCount.Size = new System.Drawing.Size(40, 22); + this.tsPropertiesLabelCount.Text = "Count"; + // + // toolStripSeparator4 + // + this.toolStripSeparator4.Name = "toolStripSeparator4"; + this.toolStripSeparator4.Size = new System.Drawing.Size(6, 25); + // + // tsPropertiesLabelGroups + // + this.tsPropertiesLabelGroups.Name = "tsPropertiesLabelGroups"; + this.tsPropertiesLabelGroups.Size = new System.Drawing.Size(45, 22); + this.tsPropertiesLabelGroups.Text = "Groups"; + // + // tsPropertiesButtonSave + // + this.tsPropertiesButtonSave.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.tsPropertiesButtonSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; + this.tsPropertiesButtonSave.Enabled = false; + this.tsPropertiesButtonSave.Image = global::PrusaSL1Viewer.Properties.Resources.Save_16x16; + this.tsPropertiesButtonSave.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tsPropertiesButtonSave.Name = "tsPropertiesButtonSave"; + this.tsPropertiesButtonSave.Size = new System.Drawing.Size(23, 22); + this.tsPropertiesButtonSave.Text = "Save Thumbnail"; + this.tsPropertiesButtonSave.ToolTipText = "Save properties to a file"; + this.tsPropertiesButtonSave.Click += new System.EventHandler(this.ItemClicked); + // // tbpGCode // this.tbpGCode.Controls.Add(this.tbGCode); @@ -633,75 +702,19 @@ this.imageList16x16.Images.SetKeyName(1, "PhotoInfo-16x16.png"); this.imageList16x16.Images.SetKeyName(2, "GCode-16x16.png"); // - // tsProperties - // - this.tsProperties.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden; - this.tsProperties.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.tsPropertiesLabelCount, - this.toolStripSeparator4, - this.tsPropertiesLabelGroups, - this.tsPropertiesButtonSave}); - this.tsProperties.Location = new System.Drawing.Point(0, 0); - this.tsProperties.Name = "tsProperties"; - this.tsProperties.Size = new System.Drawing.Size(380, 25); - this.tsProperties.TabIndex = 0; - this.tsProperties.Text = "Properties"; - // - // lvProperties - // - this.lvProperties.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.lvChKey, - this.lvChValue}); - this.lvProperties.Dock = System.Windows.Forms.DockStyle.Fill; - this.lvProperties.FullRowSelect = true; - this.lvProperties.GridLines = true; - this.lvProperties.HideSelection = false; - this.lvProperties.Location = new System.Drawing.Point(0, 25); - this.lvProperties.Name = "lvProperties"; - this.lvProperties.Size = new System.Drawing.Size(380, 244); - this.lvProperties.TabIndex = 1; - this.lvProperties.UseCompatibleStateImageBehavior = false; - this.lvProperties.View = System.Windows.Forms.View.Details; - // - // lvChKey - // - this.lvChKey.Text = "Key"; - this.lvChKey.Width = 183; + // tsLayerImageRotate // - // lvChValue - // - this.lvChValue.Text = "Value"; - this.lvChValue.Width = 205; - // - // tsPropertiesLabelCount - // - this.tsPropertiesLabelCount.Name = "tsPropertiesLabelCount"; - this.tsPropertiesLabelCount.Size = new System.Drawing.Size(40, 22); - this.tsPropertiesLabelCount.Text = "Count"; - // - // toolStripSeparator4 - // - this.toolStripSeparator4.Name = "toolStripSeparator4"; - this.toolStripSeparator4.Size = new System.Drawing.Size(6, 25); - // - // tsPropertiesLabelGroups - // - this.tsPropertiesLabelGroups.Name = "tsPropertiesLabelGroups"; - this.tsPropertiesLabelGroups.Size = new System.Drawing.Size(45, 22); - this.tsPropertiesLabelGroups.Text = "Groups"; - // - // tsPropertiesButtonSave - // - this.tsPropertiesButtonSave.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; - this.tsPropertiesButtonSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image; - this.tsPropertiesButtonSave.Enabled = false; - this.tsPropertiesButtonSave.Image = global::PrusaSL1Viewer.Properties.Resources.Save_16x16; - this.tsPropertiesButtonSave.ImageTransparentColor = System.Drawing.Color.Magenta; - this.tsPropertiesButtonSave.Name = "tsPropertiesButtonSave"; - this.tsPropertiesButtonSave.Size = new System.Drawing.Size(23, 22); - this.tsPropertiesButtonSave.Text = "Save Thumbnail"; - this.tsPropertiesButtonSave.ToolTipText = "Save properties to a file"; - this.tsPropertiesButtonSave.Click += new System.EventHandler(this.ItemClicked); + this.tsLayerImageRotate.Checked = true; + this.tsLayerImageRotate.CheckOnClick = true; + this.tsLayerImageRotate.CheckState = System.Windows.Forms.CheckState.Checked; + this.tsLayerImageRotate.Image = global::PrusaSL1Viewer.Properties.Resources.Rotate_16x16; + this.tsLayerImageRotate.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tsLayerImageRotate.Name = "tsLayerImageRotate"; + this.tsLayerImageRotate.Size = new System.Drawing.Size(117, 22); + this.tsLayerImageRotate.Text = "Rotate Image 90º"; + this.tsLayerImageRotate.ToolTipText = "Auto rotate layer preview image at 90º (This can slow down the layer preview) [CT" + + "RL+R]"; + this.tsLayerImageRotate.Click += new System.EventHandler(this.ItemClicked); // // FrmMain // @@ -714,6 +727,7 @@ this.Controls.Add(this.menu); this.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.KeyPreview = true; this.MainMenuStrip = this.menu; this.MinimumSize = new System.Drawing.Size(1000, 600); this.Name = "FrmMain"; @@ -744,12 +758,12 @@ ((System.ComponentModel.ISupportInitialize)(this.pbThumbnail)).EndInit(); this.tsThumbnails.ResumeLayout(false); this.tsThumbnails.PerformLayout(); + this.tsProperties.ResumeLayout(false); + this.tsProperties.PerformLayout(); this.tbpGCode.ResumeLayout(false); this.tbpGCode.PerformLayout(); this.tsGCode.ResumeLayout(false); this.tsGCode.PerformLayout(); - this.tsProperties.ResumeLayout(false); - this.tsProperties.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); @@ -775,7 +789,6 @@ private System.Windows.Forms.PictureBox pbLayer; private System.Windows.Forms.ProgressBar pbLayers; private System.Windows.Forms.ToolStripMenuItem viewToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem menuViewRotateImage; private System.Windows.Forms.ToolStripMenuItem menuFileClose; private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; private System.Windows.Forms.ToolStripMenuItem menuFileExtract; @@ -813,6 +826,8 @@ private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; private System.Windows.Forms.ToolStripLabel tsPropertiesLabelGroups; private System.Windows.Forms.ToolStripButton tsPropertiesButtonSave; + private System.Windows.Forms.ToolStripMenuItem menuFileOpenNewWindow; + private System.Windows.Forms.ToolStripButton tsLayerImageRotate; } } diff --git a/PrusaSL1Viewer/FrmMain.cs b/PrusaSL1Viewer/FrmMain.cs index d1b992d..e6bfb63 100644 --- a/PrusaSL1Viewer/FrmMain.cs +++ b/PrusaSL1Viewer/FrmMain.cs @@ -40,13 +40,29 @@ namespace PrusaSL1Viewer DragEnter += (s, e) => { if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy; }; DragDrop += (s, e) => { ProcessFile((string[])e.Data.GetData(DataFormats.FileDrop)); }; - ProcessFile(Environment.GetCommandLineArgs()); + ProcessFile(Program.Args); + } + + #endregion + + #region Overrides + + protected override void OnKeyUp(KeyEventArgs e) + { + base.OnKeyUp(e); + + if (e.KeyCode == Keys.R && e.Control) + { + tsLayerImageRotate.PerformClick(); + e.Handled = true; + return; + } } #endregion #region Events - + private void ItemClicked(object sender, EventArgs e) { if (sender.GetType() == typeof(ToolStripMenuItem)) @@ -73,7 +89,6 @@ namespace PrusaSL1Viewer { MessageBox.Show(exception.ToString(), "Error while try opening the file", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; } } } @@ -81,6 +96,22 @@ namespace PrusaSL1Viewer return; } + if (ReferenceEquals(sender, menuFileOpenNewWindow)) + { + using (OpenFileDialog openFile = new OpenFileDialog()) + { + openFile.CheckFileExists = true; + openFile.Filter = FileFormat.AllFileFilters; + openFile.FilterIndex = 0; + if (openFile.ShowDialog() == DialogResult.OK) + { + Program.NewInstance(openFile.FileName); + } + } + + return; + } + if (ReferenceEquals(sender, menuFileReload)) { ProcessFile(); @@ -208,11 +239,7 @@ namespace PrusaSL1Viewer } // View - if (ReferenceEquals(sender, menuViewRotateImage)) - { - sbLayers_ValueChanged(sbLayers, null); - return; - } + // About if (ReferenceEquals(sender, menuAboutAbout)) @@ -265,6 +292,8 @@ namespace PrusaSL1Viewer tsThumbnailsNext.Enabled = true; tsThumbnailsResolution.Text = pbThumbnail.Image.PhysicalDimension.ToString(); + + AdjustThumbnailSplitter(); return; } @@ -291,6 +320,8 @@ namespace PrusaSL1Viewer tsThumbnailsPrevious.Enabled = true; tsThumbnailsResolution.Text = pbThumbnail.Image.PhysicalDimension.ToString(); + + AdjustThumbnailSplitter(); return; } @@ -358,7 +389,6 @@ namespace PrusaSL1Viewer } return; } - return; } /************************ @@ -395,6 +425,12 @@ namespace PrusaSL1Viewer /************************ * Layer Menu * ***********************/ + if (ReferenceEquals(sender, tsLayerImageRotate)) + { + sbLayers_ValueChanged(sbLayers, null); + return; + } + if (ReferenceEquals(sender, tsLayerImageExport)) { using (SaveFileDialog dialog = new SaveFileDialog()) @@ -424,6 +460,7 @@ namespace PrusaSL1Viewer } } + private void sbLayers_ValueChanged(object sender, EventArgs e) { if (ReferenceEquals(SlicerFile, null)) return; @@ -448,8 +485,13 @@ namespace PrusaSL1Viewer if (dialog.ShowDialog() == DialogResult.OK) { SlicerFile.Convert(fileFormat, dialog.FileName); - MessageBox.Show("Convertion is completed", "Convertion completed", MessageBoxButtons.OK, - MessageBoxIcon.Information); + if (MessageBox.Show($"Convertion is completed: {Path.GetFileName(dialog.FileName)}\n" + + "Do you want open the converted file in a new window?", + "Convertion completed", MessageBoxButtons.YesNo, + MessageBoxIcon.Information) == DialogResult.Yes) + { + Program.NewInstance(dialog.FileName); + } } } @@ -574,7 +616,9 @@ namespace PrusaSL1Viewer foreach (var fileFormat in FileFormat.AvaliableFormats) { if (fileFormat.GetType() == SlicerFile.GetType()) continue; - ToolStripMenuItem menuItem = new ToolStripMenuItem(fileFormat.GetType().Name.Replace("File", string.Empty)) + + string extensions = fileFormat.FileExtensions.Length > 0 ? $" ({fileFormat.GetFileExtensions()})" : string.Empty; + ToolStripMenuItem menuItem = new ToolStripMenuItem(fileFormat.GetType().Name.Replace("File", extensions)) { Tag = fileFormat, Image = Properties.Resources.layers_16x16 @@ -597,7 +641,7 @@ namespace PrusaSL1Viewer } tsThumbnailsNext.Enabled = SlicerFile.CreatedThumbnailsCount > 1; - scLeft.SplitterDistance = pbThumbnail.Image.Height + 5; + AdjustThumbnailSplitter(); } foreach (ToolStripItem item in tsLayer.Items) { @@ -720,7 +764,7 @@ namespace PrusaSL1Viewer //Stopwatch watch = Stopwatch.StartNew(); var image = SlicerFile.GetLayerImage(layerNum); //Debug.Write(watch.ElapsedMilliseconds.ToString()); - if (menuViewRotateImage.Checked) + if (tsLayerImageRotate.Checked) { //watch.Restart(); image.Mutate(x => x.Rotate(RotateMode.Rotate90)); @@ -757,6 +801,11 @@ namespace PrusaSL1Viewer ToolStripLabel label = new ToolStripLabel($"{name}: {item}{extraText}"); statusBar.Items.Add(label); } + + void AdjustThumbnailSplitter() + { + scLeft.SplitterDistance = Math.Min(pbThumbnail.Image.Height + 5, 400); + } #endregion } } diff --git a/PrusaSL1Viewer/FrmMain.resx b/PrusaSL1Viewer/FrmMain.resx index 7af10c6..fb1a18a 100644 --- a/PrusaSL1Viewer/FrmMain.resx +++ b/PrusaSL1Viewer/FrmMain.resx @@ -126,12 +126,6 @@ <metadata name="tsLayer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <value>357, 17</value> </metadata> - <metadata name="tsThumbnails.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> - <value>447, 17</value> - </metadata> - <metadata name="tsProperties.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> - <value>805, 17</value> - </metadata> <metadata name="tsGCode.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <value>707, 17</value> </metadata> @@ -143,46 +137,55 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADg - CAAAAk1TRnQBSQFMAgEBAwEAARABAAEQAQABEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA - AwABEAMAAQEBAAEgBgABEFYAA0QBowNCAakDQgGpA0IBqQNCAakDQgGpA0IBqQNCAakDQgGpA0IBqQNC - AakDQgGpA0IBqQNEAaOEAANAAbQDOAHHAy4BSQMAAQEDGwEmAxwBJwMcAScDHAEnAxwBJwMcAScDHAEn - AxwBJwMcAScDHAEnAxwBJwMCAQMEAANCAakwAANCAakQAAMmAToDMAFMAzABTAMwAUwDMAFMAzABTAMw - AUwDMAFMAzABTAMmATpPAAH/AwAB/wNAAXcDKQE+AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMA - Af8DAAH/AwAB/wMAAf8DMgFRBAADQgGpBAADRAGdA0MBqgNDAaoDQwGqA0MBqgNDAaoDQwGqA0QBnQwA - A0IBqRAAA04B+wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A04B+0wAA0MBogM/AbYDKgFA - BAADEAEVAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxABFggAA0IBqQQAA0QBnQND - AaoDQwGqAx8BLBwAA0IBqRAAA00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/0wA - AwoBDgMRARcDAAEBOAADQgGpMAADQgGpEAADTQH/A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/wNN - Af8DTQH/TAADEAH0AwAB/wM9AWwDDgETAz8BdgNAAXcDQAF3A0ABdwNAAXcDQAF3A0ABdwNAAXcDQAF3 - A0ABdwM/AXYDFAEbBAADQgGpAyIBMgNCAakDQgGpA0IBqQNCAakDQgGpA0IBqQNCAakDQgGpA0IBqQNC - AakDIgEyA0IBqRAAA00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/08AAf4DAAH/ - A0ABdwMeASsDOgHFAzgBxwM4AccDOAHHAzgBxwM4AccDOAHHAzgBxwM4AccDOAHHAzoBxgMmATkEAANC - AakDNAFVAzQBVSAAAzQBVQM0AVUDQgGpEAADTQH/A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/wNN - Af8DTQH/TAADMwFTAzsBZwMUARw4AANCAakDNAFVAzQBVQNCAYADQgGpA0IBqQNCAakDQgGpA0IBqQNC - AakDQQF/AzQBVQM0AVUDQgGpEAADTQH/A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/ - TAADMwFTAzsBZwMUARw4AANCAakDNAFVAzQBVQM9AW4DMgFQEAADJwE7A0ABfAM0AVUDNAFVA0IBqRAA - A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A00B/08AAf4DAAH/A0ABdwMfASwDOgHF - AzgBxwM4AccDOAHHAzgBxwM4AccDOAHHAzgBxwM4AccDOAHHAzoBxgMmATkEAANCAakDNAFVAzQBVQMF - AQcDQQG1AxEBFwNCAakDKQE+BAADRAGfAxEBFwM0AVUDNAFVA0IBqRAAA00B/wNNAf8DTQH/A00B/wNN - Af8DTQH/A00B/wNNAf8DTQH/A00B/0wAAxAB9AMAAf8DPQFsAw4BEwM/AXUDQAF3A0ABdwNAAXcDQAF3 - A0ABdwNAAXcDQAF3A0ABdwNAAXcDQAF3AxQBGwQAA0IBqQM0AVUDNAFVBAADOwFoAz4BvgMjATQDQQG1 - AxIBGQNEAaAEAAM0AVUDNAFVA0IBqRAAA00B/wNNAf8DTQH/A00B/wNNAf8DTQH/A1EB/wNWAf8DVgH/ - A1oB30wAAwoBDgMRARcDAAEBOAADQgGpAzQBVQM0AVUDAAEBAy0BRgMKAQ4EAAM4AV8DNQHOAygBPAQA - AzQBVQM0AVUDQgGpEAADTQH/A4IB/wNsAf8DVgH/A00B/wNNAf8DYAH/A2cB/wNjAd8DFwEgTAADQwGi - Az8BtgMqAUAEAAMQARUDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEAEWCAADQgGp - AzQBVQM0AVUDMwFTA0QBpgNCAYwHAAEBA0MBgwgAAzQBVQM0AVUDQgGpEAADTQH/A5kB/wOFAf8DeQH/ - A00B/wNNAf8DYAH/A2MB3wMXASBTAAH/AwAB/wNAAXcDKQE+AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/ - AwAB/wMAAf8DAAH/AwAB/wMAAf8DMgFRBAADQgGpAzQBVQM0AVUDEQEXA0MBngMkATYUAAM0AVUDNAFV - A0IBqRAAA1AB+wNYAf8DWAH/A1gB/wNNAf8DTQH/A2AB3wMXASBUAANAAbQDOAHHAy4BSQMAAQEDGwEm - AxwBJwMcAScDHAEnAxwBJwMcAScDHAEnAxwBJwMcAScDHAEnAxwBJwMCAQMEAANCAakDIgEyA0IBqQNC - AakDQgGpA0IBqQNCAakDQgGpA0IBqQNCAakDQgGpA0IBqQMiATIDQgGpEAADIAEuAykBPwMpAT8DKQE/ - AykBPwMpAT8DEQEXnAADRAGjA0IBqQNCAakDQgGpA0IBqQNCAakDQgGpA0IBqQNCAakDQgGpA0IBqQNC - AakDQgGpA0QBo4QAAUIBTQE+BwABPgMAASgDAAFAAwABEAMAAQEBAAEBBQABgBcAA/8BAAL/AYABAQL/ + CAAAAk1TRnQBSQFMAgEBAwEAASABAAEgAQABEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA + AwABEAMAAQEBAAEgBgABEFYAA04BowNPAakDTwGpA08BqQNPAakDTwGpA08BqQNPAakDTwGpA08BqQNP + AakDTwGpA08BqQNOAaOEAANQAbQDTQHHAy8BSQMAAQEDGwEmAxwBJwMcAScDHAEnAxwBJwMcAScDHAEn + AxwBJwMcAScDHAEnAxwBJwMCAQMEAANPAakwAANPAakQAAMnAToDMAFMAzABTAMwAUwDMAFMAzABTAMw + AUwDMAFMAzABTAMnATpPAAH/AwAB/wNDAXcDKQE+AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMA + Af8DAAH/AwAB/wMAAf8DMgFRBAADTwGpBAADTQGdA1ABqgNQAaoDUAGqA1ABqgNQAaoDUAGqA00BnQwA + A08BqRAAA04B+wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A04B+0wAA08BogNQAbYDKgFA + BAADEAEVAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxABFggAA08BqQQAA00BnQNQ + AaoDUAGqAx8BLBwAA08BqRAAA0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/0wA + AwoBDgMRARcDAAEBOAADTwGpMAADTwGpEAADSwH/A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/wNL + Af8DSwH/TAADGgH0AwAB/wM+AWwDDgETA0IBdgNDAXcDQwF3A0MBdwNDAXcDQwF3A0MBdwNDAXcDQwF3 + A0MBdwNCAXYDFAEbBAADTwGpAyIBMgNPAakDTwGpA08BqQNPAakDTwGpA08BqQNPAakDTwGpA08BqQNP + AakDIgEyA08BqRAAA0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/08AAf4DAAH/ + A0MBdwMeASsDTwHFA00BxwNNAccDTQHHA00BxwNNAccDTQHHA00BxwNNAccDTQHHA04BxgMmATkEAANP + AakDNAFVAzQBVSAAAzQBVQM0AVUDTwGpEAADSwH/A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/wNL + Af8DSwH/TAADMwFTAzwBZwMUARw4AANPAakDNAFVAzQBVQNGAYADTwGpA08BqQNPAakDTwGpA08BqQNP + AakDRQF/AzQBVQM0AVUDTwGpEAADSwH/A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/ + TAADMwFTAzwBZwMUARw4AANPAakDNAFVAzQBVQM/AW4DMgFQEAADJwE7A0QBfAM0AVUDNAFVA08BqRAA + A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A0sB/08AAf4DAAH/A0MBdwMfASwDTwHF + A00BxwNNAccDTQHHA00BxwNNAccDTQHHA00BxwNNAccDTQHHA04BxgMmATkEAANPAakDNAFVAzQBVQMF + AQcDUAG1AxEBFwNPAakDKQE+BAADTgGfAxEBFwM0AVUDNAFVA08BqRAAA0sB/wNLAf8DSwH/A0sB/wNL + Af8DSwH/A0sB/wNLAf8DSwH/A0sB/0wAAxoB9AMAAf8DPgFsAw4BEwNCAXUDQwF3A0MBdwNDAXcDQwF3 + A0MBdwNDAXcDQwF3A0MBdwNDAXcDQwF3AxQBGwQAA08BqQM0AVUDNAFVBAADPAFoA1ABvgMjATQDUAG1 + AxIBGQNPAaAEAAM0AVUDNAFVA08BqRAAA0sB/wNLAf8DSwH/A0sB/wNLAf8DSwH/A08B/wNUAf8DVAH/ + A1wB30wAAwoBDgMRARcDAAEBOAADTwGpAzQBVQM0AVUDAAEBAy0BRgMKAQ4EAAM5AV8DSwHOAygBPAQA + AzQBVQM0AVUDTwGpEAADSwH/A4IB/wNqAf8DVAH/A0sB/wNLAf8DXgH/A2UB/wNhAd8DFwEgTAADTwGi + A1ABtgMqAUAEAAMQARUDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEAEWCAADTwGp + AzQBVQM0AVUDMwFTA08BpgNKAYwHAAEBA0cBgwgAAzQBVQM0AVUDTwGpEAADSwH/A5kB/wOFAf8DdwH/ + A0sB/wNLAf8DXgH/A2EB3wMXASBTAAH/AwAB/wNDAXcDKQE+AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/ + AwAB/wMAAf8DAAH/AwAB/wMAAf8DMgFRBAADTwGpAzQBVQM0AVUDEQEXA00BngMkATYUAAM0AVUDNAFV + A08BqRAAA1AB+wNWAf8DVgH/A1YB/wNLAf8DSwH/A14B3wMXASBUAANQAbQDTQHHAy8BSQMAAQEDGwEm + AxwBJwMcAScDHAEnAxwBJwMcAScDHAEnAxwBJwMcAScDHAEnAxwBJwMCAQMEAANPAakDIgEyA08BqQNP + AakDTwGpA08BqQNPAakDTwGpA08BqQNPAakDTwGpA08BqQMiATIDTwGpEAADIAEuAykBPwMpAT8DKQE/ + AykBPwMpAT8DEQEXnAADTgGjA08BqQNPAakDTwGpA08BqQNPAakDTwGpA08BqQNPAakDTwGpA08BqQNP + AakDTwGpA04Bo4QAAUIBTQE+BwABPgMAASgDAAFAAwABEAMAAQEBAAEBBQABgBcAA/8BAAL/AYABAQL/ BAABvwH9AeABBwQAAaABHQHgAQcCAAEQAQEBoQH9AeABBwIAAR8B/wG/Af0B4AEHBAABgAEBAeABBwQA AY8B8QHgAQcCAAEfAf8BgAEBAeABBwIAAR8B/wGDAcEB4AEHBAABgAFBAeABBwQAAYgBEQHgAQcCAAEf Af8BgQERAeABBwIAARABAQGBATEB4AEPBAABgQHxAeABHwQAAYABAQHgAT8CAAL/AYABAQL/AgAL </value> </data> + <metadata name="tsThumbnails.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <value>447, 17</value> + </metadata> + <metadata name="tsProperties.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <value>805, 17</value> + </metadata> + <metadata name="tsGCode.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> + <value>707, 17</value> + </metadata> <assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <value> diff --git a/PrusaSL1Viewer/Images/Open-16x16.png b/PrusaSL1Viewer/Images/Open-16x16.png Binary files differindex 062ecd5..5117a1c 100644 --- a/PrusaSL1Viewer/Images/Open-16x16.png +++ b/PrusaSL1Viewer/Images/Open-16x16.png diff --git a/PrusaSL1Viewer/Program.cs b/PrusaSL1Viewer/Program.cs index 6e1bc5d..d8cf1b5 100644 --- a/PrusaSL1Viewer/Program.cs +++ b/PrusaSL1Viewer/Program.cs @@ -6,6 +6,7 @@ * of this license document, but changing it is not allowed. */ using System; +using System.Diagnostics; using System.Drawing; using System.Globalization; using System.Threading; @@ -63,12 +64,14 @@ namespace PrusaSL1Viewer public static FileFormat SlicerFile { get; set; } public static FrmMain FrmMain { get; private set; } public static FrmAbout FrmAbout { get; private set; } + public static string[] Args { get; private set; } /// <summary> /// The main entry point for the application. /// </summary> [STAThread] - static void Main() + static void Main(string[] args) { + Args = args; Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB"); Application.EnableVisualStyles(); @@ -79,5 +82,11 @@ namespace PrusaSL1Viewer Application.Run(FrmMain); } + + public static void NewInstance(string filePath) + { + var info = new ProcessStartInfo(Application.ExecutablePath, $"\"{filePath}\""); + Process.Start(info); + } } } diff --git a/PrusaSL1Viewer/Properties/AssemblyInfo.cs b/PrusaSL1Viewer/Properties/AssemblyInfo.cs index 18b03b4..371693f 100644 --- a/PrusaSL1Viewer/Properties/AssemblyInfo.cs +++ b/PrusaSL1Viewer/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.3.0.0")] -[assembly: AssemblyFileVersion("0.3.0.0")] +[assembly: AssemblyVersion("0.3.1.0")] +[assembly: AssemblyFileVersion("0.3.1.0")] diff --git a/PrusaSL1Viewer/PrusaSL1Viewer.csproj b/PrusaSL1Viewer/PrusaSL1Viewer.csproj index a4c97fd..74f7c79 100644 --- a/PrusaSL1Viewer/PrusaSL1Viewer.csproj +++ b/PrusaSL1Viewer/PrusaSL1Viewer.csproj @@ -256,4 +256,7 @@ </PropertyGroup> <Error Condition="!Exists('..\packages\Microsoft.Net.Compilers.Toolset.3.6.0-3.final\build\Microsoft.Net.Compilers.Toolset.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Net.Compilers.Toolset.3.6.0-3.final\build\Microsoft.Net.Compilers.Toolset.props'))" /> </Target> + <PropertyGroup> + <PostBuildEvent>xcopy /y $(ProjectDir)..\LICENSE $(ProjectDir)$(OutDir)</PostBuildEvent> + </PropertyGroup> </Project>
\ No newline at end of file @@ -45,6 +45,7 @@ But also, i need victims for test subject. Proceed at your own risk! * SL1 (Prusa SL1) * CBDDLP (Chitubox DLP) +* CBT (Chitubox) * Photon * ZCodex |