From 2c514fe260b7939f372c745b5e600ee877b88e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Sat, 4 Jul 2020 21:29:58 +0100 Subject: v0.6.1.1 * (Add) Allow chitubox, phz, pws, pw0 files convert to cws * (Add) Allow convert between cbddlp, ctb and photon * (Add) Allow convert between pws and pw0 * (Improvement) Layers can now have modified heights and independent parameters (#9) * (Improvement) UVtools now generate better gcode and detect the lack of Lift and same z position and optimize the commands * (Fix) zcodex: Wasn't reporting layer decoding progress --- CHANGELOG.md | 10 + UVtools.Cmd/Program.cs | 1 + UVtools.Core/CWSFile.cs | 480 ------ UVtools.Core/ChituboxFile.cs | 1650 -------------------- UVtools.Core/ChituboxZipFile.cs | 448 ------ UVtools.Core/FileExtension.cs | 90 -- UVtools.Core/FileFormat.cs | 669 --------- UVtools.Core/FileFormats/CWSFile.cs | 558 +++++++ UVtools.Core/FileFormats/ChituboxFile.cs | 1761 ++++++++++++++++++++++ UVtools.Core/FileFormats/ChituboxZipFile.cs | 512 +++++++ UVtools.Core/FileFormats/FileExtension.cs | 90 ++ UVtools.Core/FileFormats/FileFormat.cs | 669 +++++++++ UVtools.Core/FileFormats/IFileFormat.cs | 355 +++++ UVtools.Core/FileFormats/ImageFile.cs | 87 ++ UVtools.Core/FileFormats/PHZFile.cs | 1416 ++++++++++++++++++ UVtools.Core/FileFormats/PWSFile.cs | 1349 +++++++++++++++++ UVtools.Core/FileFormats/SL1File.cs | 987 ++++++++++++ UVtools.Core/FileFormats/ZCodexFile.cs | 533 +++++++ UVtools.Core/IFileFormat.cs | 354 ----- UVtools.Core/ImageFile.cs | 87 -- UVtools.Core/Layer/Layer.cs | 692 +++++++++ UVtools.Core/Layer/LayerIssue.cs | 245 +++ UVtools.Core/Layer/LayerManager.cs | 1161 ++++++++++++++ UVtools.Core/LayerManager.cs | 2081 -------------------------- UVtools.Core/MatBytes.cs | 84 -- UVtools.Core/OperationMove.cs | 94 -- UVtools.Core/OperationPattern.cs | 116 -- UVtools.Core/OperationProgress.cs | 113 -- UVtools.Core/Operations/OperationMove.cs | 92 ++ UVtools.Core/Operations/OperationPattern.cs | 116 ++ UVtools.Core/Operations/OperationProgress.cs | 113 ++ UVtools.Core/PHZFile.cs | 1367 ----------------- UVtools.Core/PWSFile.cs | 1269 ---------------- UVtools.Core/SL1File.cs | 971 ------------ UVtools.Core/UVtools.Core.csproj | 7 +- UVtools.Core/ZCodexFile.cs | 447 ------ UVtools.GUI/Forms/FrmAbout.cs | 1 + UVtools.GUI/Forms/FrmInputBox.cs | 1 + UVtools.GUI/Forms/FrmLoading.cs | 1 + UVtools.GUI/Forms/FrmMutationMove.cs | 1 + UVtools.GUI/Forms/FrmSettings.cs | 1 + UVtools.GUI/Forms/FrmToolPattern.cs | 1 + UVtools.GUI/FrmMain.Designer.cs | 20 +- UVtools.GUI/FrmMain.cs | 32 +- UVtools.GUI/Program.cs | 1 + UVtools.GUI/Properties/AssemblyInfo.cs | 4 +- 46 files changed, 10786 insertions(+), 10351 deletions(-) delete mode 100644 UVtools.Core/CWSFile.cs delete mode 100644 UVtools.Core/ChituboxFile.cs delete mode 100644 UVtools.Core/ChituboxZipFile.cs delete mode 100644 UVtools.Core/FileExtension.cs delete mode 100644 UVtools.Core/FileFormat.cs create mode 100644 UVtools.Core/FileFormats/CWSFile.cs create mode 100644 UVtools.Core/FileFormats/ChituboxFile.cs create mode 100644 UVtools.Core/FileFormats/ChituboxZipFile.cs create mode 100644 UVtools.Core/FileFormats/FileExtension.cs create mode 100644 UVtools.Core/FileFormats/FileFormat.cs create mode 100644 UVtools.Core/FileFormats/IFileFormat.cs create mode 100644 UVtools.Core/FileFormats/ImageFile.cs create mode 100644 UVtools.Core/FileFormats/PHZFile.cs create mode 100644 UVtools.Core/FileFormats/PWSFile.cs create mode 100644 UVtools.Core/FileFormats/SL1File.cs create mode 100644 UVtools.Core/FileFormats/ZCodexFile.cs delete mode 100644 UVtools.Core/IFileFormat.cs delete mode 100644 UVtools.Core/ImageFile.cs create mode 100644 UVtools.Core/Layer/Layer.cs create mode 100644 UVtools.Core/Layer/LayerIssue.cs create mode 100644 UVtools.Core/Layer/LayerManager.cs delete mode 100644 UVtools.Core/LayerManager.cs delete mode 100644 UVtools.Core/MatBytes.cs delete mode 100644 UVtools.Core/OperationMove.cs delete mode 100644 UVtools.Core/OperationPattern.cs delete mode 100644 UVtools.Core/OperationProgress.cs create mode 100644 UVtools.Core/Operations/OperationMove.cs create mode 100644 UVtools.Core/Operations/OperationPattern.cs create mode 100644 UVtools.Core/Operations/OperationProgress.cs delete mode 100644 UVtools.Core/PHZFile.cs delete mode 100644 UVtools.Core/PWSFile.cs delete mode 100644 UVtools.Core/SL1File.cs delete mode 100644 UVtools.Core/ZCodexFile.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db33d3..4cc4018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 02/07/2020 - v0.6.1.1 + +* (Add) Allow chitubox, phz, pws, pw0 files convert to cws +* (Add) Allow convert between cbddlp, ctb and photon +* (Add) Allow convert between pws and pw0 +* (Improvement) Layers can now have modified heights and independent parameters (#9) +* (Improvement) UVtools now generate better gcode and detect the lack of Lift and same z position and optimize the commands +* (Fix) zcodex: Wasn't reporting layer decoding progress + + ## 02/07/2020 - v0.6.1.0 * (Add) Thumbnail image can now saved to clipboard diff --git a/UVtools.Cmd/Program.cs b/UVtools.Cmd/Program.cs index b1224f7..15cbff0 100644 --- a/UVtools.Cmd/Program.cs +++ b/UVtools.Cmd/Program.cs @@ -10,6 +10,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; using UVtools.Core; +using UVtools.Core.FileFormats; namespace UVtools.Cmd { diff --git a/UVtools.Core/CWSFile.cs b/UVtools.Core/CWSFile.cs deleted file mode 100644 index 4dcfa76..0000000 --- a/UVtools.Core/CWSFile.cs +++ /dev/null @@ -1,480 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.ComponentModel; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - public class CWSFile : FileFormat - { - #region Constants - - public const string GCodeStart = "G28 ; Auto Home{0}" + - "G21 ;Set units to be mm{0}" + - "G91 ;Relative Positioning{0}" + - "M17 ;Enable motors{0}" + - " Blank{0}" + - "M106 S0{0}{0}"; - - public const string GCodeEnd = "M106 S0{0}" + - "G1 Z{1}{0}" + - "{0}M18 ;Disable Motors{0}" + - ";{0}"; - - public const string GCodeKeywordSlice = ";"; - public const string GCodeKeywordSliceBlank = "; Blank"; - public const string GCodeKeywordDelay = ";"; - #endregion - - #region Sub Classes - - public class Output - { - // ;(****Build and Slicing Parameters****) - [DisplayName("Pix per mm X")] public float PixPermmX { get; set; } = 19.324f; - [DisplayName("Pix per mm Y")] public float PixPermmY { get; set; } = 19.324f; - [DisplayName("X Resolution")] public ushort XResolution { get; set; } - [DisplayName("Y Resolution")] public ushort YResolution { get; set; } - [DisplayName("Layer Thickness")] public float LayerThickness { get; set; } - [DisplayName("Layer Time")] public uint LayerTime { get; set; } = 5500; - [DisplayName("Render Outlines")] public bool RenderOutlines { get; set; } = false; - [DisplayName("Outline Width Inset")] public ushort OutlineWidthInset { get; set; } = 2; - [DisplayName("Outline Width Outset")] public ushort OutlineWidthOutset { get; set; } = 0; - [DisplayName("Bottom Layers Time")] public uint BottomLayersTime { get; set; } = 35000; - [DisplayName("Number of Bottom Layers")] public ushort NumberBottomLayers { get; set; } = 3; - [DisplayName("Blanking Layer Time")] public uint BlankingLayerTime { get; set; } - [DisplayName("BuildDirection")] public string BuildDirection { get; set; } = "Bottom_Up"; - [DisplayName("Lift Distance")] public float LiftDistance { get; set; } = 4; - [DisplayName("Slide/Tilt Value")] public byte TiltValue { get; set; } - [DisplayName("Use Mainlift GCode Tab")] public bool UseMainliftGCodeTab { get; set; } - [DisplayName("Anti Aliasing")] public bool AntiAliasing { get; set; } = true; - [DisplayName("Anti Aliasing Value")] public float AntiAliasingValue { get; set; } = 2; - [DisplayName("Z Lift Feed Rate")] public float ZLiftFeedRate { get; set; } = 120; - [DisplayName("Z Bottom Lift Feed Rate")] public float ZBottomLiftFeedRate { get; set; } = 120; - [DisplayName("Z Lift Retract Rate")] public float ZLiftRetractRate { get; set; } = 120; - [DisplayName("Flip X")] public bool FlipX { get; set; } - [DisplayName("Flip Y")] public bool FlipY { get; set; } - [DisplayName("Number of Slices")] public uint LayersNum { get; set; } - - // ;(****Machine Configuration ******) - [DisplayName("Platform X Size")] public float PlatformXSize { get; set; } - [DisplayName("Platform Y Size")] public float PlatformYSize { get; set; } - [DisplayName("Platform Z Size")] public float PlatformZSize { get; set; } - [DisplayName("Max X Feedrate")] public ushort MaxXFeedrate { get; set; } = 200; - [DisplayName("Max Y Feedrate")] public ushort MaxYFeedrate { get; set; } = 200; - [DisplayName("Max Z Feedrate")] public ushort MaxZFeedrate { get; set; } = 200; - [DisplayName("Machine Type")] public string MachineType { get; set; } = "UV_LCD"; - - // ;(****UVtools Configuration ******) - [DisplayName("Bottom Layer Light PWM")] public byte BottomLayerLightPWM { get; set; } = 255; - [DisplayName("Layer Light PWM")] public byte LayerLightPWM { get; set; } = 255; - } - - public class Slice - { - [DisplayName("xppm")] public float Xppm { get; set; } = 19.324f; - [DisplayName("yppm")] public float Yppm { get; set; } = 19.324f; - [DisplayName("xres")] public ushort Xres { get; set; } - [DisplayName("yres")] public ushort Yres { get; set; } - [DisplayName("thickness")] public float Thickness { get; set; } - [DisplayName("layers_num")] public uint LayersNum { get; set; } - [DisplayName("head_layers_num")] public ushort HeadLayersNum { get; set; } = 3; - [DisplayName("layers_expo_ms")] public uint LayersExpoMs { get; set; } = 5500; - [DisplayName("head_layers_expo_ms")] public uint HeadLayersExpoMs { get; set; } = 35000; - [DisplayName("wait_before_expo_ms")] public uint WaitBeforeExpoMs { get; set; } = 2000; - [DisplayName("lift_distance")] public float LiftDistance { get; set; } = 4; - [DisplayName("lift_up_speed")] public float LiftUpSpeed { get; set; } = 120; - [DisplayName("lift_down_speed")] public float LiftDownSpeed { get; set; } = 120; - [DisplayName("lift_when_finished")] public byte LiftWhenFinished { get; set; } = 80; - } - - #endregion - - #region Properties - public Slice SliceSettings { get; } = new Slice(); - public Output OutputSettings { get; } = new Output(); - - - public override FileFormatType FileType => FileFormatType.Archive; - - public override FileExtension[] FileExtensions { get; } = { - new FileExtension("cws", "NovaMaker CWS Files") - }; - - public override Type[] ConvertToFormats { get; } = null; - - public override PrintParameterModifier[] PrintParameterModifiers { get; } = { - PrintParameterModifier.InitialLayerCount, - PrintParameterModifier.InitialExposureSeconds, - PrintParameterModifier.ExposureSeconds, - - - PrintParameterModifier.LiftHeight, - PrintParameterModifier.RetractSpeed, - PrintParameterModifier.LiftSpeed, - - PrintParameterModifier.BottomLightPWM, - PrintParameterModifier.LightPWM, - }; - - public override byte ThumbnailsCount { get; } = 0; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = null; - - public override uint ResolutionX => SliceSettings.Xres; - - public override uint ResolutionY => SliceSettings.Yres; - public override byte AntiAliasing => (byte) OutputSettings.AntiAliasingValue; - - public override float LayerHeight => SliceSettings.Thickness; - - public override ushort InitialLayerCount => SliceSettings.HeadLayersNum; - - public override float InitialExposureTime => SliceSettings.HeadLayersExpoMs / 1000f; - - public override float LayerExposureTime => SliceSettings.LayersExpoMs / 1000f; - - public override float LiftHeight => SliceSettings.LiftDistance; - - public override float LiftSpeed => SliceSettings.LiftDownSpeed; - - public override float RetractSpeed => OutputSettings.ZLiftRetractRate; - - public override float PrintTime => 0; - - public override float UsedMaterial => 0; - - public override float MaterialCost => 0; - - public override string MaterialName => string.Empty; - - public override string MachineName => "Unknown"; - - public override object[] Configs => new object[] { SliceSettings, OutputSettings}; - #endregion - - #region Methods - - public override void Clear() - { - base.Clear(); - GCode = null; - } - - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - base.Encode(fileFullPath, progress); - using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) - { - string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits"; - var entry = outputFile.CreateEntry("slice.conf"); - var stream = entry.Open(); - - using (TextWriter tw = new StreamWriter(stream)) - { - - tw.WriteLine($"# {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}"); - tw.WriteLine("# conf version 1.0"); - tw.WriteLine(""); - - foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); - if (ReferenceEquals(displayNameAttribute, null)) continue; - tw.WriteLine($"{displayNameAttribute.DisplayName.PadRight(24)}= {propertyInfo.GetValue(SliceSettings)}"); - } - } - - - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - progress.Token.ThrowIfCancellationRequested(); - Layer layer = this[layerIndex]; - var layerImagePath = $"{Path.GetFileNameWithoutExtension(fileFullPath)}{layer.Index.ToString().PadLeft(LayerCount.ToString().Length, '0')}.png"; - outputFile.PutFileContent(layerImagePath, layer.CompressedBytes, ZipArchiveMode.Create); - progress++; - } - - UpdateGCode(); - outputFile.PutFileContent($"{Path.GetFileNameWithoutExtension(fileFullPath)}.gcode", GCode.ToString(), ZipArchiveMode.Create); - } - } - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - FileFullPath = fileFullPath; - using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) - { - var entry = inputFile.GetEntry("slice.conf"); - if (ReferenceEquals(entry, null)) - { - Clear(); - throw new FileLoadException("slice.conf not found", fileFullPath); - } - - - - using (TextReader tr = new StreamReader(entry.Open())) - { - string line; - while ((line = tr.ReadLine()) != null) - { - if (string.IsNullOrEmpty(line)) continue; - if(line[0] == '#') continue; - - var splitLine = line.Split('='); - if(splitLine.Length < 2) continue; - - foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); - if(ReferenceEquals(displayNameAttribute, null)) continue; - if(!splitLine[0].Trim().Equals(displayNameAttribute.DisplayName)) continue; - Helpers.SetPropertyValue(propertyInfo, SliceSettings, splitLine[1].Trim()); - } - } - tr.Close(); - } - - entry = inputFile.GetEntry($"{Path.GetFileNameWithoutExtension(fileFullPath)}.gcode"); - if (ReferenceEquals(entry, null)) - { - Clear(); - throw new FileLoadException($"{Path.GetFileNameWithoutExtension(fileFullPath)}.gcode not found", fileFullPath); - } - - using (TextReader tr = new StreamReader(entry.Open())) - { - string line; - GCode = new StringBuilder(); - while ((line = tr.ReadLine()) != null) - { - GCode.AppendLine(line); - if (string.IsNullOrEmpty(line)) continue; - - if (line[0] != ';') - { - continue; - } - - var splitLine = line.Split('='); - if (splitLine.Length < 2) continue; - - foreach (var propertyInfo in OutputSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); - if (ReferenceEquals(displayNameAttribute, null)) continue; - if (!splitLine[0].Trim(' ', ';', '(').Equals(displayNameAttribute.DisplayName)) continue; - Helpers.SetPropertyValue(propertyInfo, OutputSettings, splitLine[1].Trim(' ', ')', 'm', 'n', 's', '/')); - //Debug.WriteLine(splitLine[1].Trim(' ', ')', 'm', 'n', '/')); - } - } - tr.Close(); - } - - - LayerManager = new LayerManager(OutputSettings.LayersNum); - - foreach (var zipArchiveEntry in inputFile.Entries) - { - if (!zipArchiveEntry.Name.EndsWith(".png")) continue; - - // - .png - 4 numbers - int layerSize = OutputSettings.LayersNum.ToString().Length; - string layerStr = zipArchiveEntry.Name.Substring(zipArchiveEntry.Name.Length - 4 - layerSize, layerSize); - uint iLayer = uint.Parse(layerStr); - LayerManager[iLayer] = new Layer(iLayer, zipArchiveEntry.Open(), zipArchiveEntry.Name); - } - } - - LayerManager.GetBoundingRectangle(progress); - } - - public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) - { - var baseValue = base.GetValueFromPrintParameterModifier(modifier); - if (!ReferenceEquals(baseValue, null)) return baseValue; - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) return OutputSettings.BottomLayerLightPWM; - if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) return OutputSettings.LayerLightPWM; - - return null; - } - - public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) - { - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - { - SliceSettings.HeadLayersNum = - OutputSettings.NumberBottomLayers = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - { - SliceSettings.HeadLayersExpoMs = - OutputSettings.BottomLayersTime = value.Convert()*1000; - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - { - SliceSettings.LayersExpoMs = - OutputSettings.LayerTime = value.Convert() * 1000; - UpdateGCode(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) - { - SliceSettings.LiftDistance = - OutputSettings.LiftDistance = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) - { - SliceSettings.LiftUpSpeed = - OutputSettings.ZLiftFeedRate = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) - { - SliceSettings.LiftDownSpeed = - OutputSettings.ZLiftRetractRate = - OutputSettings.ZBottomLiftFeedRate = value.Convert(); - UpdateGCode(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) - { - OutputSettings.BottomLayerLightPWM = value.Convert(); - UpdateGCode(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) - { - OutputSettings.BottomLayerLightPWM = value.Convert(); - UpdateGCode(); - return true; - } - - return false; - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - if (!string.IsNullOrEmpty(filePath)) - { - File.Copy(FileFullPath, filePath, true); - FileFullPath = filePath; - } - - using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) - { - string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits"; - var entry = outputFile.GetPutFile("slice.conf"); - var stream = entry.Open(); - stream.SetLength(0); - - using (TextWriter tw = new StreamWriter(stream)) - { - - tw.WriteLine($"# {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}"); - tw.WriteLine("# conf version 1.0"); - tw.WriteLine(""); - - foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); - if (ReferenceEquals(displayNameAttribute, null)) continue; - tw.WriteLine($"{displayNameAttribute.DisplayName.PadRight(24)}= {propertyInfo.GetValue(SliceSettings)}"); - } - } - - - foreach (var zipentry in outputFile.Entries) - { - if (zipentry.Name.EndsWith(".gcode")) - { - zipentry.Delete(); - break; - } - } - outputFile.PutFileContent($"{Path.GetFileNameWithoutExtension(FileFullPath)}.gcode", GCode.ToString(), ZipArchiveMode.Update); - - foreach (var layer in this) - { - if (!layer.IsModified) continue; - outputFile.PutFileContent(layer.Filename, layer.CompressedBytes, ZipArchiveMode.Update); - layer.IsModified = false; - } - } - - //Decode(FileFullPath, progress); - } - - public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) - { - throw new NotImplementedException(); - } - - private void UpdateGCode() - { - string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits"; - GCode = new StringBuilder(); - GCode.AppendLine($"; {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}"); - GCode.AppendLine("(****Build and Slicing Parameters * ***)"); - - foreach (var propertyInfo in OutputSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); - if (ReferenceEquals(displayNameAttribute, null)) continue; - GCode.AppendLine($";({displayNameAttribute.DisplayName.PadRight(24)} = {propertyInfo.GetValue(OutputSettings)})"); - } - GCode.AppendLine(); - GCode.AppendFormat(GCodeStart, Environment.NewLine); - - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - //Layer layer = this[layerIndex]; - GCode.AppendLine($"{GCodeKeywordSlice} {layerIndex}"); - GCode.AppendLine($"M106 S{GetInitialLayerValueOrNormal(layerIndex, OutputSettings.BottomLayerLightPWM, OutputSettings.LayerLightPWM)}"); - GCode.AppendLine($"{GCodeKeywordDelay} {GetInitialLayerValueOrNormal(layerIndex, SliceSettings.HeadLayersExpoMs, SliceSettings.LayersExpoMs)}"); - GCode.AppendLine("M106 S0"); - GCode.AppendLine(GCodeKeywordSliceBlank); - GCode.AppendLine($"G1 Z{LiftHeight} F{LiftSpeed}"); - GCode.AppendLine($"G1 Z-{LiftHeight - LayerHeight} F{RetractSpeed}"); - GCode.AppendLine($"{GCodeKeywordDelay} {GetInitialLayerValueOrNormal(layerIndex, SliceSettings.HeadLayersExpoMs, SliceSettings.LayersExpoMs)}"); - } - - GCode.AppendFormat(GCodeEnd, Environment.NewLine, SliceSettings.LiftWhenFinished); - - /*GCode = Regex.Replace(GCode, @"Z[+]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", - $"Z{SliceSettings.LiftDistance} F{SliceSettings.LiftUpSpeed}"); - - GCode = Regex.Replace(GCode, @"Z-[-]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", - $"Z-{SliceSettings.LiftDistance - LayerHeight} F{SliceSettings.LiftDownSpeed}");*/ - - } - #endregion - } -} diff --git a/UVtools.Core/ChituboxFile.cs b/UVtools.Core/ChituboxFile.cs deleted file mode 100644 index a011dd0..0000000 --- a/UVtools.Core/ChituboxFile.cs +++ /dev/null @@ -1,1650 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -// 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 System.Linq; -using System.Threading.Tasks; -using BinarySerialization; -using Emgu.CV; -using Emgu.CV.CvEnum; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - public class ChituboxFile : FileFormat - { - #region Constants - private const uint MAGIC_CBDDLP = 0x12FD0019; - private const uint MAGIC_CBT = 0x12FD0086; - private const ushort REPEATRGB15MASK = 0x20; - - private const byte RLE8EncodingLimit = 0x7d; // 125; - private const ushort RLE16EncodingLimit = 0xFFF; - #endregion - - #region Sub Classes - #region Header - public class Header - { - - /// - /// Gets a magic number identifying the file type. - /// 0x12fd_0019 for cbddlp - /// 0x12fd_0086 for ctb - /// - [FieldOrder(0)] public uint Magic { get; set; } - - /// - /// Gets the software version - /// - [FieldOrder(1)] public uint Version { get; set; } = 2; - - /// - /// Gets dimensions of the printer’s X output volume, in millimeters. - /// - [FieldOrder(2)] public float BedSizeX { get; set; } - - /// - /// Gets dimensions of the printer’s Y output volume, in millimeters. - /// - [FieldOrder(3)] public float BedSizeY { get; set; } - - /// - /// Gets dimensions of the printer’s Z output volume, in millimeters. - /// - [FieldOrder(4)] public float BedSizeZ { get; set; } - - [FieldOrder(5)] public uint Unknown1 { get; set; } - [FieldOrder(6)] public uint Unknown2 { get; set; } - - /// - /// Gets the height of the model described by this file, in millimeters. - /// - [FieldOrder(7)] public float OverallHeightMilimeter { get; set; } - - /// - /// Gets the layer height setting used at slicing, in millimeters. Actual height used by the machine is in the layer table. - /// - [FieldOrder(8)] public float LayerHeightMilimeter { get; set; } - - /// - /// 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. - /// - [FieldOrder(9)] public float LayerExposureSeconds { get; set; } - - /// - /// Gets the exposure time setting used at slicing, in seconds, for bottom layers. Actual time used by the machine is in the layer table. - /// - [FieldOrder(10)] public float BottomExposureSeconds { get; set; } - - /// - /// 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. - /// - [FieldOrder(11)] public float LayerOffTime { get; set; } = 1; - - /// - /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig.. - /// - [FieldOrder(12)] public uint BottomLayersCount { get; set; } = 10; - - /// - /// Gets the printer resolution along X axis, in pixels. This information is critical to correctly decoding layer images. - /// - [FieldOrder(13)] public uint ResolutionX { get; set; } - - /// - /// Gets the printer resolution along Y axis, in pixels. This information is critical to correctly decoding layer images. - /// - [FieldOrder(14)] public uint ResolutionY { get; set; } - - /// - /// Gets the file offsets of ImageHeader records describing the larger preview images. - /// - [FieldOrder(15)] public uint PreviewLargeOffsetAddress { get; set; } - - /// - /// Gets the file offset of a table of LayerHeader records giving parameters for each printed layer. - /// - [FieldOrder(16)] public uint LayersDefinitionOffsetAddress { get; set; } - - /// - /// 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. - /// - [FieldOrder(17)] public uint LayerCount { get; set; } - - /// - /// Gets the file offsets of ImageHeader records describing the smaller preview images. - /// - [FieldOrder(18)] public uint PreviewSmallOffsetAddress { get; set; } - - /// - /// Gets the estimated duration of print, in seconds. - /// - [FieldOrder(19)] public uint PrintTime { get; set; } - - /// - /// Gets the records whether this file was generated assuming normal (0) or mirrored (1) image projection. LCD printers are "mirrored" for this purpose. - /// - [FieldOrder(20)] public uint ProjectorType { get; set; } - - /// - /// Gets the print parameters table offset - /// - [FieldOrder(21)] public uint PrintParametersOffsetAddress { get; set; } - - /// - /// Gets the print parameters table size in bytes. - /// - [FieldOrder(22)] public uint PrintParametersSize { get; set; } - - /// - /// 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. - /// - [FieldOrder(23)] public uint AntiAliasLevel { get; set; } = 1; - - /// - /// 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. - /// - [FieldOrder(24)] public ushort LightPWM { get; set; } = 255; - - /// - /// 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. - /// - [FieldOrder(25)] public ushort BottomLightPWM { get; set; } = 255; - - /// - /// Gets the key used to encrypt layer data, or 0 if encryption is not used. - /// - [FieldOrder(26)] public uint EncryptionKey { get; set; } - - /// - /// Gets the slicer tablet offset - /// - [FieldOrder(27)] public uint SlicerOffset { get; set; } - - /// - /// Gets the slicer table size in bytes - /// - [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 - { - /// - /// Gets the distance to lift the build platform away from the vat after bottom layers, in millimeters. - /// - [FieldOrder(0)] public float BottomLiftHeight { get; set; } = 5; - - /// - /// Gets the speed at which to lift the build platform away from the vat after bottom layers, in millimeters per minute. - /// - [FieldOrder(1)] public float BottomLiftSpeed { get; set; } = 300; - - /// - /// Gets the distance to lift the build platform away from the vat after normal layers, in millimeters. - /// - [FieldOrder(2)] public float LiftHeight { get; set; } = 5; - - /// - /// Gets the speed at which to lift the build platform away from the vat after normal layers, in millimeters per minute. - /// - [FieldOrder(3)] public float LiftSpeed { get; set; } = 300; - - /// - /// Gets the speed to use when the build platform re-approaches the vat after lift, in millimeters per minute. - /// - [FieldOrder(4)] public float RetractSpeed { get; set; } = 300; - - /// - /// Gets the estimated required resin, measured in milliliters. The volume number is derived from the model. - /// - [FieldOrder(5)] public float VolumeMl { get; set; } - - /// - /// Gets the estimated grams, derived from volume using configured factors for density. - /// - [FieldOrder(6)] public float WeightG { get; set; } - - /// - /// Gets the estimated cost based on currency unit the user had configured. Derived from volume using configured factors for density and cost. - /// - [FieldOrder(7)] public float CostDollars { get; set; } - - /// - /// 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. - /// - [FieldOrder(8)] public float BottomLightOffDelay { get; set; } = 1; - - /// - /// 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. - /// - [FieldOrder(9)] public float LightOffDelay { get; set; } = 1; - - /// - /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig. - /// - [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(LiftSpeed)}: {LiftSpeed}, {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; } - - /// - /// Gets the machine name offset to a string naming the machine type, and its length in bytes. - /// - [FieldOrder(7)] public uint MachineNameAddress { get; set; } - - /// - /// Gets the machine size in bytes - /// - [FieldOrder(8)] public uint MachineNameSize { get; set; } - - /// - /// Gets the parameter used to control encryption. - /// Not totally understood. 0 for cbddlp files, 0xF for ctb files. - /// - [FieldOrder(9)] public uint EncryptionMode { get; set; } = 8; - - /// - /// 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. - /// - [FieldOrder(10)] public uint MysteriousId { get; set; } - - /// - /// Gets the user-selected antialiasing level. For cbddlp files this will match the level_set_count. For ctb files, this number is essentially arbitrary. - /// - [FieldOrder(11)] public uint AntiAliasLevel { get; set; } = 1; - - /// - /// 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.) - /// - [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; } - - /// - /// 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. - /// - [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 - /// - /// The files contain two preview images. - /// These are shown on the printer display when choosing which file to print, sparing the poor printer from needing to render a 3D image from scratch. - /// - public class Preview - { - /// - /// Gets the X dimension of the preview image, in pixels. - /// - [FieldOrder(0)] public uint ResolutionX { get; set; } - - /// - /// Gets the Y dimension of the preview image, in pixels. - /// - [FieldOrder(1)] public uint ResolutionY { get; set; } - - /// - /// Gets the image offset of the encoded data blob. - /// - [FieldOrder(2)] public uint ImageOffset { get; set; } - - /// - /// Gets the image length in bytes. - /// - [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 Mat Decode(byte[] rawImageData) - { - var image = new Mat(new Size((int) ResolutionX, (int) ResolutionY), DepthType.Cv8U, 3); - var span = image.GetPixelSpan(); - //var image = EmguExtensions.CreateMat(out var bytes, new Size((int) ResolutionX, (int) ResolutionY), 3); - //var image = new MatBytes(new Size((int)ResolutionX, (int)ResolutionY), 3); - //var bytes = image.Bytes; - - int pixel = 0; - for (int n = 0; n < rawImageData.Length; 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++) - { - span[pixel++] = blue; - span[pixel++] = green; - span[pixel++] = red; - //span[pixel++] = new Rgba32(red, green, blue); - } - } - - return image; - } - - 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}"; - } - - public byte[] Encode(Mat image) - { - List rawData = new List(); - ushort color15 = 0; - uint rep = 0; - - var span = image.GetPixelSpan(); - - 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; - } - } - - int pixel = 0; - while (pixel < span.Length) - { - var ncolor15 = - // bgr - (span[pixel++] >> 3) | ((span[pixel++] >> 2) << 5) | ((span[pixel++] >> 3) << 11); - - if (ncolor15 == color15) - { - rep++; - if (rep == RLE16EncodingLimit) - { - RleRGB15(); - rep = 0; - } - } - else - { - RleRGB15(); - color15 = (ushort) ncolor15; - rep = 1; - } - } - - RleRGB15(); - - ImageLength = (uint) rawData.Count; - - return rawData.ToArray(); - } - } - - #endregion - - #region Layer - public class LayerData - { - /// - /// Gets the build platform Z position for this layer, measured in millimeters. - /// - [FieldOrder(0)] public float LayerPositionZ { get; set; } - - /// - /// Gets the exposure time for this layer, in seconds. - /// - [FieldOrder(1)] public float LayerExposure { get; set; } - - /// - /// Gets how long to keep the light off after exposing this layer, in seconds. - /// - [FieldOrder(2)] public float LayerOffTimeSeconds { get; set; } - - /// - /// Gets the layer image offset to encoded layer data, and its length in bytes. - /// - [FieldOrder(3)] public uint DataAddress { get; set; } - - /// - /// Gets the layer image length in bytes. - /// - [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; } - - [Ignore] public byte[] EncodedRle { get; set; } - [Ignore] public ChituboxFile Parent { get; set; } - - public LayerData() - { - } - - public LayerData(ChituboxFile parent, uint layerIndex) - { - Parent = parent; - LayerPositionZ = parent.GetHeightFromLayer(layerIndex); - - LayerOffTimeSeconds = layerIndex < parent.HeaderSettings.BottomLayersCount - ? parent.PrintParametersSettings.BottomLightOffDelay - : parent.PrintParametersSettings.LightOffDelay; - - LayerExposure = layerIndex < parent.HeaderSettings.BottomLayersCount - ? parent.HeaderSettings.BottomExposureSeconds - : parent.HeaderSettings.LayerExposureSeconds; - } - - public Mat Decode(uint layerIndex, bool consumeData = true) - { - var image = Parent.IsCbtFile ? DecodeCbtImage(layerIndex) : DecodeCbddlpImage(Parent, layerIndex); - - if (consumeData) - EncodedRle = null; - - return image; - } - - public static Mat DecodeCbddlpImage(ChituboxFile parent, uint layerIndex) - { - //Mat image = new Mat(new Size((int)parent.HeaderSettings.ResolutionX, (int)parent.HeaderSettings.ResolutionY), DepthType.Cv8U, 1); - //var bytes = image.GetBytesBlank(); - //var image = EmguExtensions.CreateMat(out var bytes, new Size((int)parent.HeaderSettings.ResolutionX, (int)parent.HeaderSettings.ResolutionY)); - var image = new Mat(new Size((int)parent.HeaderSettings.ResolutionX, (int)parent.HeaderSettings.ResolutionY), DepthType.Cv8U, 1); - var span = image.GetPixelSpan(); - - for (byte bit = 0; bit < parent.AntiAliasing; bit++) - { - var layer = parent.LayersDefinitions[bit, layerIndex]; - - int n = 0; - for (int index = 0; index < layer.DataSize; index++) - { - // Lower 7 bits is the repeat count for the bit (0..127) - int reps = layer.EncodedRle[index] & 0x7f; - - // We only need to set the non-zero pixels - // High bit is on for white, off for black - if ((layer.EncodedRle[index] & 0x80) != 0) - { - for (int i = 0; i < reps; i++) - { - span[n + i]++; - } - } - - n += reps; - - if (n == span.Length) - { - break; - } - - if (n > span.Length) - { - image.Dispose(); - throw new FileLoadException("Error image ran off the end"); - } - } - } - - for (int i = 0; i < span.Length; i++) - { - int newC = span[i] * (256 / parent.AntiAliasing); - - if (newC > 0) - { - newC--; - } - - span[i] = (byte) newC; - - - } - - return image; - } - - private Mat DecodeCbtImage(uint layerIndex) - { - //Mat image = new Mat(new Size((int)Parent.HeaderSettings.ResolutionX, (int)Parent.HeaderSettings.ResolutionY), DepthType.Cv8U, 1); - //var bytes = image.GetBytes(); - //var image = EmguExtensions.CreateMat(out var bytes, new Size((int)Parent.HeaderSettings.ResolutionX, (int)Parent.HeaderSettings.ResolutionY)); - var image = new Mat(new Size((int)Parent.HeaderSettings.ResolutionX, (int)Parent.HeaderSettings.ResolutionY), DepthType.Cv8U, 1); - var span = image.GetPixelSpan(); - - if (Parent.HeaderSettings.EncryptionKey > 0) - { - KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex); - EncodedRle = kr.Read(EncodedRle); - } - - int pixel = 0; - for (var n = 0; n < EncodedRle.Length; n++) - { - byte code = EncodedRle[n]; - uint stride = 1; - - if ((code & 0x80) == 0x80) // It's a run - { - code &= 0x7f; // Get the run length - n++; - - var slen = EncodedRle[n]; - - if ((slen & 0x80) == 0) - { - stride = slen; - } - else if ((slen & 0xc0) == 0x80) - { - stride = (uint)(((slen & 0x3f) << 8) + EncodedRle[n + 1]); - n++; - } - else if ((slen & 0xe0) == 0xc0) - { - stride = (uint)(((slen & 0x1f) << 16) + (EncodedRle[n + 1] << 8) + EncodedRle[n + 2]); - n += 2; - } - else if ((slen & 0xf0) == 0xe0) - { - stride = (uint)(((slen & 0xf) << 24) + (EncodedRle[n + 1] << 16) + (EncodedRle[n + 2] << 8) + EncodedRle[n + 3]); - - n += 3; - } - else - { - image.Dispose(); - throw new FileLoadException("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 - { - pixel += (int)stride; - continue; - } - - while (stride-- > 0) - { - span[pixel] = code; - pixel++; - } - } - - return image; - } - - public byte[] Encode(Mat image, byte aaIndex, uint layerIndex) - { - return Parent.IsCbtFile ? EncodeCbtImage(image, layerIndex) : EncodeCbddlpImage(image, aaIndex); - } - - public byte[] EncodeCbddlpImage(Mat image, byte bit) - { - List rawData = new List(); - var span = image.GetPixelSpan(); - - bool obit = false; - int rep = 0; - - //ngrey:= uint16(r | g | b) - // thresholds: - // aa 1: 127 - // aa 2: 255 127 - // aa 4: 255 191 127 63 - // aa 8: 255 223 191 159 127 95 63 31 - byte threshold = (byte)(256 / Parent.AntiAliasing * bit - 1); - - void AddRep() - { - if (rep <= 0) return; - - byte by = (byte)rep; - - if (obit) - { - by |= 0x80; - //bitsOn += uint(rep) - } - - rawData.Add(by); - } - - for (int pixel = 0; pixel < span.Length; pixel++) - { - var nbit = span[pixel] >= threshold; - - if (nbit == obit) - { - rep++; - - if (rep == RLE8EncodingLimit) - { - AddRep(); - rep = 0; - } - } - else - { - AddRep(); - obit = nbit; - rep = 1; - } - } - - // Collect stragglers - AddRep(); - - EncodedRle = rawData.ToArray(); - DataSize = (uint) EncodedRle.Length; - - return EncodedRle; - } - - private byte[] EncodeCbtImage(Mat image, uint layerIndex) - { - List rawData = new List(); - byte color = byte.MaxValue >> 1; - uint stride = 0; - var span = image.GetPixelSpan(); - - 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 pixel = 0; pixel < span.Length; pixel++) - { - var grey7 = (byte) (span[pixel] >> 1); - - if (grey7 == color) - { - stride++; - } - else - { - AddRep(); - color = grey7; - stride = 1; - } - } - - AddRep(); - - if (Parent.HeaderSettings.EncryptionKey > 0) - { - KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex); - EncodedRle = kr.Read(rawData.ToArray()); - } - else - { - EncodedRle = rawData.ToArray(); - } - - DataSize = (uint)EncodedRle.Length; - - return EncodedRle; - } - - 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 uint Init { get; } - public uint 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 Read(List input) - { - List data = new List(input.Count); - data.AddRange(input.Select(t => (byte) (t ^ 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 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 LayerData[,] LayersDefinitions { get; private set; } - - public Dictionary LayersHash { get; } = new Dictionary(); - - 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", "Chitubox Photon Files"), - }; - - public override Type[] ConvertToFormats { get; } = - { - typeof(ChituboxZipFile), - typeof(PWSFile), - typeof(PHZFile), - typeof(ZCodexFile), - }; - - public override PrintParameterModifier[] PrintParameterModifiers { get; } = - { - PrintParameterModifier.InitialLayerCount, - PrintParameterModifier.InitialExposureSeconds, - PrintParameterModifier.ExposureSeconds, - - PrintParameterModifier.BottomLayerOffTime, - PrintParameterModifier.LayerOffTime, - PrintParameterModifier.BottomLiftHeight, - PrintParameterModifier.BottomLiftSpeed, - PrintParameterModifier.LiftHeight, - PrintParameterModifier.LiftSpeed, - PrintParameterModifier.RetractSpeed, - - PrintParameterModifier.BottomLightPWM, - PrintParameterModifier.LightPWM, - }; - - public override byte ThumbnailsCount { get; } = 2; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(400, 300), new System.Drawing.Size(200, 125)}; - - public override uint ResolutionX => HeaderSettings.ResolutionX; - - public override uint ResolutionY => HeaderSettings.ResolutionY; - public override byte AntiAliasing => (byte) (IsCbtFile ? SlicerInfoSettings.AntiAliasLevel : HeaderSettings.AntiAliasLevel); - - public override float LayerHeight => HeaderSettings.LayerHeightMilimeter; - - 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.LiftSpeed; - 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 IsCbddlpFile => HeaderSettings.Magic == MAGIC_CBDDLP; - 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(); - } - - LayersDefinitions = null; - } - - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - base.Encode(fileFullPath, progress); - LayersHash.Clear(); - - HeaderSettings.Magic = fileFullPath.EndsWith(".ctb") ? MAGIC_CBT : MAGIC_CBDDLP; - HeaderSettings.PrintParametersSize = (uint)Helpers.Serializer.SizeOf(PrintParametersSettings); - - - if (IsCbtFile) - { - SlicerInfoSettings.AntiAliasLevel = HeaderSettings.AntiAliasLevel; - HeaderSettings.AntiAliasLevel = 1; - PrintParametersSettings.Padding4 = 0x1234; - //SlicerInfoSettings.EncryptionMode = 0xf; - SlicerInfoSettings.EncryptionMode = 7; - SlicerInfoSettings.MysteriousId = 0x12345678; - SlicerInfoSettings.Unknown1 = 0x200; - - if (HeaderSettings.EncryptionKey == 0) - { - Random rnd = new Random(); - HeaderSettings.EncryptionKey = (uint)rnd.Next(byte.MaxValue, int.MaxValue); - } - } - - uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); - LayersDefinitions = new LayerData[HeaderSettings.AntiAliasLevel, HeaderSettings.LayerCount]; - using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) - { - - outputFile.Seek((int) currentOffset, SeekOrigin.Begin); - - - for (byte i = 0; i < ThumbnailsCount; i++) - { - var image = Thumbnails[i]; - - Preview preview = new Preview - { - ResolutionX = (uint)image.Width, - ResolutionY = (uint)image.Height, - }; - - var previewBytes = preview.Encode(image); - - if (previewBytes.Length == 0) continue; - - if (i == (byte) FileThumbnailSize.Small) - { - HeaderSettings.PreviewSmallOffsetAddress = currentOffset; - } - else - { - HeaderSettings.PreviewLargeOffsetAddress = currentOffset; - } - - - currentOffset += (uint) Helpers.Serializer.SizeOf(preview); - preview.ImageOffset = currentOffset; - - Helpers.SerializeWriteFileStream(outputFile, preview); - currentOffset += outputFile.WriteBytes(previewBytes); - } - - - if (HeaderSettings.Version == 2) - { - HeaderSettings.PrintParametersOffsetAddress = currentOffset; - - currentOffset += Helpers.SerializeWriteFileStream(outputFile, PrintParametersSettings); - - HeaderSettings.SlicerOffset = currentOffset; - HeaderSettings.SlicerSize = (uint) Helpers.Serializer.SizeOf(SlicerInfoSettings) - SlicerInfoSettings.MachineNameSize; - - SlicerInfoSettings.MachineNameAddress = currentOffset + HeaderSettings.SlicerSize; - - - currentOffset += Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); - } - - HeaderSettings.LayersDefinitionOffsetAddress = currentOffset; - uint layerDataCurrentOffset = currentOffset + (uint)Helpers.Serializer.SizeOf(new LayerData()) * HeaderSettings.LayerCount * HeaderSettings.AntiAliasLevel; - - progress.ItemCount *= 2 * HeaderSettings.AntiAliasLevel; - - for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) - { - progress.Token.ThrowIfCancellationRequested(); - Parallel.For(0, LayerCount, /*new ParallelOptions{MaxDegreeOfParallelism = 1},*/ layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - LayerData layerData = new LayerData(this, (uint) layerIndex); - using (var image = this[layerIndex].LayerMat) - { - layerData.Encode(image, aaIndex, (uint) layerIndex); - LayersDefinitions[aaIndex, layerIndex] = layerData; - } - - lock (progress.Mutex) - { - progress++; - } - }); - - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - progress.Token.ThrowIfCancellationRequested(); - var layerData = LayersDefinitions[aaIndex, layerIndex]; - LayerData layerDataHash = null; - - if (!IsCbtFile && HeaderSettings.EncryptionKey == 0) - { - string hash = Helpers.ComputeSHA1Hash(layerData.EncodedRle); - if (LayersHash.TryGetValue(hash, out layerDataHash)) - { - layerData.DataAddress = layerDataHash.DataAddress; - layerData.DataSize = layerDataHash.DataSize; - } - else - { - LayersHash.Add(hash, layerData); - } - } - - if (ReferenceEquals(layerDataHash, null)) - { - layerData.DataAddress = layerDataCurrentOffset; - - outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin); - layerDataCurrentOffset += outputFile.WriteBytes(layerData.EncodedRle); - } - - outputFile.Seek(currentOffset, SeekOrigin.Begin); - currentOffset += Helpers.SerializeWriteFileStream(outputFile, layerData); - - progress++; - } - } - - outputFile.Seek(0, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - - 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, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) - { - //HeaderSettings = Helpers.ByteToType(InputFile); - //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); - HeaderSettings = Helpers.Deserialize
(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; - - progress.Reset(OperationProgress.StatusDecodeThumbnails, ThumbnailsCount); - - 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(inputFile); - - Debug.Write($"Preview {i} -> "); - Debug.WriteLine(Previews[i]); - - inputFile.Seek(Previews[i].ImageOffset, SeekOrigin.Begin); - byte[] rawImageData = new byte[Previews[i].ImageLength]; - inputFile.Read(rawImageData, 0, (int) Previews[i].ImageLength); - - Thumbnails[i] = Previews[i].Decode(rawImageData); - progress++; - } - - //if (HeaderSettings.Version == 2) - //{ - if (HeaderSettings.PrintParametersOffsetAddress > 0) - { - inputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); - PrintParametersSettings = Helpers.Deserialize(inputFile); - Debug.Write("Print Parameters -> "); - Debug.WriteLine(PrintParametersSettings); - - - } - - if (HeaderSettings.SlicerOffset > 0) - { - inputFile.Seek(HeaderSettings.SlicerOffset, SeekOrigin.Begin); - SlicerInfoSettings = Helpers.Deserialize(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}");*/ - //} - - LayersDefinitions = new LayerData[HeaderSettings.AntiAliasLevel, HeaderSettings.LayerCount]; - - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - - progress.Reset(OperationProgress.StatusGatherLayers, - HeaderSettings.AntiAliasLevel * HeaderSettings.LayerCount); - - 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); - LayerData layerData = Helpers.Deserialize(inputFile); - layerData.Parent = this; - LayersDefinitions[aaIndex, layerIndex] = layerData; - - layerOffset += (uint) Helpers.Serializer.SizeOf(layerData); - //Debug.Write($"LAYER {layerIndex} -> "); - //Debug.WriteLine(layerData); - - layerData.EncodedRle = new byte[layerData.DataSize]; - inputFile.Seek(layerData.DataAddress, SeekOrigin.Begin); - inputFile.Read(layerData.EncodedRle, 0, (int) layerData.DataSize); - progress++; - progress.Token.ThrowIfCancellationRequested(); - } - } - - LayerManager = new LayerManager(HeaderSettings.LayerCount); - - progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); - - Parallel.For(0, LayerCount, layerIndex => - { - if (progress.Token.IsCancellationRequested) - { - return; - } - - using (var image = LayersDefinitions[0, layerIndex].Decode((uint) layerIndex)) - { - this[layerIndex] = new Layer((uint) layerIndex, image); - lock (progress.Mutex) - { - progress++; - } - } - }); - } - - progress.Token.ThrowIfCancellationRequested(); - } - - 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 - LayersDefinitions[aaIndex, layerIndex].LayerExposure = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomExposureSeconds : HeaderSettings.LayerExposureSeconds; - LayersDefinitions[aaIndex, layerIndex].LayerOffTimeSeconds = layerIndex < HeaderSettings.BottomLayersCount ? PrintParametersSettings.BottomLightOffDelay : PrintParametersSettings.LightOffDelay; - } - } - } - - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - { - HeaderSettings.BottomLayersCount = - PrintParametersSettings.BottomLayerCount = value.Convert(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - { - HeaderSettings.BottomExposureSeconds = value.Convert(); - UpdateLayers(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - { - HeaderSettings.LayerExposureSeconds = value.Convert(); - UpdateLayers(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) - { - PrintParametersSettings.BottomLightOffDelay = value.Convert(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) - { - HeaderSettings.LayerOffTime = - PrintParametersSettings.LightOffDelay = value.Convert(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) - { - PrintParametersSettings.BottomLiftHeight = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) - { - PrintParametersSettings.BottomLiftSpeed = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) - { - PrintParametersSettings.LiftHeight = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) - { - PrintParametersSettings.LiftSpeed = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) - { - PrintParametersSettings.RetractSpeed = value.Convert(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) - { - HeaderSettings.BottomLightPWM = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) - { - HeaderSettings.LightPWM = value.Convert(); - return true; - } - - return false; - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - if (LayerManager.IsModified) - { - if (!string.IsNullOrEmpty(filePath)) - { - FileFullPath = filePath; - } - Encode(FileFullPath, progress); - return; - } - - - if (!string.IsNullOrEmpty(filePath)) - { - File.Copy(FileFullPath, filePath, true); - FileFullPath = filePath; - } - - using (var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) - { - - outputFile.Seek(0, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - - if (HeaderSettings.Version == 2 && HeaderSettings.PrintParametersOffsetAddress > 0) - { - outputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, PrintParametersSettings); - Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); - } - - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) - { - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - outputFile.Seek(layerOffset, SeekOrigin.Begin); - layerOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinitions[aaIndex, layerIndex]); - } - } - } - - //Decode(FileFullPath, progress); - } - - public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) - { - if (to == typeof(ChituboxZipFile)) - { - ChituboxZipFile file = new ChituboxZipFile - { - LayerManager = LayerManager, - HeaderSettings = - { - Filename = Path.GetFileName(FileFullPath), - - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - MachineX = HeaderSettings.BedSizeX, - MachineY = HeaderSettings.BedSizeY, - MachineZ = HeaderSettings.BedSizeZ, - MachineType = MachineName, - ProjectType = HeaderSettings.ProjectorType == 0 ? "Normal" : "LCD_mirror", - - Resin = MaterialName, - Price = MaterialCost, - Weight = PrintParametersSettings.WeightG, - Volume = UsedMaterial, - Mirror = (byte) (HeaderSettings.ProjectorType == 0 ? 0 : 1), - - - BottomLayerLiftHeight = PrintParametersSettings.BottomLiftHeight, - LayerLiftHeight = PrintParametersSettings.LiftHeight, - BottomLayerLiftSpeed = PrintParametersSettings.BottomLiftSpeed, - LayerLiftSpeed = PrintParametersSettings.LiftSpeed, - RetractSpeed = PrintParametersSettings.RetractSpeed, - BottomLayCount = InitialLayerCount, - BottomLayerCount = InitialLayerCount, - BottomLightOffTime = PrintParametersSettings.BottomLightOffDelay, - LayerLightOffTime = PrintParametersSettings.LightOffDelay, - BottomLayExposureTime = InitialExposureTime, - BottomLayerExposureTime = InitialExposureTime, - LayerExposureTime = LayerExposureTime, - LayerHeight = LayerHeight, - LayerCount = LayerCount, - AntiAliasing = ValidateAntiAliasingLevel(), - BottomLightPWM = (byte) HeaderSettings.BottomLightPWM, - LayerLightPWM = (byte) HeaderSettings.LightPWM, - - EstimatedPrintTime = PrintTime - }, - }; - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(PWSFile)) - { - PWSFile file = new PWSFile - { - LayerManager = LayerManager, - HeaderSettings = - { - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - LayerHeight = LayerHeight, - LayerExposureTime = LayerExposureTime, - LiftHeight = LiftHeight, - LiftSpeed = LiftSpeed / 60, - RetractSpeed = RetractSpeed / 60, - LayerOffTime = HeaderSettings.LayerOffTime, - BottomLayersCount = InitialLayerCount, - BottomExposureSeconds = InitialExposureTime, - Price = MaterialCost, - Volume = UsedMaterial, - Weight = PrintParametersSettings.WeightG, - AntiAliasing = ValidateAntiAliasingLevel() - } - }; - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(PHZFile)) - { - PHZFile file = new PHZFile - { - LayerManager = LayerManager, - HeaderSettings = - { - Version = 2, - BedSizeX = HeaderSettings.BedSizeX, - BedSizeY = HeaderSettings.BedSizeY, - BedSizeZ = HeaderSettings.BedSizeZ, - OverallHeightMilimeter = TotalHeight, - BottomExposureSeconds = InitialExposureTime, - BottomLayersCount = InitialLayerCount, - BottomLightPWM = HeaderSettings.BottomLightPWM, - LayerCount = LayerCount, - LayerExposureSeconds = LayerExposureTime, - LayerHeightMilimeter = LayerHeight, - LayerOffTime = HeaderSettings.LayerOffTime, - LightPWM = HeaderSettings.LightPWM, - PrintTime = HeaderSettings.PrintTime, - ProjectorType = HeaderSettings.ProjectorType, - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - BottomLayerCount = InitialLayerCount, - BottomLiftHeight = PrintParametersSettings.BottomLiftHeight, - BottomLiftSpeed = PrintParametersSettings.BottomLiftSpeed, - BottomLightOffDelay = PrintParametersSettings.BottomLightOffDelay, - CostDollars = MaterialCost, - LiftHeight = PrintParametersSettings.LiftHeight, - LiftSpeed = PrintParametersSettings.LiftSpeed, - RetractSpeed = PrintParametersSettings.RetractSpeed, - VolumeMl = UsedMaterial, - AntiAliasLevelInfo = ValidateAntiAliasingLevel(), - WeightG = PrintParametersSettings.WeightG, - MachineName = MachineName, - MachineNameSize = (uint)MachineName.Length - } - }; - - - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(ZCodexFile)) - { - TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); - ZCodexFile file = new ZCodexFile - { - ResinMetadataSettings = new ZCodexFile.ResinMetadata - { - MaterialId = 2, - Material = MaterialName, - AdditionalSupportLayerTime = 0, - BottomLayersNumber = InitialLayerCount, - BottomLayersTime = (uint)(InitialExposureTime * 1000), - LayerTime = (uint)(LayerExposureTime * 1000), - DisableSettingsChanges = false, - LayerThickness = LayerHeight, - PrintTime = (uint)PrintTime, - TotalLayersCount = LayerCount, - TotalMaterialVolumeUsed = UsedMaterial, - TotalMaterialWeightUsed = UsedMaterial, - }, - UserSettings = new ZCodexFile.UserSettingsdata - { - Printer = MachineName, - BottomLayersCount = InitialLayerCount, - PrintTime = $"{ts.Hours}h {ts.Minutes}m", - LayerExposureTime = (uint)(LayerExposureTime * 1000), - BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), - MaterialId = 2, - LayerThickness = $"{LayerHeight} mm", - AntiAliasing = (byte)(ValidateAntiAliasingLevel() > 1 ? 1 : 0), - CrossSupportEnabled = 1, - ExposureOffTime = (uint) HeaderSettings.LayerOffTime, - HollowEnabled = 0, - HollowThickness = 0, - InfillDensity = 0, - IsAdvanced = 0, - MaterialType = MaterialName, - MaterialVolume = UsedMaterial, - MaxLayer = LayerCount - 1, - ModelLiftEnabled = 0, - ModelLiftHeight = 0, - RaftEnabled = 0, - RaftHeight = 0, - RaftOffset = 0, - SupportAdditionalExposureEnabled = 0, - SupportAdditionalExposureTime = 0, - XCorrection = 0, - YCorrection = 0, - ZLiftDistance = PrintParametersSettings.LiftHeight, - ZLiftFeedRate = PrintParametersSettings.LiftSpeed, - ZLiftRetractRate = PrintParametersSettings.RetractSpeed, - }, - ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata - { - PrintTime = (uint)PrintTime, - PrinterName = MachineName, - Materials = new List - { - new ZCodexFile.ZCodeMetadata.MaterialsData - { - Name = MaterialName, - ExtruderType = "MAIN", - Id = 0, - Usage = 0, - Temperature = 0 - } - }, - }, - LayerManager = LayerManager - }; - - float usedMaterial = UsedMaterial / LayerCount; - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData - { - Layer = layerIndex, - UsedMaterialVolume = usedMaterial - }); - } - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - return true; - } - - return false; - } - #endregion - } -} diff --git a/UVtools.Core/ChituboxZipFile.cs b/UVtools.Core/ChituboxZipFile.cs deleted file mode 100644 index 5fea234..0000000 --- a/UVtools.Core/ChituboxZipFile.cs +++ /dev/null @@ -1,448 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.ComponentModel; -using System.Drawing; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using System.Text; -using Emgu.CV; -using Emgu.CV.CvEnum; -using Emgu.CV.Util; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - public class ChituboxZipFile : FileFormat - { - #region Constants - - public const string GCodeStart = ";START_GCODE_BEGIN{0}" + - "G21 ;Set units to be mm{0}" + - "G90 ;Absolute Positioning{0}" + - "M17 ;Enable motors{0}" + - "G28 Z0 ;Home Z{0}" + - //"G91 ;Relative Positioning{0}" + - "M106 S0 ;Light off{0}" + - ";START_GCODE_END{0}{0}"; - - public const string GCodeEnd = ";END_GCODE_BEGIN{0}" + - "M106 S0 ;Light off{0}" + - "G1 Z{1} F25 ;Raize Z{0}" + - "M18 ;Disable Motors{0}" + - ";END_GCODE_END{0}"; - - #endregion - - #region Sub Classes - - public class Header - { - // ;(****Build and Slicing Parameters****) - [DisplayName("fileName")] public string Filename { get; set; } = string.Empty; - [DisplayName("machineType")] public string MachineType { get; set; } = "Default"; - [DisplayName("estimatedPrintTime")] public float EstimatedPrintTime { get; set; } - [DisplayName("volume")] public float Volume { get; set; } - [DisplayName("resin")] public string Resin { get; set; } = "Normal"; - [DisplayName("weight")] public float Weight { get; set; } - [DisplayName("price")] public float Price { get; set; } - [DisplayName("layerHeight")] public float LayerHeight { get; set; } - [DisplayName("resolutionX")] public uint ResolutionX { get; set; } - [DisplayName("resolutionY")] public uint ResolutionY { get; set; } - [DisplayName("machineX")] public float MachineX { get; set; } - [DisplayName("machineY")] public float MachineY { get; set; } - [DisplayName("machineZ")] public float MachineZ { get; set; } - [DisplayName("projectType")] public string ProjectType { get; set; } = "Normal"; - [DisplayName("normalExposureTime")] public float LayerExposureTime { get; set; } = 7; // 35s - [DisplayName("bottomLayExposureTime")] public float BottomLayExposureTime { get; set; } = 35; // 35s - [DisplayName("bottomLayerExposureTime")] public float BottomLayerExposureTime { get; set; } = 35; // 35s - [DisplayName("normalDropSpeed")] public float RetractSpeed { get; set; } = 150; // 150 mm/m - [DisplayName("normalLayerLiftSpeed")] public float LayerLiftSpeed { get; set; } = 60; // 60 mm/m - [DisplayName("normalLayerLiftHeight")] public float LayerLiftHeight { get; set; } = 5; // 5 mm - [DisplayName("zSlowUpDistance")] public float ZSlowUpDistance { get; set; } - [DisplayName("bottomLayCount")] public ushort BottomLayCount { get; set; } = 4; - [DisplayName("bottomLayerCount")] public ushort BottomLayerCount { get; set; } = 4; - [DisplayName("mirror")] public byte Mirror { get; set; } // 0/1 - [DisplayName("totalLayer")] public uint LayerCount { get; set; } - [DisplayName("bottomLayerLiftHeight")] public float BottomLayerLiftHeight { get; set; } = 5; - [DisplayName("bottomLayerLiftSpeed")] public float BottomLayerLiftSpeed { get; set; } = 60; - [DisplayName("bottomLightOffTime")] public float BottomLightOffTime { get; set; } - [DisplayName("lightOffTime")] public float LayerLightOffTime { get; set; } - [DisplayName("bottomPWMLight")] public byte BottomLightPWM { get; set; } = 255; - [DisplayName("PWMLight")] public byte LayerLightPWM { get; set; } = 255; - [DisplayName("antiAliasLevel")] public byte AntiAliasing { get; set; } = 1; - } - - #endregion - - #region Properties - public Header HeaderSettings { get; } = new Header(); - - public override FileFormatType FileType => FileFormatType.Archive; - - public override FileExtension[] FileExtensions { get; } = { - new FileExtension("zip", "Chitubox Zip Files") - }; - - public override Type[] ConvertToFormats { get; } = null; - - 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(954, 850), new Size(168, 150)}; - - public override uint ResolutionX => HeaderSettings.ResolutionX; - - public override uint ResolutionY => HeaderSettings.ResolutionY; - public override byte AntiAliasing => HeaderSettings.AntiAliasing; - - public override float LayerHeight => HeaderSettings.LayerHeight; - - public override ushort InitialLayerCount => HeaderSettings.BottomLayerCount; - - public override float InitialExposureTime => HeaderSettings.BottomLayerExposureTime; - - public override float LayerExposureTime => HeaderSettings.LayerExposureTime; - - public override float LiftHeight => HeaderSettings.LayerLiftHeight; - - public override float LiftSpeed => HeaderSettings.LayerLiftSpeed; - - public override float RetractSpeed => HeaderSettings.RetractSpeed; - - public override float PrintTime => HeaderSettings.EstimatedPrintTime; - - public override float UsedMaterial => HeaderSettings.Weight; - - public override float MaterialCost => HeaderSettings.Price; - - public override string MaterialName => HeaderSettings.Resin; - - public override string MachineName => HeaderSettings.MachineType; - - public override object[] Configs => new object[] { HeaderSettings }; - #endregion - - #region Methods - - public override void Clear() - { - base.Clear(); - GCode = null; - } - - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - base.Encode(fileFullPath, progress); - using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) - { - for(uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - progress.Token.ThrowIfCancellationRequested(); - Layer layer = this[layerIndex]; - outputFile.PutFileContent($"{layerIndex+1}.png", layer.CompressedBytes, ZipArchiveMode.Create); - progress++; - } - - if (Thumbnails.Length > 0 && !ReferenceEquals(Thumbnails[0], null)) - { - using (Stream stream = outputFile.CreateEntry("preview.png").Open()) - { - var vec = new VectorOfByte(); - CvInvoke.Imencode(".png", Thumbnails[0], vec); - stream.WriteBytes(vec.ToArray()); - stream.Close(); - } - } - - if (Thumbnails.Length > 1 && !ReferenceEquals(Thumbnails[1], null)) - { - using (Stream stream = outputFile.CreateEntry("preview_cropping.png").Open()) - { - var vec = new VectorOfByte(); - CvInvoke.Imencode(".png", Thumbnails[1], vec); - stream.WriteBytes(vec.ToArray()); - stream.Close(); - } - } - - UpdateGCode(); - outputFile.PutFileContent("run.gcode", GCode.ToString(), ZipArchiveMode.Create); - } - } - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - FileFullPath = fileFullPath; - using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) - { - var entry = inputFile.GetEntry("run.gcode"); - if (ReferenceEquals(entry, null)) - { - Clear(); - throw new FileLoadException("run.gcode not found", fileFullPath); - } - - using (TextReader tr = new StreamReader(entry.Open())) - { - string line; - GCode = new StringBuilder(); - while ((line = tr.ReadLine()) != null) - { - GCode.AppendLine(line); - if (string.IsNullOrEmpty(line)) continue; - - if (line[0] != ';') - { - continue; - } - - var splitLine = line.Split(':'); - if (splitLine.Length < 2) continue; - - foreach (var propertyInfo in HeaderSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); - if (ReferenceEquals(displayNameAttribute, null)) continue; - if (!splitLine[0].Trim(' ', ';').Equals(displayNameAttribute.DisplayName)) continue; - Helpers.SetPropertyValue(propertyInfo, HeaderSettings, splitLine[1].Trim()); - } - } - tr.Close(); - } - - - LayerManager = new LayerManager(HeaderSettings.LayerCount); - - progress.ItemCount = LayerCount; - - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - entry = inputFile.GetEntry($"{layerIndex+1}.png"); - if (ReferenceEquals(entry, null)) - { - Clear(); - throw new FileLoadException($"Layer {layerIndex+1} not found", fileFullPath); - } - - LayerManager[layerIndex] = new Layer(layerIndex, entry.Open(), entry.Name); - progress++; - } - - entry = inputFile.GetEntry("preview.png"); - if (!ReferenceEquals(entry, null)) - { - CvInvoke.Imdecode(entry.Open().ToArray(), ImreadModes.AnyColor, Thumbnails[0]); - } - - entry = inputFile.GetEntry("preview_cropping.png"); - if (!ReferenceEquals(entry, null)) - { - CvInvoke.Imdecode(entry.Open().ToArray(), ImreadModes.AnyColor, Thumbnails[CreatedThumbnailsCount]); - } - } - - LayerManager.GetBoundingRectangle(progress); - } - - public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) - { - var baseValue = base.GetValueFromPrintParameterModifier(modifier); - if (!ReferenceEquals(baseValue, null)) return baseValue; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) return HeaderSettings.BottomLightOffTime; - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return HeaderSettings.LayerLightOffTime; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) return HeaderSettings.BottomLayerLiftHeight; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) return HeaderSettings.BottomLayerLiftSpeed; - /*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.LayerLightPWM; - - - - return null; - } - - public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) - { - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - { - HeaderSettings.BottomLayerCount = - HeaderSettings.BottomLayCount = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - { - HeaderSettings.BottomLayerExposureTime = value.Convert(); - UpdateGCode(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - { - HeaderSettings.LayerExposureTime = value.Convert(); - UpdateGCode(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) - { - HeaderSettings.BottomLightOffTime = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) - { - HeaderSettings.LayerLightOffTime = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) - { - HeaderSettings.BottomLayerLiftHeight = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) - { - HeaderSettings.LayerLiftSpeed = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) - { - HeaderSettings.LayerLiftHeight = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) - { - HeaderSettings.LayerLiftSpeed = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) - { - HeaderSettings.RetractSpeed = value.Convert(); - UpdateGCode(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) - { - HeaderSettings.BottomLightPWM = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) - { - HeaderSettings.LayerLightPWM = value.Convert(); - UpdateGCode(); - return true; - } - - return false; - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - if (!string.IsNullOrEmpty(filePath)) - { - File.Copy(FileFullPath, filePath, true); - FileFullPath = filePath; - } - - using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) - { - foreach (var zipentry in outputFile.Entries) - { - if (zipentry.Name.EndsWith(".gcode")) - { - zipentry.Delete(); - break; - } - } - - outputFile.PutFileContent("run.gcode", GCode.ToString(), ZipArchiveMode.Update); - - foreach (var layer in this) - { - if (!layer.IsModified) continue; - outputFile.PutFileContent(layer.Filename, layer.CompressedBytes, ZipArchiveMode.Update); - layer.IsModified = false; - } - } - - //Decode(FileFullPath, progress); - } - - public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) - { - throw new NotImplementedException(); - } - - private void UpdateGCode() - { - string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits"; - GCode = new StringBuilder(); - GCode.AppendLine($"; {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}"); - - foreach (var propertyInfo in HeaderSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); - if (ReferenceEquals(displayNameAttribute, null)) continue; - GCode.AppendLine($";{displayNameAttribute.DisplayName}:{propertyInfo.GetValue(HeaderSettings)}"); - } - - GCode.AppendLine(); - GCode.AppendFormat(GCodeStart, Environment.NewLine); - - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - GCode.AppendLine($";LAYER_START:{layerIndex}"); - GCode.AppendLine($";currPos:{GetHeightFromLayer(layerIndex, false)}"); - GCode.AppendLine($"M6054 \"{layerIndex+1}.png\";show Image"); - GCode.AppendLine($"G0 Z{(layerIndex < InitialLayerCount ? HeaderSettings.BottomLayerLiftHeight : HeaderSettings.LayerLiftHeight) +GetHeightFromLayer(layerIndex, false)} F{(layerIndex < InitialLayerCount ? HeaderSettings.BottomLayerLiftSpeed : HeaderSettings.LayerLiftSpeed)};Z Lift"); - GCode.AppendLine($"G0 Z{GetHeightFromLayer(layerIndex)} F{HeaderSettings.RetractSpeed};Layer position"); - GCode.AppendLine($"G4 P{(layerIndex < InitialLayerCount ? HeaderSettings.BottomLightOffTime : HeaderSettings.LayerLightOffTime)*1000};Before cure delay"); - GCode.AppendLine($"M106 S{(layerIndex < InitialLayerCount ? HeaderSettings.BottomLightPWM : HeaderSettings.LayerLightPWM)};light on"); - GCode.AppendLine($"G4 P{(layerIndex < InitialLayerCount ? HeaderSettings.BottomLayerExposureTime : HeaderSettings.LayerExposureTime) * 1000};Cure time"); - GCode.AppendLine("M106 S0;light off"); - GCode.AppendLine(";LAYER_END"); - GCode.AppendLine(); - } - - GCode.AppendFormat(GCodeEnd, Environment.NewLine, HeaderSettings.MachineZ); - } - #endregion - } -} diff --git a/UVtools.Core/FileExtension.cs b/UVtools.Core/FileExtension.cs deleted file mode 100644 index 7838211..0000000 --- a/UVtools.Core/FileExtension.cs +++ /dev/null @@ -1,90 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System.Collections.Generic; - -namespace UVtools.Core -{ - /// - /// Represents a file extension for slicer file formats - /// - public sealed class FileExtension - { - #region Properties - /// - /// Gets the extension name without the dot (.) - /// - public string Extension { get; } - - /// - /// Gets the extension description - /// - public string Description { get; } - - /// - /// Gets the file filter for open and save dialogs - /// - public string Filter => $@"{Description} (*.{Extension})|*.{Extension}"; - #endregion - - #region Constructor - /// - /// Constructor - /// - /// The extension name without the dot (.) - /// The extension description - public FileExtension(string extension, string description) - { - Extension = extension; - Description = description; - } - #endregion - - #region Overrides - - public override string ToString() - { - return $"{nameof(Extension)}: {Extension}, {nameof(Description)}: {Description}"; - } - - private bool Equals(FileExtension other) - { - return Extension == other.Extension; - } - - public override bool Equals(object obj) - { - return ReferenceEquals(this, obj) || obj is FileExtension other && Equals(other); - } - - public override int GetHashCode() - { - return (Extension != null ? Extension.GetHashCode() : 0); - } - - private sealed class ExtensionEqualityComparer : IEqualityComparer - { - public bool Equals(FileExtension x, FileExtension y) - { - if (ReferenceEquals(x, y)) return true; - if (ReferenceEquals(x, null)) return false; - if (ReferenceEquals(y, null)) return false; - if (x.GetType() != y.GetType()) return false; - return x.Extension == y.Extension; - } - - public int GetHashCode(FileExtension obj) - { - return (obj.Extension != null ? obj.Extension.GetHashCode() : 0); - } - } - - public static IEqualityComparer ExtensionComparer { get; } = new ExtensionEqualityComparer(); - #endregion - } -} diff --git a/UVtools.Core/FileFormat.cs b/UVtools.Core/FileFormat.cs deleted file mode 100644 index 018de42..0000000 --- a/UVtools.Core/FileFormat.cs +++ /dev/null @@ -1,669 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using Emgu.CV; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - /// - /// Slicer representation - /// - public abstract class FileFormat : IFileFormat, IDisposable, IEquatable, IEnumerable - { - #region Enums - - /// - /// Enumeration of file format types - /// - public enum FileFormatType : byte - { - Archive, - Binary - } - - /// - /// Enumeration of file thumbnail size types - /// - public enum FileThumbnailSize : byte - { - Small = 0, - Large - } - #endregion - - #region Sub Classes - /// - /// Available Print Parameters to modify - /// - public class PrintParameterModifier - { - - #region Instances - public static PrintParameterModifier InitialLayerCount { get; } = new PrintParameterModifier("Initial Layer Count", @"Modify 'Initial Layer Count' value", null,0, ushort.MaxValue); - public static PrintParameterModifier InitialExposureSeconds { get; } = new PrintParameterModifier("Initial Exposure Time", @"Modify 'Initial Exposure Time' seconds", "s", 0.1M, byte.MaxValue); - public static PrintParameterModifier ExposureSeconds { get; } = new PrintParameterModifier("Exposure Time", @"Modify 'Exposure Time' seconds", "s", 0.1M, byte.MaxValue); - - public static PrintParameterModifier BottomLayerOffTime { get; } = new PrintParameterModifier("Bottom Layer Off Time", @"Modify 'Bottom Layer Off Time' seconds", "s"); - 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 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); - #endregion - - #region Properties - - /// - /// Gets the name - /// - public string Name { get; } - - /// - /// Gets the description - /// - public string Description { get; } - - /// - /// Gets the value unit - /// - public string ValueUnit { get; } - - /// - /// Gets the minimum value - /// - public decimal Minimum { get; } - - /// - /// Gets the maximum value - /// - public decimal Maximum { get; } - #endregion - - #region Constructor - public PrintParameterModifier(string name, string description, string valueUnit = null, decimal minimum = 0, decimal maximum = 1000) - { - Name = name; - Description = description; - ValueUnit = valueUnit ?? string.Empty; - Minimum = minimum; - Maximum = maximum; - } - #endregion - - #region Overrides - public override string ToString() - { - return $"{nameof(Name)}: {Name}, {nameof(Description)}: {Description}, {nameof(ValueUnit)}: {ValueUnit}, {nameof(Minimum)}: {Minimum}, {nameof(Maximum)}: {Maximum}"; - } - #endregion - } - #endregion - - #region Constants - private const string ExtractConfigFileName = "Configuration"; - private const string ExtractConfigFileExtension = "ini"; - #endregion - - #region Static Methods - /// - /// Gets the available formats to process - /// - public static FileFormat[] AvaliableFormats { get; } = - { - new SL1File(), // Prusa SL1 - new ChituboxZipFile(), // Zip - new ChituboxFile(), // cbddlp, cbt, photon - new PHZFile(), // phz - new PWSFile(), // PSW - new ZCodexFile(), // zcodex - new CWSFile(), // CWS - new ImageFile(), // images - }; - - public static string AllSlicerFiles => AvaliableFormats.Aggregate("All slicer files|", - (current, fileFormat) => current.EndsWith("|") - ? $"{current}{fileFormat.FileFilterExtensionsOnly}" - : $"{current}; {fileFormat.FileFilterExtensionsOnly}"); - - /// - /// Gets all filters for open and save file dialogs - /// - public static string AllFileFilters => - AllSlicerFiles - + - AvaliableFormats.Aggregate(string.Empty, - (current, fileFormat) => $"{current}|" + fileFormat.FileFilter); - - - - /// - /// Gets the count of available file extensions - /// - public static byte FileExtensionsCount - { - get - { - return AvaliableFormats.Aggregate(0, (current, fileFormat) => (byte) (current + fileFormat.FileExtensions.Length)); - } - } - - /// - /// Find by an extension - /// - /// Extension name to find - /// True if is a file path rather than only a extension name - /// True to create a new instance of found file format, otherwise will return a pre created one which should be used for read-only purpose - /// object or null if not found - public static FileFormat FindByExtension(string extension, bool isFilePath = false, bool createNewInstance = false) - { - return (from fileFormat in AvaliableFormats where fileFormat.IsExtensionValid(extension, isFilePath) select createNewInstance ? (FileFormat) Activator.CreateInstance(fileFormat.GetType()) : fileFormat).FirstOrDefault(); - } - - /// - /// Find by an type - /// - /// Type to find - /// True to create a new instance of found file format, otherwise will return a pre created one which should be used for read-only purpose - /// object or null if not found - public static FileFormat FindByType(Type type, bool createNewInstance = false) - { - return (from t in AvaliableFormats where type == t.GetType() select createNewInstance ? (FileFormat) Activator.CreateInstance(type) : t).FirstOrDefault(); - } - #endregion - - #region Properties - - public abstract FileFormatType FileType { get; } - - public abstract FileExtension[] FileExtensions { get; } - public abstract Type[] ConvertToFormats { get; } - - public abstract PrintParameterModifier[] PrintParameterModifiers { get; } - - public string FileFilter { - get - { - var result = string.Empty; - - foreach (var fileExt in FileExtensions) - { - if (!ReferenceEquals(result, string.Empty)) - { - result += '|'; - } - result += fileExt.Filter; - } - - return result; - } - } - - public string FileFilterExtensionsOnly - { - get - { - var result = string.Empty; - - foreach (var fileExt in FileExtensions) - { - if (!ReferenceEquals(result, string.Empty)) - { - result += "; "; - } - result += $"*.{fileExt.Extension}"; - } - - return result; - } - } - - public string FileFullPath { get; set; } - - public abstract byte ThumbnailsCount { get; } - - public byte CreatedThumbnailsCount { - get - { - if (ReferenceEquals(Thumbnails, null)) return 0; - byte count = 0; - - foreach (var thumbnail in Thumbnails) - { - if (ReferenceEquals(thumbnail, null)) continue; - count++; - } - - return count; - } - } - - public abstract Size[] ThumbnailsOriginalSize { get; } - - public Mat[] Thumbnails { get; set; } - public LayerManager LayerManager { get; set; } - - /// - /// Gets if any layer got modified - /// - public bool ModifiedLayers => LayerManager.IsModified; - - public abstract uint ResolutionX { get; } - - public abstract uint ResolutionY { get; } - public bool HaveAntiAliasing => AntiAliasing > 1; - public abstract byte AntiAliasing { get; } - - public abstract float LayerHeight { get; } - - public float TotalHeight => (float)Math.Round(LayerCount * LayerHeight, 2); - - public uint LayerCount => LayerManager?.Count ?? 0; - - public abstract ushort InitialLayerCount { get; } - - public abstract float InitialExposureTime { get; } - - public abstract float LayerExposureTime { get; } - - public abstract float LiftHeight { get; } - - public abstract float RetractSpeed { get; } - - public abstract float LiftSpeed { get; } - - public abstract float PrintTime { get; } - - public abstract float UsedMaterial { get; } - - public abstract float MaterialCost { get; } - - public abstract string MaterialName { get; } - - public abstract string MachineName { get; } - - public StringBuilder GCode { get; set; } - - public abstract object[] Configs { get; } - - public bool IsValid => !ReferenceEquals(FileFullPath, null); - #endregion - - #region Constructor - protected FileFormat() - { - Thumbnails = new Mat[ThumbnailsCount]; - } - #endregion - - #region Indexers - public Layer this[int index] - { - get => LayerManager[index]; - set => LayerManager[index] = value; - } - - public Layer this[uint index] - { - get => LayerManager[index]; - set => LayerManager[index] = value; - } - - public Layer this[long index] - { - get => LayerManager[index]; - set => LayerManager[index] = value; - } - #endregion - - #region Numerators - public IEnumerator GetEnumerator() - { - return ((IEnumerable)LayerManager.Layers).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - #endregion - - #region Overrides - public override bool Equals(object obj) - { - return Equals(obj as FileFormat); - } - - public bool Equals(FileFormat other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return FileFullPath.Equals(other.FileFullPath); - } - - public override int GetHashCode() - { - return (FileFullPath != null ? FileFullPath.GetHashCode() : 0); - } - - public void Dispose() - { - Clear(); - } - - #endregion - - #region Methods - public virtual void Clear() - { - FileFullPath = null; - LayerManager = null; - GCode = null; - - if (!ReferenceEquals(Thumbnails, null)) - { - for (int i = 0; i < ThumbnailsCount; i++) - { - Thumbnails[i]?.Dispose(); - } - } - - - } - - public void FileValidation(string fileFullPath) - { - if (ReferenceEquals(fileFullPath, null)) throw new ArgumentNullException(nameof(FileFullPath), "fullFilePath can't be null."); - if (!File.Exists(fileFullPath)) throw new FileNotFoundException("The specified file does not exists.", fileFullPath); - - if (IsExtensionValid(fileFullPath, true)) - { - return; - } - - throw new FileLoadException($"The specified file is not valid.", fileFullPath); - } - - public bool IsExtensionValid(string extension, bool isFilePath = false) - { - extension = isFilePath ? Path.GetExtension(extension)?.Remove(0, 1) : extension; - return FileExtensions.Any(fileExtension => fileExtension.Extension.Equals(extension, StringComparison.InvariantCultureIgnoreCase)); - } - - 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 Mat 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(Mat[] images) - { - for (var i = 0; i < ThumbnailsCount; i++) - { - Thumbnails[i] = images[Math.Min(i, images.Length - 1)].Clone(); - } - } - - public void SetThumbnails(Mat image) - { - for (var i = 0; i < ThumbnailsCount; i++) - { - Thumbnails[i] = image.Clone(); - } - } - - public virtual void Encode(string fileFullPath, OperationProgress progress = null) - { - FileFullPath = fileFullPath; - - if (File.Exists(fileFullPath)) - { - File.Delete(fileFullPath); - } - - for (var i = 0; i < Thumbnails.Length; i++) - { - if (ReferenceEquals(Thumbnails[i], null)) continue; - CvInvoke.Resize(Thumbnails[i], Thumbnails[i], new Size(ThumbnailsOriginalSize[i].Width, ThumbnailsOriginalSize[i].Height)); - } - - if(ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); - } - - /*public virtual void BeginEncode(string fileFullPath) - { - } - - - public abstract void InsertLayerImageEncode(Image image, uint layerIndex); - - public abstract void EndEncode();*/ - - public virtual void Decode(string fileFullPath, OperationProgress progress = null) - { - Clear(); - FileValidation(fileFullPath); - FileFullPath = fileFullPath; - if(ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.ItemName = OperationProgress.StatusGatherLayers; - } - - public virtual void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true, - OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.ItemName = OperationProgress.StatusExtracting; - /*if (emptyFirst) - { - if (Directory.Exists(path)) - { - DirectoryInfo di = new DirectoryInfo(path); - - foreach (FileInfo file in di.GetFiles()) - { - file.Delete(); - } - foreach (DirectoryInfo dir in di.GetDirectories()) - { - dir.Delete(true); - } - } - }*/ - - //if (!Directory.Exists(path)) - //{ - Directory.CreateDirectory(path); - //} - - - if (FileType == FileFormatType.Archive) - { - - progress.CanCancel = false; - //ZipFile.ExtractToDirectory(FileFullPath, path); - ZipArchiveExtensions.ImprovedExtractToDirectory(FileFullPath, path, ZipArchiveExtensions.Overwrite.Always); - return; - } - - progress.ItemCount = LayerCount; - - if (genericConfigExtract) - { - if (!ReferenceEquals(Configs, null)) - { - using (TextWriter tw = new StreamWriter(Path.Combine(path, $"{ExtractConfigFileName}.{ExtractConfigFileExtension}"), false)) - { - foreach (var config in Configs) - { - var type = config.GetType(); - tw.WriteLine($"[{type.Name}]"); - foreach (var property in type.GetProperties()) - { - tw.WriteLine($"{property.Name} = {property.GetValue(config)}"); - } - - tw.WriteLine(); - } - - tw.Close(); - } - } - } - - if (genericLayersExtract) - { - uint i = 0; - if (!ReferenceEquals(Thumbnails, null)) - { - foreach (var thumbnail in Thumbnails) - { - if (ReferenceEquals(thumbnail, null)) - { - continue; - } - - thumbnail.Save(Path.Combine(path, $"Thumbnail{i}.png")); - i++; - } - } - - if (LayerCount > 0) - { - Parallel.ForEach(this, (layer) => - { - if (progress.Token.IsCancellationRequested) return; - var byteArr = layer.CompressedBytes; - using (FileStream stream = File.Create(Path.Combine(path, $"Layer{layer.Index}.png"), - byteArr.Length)) - { - stream.Write(byteArr, 0, byteArr.Length); - stream.Close(); - lock (progress.Mutex) - { - progress++; - } - } - }); - } - - /* Parallel.For(0, LayerCount, layerIndex => { - var byteArr = this[layerIndex].RawData; - using (FileStream stream = File.Create(Path.Combine(path, $"Layer{layerIndex}.png"), byteArr.Length)) - { - stream.Write(byteArr, 0, byteArr.Length); - stream.Close(); - } - });*/ - /*for (i = 0; i < LayerCount; i++) - { - var byteArr = GetLayer(i); - using (FileStream stream = File.Create(Path.Combine(path, $"Layer{i}.png"), byteArr.Length)) - { - stream.Write(byteArr, 0, byteArr.Length); - stream.Close(); - } - }*/ - } - } - - public virtual float GetHeightFromLayer(uint layerIndex, bool realHeight = true) - { - return (float)Math.Round((layerIndex+(realHeight ? 1 : 0)) * LayerHeight, 2); - } - - public T GetInitialLayerValueOrNormal(uint layerIndex, T initialLayerValue, T normalLayerValue) - { - return layerIndex < InitialLayerCount ? initialLayerValue : normalLayerValue; - } - - public virtual object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) - { - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - return InitialLayerCount; - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - return InitialExposureTime; - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - return LayerExposureTime; - - if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) - return LiftHeight; - if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) - return LiftSpeed; - if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) - return RetractSpeed; - - - - return null; - } - - public virtual bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, object value) - { - return SetValueFromPrintParameterModifier(modifier, value.ToString()); - } - - public abstract bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value); - - public void Save(OperationProgress progress = null) - { - SaveAs(null, progress); - } - - public abstract void SaveAs(string filePath = null, OperationProgress progress = null); - - public abstract bool Convert(Type to, string fileFullPath, OperationProgress progress = null); - public bool Convert(FileFormat to, string fileFullPath, OperationProgress progress = null) - { - return Convert(to.GetType(), fileFullPath, progress); - } - - public byte ValidateAntiAliasingLevel() - { - if (AntiAliasing < 2) return 1; - if(AntiAliasing % 2 != 0) throw new ArgumentException("AntiAliasing must be multiples of 2, otherwise use 0 or 1 to disable it", nameof(AntiAliasing)); - return AntiAliasing; - } - - #endregion - } -} diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs new file mode 100644 index 0000000..f6a1ddb --- /dev/null +++ b/UVtools.Core/FileFormats/CWSFile.cs @@ -0,0 +1,558 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.ComponentModel; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class CWSFile : FileFormat + { + #region Constants + + public const string GCodeStart = "G28 ; Auto Home{0}" + + "G21 ;Set units to be mm{0}" + + "G91 ;Relative Positioning{0}" + + "M17 ;Enable motors{0}" + + " Blank{0}" + + "M106 S0{0}{0}"; + + public const string GCodeEnd = "M106 S0{0}" + + "G1 Z{1}{0}" + + "{0}M18 ;Disable Motors{0}" + + ";{0}"; + + public const string GCodeKeywordSlice = ";"; + public const string GCodeKeywordSliceBlank = "; Blank"; + public const string GCodeKeywordDelay = ";"; + #endregion + + #region Sub Classes + + public class Output + { + // ;(****Build and Slicing Parameters****) + [DisplayName("Pix per mm X")] public float PixPermmX { get; set; } = 19.324f; + [DisplayName("Pix per mm Y")] public float PixPermmY { get; set; } = 19.324f; + [DisplayName("X Resolution")] public ushort XResolution { get; set; } + [DisplayName("Y Resolution")] public ushort YResolution { get; set; } + [DisplayName("Layer Thickness")] public float LayerThickness { get; set; } + [DisplayName("Layer Time")] public uint LayerTime { get; set; } = 5500; + [DisplayName("Render Outlines")] public bool RenderOutlines { get; set; } = false; + [DisplayName("Outline Width Inset")] public ushort OutlineWidthInset { get; set; } = 2; + [DisplayName("Outline Width Outset")] public ushort OutlineWidthOutset { get; set; } = 0; + [DisplayName("Bottom Layers Time")] public uint BottomLayersTime { get; set; } = 35000; + [DisplayName("Number of Bottom Layers")] public ushort NumberBottomLayers { get; set; } = 3; + [DisplayName("Blanking Layer Time")] public uint BlankingLayerTime { get; set; } + [DisplayName("BuildDirection")] public string BuildDirection { get; set; } = "Bottom_Up"; + [DisplayName("Lift Distance")] public float LiftDistance { get; set; } = 4; + [DisplayName("Slide/Tilt Value")] public byte TiltValue { get; set; } + [DisplayName("Use Mainlift GCode Tab")] public bool UseMainliftGCodeTab { get; set; } + [DisplayName("Anti Aliasing")] public bool AntiAliasing { get; set; } = true; + [DisplayName("Anti Aliasing Value")] public float AntiAliasingValue { get; set; } = 2; + [DisplayName("Z Lift Feed Rate")] public float ZLiftFeedRate { get; set; } = 120; + [DisplayName("Z Bottom Lift Feed Rate")] public float ZBottomLiftFeedRate { get; set; } = 120; + [DisplayName("Z Lift Retract Rate")] public float ZLiftRetractRate { get; set; } = 120; + [DisplayName("Flip X")] public bool FlipX { get; set; } + [DisplayName("Flip Y")] public bool FlipY { get; set; } + [DisplayName("Number of Slices")] public uint LayersNum { get; set; } + + // ;(****Machine Configuration ******) + [DisplayName("Platform X Size")] public float PlatformXSize { get; set; } + [DisplayName("Platform Y Size")] public float PlatformYSize { get; set; } + [DisplayName("Platform Z Size")] public float PlatformZSize { get; set; } + [DisplayName("Max X Feedrate")] public ushort MaxXFeedrate { get; set; } = 200; + [DisplayName("Max Y Feedrate")] public ushort MaxYFeedrate { get; set; } = 200; + [DisplayName("Max Z Feedrate")] public ushort MaxZFeedrate { get; set; } = 200; + [DisplayName("Machine Type")] public string MachineType { get; set; } = "UV_LCD"; + + // ;(****UVtools Configuration ******) + [DisplayName("Bottom Layer Light PWM")] public byte BottomLayerLightPWM { get; set; } = 255; + [DisplayName("Layer Light PWM")] public byte LayerLightPWM { get; set; } = 255; + } + + public class Slice + { + [DisplayName("xppm")] public float Xppm { get; set; } = 19.324f; + [DisplayName("yppm")] public float Yppm { get; set; } = 19.324f; + [DisplayName("xres")] public ushort Xres { get; set; } + [DisplayName("yres")] public ushort Yres { get; set; } + [DisplayName("thickness")] public float Thickness { get; set; } + [DisplayName("layers_num")] public uint LayersNum { get; set; } + [DisplayName("head_layers_num")] public ushort HeadLayersNum { get; set; } = 3; + [DisplayName("layers_expo_ms")] public uint LayersExpoMs { get; set; } = 5500; + [DisplayName("head_layers_expo_ms")] public uint HeadLayersExpoMs { get; set; } = 35000; + [DisplayName("wait_before_expo_ms")] public uint WaitBeforeExpoMs { get; set; } = 2000; + [DisplayName("lift_distance")] public float LiftDistance { get; set; } = 4; + [DisplayName("lift_up_speed")] public float LiftUpSpeed { get; set; } = 120; + [DisplayName("lift_down_speed")] public float LiftDownSpeed { get; set; } = 120; + [DisplayName("lift_when_finished")] public byte LiftWhenFinished { get; set; } = 80; + } + + #endregion + + #region Properties + public Slice SliceSettings { get; } = new Slice(); + public Output OutputSettings { get; } = new Output(); + + + public override FileFormatType FileType => FileFormatType.Archive; + + public override FileExtension[] FileExtensions { get; } = { + new FileExtension("cws", "NovaMaker CWS Files") + }; + + public override Type[] ConvertToFormats { get; } = null; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = { + PrintParameterModifier.InitialLayerCount, + PrintParameterModifier.InitialExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + + PrintParameterModifier.LiftHeight, + PrintParameterModifier.RetractSpeed, + PrintParameterModifier.LiftSpeed, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM, + }; + + public override byte ThumbnailsCount { get; } = 0; + + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = null; + + public override uint ResolutionX => SliceSettings.Xres; + + public override uint ResolutionY => SliceSettings.Yres; + public override byte AntiAliasing => (byte) OutputSettings.AntiAliasingValue; + + public override float LayerHeight => SliceSettings.Thickness; + + public override ushort InitialLayerCount => SliceSettings.HeadLayersNum; + + public override float InitialExposureTime => SliceSettings.HeadLayersExpoMs / 1000f; + + public override float LayerExposureTime => SliceSettings.LayersExpoMs / 1000f; + + public override float LiftHeight => SliceSettings.LiftDistance; + + public override float LiftSpeed => SliceSettings.LiftDownSpeed; + + public override float RetractSpeed => OutputSettings.ZLiftRetractRate; + + public override float PrintTime => 0; + + public override float UsedMaterial => 0; + + public override float MaterialCost => 0; + + public override string MaterialName => string.Empty; + + public override string MachineName => "Unknown"; + + public override object[] Configs => new object[] { SliceSettings, OutputSettings}; + #endregion + + #region Methods + + public override void Clear() + { + base.Clear(); + GCode = null; + } + + public override void Encode(string fileFullPath, OperationProgress progress = null) + { + base.Encode(fileFullPath, progress); + using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) + { + string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits"; + var entry = outputFile.CreateEntry("slice.conf"); + var stream = entry.Open(); + + using (TextWriter tw = new StreamWriter(stream)) + { + + tw.WriteLine($"# {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}"); + tw.WriteLine("# conf version 1.0"); + tw.WriteLine(""); + + foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + if (ReferenceEquals(displayNameAttribute, null)) continue; + tw.WriteLine($"{displayNameAttribute.DisplayName.PadRight(24)}= {propertyInfo.GetValue(SliceSettings)}"); + } + } + + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + Layer layer = this[layerIndex]; + var layerImagePath = $"{Path.GetFileNameWithoutExtension(fileFullPath)}{layer.Index.ToString().PadLeft(LayerCount.ToString().Length, '0')}.png"; + outputFile.PutFileContent(layerImagePath, layer.CompressedBytes, ZipArchiveMode.Create); + progress++; + } + + UpdateGCode(); + outputFile.PutFileContent($"{Path.GetFileNameWithoutExtension(fileFullPath)}.gcode", GCode.ToString(), ZipArchiveMode.Create); + } + } + + public override void Decode(string fileFullPath, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + FileFullPath = fileFullPath; + using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) + { + var entry = inputFile.GetEntry("slice.conf"); + if (ReferenceEquals(entry, null)) + { + Clear(); + throw new FileLoadException("slice.conf not found", fileFullPath); + } + + + + using (TextReader tr = new StreamReader(entry.Open())) + { + string line; + while ((line = tr.ReadLine()) != null) + { + if (string.IsNullOrEmpty(line)) continue; + if(line[0] == '#') continue; + + var splitLine = line.Split('='); + if(splitLine.Length < 2) continue; + + foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + if(ReferenceEquals(displayNameAttribute, null)) continue; + if(!splitLine[0].Trim().Equals(displayNameAttribute.DisplayName)) continue; + Helpers.SetPropertyValue(propertyInfo, SliceSettings, splitLine[1].Trim()); + } + } + tr.Close(); + } + + entry = inputFile.GetEntry($"{Path.GetFileNameWithoutExtension(fileFullPath)}.gcode"); + if (ReferenceEquals(entry, null)) + { + Clear(); + throw new FileLoadException($"{Path.GetFileNameWithoutExtension(fileFullPath)}.gcode not found", fileFullPath); + } + + using (TextReader tr = new StreamReader(entry.Open())) + { + string line; + GCode = new StringBuilder(); + while ((line = tr.ReadLine()) != null) + { + GCode.AppendLine(line); + if (string.IsNullOrEmpty(line)) continue; + + if (line[0] != ';') + { + continue; + } + + var splitLine = line.Split('='); + if (splitLine.Length < 2) continue; + + foreach (var propertyInfo in OutputSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + if (ReferenceEquals(displayNameAttribute, null)) continue; + if (!splitLine[0].Trim(' ', ';', '(').Equals(displayNameAttribute.DisplayName)) continue; + Helpers.SetPropertyValue(propertyInfo, OutputSettings, splitLine[1].Trim(' ', ')', 'm', 'n', 's', '/')); + //Debug.WriteLine(splitLine[1].Trim(' ', ')', 'm', 'n', '/')); + } + } + tr.Close(); + } + + + LayerManager = new LayerManager(OutputSettings.LayersNum); + + var gcode = GCode.ToString(); + float currentHeight = 0; + + foreach (var zipArchiveEntry in inputFile.Entries) + { + if (!zipArchiveEntry.Name.EndsWith(".png")) continue; + + // - .png - 4 numbers + int layerSize = OutputSettings.LayersNum.ToString().Length; + string layerStr = zipArchiveEntry.Name.Substring(zipArchiveEntry.Name.Length - 4 - layerSize, layerSize); + uint layerIndex = uint.Parse(layerStr); + + var startStr = $"{GCodeKeywordSlice} {layerIndex}"; + var stripGcode = gcode.Substring(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length); + stripGcode = stripGcode.Substring(0, stripGcode.IndexOf(GCodeKeywordDelay, stripGcode.IndexOf(GCodeKeywordSlice))).Trim(' ', '\n', '\r', '\t'); + //var startCurrPos = stripGcode.Remove(0, ";currPos:".Length); + + /* + * +; 0 +M106 S255 +; 45000 +M106 S0 +; Blank +G1 Z4 F120 +G1 Z-3.9 F120 +; 45000 + */ + + var currPos = Regex.Match(stripGcode, "G1 Z([+-]?([0-9]*[.])?[0-9]+)", RegexOptions.IgnoreCase); + var exposureTime = Regex.Match(stripGcode, "; (\\d+)", RegexOptions.IgnoreCase); + /*var pwm = Regex.Match(stripGcode, "M106 S(\\d+)", RegexOptions.IgnoreCase); + if (layerIndex < InitialLayerCount) + { + OutputSettings.BottomLayerLightPWM = byte.Parse(pwm.Groups[1].Value); + } + else + { + OutputSettings.LayerLightPWM = byte.Parse(pwm.Groups[1].Value); + }*/ + + if (currPos.Success) + { + var nextMatch = currPos.NextMatch(); + if (nextMatch.Success) + { + currentHeight = (float)Math.Round(currentHeight + float.Parse(currPos.Groups[1].Value) + float.Parse(currPos.NextMatch().Groups[1].Value), 2); + } + else + { + currentHeight = (float)Math.Round(currentHeight + float.Parse(currPos.Groups[1].Value), 2); + } + } + + LayerManager[layerIndex] = new Layer(layerIndex, zipArchiveEntry.Open(), zipArchiveEntry.Name) + { + PositionZ = currentHeight, + ExposureTime = float.Parse(exposureTime.Groups[1].Value) / 1000f + }; + } + } + + LayerManager.GetBoundingRectangle(progress); + } + + public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) + { + var baseValue = base.GetValueFromPrintParameterModifier(modifier); + if (!ReferenceEquals(baseValue, null)) return baseValue; + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) return OutputSettings.BottomLayerLightPWM; + if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) return OutputSettings.LayerLightPWM; + + return null; + } + + public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) + { + void UpdateLayers() + { + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + this[layerIndex].ExposureTime = GetInitialLayerValueOrNormal(layerIndex, InitialExposureTime, LayerExposureTime); + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + SliceSettings.HeadLayersNum = + OutputSettings.NumberBottomLayers = value.Convert(); + UpdateLayers(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + SliceSettings.HeadLayersExpoMs = + OutputSettings.BottomLayersTime = value.Convert()*1000; + UpdateLayers(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + SliceSettings.LayersExpoMs = + OutputSettings.LayerTime = value.Convert() * 1000; + UpdateLayers(); + UpdateGCode(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + { + SliceSettings.LiftDistance = + OutputSettings.LiftDistance = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + { + SliceSettings.LiftUpSpeed = + OutputSettings.ZLiftFeedRate = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + { + SliceSettings.LiftDownSpeed = + OutputSettings.ZLiftRetractRate = + OutputSettings.ZBottomLiftFeedRate = value.Convert(); + UpdateGCode(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) + { + OutputSettings.BottomLayerLightPWM = value.Convert(); + UpdateGCode(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) + { + OutputSettings.BottomLayerLightPWM = value.Convert(); + UpdateGCode(); + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + } + + using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) + { + string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits"; + var entry = outputFile.GetPutFile("slice.conf"); + var stream = entry.Open(); + stream.SetLength(0); + + using (TextWriter tw = new StreamWriter(stream)) + { + + tw.WriteLine($"# {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}"); + tw.WriteLine("# conf version 1.0"); + tw.WriteLine(""); + + foreach (var propertyInfo in SliceSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + if (ReferenceEquals(displayNameAttribute, null)) continue; + tw.WriteLine($"{displayNameAttribute.DisplayName.PadRight(24)}= {propertyInfo.GetValue(SliceSettings)}"); + } + } + + + foreach (var zipentry in outputFile.Entries) + { + if (zipentry.Name.EndsWith(".gcode")) + { + zipentry.Delete(); + break; + } + } + outputFile.PutFileContent($"{Path.GetFileNameWithoutExtension(FileFullPath)}.gcode", GCode.ToString(), ZipArchiveMode.Update); + + foreach (var layer in this) + { + if (!layer.IsModified) continue; + outputFile.PutFileContent(layer.Filename, layer.CompressedBytes, ZipArchiveMode.Update); + layer.IsModified = false; + } + } + + //Decode(FileFullPath, progress); + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + throw new NotImplementedException(); + } + + private void UpdateGCode() + { + string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits"; + GCode = new StringBuilder(); + GCode.AppendLine($"; {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}"); + GCode.AppendLine("(****Build and Slicing Parameters * ***)"); + + foreach (var propertyInfo in OutputSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + if (ReferenceEquals(displayNameAttribute, null)) continue; + GCode.AppendLine($";({displayNameAttribute.DisplayName.PadRight(24)} = {propertyInfo.GetValue(OutputSettings)})"); + } + GCode.AppendLine(); + GCode.AppendFormat(GCodeStart, Environment.NewLine); + + float lastZPosition = 0; + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + Layer layer = this[layerIndex]; + GCode.AppendLine($"{GCodeKeywordSlice} {layerIndex}"); + GCode.AppendLine($"M106 S{GetInitialLayerValueOrNormal(layerIndex, OutputSettings.BottomLayerLightPWM, OutputSettings.LayerLightPWM)}"); + GCode.AppendLine($"{GCodeKeywordDelay} {layer.ExposureTime}"); + GCode.AppendLine("M106 S0"); + GCode.AppendLine(GCodeKeywordSliceBlank); + + if (lastZPosition != layer.PositionZ) + { + if (LiftHeight > 0) + { + GCode.AppendLine($"G1 Z{LiftHeight} F{LiftSpeed}"); + GCode.AppendLine($"G1 Z-{LiftHeight - layer.PositionZ + lastZPosition} F{RetractSpeed}"); + } + else + { + GCode.AppendLine($"G1 Z{layer.PositionZ - lastZPosition} F{LiftSpeed}"); + } + } + + GCode.AppendLine($"{GCodeKeywordDelay} {layer.ExposureTime}"); + + lastZPosition = layer.PositionZ; + } + + GCode.AppendFormat(GCodeEnd, Environment.NewLine, SliceSettings.LiftWhenFinished); + + /*GCode = Regex.Replace(GCode, @"Z[+]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", + $"Z{SliceSettings.LiftDistance} F{SliceSettings.LiftUpSpeed}"); + + GCode = Regex.Replace(GCode, @"Z-[-]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", + $"Z-{SliceSettings.LiftDistance - LayerHeight} F{SliceSettings.LiftDownSpeed}");*/ + + } + #endregion + } +} diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs new file mode 100644 index 0000000..2ad10d1 --- /dev/null +++ b/UVtools.Core/FileFormats/ChituboxFile.cs @@ -0,0 +1,1761 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +// 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 System.Linq; +using System.Threading.Tasks; +using BinarySerialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class ChituboxFile : FileFormat + { + #region Constants + private const uint MAGIC_CBDDLP = 0x12FD0019; + private const uint MAGIC_CBT = 0x12FD0086; + private const ushort REPEATRGB15MASK = 0x20; + + private const byte RLE8EncodingLimit = 0x7d; // 125; + private const ushort RLE16EncodingLimit = 0xFFF; + #endregion + + #region Sub Classes + #region Header + public class Header + { + + /// + /// Gets a magic number identifying the file type. + /// 0x12fd_0019 for cbddlp + /// 0x12fd_0086 for ctb + /// + [FieldOrder(0)] public uint Magic { get; set; } + + /// + /// Gets the software version + /// + [FieldOrder(1)] public uint Version { get; set; } = 2; + + /// + /// Gets dimensions of the printer’s X output volume, in millimeters. + /// + [FieldOrder(2)] public float BedSizeX { get; set; } + + /// + /// Gets dimensions of the printer’s Y output volume, in millimeters. + /// + [FieldOrder(3)] public float BedSizeY { get; set; } + + /// + /// Gets dimensions of the printer’s Z output volume, in millimeters. + /// + [FieldOrder(4)] public float BedSizeZ { get; set; } + + [FieldOrder(5)] public uint Unknown1 { get; set; } + [FieldOrder(6)] public uint Unknown2 { get; set; } + + /// + /// Gets the height of the model described by this file, in millimeters. + /// + [FieldOrder(7)] public float OverallHeightMilimeter { get; set; } + + /// + /// Gets the layer height setting used at slicing, in millimeters. Actual height used by the machine is in the layer table. + /// + [FieldOrder(8)] public float LayerHeightMilimeter { get; set; } + + /// + /// 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. + /// + [FieldOrder(9)] public float LayerExposureSeconds { get; set; } + + /// + /// Gets the exposure time setting used at slicing, in seconds, for bottom layers. Actual time used by the machine is in the layer table. + /// + [FieldOrder(10)] public float BottomExposureSeconds { get; set; } + + /// + /// 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. + /// + [FieldOrder(11)] public float LayerOffTime { get; set; } = 1; + + /// + /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig.. + /// + [FieldOrder(12)] public uint BottomLayersCount { get; set; } = 10; + + /// + /// Gets the printer resolution along X axis, in pixels. This information is critical to correctly decoding layer images. + /// + [FieldOrder(13)] public uint ResolutionX { get; set; } + + /// + /// Gets the printer resolution along Y axis, in pixels. This information is critical to correctly decoding layer images. + /// + [FieldOrder(14)] public uint ResolutionY { get; set; } + + /// + /// Gets the file offsets of ImageHeader records describing the larger preview images. + /// + [FieldOrder(15)] public uint PreviewLargeOffsetAddress { get; set; } + + /// + /// Gets the file offset of a table of LayerHeader records giving parameters for each printed layer. + /// + [FieldOrder(16)] public uint LayersDefinitionOffsetAddress { get; set; } + + /// + /// 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. + /// + [FieldOrder(17)] public uint LayerCount { get; set; } + + /// + /// Gets the file offsets of ImageHeader records describing the smaller preview images. + /// + [FieldOrder(18)] public uint PreviewSmallOffsetAddress { get; set; } + + /// + /// Gets the estimated duration of print, in seconds. + /// + [FieldOrder(19)] public uint PrintTime { get; set; } + + /// + /// Gets the records whether this file was generated assuming normal (0) or mirrored (1) image projection. LCD printers are "mirrored" for this purpose. + /// + [FieldOrder(20)] public uint ProjectorType { get; set; } + + /// + /// Gets the print parameters table offset + /// + [FieldOrder(21)] public uint PrintParametersOffsetAddress { get; set; } + + /// + /// Gets the print parameters table size in bytes. + /// + [FieldOrder(22)] public uint PrintParametersSize { get; set; } + + /// + /// 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. + /// + [FieldOrder(23)] public uint AntiAliasLevel { get; set; } = 1; + + /// + /// 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. + /// + [FieldOrder(24)] public ushort LightPWM { get; set; } = 255; + + /// + /// 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. + /// + [FieldOrder(25)] public ushort BottomLightPWM { get; set; } = 255; + + /// + /// Gets the key used to encrypt layer data, or 0 if encryption is not used. + /// + [FieldOrder(26)] public uint EncryptionKey { get; set; } + + /// + /// Gets the slicer tablet offset + /// + [FieldOrder(27)] public uint SlicerOffset { get; set; } + + /// + /// Gets the slicer table size in bytes + /// + [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 + { + /// + /// Gets the distance to lift the build platform away from the vat after bottom layers, in millimeters. + /// + [FieldOrder(0)] public float BottomLiftHeight { get; set; } = 5; + + /// + /// Gets the speed at which to lift the build platform away from the vat after bottom layers, in millimeters per minute. + /// + [FieldOrder(1)] public float BottomLiftSpeed { get; set; } = 300; + + /// + /// Gets the distance to lift the build platform away from the vat after normal layers, in millimeters. + /// + [FieldOrder(2)] public float LiftHeight { get; set; } = 5; + + /// + /// Gets the speed at which to lift the build platform away from the vat after normal layers, in millimeters per minute. + /// + [FieldOrder(3)] public float LiftSpeed { get; set; } = 300; + + /// + /// Gets the speed to use when the build platform re-approaches the vat after lift, in millimeters per minute. + /// + [FieldOrder(4)] public float RetractSpeed { get; set; } = 300; + + /// + /// Gets the estimated required resin, measured in milliliters. The volume number is derived from the model. + /// + [FieldOrder(5)] public float VolumeMl { get; set; } + + /// + /// Gets the estimated grams, derived from volume using configured factors for density. + /// + [FieldOrder(6)] public float WeightG { get; set; } + + /// + /// Gets the estimated cost based on currency unit the user had configured. Derived from volume using configured factors for density and cost. + /// + [FieldOrder(7)] public float CostDollars { get; set; } + + /// + /// 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. + /// + [FieldOrder(8)] public float BottomLightOffDelay { get; set; } = 1; + + /// + /// 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. + /// + [FieldOrder(9)] public float LightOffDelay { get; set; } = 1; + + /// + /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig. + /// + [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(LiftSpeed)}: {LiftSpeed}, {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; } + + /// + /// Gets the machine name offset to a string naming the machine type, and its length in bytes. + /// + [FieldOrder(7)] public uint MachineNameAddress { get; set; } + + /// + /// Gets the machine size in bytes + /// + [FieldOrder(8)] public uint MachineNameSize { get; set; } + + /// + /// Gets the parameter used to control encryption. + /// Not totally understood. 0 for cbddlp files, 0xF for ctb files. + /// + [FieldOrder(9)] public uint EncryptionMode { get; set; } = 8; + + /// + /// 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. + /// + [FieldOrder(10)] public uint MysteriousId { get; set; } + + /// + /// Gets the user-selected antialiasing level. For cbddlp files this will match the level_set_count. For ctb files, this number is essentially arbitrary. + /// + [FieldOrder(11)] public uint AntiAliasLevel { get; set; } = 1; + + /// + /// 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.) + /// + [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; } + + /// + /// 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. + /// + [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 + /// + /// The files contain two preview images. + /// These are shown on the printer display when choosing which file to print, sparing the poor printer from needing to render a 3D image from scratch. + /// + public class Preview + { + /// + /// Gets the X dimension of the preview image, in pixels. + /// + [FieldOrder(0)] public uint ResolutionX { get; set; } + + /// + /// Gets the Y dimension of the preview image, in pixels. + /// + [FieldOrder(1)] public uint ResolutionY { get; set; } + + /// + /// Gets the image offset of the encoded data blob. + /// + [FieldOrder(2)] public uint ImageOffset { get; set; } + + /// + /// Gets the image length in bytes. + /// + [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 Mat Decode(byte[] rawImageData) + { + var image = new Mat(new Size((int) ResolutionX, (int) ResolutionY), DepthType.Cv8U, 3); + var span = image.GetPixelSpan(); + //var image = EmguExtensions.CreateMat(out var bytes, new Size((int) ResolutionX, (int) ResolutionY), 3); + //var image = new MatBytes(new Size((int)ResolutionX, (int)ResolutionY), 3); + //var bytes = image.Bytes; + + int pixel = 0; + for (int n = 0; n < rawImageData.Length; 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++) + { + span[pixel++] = blue; + span[pixel++] = green; + span[pixel++] = red; + //span[pixel++] = new Rgba32(red, green, blue); + } + } + + return image; + } + + 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}"; + } + + public byte[] Encode(Mat image) + { + List rawData = new List(); + ushort color15 = 0; + uint rep = 0; + + var span = image.GetPixelSpan(); + + 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; + } + } + + int pixel = 0; + while (pixel < span.Length) + { + var ncolor15 = + // bgr + (span[pixel++] >> 3) | ((span[pixel++] >> 2) << 5) | ((span[pixel++] >> 3) << 11); + + if (ncolor15 == color15) + { + rep++; + if (rep == RLE16EncodingLimit) + { + RleRGB15(); + rep = 0; + } + } + else + { + RleRGB15(); + color15 = (ushort) ncolor15; + rep = 1; + } + } + + RleRGB15(); + + ImageLength = (uint) rawData.Count; + + return rawData.ToArray(); + } + } + + #endregion + + #region Layer + public class LayerData + { + /// + /// Gets the build platform Z position for this layer, measured in millimeters. + /// + [FieldOrder(0)] public float LayerPositionZ { get; set; } + + /// + /// Gets the exposure time for this layer, in seconds. + /// + [FieldOrder(1)] public float LayerExposure { get; set; } + + /// + /// Gets how long to keep the light off after exposing this layer, in seconds. + /// + [FieldOrder(2)] public float LayerOffTimeSeconds { get; set; } + + /// + /// Gets the layer image offset to encoded layer data, and its length in bytes. + /// + [FieldOrder(3)] public uint DataAddress { get; set; } + + /// + /// Gets the layer image length in bytes. + /// + [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; } + + [Ignore] public byte[] EncodedRle { get; set; } + [Ignore] public ChituboxFile Parent { get; set; } + + public LayerData() + { + } + + public LayerData(ChituboxFile parent, uint layerIndex) + { + Parent = parent; + LayerPositionZ = parent[layerIndex].PositionZ; + LayerExposure = parent[layerIndex].ExposureTime; + + LayerOffTimeSeconds = parent.GetInitialLayerValueOrNormal(layerIndex, + parent.PrintParametersSettings.BottomLightOffDelay, + parent.PrintParametersSettings.LightOffDelay); + + /*LayerExposure = layerIndex < parent.HeaderSettings.BottomLayersCount + ? parent.HeaderSettings.BottomExposureSeconds + : parent.HeaderSettings.LayerExposureSeconds;*/ + } + + public Mat Decode(uint layerIndex, bool consumeData = true) + { + var image = Parent.IsCbtFile ? DecodeCbtImage(layerIndex) : DecodeCbddlpImage(Parent, layerIndex); + + if (consumeData) + EncodedRle = null; + + return image; + } + + public static Mat DecodeCbddlpImage(ChituboxFile parent, uint layerIndex) + { + var image = new Mat(new Size((int)parent.HeaderSettings.ResolutionX, (int)parent.HeaderSettings.ResolutionY), DepthType.Cv8U, 1); + var span = image.GetPixelSpan(); + + for (byte bit = 0; bit < parent.AntiAliasing; bit++) + { + var layer = parent.LayersDefinitions[bit, layerIndex]; + + int n = 0; + for (int index = 0; index < layer.DataSize; index++) + { + // Lower 7 bits is the repeat count for the bit (0..127) + int reps = layer.EncodedRle[index] & 0x7f; + + // We only need to set the non-zero pixels + // High bit is on for white, off for black + if ((layer.EncodedRle[index] & 0x80) != 0) + { + for (int i = 0; i < reps; i++) + { + span[n + i]++; + } + } + + n += reps; + + if (n == span.Length) + { + break; + } + + if (n > span.Length) + { + image.Dispose(); + throw new FileLoadException("Error image ran off the end"); + } + } + } + + for (int i = 0; i < span.Length; i++) + { + int newC = span[i] * (256 / parent.AntiAliasing); + + if (newC > 0) + { + newC--; + } + + span[i] = (byte) newC; + + + } + + return image; + } + + private Mat DecodeCbtImage(uint layerIndex) + { + //Mat image = new Mat(new Size((int)Parent.HeaderSettings.ResolutionX, (int)Parent.HeaderSettings.ResolutionY), DepthType.Cv8U, 1); + //var bytes = image.GetBytes(); + //var image = EmguExtensions.CreateMat(out var bytes, new Size((int)Parent.HeaderSettings.ResolutionX, (int)Parent.HeaderSettings.ResolutionY)); + var image = new Mat(new Size((int)Parent.HeaderSettings.ResolutionX, (int)Parent.HeaderSettings.ResolutionY), DepthType.Cv8U, 1); + var span = image.GetPixelSpan(); + + if (Parent.HeaderSettings.EncryptionKey > 0) + { + KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex); + EncodedRle = kr.Read(EncodedRle); + } + + int pixel = 0; + for (var n = 0; n < EncodedRle.Length; n++) + { + byte code = EncodedRle[n]; + uint stride = 1; + + if ((code & 0x80) == 0x80) // It's a run + { + code &= 0x7f; // Get the run length + n++; + + var slen = EncodedRle[n]; + + if ((slen & 0x80) == 0) + { + stride = slen; + } + else if ((slen & 0xc0) == 0x80) + { + stride = (uint)(((slen & 0x3f) << 8) + EncodedRle[n + 1]); + n++; + } + else if ((slen & 0xe0) == 0xc0) + { + stride = (uint)(((slen & 0x1f) << 16) + (EncodedRle[n + 1] << 8) + EncodedRle[n + 2]); + n += 2; + } + else if ((slen & 0xf0) == 0xe0) + { + stride = (uint)(((slen & 0xf) << 24) + (EncodedRle[n + 1] << 16) + (EncodedRle[n + 2] << 8) + EncodedRle[n + 3]); + + n += 3; + } + else + { + image.Dispose(); + throw new FileLoadException("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 + { + pixel += (int)stride; + continue; + } + + while (stride-- > 0) + { + span[pixel] = code; + pixel++; + } + } + + return image; + } + + public byte[] Encode(Mat image, byte aaIndex, uint layerIndex) + { + return Parent.IsCbtFile ? EncodeCbtImage(image, layerIndex) : EncodeCbddlpImage(image, aaIndex); + } + + public byte[] EncodeCbddlpImage(Mat image, byte bit) + { + List rawData = new List(); + var span = image.GetPixelSpan(); + + bool obit = false; + int rep = 0; + + //ngrey:= uint16(r | g | b) + // thresholds: + // aa 1: 127 + // aa 2: 255 127 + // aa 4: 255 191 127 63 + // aa 8: 255 223 191 159 127 95 63 31 + byte threshold = (byte)(256 / Parent.AntiAliasing * bit - 1); + + void AddRep() + { + if (rep <= 0) return; + + byte by = (byte)rep; + + if (obit) + { + by |= 0x80; + //bitsOn += uint(rep) + } + + rawData.Add(by); + } + + for (int pixel = 0; pixel < span.Length; pixel++) + { + var nbit = span[pixel] >= threshold; + + if (nbit == obit) + { + rep++; + + if (rep == RLE8EncodingLimit) + { + AddRep(); + rep = 0; + } + } + else + { + AddRep(); + obit = nbit; + rep = 1; + } + } + + // Collect stragglers + AddRep(); + + EncodedRle = rawData.ToArray(); + DataSize = (uint) EncodedRle.Length; + + return EncodedRle; + } + + private byte[] EncodeCbtImage(Mat image, uint layerIndex) + { + List rawData = new List(); + byte color = byte.MaxValue >> 1; + uint stride = 0; + var span = image.GetPixelSpan(); + + 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 pixel = 0; pixel < span.Length; pixel++) + { + var grey7 = (byte) (span[pixel] >> 1); + + if (grey7 == color) + { + stride++; + } + else + { + AddRep(); + color = grey7; + stride = 1; + } + } + + AddRep(); + + if (Parent.HeaderSettings.EncryptionKey > 0) + { + KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex); + EncodedRle = kr.Read(rawData.ToArray()); + } + else + { + EncodedRle = rawData.ToArray(); + } + + DataSize = (uint)EncodedRle.Length; + + return EncodedRle; + } + + 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 uint Init { get; } + public uint 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 Read(List input) + { + List data = new List(input.Count); + data.AddRange(input.Select(t => (byte) (t ^ 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 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 LayerData[,] LayersDefinitions { get; private set; } + + public Dictionary LayersHash { get; } = new Dictionary(); + + 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", "Chitubox Photon Files"), + }; + + public override Type[] ConvertToFormats { get; } = + { + typeof(ChituboxFile), + typeof(ChituboxZipFile), + typeof(PWSFile), + typeof(PHZFile), + typeof(ZCodexFile), + typeof(CWSFile), + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = + { + PrintParameterModifier.InitialLayerCount, + PrintParameterModifier.InitialExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + PrintParameterModifier.BottomLayerOffTime, + PrintParameterModifier.LayerOffTime, + PrintParameterModifier.BottomLiftHeight, + PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM, + }; + + public override byte ThumbnailsCount { get; } = 2; + + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(400, 300), new System.Drawing.Size(200, 125)}; + + public override uint ResolutionX => HeaderSettings.ResolutionX; + + public override uint ResolutionY => HeaderSettings.ResolutionY; + public override byte AntiAliasing => (byte) (IsCbtFile ? SlicerInfoSettings.AntiAliasLevel : HeaderSettings.AntiAliasLevel); + + public override float LayerHeight => HeaderSettings.LayerHeightMilimeter; + + 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.LiftSpeed; + 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 IsCbddlpFile => HeaderSettings.Magic == MAGIC_CBDDLP; + 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(); + } + + LayersDefinitions = null; + } + + public override void Encode(string fileFullPath, OperationProgress progress = null) + { + base.Encode(fileFullPath, progress); + LayersHash.Clear(); + + HeaderSettings.Magic = fileFullPath.EndsWith(".ctb") ? MAGIC_CBT : MAGIC_CBDDLP; + HeaderSettings.PrintParametersSize = (uint)Helpers.Serializer.SizeOf(PrintParametersSettings); + + + if (IsCbtFile) + { + SlicerInfoSettings.AntiAliasLevel = HeaderSettings.AntiAliasLevel; + HeaderSettings.AntiAliasLevel = 1; + PrintParametersSettings.Padding4 = 0x1234; + //SlicerInfoSettings.EncryptionMode = 0xf; + SlicerInfoSettings.EncryptionMode = 7; + SlicerInfoSettings.MysteriousId = 0x12345678; + SlicerInfoSettings.Unknown1 = 0x200; + + if (HeaderSettings.EncryptionKey == 0) + { + Random rnd = new Random(); + HeaderSettings.EncryptionKey = (uint)rnd.Next(byte.MaxValue, int.MaxValue); + } + } + else + { + HeaderSettings.EncryptionKey = 0; + } + + uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); + LayersDefinitions = new LayerData[HeaderSettings.AntiAliasLevel, HeaderSettings.LayerCount]; + using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) + { + + outputFile.Seek((int) currentOffset, SeekOrigin.Begin); + + + for (byte i = 0; i < ThumbnailsCount; i++) + { + var image = Thumbnails[i]; + + Preview preview = new Preview + { + ResolutionX = (uint)image.Width, + ResolutionY = (uint)image.Height, + }; + + var previewBytes = preview.Encode(image); + + if (previewBytes.Length == 0) continue; + + if (i == (byte) FileThumbnailSize.Small) + { + HeaderSettings.PreviewSmallOffsetAddress = currentOffset; + } + else + { + HeaderSettings.PreviewLargeOffsetAddress = currentOffset; + } + + + currentOffset += (uint) Helpers.Serializer.SizeOf(preview); + preview.ImageOffset = currentOffset; + + Helpers.SerializeWriteFileStream(outputFile, preview); + currentOffset += outputFile.WriteBytes(previewBytes); + } + + + if (HeaderSettings.Version == 2) + { + HeaderSettings.PrintParametersOffsetAddress = currentOffset; + + currentOffset += Helpers.SerializeWriteFileStream(outputFile, PrintParametersSettings); + + HeaderSettings.SlicerOffset = currentOffset; + HeaderSettings.SlicerSize = (uint) Helpers.Serializer.SizeOf(SlicerInfoSettings) - SlicerInfoSettings.MachineNameSize; + + SlicerInfoSettings.MachineNameAddress = currentOffset + HeaderSettings.SlicerSize; + + + currentOffset += Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + } + + HeaderSettings.LayersDefinitionOffsetAddress = currentOffset; + uint layerDataCurrentOffset = currentOffset + (uint)Helpers.Serializer.SizeOf(new LayerData()) * HeaderSettings.LayerCount * HeaderSettings.AntiAliasLevel; + + progress.ItemCount *= 2 * HeaderSettings.AntiAliasLevel; + + for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + Parallel.For(0, LayerCount, /*new ParallelOptions{MaxDegreeOfParallelism = 1},*/ layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + LayerData layerData = new LayerData(this, (uint) layerIndex); + using (var image = this[layerIndex].LayerMat) + { + layerData.Encode(image, aaIndex, (uint) layerIndex); + LayersDefinitions[aaIndex, layerIndex] = layerData; + } + + lock (progress.Mutex) + { + progress++; + } + }); + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + var layerData = LayersDefinitions[aaIndex, layerIndex]; + LayerData layerDataHash = null; + + if (!IsCbtFile /*&& HeaderSettings.EncryptionKey == 0*/) + { + string hash = Helpers.ComputeSHA1Hash(layerData.EncodedRle); + if (LayersHash.TryGetValue(hash, out layerDataHash)) + { + layerData.DataAddress = layerDataHash.DataAddress; + layerData.DataSize = layerDataHash.DataSize; + } + else + { + LayersHash.Add(hash, layerData); + } + } + + if (ReferenceEquals(layerDataHash, null)) + { + layerData.DataAddress = layerDataCurrentOffset; + + outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin); + layerDataCurrentOffset += outputFile.WriteBytes(layerData.EncodedRle); + } + + outputFile.Seek(currentOffset, SeekOrigin.Begin); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, layerData); + + progress++; + } + } + + outputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + 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, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) + { + //HeaderSettings = Helpers.ByteToType(InputFile); + //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); + HeaderSettings = Helpers.Deserialize
(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; + + progress.Reset(OperationProgress.StatusDecodeThumbnails, ThumbnailsCount); + + 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(inputFile); + + Debug.Write($"Preview {i} -> "); + Debug.WriteLine(Previews[i]); + + inputFile.Seek(Previews[i].ImageOffset, SeekOrigin.Begin); + byte[] rawImageData = new byte[Previews[i].ImageLength]; + inputFile.Read(rawImageData, 0, (int) Previews[i].ImageLength); + + Thumbnails[i] = Previews[i].Decode(rawImageData); + progress++; + } + + //if (HeaderSettings.Version == 2) + //{ + if (HeaderSettings.PrintParametersOffsetAddress > 0) + { + inputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); + PrintParametersSettings = Helpers.Deserialize(inputFile); + Debug.Write("Print Parameters -> "); + Debug.WriteLine(PrintParametersSettings); + + + } + + if (HeaderSettings.SlicerOffset > 0) + { + inputFile.Seek(HeaderSettings.SlicerOffset, SeekOrigin.Begin); + SlicerInfoSettings = Helpers.Deserialize(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}");*/ + //} + + LayersDefinitions = new LayerData[HeaderSettings.AntiAliasLevel, HeaderSettings.LayerCount]; + + uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + + progress.Reset(OperationProgress.StatusGatherLayers, + HeaderSettings.AntiAliasLevel * HeaderSettings.LayerCount); + + 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); + LayerData layerData = Helpers.Deserialize(inputFile); + layerData.Parent = this; + LayersDefinitions[aaIndex, layerIndex] = layerData; + + layerOffset += (uint) Helpers.Serializer.SizeOf(layerData); + //Debug.Write($"LAYER {layerIndex} -> "); + //Debug.WriteLine(layerData); + + layerData.EncodedRle = new byte[layerData.DataSize]; + inputFile.Seek(layerData.DataAddress, SeekOrigin.Begin); + inputFile.Read(layerData.EncodedRle, 0, (int) layerData.DataSize); + progress++; + progress.Token.ThrowIfCancellationRequested(); + } + } + + LayerManager = new LayerManager(HeaderSettings.LayerCount); + + progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) + { + return; + } + + using (var image = LayersDefinitions[0, layerIndex].Decode((uint) layerIndex)) + { + this[layerIndex] = new Layer((uint) layerIndex, image) + { + PositionZ = LayersDefinitions[0, layerIndex].LayerPositionZ, + ExposureTime = LayersDefinitions[0, layerIndex].LayerExposure + }; + + lock (progress.Mutex) + { + progress++; + } + } + }); + } + + progress.Token.ThrowIfCancellationRequested(); + } + + 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 + this[layerIndex].ExposureTime = + LayersDefinitions[aaIndex, layerIndex].LayerExposure = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomExposureSeconds, HeaderSettings.LayerExposureSeconds); + + LayersDefinitions[aaIndex, layerIndex].LayerOffTimeSeconds = GetInitialLayerValueOrNormal(layerIndex, PrintParametersSettings.BottomLightOffDelay, HeaderSettings.LayerOffTime); + } + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + HeaderSettings.BottomLayersCount = + PrintParametersSettings.BottomLayerCount = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + HeaderSettings.BottomExposureSeconds = value.Convert(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + HeaderSettings.LayerExposureSeconds = value.Convert(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) + { + PrintParametersSettings.BottomLightOffDelay = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) + { + HeaderSettings.LayerOffTime = + PrintParametersSettings.LightOffDelay = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) + { + PrintParametersSettings.BottomLiftHeight = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) + { + PrintParametersSettings.BottomLiftSpeed = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + { + PrintParametersSettings.LiftHeight = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + { + PrintParametersSettings.LiftSpeed = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + { + PrintParametersSettings.RetractSpeed = value.Convert(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) + { + HeaderSettings.BottomLightPWM = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) + { + HeaderSettings.LightPWM = value.Convert(); + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (LayerManager.IsModified) + { + if (!string.IsNullOrEmpty(filePath)) + { + FileFullPath = filePath; + } + Encode(FileFullPath, progress); + return; + } + + + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + } + + using (var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) + { + + outputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + if (HeaderSettings.Version == 2 && HeaderSettings.PrintParametersOffsetAddress > 0) + { + outputFile.Seek(HeaderSettings.PrintParametersOffsetAddress, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, PrintParametersSettings); + Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings); + } + + uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) + { + for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + outputFile.Seek(layerOffset, SeekOrigin.Begin); + layerOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinitions[aaIndex, layerIndex]); + } + } + } + + //Decode(FileFullPath, progress); + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + if (to == typeof(ChituboxFile)) + { + if (Path.GetExtension(FileFullPath).Equals(Path.GetExtension(fileFullPath))) + { + return false; + } + ChituboxFile file = new ChituboxFile + { + LayerManager = LayerManager, + HeaderSettings = + { + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + BedSizeX = HeaderSettings.BedSizeX, + BedSizeY = HeaderSettings.BedSizeY, + BedSizeZ = HeaderSettings.BedSizeZ, + ProjectorType = HeaderSettings.ProjectorType, + LayerCount = LayerCount, + AntiAliasLevel = ValidateAntiAliasingLevel(), + BottomLightPWM = (byte) HeaderSettings.BottomLightPWM, + LightPWM = (byte) HeaderSettings.LightPWM, + LayerOffTime = HeaderSettings.LayerOffTime, + PrintTime = HeaderSettings.PrintTime, + BottomExposureSeconds = HeaderSettings.BottomExposureSeconds, + BottomLayersCount = HeaderSettings.BottomLayersCount, + //EncryptionKey = HeaderSettings.EncryptionKey, + LayerExposureSeconds = HeaderSettings.LayerExposureSeconds, + LayerHeightMilimeter = HeaderSettings.LayerHeightMilimeter, + OverallHeightMilimeter = HeaderSettings.OverallHeightMilimeter, + }, + PrintParametersSettings = + { + LiftSpeed = PrintParametersSettings.LiftSpeed, + LiftHeight = PrintParametersSettings.LiftHeight, + BottomLiftSpeed = PrintParametersSettings.BottomLiftSpeed, + RetractSpeed = PrintParametersSettings.RetractSpeed, + BottomLightOffDelay = PrintParametersSettings.BottomLightOffDelay, + LightOffDelay = PrintParametersSettings.BottomLightOffDelay, + BottomLayerCount = PrintParametersSettings.BottomLayerCount, + VolumeMl = PrintParametersSettings.VolumeMl, + BottomLiftHeight = PrintParametersSettings.BottomLiftHeight, + CostDollars = PrintParametersSettings.CostDollars, + WeightG = PrintParametersSettings.WeightG + }, + SlicerInfoSettings = + { + AntiAliasLevel = SlicerInfoSettings.AntiAliasLevel, + MachineName = SlicerInfoSettings.MachineName, + //EncryptionMode = SlicerInfoSettings.EncryptionMode, + MachineNameSize = SlicerInfoSettings.MachineNameSize, + }, + Thumbnails = Thumbnails, + }; + + //file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ChituboxZipFile)) + { + ChituboxZipFile file = new ChituboxZipFile + { + LayerManager = LayerManager, + HeaderSettings = + { + Filename = Path.GetFileName(FileFullPath), + + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + MachineX = HeaderSettings.BedSizeX, + MachineY = HeaderSettings.BedSizeY, + MachineZ = HeaderSettings.BedSizeZ, + MachineType = MachineName, + ProjectType = HeaderSettings.ProjectorType == 0 ? "Normal" : "LCD_mirror", + + Resin = MaterialName, + Price = MaterialCost, + Weight = PrintParametersSettings.WeightG, + Volume = UsedMaterial, + Mirror = (byte) (HeaderSettings.ProjectorType == 0 ? 0 : 1), + + + BottomLayerLiftHeight = PrintParametersSettings.BottomLiftHeight, + LayerLiftHeight = PrintParametersSettings.LiftHeight, + BottomLayerLiftSpeed = PrintParametersSettings.BottomLiftSpeed, + LayerLiftSpeed = PrintParametersSettings.LiftSpeed, + RetractSpeed = PrintParametersSettings.RetractSpeed, + BottomLayCount = InitialLayerCount, + BottomLayerCount = InitialLayerCount, + BottomLightOffTime = PrintParametersSettings.BottomLightOffDelay, + LayerLightOffTime = PrintParametersSettings.LightOffDelay, + BottomLayExposureTime = InitialExposureTime, + BottomLayerExposureTime = InitialExposureTime, + LayerExposureTime = LayerExposureTime, + LayerHeight = LayerHeight, + LayerCount = LayerCount, + AntiAliasing = ValidateAntiAliasingLevel(), + BottomLightPWM = (byte) HeaderSettings.BottomLightPWM, + LayerLightPWM = (byte) HeaderSettings.LightPWM, + + EstimatedPrintTime = PrintTime + }, + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(PWSFile)) + { + PWSFile file = new PWSFile + { + LayerManager = LayerManager, + HeaderSettings = + { + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + LayerHeight = LayerHeight, + LayerExposureTime = LayerExposureTime, + LiftHeight = LiftHeight, + LiftSpeed = LiftSpeed / 60, + RetractSpeed = RetractSpeed / 60, + LayerOffTime = HeaderSettings.LayerOffTime, + BottomLayersCount = InitialLayerCount, + BottomExposureSeconds = InitialExposureTime, + Price = MaterialCost, + Volume = UsedMaterial, + Weight = PrintParametersSettings.WeightG, + AntiAliasing = ValidateAntiAliasingLevel() + } + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(PHZFile)) + { + PHZFile file = new PHZFile + { + LayerManager = LayerManager, + HeaderSettings = + { + Version = 2, + BedSizeX = HeaderSettings.BedSizeX, + BedSizeY = HeaderSettings.BedSizeY, + BedSizeZ = HeaderSettings.BedSizeZ, + OverallHeightMilimeter = TotalHeight, + BottomExposureSeconds = InitialExposureTime, + BottomLayersCount = InitialLayerCount, + BottomLightPWM = HeaderSettings.BottomLightPWM, + LayerCount = LayerCount, + LayerExposureSeconds = LayerExposureTime, + LayerHeightMilimeter = LayerHeight, + LayerOffTime = HeaderSettings.LayerOffTime, + LightPWM = HeaderSettings.LightPWM, + PrintTime = HeaderSettings.PrintTime, + ProjectorType = HeaderSettings.ProjectorType, + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + BottomLayerCount = InitialLayerCount, + BottomLiftHeight = PrintParametersSettings.BottomLiftHeight, + BottomLiftSpeed = PrintParametersSettings.BottomLiftSpeed, + BottomLightOffDelay = PrintParametersSettings.BottomLightOffDelay, + CostDollars = MaterialCost, + LiftHeight = PrintParametersSettings.LiftHeight, + LiftSpeed = PrintParametersSettings.LiftSpeed, + RetractSpeed = PrintParametersSettings.RetractSpeed, + VolumeMl = UsedMaterial, + AntiAliasLevelInfo = ValidateAntiAliasingLevel(), + WeightG = PrintParametersSettings.WeightG, + MachineName = MachineName, + MachineNameSize = (uint)MachineName.Length + } + }; + + + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ZCodexFile)) + { + TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); + ZCodexFile file = new ZCodexFile + { + ResinMetadataSettings = new ZCodexFile.ResinMetadata + { + MaterialId = 2, + Material = MaterialName, + AdditionalSupportLayerTime = 0, + BottomLayersNumber = InitialLayerCount, + BottomLayersTime = (uint)(InitialExposureTime * 1000), + LayerTime = (uint)(LayerExposureTime * 1000), + DisableSettingsChanges = false, + LayerThickness = LayerHeight, + PrintTime = (uint)PrintTime, + TotalLayersCount = LayerCount, + TotalMaterialVolumeUsed = UsedMaterial, + TotalMaterialWeightUsed = UsedMaterial, + }, + UserSettings = new ZCodexFile.UserSettingsdata + { + Printer = MachineName, + BottomLayersCount = InitialLayerCount, + PrintTime = $"{ts.Hours}h {ts.Minutes}m", + LayerExposureTime = (uint)(LayerExposureTime * 1000), + BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), + MaterialId = 2, + LayerThickness = $"{LayerHeight} mm", + AntiAliasing = (byte)(ValidateAntiAliasingLevel() > 1 ? 1 : 0), + CrossSupportEnabled = 1, + ExposureOffTime = (uint) HeaderSettings.LayerOffTime, + HollowEnabled = 0, + HollowThickness = 0, + InfillDensity = 0, + IsAdvanced = 0, + MaterialType = MaterialName, + MaterialVolume = UsedMaterial, + MaxLayer = LayerCount - 1, + ModelLiftEnabled = 0, + ModelLiftHeight = 0, + RaftEnabled = 0, + RaftHeight = 0, + RaftOffset = 0, + SupportAdditionalExposureEnabled = 0, + SupportAdditionalExposureTime = 0, + XCorrection = 0, + YCorrection = 0, + ZLiftDistance = PrintParametersSettings.LiftHeight, + ZLiftFeedRate = PrintParametersSettings.LiftSpeed, + ZLiftRetractRate = PrintParametersSettings.RetractSpeed, + }, + ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata + { + PrintTime = (uint)PrintTime, + PrinterName = MachineName, + Materials = new List + { + new ZCodexFile.ZCodeMetadata.MaterialsData + { + Name = MaterialName, + ExtruderType = "MAIN", + Id = 0, + Usage = 0, + Temperature = 0 + } + }, + }, + LayerManager = LayerManager + }; + + float usedMaterial = UsedMaterial / LayerCount; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData + { + Layer = layerIndex, + UsedMaterialVolume = usedMaterial + }); + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + return true; + } + + if (to == typeof(CWSFile)) + { + CWSFile defaultFormat = (CWSFile)FindByType(typeof(CWSFile)); + CWSFile file = new CWSFile { LayerManager = LayerManager }; + + file.SliceSettings.Xppm = file.OutputSettings.PixPermmX = (float)Math.Round(ResolutionX / HeaderSettings.BedSizeX, 3); + file.SliceSettings.Yppm = file.OutputSettings.PixPermmY = (float)Math.Round(ResolutionY / HeaderSettings.BedSizeY, 3); + file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort)ResolutionX; + file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort)ResolutionY; + file.SliceSettings.Thickness = file.OutputSettings.LayerThickness = LayerHeight; + file.SliceSettings.LayersNum = file.OutputSettings.LayersNum = LayerCount; + file.SliceSettings.HeadLayersNum = file.OutputSettings.NumberBottomLayers = InitialLayerCount; + file.SliceSettings.LayersExpoMs = file.OutputSettings.LayerTime = (uint)LayerExposureTime * 1000; + file.SliceSettings.HeadLayersExpoMs = file.OutputSettings.BottomLayersTime = (uint)InitialExposureTime * 1000; + file.SliceSettings.WaitBeforeExpoMs = (uint)(PrintParametersSettings.LightOffDelay * 1000); + file.SliceSettings.LiftDistance = file.OutputSettings.LiftDistance = LiftHeight; + file.SliceSettings.LiftUpSpeed = file.OutputSettings.ZLiftFeedRate = LiftSpeed; + file.SliceSettings.LiftDownSpeed = file.OutputSettings.ZLiftRetractRate = RetractSpeed; + file.SliceSettings.LiftWhenFinished = defaultFormat.SliceSettings.LiftWhenFinished; + + file.OutputSettings.BlankingLayerTime = (uint) (PrintParametersSettings.LightOffDelay * 1000); + //file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.OutlineWidthInset = 0; + //file.OutputSettings.OutlineWidthOutset = 0; + file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.TiltValue = 0; + //file.OutputSettings.UseMainliftGCodeTab = false; + //file.OutputSettings.AntiAliasing = 0; + //file.OutputSettings.AntiAliasingValue = 0; + file.OutputSettings.FlipX = HeaderSettings.ProjectorType != 0; + file.OutputSettings.FlipY = file.OutputSettings.FlipX; + file.OutputSettings.AntiAliasingValue = ValidateAntiAliasingLevel(); + file.OutputSettings.AntiAliasing = file.OutputSettings.AntiAliasingValue > 1; + + file.Encode(fileFullPath, progress); + + return true; + } + + return false; + } + #endregion + } +} diff --git a/UVtools.Core/FileFormats/ChituboxZipFile.cs b/UVtools.Core/FileFormats/ChituboxZipFile.cs new file mode 100644 index 0000000..07749a7 --- /dev/null +++ b/UVtools.Core/FileFormats/ChituboxZipFile.cs @@ -0,0 +1,512 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.ComponentModel; +using System.Drawing; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Util; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class ChituboxZipFile : FileFormat + { + #region Constants + + public const string GCodeStart = ";START_GCODE_BEGIN{0}" + + "G21 ;Set units to be mm{0}" + + "G90 ;Absolute Positioning{0}" + + "M17 ;Enable motors{0}" + + "G28 Z0 ;Home Z{0}" + + //"G91 ;Relative Positioning{0}" + + "M106 S0 ;Light off{0}" + + ";START_GCODE_END{0}{0}"; + + public const string GCodeEnd = ";END_GCODE_BEGIN{0}" + + "M106 S0 ;Light off{0}" + + "G1 Z{1} F25 ;Raize Z{0}" + + "M18 ;Disable Motors{0}" + + ";END_GCODE_END{0}"; + + #endregion + + #region Sub Classes + + public class Header + { + // ;(****Build and Slicing Parameters****) + [DisplayName("fileName")] public string Filename { get; set; } = string.Empty; + [DisplayName("machineType")] public string MachineType { get; set; } = "Default"; + [DisplayName("estimatedPrintTime")] public float EstimatedPrintTime { get; set; } + [DisplayName("volume")] public float Volume { get; set; } + [DisplayName("resin")] public string Resin { get; set; } = "Normal"; + [DisplayName("weight")] public float Weight { get; set; } + [DisplayName("price")] public float Price { get; set; } + [DisplayName("layerHeight")] public float LayerHeight { get; set; } + [DisplayName("resolutionX")] public uint ResolutionX { get; set; } + [DisplayName("resolutionY")] public uint ResolutionY { get; set; } + [DisplayName("machineX")] public float MachineX { get; set; } + [DisplayName("machineY")] public float MachineY { get; set; } + [DisplayName("machineZ")] public float MachineZ { get; set; } + [DisplayName("projectType")] public string ProjectType { get; set; } = "Normal"; + [DisplayName("normalExposureTime")] public float LayerExposureTime { get; set; } = 7; // 35s + [DisplayName("bottomLayExposureTime")] public float BottomLayExposureTime { get; set; } = 35; // 35s + [DisplayName("bottomLayerExposureTime")] public float BottomLayerExposureTime { get; set; } = 35; // 35s + [DisplayName("normalDropSpeed")] public float RetractSpeed { get; set; } = 150; // 150 mm/m + [DisplayName("normalLayerLiftSpeed")] public float LayerLiftSpeed { get; set; } = 60; // 60 mm/m + [DisplayName("normalLayerLiftHeight")] public float LayerLiftHeight { get; set; } = 5; // 5 mm + [DisplayName("zSlowUpDistance")] public float ZSlowUpDistance { get; set; } + [DisplayName("bottomLayCount")] public ushort BottomLayCount { get; set; } = 4; + [DisplayName("bottomLayerCount")] public ushort BottomLayerCount { get; set; } = 4; + [DisplayName("mirror")] public byte Mirror { get; set; } // 0/1 + [DisplayName("totalLayer")] public uint LayerCount { get; set; } + [DisplayName("bottomLayerLiftHeight")] public float BottomLayerLiftHeight { get; set; } = 5; + [DisplayName("bottomLayerLiftSpeed")] public float BottomLayerLiftSpeed { get; set; } = 60; + [DisplayName("bottomLightOffTime")] public float BottomLightOffTime { get; set; } + [DisplayName("lightOffTime")] public float LayerLightOffTime { get; set; } + [DisplayName("bottomPWMLight")] public byte BottomLightPWM { get; set; } = 255; + [DisplayName("PWMLight")] public byte LayerLightPWM { get; set; } = 255; + [DisplayName("antiAliasLevel")] public byte AntiAliasing { get; set; } = 1; + } + + #endregion + + #region Properties + public Header HeaderSettings { get; } = new Header(); + + public override FileFormatType FileType => FileFormatType.Archive; + + public override FileExtension[] FileExtensions { get; } = { + new FileExtension("zip", "Chitubox Zip Files") + }; + + public override Type[] ConvertToFormats { get; } = null; + + 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(954, 850), new Size(168, 150)}; + + public override uint ResolutionX => HeaderSettings.ResolutionX; + + public override uint ResolutionY => HeaderSettings.ResolutionY; + public override byte AntiAliasing => HeaderSettings.AntiAliasing; + + public override float LayerHeight => HeaderSettings.LayerHeight; + + public override ushort InitialLayerCount => HeaderSettings.BottomLayerCount; + + public override float InitialExposureTime => HeaderSettings.BottomLayerExposureTime; + + public override float LayerExposureTime => HeaderSettings.LayerExposureTime; + + public override float LiftHeight => HeaderSettings.LayerLiftHeight; + + public override float LiftSpeed => HeaderSettings.LayerLiftSpeed; + + public override float RetractSpeed => HeaderSettings.RetractSpeed; + + public override float PrintTime => HeaderSettings.EstimatedPrintTime; + + public override float UsedMaterial => HeaderSettings.Weight; + + public override float MaterialCost => HeaderSettings.Price; + + public override string MaterialName => HeaderSettings.Resin; + + public override string MachineName => HeaderSettings.MachineType; + + public override object[] Configs => new object[] { HeaderSettings }; + #endregion + + #region Methods + + public override void Clear() + { + base.Clear(); + GCode = null; + } + + public override void Encode(string fileFullPath, OperationProgress progress = null) + { + base.Encode(fileFullPath, progress); + using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) + { + for(uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + Layer layer = this[layerIndex]; + outputFile.PutFileContent($"{layerIndex+1}.png", layer.CompressedBytes, ZipArchiveMode.Create); + progress++; + } + + if (Thumbnails.Length > 0 && !ReferenceEquals(Thumbnails[0], null)) + { + using (Stream stream = outputFile.CreateEntry("preview.png").Open()) + { + var vec = new VectorOfByte(); + CvInvoke.Imencode(".png", Thumbnails[0], vec); + stream.WriteBytes(vec.ToArray()); + stream.Close(); + } + } + + if (Thumbnails.Length > 1 && !ReferenceEquals(Thumbnails[1], null)) + { + using (Stream stream = outputFile.CreateEntry("preview_cropping.png").Open()) + { + var vec = new VectorOfByte(); + CvInvoke.Imencode(".png", Thumbnails[1], vec); + stream.WriteBytes(vec.ToArray()); + stream.Close(); + } + } + + UpdateGCode(); + outputFile.PutFileContent("run.gcode", GCode.ToString(), ZipArchiveMode.Create); + } + } + + public override void Decode(string fileFullPath, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + FileFullPath = fileFullPath; + using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) + { + var entry = inputFile.GetEntry("run.gcode"); + if (ReferenceEquals(entry, null)) + { + Clear(); + throw new FileLoadException("run.gcode not found", fileFullPath); + } + + using (TextReader tr = new StreamReader(entry.Open())) + { + string line; + GCode = new StringBuilder(); + while ((line = tr.ReadLine()) != null) + { + GCode.AppendLine(line); + if (string.IsNullOrEmpty(line)) continue; + + if (line[0] != ';') + { + continue; + } + + var splitLine = line.Split(':'); + if (splitLine.Length < 2) continue; + + foreach (var propertyInfo in HeaderSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + if (ReferenceEquals(displayNameAttribute, null)) continue; + if (!splitLine[0].Trim(' ', ';').Equals(displayNameAttribute.DisplayName)) continue; + Helpers.SetPropertyValue(propertyInfo, HeaderSettings, splitLine[1].Trim()); + } + } + tr.Close(); + } + + + LayerManager = new LayerManager(HeaderSettings.LayerCount); + + progress.ItemCount = LayerCount; + + var gcode = GCode.ToString(); + + for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + entry = inputFile.GetEntry($"{layerIndex+1}.png"); + if (ReferenceEquals(entry, null)) + { + Clear(); + throw new FileLoadException($"Layer {layerIndex+1} not found", fileFullPath); + } + + var startStr = $";LAYER_START:{layerIndex}"; + var stripGcode = gcode.Substring(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length); + stripGcode = stripGcode.Substring(0, stripGcode.IndexOf(";LAYER_END")).Trim(' ', '\n', '\r', '\t'); + //var startCurrPos = stripGcode.Remove(0, ";currPos:".Length); + + var currPos = Regex.Match(stripGcode, ";currPos:([+-]?([0-9]*[.])?[0-9]+)", RegexOptions.IgnoreCase); + var exposureTime = Regex.Match(stripGcode, "G4 P(\\d+)", RegexOptions.IgnoreCase); + var pwm = Regex.Match(stripGcode, "M106 S(\\d+)", RegexOptions.IgnoreCase); + if (layerIndex < InitialLayerCount) + { + HeaderSettings.BottomLightPWM = byte.Parse(pwm.Groups[1].Value); + } + else + { + HeaderSettings.LayerLightPWM = byte.Parse(pwm.Groups[1].Value); + } + + LayerManager[layerIndex] = new Layer(layerIndex, entry.Open(), entry.Name) + { + PositionZ = float.Parse(currPos.Groups[1].Value), + ExposureTime = float.Parse(exposureTime.NextMatch().Groups[1].Value) / 1000f + }; + progress++; + } + + entry = inputFile.GetEntry("preview.png"); + if (!ReferenceEquals(entry, null)) + { + CvInvoke.Imdecode(entry.Open().ToArray(), ImreadModes.AnyColor, Thumbnails[0]); + } + + entry = inputFile.GetEntry("preview_cropping.png"); + if (!ReferenceEquals(entry, null)) + { + CvInvoke.Imdecode(entry.Open().ToArray(), ImreadModes.AnyColor, Thumbnails[CreatedThumbnailsCount]); + } + } + + LayerManager.GetBoundingRectangle(progress); + } + + public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) + { + var baseValue = base.GetValueFromPrintParameterModifier(modifier); + if (!ReferenceEquals(baseValue, null)) return baseValue; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) return HeaderSettings.BottomLightOffTime; + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return HeaderSettings.LayerLightOffTime; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) return HeaderSettings.BottomLayerLiftHeight; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) return HeaderSettings.BottomLayerLiftSpeed; + /*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.LayerLightPWM; + + + + return null; + } + + public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) + { + void UpdateLayers() + { + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + this[layerIndex].ExposureTime = GetInitialLayerValueOrNormal(layerIndex, InitialExposureTime, LayerExposureTime); + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + HeaderSettings.BottomLayerCount = + HeaderSettings.BottomLayCount = value.Convert(); + UpdateLayers(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + HeaderSettings.BottomLayerExposureTime = value.Convert(); + UpdateLayers(); + UpdateGCode(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + HeaderSettings.LayerExposureTime = value.Convert(); + UpdateLayers(); + UpdateGCode(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) + { + HeaderSettings.BottomLightOffTime = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) + { + HeaderSettings.LayerLightOffTime = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) + { + HeaderSettings.BottomLayerLiftHeight = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) + { + HeaderSettings.LayerLiftSpeed = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + { + HeaderSettings.LayerLiftHeight = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + { + HeaderSettings.LayerLiftSpeed = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + { + HeaderSettings.RetractSpeed = value.Convert(); + UpdateGCode(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) + { + HeaderSettings.BottomLightPWM = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) + { + HeaderSettings.LayerLightPWM = value.Convert(); + UpdateGCode(); + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + } + + using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) + { + foreach (var zipentry in outputFile.Entries) + { + if (zipentry.Name.EndsWith(".gcode")) + { + zipentry.Delete(); + break; + } + } + + outputFile.PutFileContent("run.gcode", GCode.ToString(), ZipArchiveMode.Update); + + foreach (var layer in this) + { + if (!layer.IsModified) continue; + outputFile.PutFileContent(layer.Filename, layer.CompressedBytes, ZipArchiveMode.Update); + layer.IsModified = false; + } + } + + //Decode(FileFullPath, progress); + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + throw new NotImplementedException(); + } + + private void UpdateGCode() + { + string arch = Environment.Is64BitOperatingSystem ? "64-bits" : "32-bits"; + GCode = new StringBuilder(); + GCode.AppendLine($"; {About.Website} {About.Software} {Assembly.GetExecutingAssembly().GetName().Version} {arch} {DateTime.Now}"); + + foreach (var propertyInfo in HeaderSettings.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + var displayNameAttribute = propertyInfo.GetCustomAttributes(false).OfType().FirstOrDefault(); + if (ReferenceEquals(displayNameAttribute, null)) continue; + GCode.AppendLine($";{displayNameAttribute.DisplayName}:{propertyInfo.GetValue(HeaderSettings)}"); + } + + GCode.AppendLine(); + GCode.AppendFormat(GCodeStart, Environment.NewLine); + + float lastZPosition = 0; + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + var liftHeight = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomLayerLiftHeight, + HeaderSettings.LayerLiftHeight); + + var liftZHeight = liftHeight + this[layerIndex].PositionZ; + + var liftZSpeed = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomLayerLiftSpeed, + HeaderSettings.LayerLiftSpeed); + + var lightOffDelay = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomLightOffTime, + HeaderSettings.LayerLightOffTime) * 1000; + + var pwmValue = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomLightPWM, HeaderSettings.LayerLightPWM); + var exposureTime = GetInitialLayerValueOrNormal(layerIndex, InitialExposureTime, LayerExposureTime) * 1000; + + GCode.AppendLine($";LAYER_START:{layerIndex}"); + GCode.AppendLine($";currPos:{this[layerIndex].PositionZ}"); + GCode.AppendLine($"M6054 \"{layerIndex+1}.png\";show Image"); + + // Absolute gcode + if (liftHeight > 0 && liftZHeight > this[layerIndex].PositionZ) + { + GCode.AppendLine($"G0 Z{liftZHeight} F{liftZSpeed};Z Lift"); + } + + if (lastZPosition < this[layerIndex].PositionZ) + { + GCode.AppendLine($"G0 Z{this[layerIndex].PositionZ} F{HeaderSettings.RetractSpeed};Layer position"); + } + + GCode.AppendLine($"G4 P{lightOffDelay};Before cure delay"); + GCode.AppendLine($"M106 S{pwmValue};light on"); + GCode.AppendLine($"G4 P{exposureTime};Cure time"); + GCode.AppendLine("M106 S0;light off"); + GCode.AppendLine(";LAYER_END"); + GCode.AppendLine(); + + lastZPosition = this[layerIndex].PositionZ; + } + + GCode.AppendFormat(GCodeEnd, Environment.NewLine, HeaderSettings.MachineZ); + } + #endregion + } +} diff --git a/UVtools.Core/FileFormats/FileExtension.cs b/UVtools.Core/FileFormats/FileExtension.cs new file mode 100644 index 0000000..904da8c --- /dev/null +++ b/UVtools.Core/FileFormats/FileExtension.cs @@ -0,0 +1,90 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System.Collections.Generic; + +namespace UVtools.Core.FileFormats +{ + /// + /// Represents a file extension for slicer file formats + /// + public sealed class FileExtension + { + #region Properties + /// + /// Gets the extension name without the dot (.) + /// + public string Extension { get; } + + /// + /// Gets the extension description + /// + public string Description { get; } + + /// + /// Gets the file filter for open and save dialogs + /// + public string Filter => $@"{Description} (*.{Extension})|*.{Extension}"; + #endregion + + #region Constructor + /// + /// Constructor + /// + /// The extension name without the dot (.) + /// The extension description + public FileExtension(string extension, string description) + { + Extension = extension; + Description = description; + } + #endregion + + #region Overrides + + public override string ToString() + { + return $"{nameof(Extension)}: {Extension}, {nameof(Description)}: {Description}"; + } + + private bool Equals(FileExtension other) + { + return Extension == other.Extension; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || obj is FileExtension other && Equals(other); + } + + public override int GetHashCode() + { + return (Extension != null ? Extension.GetHashCode() : 0); + } + + private sealed class ExtensionEqualityComparer : IEqualityComparer + { + public bool Equals(FileExtension x, FileExtension y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + if (x.GetType() != y.GetType()) return false; + return x.Extension == y.Extension; + } + + public int GetHashCode(FileExtension obj) + { + return (obj.Extension != null ? obj.Extension.GetHashCode() : 0); + } + } + + public static IEqualityComparer ExtensionComparer { get; } = new ExtensionEqualityComparer(); + #endregion + } +} diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs new file mode 100644 index 0000000..89235cc --- /dev/null +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -0,0 +1,669 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Emgu.CV; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + /// + /// Slicer representation + /// + public abstract class FileFormat : IFileFormat, IDisposable, IEquatable, IEnumerable + { + #region Enums + + /// + /// Enumeration of file format types + /// + public enum FileFormatType : byte + { + Archive, + Binary + } + + /// + /// Enumeration of file thumbnail size types + /// + public enum FileThumbnailSize : byte + { + Small = 0, + Large + } + #endregion + + #region Sub Classes + /// + /// Available Print Parameters to modify + /// + public class PrintParameterModifier + { + + #region Instances + public static PrintParameterModifier InitialLayerCount { get; } = new PrintParameterModifier("Initial Layer Count", @"Modify 'Initial Layer Count' value", null,0, ushort.MaxValue); + public static PrintParameterModifier InitialExposureSeconds { get; } = new PrintParameterModifier("Initial Exposure Time", @"Modify 'Initial Exposure Time' seconds", "s", 0.1M, byte.MaxValue); + public static PrintParameterModifier ExposureSeconds { get; } = new PrintParameterModifier("Exposure Time", @"Modify 'Exposure Time' seconds", "s", 0.1M, byte.MaxValue); + + public static PrintParameterModifier BottomLayerOffTime { get; } = new PrintParameterModifier("Bottom Layer Off Time", @"Modify 'Bottom Layer Off Time' seconds", "s"); + 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 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); + #endregion + + #region Properties + + /// + /// Gets the name + /// + public string Name { get; } + + /// + /// Gets the description + /// + public string Description { get; } + + /// + /// Gets the value unit + /// + public string ValueUnit { get; } + + /// + /// Gets the minimum value + /// + public decimal Minimum { get; } + + /// + /// Gets the maximum value + /// + public decimal Maximum { get; } + #endregion + + #region Constructor + public PrintParameterModifier(string name, string description, string valueUnit = null, decimal minimum = 0, decimal maximum = 1000) + { + Name = name; + Description = description; + ValueUnit = valueUnit ?? string.Empty; + Minimum = minimum; + Maximum = maximum; + } + #endregion + + #region Overrides + public override string ToString() + { + return $"{nameof(Name)}: {Name}, {nameof(Description)}: {Description}, {nameof(ValueUnit)}: {ValueUnit}, {nameof(Minimum)}: {Minimum}, {nameof(Maximum)}: {Maximum}"; + } + #endregion + } + #endregion + + #region Constants + private const string ExtractConfigFileName = "Configuration"; + private const string ExtractConfigFileExtension = "ini"; + #endregion + + #region Static Methods + /// + /// Gets the available formats to process + /// + public static FileFormat[] AvaliableFormats { get; } = + { + new SL1File(), // Prusa SL1 + new ChituboxZipFile(), // Zip + new ChituboxFile(), // cbddlp, cbt, photon + new PHZFile(), // phz + new PWSFile(), // PSW + new ZCodexFile(), // zcodex + new CWSFile(), // CWS + new ImageFile(), // images + }; + + public static string AllSlicerFiles => AvaliableFormats.Aggregate("All slicer files|", + (current, fileFormat) => current.EndsWith("|") + ? $"{current}{fileFormat.FileFilterExtensionsOnly}" + : $"{current}; {fileFormat.FileFilterExtensionsOnly}"); + + /// + /// Gets all filters for open and save file dialogs + /// + public static string AllFileFilters => + AllSlicerFiles + + + AvaliableFormats.Aggregate(string.Empty, + (current, fileFormat) => $"{current}|" + fileFormat.FileFilter); + + + + /// + /// Gets the count of available file extensions + /// + public static byte FileExtensionsCount + { + get + { + return AvaliableFormats.Aggregate(0, (current, fileFormat) => (byte) (current + fileFormat.FileExtensions.Length)); + } + } + + /// + /// Find by an extension + /// + /// Extension name to find + /// True if is a file path rather than only a extension name + /// True to create a new instance of found file format, otherwise will return a pre created one which should be used for read-only purpose + /// object or null if not found + public static FileFormat FindByExtension(string extension, bool isFilePath = false, bool createNewInstance = false) + { + return (from fileFormat in AvaliableFormats where fileFormat.IsExtensionValid(extension, isFilePath) select createNewInstance ? (FileFormat) Activator.CreateInstance(fileFormat.GetType()) : fileFormat).FirstOrDefault(); + } + + /// + /// Find by an type + /// + /// Type to find + /// True to create a new instance of found file format, otherwise will return a pre created one which should be used for read-only purpose + /// object or null if not found + public static FileFormat FindByType(Type type, bool createNewInstance = false) + { + return (from t in AvaliableFormats where type == t.GetType() select createNewInstance ? (FileFormat) Activator.CreateInstance(type) : t).FirstOrDefault(); + } + #endregion + + #region Properties + + public abstract FileFormatType FileType { get; } + + public abstract FileExtension[] FileExtensions { get; } + public abstract Type[] ConvertToFormats { get; } + + public abstract PrintParameterModifier[] PrintParameterModifiers { get; } + + public string FileFilter { + get + { + var result = string.Empty; + + foreach (var fileExt in FileExtensions) + { + if (!ReferenceEquals(result, string.Empty)) + { + result += '|'; + } + result += fileExt.Filter; + } + + return result; + } + } + + public string FileFilterExtensionsOnly + { + get + { + var result = string.Empty; + + foreach (var fileExt in FileExtensions) + { + if (!ReferenceEquals(result, string.Empty)) + { + result += "; "; + } + result += $"*.{fileExt.Extension}"; + } + + return result; + } + } + + public string FileFullPath { get; set; } + + public abstract byte ThumbnailsCount { get; } + + public byte CreatedThumbnailsCount { + get + { + if (ReferenceEquals(Thumbnails, null)) return 0; + byte count = 0; + + foreach (var thumbnail in Thumbnails) + { + if (ReferenceEquals(thumbnail, null)) continue; + count++; + } + + return count; + } + } + + public abstract Size[] ThumbnailsOriginalSize { get; } + + public Mat[] Thumbnails { get; set; } + public LayerManager LayerManager { get; set; } + + /// + /// Gets if any layer got modified + /// + public bool ModifiedLayers => LayerManager.IsModified; + + public abstract uint ResolutionX { get; } + + public abstract uint ResolutionY { get; } + public bool HaveAntiAliasing => AntiAliasing > 1; + public abstract byte AntiAliasing { get; } + + public abstract float LayerHeight { get; } + + public float TotalHeight => LayerCount == 0 ? 0 : this[LayerCount - 1].PositionZ; //(float)Math.Round(LayerCount * LayerHeight, 2); + + public uint LayerCount => LayerManager?.Count ?? 0; + + public abstract ushort InitialLayerCount { get; } + + public abstract float InitialExposureTime { get; } + + public abstract float LayerExposureTime { get; } + + public abstract float LiftHeight { get; } + + public abstract float RetractSpeed { get; } + + public abstract float LiftSpeed { get; } + + public abstract float PrintTime { get; } + + public abstract float UsedMaterial { get; } + + public abstract float MaterialCost { get; } + + public abstract string MaterialName { get; } + + public abstract string MachineName { get; } + + public StringBuilder GCode { get; set; } + + public abstract object[] Configs { get; } + + public bool IsValid => !ReferenceEquals(FileFullPath, null); + #endregion + + #region Constructor + protected FileFormat() + { + Thumbnails = new Mat[ThumbnailsCount]; + } + #endregion + + #region Indexers + public Layer this[int index] + { + get => LayerManager[index]; + set => LayerManager[index] = value; + } + + public Layer this[uint index] + { + get => LayerManager[index]; + set => LayerManager[index] = value; + } + + public Layer this[long index] + { + get => LayerManager[index]; + set => LayerManager[index] = value; + } + #endregion + + #region Numerators + public IEnumerator GetEnumerator() + { + return ((IEnumerable)LayerManager.Layers).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + #region Overrides + public override bool Equals(object obj) + { + return Equals(obj as FileFormat); + } + + public bool Equals(FileFormat other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return FileFullPath.Equals(other.FileFullPath); + } + + public override int GetHashCode() + { + return (FileFullPath != null ? FileFullPath.GetHashCode() : 0); + } + + public void Dispose() + { + Clear(); + } + + #endregion + + #region Methods + public virtual void Clear() + { + FileFullPath = null; + LayerManager = null; + GCode = null; + + if (!ReferenceEquals(Thumbnails, null)) + { + for (int i = 0; i < ThumbnailsCount; i++) + { + Thumbnails[i]?.Dispose(); + } + } + + + } + + public void FileValidation(string fileFullPath) + { + if (ReferenceEquals(fileFullPath, null)) throw new ArgumentNullException(nameof(FileFullPath), "fullFilePath can't be null."); + if (!File.Exists(fileFullPath)) throw new FileNotFoundException("The specified file does not exists.", fileFullPath); + + if (IsExtensionValid(fileFullPath, true)) + { + return; + } + + throw new FileLoadException($"The specified file is not valid.", fileFullPath); + } + + public bool IsExtensionValid(string extension, bool isFilePath = false) + { + extension = isFilePath ? Path.GetExtension(extension)?.Remove(0, 1) : extension; + return FileExtensions.Any(fileExtension => fileExtension.Extension.Equals(extension, StringComparison.InvariantCultureIgnoreCase)); + } + + 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 Mat 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(Mat[] images) + { + for (var i = 0; i < ThumbnailsCount; i++) + { + Thumbnails[i] = images[Math.Min(i, images.Length - 1)].Clone(); + } + } + + public void SetThumbnails(Mat image) + { + for (var i = 0; i < ThumbnailsCount; i++) + { + Thumbnails[i] = image.Clone(); + } + } + + public virtual void Encode(string fileFullPath, OperationProgress progress = null) + { + FileFullPath = fileFullPath; + + if (File.Exists(fileFullPath)) + { + File.Delete(fileFullPath); + } + + for (var i = 0; i < Thumbnails.Length; i++) + { + if (ReferenceEquals(Thumbnails[i], null)) continue; + CvInvoke.Resize(Thumbnails[i], Thumbnails[i], new Size(ThumbnailsOriginalSize[i].Width, ThumbnailsOriginalSize[i].Height)); + } + + if(ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); + } + + /*public virtual void BeginEncode(string fileFullPath) + { + } + + + public abstract void InsertLayerImageEncode(Image image, uint layerIndex); + + public abstract void EndEncode();*/ + + public virtual void Decode(string fileFullPath, OperationProgress progress = null) + { + Clear(); + FileValidation(fileFullPath); + FileFullPath = fileFullPath; + if(ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.ItemName = OperationProgress.StatusGatherLayers; + } + + public virtual void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true, + OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.ItemName = OperationProgress.StatusExtracting; + /*if (emptyFirst) + { + if (Directory.Exists(path)) + { + DirectoryInfo di = new DirectoryInfo(path); + + foreach (FileInfo file in di.GetFiles()) + { + file.Delete(); + } + foreach (DirectoryInfo dir in di.GetDirectories()) + { + dir.Delete(true); + } + } + }*/ + + //if (!Directory.Exists(path)) + //{ + Directory.CreateDirectory(path); + //} + + + if (FileType == FileFormatType.Archive) + { + + progress.CanCancel = false; + //ZipFile.ExtractToDirectory(FileFullPath, path); + ZipArchiveExtensions.ImprovedExtractToDirectory(FileFullPath, path, ZipArchiveExtensions.Overwrite.Always); + return; + } + + progress.ItemCount = LayerCount; + + if (genericConfigExtract) + { + if (!ReferenceEquals(Configs, null)) + { + using (TextWriter tw = new StreamWriter(Path.Combine(path, $"{ExtractConfigFileName}.{ExtractConfigFileExtension}"), false)) + { + foreach (var config in Configs) + { + var type = config.GetType(); + tw.WriteLine($"[{type.Name}]"); + foreach (var property in type.GetProperties()) + { + tw.WriteLine($"{property.Name} = {property.GetValue(config)}"); + } + + tw.WriteLine(); + } + + tw.Close(); + } + } + } + + if (genericLayersExtract) + { + uint i = 0; + if (!ReferenceEquals(Thumbnails, null)) + { + foreach (var thumbnail in Thumbnails) + { + if (ReferenceEquals(thumbnail, null)) + { + continue; + } + + thumbnail.Save(Path.Combine(path, $"Thumbnail{i}.png")); + i++; + } + } + + if (LayerCount > 0) + { + Parallel.ForEach(this, (layer) => + { + if (progress.Token.IsCancellationRequested) return; + var byteArr = layer.CompressedBytes; + using (FileStream stream = File.Create(Path.Combine(path, $"Layer{layer.Index}.png"), + byteArr.Length)) + { + stream.Write(byteArr, 0, byteArr.Length); + stream.Close(); + lock (progress.Mutex) + { + progress++; + } + } + }); + } + + /* Parallel.For(0, LayerCount, layerIndex => { + var byteArr = this[layerIndex].RawData; + using (FileStream stream = File.Create(Path.Combine(path, $"Layer{layerIndex}.png"), byteArr.Length)) + { + stream.Write(byteArr, 0, byteArr.Length); + stream.Close(); + } + });*/ + /*for (i = 0; i < LayerCount; i++) + { + var byteArr = GetLayer(i); + using (FileStream stream = File.Create(Path.Combine(path, $"Layer{i}.png"), byteArr.Length)) + { + stream.Write(byteArr, 0, byteArr.Length); + stream.Close(); + } + }*/ + } + } + + public virtual float GetHeightFromLayer(uint layerIndex, bool realHeight = true) + { + return (float)Math.Round((layerIndex+(realHeight ? 1 : 0)) * LayerHeight, 2); + } + + public T GetInitialLayerValueOrNormal(uint layerIndex, T initialLayerValue, T normalLayerValue) + { + return layerIndex < InitialLayerCount ? initialLayerValue : normalLayerValue; + } + + public virtual object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) + { + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + return InitialLayerCount; + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + return InitialExposureTime; + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + return LayerExposureTime; + + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + return LiftHeight; + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + return LiftSpeed; + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + return RetractSpeed; + + + + return null; + } + + public virtual bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, object value) + { + return SetValueFromPrintParameterModifier(modifier, value.ToString()); + } + + public abstract bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value); + + public void Save(OperationProgress progress = null) + { + SaveAs(null, progress); + } + + public abstract void SaveAs(string filePath = null, OperationProgress progress = null); + + public abstract bool Convert(Type to, string fileFullPath, OperationProgress progress = null); + public bool Convert(FileFormat to, string fileFullPath, OperationProgress progress = null) + { + return Convert(to.GetType(), fileFullPath, progress); + } + + public byte ValidateAntiAliasingLevel() + { + if (AntiAliasing < 2) return 1; + if(AntiAliasing % 2 != 0) throw new ArgumentException("AntiAliasing must be multiples of 2, otherwise use 0 or 1 to disable it", nameof(AntiAliasing)); + return AntiAliasing; + } + + #endregion + } +} diff --git a/UVtools.Core/FileFormats/IFileFormat.cs b/UVtools.Core/FileFormats/IFileFormat.cs new file mode 100644 index 0000000..8b88602 --- /dev/null +++ b/UVtools.Core/FileFormats/IFileFormat.cs @@ -0,0 +1,355 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Text; +using Emgu.CV; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + /// + /// Slicer file format representation interface + /// + public interface IFileFormat + { + #region Properties + /// + /// Gets the file format type + /// + FileFormat.FileFormatType FileType { get; } + + /// + /// Gets the valid file extensions for this + /// + FileExtension[] FileExtensions { get; } + + /// + /// Gets the implemented file formats able to convert to + /// + Type[] ConvertToFormats { get; } + + /// + /// Gets the available + /// + FileFormat.PrintParameterModifier[] PrintParameterModifiers { get; } + + /// + /// Gets the file filter for open and save dialogs + /// + string FileFilter { get; } + + /// + /// Gets all valid file extensions in "*.extension1;*.extension2" format + /// + + string FileFilterExtensionsOnly { get; } + + /// + /// Gets the input file path loaded into this + /// + string FileFullPath { get; set; } + + /// + /// Gets the thumbnails count present in this file format + /// + byte ThumbnailsCount { get; } + + /// + /// Gets the number of created thumbnails + /// + byte CreatedThumbnailsCount { get; } + + /// + /// Gets the original thumbnail sizes + /// + System.Drawing.Size[] ThumbnailsOriginalSize { get; } + + /// + /// Gets the thumbnails for this + /// + Mat[] Thumbnails { get; set; } + + /// + /// Gets the cached layers into compressed bytes + /// + LayerManager LayerManager { get; set; } + + /// + /// Gets the image width resolution + /// + uint ResolutionX { get; } + + /// + /// Gets the image height resolution + /// + uint ResolutionY { get; } + + bool HaveAntiAliasing { get; } + + /// + /// Gets the AntiAliasing level + /// + byte AntiAliasing { get; } + + /// + /// Gets Layer Height in mm + /// + float LayerHeight { get; } + + /// + /// Gets Total Height in mm + /// + float TotalHeight { get; } + + /// + /// Gets the number of layers present in this file + /// + uint LayerCount { get; } + + /// + /// Gets the number of initial layer count + /// + /// + ushort InitialLayerCount { get; } + + /// + /// Gets the initial exposure time for + /// + float InitialExposureTime { get; } + + /// + /// Gets the normal layer exposure time + /// + float LayerExposureTime { get; } + + /// + /// Gets the speed in mm/min for the detracts + /// + float LiftSpeed { get; } + + /// + /// Gets the height in mm to retract between layers + /// + float LiftHeight { get; } + + /// + /// Gets the speed in mm/min for the retracts + /// + float RetractSpeed { get; } + + /// + /// Gets the estimate print time in seconds + /// + float PrintTime { get; } + + /// + /// Gets the estimate used material in ml + /// + float UsedMaterial { get; } + + /// + /// Gets the estimate material cost + /// + float MaterialCost { get; } + + /// + /// Gets the material name + /// + string MaterialName { get; } + + /// + /// Gets the machine name + /// + string MachineName { get; } + + /// + /// Gets the GCode, returns null if not supported + /// + StringBuilder GCode { get; set; } + + /// + /// Get all configuration objects with properties and values + /// + object[] Configs { get; } + + /// + /// Gets if this file is valid to read + /// + bool IsValid { get; } + + #endregion + + #region Methods + /// + /// Clears all definitions and properties, it also dispose valid candidates + /// + void Clear(); + + /// + /// Validate if a file is a valid + /// + /// Full file path + void FileValidation(string fileFullPath); + + /// + /// Checks if a extension is valid under the + /// + /// Extension to check + /// True if is a full file path, otherwise false for extension only + /// True if valid, otherwise false + bool IsExtensionValid(string extension, bool isFilePath = false); + + /// + /// Gets all valid file extensions in a specified format + /// + + string GetFileExtensions(string prepend = ".", string separator = ", "); + + /// + /// Gets a thumbnail by it height or lower + /// + /// Max height allowed + /// + Mat GetThumbnail(uint maxHeight = 400); + + /// + /// Sets thumbnails from a list of thumbnails and clone them + /// + /// + void SetThumbnails(Mat[] images); + + /// + /// Sets all thumbnails the same image + /// + /// Image to set + void SetThumbnails(Mat images); + + /// + /// Encode to an output file + /// + /// Output file + /// + void Encode(string fileFullPath, OperationProgress progress = null); + + /* + /// + /// Begin encode to an output file + /// + /// Output file + //void BeginEncode(string fileFullPath); + + /// + /// Insert a layer image to be encoded + /// + /// + /// + //void InsertLayerImageEncode(Image image, uint layerIndex); + + /// + /// Finish the encoding procedure + /// + //void EndEncode();*/ + + /// + /// Decode a slicer file + /// + /// + /// + void Decode(string fileFullPath, OperationProgress progress = null); + + /// + /// Extract contents to a folder + /// + /// Path to folder where content will be extracted + /// + /// + /// + void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true, + OperationProgress progress = null); + + /// + /// Get height in mm from layer height + /// + /// + /// + /// The height in mm + float GetHeightFromLayer(uint layerIndex, bool realHeight = true); + + /// + /// Gets the value for initial layer or normal layers based on layer index + /// + /// Type of value + /// Layer index + /// Initial value + /// Normal value + /// + T GetInitialLayerValueOrNormal(uint layerIndex, T initialLayerValue, T normalLayerValue); + + /// + /// Gets the value attributed to + /// + /// Modifier to use + /// A value + object GetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier); + + /// + /// Sets a property value attributed to + /// + /// Modifier to use + /// Value to set + /// True if set, otherwise false = not found + bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, object value); + + /// + /// Sets a property value attributed to + /// + /// Modifier to use + /// Value to set + /// True if set, otherwise false = not found + bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, string value); + + /// + /// Saves current configuration on input file + /// + /// + void Save(OperationProgress progress = null); + + /// + /// Saves current configuration on a copy + /// + /// File path to save copy as, use null to overwrite active file (Same as ) + /// + void SaveAs(string filePath = null, OperationProgress progress = null); + + /// + /// Converts this file type to another file type + /// + /// Target file format + /// Output path file + /// + /// True if convert succeed, otherwise false + bool Convert(Type to, string fileFullPath, OperationProgress progress = null); + + /// + /// Converts this file type to another file type + /// + /// Target file format + /// Output path file + /// + /// True if convert succeed, otherwise false + bool Convert(FileFormat to, string fileFullPath, OperationProgress progress = null); + + /// + /// Validate AntiAlias Level + /// + byte ValidateAntiAliasingLevel(); + + #endregion + } +} diff --git a/UVtools.Core/FileFormats/ImageFile.cs b/UVtools.Core/FileFormats/ImageFile.cs new file mode 100644 index 0000000..5e2761e --- /dev/null +++ b/UVtools.Core/FileFormats/ImageFile.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using Emgu.CV; +using Emgu.CV.CvEnum; +using UVtools.Core.Operations; +using Size = System.Drawing.Size; + +namespace UVtools.Core.FileFormats +{ + public class ImageFile : FileFormat + { + public override FileFormatType FileType { get; } = FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = + { + new FileExtension("jpg", "JPG"), + new FileExtension("jpeg", "JPEG"), + new FileExtension("png", "PNG"), + new FileExtension("bmp", "BMP"), + new FileExtension("gif", "GIF"), + new FileExtension("tga", "TGA"), + }; + + public override Type[] ConvertToFormats { get; } = null; + public override PrintParameterModifier[] PrintParameterModifiers { get; } = null; + public override byte ThumbnailsCount { get; } = 4; + public override Size[] ThumbnailsOriginalSize { get; } = null; + public override uint ResolutionX => (uint)ImageMat.Width; + public override uint ResolutionY => (uint)ImageMat.Height; + public override byte AntiAliasing { get; } = 1; + public override float LayerHeight { get; } = 0; + public override ushort InitialLayerCount { get; } = 1; + public override float InitialExposureTime { get; } = 0; + public override float LayerExposureTime { get; } = 0; + public override float LiftHeight { get; } = 0; + public override float RetractSpeed { get; } = 0; + public override float LiftSpeed { get; } = 0; + public override float PrintTime { get; } = 0; + public override float UsedMaterial { get; } = 0; + public override float MaterialCost { get; } = 0; + public override string MaterialName { get; } = null; + public override string MachineName { get; } = null; + public override object[] Configs { get; } = null; + + private Mat ImageMat { get; set; } + + public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) + { + throw new NotImplementedException(); + } + + public override void Decode(string fileFullPath, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + ImageMat = CvInvoke.Imread(fileFullPath, ImreadModes.AnyColor); + const byte startDivisor = 2; + for (int i = 0; i < ThumbnailsCount; i++) + { + Thumbnails[i] = new Mat(); + var divisor = (i + 1) * startDivisor; + CvInvoke.Resize(ImageMat, Thumbnails[i], + new Size(ImageMat.Width / divisor, ImageMat.Height / divisor)); + } + + if (ImageMat.NumberOfChannels > 1) + { + CvInvoke.CvtColor(ImageMat, ImageMat, ColorConversion.Bgr2Gray); + } + + LayerManager = new LayerManager(1); + this[0] = new Layer(0, ImageMat, Path.GetFileName(fileFullPath)); + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + this[0].LayerMat.Save(filePath ?? FileFullPath); + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + throw new NotImplementedException(); + } + + + } +} diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs new file mode 100644 index 0000000..c921493 --- /dev/null +++ b/UVtools.Core/FileFormats/PHZFile.cs @@ -0,0 +1,1416 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +// 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 System.Linq; +using System.Text; +using System.Threading.Tasks; +using BinarySerialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class PHZFile : FileFormat + { + #region Constants + private const uint MAGIC_PHZ = 0x9FDA83AE; + private const ushort REPEATRGB15MASK = 0x20; + + private const ushort RLE16EncodingLimit = 0x1000; + #endregion + + #region Sub Classes + #region Header + public class Header + { + + /// + /// Gets a magic number identifying the file type. + /// 0x12fd_0019 for cbddlp + /// 0x12fd_0086 for ctb + /// 0x9FDA83AE for phz + /// + [FieldOrder(0)] public uint Magic { get; set; } = MAGIC_PHZ; + + /// + /// Gets the software version + /// + [FieldOrder(1)] public uint Version { get; set; } = 2; + + /// + /// Gets the layer height setting used at slicing, in millimeters. Actual height used by the machine is in the layer table. + /// + [FieldOrder(2)] public float LayerHeightMilimeter { get; set; } + + /// + /// 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. + /// + [FieldOrder(3)] public float LayerExposureSeconds { get; set; } + + /// + /// Gets the exposure time setting used at slicing, in seconds, for bottom layers. Actual time used by the machine is in the layer table. + /// + [FieldOrder(4)] public float BottomExposureSeconds { get; set; } + + /// + /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig.. + /// + [FieldOrder(5)] public uint BottomLayersCount { get; set; } = 10; + + /// + /// Gets the printer resolution along X axis, in pixels. This information is critical to correctly decoding layer images. + /// + [FieldOrder(6)] public uint ResolutionX { get; set; } + + /// + /// Gets the printer resolution along Y axis, in pixels. This information is critical to correctly decoding layer images. + /// + [FieldOrder(7)] public uint ResolutionY { get; set; } + + /// + /// Gets the file offsets of ImageHeader records describing the larger preview images. + /// + [FieldOrder(8)] public uint PreviewLargeOffsetAddress { get; set; } + + /// + /// Gets the file offset of a table of LayerHeader records giving parameters for each printed layer. + /// + [FieldOrder(9)] public uint LayersDefinitionOffsetAddress { get; set; } + + /// + /// 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. + /// + [FieldOrder(10)] public uint LayerCount { get; set; } + + /// + /// Gets the file offsets of ImageHeader records describing the smaller preview images. + /// + [FieldOrder(11)] public uint PreviewSmallOffsetAddress { get; set; } + + /// + /// Gets the estimated duration of print, in seconds. + /// + [FieldOrder(12)] public uint PrintTime { get; set; } + + /// + /// Gets the records whether this file was generated assuming normal (0) or mirrored (1) image projection. LCD printers are "mirrored" for this purpose. + /// + [FieldOrder(13)] public uint ProjectorType { get; set; } + + /// + /// 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. + /// + [FieldOrder(14)] public uint AntiAliasLevel { get; set; } = 1; + + /// + /// 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. + /// + [FieldOrder(15)] public ushort LightPWM { get; set; } = 255; + + /// + /// 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. + /// + [FieldOrder(16)] public ushort BottomLightPWM { get; set; } = 255; + + [FieldOrder(17)] public uint Padding1 { get; set; } + [FieldOrder(18)] public uint Padding2 { get; set; } + + /// + /// Gets the height of the model described by this file, in millimeters. + /// + [FieldOrder(19)] public float OverallHeightMilimeter { get; set; } + + /// + /// Gets dimensions of the printer’s X output volume, in millimeters. + /// + [FieldOrder(20)] public float BedSizeX { get; set; } + + /// + /// Gets dimensions of the printer’s Y output volume, in millimeters. + /// + [FieldOrder(21)] public float BedSizeY { get; set; } + + /// + /// Gets dimensions of the printer’s Z output volume, in millimeters. + /// + [FieldOrder(22)] public float BedSizeZ { get; set; } + + /// + /// Gets the key used to encrypt layer data, or 0 if encryption is not used. + /// + [FieldOrder(23)] public uint EncryptionKey { get; set; } + + /// + /// 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. + /// + [FieldOrder(24)] public float BottomLightOffDelay { get; set; } = 1; + + /// + /// 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. + /// + [FieldOrder(25)] public float LayerOffTime { get; set; } = 1; + + /// + /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig. + /// + [FieldOrder(26)] public uint BottomLayerCount { get; set; } = 10; + + [FieldOrder(27)] public uint Padding3 { get; set; } + + /// + /// Gets the distance to lift the build platform away from the vat after bottom layers, in millimeters. + /// + [FieldOrder(28)] public float BottomLiftHeight { get; set; } = 5; + + /// + /// Gets the speed at which to lift the build platform away from the vat after bottom layers, in millimeters per minute. + /// + [FieldOrder(29)] public float BottomLiftSpeed { get; set; } = 300; + + /// + /// Gets the distance to lift the build platform away from the vat after normal layers, in millimeters. + /// + [FieldOrder(30)] public float LiftHeight { get; set; } = 5; + + /// + /// Gets the speed at which to lift the build platform away from the vat after normal layers, in millimeters per minute. + /// + [FieldOrder(31)] public float LiftSpeed { get; set; } = 300; + + /// + /// Gets the speed to use when the build platform re-approaches the vat after lift, in millimeters per minute. + /// + [FieldOrder(32)] public float RetractSpeed { get; set; } = 300; + + /// + /// Gets the estimated required resin, measured in milliliters. The volume number is derived from the model. + /// + [FieldOrder(33)] public float VolumeMl { get; set; } + + /// + /// Gets the estimated grams, derived from volume using configured factors for density. + /// + [FieldOrder(34)] public float WeightG { get; set; } + + /// + /// Gets the estimated cost based on currency unit the user had configured. Derived from volume using configured factors for density and cost. + /// + [FieldOrder(35)] public float CostDollars { get; set; } + + [FieldOrder(36)] public uint Padding4 { get; set; } + + /// + /// Gets the machine name offset to a string naming the machine type, and its length in bytes. + /// + [FieldOrder(37)] public uint MachineNameAddress { get; set; } + + /// + /// Gets the machine size in bytes + /// + [FieldOrder(38)] public uint MachineNameSize { get; set; } + + /// + /// 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. + /// + [Ignore] public string MachineName { get; set; } + + [FieldOrder(39)] public uint Padding5 { get; set; } + [FieldOrder(40)] public uint Padding6 { get; set; } + [FieldOrder(41)] public uint Padding7 { get; set; } + [FieldOrder(42)] public uint Padding8 { get; set; } + [FieldOrder(43)] public uint Padding9 { get; set; } + [FieldOrder(44)] public uint Padding10 { get; set; } + + /// + /// Gets the parameter used to control encryption. + /// Not totally understood. 0 for cbddlp files, 0xF for ctb files, 0x1c for phz + /// + [FieldOrder(45)] public uint EncryptionMode { get; set; } = 28; + + /// + /// 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. + /// + [FieldOrder(46)] public uint MysteriousId { get; set; } + + /// + /// 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. + /// + [FieldOrder(47)] public uint AntiAliasLevelInfo { get; set; } + + [FieldOrder(48)] public uint SoftwareVersion { get; set; } = 0x01060300; + + [FieldOrder(49)] public uint Padding11 { get; set; } + [FieldOrder(50)] public uint Padding12 { get; set; } + [FieldOrder(51)] public uint Padding13 { get; set; } + [FieldOrder(52)] public uint Padding14 { get; set; } + [FieldOrder(53)] public uint Padding15 { get; set; } + [FieldOrder(54)] public uint Padding16{ get; set; } + + public override string ToString() + { + return $"{nameof(Magic)}: {Magic}, {nameof(Version)}: {Version}, {nameof(LayerHeightMilimeter)}: {LayerHeightMilimeter}, {nameof(LayerExposureSeconds)}: {LayerExposureSeconds}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {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(AntiAliasLevel)}: {AntiAliasLevel}, {nameof(LightPWM)}: {LightPWM}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(OverallHeightMilimeter)}: {OverallHeightMilimeter}, {nameof(BedSizeX)}: {BedSizeX}, {nameof(BedSizeY)}: {BedSizeY}, {nameof(BedSizeZ)}: {BedSizeZ}, {nameof(EncryptionKey)}: {EncryptionKey}, {nameof(BottomLightOffDelay)}: {BottomLightOffDelay}, {nameof(LayerOffTime)}: {LayerOffTime}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(Padding3)}: {Padding3}, {nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(VolumeMl)}: {VolumeMl}, {nameof(WeightG)}: {WeightG}, {nameof(CostDollars)}: {CostDollars}, {nameof(Padding4)}: {Padding4}, {nameof(MachineNameAddress)}: {MachineNameAddress}, {nameof(MachineNameSize)}: {MachineNameSize}, {nameof(MachineName)}: {MachineName}, {nameof(Padding5)}: {Padding5}, {nameof(Padding6)}: {Padding6}, {nameof(Padding7)}: {Padding7}, {nameof(Padding8)}: {Padding8}, {nameof(Padding9)}: {Padding9}, {nameof(Padding10)}: {Padding10}, {nameof(EncryptionMode)}: {EncryptionMode}, {nameof(MysteriousId)}: {MysteriousId}, {nameof(AntiAliasLevelInfo)}: {AntiAliasLevelInfo}, {nameof(SoftwareVersion)}: {SoftwareVersion}, {nameof(Padding11)}: {Padding11}, {nameof(Padding12)}: {Padding12}, {nameof(Padding13)}: {Padding13}, {nameof(Padding14)}: {Padding14}, {nameof(Padding15)}: {Padding15}, {nameof(Padding16)}: {Padding16}"; + } + } + #endregion + + #region Preview + /// + /// The files contain two preview images. + /// These are shown on the printer display when choosing which file to print, sparing the poor printer from needing to render a 3D image from scratch. + /// + public class Preview + { + /// + /// Gets the X dimension of the preview image, in pixels. + /// + [FieldOrder(0)] public uint ResolutionX { get; set; } + + /// + /// Gets the Y dimension of the preview image, in pixels. + /// + [FieldOrder(1)] public uint ResolutionY { get; set; } + + /// + /// Gets the image offset of the encoded data blob. + /// + [FieldOrder(2)] public uint ImageOffset { get; set; } + + /// + /// Gets the image length in bytes. + /// + [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 Mat Decode(byte[] rawImageData) + { + var image = new Mat(new Size((int)ResolutionX, (int)ResolutionY), DepthType.Cv8U, 3); + var span = image.GetPixelSpan(); + + + int pixel = 0; + for (uint n = 0; n < 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++) + { + span[pixel++] = blue; + span[pixel++] = green; + span[pixel++] = red; + //span[pixel] = new Rgba32(red, green, blue, byte.MaxValue); + } + } + + return image; + } + + public static byte[] Encode(Mat image) + { + List rawData = new List(); + var span = image.GetPixelSpan(); + 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 (int pixel = 0; pixel < span.Length; pixel += image.NumberOfChannels) + { + var ncolor15 = + (span[pixel] >> 3) + | ((span[pixel+1] >> 2) << 5) + | ((span[pixel+2] >> 3) << 11); + + if (ncolor15 == color15) + { + rep++; + if (rep == RLE16EncodingLimit) + { + RleRGB15(); + rep = 0; + } + } + else + { + RleRGB15(); + color15 = (ushort) ncolor15; + rep = 1; + } + } + + RleRGB15(); + + return rawData.ToArray(); + } + + 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 LayerData + { + /// + /// Gets the build platform Z position for this layer, measured in millimeters. + /// + [FieldOrder(0)] public float LayerPositionZ { get; set; } + + /// + /// Gets the exposure time for this layer, in seconds. + /// + [FieldOrder(1)] public float LayerExposure { get; set; } + + /// + /// Gets how long to keep the light off after exposing this layer, in seconds. + /// + [FieldOrder(2)] public float LayerOffTimeSeconds { get; set; } + + /// + /// Gets the layer image offset to encoded layer data, and its length in bytes. + /// + [FieldOrder(3)] public uint DataAddress { get; set; } + + /// + /// Gets the layer image length in bytes. + /// + [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; } + + [Ignore] public byte[] EncodedRle { get; set; } + + [Ignore] public PHZFile Parent { get; set; } + + public LayerData() + { + } + + public LayerData(PHZFile parent, uint layerIndex) + { + Parent = parent; + LayerPositionZ = parent[layerIndex].PositionZ; + LayerExposure = parent[layerIndex].ExposureTime; + + LayerOffTimeSeconds = parent.GetInitialLayerValueOrNormal(layerIndex, + parent.HeaderSettings.BottomLightOffDelay, + parent.HeaderSettings.LayerOffTime); + } + + public Mat Decode(uint layerIndex, bool consumeData = true) + { + var image = new Mat(new Size((int)Parent.ResolutionX, (int)Parent.ResolutionY), DepthType.Cv8U, 1); + var span = image.GetPixelSpan(); + + if (Parent.HeaderSettings.EncryptionKey > 0) + { + KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex); + EncodedRle = kr.Read(EncodedRle); + } + + int limit = image.Width * image.Height; + int index = 0; + byte lastColor = 0; + + foreach (var code in EncodedRle) + { + if ((code & 0x80) == 0x80) + { + //lastColor = (byte) (code << 1); + // // Convert from 7bpp to 8bpp (extending the last bit) + lastColor = (byte)(((code & 0x7f) << 1) | (code & 1)); + if (lastColor >= 0xfc) + { + // Make 'white' actually white + lastColor = 0xff; + + } + + if (index < limit) + { + span[index] = lastColor; + } + else + { + image.Dispose(); + throw new FileLoadException("Corrupted RLE data."); + } + + index++; + } + else + { + for (uint i = 0; i < code; i++) + { + if (index < limit) + { + span[index] = lastColor; + } + else + { + image.Dispose(); + throw new FileLoadException("Corrupted RLE data."); + } + index++; + } + } + } + + if (consumeData) + EncodedRle = null; + + return image; + } + + public void Encode(Mat image, uint layerIndex) + { + List rawData = new List(); + + //byte color = byte.MaxValue >> 1; + byte color = byte.MaxValue; + uint stride = 0; + + void AddRep() + { + rawData.Add((byte)(color | 0x80)); + stride--; + int done = 0; + while (done < stride) + { + int todo = 0x7d; + + if (stride - done < todo) + { + todo = (int)(stride - done); + } + + rawData.Add((byte)(todo)); + + done += todo; + } + } + + int halfWidth = image.Width / 2; + + //int pixel = 0; + for (int y = 0; y < image.Height; y++) + { + var span = image.GetPixelRowSpan(y); + for (int x = 0; x < span.Length; x++) + { + + var grey7 = (byte)((span[x] >> 1) & 0x7f); + if (grey7 > 0x7c) + { + grey7 = 0x7c; + } + + if (color == byte.MaxValue) + { + color = grey7; + stride = 1; + } + else if (grey7 != color || x == halfWidth) + { + AddRep(); + color = grey7; + stride = 1; + } + else + { + stride++; + } + } + + AddRep(); + color = byte.MaxValue; + } + + + if (Parent.HeaderSettings.EncryptionKey > 0) + { + KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex); + EncodedRle = kr.Read(rawData).ToArray(); + } + else + { + EncodedRle = rawData.ToArray(); + } + + DataSize = (uint) EncodedRle.Length; + } + + 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 uint Init { get; } + public uint Key { get; private set; } + public uint Index { get; private set; } + + public KeyRing(uint seed, uint layerIndex) + { + seed %= 0x4324; + Init = seed * 0x34a32231; + Key = (layerIndex ^ 0x3fad2212) * seed * 0x4910913d; + } + + public byte Next() + { + byte k = (byte)(Key >> (int)(8 * Index)); + Index++; + + if ((Index & 3) == 0) + { + Key += Init; + Index = 0; + } + + return k; + } + + public List Read(List input) + { + List data = new List(input.Count); + data.AddRange(input.Select(t => (byte)(t ^ 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 Header HeaderSettings { get; protected internal set; } = new Header(); + + public Preview[] Previews { get; protected internal set; } + + public LayerData[] LayersDefinitions { get; private set; } + + public Dictionary LayersHash { get; } = new Dictionary(); + + public override FileFormatType FileType => FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = { + new FileExtension("phz", "Chitubox PHZ Files"), + }; + + public override Type[] ConvertToFormats { get; } = + { + typeof(ChituboxFile), + typeof(ChituboxZipFile), + typeof(PWSFile), + typeof(ZCodexFile), + typeof(CWSFile), + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = + { + PrintParameterModifier.InitialLayerCount, + PrintParameterModifier.InitialExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + PrintParameterModifier.BottomLayerOffTime, + PrintParameterModifier.LayerOffTime, + PrintParameterModifier.BottomLiftHeight, + PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + + PrintParameterModifier.BottomLightPWM, + PrintParameterModifier.LightPWM, + }; + + public override byte ThumbnailsCount { get; } = 2; + + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(400, 300), new System.Drawing.Size(200, 125)}; + + public override uint ResolutionX => HeaderSettings.ResolutionX; + + public override uint ResolutionY => HeaderSettings.ResolutionY; + public override byte AntiAliasing => (byte) HeaderSettings.AntiAliasLevelInfo; + + public override float LayerHeight => HeaderSettings.LayerHeightMilimeter; + + public override ushort InitialLayerCount => (ushort)HeaderSettings.BottomLayersCount; + + public override float InitialExposureTime => HeaderSettings.BottomExposureSeconds; + + public override float LayerExposureTime => HeaderSettings.LayerExposureSeconds; + public override float LiftHeight => HeaderSettings.LiftHeight; + public override float LiftSpeed => HeaderSettings.LiftSpeed; + public override float RetractSpeed => HeaderSettings.RetractSpeed; + + public override float PrintTime => HeaderSettings.PrintTime; + + public override float UsedMaterial => (float) Math.Round(HeaderSettings.VolumeMl, 2); + + public override float MaterialCost => (float) Math.Round(HeaderSettings.CostDollars, 2); + + public override string MaterialName => "Unknown"; + public override string MachineName => HeaderSettings.MachineName; + + public override object[] Configs => new object[] { HeaderSettings }; + + #endregion + + #region Constructors + public PHZFile() + { + Previews = new Preview[ThumbnailsCount]; + } + #endregion + + #region Methods + public override void Clear() + { + base.Clear(); + + for (byte i = 0; i < ThumbnailsCount; i++) + { + Previews[i] = new Preview(); + } + + LayersDefinitions = null; + } + + public override void Encode(string fileFullPath, OperationProgress progress = null) + { + base.Encode(fileFullPath, progress); + LayersHash.Clear(); + + /*if (HeaderSettings.EncryptionKey == 0) + { + Random rnd = new Random(); + HeaderSettings.EncryptionKey = (uint)rnd.Next(short.MaxValue, int.MaxValue); + }*/ + + + uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); + LayersDefinitions = new LayerData[HeaderSettings.LayerCount]; + using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) + { + outputFile.Seek((int) currentOffset, SeekOrigin.Begin); + + for (byte i = 0; i < ThumbnailsCount; i++) + { + var image = Thumbnails[i]; + + var bytes = Preview.Encode(image); + + if (bytes.Length == 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)bytes.Length, + }; + + currentOffset += (uint) Helpers.Serializer.SizeOf(preview); + preview.ImageOffset = currentOffset; + + Helpers.SerializeWriteFileStream(outputFile, preview); + + currentOffset += (uint)bytes.Length; + outputFile.WriteBytes(bytes); + } + + if (HeaderSettings.MachineNameSize > 0) + { + HeaderSettings.MachineNameAddress = currentOffset; + var machineBytes = Encoding.ASCII.GetBytes(HeaderSettings.MachineName); + outputFile.Write(machineBytes, 0, machineBytes.Length); + currentOffset += (uint)machineBytes.Length; + } + + Parallel.For(0, LayerCount, /*new ParallelOptions{MaxDegreeOfParallelism = 1},*/ layerIndex => + { + if(progress.Token.IsCancellationRequested) return; + LayerData layer = new LayerData(this, (uint) layerIndex); + using (var image = this[layerIndex].LayerMat) + { + layer.Encode(image, (uint) layerIndex); + LayersDefinitions[layerIndex] = layer; + } + + lock (progress.Mutex) + { + progress++; + } + }); + + progress.Reset(OperationProgress.StatusWritingFile, LayerCount); + + HeaderSettings.LayersDefinitionOffsetAddress = currentOffset; + uint layerDataCurrentOffset = currentOffset + (uint) Helpers.Serializer.SizeOf(LayersDefinitions[0]) * LayerCount; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + LayerData layerData = LayersDefinitions[layerIndex]; + LayerData layerDataHash = null; + + if (HeaderSettings.EncryptionKey == 0) + { + string hash = Helpers.ComputeSHA1Hash(layerData.EncodedRle); + if (LayersHash.TryGetValue(hash, out layerDataHash)) + { + layerData.DataAddress = layerDataHash.DataAddress; + layerData.DataSize = layerDataHash.DataSize; + } + else + { + LayersHash.Add(hash, layerData); + } + } + + if (ReferenceEquals(layerDataHash, null)) + { + layerData.DataAddress = layerDataCurrentOffset; + + outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin); + layerDataCurrentOffset += outputFile.WriteBytes(layerData.EncodedRle); + } + + + LayersDefinitions[layerIndex] = layerData; + + outputFile.Seek(currentOffset, SeekOrigin.Begin); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, layerData); + progress++; + } + + outputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + Debug.WriteLine("Encode Results:"); + Debug.WriteLine(HeaderSettings); + Debug.WriteLine(Previews[0]); + Debug.WriteLine(Previews[1]); + Debug.WriteLine("-End-"); + } + } + + public override void Decode(string fileFullPath, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) + { + + //HeaderSettings = Helpers.ByteToType(InputFile); + //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); + HeaderSettings = Helpers.Deserialize
(inputFile); + if (HeaderSettings.Magic != MAGIC_PHZ) + { + throw new FileLoadException("Not a valid PHZ file!", fileFullPath); + } + + HeaderSettings.AntiAliasLevel = 1; + + FileFullPath = fileFullPath; + + + progress.Reset(OperationProgress.StatusDecodeThumbnails, ThumbnailsCount); + 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(inputFile); + + Debug.Write($"Preview {i} -> "); + Debug.WriteLine(Previews[i]); + + inputFile.Seek(Previews[i].ImageOffset, SeekOrigin.Begin); + byte[] rawImageData = new byte[Previews[i].ImageLength]; + inputFile.Read(rawImageData, 0, (int) Previews[i].ImageLength); + + Thumbnails[i] = Previews[i].Decode(rawImageData); + progress++; + } + + if (HeaderSettings.MachineNameAddress > 0 && HeaderSettings.MachineNameSize > 0) + { + inputFile.Seek(HeaderSettings.MachineNameAddress, SeekOrigin.Begin); + byte[] buffer = new byte[HeaderSettings.MachineNameSize]; + inputFile.Read(buffer, 0, (int) HeaderSettings.MachineNameSize); + HeaderSettings.MachineName = Encoding.ASCII.GetString(buffer); + } + + + LayersDefinitions = new LayerData[HeaderSettings.LayerCount]; + + uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + + progress.Reset(OperationProgress.StatusGatherLayers, HeaderSettings.LayerCount); + + for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + inputFile.Seek(layerOffset, SeekOrigin.Begin); + LayerData layerData = Helpers.Deserialize(inputFile); + layerData.Parent = this; + LayersDefinitions[layerIndex] = layerData; + + layerOffset += (uint) Helpers.Serializer.SizeOf(layerData); + Debug.Write($"LAYER {layerIndex} -> "); + Debug.WriteLine(layerData); + + layerData.EncodedRle = new byte[layerData.DataSize]; + inputFile.Seek(layerData.DataAddress, SeekOrigin.Begin); + inputFile.Read(layerData.EncodedRle, 0, (int) layerData.DataSize); + + progress++; + progress.Token.ThrowIfCancellationRequested(); + } + + LayerManager = new LayerManager(HeaderSettings.LayerCount); + + progress.Reset(OperationProgress.StatusDecodeLayers, HeaderSettings.LayerCount); + + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) + { + return; + } + + using (var image = LayersDefinitions[layerIndex].Decode((uint) layerIndex, true)) + { + this[layerIndex] = new Layer((uint) layerIndex, image) + { + PositionZ = LayersDefinitions[layerIndex].LayerPositionZ, + ExposureTime = LayersDefinitions[layerIndex].LayerExposure + }; + } + + lock (progress.Mutex) + { + progress++; + } + }); + } + + progress.Token.ThrowIfCancellationRequested(); + } + + public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) + { + var baseValue = base.GetValueFromPrintParameterModifier(modifier); + if (!ReferenceEquals(baseValue, null)) return baseValue; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) return HeaderSettings.BottomLightOffDelay; + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return HeaderSettings.LayerOffTime; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) return HeaderSettings.BottomLiftHeight; + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) return HeaderSettings.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 (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + // Bottom : others + this[layerIndex].ExposureTime = + LayersDefinitions[layerIndex].LayerExposure = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomExposureSeconds, HeaderSettings.LayerExposureSeconds); + + LayersDefinitions[layerIndex].LayerOffTimeSeconds = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomLightOffDelay, HeaderSettings.LayerOffTime); + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + HeaderSettings.BottomLayersCount = + HeaderSettings.BottomLayerCount = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + HeaderSettings.BottomExposureSeconds = value.Convert(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + HeaderSettings.LayerExposureSeconds = value.Convert(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) + { + HeaderSettings.BottomLightOffDelay = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) + { + HeaderSettings.LayerOffTime = + HeaderSettings.LayerOffTime = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) + { + HeaderSettings.BottomLiftHeight = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) + { + HeaderSettings.BottomLiftSpeed = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + { + HeaderSettings.LiftHeight = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + { + HeaderSettings.LiftSpeed = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + { + HeaderSettings.RetractSpeed = value.Convert(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) + { + HeaderSettings.BottomLightPWM = value.Convert(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) + { + HeaderSettings.LightPWM = value.Convert(); + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (LayerManager.IsModified) + { + if (!string.IsNullOrEmpty(filePath)) + { + FileFullPath = filePath; + } + Encode(FileFullPath, progress); + return; + } + + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + } + + using (var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) + { + outputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + /*if (HeaderSettings.MachineNameAddress > 0 && HeaderSettings.MachineNameSize > 0) + { + outputFile.Seek(HeaderSettings.MachineNameAddress, SeekOrigin.Begin); + byte[] buffer = new byte[HeaderSettings.MachineNameSize]; + outputFile.Write(Encoding.ASCII.GetBytes(HeaderSettings.MachineName), 0, (int)HeaderSettings.MachineNameSize); + }*/ + + uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) + { + outputFile.Seek(layerOffset, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, LayersDefinitions[layerIndex]); + layerOffset += (uint)Helpers.Serializer.SizeOf(LayersDefinitions[layerIndex]); + } + } + + //Decode(FileFullPath, progress); + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + if (to == typeof(ChituboxFile)) + { + ChituboxFile file = new ChituboxFile + { + LayerManager = LayerManager, + HeaderSettings + = + { + Version = 2, + BedSizeX = HeaderSettings.BedSizeX, + BedSizeY = HeaderSettings.BedSizeY, + BedSizeZ = HeaderSettings.BedSizeZ, + OverallHeightMilimeter = TotalHeight, + BottomExposureSeconds = InitialExposureTime, + BottomLayersCount = InitialLayerCount, + BottomLightPWM = HeaderSettings.BottomLightPWM, + LayerCount = LayerCount, + LayerExposureSeconds = LayerExposureTime, + LayerHeightMilimeter = LayerHeight, + LayerOffTime = HeaderSettings.LayerOffTime, + LightPWM = HeaderSettings.LightPWM, + PrintTime = HeaderSettings.PrintTime, + ProjectorType = HeaderSettings.ProjectorType, + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + AntiAliasLevel = ValidateAntiAliasingLevel() + }, + PrintParametersSettings = + { + BottomLayerCount = InitialLayerCount, + BottomLiftHeight = HeaderSettings.BottomLiftHeight, + BottomLiftSpeed = HeaderSettings.BottomLiftSpeed, + BottomLightOffDelay = HeaderSettings.BottomLightOffDelay, + CostDollars = MaterialCost, + LiftHeight = HeaderSettings.LiftHeight, + LiftSpeed = HeaderSettings.LiftSpeed, + LightOffDelay = HeaderSettings.LayerOffTime, + RetractSpeed = HeaderSettings.RetractSpeed, + VolumeMl = UsedMaterial, + WeightG = HeaderSettings.WeightG + }, + SlicerInfoSettings = {MachineName = MachineName, MachineNameSize = (uint) MachineName.Length} + }; + + + + + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ChituboxZipFile)) + { + ChituboxZipFile file = new ChituboxZipFile + { + LayerManager = LayerManager, + HeaderSettings = + { + Filename = Path.GetFileName(FileFullPath), + + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + MachineX = HeaderSettings.BedSizeX, + MachineY = HeaderSettings.BedSizeY, + MachineZ = HeaderSettings.BedSizeZ, + MachineType = MachineName, + ProjectType = HeaderSettings.ProjectorType == 0 ? "Normal" : "LCD_mirror", + + Resin = MaterialName, + Price = MaterialCost, + Weight = HeaderSettings.WeightG, + Volume = UsedMaterial, + Mirror = (byte) (HeaderSettings.ProjectorType == 0 ? 0 : 1), + + + BottomLayerLiftHeight = HeaderSettings.BottomLiftHeight, + LayerLiftHeight = HeaderSettings.LiftHeight, + BottomLayerLiftSpeed = HeaderSettings.BottomLiftSpeed, + LayerLiftSpeed = HeaderSettings.LiftSpeed, + RetractSpeed = HeaderSettings.RetractSpeed, + BottomLayCount = InitialLayerCount, + BottomLayerCount = InitialLayerCount, + BottomLightOffTime = HeaderSettings.BottomLightOffDelay, + LayerLightOffTime = HeaderSettings.LayerOffTime, + BottomLayExposureTime = InitialExposureTime, + BottomLayerExposureTime = InitialExposureTime, + LayerExposureTime = LayerExposureTime, + LayerHeight = LayerHeight, + LayerCount = LayerCount, + AntiAliasing = ValidateAntiAliasingLevel(), + BottomLightPWM = (byte) HeaderSettings.BottomLightPWM, + LayerLightPWM = (byte) HeaderSettings.LightPWM, + + EstimatedPrintTime = PrintTime + }, + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(PWSFile)) + { + PWSFile file = new PWSFile + { + LayerManager = LayerManager, + HeaderSettings = + { + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + LayerHeight = LayerHeight, + LayerExposureTime = LayerExposureTime, + LiftHeight = LiftHeight, + LiftSpeed = LiftSpeed / 60, + RetractSpeed = RetractSpeed / 60, + LayerOffTime = HeaderSettings.LayerOffTime, + BottomLayersCount = InitialLayerCount, + BottomExposureSeconds = InitialExposureTime, + Price = MaterialCost, + Volume = UsedMaterial, + Weight = HeaderSettings.WeightG, + AntiAliasing = ValidateAntiAliasingLevel() + } + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ZCodexFile)) + { + TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); + ZCodexFile file = new ZCodexFile + { + ResinMetadataSettings = new ZCodexFile.ResinMetadata + { + MaterialId = 2, + Material = MaterialName, + AdditionalSupportLayerTime = 0, + BottomLayersNumber = InitialLayerCount, + BottomLayersTime = (uint)(InitialExposureTime * 1000), + LayerTime = (uint)(LayerExposureTime * 1000), + DisableSettingsChanges = false, + LayerThickness = LayerHeight, + PrintTime = (uint)PrintTime, + TotalLayersCount = LayerCount, + TotalMaterialVolumeUsed = UsedMaterial, + TotalMaterialWeightUsed = UsedMaterial, + }, + UserSettings = new ZCodexFile.UserSettingsdata + { + Printer = MachineName, + BottomLayersCount = InitialLayerCount, + PrintTime = $"{ts.Hours}h {ts.Minutes}m", + LayerExposureTime = (uint)(LayerExposureTime * 1000), + BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), + MaterialId = 2, + LayerThickness = $"{LayerHeight} mm", + AntiAliasing = (byte) (AntiAliasing > 1 ? 1 : 0), + CrossSupportEnabled = 1, + ExposureOffTime = (uint)HeaderSettings.LayerOffTime, + HollowEnabled = 0, + HollowThickness = 0, + InfillDensity = 0, + IsAdvanced = 0, + MaterialType = MaterialName, + MaterialVolume = UsedMaterial, + MaxLayer = LayerCount - 1, + ModelLiftEnabled = 0, + ModelLiftHeight = 0, + RaftEnabled = 0, + RaftHeight = 0, + RaftOffset = 0, + SupportAdditionalExposureEnabled = 0, + SupportAdditionalExposureTime = 0, + XCorrection = 0, + YCorrection = 0, + ZLiftDistance = HeaderSettings.LiftHeight, + ZLiftFeedRate = HeaderSettings.LiftSpeed, + ZLiftRetractRate = HeaderSettings.RetractSpeed, + }, + ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata + { + PrintTime = (uint)PrintTime, + PrinterName = MachineName, + Materials = new List + { + new ZCodexFile.ZCodeMetadata.MaterialsData + { + Name = MaterialName, + ExtruderType = "MAIN", + Id = 0, + Usage = 0, + Temperature = 0 + } + }, + }, + LayerManager = LayerManager + }; + + float usedMaterial = UsedMaterial / LayerCount; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData + { + Layer = layerIndex, + UsedMaterialVolume = usedMaterial + }); + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + return true; + } + + if (to == typeof(CWSFile)) + { + CWSFile defaultFormat = (CWSFile)FindByType(typeof(CWSFile)); + CWSFile file = new CWSFile { LayerManager = LayerManager }; + + file.SliceSettings.Xppm = file.OutputSettings.PixPermmX = (float)Math.Round(ResolutionX / HeaderSettings.BedSizeX, 3); + file.SliceSettings.Yppm = file.OutputSettings.PixPermmY = (float)Math.Round(ResolutionY / HeaderSettings.BedSizeY, 3); + file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort)ResolutionX; + file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort)ResolutionY; + file.SliceSettings.Thickness = file.OutputSettings.LayerThickness = LayerHeight; + file.SliceSettings.LayersNum = file.OutputSettings.LayersNum = LayerCount; + file.SliceSettings.HeadLayersNum = file.OutputSettings.NumberBottomLayers = InitialLayerCount; + file.SliceSettings.LayersExpoMs = file.OutputSettings.LayerTime = (uint)LayerExposureTime * 1000; + file.SliceSettings.HeadLayersExpoMs = file.OutputSettings.BottomLayersTime = (uint)InitialExposureTime * 1000; + file.SliceSettings.WaitBeforeExpoMs = (uint)(HeaderSettings.LayerOffTime * 1000); + file.SliceSettings.LiftDistance = file.OutputSettings.LiftDistance = LiftHeight; + file.SliceSettings.LiftUpSpeed = file.OutputSettings.ZLiftFeedRate = LiftSpeed; + file.SliceSettings.LiftDownSpeed = file.OutputSettings.ZLiftRetractRate = RetractSpeed; + file.SliceSettings.LiftWhenFinished = defaultFormat.SliceSettings.LiftWhenFinished; + + file.OutputSettings.BlankingLayerTime = (uint)(HeaderSettings.LayerOffTime * 1000); + //file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.OutlineWidthInset = 0; + //file.OutputSettings.OutlineWidthOutset = 0; + file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.TiltValue = 0; + //file.OutputSettings.UseMainliftGCodeTab = false; + //file.OutputSettings.AntiAliasing = 0; + //file.OutputSettings.AntiAliasingValue = 0; + file.OutputSettings.FlipX = HeaderSettings.ProjectorType != 0; + file.OutputSettings.FlipY = file.OutputSettings.FlipX; + file.OutputSettings.AntiAliasingValue = ValidateAntiAliasingLevel(); + file.OutputSettings.AntiAliasing = file.OutputSettings.AntiAliasingValue > 1; + + file.Encode(fileFullPath, progress); + + return true; + } + + return false; + } + #endregion + } +} diff --git a/UVtools.Core/FileFormats/PWSFile.cs b/UVtools.Core/FileFormats/PWSFile.cs new file mode 100644 index 0000000..c4c4fce --- /dev/null +++ b/UVtools.Core/FileFormats/PWSFile.cs @@ -0,0 +1,1349 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Threading.Tasks; +using BinarySerialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class PWSFile : FileFormat + { + #region Constants + public const byte MarkSize = 12; + public const byte RLE1EncodingLimit = 0x7d; // 125; + public const ushort RLE4EncodingLimit = 0xfff; // 4095; + + // CRC-16-ANSI (aka CRC-16-IMB) Polynomial: x^16 + x^15 + x^2 + 1 + public static readonly int[] CRC16Table = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040, + }; + + #endregion + + #region Enums + public enum LayerRleFormat + { + PWS, + PW0 + } + #endregion + + #region Sub Classes + + #region FileMark + public class FileMark + { + public const string SectionMarkFile = "ANYCUBIC"; + + private string _mark = SectionMarkFile; + /// + /// Gets the file mark placeholder + /// Fixed to "ANYCUBIC" + /// + [FieldOrder(0)] + [FieldLength(MarkSize)] + public string Mark + { + get => _mark; + set => _mark = value.TrimEnd('\0'); + } + + /// + /// Gets the file format version + /// + [FieldOrder(1)] public uint Version { get; set; } = 1; + + /// + /// Gets the area num + /// + [FieldOrder(2)] public uint AreaNum { get; set; } = 4; + + /// + /// Gets the header start address + /// + [FieldOrder(3)] public uint HeaderAddress { get; set; } + + [FieldOrder(4)] public uint Offset1 { get; set; } + + /// + /// Gets the preview start offset + /// + [FieldOrder(5)] public uint PreviewAddress { get; set; } + + [FieldOrder(6)] public uint Offset2 { get; set; } + + /// + /// Gets the layer definition start address + /// + [FieldOrder(7)] public uint LayerDefinitionAddress { get; set; } + + [FieldOrder(8)] public uint Offset3 { get; set; } + + /// + /// Gets layer image start address + /// + [FieldOrder(9)] public uint LayerImageAddress { get; set; } + + public override string ToString() + { + return $"{nameof(Mark)}: {Mark}, {nameof(Version)}: {Version}, {nameof(AreaNum)}: {AreaNum}, {nameof(HeaderAddress)}: {HeaderAddress}, {nameof(Offset1)}: {Offset1}, {nameof(PreviewAddress)}: {PreviewAddress}, {nameof(Offset2)}: {Offset2}, {nameof(LayerDefinitionAddress)}: {LayerDefinitionAddress}, {nameof(Offset3)}: {Offset3}, {nameof(LayerImageAddress)}: {LayerImageAddress}"; + } + } + #endregion + + #region Section + + public class Section + { + private string _mark; + + /// + /// Gets the section mark placeholder + /// + [FieldOrder(0)] + [FieldLength(MarkSize)] + public string Mark + { + get => _mark; + set => _mark = value.TrimEnd('\0'); + } + + /// + /// Gets the length of this section + /// + [FieldOrder(1)] public uint Length { get; set; } + + public Section() { } + + public Section(string mark, object obj) : this(mark, (uint)Helpers.Serializer.SizeOf(obj)) { } + + public Section(string mark, uint length = 0) + { + Mark = mark; + Length = length; + } + + + public void Validate(string mark, object obj = null) + { + Validate(mark, 0u, obj); + } + + public void Validate(string mark, uint length, object obj = null) + { + if (!Mark.Equals(mark)) + { + throw new FileLoadException($"'{Mark}' section expected, but got '{mark}'"); + } + + if (!ReferenceEquals(obj, null)) + { + length += (uint)Helpers.Serializer.SizeOf(obj); + } + + if (length > 0 && Length != length) + { + throw new FileLoadException($"{Mark} section bytes: expected {Length}, got {length}, difference: {(int)Length - length}"); + } + } + + public override string ToString() => $"{{{nameof(Mark)}: {Mark}, {nameof(Length)}: {Length}}}"; + } + + #endregion + + #region Header + public class Header + { + public const string SectionMark = "HEADER"; + + [Ignore] public Section Section { get; set; } + [FieldOrder(0)] public float PixelSize { get; set; } + [FieldOrder(1)] public float LayerHeight { get; set; } + [FieldOrder(2)] public float LayerExposureTime { get; set; } + [FieldOrder(3)] public float LayerOffTime { get; set; } = 1; + [FieldOrder(4)] public float BottomExposureSeconds { get; set; } + [FieldOrder(5)] public float BottomLayersCount { get; set; } + [FieldOrder(6)] public float LiftHeight { get; set; } = 6; + + /// + /// Gets the lift speed in mm/s + /// + [FieldOrder(7)] public float LiftSpeed { get; set; } = 3; // mm/s + + /// + /// Gets the retract speed in mm/s + /// + [FieldOrder(8)] public float RetractSpeed { get; set; } = 3; // mm/s + [FieldOrder(9)] public float Volume { get; set; } + [FieldOrder(10)] public uint AntiAliasing { get; set; } = 1; + [FieldOrder(11)] public uint ResolutionX { get; set; } + [FieldOrder(12)] public uint ResolutionY { get; set; } + [FieldOrder(13)] public float Weight { get; set; } + [FieldOrder(14)] public float Price { get; set; } + [FieldOrder(15)] public uint ResinType { get; set; } // 0x24 ? + [FieldOrder(16)] public uint PerLayerOverride { get; set; } // bool + [FieldOrder(17)] public uint Offset1 { get; set; } + [FieldOrder(18)] public uint Offset2 { get; set; } + [FieldOrder(19)] public uint Offset3 { get; set; } + + public Header() + { + Section = new Section(SectionMark, this); + } + + public override string ToString() => $"{nameof(Section)}: {Section}, {nameof(PixelSize)}: {PixelSize}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(LayerExposureTime)}: {LayerExposureTime}, {nameof(LayerOffTime)}: {LayerOffTime}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {nameof(BottomLayersCount)}: {BottomLayersCount}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(Volume)}: {Volume}, {nameof(AntiAliasing)}: {AntiAliasing}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(Weight)}: {Weight}, {nameof(Price)}: {Price}, {nameof(ResinType)}: {ResinType}, {nameof(PerLayerOverride)}: {PerLayerOverride}, {nameof(Offset1)}: {Offset1}, {nameof(Offset2)}: {Offset2}, {nameof(Offset3)}: {Offset3}"; + + public void Validate() + { + Section.Validate(SectionMark, this); + } + } + + #endregion + + #region Preview + + /// + /// The files contain two preview images. + /// These are shown on the printer display when choosing which file to print, sparing the poor printer from needing to render a 3D image from scratch. + /// + public class Preview + { + public const string SectionMark = "PREVIEW"; + [Ignore] public Section Section { get; set; } + + /// + /// Gets the image width, in pixels. + /// + [FieldOrder(1)] public uint Width { get; set; } = 224; + + /// + /// Gets the resolution of the image, in dpi. + /// + [FieldOrder(2)] public uint Resolution { get; set; } = 42; + + /// + /// Gets the image height, in pixels. + /// + [FieldOrder(3)] public uint Height { get; set; } = 168; + + // little-endian 16bit colors, RGB 565 encoded. + //[FieldOrder(4)] + //[FieldLength("Section.Length")] + [Ignore] + public byte[] Data { get; set; } + + public Preview() + { + Section = new Section(SectionMark, this); + } + + public Mat Decode(bool consumeData = true) + { + Mat image = new Mat(new Size((int) Width, (int) Height), DepthType.Cv8U, 3); + var span = image.GetPixelSpan(); + + int pixel = 0; + for (uint i = 0; i < Data.Length; i += 2) + { + ushort color16 = (ushort)(Data[i] + (Data[i+1] << 8)); + var r =(color16 >> 11) & 0x1f; + var g = (color16 >> 5) & 0x3f; + var b = (color16 >> 0) & 0x1f; + + /*span[pixel++] = new Rgba32( + (byte)((r << 3) | (r & 0x7)), + (byte)((g << 2) | (g & 0x3)), + (byte)((b << 3) | (b & 0x7)) + );*/ + span[pixel++] = (byte) ((b << 3) | (b & 0x7)); + span[pixel++] = (byte) ((g << 2) | (g & 0x3)); + span[pixel++] = (byte) ((r << 3) | (r & 0x7)); + } + + if (consumeData) + Data = null; + + return image; + } + + public static Preview Encode(Mat image) + { + var span = image.GetPixelSpan(); + + Preview preview = new Preview + { + Width = (uint) image.Width, + Height = (uint) image.Height, + Resolution = 42, + Data = new byte[image.Width * image.Height * 2] + }; + + int i = 0; + for (int pixel = 0; pixel < span.Length; pixel += image.NumberOfChannels) + { + // BGR + int b = span[pixel] >> 3; + int g = span[pixel+1] >> 2; + int r = span[pixel+2] >> 3; + + + ushort color = (ushort) ((r << 11) | (g << 5) | (b << 0)); + + preview.Data[i++] = (byte) color; + preview.Data[i++] = (byte) (color >> 8); + } + + preview.Section.Length += (uint) preview.Data.Length; + return preview; + } + + public override string ToString() + { + return $"{nameof(Section)}: {Section}, {nameof(Width)}: {Width}, {nameof(Resolution)}: {Resolution}, {nameof(Height)}: {Height}, {nameof(Data)}: {Data}"; + } + + public void Validate(uint size) + { + Section.Validate(SectionMark, size, this); + } + } + + #endregion + + #region Layer + + public class LayerData + { + /// + /// Gets the layer image offset to encoded layer data, and its length in bytes. + /// + [FieldOrder(0)] + public uint DataAddress { get; set; } + + /// + /// Gets the layer image length in bytes. + /// + [FieldOrder(1)] + public uint DataLength { get; set; } + + [FieldOrder(2)] public float LiftHeight { get; set; } + + [FieldOrder(3)] public float LiftSpeed { get; set; } + + /// + /// Gets the exposure time for this layer, in seconds. + /// + [FieldOrder(4)] + public float LayerExposure { get; set; } + + /// + /// Gets the build platform Z position for this layer, measured in millimeters. + /// + [FieldOrder(5)] + public float LayerPositionZ { get; set; } + + [FieldOrder(6)] public float Offset1 { get; set; } + [FieldOrder(7)] public float Offset2 { get; set; } + + [Ignore] public byte[] EncodedRle { get; set; } + [Ignore] public PWSFile Parent { get; set; } + + public LayerData() + { + } + + public LayerData(PWSFile parent, uint layerIndex) + { + Parent = parent; + LiftHeight = Parent.HeaderSettings.LiftHeight; + LiftSpeed = Parent.HeaderSettings.LiftSpeed; + + LayerPositionZ = parent[layerIndex].PositionZ; + LayerExposure = parent[layerIndex].ExposureTime; + } + + public Mat Decode(bool consumeData = true) + { + var result = Parent.LayerFormat == LayerRleFormat.PWS ? DecodePWS() : DecodePW0(); + if (consumeData) + EncodedRle = null; + + return result; + } + + public byte[] Encode(Mat image) + { + EncodedRle = Parent.LayerFormat == LayerRleFormat.PWS ? EncodePWS(image) : EncodePW0(image); + return EncodedRle; + } + + private Mat DecodePWS() + { + var image = new Mat(new Size((int) Parent.ResolutionX, (int) Parent.ResolutionY), DepthType.Cv8U, 1); + var span = image.GetPixelSpan(); + + int index = 0; + for (byte bit = 0; bit < Parent.AntiAliasing; bit++) + { + byte bitValue = (byte)(byte.MaxValue / ((1 << Parent.AntiAliasing) - 1) * (1 << bit)); + + int pixel = 0; + for (; index < EncodedRle.Length; index++) + { + // Lower 7 bits is the repeat count for the bit (0..127) + int reps = EncodedRle[index] & 0x7f; + + // We only need to set the non-zero pixels + // High bit is on for white, off for black + if ((EncodedRle[index] & 0x80) != 0) + { + for (int i = 0; i < reps; i++) + { + span[pixel + i] |= bitValue; + } + } + + pixel += reps; + + if (pixel == span.Length) + { + index++; + break; + } + + if (pixel > span.Length) + { + image.Dispose(); + throw new FileLoadException("Error image ran off the end"); + } + } + } + + return image; + } + + public byte[] EncodePWS(Mat image) + { + List rawData = new List(); + var span = image.GetPixelSpan(); + + bool obit; + int rep; + + void AddRep() + { + if (rep <= 0) return; + + byte by = (byte)rep; + + if (obit) + { + by |= 0x80; + //bitsOn += uint(rep) + } + + rawData.Add(by); + } + + for (byte aalevel = 0; aalevel < Parent.AntiAliasing; aalevel++) + { + obit = false; + rep = 0; + + + for (int pixel = 0; pixel < span.Length; pixel++) + { + var nbit = (span[pixel] & (1 << (8 - Parent.AntiAliasing + aalevel))) != 0; + + if (nbit == obit) + { + rep++; + + if (rep == RLE1EncodingLimit) + { + AddRep(); + rep = 0; + } + } + else + { + AddRep(); + obit = nbit; + rep = 1; + } + } + + // Collect stragglers + AddRep(); + } + + DataLength = (uint) rawData.Count; + + return rawData.ToArray(); + } + + private Mat DecodePW0() + { + var image = new Mat(new Size((int)Parent.ResolutionX, (int)Parent.ResolutionY), DepthType.Cv8U, 1); + var span = image.GetPixelSpan(); + + uint n = 0; + for (int index = 0; index < EncodedRle.Length; index++) + { + byte b = EncodedRle[index]; + int code = (b >> 4); + uint reps = (uint) (b & 0xf); + byte color; + switch (code) + { + case 0x0: + color = 0x00; + index++; + reps = reps * 256 + EncodedRle[index]; + break; + case 0xf: + color = 0xff; + index++; + reps = reps * 256 + EncodedRle[index]; + break; + default: + color = (byte) ((code << 4) | code); + break; + } + + color &= 0xff; + + // We only need to set the non-zero pixels + if (color != 0) + { + for (int i = 0; i < reps; i++) + { + span[(int) (n + i)] |= color; + } + } + + n += reps; + + + if (n == span.Length) + { + //index++; + break; + } + + if (n > span.Length) + { + image.Dispose(); + throw new FileLoadException($"Error image ran off the end: {n - reps}({reps}) of {span.Length}"); + } + } + + if (n != span.Length) + { + image.Dispose(); + throw new FileLoadException($"Error image ended short: {n} of {span.Length}"); + } + + return image; + } + + public byte[] EncodePW0(Mat image) + { + List rawData = new List(); + var span = image.GetPixelSpan(); + + int lastColor = -1; + int reps = 0; + + void PutReps() + { + while (reps > 0) + { + int done = reps; + + if (lastColor == 0 || lastColor == 0xf) + { + if (done > RLE4EncodingLimit) + { + done = RLE4EncodingLimit; + } + //more:= []byte{ 0, 0} + //binary.BigEndian.PutUint16(more, uint16(done | (color << 12))) + + //rle = append(rle, more...) + + ushort more = (ushort)(done | (lastColor << 12)); + rawData.Add((byte)(more >> 8)); + rawData.Add((byte)more); + } + else + { + if (done > 0xf) + { + done = 0xf; + } + rawData.Add((byte)(done | lastColor << 4)); + } + + reps -= done; + } + } + + for (int i = 0; i < span.Length; i++) + { + int color = span[i] >> 4; + + if (color == lastColor) + { + reps++; + } + else + { + PutReps(); + lastColor = color; + reps = 1; + } + } + + PutReps(); + + EncodedRle = rawData.ToArray(); + DataLength = (uint)rawData.Count; + + ushort crc = CRCRle4(EncodedRle); + rawData.Add((byte)(crc >> 8)); + rawData.Add((byte)crc); + + return EncodedRle; + } + + public static ushort CRCRle4(byte[] data) + { + ushort crc16 = 0; + for (int i = 0; i < data.Length; i++) + { + crc16 = (ushort) ((crc16 << 8) ^ CRC16Table[((crc16 >> 8) ^ CRC16Table[data[i]]) & 0xff]); + + } + + crc16 = (ushort) ((CRC16Table[crc16 & 0xff] * 0x100) + CRC16Table[(crc16 >> 8) & 0xff]); + + return crc16; + } + + public ushort CRCEncodedRle() + { + return CRCRle4(EncodedRle); + } + } + + #endregion + + #region LayerDefinition + public class LayerDefinition + { + public const string SectionMark = "LAYERDEF"; + + [Ignore] public Section Section { get; set; } = new Section(SectionMark); + + [FieldOrder(0)] public uint LayersCount { get; set; } + + [Ignore] public LayerData[] Layers; + + public LayerDefinition() + { + Section = new Section(SectionMark, this); + } + + public LayerDefinition(uint layersCount) : this() + { + LayersCount = layersCount; + Layers = new LayerData[layersCount]; + } + + [Ignore] + public LayerData this[uint index] + { + get => Layers[index]; + set => Layers[index] = value; + } + + [Ignore] + public LayerData this[int index] + { + get => Layers[index]; + set => Layers[index] = value; + } + + public void Validate() + { + Section.Validate(SectionMark, (uint)(LayersCount * Helpers.Serializer.SizeOf(new LayerData())), this); + } + + public override string ToString() => $"{nameof(Section)}: {Section}, {nameof(LayersCount)}: {LayersCount}"; + } + #endregion + + #endregion + + #region Properties + + public FileMark FileMarkSettings { get; protected internal set; } = new FileMark(); + + public Header HeaderSettings { get; protected internal set; } = new Header(); + + public Preview PreviewSettings { get; protected internal set; } = new Preview(); + + public LayerDefinition LayersDefinition { get; private set; } = new LayerDefinition(); + + public Dictionary LayersHash { get; } = new Dictionary(); + + public override FileFormatType FileType => FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = { + new FileExtension("pws", "Photon Workshop PWS Files"), + new FileExtension("pw0", "Photon Workshop PW0 Files") + }; + + public override Type[] ConvertToFormats { get; } = + { + //typeof(ChituboxZipFile) + //typeof(PHZFile), + typeof(PWSFile), + typeof(CWSFile), + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = + { + PrintParameterModifier.InitialLayerCount, + PrintParameterModifier.InitialExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + //PrintParameterModifier.BottomLayerOffTime, + PrintParameterModifier.LayerOffTime, + //PrintParameterModifier.BottomLiftHeight, + //PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + PrintParameterModifier.RetractSpeed, + }; + + public override byte ThumbnailsCount { get; } = 1; + + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(224, 168)}; + + public override uint ResolutionX => HeaderSettings.ResolutionX; + + public override uint ResolutionY => HeaderSettings.ResolutionY; + public override byte AntiAliasing => (byte) HeaderSettings.AntiAliasing; + + public override float LayerHeight => HeaderSettings.LayerHeight; + + public override ushort InitialLayerCount => (ushort)HeaderSettings.BottomLayersCount; + + public override float InitialExposureTime => HeaderSettings.BottomExposureSeconds; + + public override float LayerExposureTime => HeaderSettings.LayerExposureTime; + public override float LiftHeight => HeaderSettings.LiftHeight; + public override float LiftSpeed => HeaderSettings.LiftSpeed * 60; + public override float RetractSpeed => HeaderSettings.RetractSpeed * 60; + + public override float PrintTime => 0; + + public override float UsedMaterial => HeaderSettings.Volume; + + public override float MaterialCost => HeaderSettings.Price; + + public override string MaterialName => null; + public override string MachineName => LayerFormat == LayerRleFormat.PWS ? "AnyCubic Photon S" : "AnyCubic Photon Zero"; + + public override object[] Configs => new object[] { FileMarkSettings, HeaderSettings, PreviewSettings, LayersDefinition }; + + public LayerRleFormat LayerFormat => FileFullPath.EndsWith(".pws") ? LayerRleFormat.PWS : LayerRleFormat.PW0; + + #endregion + + #region Constructors + public PWSFile() + { + } + #endregion + + #region Methods + public override void Clear() + { + base.Clear(); + + LayersDefinition = null; + } + + public override void Encode(string fileFullPath, OperationProgress progress = null) + { + base.Encode(fileFullPath, progress); + LayersHash.Clear(); + + LayersDefinition = new LayerDefinition(LayerCount); + + uint currentOffset = FileMarkSettings.HeaderAddress = (uint) Helpers.Serializer.SizeOf(FileMarkSettings); + using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) + { + outputFile.Seek((int) currentOffset, SeekOrigin.Begin); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, HeaderSettings.Section); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + if (CreatedThumbnailsCount > 0) + { + FileMarkSettings.PreviewAddress = currentOffset; + Preview preview = Preview.Encode(Thumbnails[0]); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, preview.Section); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, preview); + currentOffset += outputFile.WriteBytes(preview.Data); + } + + FileMarkSettings.LayerDefinitionAddress = currentOffset; + + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + LayerData layer = new LayerData(this, (uint) layerIndex); + using (var image = this[layerIndex].LayerMat) + { + layer.Encode(image); + LayersDefinition.Layers[layerIndex] = layer; + } + lock (progress.Mutex) + { + progress++; + } + }); + + LayersDefinition.Section.Length += (uint)Helpers.Serializer.SizeOf(LayersDefinition[0]) * LayerCount; + currentOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinition.Section); + uint offsetLayerRle = FileMarkSettings.LayerImageAddress = currentOffset + LayersDefinition.Section.Length; + + currentOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinition); + + progress.Reset(OperationProgress.StatusWritingFile, LayerCount); + + foreach (var layer in LayersDefinition.Layers) + { + progress.Token.ThrowIfCancellationRequested(); + string hash = Helpers.ComputeSHA1Hash(layer.EncodedRle); + + if (LayersHash.TryGetValue(hash, out var layerDataHash)) + { + layer.DataAddress = layerDataHash.DataAddress; + layer.DataLength = (uint)layerDataHash.EncodedRle.Length; + } + else + { + LayersHash.Add(hash, layer); + + layer.DataAddress = offsetLayerRle; + + outputFile.Seek(offsetLayerRle, SeekOrigin.Begin); + offsetLayerRle += Helpers.SerializeWriteFileStream(outputFile, layer.EncodedRle); + } + + outputFile.Seek(currentOffset, SeekOrigin.Begin); + currentOffset += Helpers.SerializeWriteFileStream(outputFile, layer); + + progress++; + } + + // Rewind + outputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, FileMarkSettings); + } + } + + public override void Decode(string fileFullPath, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) + { + + //HeaderSettings = Helpers.ByteToType(InputFile); + //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); + FileMarkSettings = Helpers.Deserialize(inputFile); + + Debug.Write("FileMark -> "); + Debug.WriteLine(FileMarkSettings); + + if (!FileMarkSettings.Mark.Equals(FileMark.SectionMarkFile)) + { + throw new FileLoadException( + $"Invalid Filemark {FileMarkSettings.Mark}, expected {FileMark.SectionMarkFile}", fileFullPath); + } + + if (FileMarkSettings.Version != 1) + { + throw new FileLoadException($"Invalid Version {FileMarkSettings.Version}, expected 1", + fileFullPath); + } + + FileFullPath = fileFullPath; + + inputFile.Seek(FileMarkSettings.HeaderAddress, SeekOrigin.Begin); + //Section sectionHeader = Helpers.Deserialize
(inputFile); + //Debug.Write("SectionHeader -> "); + //Debug.WriteLine(sectionHeader); + + var section = Helpers.Deserialize
(inputFile); + HeaderSettings = Helpers.Deserialize
(inputFile); + HeaderSettings.Section = section; + + + Debug.Write("Header -> "); + Debug.WriteLine(HeaderSettings); + + HeaderSettings.Validate(); + + if (FileMarkSettings.PreviewAddress > 0) + { + inputFile.Seek(FileMarkSettings.PreviewAddress, SeekOrigin.Begin); + + section = Helpers.Deserialize
(inputFile); + PreviewSettings = Helpers.Deserialize(inputFile); + PreviewSettings.Section = section; + Debug.Write("Preview -> "); + Debug.WriteLine(PreviewSettings); + + uint datasize = PreviewSettings.Width * PreviewSettings.Height * 2; + PreviewSettings.Validate(datasize); + + PreviewSettings.Data = new byte[datasize]; + inputFile.ReadBytes(PreviewSettings.Data); + + Thumbnails[0] = PreviewSettings.Decode(true); + } + + inputFile.Seek(FileMarkSettings.LayerDefinitionAddress, SeekOrigin.Begin); + + section = Helpers.Deserialize
(inputFile); + LayersDefinition = Helpers.Deserialize(inputFile); + LayersDefinition.Section = section; + Debug.Write("LayersDefinition -> "); + Debug.WriteLine(LayersDefinition); + + LayerManager = new LayerManager(LayersDefinition.LayersCount); + LayersDefinition.Layers = new LayerData[LayerCount]; + + + LayersDefinition.Validate(); + + for (int i = 0; i < LayerCount; i++) + { + LayersDefinition[i] = Helpers.Deserialize(inputFile); + LayersDefinition[i].Parent = this; + } + + progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); + + for (int i = 0; i < LayerCount; i++) + { + var layer = LayersDefinition[i]; + inputFile.Seek(layer.DataAddress, SeekOrigin.Begin); + layer.EncodedRle = new byte[layer.DataLength]; + inputFile.ReadBytes(layer.EncodedRle); + + progress++; + progress.Token.ThrowIfCancellationRequested(); + } + + progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + + Parallel.For(0, LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) + { + return; + } + + using (var image = LayersDefinition[(uint) layerIndex].Decode()) + { + this[layerIndex] = new Layer((uint) layerIndex, image) + { + PositionZ = LayersDefinition[(uint)layerIndex].LayerPositionZ, + ExposureTime = LayersDefinition[(uint)layerIndex].LayerExposure + }; + } + + lock (progress.Mutex) + { + progress++; + } + }); + } + + progress.Token.ThrowIfCancellationRequested(); + } + + public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) + { + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return HeaderSettings.LayerOffTime; + + var baseValue = base.GetValueFromPrintParameterModifier(modifier); + return baseValue; + } + + public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) + { + void UpdateLayers() + { + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + // Bottom : others + + this[layerIndex].ExposureTime = + LayersDefinition[layerIndex].LayerExposure = GetInitialLayerValueOrNormal(layerIndex, HeaderSettings.BottomExposureSeconds, HeaderSettings.LayerExposureTime); + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + HeaderSettings.BottomLayersCount = + HeaderSettings.BottomLayersCount = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + HeaderSettings.BottomExposureSeconds = value.Convert(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + HeaderSettings.LayerExposureTime = value.Convert(); + UpdateLayers(); + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) + { + HeaderSettings.LayerOffTime = value.Convert(); + UpdateLayers(); + return true; + } + + + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + { + HeaderSettings.LiftHeight = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + { + HeaderSettings.LiftSpeed = value.Convert() / 60f; + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + { + HeaderSettings.RetractSpeed = value.Convert() / 60f; + UpdateLayers(); + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (LayerManager.IsModified) + { + if (!string.IsNullOrEmpty(filePath)) + { + FileFullPath = filePath; + } + Encode(FileFullPath, progress); + return; + } + + + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + } + + using (var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) + { + outputFile.Seek(FileMarkSettings.HeaderAddress+Helpers.Serializer.SizeOf(HeaderSettings.Section), SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + + + outputFile.Seek(FileMarkSettings.LayerDefinitionAddress + Helpers.Serializer.SizeOf(HeaderSettings.Section) + Helpers.Serializer.SizeOf(LayersDefinition), SeekOrigin.Begin); + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + Helpers.SerializeWriteFileStream(outputFile, LayersDefinition[layerIndex]); + } + } + + //Decode(FileFullPath, progress); + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + if (to == typeof(PWSFile)) + { + if (Path.GetExtension(FileFullPath).Equals(Path.GetExtension(fileFullPath))) + { + return false; + } + PWSFile file = new PWSFile + { + LayerManager = LayerManager, + HeaderSettings = + { + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + LayerHeight = LayerHeight, + LayerExposureTime = LayerExposureTime, + LiftHeight = LiftHeight, + LiftSpeed = LiftSpeed / 60, + RetractSpeed = RetractSpeed / 60, + LayerOffTime = HeaderSettings.LayerOffTime, + BottomLayersCount = InitialLayerCount, + BottomExposureSeconds = InitialExposureTime, + Price = MaterialCost, + Volume = UsedMaterial, + Weight = HeaderSettings.Weight, + AntiAliasing = ValidateAntiAliasingLevel() + } + }; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + /*if (to == typeof(PHZFile)) + { + PHZFile file = new PHZFile + { + LayerManager = LayerManager + }; + + + file.HeaderSettings.Version = 2; + file.HeaderSettings.BedSizeX = HeaderSettings.BedSizeX; + file.HeaderSettings.BedSizeY = HeaderSettings.BedSizeY; + file.HeaderSettings.BedSizeZ = HeaderSettings.BedSizeZ; + file.HeaderSettings.OverallHeightMilimeter = TotalHeight; + file.HeaderSettings.BottomExposureSeconds = InitialExposureTime; + file.HeaderSettings.BottomLayersCount = InitialLayerCount; + file.HeaderSettings.BottomLightPWM = HeaderSettings.BottomLightPWM; + file.HeaderSettings.LayerCount = LayerCount; + file.HeaderSettings.LayerExposureSeconds = LayerExposureTime; + file.HeaderSettings.LayerHeightMilimeter = LayerHeight; + file.HeaderSettings.LayerOffTime = HeaderSettings.LayerOffTime; + file.HeaderSettings.LightPWM = HeaderSettings.LightPWM; + file.HeaderSettings.PrintTime = HeaderSettings.PrintTime; + file.HeaderSettings.ProjectorType = HeaderSettings.ProjectorType; + file.HeaderSettings.ResolutionX = ResolutionX; + file.HeaderSettings.ResolutionY = ResolutionY; + + file.HeaderSettings.BottomLayerCount = InitialLayerCount; + file.HeaderSettings.BottomLiftHeight = PrintParametersSettings.BottomLiftHeight; + file.HeaderSettings.BottomLiftSpeed = PrintParametersSettings.BottomLiftSpeed; + file.HeaderSettings.BottomLightOffDelay = PrintParametersSettings.BottomLightOffDelay; + file.HeaderSettings.CostDollars = MaterialCost; + file.HeaderSettings.LiftHeight = PrintParametersSettings.LiftHeight; + file.HeaderSettings.LiftingSpeed = PrintParametersSettings.LiftingSpeed; + file.HeaderSettings.LayerOffTime = HeaderSettings.LayerOffTime; + file.HeaderSettings.RetractSpeed = PrintParametersSettings.RetractSpeed; + file.HeaderSettings.VolumeMl = UsedMaterial; + file.HeaderSettings.WeightG = PrintParametersSettings.WeightG; + + file.HeaderSettings.MachineName = MachineName; + file.HeaderSettings.MachineNameSize = (uint) MachineName.Length; + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ZCodexFile)) + { + TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); + ZCodexFile file = new ZCodexFile + { + ResinMetadataSettings = new ZCodexFile.ResinMetadata + { + MaterialId = 2, + Material = MaterialName, + AdditionalSupportLayerTime = 0, + BottomLayersNumber = InitialLayerCount, + BottomLayersTime = (uint)(InitialExposureTime * 1000), + LayerTime = (uint)(LayerExposureTime * 1000), + DisableSettingsChanges = false, + LayerThickness = LayerHeight, + PrintTime = (uint)PrintTime, + TotalLayersCount = LayerCount, + TotalMaterialVolumeUsed = UsedMaterial, + TotalMaterialWeightUsed = UsedMaterial, + }, + UserSettings = new ZCodexFile.UserSettingsdata + { + Printer = MachineName, + BottomLayersCount = InitialLayerCount, + PrintTime = $"{ts.Hours}h {ts.Minutes}m", + LayerExposureTime = (uint)(LayerExposureTime * 1000), + BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), + MaterialId = 2, + LayerThickness = $"{LayerHeight} mm", + AntiAliasing = 0, + CrossSupportEnabled = 1, + ExposureOffTime = (uint) HeaderSettings.LayerOffTime, + HollowEnabled = 0, + HollowThickness = 0, + InfillDensity = 0, + IsAdvanced = 0, + MaterialType = MaterialName, + MaterialVolume = UsedMaterial, + MaxLayer = LayerCount - 1, + ModelLiftEnabled = 0, + ModelLiftHeight = 0, + RaftEnabled = 0, + RaftHeight = 0, + RaftOffset = 0, + SupportAdditionalExposureEnabled = 0, + SupportAdditionalExposureTime = 0, + XCorrection = 0, + YCorrection = 0, + ZLiftDistance = PrintParametersSettings.LiftHeight, + ZLiftFeedRate = PrintParametersSettings.LiftingSpeed, + ZLiftRetractRate = PrintParametersSettings.RetractSpeed, + }, + ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata + { + PrintTime = (uint)PrintTime, + PrinterName = MachineName, + Materials = new List + { + new ZCodexFile.ZCodeMetadata.MaterialsData + { + Name = MaterialName, + ExtruderType = "MAIN", + Id = 0, + Usage = 0, + Temperature = 0 + } + }, + }, + LayerManager = LayerManager + }; + + float usedMaterial = UsedMaterial / LayerCount; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData + { + Layer = layerIndex, + UsedMaterialVolume = usedMaterial + }); + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + return true; + } + */ + + if (to == typeof(CWSFile)) + { + CWSFile defaultFormat = (CWSFile)FindByType(typeof(CWSFile)); + CWSFile file = new CWSFile { LayerManager = LayerManager }; + + //file.SliceSettings.Xppm = file.OutputSettings.PixPermmX = defaultFormat.OutputSettings. + //file.SliceSettings.Yppm = file.OutputSettings.PixPermmY = (float)Math.Round(ResolutionY / HeaderSettings.BedSizeY, 3); + file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort)ResolutionX; + file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort)ResolutionY; + file.SliceSettings.Thickness = file.OutputSettings.LayerThickness = LayerHeight; + file.SliceSettings.LayersNum = file.OutputSettings.LayersNum = LayerCount; + file.SliceSettings.HeadLayersNum = file.OutputSettings.NumberBottomLayers = InitialLayerCount; + file.SliceSettings.LayersExpoMs = file.OutputSettings.LayerTime = (uint)LayerExposureTime * 1000; + file.SliceSettings.HeadLayersExpoMs = file.OutputSettings.BottomLayersTime = (uint)InitialExposureTime * 1000; + file.SliceSettings.WaitBeforeExpoMs = (uint)(HeaderSettings.LayerOffTime * 1000); + file.SliceSettings.LiftDistance = file.OutputSettings.LiftDistance = LiftHeight; + file.SliceSettings.LiftUpSpeed = file.OutputSettings.ZLiftFeedRate = LiftSpeed; + file.SliceSettings.LiftDownSpeed = file.OutputSettings.ZLiftRetractRate = RetractSpeed; + file.SliceSettings.LiftWhenFinished = defaultFormat.SliceSettings.LiftWhenFinished; + + file.OutputSettings.BlankingLayerTime = (uint)(HeaderSettings.LayerOffTime * 1000); + //file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.OutlineWidthInset = 0; + //file.OutputSettings.OutlineWidthOutset = 0; + file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.TiltValue = 0; + //file.OutputSettings.UseMainliftGCodeTab = false; + //file.OutputSettings.AntiAliasing = 0; + //file.OutputSettings.AntiAliasingValue = 0; + //file.OutputSettings.FlipX = HeaderSettings. != 0; + //file.OutputSettings.FlipY = file.OutputSettings.FlipX; + file.OutputSettings.AntiAliasingValue = ValidateAntiAliasingLevel(); + file.OutputSettings.AntiAliasing = file.OutputSettings.AntiAliasingValue > 1; + + file.Encode(fileFullPath, progress); + + return true; + } + + return false; + } + #endregion + } +} diff --git a/UVtools.Core/FileFormats/SL1File.cs b/UVtools.Core/FileFormats/SL1File.cs new file mode 100644 index 0000000..3f88b2c --- /dev/null +++ b/UVtools.Core/FileFormats/SL1File.cs @@ -0,0 +1,987 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using Emgu.CV; +using Emgu.CV.CvEnum; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class SL1File : FileFormat + { + #region Constants + + public const string Keyword_AntiAliasing = "AntiAliasing"; + public const string Keyword_BottomLightOffDelay = "BottomLightOffDelay"; + public const string Keyword_LayerOffTime = "LayerOffTime"; + public const string Keyword_LightOffDelay = "LightOffDelay"; + public const string Keyword_BottomLiftHeight = "BottomLiftHeight"; + public const string Keyword_BottomLiftSpeed = "BottomLiftSpeed"; + public const string Keyword_LiftHeight = "LiftHeight"; + public const string Keyword_LiftSpeed = "LiftSpeed"; + public const string Keyword_RetractSpeed = "RetractSpeed"; + public const string Keyword_BottomLightPWM = "BottomLightPWM"; + public const string Keyword_LightPWM = "LightPWM"; + #endregion + + #region Sub Classes + + #region Printer + public class Printer + { + #region Printer + public string InheritsCummulative { get; set; } + public string PrinterSettingsId { get; set; } + public string PrinterTechnology { get; set; } + public string PrinterModel { get; set; } + public string PrinterVariant { get; set; } + public string PrinterVendor { get; set; } + public string DefaultSlaMaterialProfile { get; set; } + public string DefaultSlaPrintProfile { get; set; } + public string PrinterNotes { get; set; } + public string Thumbnails { get; set; } + #endregion + + #region Size and Coordinates + public string BedCustomModel { get; set; } + public string BedCustomTexture { get; set; } + public string BedShape { get; set; } + public float MaxPrintHeight { get; set; } + #endregion + + #region Display + public float DisplayWidth { get; set; } + public float DisplayHeight { get; set; } + public uint DisplayPixelsX { get; set; } + public uint DisplayPixelsY { get; set; } + public string DisplayOrientation { get; set; } + public bool DisplayMirrorX { get; set; } + public bool DisplayMirrorY { get; set; } + #endregion + + #region Tilt + public float FastTiltTime { get; set; } + public float SlowTiltTime { get; set; } + public float AreaFill { get; set; } + #endregion + + #region Corrections + public string RelativeCorrection { get; set; } + public float AbsoluteCorrection { get; set; } + public float ElefantFootCompensation { get; set; } + public float ElefantFootMinWidth { get; set; } + public float GammaCorrection { get; set; } + + #endregion + + #region Exposure + + public float MinExposureTime { get; set; } + public float MaxExposureTime { get; set; } + public float MinInitialExposureTime { get; set; } + public float MaxInitialExposureTime { get; set; } + + #endregion + + #region Overrides + public override string ToString() + { + return $"{nameof(PrinterSettingsId)}: {PrinterSettingsId}, {nameof(PrinterTechnology)}: {PrinterTechnology}, {nameof(PrinterModel)}: {PrinterModel}, {nameof(PrinterVariant)}: {PrinterVariant}, {nameof(PrinterVendor)}: {PrinterVendor}, {nameof(DefaultSlaMaterialProfile)}: {DefaultSlaMaterialProfile}, {nameof(DefaultSlaPrintProfile)}: {DefaultSlaPrintProfile}, {nameof(PrinterNotes)}: {PrinterNotes}, {nameof(Thumbnails)}: {Thumbnails}, {nameof(BedCustomModel)}: {BedCustomModel}, {nameof(BedCustomTexture)}: {BedCustomTexture}, {nameof(BedShape)}: {BedShape}, {nameof(MaxPrintHeight)}: {MaxPrintHeight}, {nameof(DisplayWidth)}: {DisplayWidth}, {nameof(DisplayHeight)}: {DisplayHeight}, {nameof(DisplayPixelsX)}: {DisplayPixelsX}, {nameof(DisplayPixelsY)}: {DisplayPixelsY}, {nameof(DisplayOrientation)}: {DisplayOrientation}, {nameof(DisplayMirrorX)}: {DisplayMirrorX}, {nameof(DisplayMirrorY)}: {DisplayMirrorY}, {nameof(FastTiltTime)}: {FastTiltTime}, {nameof(SlowTiltTime)}: {SlowTiltTime}, {nameof(AreaFill)}: {AreaFill}, {nameof(RelativeCorrection)}: {RelativeCorrection}, {nameof(AbsoluteCorrection)}: {AbsoluteCorrection}, {nameof(ElefantFootCompensation)}: {ElefantFootCompensation}, {nameof(ElefantFootMinWidth)}: {ElefantFootMinWidth}, {nameof(GammaCorrection)}: {GammaCorrection}, {nameof(MinExposureTime)}: {MinExposureTime}, {nameof(MaxExposureTime)}: {MaxExposureTime}, {nameof(MinInitialExposureTime)}: {MinInitialExposureTime}, {nameof(MaxInitialExposureTime)}: {MaxInitialExposureTime}"; + } + #endregion + } + #endregion + + #region Material + public class Material + { + #region Material + public string MaterialVendor { get; set; } + public string MaterialType { get; set; } + public string SlaMaterialSettingsId { get; set; } + public float BottleCost { get; set; } + public ushort BottleVolume { get; set; } + public float BottleWeight { get; set; } + public float MaterialDensity { get; set; } + public string MaterialNotes { get; set; } + + #endregion + + #region Layers + + public float InitialLayerHeight { get; set; } + #endregion + + #region Exposure + + public float ExposureTime { get; set; } + public float InitialExposureTime { get; set; } + #endregion + + #region Corrections + public string MaterialCorrection { get; set; } + + #endregion + + #region Dependencies + + public string CompatiblePrintersConditionCummulative { get; set; } + public string CompatiblePrintsConditionCummulative { get; set; } + + #endregion + + #region Overrides + public override string ToString() + { + return $"{nameof(MaterialVendor)}: {MaterialVendor}, {nameof(MaterialType)}: {MaterialType}, {nameof(SlaMaterialSettingsId)}: {SlaMaterialSettingsId}, {nameof(BottleCost)}: {BottleCost}, {nameof(BottleVolume)}: {BottleVolume}, {nameof(BottleWeight)}: {BottleWeight}, {nameof(MaterialDensity)}: {MaterialDensity}, {nameof(MaterialNotes)}: {MaterialNotes}, {nameof(InitialLayerHeight)}: {InitialLayerHeight}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(InitialExposureTime)}: {InitialExposureTime}, {nameof(MaterialCorrection)}: {MaterialCorrection}, {nameof(CompatiblePrintersConditionCummulative)}: {CompatiblePrintersConditionCummulative}, {nameof(CompatiblePrintsConditionCummulative)}: {CompatiblePrintsConditionCummulative}"; + } + #endregion + } + #endregion + + #region Print + + public class Print + { + #region Print + public string SlaPrintSettingsId { get; set; } + #endregion + + #region Layers + + public float LayerHeight { get; set; } + public byte FadedLayers { get; set; } + #endregion + + #region Supports + public bool SupportsEnable { get; set; } + + + public float SupportHeadFrontDiameter { get; set; } + public float SupportHeadPenetration { get; set; } + public float SupportHeadWidth { get; set; } + + public byte SupportPillarWideningFactor { set; get; } + public float SupportPillarDiameter { get; set; } + public float SupportMaxBridgesOnPillar { get; set; } + public string SupportPillarConnectionMode { get; set; } + public bool SupportBuildplateOnly { get; set; } + public float SupportBaseDiameter { get; set; } + public float SupportBaseHeight { get; set; } + public float SupportBaseSafetyDistance { get; set; } + public bool PadAroundObject { get; set; } + public float SupportObjectElevation { get; set; } + + + public ushort SupportCriticalAngle { get; set; } + public float SupportMaxBridgeLength { get; set; } + public float SupportMaxPillarLinkDistance { get; set; } + + + public byte SupportPointsDensityRelative { get; set; } + public float SupportPointsMinimalDistance { get; set; } + + #endregion + + #region Pad + + public bool PadEnable { set; get; } + public float PadWallThickness { set; get; } + public float PadWallHeight { set; get; } + public float PadBrimSize { set; get; } + public float PadMaxMergeDistance { set; get; } + public float PadWallSlope { set; get; } + //public float PadAroundObject { set; get; } + public bool PadAroundObjectEverywhere { set; get; } + public float PadObjectGap { set; get; } + public float PadObjectConnectorStride { set; get; } + public float PadObjectConnectorWidth { set; get; } + public float PadObjectConnectorPenetration { set; get; } + #endregion + + #region Hollowing + public bool HollowingEnable { set; get; } + public float HollowingMinThickness { set; get; } + public float HollowingQuality { set; get; } + public float HollowingClosingDistance { set; get; } + #endregion + + #region Advanced + public float SliceClosingRadius { set; get; } + #endregion + + #region Output + public string OutputFilenameFormat { set; get; } + #endregion + + #region Dependencies + public string CompatiblePrintsCondition { set; get; } + #endregion + + #region Overrides + public override string ToString() + { + return $"{nameof(SlaPrintSettingsId)}: {SlaPrintSettingsId}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(FadedLayers)}: {FadedLayers}, {nameof(SupportsEnable)}: {SupportsEnable}, {nameof(SupportHeadFrontDiameter)}: {SupportHeadFrontDiameter}, {nameof(SupportHeadPenetration)}: {SupportHeadPenetration}, {nameof(SupportHeadWidth)}: {SupportHeadWidth}, {nameof(SupportPillarWideningFactor)}: {SupportPillarWideningFactor}, {nameof(SupportPillarDiameter)}: {SupportPillarDiameter}, {nameof(SupportMaxBridgesOnPillar)}: {SupportMaxBridgesOnPillar}, {nameof(SupportPillarConnectionMode)}: {SupportPillarConnectionMode}, {nameof(SupportBuildplateOnly)}: {SupportBuildplateOnly}, {nameof(SupportBaseDiameter)}: {SupportBaseDiameter}, {nameof(SupportBaseHeight)}: {SupportBaseHeight}, {nameof(SupportBaseSafetyDistance)}: {SupportBaseSafetyDistance}, {nameof(PadAroundObject)}: {PadAroundObject}, {nameof(SupportObjectElevation)}: {SupportObjectElevation}, {nameof(SupportCriticalAngle)}: {SupportCriticalAngle}, {nameof(SupportMaxBridgeLength)}: {SupportMaxBridgeLength}, {nameof(SupportMaxPillarLinkDistance)}: {SupportMaxPillarLinkDistance}, {nameof(SupportPointsDensityRelative)}: {SupportPointsDensityRelative}, {nameof(SupportPointsMinimalDistance)}: {SupportPointsMinimalDistance}, {nameof(PadEnable)}: {PadEnable}, {nameof(PadWallThickness)}: {PadWallThickness}, {nameof(PadWallHeight)}: {PadWallHeight}, {nameof(PadBrimSize)}: {PadBrimSize}, {nameof(PadMaxMergeDistance)}: {PadMaxMergeDistance}, {nameof(PadWallSlope)}: {PadWallSlope}, {nameof(PadAroundObjectEverywhere)}: {PadAroundObjectEverywhere}, {nameof(PadObjectGap)}: {PadObjectGap}, {nameof(PadObjectConnectorStride)}: {PadObjectConnectorStride}, {nameof(PadObjectConnectorWidth)}: {PadObjectConnectorWidth}, {nameof(PadObjectConnectorPenetration)}: {PadObjectConnectorPenetration}, {nameof(HollowingEnable)}: {HollowingEnable}, {nameof(HollowingMinThickness)}: {HollowingMinThickness}, {nameof(HollowingQuality)}: {HollowingQuality}, {nameof(HollowingClosingDistance)}: {HollowingClosingDistance}, {nameof(SliceClosingRadius)}: {SliceClosingRadius}, {nameof(OutputFilenameFormat)}: {OutputFilenameFormat}, {nameof(CompatiblePrintsCondition)}: {CompatiblePrintsCondition}"; + } + #endregion + } + + #endregion + + #region OutputConfig + + public class OutputConfig + { + public string Action { get; set; } + public string JobDir { get; set; } + public float ExpTime { get; set; } + public float ExpTimeFirst { get; set; } + public string FileCreationTimestamp { get; set; } + public float LayerHeight { get; set; } + public string MaterialName { get; set; } + public byte NumFade { get; set; } + public ushort NumFast { get; set; } + public byte NumSlow { get; set; } + public string PrintProfile { get; set; } + public float PrintTime { get; set; } + public string PrinterModel { get; set; } + public string PrinterProfile { get; set; } + public string PrinterVariant { get; set; } + public string PrusaSlicerVersion { get; set; } + public float UsedMaterial { get; set; } + + public override string ToString() + { + return $"{nameof(Action)}: {Action}, {nameof(JobDir)}: {JobDir}, {nameof(ExpTime)}: {ExpTime}, {nameof(ExpTimeFirst)}: {ExpTimeFirst}, {nameof(FileCreationTimestamp)}: {FileCreationTimestamp}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(MaterialName)}: {MaterialName}, {nameof(NumFade)}: {NumFade}, {nameof(NumFast)}: {NumFast}, {nameof(NumSlow)}: {NumSlow}, {nameof(PrintProfile)}: {PrintProfile}, {nameof(PrintTime)}: {PrintTime}, {nameof(PrinterModel)}: {PrinterModel}, {nameof(PrinterProfile)}: {PrinterProfile}, {nameof(PrinterVariant)}: {PrinterVariant}, {nameof(PrusaSlicerVersion)}: {PrusaSlicerVersion}, {nameof(UsedMaterial)}: {UsedMaterial}"; + } + } + + #endregion + + #endregion + + #region Properties + public Printer PrinterSettings { get; private set; } + + public Material MaterialSettings { get; private set; } + + public Print PrintSettings { get; private set; } + + public OutputConfig OutputConfigSettings { get; private set; } + + public Statistics Statistics { get; } = new Statistics(); + + + public override FileFormatType FileType => FileFormatType.Archive; + + public override FileExtension[] FileExtensions { get; } = { + new FileExtension("sl1", "PrusaSlicer SL1 Files") + }; + + public override Type[] ConvertToFormats { get; } = + { + typeof(ChituboxFile), + typeof(ChituboxZipFile), + typeof(PWSFile), + typeof(PHZFile), + typeof(ZCodexFile), + typeof(CWSFile), + }; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = { + PrintParameterModifier.InitialLayerCount, + PrintParameterModifier.InitialExposureSeconds, + PrintParameterModifier.ExposureSeconds, + }; + + public override byte ThumbnailsCount { get; } = 2; + + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = { new System.Drawing.Size(400, 400), new System.Drawing.Size(800, 480) }; + //public override Image[] Thumbnails { get; set; } + + public override uint ResolutionX => PrinterSettings.DisplayPixelsX; + + public override uint ResolutionY => PrinterSettings.DisplayPixelsY; + public override byte AntiAliasing => (byte) (PrinterSettings.GammaCorrection > 0 ? LookupCustomValue(Keyword_AntiAliasing, 4) : 1); + + public override float LayerHeight => OutputConfigSettings.LayerHeight; + + public override ushort InitialLayerCount => OutputConfigSettings.NumFade; + + public override float InitialExposureTime => OutputConfigSettings.ExpTimeFirst; + + public override float LayerExposureTime => OutputConfigSettings.ExpTime; + + public override float LiftHeight { get; } = 0; + + public override float LiftSpeed { get; } = 0; + + public override float RetractSpeed { get; } = 0; + + public override float PrintTime => OutputConfigSettings.PrintTime; + + public override float UsedMaterial => OutputConfigSettings.UsedMaterial; + + public override float MaterialCost => (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.BottleCost / MaterialSettings.BottleVolume, 2); + + public override string MaterialName => OutputConfigSettings.MaterialName; + + public override string MachineName => PrinterSettings.PrinterSettingsId; + + public override object[] Configs => new object[] { PrinterSettings, MaterialSettings, PrintSettings, OutputConfigSettings }; + #endregion + + #region Overrides + public override string ToString() + { + return $"{nameof(FileFullPath)}: {FileFullPath}, {nameof(MaterialSettings)}: {MaterialSettings}, {nameof(PrintSettings)}: {PrintSettings}, {nameof(OutputConfigSettings)}: {OutputConfigSettings}, {nameof(Statistics)}: {Statistics}, {nameof(LayerCount)}: {LayerCount}, {nameof(TotalHeight)}: {TotalHeight}"; + } + + #endregion + + #region Contructors + public SL1File() { } + #endregion + + #region Static Methods + public static string IniKeyToMemberName(string keyName) + { + string memberName = string.Empty; + string[] objs = keyName.Split('_'); + return objs.Aggregate(memberName, (current, obj) => current + obj.FirstCharToUpper()); + } + + public static string MemberNameToIniKey(string memberName) + { + string iniKey = char.ToLowerInvariant(memberName[0]).ToString(); + for (var i = 1; i < memberName.Length; i++) + { + iniKey += char.IsUpper(memberName[i]) + ? $"_{char.ToLowerInvariant(memberName[i])}" + : memberName[i].ToString(); + } + + + if (iniKey.EndsWith("_")) + iniKey.Remove(iniKey.Length - 1); + + return iniKey; + } + + + #endregion + + #region Methods + public override void Clear() + { + base.Clear(); + Statistics.Clear(); + } + + public override void Encode(string fileFullPath, OperationProgress progress = null) + { + throw new NotImplementedException(); + } + + + public override void Decode(string fileFullPath, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + FileFullPath = fileFullPath; + + PrinterSettings = new Printer(); + MaterialSettings = new Material(); + PrintSettings = new Print(); + OutputConfigSettings = new OutputConfig(); + + Statistics.ExecutionTime.Restart(); + + using (var inputFile = ZipFile.OpenRead(FileFullPath)) + { + + foreach (ZipArchiveEntry entity in inputFile.Entries) + { + if (!entity.Name.EndsWith(".ini")) continue; + using (StreamReader streamReader = new StreamReader(entity.Open())) + { + string line = null; + while ((line = streamReader.ReadLine()) != null) + { + string[] keyValue = line.Split(new[] {'='}, 2); + if (keyValue.Length < 2) continue; + keyValue[0] = keyValue[0].Trim(); + keyValue[1] = keyValue[1].Trim(); + + var fieldName = IniKeyToMemberName(keyValue[0]); + bool foundMember = false; + + foreach (var obj in Configs) + { + var attribute = obj.GetType().GetProperty(fieldName); + if (ReferenceEquals(attribute, null)) continue; + Helpers.SetPropertyValue(attribute, obj, keyValue[1]); + Statistics.ImplementedKeys.Add(keyValue[0]); + foundMember = true; + } + + if (!foundMember) + { + Statistics.MissingKeys.Add(keyValue[0]); + } + } + } + } + + LayerManager = new LayerManager((uint) (OutputConfigSettings.NumSlow + OutputConfigSettings.NumFast)); + + progress.ItemCount = LayerCount; + + foreach (ZipArchiveEntry entity in inputFile.Entries) + { + if (!entity.Name.EndsWith(".png")) continue; + if (entity.Name.StartsWith("thumbnail")) + { + using (Stream stream = entity.Open()) + { + Mat image = new Mat(); + CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); + 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++; + + continue; + } + + // - .png - 5 numbers + string layerStr = entity.Name.Substring(entity.Name.Length - 4 - 5, 5); + uint iLayer = uint.Parse(layerStr); + LayerManager[iLayer] = new Layer(iLayer, entity.Open(), entity.Name) + { + PositionZ = GetHeightFromLayer(iLayer), + ExposureTime = GetInitialLayerValueOrNormal(iLayer, InitialExposureTime, LayerExposureTime) + }; + progress.ProcessedItems++; + } + } + + LayerManager.GetBoundingRectangle(progress); + + Statistics.ExecutionTime.Stop(); + + Debug.WriteLine(Statistics); + } + + /*public override Image GetLayerImage(uint layerIndex) + { + //Stopwatch sw = Stopwatch.StartNew(); + var image = Image.Load(DecompressLayer(Layers[layerIndex])); + //Debug.WriteLine(sw.ElapsedMilliseconds); + + return layerIndex >= LayerCount ? null : image; + //return layerIndex >= LayerCount ? null : Image.Load(LayerEntries[(int)layerIndex].Open()); + //return layerIndex >= LayerCount ? null : Image.Load(DecompressLayer(Layers[layerIndex])); + }*/ + + public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) + { + void UpdateLayers() + { + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + this[layerIndex].ExposureTime = GetInitialLayerValueOrNormal(layerIndex, InitialExposureTime, LayerExposureTime); + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + PrintSettings.FadedLayers = + OutputConfigSettings.NumFade = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + MaterialSettings.InitialExposureTime = + OutputConfigSettings.ExpTimeFirst = value.Convert(); + + UpdateLayers(); + + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + MaterialSettings.ExposureTime = + OutputConfigSettings.ExpTime = value.Convert(); + + UpdateLayers(); + + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + + } + + using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) + { + + //InputFile.CreateEntry("Modified"); + using (TextWriter tw = new StreamWriter(outputFile.PutFileContent("config.ini", string.Empty, ZipArchiveMode.Update).Open())) + { + var properties = OutputConfigSettings.GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var property in properties) + { + var name = char.ToLowerInvariant(property.Name[0]) + property.Name.Substring(1); + tw.WriteLine($"{name} = {property.GetValue(OutputConfigSettings)}"); + } + + tw.Close(); + } + + using (TextWriter tw = new StreamWriter(outputFile.PutFileContent("prusaslicer.ini", string.Empty, ZipArchiveMode.Update).Open())) + { + foreach (var config in Configs) + { + if (ReferenceEquals(config, OutputConfigSettings)) + continue; + + var properties = config.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + + foreach (var property in properties) + { + tw.WriteLine($"{MemberNameToIniKey(property.Name)} = {property.GetValue(config)}"); + } + } + + tw.Close(); + } + + foreach (var layer in this) + { + if (!layer.IsModified) continue; + outputFile.PutFileContent(layer.Filename, layer.CompressedBytes, ZipArchiveMode.Update); + layer.IsModified = false; + } + } + + //Decode(FileFullPath, progress); + + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + if (!IsValid) return false; + + if (to == typeof(ChituboxFile)) + { + ChituboxFile defaultFormat = (ChituboxFile)FindByType(typeof(ChituboxFile)); + ChituboxFile file = new ChituboxFile + { + LayerManager = LayerManager, + HeaderSettings = + { + Version = 2, + BedSizeX = PrinterSettings.DisplayWidth, + BedSizeY = PrinterSettings.DisplayHeight, + BedSizeZ = PrinterSettings.MaxPrintHeight, + OverallHeightMilimeter = TotalHeight, + BottomExposureSeconds = InitialExposureTime, + BottomLayersCount = InitialLayerCount, + BottomLightPWM = LookupCustomValue(Keyword_BottomLightPWM, defaultFormat.HeaderSettings.BottomLightPWM), + LayerCount = LayerCount, + LayerExposureSeconds = LayerExposureTime, + LayerHeightMilimeter = LayerHeight, + LayerOffTime = LookupCustomValue(Keyword_LayerOffTime, defaultFormat.HeaderSettings.LayerOffTime), + LightPWM = LookupCustomValue(Keyword_LightPWM, defaultFormat.HeaderSettings.LightPWM), + PrintTime = (uint) OutputConfigSettings.PrintTime, + ProjectorType = PrinterSettings.DisplayMirrorX || PrinterSettings.DisplayMirrorY ? 1u : 0u, + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + AntiAliasLevel = ValidateAntiAliasingLevel() + }, + PrintParametersSettings = + { + BottomLayerCount = PrintSettings.FadedLayers, + BottomLiftHeight = LookupCustomValue(Keyword_BottomLiftHeight, + defaultFormat.PrintParametersSettings.BottomLiftHeight), + BottomLiftSpeed = LookupCustomValue(Keyword_BottomLiftSpeed, + defaultFormat.PrintParametersSettings.BottomLiftSpeed), + BottomLightOffDelay = LookupCustomValue(Keyword_BottomLightOffDelay, + defaultFormat.PrintParametersSettings.BottomLightOffDelay), + CostDollars = MaterialCost, + LiftHeight = LookupCustomValue(Keyword_LiftHeight, + defaultFormat.PrintParametersSettings.LiftHeight), + LiftSpeed = LookupCustomValue(Keyword_LiftSpeed, + defaultFormat.PrintParametersSettings.LiftSpeed), + LightOffDelay = LookupCustomValue(Keyword_LightOffDelay, + defaultFormat.PrintParametersSettings.LightOffDelay), + RetractSpeed = LookupCustomValue(Keyword_RetractSpeed, + defaultFormat.PrintParametersSettings.RetractSpeed), + VolumeMl = UsedMaterial, + WeightG = (float) Math.Round( + OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 2) + }, + SlicerInfoSettings = {MachineName = MachineName, MachineNameSize = (uint) MachineName.Length} + }; + + + if (LookupCustomValue("FLIP_XY", false, true)) + { + file.HeaderSettings.ResolutionX = PrinterSettings.DisplayPixelsY; + file.HeaderSettings.ResolutionY = PrinterSettings.DisplayPixelsX; + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ChituboxZipFile)) + { + ChituboxZipFile defaultFormat = (ChituboxZipFile)FindByType(typeof(ChituboxZipFile)); + ChituboxZipFile file = new ChituboxZipFile + { + LayerManager = LayerManager, + HeaderSettings = + { + Filename = Path.GetFileName(FileFullPath), + + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + MachineX = PrinterSettings.DisplayWidth, + MachineY = PrinterSettings.DisplayHeight, + MachineZ = PrinterSettings.MaxPrintHeight, + MachineType = MachineName, + ProjectType = PrinterSettings.DisplayMirrorX || PrinterSettings.DisplayMirrorY ? "LCD_mirror" : "Normal", + + Resin = MaterialName, + Price = MaterialCost, + Weight = (float) Math.Round(UsedMaterial * MaterialSettings.MaterialDensity, 2), + Volume = UsedMaterial, + Mirror = (byte) (PrinterSettings.DisplayMirrorX || PrinterSettings.DisplayMirrorY ? 1 : 0), + + + BottomLayerLiftHeight = LookupCustomValue(Keyword_BottomLiftHeight, defaultFormat.HeaderSettings.BottomLayerLiftHeight), + LayerLiftHeight = LookupCustomValue(Keyword_LiftHeight, defaultFormat.HeaderSettings.LayerLiftHeight), + BottomLayerLiftSpeed = LookupCustomValue(Keyword_BottomLiftSpeed, defaultFormat.HeaderSettings.BottomLayerLiftSpeed), + LayerLiftSpeed = LookupCustomValue(Keyword_LiftSpeed, defaultFormat.HeaderSettings.LayerLiftSpeed), + RetractSpeed = LookupCustomValue(Keyword_RetractSpeed, defaultFormat.HeaderSettings.RetractSpeed), + BottomLayCount = InitialLayerCount, + BottomLayerCount = InitialLayerCount, + BottomLightOffTime = LookupCustomValue(Keyword_BottomLightOffDelay, defaultFormat.HeaderSettings.BottomLightOffTime), + LayerLightOffTime = LookupCustomValue(Keyword_LightOffDelay, defaultFormat.HeaderSettings.LayerLightOffTime), + BottomLayExposureTime = InitialExposureTime, + BottomLayerExposureTime = InitialExposureTime, + LayerExposureTime = LayerExposureTime, + LayerHeight = LayerHeight, + LayerCount = LayerCount, + AntiAliasing = ValidateAntiAliasingLevel(), + BottomLightPWM = LookupCustomValue(Keyword_BottomLightPWM, defaultFormat.HeaderSettings.BottomLightPWM), + LayerLightPWM = LookupCustomValue(Keyword_LightPWM, defaultFormat.HeaderSettings.LayerLightPWM), + + EstimatedPrintTime = PrintTime + }, + }; + + + if (LookupCustomValue("FLIP_XY", false, true)) + { + file.HeaderSettings.ResolutionX = PrinterSettings.DisplayPixelsY; + file.HeaderSettings.ResolutionY = PrinterSettings.DisplayPixelsX; + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(PWSFile)) + { + PWSFile defaultFormat = (PWSFile)FindByType(typeof(PWSFile)); + PWSFile file = new PWSFile + { + LayerManager = LayerManager, + HeaderSettings = + { + ResolutionX = ResolutionX, + ResolutionY = ResolutionY, + LayerHeight = LayerHeight, + LayerExposureTime = LayerExposureTime, + LiftHeight = LookupCustomValue(Keyword_LiftHeight, defaultFormat.HeaderSettings.LiftHeight), + LiftSpeed = LookupCustomValue(Keyword_LiftSpeed, defaultFormat.HeaderSettings.LiftSpeed) / 60, + RetractSpeed = LookupCustomValue(Keyword_RetractSpeed, defaultFormat.HeaderSettings.RetractSpeed) / 60, + LayerOffTime = LookupCustomValue(Keyword_LayerOffTime, defaultFormat.HeaderSettings.LayerOffTime), + BottomLayersCount = InitialLayerCount, + BottomExposureSeconds = InitialExposureTime, + Price = MaterialCost, + Volume = UsedMaterial, + Weight = (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 2), + AntiAliasing = ValidateAntiAliasingLevel() + } + }; + + + if (LookupCustomValue("FLIP_XY", false, true)) + { + file.HeaderSettings.ResolutionX = PrinterSettings.DisplayPixelsY; + file.HeaderSettings.ResolutionY = PrinterSettings.DisplayPixelsX; + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(PHZFile)) + { + PHZFile defaultFormat = (PHZFile)FindByType(typeof(PHZFile)); + PHZFile file = new PHZFile + { + LayerManager = LayerManager, + HeaderSettings = + { + Version = 2, + BedSizeX = PrinterSettings.DisplayWidth, + BedSizeY = PrinterSettings.DisplayHeight, + BedSizeZ = PrinterSettings.MaxPrintHeight, + OverallHeightMilimeter = TotalHeight, + BottomExposureSeconds = MaterialSettings.InitialExposureTime, + BottomLayersCount = PrintSettings.FadedLayers, + BottomLightPWM = LookupCustomValue(Keyword_BottomLightPWM, defaultFormat.HeaderSettings.BottomLightPWM), + LayerCount = LayerCount, + LayerExposureSeconds = MaterialSettings.ExposureTime, + LayerHeightMilimeter = PrintSettings.LayerHeight, + LayerOffTime = LookupCustomValue(Keyword_LayerOffTime, defaultFormat.HeaderSettings.LayerOffTime), + LightPWM = LookupCustomValue(Keyword_LightPWM, defaultFormat.HeaderSettings.LightPWM), + PrintTime = (uint) OutputConfigSettings.PrintTime, + ProjectorType = PrinterSettings.DisplayMirrorX || PrinterSettings.DisplayMirrorY ? 1u : 0u, + ResolutionX = PrinterSettings.DisplayPixelsX, + ResolutionY = PrinterSettings.DisplayPixelsY, + BottomLayerCount = PrintSettings.FadedLayers, + BottomLiftHeight = LookupCustomValue(Keyword_BottomLiftHeight, defaultFormat.HeaderSettings.BottomLiftHeight), + BottomLiftSpeed = LookupCustomValue(Keyword_BottomLiftSpeed, defaultFormat.HeaderSettings.BottomLiftSpeed), + BottomLightOffDelay = LookupCustomValue(Keyword_BottomLightOffDelay, defaultFormat.HeaderSettings.BottomLightOffDelay), + CostDollars = MaterialCost, + LiftHeight = LookupCustomValue(Keyword_LiftHeight, defaultFormat.HeaderSettings.LiftHeight), + LiftSpeed = LookupCustomValue(Keyword_LiftSpeed, defaultFormat.HeaderSettings.LiftSpeed), + RetractSpeed = LookupCustomValue(Keyword_RetractSpeed, defaultFormat.HeaderSettings.RetractSpeed), + VolumeMl = OutputConfigSettings.UsedMaterial, + WeightG = (float)Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 2), + MachineName = MachineName, + MachineNameSize = (uint)MachineName.Length, + AntiAliasLevelInfo = ValidateAntiAliasingLevel() + } + }; + + if (LookupCustomValue("FLIP_XY", false, true)) + { + file.HeaderSettings.ResolutionX = ResolutionY; + file.HeaderSettings.ResolutionY = ResolutionX; + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + + return true; + } + + if (to == typeof(ZCodexFile)) + { + ZCodexFile defaultFormat = (ZCodexFile)FindByType(typeof(ZCodexFile)); + TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); + ZCodexFile file = new ZCodexFile + { + ResinMetadataSettings = new ZCodexFile.ResinMetadata + { + MaterialId = 2, + Material = MaterialName, + AdditionalSupportLayerTime = 0, + BottomLayersNumber = InitialLayerCount, + BottomLayersTime = (uint)(InitialExposureTime*1000), + LayerTime = (uint)(LayerExposureTime * 1000), + DisableSettingsChanges = false, + LayerThickness = LayerHeight, + PrintTime = (uint)PrintTime, + TotalLayersCount = LayerCount, + TotalMaterialVolumeUsed = UsedMaterial, + TotalMaterialWeightUsed = UsedMaterial, + }, + UserSettings = new ZCodexFile.UserSettingsdata + { + Printer = MachineName, + BottomLayersCount = InitialLayerCount, + PrintTime = $"{ts.Hours}h {ts.Minutes}m", + LayerExposureTime = (uint)(LayerExposureTime * 1000), + BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), + MaterialId = 2, + LayerThickness = $"{LayerHeight} mm", + AntiAliasing = (byte) (ValidateAntiAliasingLevel() > 1 ? 1 : 0), + CrossSupportEnabled = 1, + ExposureOffTime = LookupCustomValue(Keyword_LayerOffTime, defaultFormat.UserSettings.ExposureOffTime) * 1000, + HollowEnabled = PrintSettings.HollowingEnable ? (byte)1 : (byte)0, + HollowThickness = PrintSettings.HollowingMinThickness, + InfillDensity = 0, + IsAdvanced = 0, + MaterialType = MaterialName, + MaterialVolume = UsedMaterial, + MaxLayer = LayerCount-1, + ModelLiftEnabled = PrintSettings.SupportObjectElevation > 0 ? (byte)1 : (byte)0, + ModelLiftHeight = PrintSettings.SupportObjectElevation, + RaftEnabled = PrintSettings.SupportBaseHeight > 0 ? (byte)1 : (byte)0, + RaftHeight = PrintSettings.SupportBaseHeight, + RaftOffset = 0, + SupportAdditionalExposureEnabled = 0, + SupportAdditionalExposureTime = 0, + XCorrection = PrinterSettings.AbsoluteCorrection, + YCorrection = PrinterSettings.AbsoluteCorrection, + ZLiftDistance = (float)Math.Round(LookupCustomValue(Keyword_LiftHeight, defaultFormat.UserSettings.ZLiftDistance), 2), + ZLiftFeedRate = (float)Math.Round(LookupCustomValue(Keyword_LiftSpeed, defaultFormat.UserSettings.ZLiftFeedRate), 2), + ZLiftRetractRate = (float)Math.Round(LookupCustomValue(Keyword_RetractSpeed, defaultFormat.UserSettings.ZLiftRetractRate), 2), + }, + ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata + { + PrintTime = (uint)PrintTime, + PrinterName = MachineName, + Materials = new List + { + new ZCodexFile.ZCodeMetadata.MaterialsData + { + Name = MaterialName, + ExtruderType = "MAIN", + Id = 0, + Usage = 0, + Temperature = 0 + } + }, + }, + LayerManager = LayerManager + }; + + float usedMaterial = UsedMaterial / LayerCount; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData + { + Layer = layerIndex, + UsedMaterialVolume = usedMaterial + }); + } + + file.SetThumbnails(Thumbnails); + file.Encode(fileFullPath, progress); + return true; + } + + if (to == typeof(CWSFile)) + { + CWSFile defaultFormat = (CWSFile)FindByType(typeof(CWSFile)); + CWSFile file = new CWSFile {LayerManager = LayerManager}; + + file.SliceSettings.Xppm = file.OutputSettings.PixPermmX = (float) Math.Round(LookupCustomValue("Xppm", defaultFormat.SliceSettings.Xppm), 3); + file.SliceSettings.Yppm = file.OutputSettings.PixPermmY = (float) Math.Round(LookupCustomValue("Yppm", defaultFormat.SliceSettings.Xppm), 3); + file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort)ResolutionX; + file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort)ResolutionY; + file.SliceSettings.Thickness = file.OutputSettings.LayerThickness = LayerHeight; + file.SliceSettings.LayersNum = file.OutputSettings.LayersNum = LayerCount; + file.SliceSettings.HeadLayersNum = file.OutputSettings.NumberBottomLayers = InitialLayerCount; + file.SliceSettings.LayersExpoMs = file.OutputSettings.LayerTime = (uint) LayerExposureTime * 1000; + file.SliceSettings.HeadLayersExpoMs = file.OutputSettings.BottomLayersTime = (uint) InitialExposureTime * 1000; + file.SliceSettings.WaitBeforeExpoMs = LookupCustomValue("WaitBeforeExpoMs", file.SliceSettings.WaitBeforeExpoMs); + file.SliceSettings.LiftDistance = file.OutputSettings.LiftDistance = (float) Math.Round(LookupCustomValue(Keyword_LiftHeight, file.SliceSettings.LiftDistance), 2); + file.SliceSettings.LiftUpSpeed = file.OutputSettings.ZLiftFeedRate = file.OutputSettings.ZBottomLiftFeedRate = (float) Math.Round(LookupCustomValue(Keyword_LiftSpeed, file.SliceSettings.LiftUpSpeed), 2); + file.SliceSettings.LiftDownSpeed = file.OutputSettings.ZLiftRetractRate = (float) Math.Round(LookupCustomValue(Keyword_RetractSpeed, file.SliceSettings.LiftDownSpeed), 2); + file.SliceSettings.LiftWhenFinished = LookupCustomValue("LiftWhenFinished", file.SliceSettings.LiftWhenFinished); + + file.OutputSettings.BlankingLayerTime = LookupCustomValue("BlankingLayerTime", file.OutputSettings.BlankingLayerTime); + //file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.OutlineWidthInset = 0; + //file.OutputSettings.OutlineWidthOutset = 0; + file.OutputSettings.RenderOutlines = false; + //file.OutputSettings.TiltValue = 0; + //file.OutputSettings.UseMainliftGCodeTab = false; + //file.OutputSettings.AntiAliasing = 0; + //file.OutputSettings.AntiAliasingValue = 0; + file.OutputSettings.FlipX = PrinterSettings.DisplayMirrorX; + file.OutputSettings.FlipY = PrinterSettings.DisplayMirrorY; + file.OutputSettings.AntiAliasingValue = ValidateAntiAliasingLevel(); + file.OutputSettings.AntiAliasing = file.OutputSettings.AntiAliasingValue > 1; + + + + if (LookupCustomValue("FLIP_XY", false, true)) + { + file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort) ResolutionY; + file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort) ResolutionX; + } + + file.Encode(fileFullPath, progress); + + return true; + } + + return false; + } + + public T LookupCustomValue(string name, T defaultValue, bool existsOnly = false) + { + if (string.IsNullOrEmpty(PrinterSettings.PrinterNotes)) return defaultValue; + string result = string.Empty; + if(!existsOnly) + name += '_'; + + int index = PrinterSettings.PrinterNotes.IndexOf(name, StringComparison.Ordinal); + int startIndex = index + name.Length; + + if (index < 0 || PrinterSettings.PrinterNotes.Length < startIndex) return defaultValue; + if (existsOnly) return "true".Convert(); + for (int i = startIndex; i < PrinterSettings.PrinterNotes.Length; i++) + { + char c = PrinterSettings.PrinterNotes[i]; + if (!char.IsLetterOrDigit(c) && c != '.') + { + break; + } + + result += c; + } + + return string.IsNullOrWhiteSpace(result) ? defaultValue : result.Convert(); + } + + #endregion + } +} diff --git a/UVtools.Core/FileFormats/ZCodexFile.cs b/UVtools.Core/FileFormats/ZCodexFile.cs new file mode 100644 index 0000000..c5a28bd --- /dev/null +++ b/UVtools.Core/FileFormats/ZCodexFile.cs @@ -0,0 +1,533 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Text; +using System.Text.RegularExpressions; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Util; +using Newtonsoft.Json; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats +{ + public class ZCodexFile : FileFormat + { + #region Constants + + private const string GCodeStart = "G28\nG21\nG91\nM17\n"; + private const string GCodeKeywordSlice = ""; + private const string GCodeKeywordDelayBlank = ""; + private const string GCodeKeywordDelayModel = ""; + private const string GCodeKeywordDelaySupportPart = ""; + private const string GCodeKeywordDelaySupportFull = ""; + private const string FolderImages = "ResinSlicesData"; + private const string FolderImageName = "Slice"; + #endregion + + #region Sub Classes + + public class ResinMetadata + { + public class LayerData + { + public uint Layer { get; set; } + public float UsedMaterialVolume { get; set; } + + } + + public string Guid { get; set; } = "07452AC2-7494-4576-BA60-BFEA8815F917"; + public string Material { get; set; } + public uint MaterialId { get; set; } + public float LayerThickness { get; set; } + public uint PrintTime { get; set; } + public uint LayerTime { get; set; } + public uint BottomLayersTime { get; set; } + public uint AdditionalSupportLayerTime { get; set; } + public ushort BottomLayersNumber { get; set; } + public uint BlankingLayerTime { get; set; } + public float TotalMaterialVolumeUsed { get; set; } + public float TotalMaterialWeightUsed { get; set; } + public uint TotalLayersCount { get; set; } + public bool DisableSettingsChanges { get; set; } + + public List Layers { get; set; } = new List(); + } + + public class UserSettingsdata + { + public uint MaxLayer { get; set; } + public string PrintTime { get; set; } + public float MaterialVolume { get; set; } + public byte IsAdvanced { get; set; } + public string Printer { get; set; } = "Zortrax Inkspire"; + public string MaterialType { get; set; } + public uint MaterialId { get; set; } + public string LayerThickness { get; set; } + public byte RaftEnabled { get; set; } + public float RaftHeight { get; set; } + public float RaftOffset { get; set; } + public byte ModelLiftEnabled { get; set; } + public float ModelLiftHeight { get; set; } + public byte CrossSupportEnabled { get; set; } + public uint LayerExposureTime { get; set; } + //public uint LayerThicknessesDisplayTime { get; set; } arr + public uint ExposureOffTime { get; set; } = 5; + public uint BottomLayerExposureTime { get; set; } + public uint BottomLayersCount { get; set; } + public byte SupportAdditionalExposureEnabled { get; set; } + public uint SupportAdditionalExposureTime { get; set; } + public float ZLiftDistance { get; set; } = 5; + public float ZLiftRetractRate { get; set; } = 100; + public float ZLiftFeedRate { get; set; } = 100; + public byte AntiAliasing { get; set; } = 0; + public float XCorrection { get; set; } + public float YCorrection { get; set; } + public byte HollowEnabled { get; set; } + public float HollowThickness { get; set; } + public byte InfillDensity { get; set; } + } + + public class ZCodeMetadata + { + public class MaterialsData + { + public string ExtruderType { get; set; } + public uint Id { get; set; } + public string Name { get; set; } + public uint Usage { get; set; } + public uint Temperature { get; set; } + } + + public string ZCodexVersion { get; set; } = "2.0.0.0"; + public string SoftwareVersion { get; set; } = "2.12.2.0"; + public string MinFirmwareVersion { get; set; } = "20013"; + public uint PrinterModelEnumId { get; set; } = 40; + public string PrinterName { get; set; } = "Inkspire"; + public List Materials { get; set; } + public byte HeatbedTemperature { get; set; } + public byte ChamberTemperature { get; set; } + public uint CommandCount { get; set; } + public uint PrintTime { get; set; } + public float NozzleDiameter { get; set; } + public string PrintBoundingBox { get; set; } + public string Pauses { get; set; } + public string MaterialUsages { get; set; } + } + + public class LayerData + { + public int SupportLayerFileIndex { get; set; } = -1; + public int LayerFileIndex { get; set; } = -1; + public ZipArchiveEntry SupportLayerEntry { get; set; } + public ZipArchiveEntry LayerEntry { get; set; } + + public bool HaveSupportLayer => !ReferenceEquals(SupportLayerEntry, null); + } + + #endregion + + #region Properties + public ResinMetadata ResinMetadataSettings { get; set; } = new ResinMetadata(); + public UserSettingsdata UserSettings { get; set; } = new UserSettingsdata(); + public ZCodeMetadata ZCodeMetadataSettings { get; set; } = new ZCodeMetadata(); + + public List LayersSettings { get; } = new List(); + + public override FileFormatType FileType => FileFormatType.Archive; + + public override FileExtension[] FileExtensions { get; } = { + new FileExtension("zcodex", "Z-Suite ZCodex Files") + }; + + public override Type[] ConvertToFormats { get; } = null; + + public override PrintParameterModifier[] PrintParameterModifiers { get; } = { + PrintParameterModifier.InitialLayerCount, + PrintParameterModifier.InitialExposureSeconds, + PrintParameterModifier.ExposureSeconds, + + + PrintParameterModifier.LiftHeight, + PrintParameterModifier.RetractSpeed, + PrintParameterModifier.LiftSpeed, + }; + + public override byte ThumbnailsCount { get; } = 1; + + public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(320, 180)}; + + public override uint ResolutionX => 1440; + + public override uint ResolutionY => 2560; + public override byte AntiAliasing => UserSettings.AntiAliasing; + + public override float LayerHeight => ResinMetadataSettings.LayerThickness; + + public override ushort InitialLayerCount => ResinMetadataSettings.BottomLayersNumber; + + public override float InitialExposureTime => UserSettings.BottomLayerExposureTime / 1000f; + + public override float LayerExposureTime => UserSettings.LayerExposureTime / 1000f; + public override float LiftHeight => UserSettings.ZLiftDistance; + + public override float LiftSpeed => UserSettings.ZLiftFeedRate; + + public override float RetractSpeed => UserSettings.ZLiftRetractRate; + + public override float PrintTime => ResinMetadataSettings.PrintTime; + + public override float UsedMaterial => ResinMetadataSettings.TotalMaterialVolumeUsed; + + public override float MaterialCost => 0; + + public override string MaterialName => ResinMetadataSettings.Material; + + public override string MachineName => ZCodeMetadataSettings.PrinterName; + + public override object[] Configs => new[] {(object) ResinMetadataSettings, UserSettings, ZCodeMetadataSettings}; + #endregion + + #region Methods + + public override void Clear() + { + base.Clear(); + LayersSettings.Clear(); + } + + public override void Encode(string fileFullPath, OperationProgress progress = null) + { + base.Encode(fileFullPath, progress); + using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) + { + outputFile.PutFileContent("ResinMetadata", JsonConvert.SerializeObject(ResinMetadataSettings), ZipArchiveMode.Create); + outputFile.PutFileContent("UserSettingsData", JsonConvert.SerializeObject(UserSettings), ZipArchiveMode.Create); + outputFile.PutFileContent("ZCodeMetadata", JsonConvert.SerializeObject(ZCodeMetadataSettings), ZipArchiveMode.Create); + + if (CreatedThumbnailsCount > 0) + { + using (Stream stream = outputFile.CreateEntry("Preview.png").Open()) + { + var vec = new VectorOfByte(); + CvInvoke.Imencode(".png", Thumbnails[0], vec); + stream.WriteBytes(vec.ToArray()); + stream.Close(); + } + } + + GCode = new StringBuilder(GCodeStart); + + float lastZPosition = 0; + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + + Layer layer = this[layerIndex]; + GCode.AppendLine($"{GCodeKeywordSlice} {layerIndex}"); + + if (lastZPosition != layer.PositionZ) + { + if (LiftHeight > 0) + { + GCode.AppendLine($"G1 Z{LiftHeight} F{LiftSpeed}"); + GCode.AppendLine($"G1 Z-{LiftHeight - layer.PositionZ + lastZPosition} F{RetractSpeed}"); + } + else + { + GCode.AppendLine($"G1 Z{layer.PositionZ- lastZPosition} F{LiftSpeed}"); + } + } + /*else + { + //GCode.AppendLine($";G1 Z{LiftHeight} F{LiftSpeed}; Already here"); + //GCode.AppendLine($";G1 Z-{LiftHeight - layer.PositionZ + lastZPosition} F{RetractSpeed}; Already here"); + }*/ + + //GCode.AppendLine($"G1 Z{LiftHeight} F{LiftSpeed}"); + //GCode.AppendLine($"G1 Z-{LiftHeight - LayerHeight} F{RetractSpeed}"); + GCode.AppendLine(GCodeKeywordDelayBlank); + GCode.AppendLine("M106 S255"); + GCode.AppendLine(GCodeKeywordDelayModel); + GCode.AppendLine("M106 S0"); + + + var layerimagePath = $"{FolderImages}/{FolderImageName}{layerIndex:D5}.png"; + using (Stream stream = outputFile.CreateEntry(layerimagePath).Open()) + { + //image.Save(stream, Helpers.PngEncoder); + var byteArr = this[layerIndex].CompressedBytes; + stream.Write(byteArr, 0, byteArr.Length); + stream.Close(); + } + + lastZPosition = layer.PositionZ; + + progress++; + } + + GCode.AppendLine($"G1 Z40.0 F{UserSettings.ZLiftFeedRate}"); + GCode.AppendLine("M18"); + + outputFile.PutFileContent("ResinGCodeData", GCode.ToString(), ZipArchiveMode.Create); + } + } + + public override void Decode(string fileFullPath, OperationProgress progress = null) + { + base.Decode(fileFullPath, progress); + + FileFullPath = fileFullPath; + using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) + { + var entry = inputFile.GetEntry("ResinMetadata"); + if (ReferenceEquals(entry, null)) + { + Clear(); + throw new FileLoadException("ResinMetadata not found", fileFullPath); + } + + ResinMetadataSettings = Helpers.JsonDeserializeObject(entry.Open()); + + entry = inputFile.GetEntry("UserSettingsData"); + if (ReferenceEquals(entry, null)) + { + Clear(); + throw new FileLoadException("UserSettingsData not found", fileFullPath); + } + + UserSettings = Helpers.JsonDeserializeObject(entry.Open()); + + entry = inputFile.GetEntry("ZCodeMetadata"); + if (ReferenceEquals(entry, null)) + { + Clear(); + throw new FileLoadException("ZCodeMetadata not found", fileFullPath); + } + + ZCodeMetadataSettings = Helpers.JsonDeserializeObject(entry.Open()); + + entry = inputFile.GetEntry("ResinGCodeData"); + if (ReferenceEquals(entry, null)) + { + Clear(); + throw new FileLoadException("ResinGCodeData not found", fileFullPath); + } + + LayerManager = new LayerManager(ResinMetadataSettings.TotalLayersCount); + GCode = new StringBuilder(); + using (TextReader tr = new StreamReader(entry.Open())) + { + string line; + int layerIndex = 0; + int layerFileIndex = 0; + string layerimagePath = null; + float currentHeight = 0; + while (!ReferenceEquals(line = tr.ReadLine(), null)) + { + GCode.AppendLine(line); + if (line.StartsWith(GCodeKeywordSlice)) + { + layerFileIndex = int.Parse(line.Substring(GCodeKeywordSlice.Length)); + layerimagePath = $"{FolderImages}/{FolderImageName}{layerFileIndex:D5}.png"; + if (LayersSettings.Count - 1 < layerIndex) LayersSettings.Add(new LayerData()); + continue; + } + + if (line.StartsWith(GCodeKeywordDelaySupportPart)) + { + LayersSettings[layerIndex].SupportLayerFileIndex = layerFileIndex; + LayersSettings[layerIndex].SupportLayerEntry = inputFile.GetEntry(layerimagePath); + continue; + } + + /* + * + 0 +G1 Z5.0 F100.0 +G1 Z-4.9 F100.0 + +M106 S255 + +M106 S0 + */ + + var gcode = GCode.ToString(); + + if (line.StartsWith(GCodeKeywordDelaySupportFull) || line.StartsWith(GCodeKeywordDelayModel)) + { + var startStr = $"{GCodeKeywordSlice} {layerIndex}"; + var stripGcode = gcode.Substring(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length).Trim(' ', '\n', '\r', '\t'); + + var currPos = Regex.Match(stripGcode, "G1 Z([+-]?([0-9]*[.])?[0-9]+)", RegexOptions.IgnoreCase); + //var exposureTime = Regex.Match(stripGcode, "; (\\d+)", RegexOptions.IgnoreCase); + /*var pwm = Regex.Match(stripGcode, "M106 S(\\d+)", RegexOptions.IgnoreCase); + if (layerIndex < InitialLayerCount) + { + OutputSettings.BottomLayerLightPWM = byte.Parse(pwm.Groups[1].Value); + } + else + { + OutputSettings.LayerLightPWM = byte.Parse(pwm.Groups[1].Value); + }*/ + if (currPos.Success) + { + var nextMatch = currPos.NextMatch(); + if (nextMatch.Success) + { + currentHeight = (float)Math.Round(currentHeight + float.Parse(currPos.Groups[1].Value) + float.Parse(currPos.NextMatch().Groups[1].Value), 2); + } + else + { + currentHeight = (float)Math.Round(currentHeight + float.Parse(currPos.Groups[1].Value), 2); + } + + } + + LayersSettings[layerIndex].LayerFileIndex = layerFileIndex; + LayersSettings[layerIndex].LayerEntry = inputFile.GetEntry(layerimagePath); + this[layerIndex] = new Layer((uint) layerIndex, LayersSettings[layerIndex].LayerEntry.Open(), LayersSettings[layerIndex].LayerEntry.Name) + { + PositionZ = currentHeight, + ExposureTime = GetInitialLayerValueOrNormal((uint) layerIndex, InitialExposureTime, LayerExposureTime) + }; + layerIndex++; + + progress++; + } + } + + tr.Close(); + } + + entry = inputFile.GetEntry("Preview.png"); + if (!ReferenceEquals(entry, null)) + { + using (Stream stream = entry.Open()) + { + + CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, Thumbnails[0]); + stream.Close(); + } + } + } + + LayerManager.GetBoundingRectangle(progress); + } + + public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) + { + void UpdateLayers() + { + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + this[layerIndex].ExposureTime = GetInitialLayerValueOrNormal(layerIndex, InitialExposureTime, LayerExposureTime); + } + } + + if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) + { + UserSettings.BottomLayersCount = + ResinMetadataSettings.BottomLayersNumber = value.Convert(); + UpdateLayers(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) + { + ResinMetadataSettings.BottomLayersTime = + UserSettings.BottomLayerExposureTime = value.Convert()*1000; + + UpdateLayers(); + + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) + { + ResinMetadataSettings.LayerTime = + UserSettings.LayerExposureTime = value.Convert()*1000; + + UpdateLayers(); + + return true; + } + + if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) + { + UserSettings.ZLiftDistance = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) + { + UserSettings.ZLiftFeedRate = value.Convert(); + UpdateGCode(); + return true; + } + if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) + { + UserSettings.ZLiftRetractRate = value.Convert(); + UpdateGCode(); + return true; + } + + return false; + } + + public override void SaveAs(string filePath = null, OperationProgress progress = null) + { + if (!string.IsNullOrEmpty(filePath)) + { + File.Copy(FileFullPath, filePath, true); + FileFullPath = filePath; + + } + + using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) + { + outputFile.PutFileContent("ResinMetadata", JsonConvert.SerializeObject(ResinMetadataSettings), ZipArchiveMode.Update); + outputFile.PutFileContent("UserSettingsData", JsonConvert.SerializeObject(UserSettings), ZipArchiveMode.Update); + outputFile.PutFileContent("ZCodeMetadata", JsonConvert.SerializeObject(ZCodeMetadataSettings), ZipArchiveMode.Update); + outputFile.PutFileContent("ResinGCodeData", GCode.ToString(), ZipArchiveMode.Update); + + foreach (var layer in this) + { + if (!layer.IsModified) continue; + outputFile.PutFileContent(layer.Filename, layer.CompressedBytes, ZipArchiveMode.Update); + layer.IsModified = false; + } + } + + //Decode(FileFullPath, progress); + } + + public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) + { + throw new NotImplementedException(); + } + + private void UpdateGCode() + { + var gcode = GCode.ToString(); + gcode = Regex.Replace(gcode, @"Z[+]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", + $"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.ZLiftRetractRate}"); + + GCode.Clear(); + GCode.Append(gcode); + + } + #endregion + } +} diff --git a/UVtools.Core/IFileFormat.cs b/UVtools.Core/IFileFormat.cs deleted file mode 100644 index 058298d..0000000 --- a/UVtools.Core/IFileFormat.cs +++ /dev/null @@ -1,354 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.Text; -using Emgu.CV; - -namespace UVtools.Core -{ - /// - /// Slicer file format representation interface - /// - public interface IFileFormat - { - #region Properties - /// - /// Gets the file format type - /// - FileFormat.FileFormatType FileType { get; } - - /// - /// Gets the valid file extensions for this - /// - FileExtension[] FileExtensions { get; } - - /// - /// Gets the implemented file formats able to convert to - /// - Type[] ConvertToFormats { get; } - - /// - /// Gets the available - /// - FileFormat.PrintParameterModifier[] PrintParameterModifiers { get; } - - /// - /// Gets the file filter for open and save dialogs - /// - string FileFilter { get; } - - /// - /// Gets all valid file extensions in "*.extension1;*.extension2" format - /// - - string FileFilterExtensionsOnly { get; } - - /// - /// Gets the input file path loaded into this - /// - string FileFullPath { get; set; } - - /// - /// Gets the thumbnails count present in this file format - /// - byte ThumbnailsCount { get; } - - /// - /// Gets the number of created thumbnails - /// - byte CreatedThumbnailsCount { get; } - - /// - /// Gets the original thumbnail sizes - /// - System.Drawing.Size[] ThumbnailsOriginalSize { get; } - - /// - /// Gets the thumbnails for this - /// - Mat[] Thumbnails { get; set; } - - /// - /// Gets the cached layers into compressed bytes - /// - LayerManager LayerManager { get; set; } - - /// - /// Gets the image width resolution - /// - uint ResolutionX { get; } - - /// - /// Gets the image height resolution - /// - uint ResolutionY { get; } - - bool HaveAntiAliasing { get; } - - /// - /// Gets the AntiAliasing level - /// - byte AntiAliasing { get; } - - /// - /// Gets Layer Height in mm - /// - float LayerHeight { get; } - - /// - /// Gets Total Height in mm - /// - float TotalHeight { get; } - - /// - /// Gets the number of layers present in this file - /// - uint LayerCount { get; } - - /// - /// Gets the number of initial layer count - /// - /// - ushort InitialLayerCount { get; } - - /// - /// Gets the initial exposure time for - /// - float InitialExposureTime { get; } - - /// - /// Gets the normal layer exposure time - /// - float LayerExposureTime { get; } - - /// - /// Gets the speed in mm/min for the detracts - /// - float LiftSpeed { get; } - - /// - /// Gets the height in mm to retract between layers - /// - float LiftHeight { get; } - - /// - /// Gets the speed in mm/min for the retracts - /// - float RetractSpeed { get; } - - /// - /// Gets the estimate print time in seconds - /// - float PrintTime { get; } - - /// - /// Gets the estimate used material in ml - /// - float UsedMaterial { get; } - - /// - /// Gets the estimate material cost - /// - float MaterialCost { get; } - - /// - /// Gets the material name - /// - string MaterialName { get; } - - /// - /// Gets the machine name - /// - string MachineName { get; } - - /// - /// Gets the GCode, returns null if not supported - /// - StringBuilder GCode { get; set; } - - /// - /// Get all configuration objects with properties and values - /// - object[] Configs { get; } - - /// - /// Gets if this file is valid to read - /// - bool IsValid { get; } - - #endregion - - #region Methods - /// - /// Clears all definitions and properties, it also dispose valid candidates - /// - void Clear(); - - /// - /// Validate if a file is a valid - /// - /// Full file path - void FileValidation(string fileFullPath); - - /// - /// Checks if a extension is valid under the - /// - /// Extension to check - /// True if is a full file path, otherwise false for extension only - /// True if valid, otherwise false - bool IsExtensionValid(string extension, bool isFilePath = false); - - /// - /// Gets all valid file extensions in a specified format - /// - - string GetFileExtensions(string prepend = ".", string separator = ", "); - - /// - /// Gets a thumbnail by it height or lower - /// - /// Max height allowed - /// - Mat GetThumbnail(uint maxHeight = 400); - - /// - /// Sets thumbnails from a list of thumbnails and clone them - /// - /// - void SetThumbnails(Mat[] images); - - /// - /// Sets all thumbnails the same image - /// - /// Image to set - void SetThumbnails(Mat images); - - /// - /// Encode to an output file - /// - /// Output file - /// - void Encode(string fileFullPath, OperationProgress progress = null); - - /* - /// - /// Begin encode to an output file - /// - /// Output file - //void BeginEncode(string fileFullPath); - - /// - /// Insert a layer image to be encoded - /// - /// - /// - //void InsertLayerImageEncode(Image image, uint layerIndex); - - /// - /// Finish the encoding procedure - /// - //void EndEncode();*/ - - /// - /// Decode a slicer file - /// - /// - /// - void Decode(string fileFullPath, OperationProgress progress = null); - - /// - /// Extract contents to a folder - /// - /// Path to folder where content will be extracted - /// - /// - /// - void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true, - OperationProgress progress = null); - - /// - /// Get height in mm from layer height - /// - /// - /// - /// The height in mm - float GetHeightFromLayer(uint layerIndex, bool realHeight = true); - - /// - /// Gets the value for initial layer or normal layers based on layer index - /// - /// Type of value - /// Layer index - /// Initial value - /// Normal value - /// - T GetInitialLayerValueOrNormal(uint layerIndex, T initialLayerValue, T normalLayerValue); - - /// - /// Gets the value attributed to - /// - /// Modifier to use - /// A value - object GetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier); - - /// - /// Sets a property value attributed to - /// - /// Modifier to use - /// Value to set - /// True if set, otherwise false = not found - bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, object value); - - /// - /// Sets a property value attributed to - /// - /// Modifier to use - /// Value to set - /// True if set, otherwise false = not found - bool SetValueFromPrintParameterModifier(FileFormat.PrintParameterModifier modifier, string value); - - /// - /// Saves current configuration on input file - /// - /// - void Save(OperationProgress progress = null); - - /// - /// Saves current configuration on a copy - /// - /// File path to save copy as, use null to overwrite active file (Same as ) - /// - void SaveAs(string filePath = null, OperationProgress progress = null); - - /// - /// Converts this file type to another file type - /// - /// Target file format - /// Output path file - /// - /// True if convert succeed, otherwise false - bool Convert(Type to, string fileFullPath, OperationProgress progress = null); - - /// - /// Converts this file type to another file type - /// - /// Target file format - /// Output path file - /// - /// True if convert succeed, otherwise false - bool Convert(FileFormat to, string fileFullPath, OperationProgress progress = null); - - /// - /// Validate AntiAlias Level - /// - byte ValidateAntiAliasingLevel(); - - #endregion - } -} diff --git a/UVtools.Core/ImageFile.cs b/UVtools.Core/ImageFile.cs deleted file mode 100644 index a9c6ac5..0000000 --- a/UVtools.Core/ImageFile.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using Emgu.CV; -using Emgu.CV.CvEnum; -using Size = System.Drawing.Size; - -namespace UVtools.Core -{ - public class ImageFile : FileFormat - { - public override FileFormatType FileType { get; } = FileFormatType.Binary; - - public override FileExtension[] FileExtensions { get; } = - { - new FileExtension("jpg", "JPG"), - new FileExtension("jpeg", "JPEG"), - new FileExtension("png", "PNG"), - new FileExtension("bmp", "BMP"), - new FileExtension("gif", "GIF"), - new FileExtension("tga", "TGA"), - }; - - public override Type[] ConvertToFormats { get; } = null; - public override PrintParameterModifier[] PrintParameterModifiers { get; } = null; - public override byte ThumbnailsCount { get; } = 4; - public override Size[] ThumbnailsOriginalSize { get; } = null; - public override uint ResolutionX => (uint)ImageMat.Width; - public override uint ResolutionY => (uint)ImageMat.Height; - public override byte AntiAliasing { get; } = 1; - public override float LayerHeight { get; } = 0; - public override ushort InitialLayerCount { get; } = 1; - public override float InitialExposureTime { get; } = 0; - public override float LayerExposureTime { get; } = 0; - public override float LiftHeight { get; } = 0; - public override float RetractSpeed { get; } = 0; - public override float LiftSpeed { get; } = 0; - public override float PrintTime { get; } = 0; - public override float UsedMaterial { get; } = 0; - public override float MaterialCost { get; } = 0; - public override string MaterialName { get; } = null; - public override string MachineName { get; } = null; - public override object[] Configs { get; } = null; - - private Mat ImageMat { get; set; } - - public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) - { - throw new NotImplementedException(); - } - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - ImageMat = CvInvoke.Imread(fileFullPath, ImreadModes.AnyColor); - const byte startDivisor = 2; - for (int i = 0; i < ThumbnailsCount; i++) - { - Thumbnails[i] = new Mat(); - var divisor = (i + 1) * startDivisor; - CvInvoke.Resize(ImageMat, Thumbnails[i], - new Size(ImageMat.Width / divisor, ImageMat.Height / divisor)); - } - - if (ImageMat.NumberOfChannels > 1) - { - CvInvoke.CvtColor(ImageMat, ImageMat, ColorConversion.Bgr2Gray); - } - - LayerManager = new LayerManager(1); - this[0] = new Layer(0, ImageMat, Path.GetFileName(fileFullPath)); - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - this[0].LayerMat.Save(filePath ?? FileFullPath); - } - - public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) - { - throw new NotImplementedException(); - } - - - } -} diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs new file mode 100644 index 0000000..1842d73 --- /dev/null +++ b/UVtools.Core/Layer/Layer.cs @@ -0,0 +1,692 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Text; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Emgu.CV.Util; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core +{ + /// + /// Represent a Layer + /// + public class Layer : IEquatable, IEquatable + { + #region Properties + + /// + /// Gets the parent layer manager + /// + public LayerManager ParentLayerManager { get; set; } + + /// + /// Gets the number of non zero pixels on this layer image + /// + public uint NonZeroPixelCount { get; private protected set; } + + /// + /// Gets the bounding rectangle for the image area + /// + public Rectangle BoundingRectangle { get; private protected set; } = Rectangle.Empty; + + /// + /// Gets the layer index + /// + public uint Index { get; } + + /// + /// Gets or sets the exposure time in seconds + /// + public float ExposureTime { get; set; } + + /// + /// Gets or sets the layer position on Z in mm + /// + public float PositionZ { get; set; } + + private byte[] _compressedBytes; + /// + /// Gets or sets layer image compressed data + /// + public byte[] CompressedBytes + { + get => LayerManager.DecompressLayer(_compressedBytes); + set + { + _compressedBytes = LayerManager.CompressLayer(value); + IsModified = true; + if (!ReferenceEquals(ParentLayerManager, null)) + ParentLayerManager.BoundingRectangle = Rectangle.Empty; + } + } + + /// + /// Gets the original filename, null if no filename attached with layer + /// + public string Filename { get; set; } + + /// + /// Gets if layer has been modified + /// + public bool IsModified { get; set; } + + /// + /// Gets or sets a new image instance + /// + public Mat LayerMat + { + get + { + Mat mat = new Mat(); + CvInvoke.Imdecode(CompressedBytes, ImreadModes.Grayscale, mat); + return mat; + } + set + { + using (var vector = new VectorOfByte()) + { + CvInvoke.Imencode(".png", value, vector); + CompressedBytes = vector.ToArray(); + + GetBoundingRectangle(value, true); + } + } + } + + /// + /// Gets a new Brg image instance + /// + public Mat BrgMat + { + get + { + Mat mat = LayerMat; + CvInvoke.CvtColor(mat, mat, ColorConversion.Gray2Bgr); + return mat; + } + } + + #endregion + + #region Constructor + public Layer(uint index, byte[] compressedBytes, string filename = null, LayerManager pararentLayerManager = null) + { + ParentLayerManager = pararentLayerManager; + Index = index; + Filename = filename ?? $"Layer{index}.png"; + CompressedBytes = compressedBytes; + IsModified = false; + /*if (compressedBytes.Length > 0) + { + GetBoundingRectangle(); + }*/ + } + + public Layer(uint index, Mat layerMat, string filename = null, LayerManager pararentLayerManager = null) : this(index, new byte[0], filename, pararentLayerManager) + { + LayerMat = layerMat; + IsModified = false; + } + + + public Layer(uint index, Stream stream, string filename = null, LayerManager pararentLayerManager = null) : this(index, stream.ToArray(), filename, pararentLayerManager) + { } + #endregion + + #region Equatables + + public static bool operator ==(Layer obj1, Layer obj2) + { + return obj1.Equals(obj2); + } + + public static bool operator !=(Layer obj1, Layer obj2) + { + return !obj1.Equals(obj2); + } + + public static bool operator >(Layer obj1, Layer obj2) + { + return obj1.Index > obj2.Index; + } + + public static bool operator <(Layer obj1, Layer obj2) + { + return obj1.Index < obj2.Index; + } + + public static bool operator >=(Layer obj1, Layer obj2) + { + return obj1.Index >= obj2.Index; + } + + public static bool operator <=(Layer obj1, Layer obj2) + { + return obj1.Index <= obj2.Index; + } + + public bool Equals(uint other) + { + return Index == other; + } + + public bool Equals(Layer other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(_compressedBytes, other._compressedBytes); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Layer)obj); + } + + public override int GetHashCode() + { + return (_compressedBytes != null ? _compressedBytes.GetHashCode() : 0); + } + + private sealed class IndexRelationalComparer : IComparer + { + public int Compare(Layer x, Layer y) + { + if (ReferenceEquals(x, y)) return 0; + if (ReferenceEquals(null, y)) return 1; + if (ReferenceEquals(null, x)) return -1; + return x.Index.CompareTo(y.Index); + } + } + + public static IComparer IndexComparer { get; } = new IndexRelationalComparer(); + #endregion + + #region Formaters + public override string ToString() + { + return $"{nameof(Index)}: {Index}, {nameof(Filename)}: {Filename}, {nameof(IsModified)}: {IsModified}"; + } + #endregion + + #region Methods + + public Rectangle GetBoundingRectangle(Mat mat = null, bool reCalculate = false) + { + if (NonZeroPixelCount > 0 && !reCalculate) + { + return BoundingRectangle; + } + bool needDispose = false; + if (ReferenceEquals(mat, null)) + { + mat = LayerMat; + needDispose = true; + } + + using (var nonZeroMat = new Mat()) + { + CvInvoke.FindNonZero(mat, nonZeroMat); + NonZeroPixelCount = (uint)nonZeroMat.Rows / 2; + BoundingRectangle = CvInvoke.BoundingRectangle(nonZeroMat); + } + + + if (needDispose) mat.Dispose(); + + return BoundingRectangle; + } + + public Layer PreviousLayer() + { + if (ReferenceEquals(ParentLayerManager, null) || Index == 0) + return null; + + return ParentLayerManager[Index - 1]; + } + + public Layer NextLayer() + { + if (ReferenceEquals(ParentLayerManager, null) || Index >= ParentLayerManager.Count - 1) + return null; + + return ParentLayerManager[Index + 1]; + } + + /// + /// Gets all islands start pixel location for this layer + /// https://www.geeksforgeeks.org/find-number-of-islands/ + /// + /// holding all islands coordinates + public List GetIssues(uint requiredPixelsToSupportIsland = 5) + { + if (requiredPixelsToSupportIsland == 0) + requiredPixelsToSupportIsland = 1; + + // These arrays are used to + // get row and column numbers + // of 8 neighbors of a given cell + List result = new List(); + List pixels = new List(); + + + + var mat = LayerMat; + var bytes = mat.GetPixelSpan(); + + + + var previousLayerImage = PreviousLayer()?.LayerMat; + var previousBytes = previousLayerImage?.GetBytes(); + + + /*var nextLayerImage = NextLayer()?.Image; + byte[] nextBytes = null; + if (!ReferenceEquals(nextLayerImage, null)) + { + if (nextLayerImage.TryGetSinglePixelSpan(out var nextPixelSpan)) + { + nextBytes = MemoryMarshal.AsBytes(nextPixelSpan).ToArray(); + } + }*/ + + // Make a bool array to + // mark visited cells. + // Initially all cells + // are unvisited + bool[,] visited = new bool[mat.Width, mat.Height]; + + // Initialize count as 0 and + // traverse through the all + // cells of given matrix + //uint count = 0; + + // Island checker + sbyte[] rowNbr = { -1, -1, -1, 0, 0, 1, 1, 1 }; + sbyte[] colNbr = { -1, 0, 1, -1, 1, -1, 0, 1 }; + const uint minPixel = 10; + const uint minPixelForSupportIsland = 200; + int pixelIndex; + uint islandSupportingPixels; + if (Index > 0) + { + for (int y = 0; y < mat.Height; y++) + { + for (int x = 0; x < mat.Width; x++) + { + pixelIndex = y * mat.Width + x; + + /*if (bytes[pixelIndex] == 0 && previousBytes?[pixelIndex] == byte.MaxValue && + nextBytes?[pixelIndex] == byte.MaxValue) + { + result.Add(new LayerIssue(this, LayerIssue.IssueType.HoleSandwich, new []{new Point(x, y)})); + }*/ + + if (bytes[pixelIndex] > minPixel && !visited[x, y]) + { + // If a cell with value 1 is not + // visited yet, then new island + // found, Visit all cells in this + // island and increment island count + pixels.Clear(); + pixels.Add(new Point(x, y)); + islandSupportingPixels = previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0; + + int minX = x; + int maxX = x; + int minY = y; + int maxY = y; + + int x2; + int y2; + + + Queue queue = new Queue(); + queue.Enqueue(new Point(x, y)); + // Mark this cell as visited + visited[x, y] = true; + + while (queue.Count > 0) + { + var point = queue.Dequeue(); + y2 = point.Y; + x2 = point.X; + for (byte k = 0; k < 8; k++) + { + //if (isSafe(y2 + rowNbr[k], x2 + colNbr[k])) + var tempy2 = y2 + rowNbr[k]; + var tempx2 = x2 + colNbr[k]; + pixelIndex = tempy2 * mat.Width + tempx2; + if (tempy2 >= 0 && + tempy2 < mat.Height && + tempx2 >= 0 && tempx2 < mat.Width && + bytes[pixelIndex] >= minPixel && + !visited[tempx2, tempy2]) + { + visited[tempx2, tempy2] = true; + point = new Point(tempx2, tempy2); + pixels.Add(point); + queue.Enqueue(point); + + minX = Math.Min(minX, tempx2); + maxX = Math.Max(maxX, tempx2); + minY = Math.Min(minY, tempy2); + maxY = Math.Max(maxY, tempy2); + + islandSupportingPixels += previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0; + } + } + } + //count++; + + if (islandSupportingPixels >= requiredPixelsToSupportIsland) + continue; // Not a island, bounding is strong + if (islandSupportingPixels > 0 && pixels.Count < requiredPixelsToSupportIsland && + islandSupportingPixels >= Math.Max(1, pixels.Count / 2)) continue; // Not a island + result.Add(new LayerIssue(this, LayerIssue.IssueType.Island, pixels.ToArray(), new Rectangle(minX, minY, maxX - minX, maxY - minY))); + } + } + } + } + + pixels.Clear(); + + // TouchingBounds Checker + for (int x = 0; x < mat.Width; x++) // Check Top and Bottom bounds + { + if (bytes[x] >= 200) // Top + { + pixels.Add(new Point(x, 0)); + } + + if (bytes[mat.Width * mat.Height - mat.Width + x] >= 200) // Bottom + { + pixels.Add(new Point(x, mat.Height - 1)); + } + } + + for (int y = 0; y < mat.Height; y++) // Check Left and Right bounds + { + if (bytes[y * mat.Width] >= 200) // Left + { + pixels.Add(new Point(0, y)); + } + + if (bytes[y * mat.Width + mat.Width - 1] >= 200) // Right + { + pixels.Add(new Point(mat.Width - 1, y)); + } + } + + if (pixels.Count > 0) + { + result.Add(new LayerIssue(this, LayerIssue.IssueType.TouchingBound, pixels.ToArray())); + } + + pixels.Clear(); + + return result; + } + + public void MutateMove(OperationMove move) + { + using (var layer = LayerMat) + { + if (move.ImageWidth == 0) move.ImageWidth = (uint)layer.Width; + if (move.ImageHeight == 0) move.ImageHeight = (uint)layer.Height; + + /*layer.Transform(1.0, 1.0, move.MarginLeft - move.MarginRight, move.MarginTop-move.MarginBottom); + LayerMat = layer;*/ + using (var layerRoi = new Mat(layer, move.SrcRoi)) + { + using (var dstLayer = layer.CloneBlank()) + { + using (var dstRoi = new Mat(dstLayer, move.DstRoi)) + { + layerRoi.CopyTo(dstRoi); + LayerMat = dstLayer; + } + } + } + } + } + + + public void MutateResize(double xScale, double yScale) + { + using (var mat = LayerMat) + { + mat.TransformFromCenter(xScale, yScale); + LayerMat = mat; + } + } + + public void MutateFlip(FlipType flipType, bool makeCopy = true) + { + using (var mat = LayerMat) + { + if (makeCopy) + { + using (Mat dst = new Mat()) + { + CvInvoke.Flip(mat, dst, flipType); + var spanSrc = mat.GetPixelSpan(); + var spanDst = dst.GetPixelSpan(); + for (int i = 0; i < spanSrc.Length; i++) + { + if (spanDst[i] == 0) continue; + spanSrc[i] = spanDst[i]; + } + + LayerMat = mat; + + } + } + else + { + CvInvoke.Flip(mat, mat, flipType); + } + + LayerMat = mat; + } + } + + public void MutateRotate(double angle = 90.0, Inter interpolation = Inter.Linear) + { + using (var mat = LayerMat) + { + var halfWidth = mat.Width / 2.0f; + var halfHeight = mat.Height / 2.0f; + using (var translateTransform = new Matrix(2, 3)) + { + CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), angle, 1.0, translateTransform); + /*var rect = new RotatedRect(PointF.Empty, mat.Size, (float) angle).MinAreaRect(); + translateTransform[0, 2] += rect.Width / 2.0 - mat.Cols / 2.0; + translateTransform[0, 2] += rect.Height / 2.0 - mat.Rows / 2.0;*/ + + /* var abs_cos = Math.Abs(translateTransform[0, 0]); + var abs_sin = Math.Abs(translateTransform[0, 1]); + + var bound_w = mat.Height * abs_sin + mat.Width * abs_cos; + var bound_h = mat.Height * abs_cos + mat.Width * abs_sin; + + translateTransform[0, 2] += bound_w / 2 - halfWidth; + translateTransform[1, 2] += bound_h / 2 - halfHeight;*/ + + + CvInvoke.WarpAffine(mat, mat, translateTransform, mat.Size, interpolation); + } + + LayerMat = mat; + } + } + + public void MutateSolidify() + { + using (Mat mat = LayerMat) + { + using (Mat filteredMat = new Mat()) + { + CvInvoke.Threshold(mat, filteredMat, 254, 255, ThresholdType.Binary); // Clean AA + + using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint()) + { + using (Mat hierarchy = new Mat()) + { + CvInvoke.FindContours(filteredMat, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple); + var arr = hierarchy.GetData(); + for (int i = 0; i < contours.Size; i++) + { + if ((int)arr.GetValue(0, i, 2) != -1 || (int)arr.GetValue(0, i, 3) == -1) continue; + CvInvoke.DrawContours(mat, contours, i, new MCvScalar(255), -1); + } + } + } + } + + LayerMat = mat; + } + } + + public void MutateErode(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + if (anchor.IsEmpty) anchor = new Point(-1, -1); + if (ReferenceEquals(kernel, null)) + { + kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + } + using (Mat dst = LayerMat) + { + CvInvoke.Erode(dst, dst, kernel, anchor, iterations, borderType, borderValue); + LayerMat = dst; + } + } + + public void MutateDilate(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + if (anchor.IsEmpty) anchor = new Point(-1, -1); + if (ReferenceEquals(kernel, null)) + { + kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + } + using (Mat dst = LayerMat) + { + CvInvoke.Dilate(dst, dst, kernel, anchor, iterations, borderType, borderValue); + LayerMat = dst; + } + } + + public void MutateOpen(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + if (anchor.IsEmpty) anchor = new Point(-1, -1); + if (ReferenceEquals(kernel, null)) + { + kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + } + using (Mat dst = LayerMat) + { + CvInvoke.MorphologyEx(dst, dst, MorphOp.Open, kernel, anchor, iterations, borderType, borderValue); + LayerMat = dst; + } + } + + public void MutateClose(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + if (anchor.IsEmpty) anchor = new Point(-1, -1); + if (ReferenceEquals(kernel, null)) + { + kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + } + using (Mat dst = LayerMat) + { + CvInvoke.MorphologyEx(dst, dst, MorphOp.Close, kernel, anchor, iterations, borderType, borderValue); + LayerMat = dst; + } + } + + public void MutateGradient(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + if (anchor.IsEmpty) anchor = new Point(-1, -1); + if (ReferenceEquals(kernel, null)) + { + kernel = CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), anchor); + } + using (Mat dst = LayerMat) + { + CvInvoke.MorphologyEx(dst, dst, MorphOp.Gradient, kernel, anchor, iterations, borderType, borderValue); + LayerMat = dst; + } + } + + public void MutatePyrDownUp(BorderType borderType = BorderType.Reflect101) + { + using (Mat dst = LayerMat) + { + CvInvoke.PyrDown(dst, dst, borderType); + CvInvoke.PyrUp(dst, dst, borderType); + LayerMat = dst; + } + } + + public void MutateMedianBlur(int aperture = 1) + { + using (Mat dst = LayerMat) + { + CvInvoke.MedianBlur(dst, dst, aperture); + LayerMat = dst; + } + } + + public void MutateGaussianBlur(Size size = default, int sigmaX = 0, int sigmaY = 0, BorderType borderType = BorderType.Reflect101) + { + if (size.IsEmpty) size = new Size(5, 5); + + using (Mat dst = LayerMat) + { + CvInvoke.GaussianBlur(dst, dst, size, sigmaX, sigmaY, borderType); + LayerMat = dst; + } + } + + public void ToolPattern(OperationPattern settings) + { + using (var layer = LayerMat) + { + using (var layerRoi = new Mat(layer, settings.SrcRoi)) + { + using (var dstLayer = layer.CloneBlank()) + { + for (ushort col = 0; col < settings.Cols; col++) + { + for (ushort row = 0; row < settings.Rows; row++) + { + using (var dstRoi = new Mat(dstLayer, settings.GetRoi(col, row))) + { + layerRoi.CopyTo(dstRoi); + } + } + } + + LayerMat = dstLayer; + } + } + } + } + + + + public Layer Clone() + { + return new Layer(Index, CompressedBytes, Filename, ParentLayerManager); + } + + #endregion + } +} diff --git a/UVtools.Core/Layer/LayerIssue.cs b/UVtools.Core/Layer/LayerIssue.cs new file mode 100644 index 0000000..8618d89 --- /dev/null +++ b/UVtools.Core/Layer/LayerIssue.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace UVtools.Core +{ + #region LayerIssue Class + + public class IslandDetectionConfiguration + { + /// + /// Gets if the detection is enabled + /// + public bool Enabled { get; set; } = true; + + /// + /// Gets or sets the binary threshold, all pixels below this value will turn in black, otherwise white + /// Set to 0 to disable this operation + /// + public byte BinaryThreshold { get; set; } = 1; + + /// + /// Gets the required area size (x*y) to consider process a island (0-255) + /// + public byte RequiredAreaToProcessCheck { get; set; } = 1; + + /// + /// Gets the required brightness for check a pixel under a island (0-255) + /// + public byte RequiredPixelBrightnessToProcessCheck { get; set; } = 10; + + /// + /// Gets the required number of pixels to support a island and discard it as a issue (0-255) + /// + public byte RequiredPixelsToSupport { get; set; } = 10; + + /// + /// Gets the required brightness of supporting pixels to count as a valid support (0-255) + /// + public byte RequiredPixelBrightnessToSupport { get; set; } = 150; + } + + public class ResinTrapDetectionConfiguration + { + /// + /// Gets if the detection is enabled + /// + public bool Enabled { get; set; } = true; + + /// + /// Gets or sets the binary threshold, all pixels below this value will turn in black, otherwise white + /// Set to 0 to disable this operation + /// + public byte BinaryThreshold { get; set; } = 127; + + /// + /// Gets the required area size (x*y) to consider process a hollow area (0-255) + /// + public byte RequiredAreaToProcessCheck { get; set; } = 1; + + /// + /// Gets the number of black pixels required to consider a drain + /// + public byte RequiredBlackPixelsToDrain { get; set; } = 10; + + /// + /// Gets the maximum pixel brightness to be a drain pixel (0-150) + /// + public byte MaximumPixelBrightnessToDrain { get; set; } = 30; + } + + + public class LayerIssue : IEnumerable + { + public enum IssueType : byte + { + Island, + ResinTrap, + TouchingBound, + //HoleSandwich, + } + + /// + /// Gets the parent layer + /// + public Layer Layer { get; } + + /// + /// Gets the issue type associated + /// + public IssueType Type { get; } + + /// + /// Gets the pixels containing the issue + /// + public Point[] Pixels { get; } + + /// + /// Gets the bounding rectangle of the pixel area + /// + public Rectangle BoundingRectangle { get; } + + /// + /// Gets the X coordinate for the first point, -1 if doesn't exists + /// + public int X => HaveValidPoint ? Pixels[0].X : -1; + + /// + /// Gets the Y coordinate for the first point, -1 if doesn't exists + /// + public int Y => HaveValidPoint ? Pixels[0].Y : -1; + + /// + /// Gets the XY point for first point + /// + public Point Point => HaveValidPoint ? Pixels[0] : new Point(-1, -1); + + /// + /// Gets the number of pixels on this issue + /// + public uint Size + { + get + { + if (Type == IssueType.ResinTrap && !BoundingRectangle.IsEmpty) + { + return (uint)(BoundingRectangle.Width * BoundingRectangle.Height); + } + + if (ReferenceEquals(Pixels, null)) return 0; + return (uint)Pixels.Length; + } + } + + /// + /// Check if this issue have a valid start point to show + /// + public bool HaveValidPoint => !ReferenceEquals(Pixels, null) && Pixels.Length > 0; + + public LayerIssue(Layer layer, IssueType type, Point[] pixels = null, Rectangle boundingRectangle = new Rectangle()) + { + Layer = layer; + Type = type; + Pixels = pixels; + BoundingRectangle = boundingRectangle; + } + + public Point this[uint index] => Pixels[index]; + + public Point this[int index] => Pixels[index]; + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)Pixels).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public override string ToString() + { + return $"{nameof(Type)}: {Type}, Layer: {Layer.Index}, {nameof(X)}: {X}, {nameof(Y)}: {Y}, {nameof(Size)}: {Size}"; + } + } + #endregion + + #region LayerHollowArea + + public class LayerHollowArea : IEnumerable + { + public enum AreaType : byte + { + Unknown = 0, + Trap, + Drain + } + /// + /// Gets area pixels + /// + public Point[] Contour { get; } + + public Rectangle BoundingRectangle { get; } + + public AreaType Type { get; set; } = AreaType.Unknown; + + public bool Processed { get; set; } + + #region Indexers + public Point this[uint index] + { + get => index < Contour.Length ? Contour[index] : Point.Empty; + set => Contour[index] = value; + } + + public Point this[int index] + { + get => index < Contour.Length ? Contour[index] : Point.Empty; + set => Contour[index] = value; + } + + public Point this[uint x, uint y] + { + get + { + for (uint i = 0; i < Contour.Length; i++) + { + if (Contour[i].X == x && Contour[i].Y == y) return Contour[i]; + } + return Point.Empty; + } + } + + public Point this[int x, int y] => this[(uint)x, (uint)y]; + + public Point this[Point point] => this[point.X, point.Y]; + + #endregion + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)Contour).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public LayerHollowArea() + { + } + + public LayerHollowArea(Point[] contour, Rectangle boundingRectangle, AreaType type = AreaType.Unknown) + { + Contour = contour; + BoundingRectangle = boundingRectangle; + Type = type; + } + } + #endregion +} diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs new file mode 100644 index 0000000..cf21310 --- /dev/null +++ b/UVtools.Core/Layer/LayerManager.cs @@ -0,0 +1,1161 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Emgu.CV.Util; +using UVtools.Core.Extensions; +using UVtools.Core.Operations; + +namespace UVtools.Core +{ + public class LayerManager : IEnumerable + { + #region Enums + public enum Mutate : byte + { + Move, + Resize, + Flip, + Rotate, + Solidify, + //LayerSmash, + Erode, + Dilate, + Opening, + Closing, + Gradient, + TopHat, + BlackHat, + HitMiss, + PyrDownUp, + SmoothMedian, + SmoothGaussian, + } + #endregion + + #region Properties + /// + /// Layers List + /// + public Layer[] Layers { get; } + + private Rectangle _boundingRectangle = Rectangle.Empty; + public Rectangle BoundingRectangle + { + get => GetBoundingRectangle(); + set => _boundingRectangle = value; + } + + /// + /// Gets the layers count + /// + public uint Count => (uint) Layers.Length; + + /// + /// Gets if any layer got modified, otherwise false + /// + public bool IsModified + { + get + { + for (uint i = 0; i < Count; i++) + { + if (Layers[i].IsModified) return true; + } + return false; + } + } + + + #endregion + + #region Constructors + public LayerManager(uint layerCount) + { + Layers = new Layer[layerCount]; + } + #endregion + + #region Indexers + public Layer this[uint index] + { + get => Layers[index]; + set => AddLayer(index, value); + } + + public Layer this[int index] + { + get => Layers[index]; + set => AddLayer((uint) index, value); + } + + public Layer this[long index] + { + get => Layers[index]; + set => AddLayer((uint) index, value); + } + + #endregion + + #region Numerators + public IEnumerator GetEnumerator() + { + return ((IEnumerable)Layers).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion + + #region Static Methods + /// + /// Compress a layer from a + /// + /// to compress + /// Compressed byte array + public static byte[] CompressLayer(Stream input) + { + return CompressLayer(input.ToArray()); + } + + /// + /// Compress a layer from a byte array + /// + /// byte array to compress + /// Compressed byte array + public static byte[] CompressLayer(byte[] input) + { + return input; + /*using (MemoryStream output = new MemoryStream()) + { + using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal)) + { + dstream.Write(input, 0, input.Length); + } + return output.ToArray(); + }*/ + } + + /// + /// Decompress a layer from a byte array + /// + /// byte array to decompress + /// Decompressed byte array + public static byte[] DecompressLayer(byte[] input) + { + return input; + /*using (MemoryStream ms = new MemoryStream(input)) + { + using (MemoryStream output = new MemoryStream()) + { + using (DeflateStream dstream = new DeflateStream(ms, CompressionMode.Decompress)) + { + dstream.CopyTo(output); + } + return output.ToArray(); + } + }*/ + } + #endregion + + #region Methods + + public Rectangle GetBoundingRectangle(OperationProgress progress = null) + { + if (!_boundingRectangle.IsEmpty) return _boundingRectangle; + _boundingRectangle = this[0].BoundingRectangle; + if (_boundingRectangle.IsEmpty) // Safe checking + { + progress?.Reset(OperationProgress.StatusOptimizingBounds, Count); + Parallel.For(0, Count, layerIndex => + { + if (!ReferenceEquals(progress, null) && progress.Token.IsCancellationRequested) + { + return; + } + + this[layerIndex].GetBoundingRectangle(); + + if (ReferenceEquals(progress, null)) return; + lock (progress.Mutex) + { + progress++; + } + }); + _boundingRectangle = this[0].BoundingRectangle; + + if (!ReferenceEquals(progress, null) && progress.Token.IsCancellationRequested) + { + _boundingRectangle = Rectangle.Empty; + progress.Token.ThrowIfCancellationRequested(); + } + + } + + progress?.Reset(OperationProgress.StatusCalculatingBounds, Count); + for (int i = 1; i < Count; i++) + { + _boundingRectangle = Rectangle.Union(_boundingRectangle, this[i].BoundingRectangle); + if (ReferenceEquals(progress, null)) continue; + progress++; + } + + return _boundingRectangle; + } + + /// + /// Add a layer + /// + /// Layer index + /// Layer to add + public void AddLayer(uint index, Layer layer) + { + Layers[index] = layer; + layer.ParentLayerManager = this; + } + + /// + /// Get layer given index + /// + /// Layer index + /// + public Layer GetLayer(uint index) + { + return Layers[index]; + } + + public void MutateMove(uint startLayerIndex, uint endLayerIndex, OperationMove move, OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Moving", endLayerIndex - startLayerIndex + 1); + + if (move.SrcRoi == Rectangle.Empty) move.SrcRoi = GetBoundingRectangle(progress); + + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + + this[layerIndex].MutateMove(move); + + lock (progress.Mutex) + { + progress++; + } + }); + + _boundingRectangle = Rectangle.Empty; + + progress.Token.ThrowIfCancellationRequested(); + + + } + + /// + /// Resizes layer images in x and y factor, starting at 1 = 100% + /// + /// Layer index to start + /// Layer index to end + /// X factor, starts at 1 + /// Y factor, starts at 1 + /// Fade X/Y towards 100% + public void MutateResize(uint startLayerIndex, uint endLayerIndex, double x, double y, bool isFade, OperationProgress progress = null) + { + if (x == 1.0 && y == 1.0) return; + + if(ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Resizing", endLayerIndex - startLayerIndex + 1); + + double xSteps = Math.Abs(x - 1.0) / (endLayerIndex - startLayerIndex); + double ySteps = Math.Abs(y - 1.0) / (endLayerIndex - startLayerIndex); + + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + var newX = x; + var newY = y; + if (isFade) + { + if (newX != 1.0) + { + + //maxIteration = Math.Max(iterationsStart, iterationsEnd); + + newX = (float)(newX < 1.0 + ? newX + (layerIndex - startLayerIndex) * xSteps + : newX - (layerIndex - startLayerIndex) * xSteps); + + // constrain + //iterations = Math.Min(Math.Max(1, iterations), maxIteration); + } + + if (y != 1.0) + { + + //maxIteration = Math.Max(iterationsStart, iterationsEnd); + + newY = (float)(newY < 1.0 + ? newY + (layerIndex - startLayerIndex) * ySteps + : newY - (layerIndex - startLayerIndex) * ySteps); + + // constrain + //iterations = Math.Min(Math.Max(1, iterations), maxIteration); + } + } + + lock (progress.Mutex) + { + progress++; + } + + if (newX == 1.0 && newY == 1.0) return; + + this[layerIndex].MutateResize(newX, newY); + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateFlip(uint startLayerIndex, uint endLayerIndex, FlipType flipType, bool makeCopy = false, OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Fliping", endLayerIndex - startLayerIndex + 1); + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + this[layerIndex].MutateFlip(flipType, makeCopy); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateRotate(uint startLayerIndex, uint endLayerIndex, double angle, Inter interpolation = Inter.Linear, OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Rotating", endLayerIndex - startLayerIndex+1); + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + this[layerIndex].MutateRotate(angle, interpolation); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateSolidify(uint startLayerIndex, uint endLayerIndex, OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Solidifing", endLayerIndex - startLayerIndex+1); + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + this[layerIndex].MutateSolidify(); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + private void MutateGetVarsIterationFade(uint startLayerIndex, uint endLayerIndex, int iterationsStart, int iterationsEnd, ref bool isFade, out int iterationSteps, out int maxIteration) + { + iterationSteps = 0; + maxIteration = 0; + isFade = isFade && startLayerIndex != endLayerIndex && iterationsStart != iterationsEnd; + if (!isFade) return; + iterationSteps = (int)Math.Abs((double)(iterationsStart - iterationsEnd) / (endLayerIndex - startLayerIndex)); + maxIteration = Math.Max(iterationsStart, iterationsEnd); + } + + private int MutateGetIterationVar(bool isFade, int iterationsStart, int iterationsEnd, int iterationSteps, int maxIteration, uint startLayerIndex, uint layerIndex) + { + if(!isFade) return iterationsStart; + // calculate iterations based on range + int iterations = (int)(iterationsStart < iterationsEnd + ? iterationsStart + (layerIndex - startLayerIndex) * iterationSteps + : iterationsStart - (layerIndex - startLayerIndex) * iterationSteps); + + // constrain + return Math.Min(Math.Max(1, iterations), maxIteration); + } + + public void MutateErode(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, + IInputArray kernel = null, Point anchor = default, + BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + MutateGetVarsIterationFade( + startLayerIndex, + endLayerIndex, + iterationsStart, + iterationsEnd, + ref isFade, + out var iterationSteps, + out var maxIteration + ); + + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Eroding", endLayerIndex - startLayerIndex+1); + + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint) layerIndex); + this[layerIndex].MutateErode(iterations, kernel, anchor, borderType, borderValue); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateDilate(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, + IInputArray kernel = null, Point anchor = default, + BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + MutateGetVarsIterationFade( + startLayerIndex, + endLayerIndex, + iterationsStart, + iterationsEnd, + ref isFade, + out var iterationSteps, + out var maxIteration + ); + + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Dilating", endLayerIndex - startLayerIndex+1); + + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); + this[layerIndex].MutateDilate(iterations, kernel, anchor, borderType, borderValue); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateOpen(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, + IInputArray kernel = null, Point anchor = default, + BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + MutateGetVarsIterationFade( + startLayerIndex, + endLayerIndex, + iterationsStart, + iterationsEnd, + ref isFade, + out var iterationSteps, + out var maxIteration + ); + + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Removing Noise", endLayerIndex - startLayerIndex+1); + + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); + this[layerIndex].MutateOpen(iterations, kernel, anchor, borderType, borderValue); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateClose(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, + IInputArray kernel = null, Point anchor = default, + BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + MutateGetVarsIterationFade( + startLayerIndex, + endLayerIndex, + iterationsStart, + iterationsEnd, + ref isFade, + out var iterationSteps, + out var maxIteration + ); + + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Gap Closing", endLayerIndex - startLayerIndex+1); + + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); + this[layerIndex].MutateClose(iterations, kernel, anchor, borderType, borderValue); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateGradient(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, + IInputArray kernel = null, Point anchor = default, + BorderType borderType = BorderType.Default, MCvScalar borderValue = default) + { + MutateGetVarsIterationFade( + startLayerIndex, + endLayerIndex, + iterationsStart, + iterationsEnd, + ref isFade, + out var iterationSteps, + out var maxIteration + ); + + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Gradient", endLayerIndex - startLayerIndex+1); + + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); + this[layerIndex].MutateGradient(iterations, kernel, anchor, borderType, borderValue); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutatePyrDownUp(uint startLayerIndex, uint endLayerIndex, BorderType borderType = BorderType.Default, OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("PryDownUp", endLayerIndex - startLayerIndex+1); + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + this[layerIndex].MutatePyrDownUp(borderType); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateMedianBlur(uint startLayerIndex, uint endLayerIndex, int aperture = 1, OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Bluring", endLayerIndex - startLayerIndex+1); + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + this[layerIndex].MutateMedianBlur(aperture); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public void MutateGaussianBlur(uint startLayerIndex, uint endLayerIndex, Size size = default, int sigmaX = 0, int sigmaY = 0, BorderType borderType = BorderType.Reflect101, OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Bluring", endLayerIndex - startLayerIndex+1); + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + this[layerIndex].MutateGaussianBlur(size, sigmaX, sigmaY, borderType); + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + } + + public ConcurrentDictionary> GetAllIssues( + IslandDetectionConfiguration islandConfig = null, ResinTrapDetectionConfiguration resinTrapConfig = null, + OperationProgress progress = null) + { + if(ReferenceEquals(islandConfig, null)) islandConfig = new IslandDetectionConfiguration(); + if(ReferenceEquals(resinTrapConfig, null)) resinTrapConfig = new ResinTrapDetectionConfiguration(); + if(ReferenceEquals(progress, null)) progress = new OperationProgress(); + + const byte minTouchingBondsPixelColor = 200; + + var result = new ConcurrentDictionary>(); + var layerHollowAreas = new ConcurrentDictionary>(); + + bool islandsFinished = false; + + progress.Reset(OperationProgress.StatusIslands, Count); + + Parallel.Invoke(() => + { + if (!islandConfig.Enabled) + { + islandsFinished = true; + return; + } + // Detect contours + Parallel.ForEach(this, + //new ParallelOptions{MaxDegreeOfParallelism = 1}, + layer => + { + if (progress.Token.IsCancellationRequested) return; + using (var image = layer.LayerMat) + { + int step = image.Step; + var span = image.GetPixelSpan(); + + // TouchingBounds Checker + List pixels = new List(); + for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds + { + if (span[x] >= minTouchingBondsPixelColor) // Top + { + pixels.Add(new Point(x, 0)); + } + + if (span[step * image.Height - step + x] >= minTouchingBondsPixelColor) // Bottom + { + pixels.Add(new Point(x, image.Height - 1)); + } + } + + for (int y = 0; y < image.Height; y++) // Check Left and Right bounds + { + if (span[y * step] >= minTouchingBondsPixelColor) // Left + { + pixels.Add(new Point(0, y)); + } + + if (span[y * step + step - 1] >= minTouchingBondsPixelColor) // Right + { + pixels.Add(new Point(step - 1, y)); + } + } + + if (pixels.Count > 0) + { + result.TryAdd(layer.Index, new List + { + new LayerIssue(layer, LayerIssue.IssueType.TouchingBound, pixels.ToArray()) + }); + } + + + if (layer.Index == 0) return; // No islands for layer 0 + + VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); + Mat hierarchy = new Mat(); + + if (islandConfig.BinaryThreshold > 0) + { + using (var thresholdImage = new Mat()) + { + CvInvoke.Threshold(image, thresholdImage, 1, 255, ThresholdType.Binary); + CvInvoke.FindContours(thresholdImage, contours, hierarchy, RetrType.Ccomp, + ChainApproxMethod.ChainApproxSimple); + } + } + else + { + CvInvoke.FindContours(image, contours, hierarchy, RetrType.Ccomp, + ChainApproxMethod.ChainApproxSimple); + } + + var arr = hierarchy.GetData(); + // + //hierarchy[i][0]: the index of the next contour of the same level + //hierarchy[i][1]: the index of the previous contour of the same level + //hierarchy[i][2]: the index of the first child + //hierarchy[i][3]: the index of the parent + // + + Mat previousImage = null; + Span previousSpan = null; + for (int i = 0; i < contours.Size; i++) + { + + if ((int) arr.GetValue(0, i, 2) == -1 && (int) arr.GetValue(0, i, 3) != -1) continue; + var rect = CvInvoke.BoundingRectangle(contours[i]); + if (rect.GetArea() < islandConfig.RequiredAreaToProcessCheck) + continue; + + if (ReferenceEquals(previousImage, null)) + { + previousImage = this[layer.Index - 1].LayerMat; + previousSpan = previousImage.GetPixelSpan(); + } + + List points = new List(); + uint pixelsSupportingIsland = 0; + + using (Mat contourImage = image.CloneBlank()) + { + CvInvoke.DrawContours(contourImage, contours, i, new MCvScalar(255), -1); + var contourImageSpan = contourImage.GetPixelSpan(); + + for (int y = rect.Y; y < rect.Bottom; y++) + { + for (int x = rect.X; x < rect.Right; x++) + { + int pixel = step * y + x; + if (span[pixel] < islandConfig.RequiredPixelBrightnessToProcessCheck) + continue; // Low brightness, ignore + if (contourImageSpan[pixel] != 255) + continue; // Not inside contour, ignore + + //if (CvInvoke.PointPolygonTest(contours[i], new PointF(x, y), false) < 0) continue; // Out of contour SLOW! + //Debug.WriteLine($"Layer: {layer.Index}, Coutour: {i}, X:{x} Y:{y}"); + points.Add(new Point(x, y)); + + if (previousSpan[pixel] >= islandConfig.RequiredPixelBrightnessToSupport) + { + pixelsSupportingIsland++; + } + } + } + } + + + if (points.Count == 0) continue; + if (pixelsSupportingIsland >= islandConfig.RequiredPixelsToSupport) + continue; // Not a island, bounding is strong, i think... + if (pixelsSupportingIsland > 0 && points.Count < islandConfig.RequiredPixelsToSupport && + pixelsSupportingIsland >= Math.Max(1, points.Count / 2)) + continue; // Not a island, but maybe weak bounding... + + + var issue = new LayerIssue(layer, LayerIssue.IssueType.Island, points.ToArray(), rect); + result.AddOrUpdate(layer.Index, new List {issue}, + (layerIndex, list) => + { + list.Add(issue); + return list; + }); + } + + contours.Dispose(); + hierarchy.Dispose(); + previousImage?.Dispose(); + } + + lock (progress.Mutex) + { + progress++; + } + }); + islandsFinished = true; + }, () => + { + if (!resinTrapConfig.Enabled) return; + // Detect contours + Parallel.ForEach(this, + //new ParallelOptions{MaxDegreeOfParallelism = 1}, + layer => + { + if (progress.Token.IsCancellationRequested) return; + using (var image = layer.LayerMat) + { + if (resinTrapConfig.BinaryThreshold > 0) + { + CvInvoke.Threshold(image, image, resinTrapConfig.BinaryThreshold, 255, ThresholdType.Binary); + } + + var listHollowArea = new List(); + + using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint()) + { + using (Mat hierarchy = new Mat()) + { + + CvInvoke.FindContours(image, contours, hierarchy, RetrType.Ccomp, + ChainApproxMethod.ChainApproxSimple); + + var arr = hierarchy.GetData(); + // + //hierarchy[i][0]: the index of the next contour of the same level + //hierarchy[i][1]: the index of the previous contour of the same level + //hierarchy[i][2]: the index of the first child + //hierarchy[i][3]: the index of the parent + // + + for (int i = 0; i < contours.Size; i++) + { + if ((int) arr.GetValue(0, i, 2) != -1 || (int) arr.GetValue(0, i, 3) == -1) + continue; + var rect = CvInvoke.BoundingRectangle(contours[i]); + if(rect.GetArea() < resinTrapConfig.RequiredAreaToProcessCheck) continue; + + listHollowArea.Add(new LayerHollowArea(contours[i].ToArray(), + rect, + layer.Index == Count - 1 + ? LayerHollowArea.AreaType.Drain + : LayerHollowArea.AreaType.Unknown)); + + if (listHollowArea.Count > 0) + layerHollowAreas.TryAdd(layer.Index, listHollowArea); + } + } + } + } + }); + + + for (uint layerIndex = 0; layerIndex < Count - 1; layerIndex++) // Last layers, always drains + { + if (progress.Token.IsCancellationRequested) break; + if (!layerHollowAreas.TryGetValue(layerIndex, out var areas)) + continue; // No hollow areas in this layer, ignore + + byte areaCount = 0; + //foreach (var area in areas) + + Parallel.ForEach(from t in areas where t.Type == LayerHollowArea.AreaType.Unknown select t, area => + { + if (progress.Token.IsCancellationRequested) return; + if (area.Type != LayerHollowArea.AreaType.Unknown) return; // processed, ignore + area.Type = LayerHollowArea.AreaType.Trap; + + areaCount++; + + List linkedAreas = new List(); + + for (sbyte dir = 1; dir >= -1 && area.Type != LayerHollowArea.AreaType.Drain; dir -= 2) + //Parallel.ForEach(new sbyte[] {1, -1}, new ParallelOptions {MaxDegreeOfParallelism = 2}, dir => + { + Queue queue = new Queue(); + queue.Enqueue(area); + area.Processed = false; + int nextLayerIndex = (int) layerIndex; + while (queue.Count > 0 && area.Type != LayerHollowArea.AreaType.Drain) + { + if (progress.Token.IsCancellationRequested) return; + + LayerHollowArea checkArea = queue.Dequeue(); + if (checkArea.Processed) continue; + checkArea.Processed = true; + nextLayerIndex += dir; + + if (nextLayerIndex < 0 || nextLayerIndex >= Count) + break; // Exhausted layers + bool haveNextAreas = layerHollowAreas.TryGetValue((uint) nextLayerIndex, out var nextAreas); + Dictionary intersectingAreas = new Dictionary(); + + if (islandsFinished) + { + progress.Reset(OperationProgress.StatusResinTraps, Count, (uint) nextLayerIndex); + } + + using (var image = this[nextLayerIndex].LayerMat) + { + var span = image.GetPixelSpan(); + using (var emguImage = image.CloneBlank()) + { + using(var vec = new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour))) + { + CvInvoke.DrawContours(emguImage, vec, -1, new MCvScalar(255), -1); + } + + using (var intersectingAreasMat = image.CloneBlank()) + { + if (haveNextAreas) + { + foreach (var nextArea in nextAreas) + { + if (!checkArea.BoundingRectangle.IntersectsWith( + nextArea.BoundingRectangle)) continue; + intersectingAreas.Add(intersectingAreas.Count + 1, nextArea); + using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(nextArea.Contour))) + { + CvInvoke.DrawContours(intersectingAreasMat, vec, -1, + new MCvScalar(intersectingAreas.Count), -1); + } + } + } + + //Debug.WriteLine($"Area Count: {areaCount} | Next Areas: {intersectingAreas.Count} | Layer: {layerIndex} | Next Layer: {nextLayerIndex} | Dir: {dir}"); + + bool exitPixelLoop = false; + uint blackCount = 0; + + var spanContour = emguImage.GetPixelSpan(); + var spanIntersect = intersectingAreasMat.GetPixelSpan(); + for (int y = checkArea.BoundingRectangle.Y; + y < checkArea.BoundingRectangle.Bottom && + area.Type != LayerHollowArea.AreaType.Drain && !exitPixelLoop; + y++) + { + int pixelPos = image.GetPixelPos(checkArea.BoundingRectangle.X, y) - 1; + for (int x = checkArea.BoundingRectangle.X; + x < checkArea.BoundingRectangle.Right && + area.Type != LayerHollowArea.AreaType.Drain && !exitPixelLoop; + x++) + { + pixelPos++; + + if (spanContour[pixelPos] != 255) continue; // No contour + if (span[pixelPos] > resinTrapConfig.MaximumPixelBrightnessToDrain) continue; // Threshold to ignore white area + blackCount++; + + if (intersectingAreas.Count > 0) // Have areas, can be on same area path or not + { + byte i = spanIntersect[pixelPos]; + if (i == 0 || !intersectingAreas.ContainsKey(i)) // Black pixels + continue; + + //Debug.WriteLine($"BlackCount: {blackCount}, pixel color: {i}, layerindex: {layerIndex}"); + + if (intersectingAreas[i].Type == LayerHollowArea.AreaType.Drain) // Found a drain, stop query + { + area.Type = LayerHollowArea.AreaType.Drain; + exitPixelLoop = true; + } + else + { + queue.Enqueue(intersectingAreas[i]); + } + + linkedAreas.Add(intersectingAreas[i]); + intersectingAreas.Remove(i); + if (intersectingAreas.Count == 0) // Intersection areas sweep end, quit this path + { + exitPixelLoop = true; + break; + } + + //break; + + // Old Way + + /*foreach (var nextAreaCheck in intersectingAreas) + { + using (var vec = new VectorOfPoint(nextAreaCheck.Value.Contour)) + { + //Debug.WriteLine(CvInvoke.PointPolygonTest(vec, new PointF(x, y), false)); + if (CvInvoke.PointPolygonTest(vec, new PointF(x, y), false) < 0) continue; + } + + if (nextAreaCheck.Value.Type == LayerHollowArea.AreaType.Drain) // Found a drain, stop query + { + area.Type = LayerHollowArea.AreaType.Drain; + exitPixelLoop = true; + } + else + { + queue.Enqueue(nextAreaCheck.Value); + } + + linkedAreas.Add(nextAreaCheck.Value); + intersectingAreas.Remove(nextAreaCheck.Key); + if (intersectingAreas.Count == 0) + { + haveNextAreas = false; + exitPixelLoop = true; + } + //exitPixelLoop = true; + break; + + }*/ + } + else if (blackCount > Math.Min(checkArea.Contour.Length / 2, resinTrapConfig.RequiredBlackPixelsToDrain)) // Black pixel without next areas = Drain + { + area.Type = LayerHollowArea.AreaType.Drain; + exitPixelLoop = true; + break; + } + } // X loop + } // Y loop + + if (queue.Count == 0 && blackCount > Math.Min(checkArea.Contour.Length / 2, resinTrapConfig.RequiredBlackPixelsToDrain)) + { + area.Type = LayerHollowArea.AreaType.Drain; + } + + } // Dispose intersecting image + } // Dispose emgu image + } // Dispose image + } // Areas loop + } // Dir layer loop + + foreach (var linkedArea in linkedAreas) // Update linked areas + { + linkedArea.Type = area.Type; + } + }); + } + }); + + if (progress.Token.IsCancellationRequested) return result; + + for (uint layerIndex = 0; layerIndex < Count; layerIndex++) + { + if (!layerHollowAreas.TryGetValue(layerIndex, out var list)) continue; + if (list.Count == 0) continue; + foreach (var issue in + from area + in list + where area.Type == LayerHollowArea.AreaType.Trap + select new LayerIssue(this[layerIndex], LayerIssue.IssueType.ResinTrap, area.Contour, area.BoundingRectangle)) + { + result.AddOrUpdate(layerIndex, new List {issue}, (u, listIssues) => + { + listIssues.Add(issue); + return listIssues; + }); + } + } + + return result; + } + + public void RepairLayers(uint layerStart, uint layerEnd, uint closingIterations = 1, uint openingIterations = 1, + bool repairIslands = true, bool repairResinTraps = true, Dictionary> issues = null, + OperationProgress progress = null) + { + if(ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset(OperationProgress.StatusRepairLayers, layerEnd - layerStart + 1); + Parallel.For(layerStart, layerEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + Layer layer = this[layerIndex]; + using (var image = layer.LayerMat) + { + if (repairResinTraps && !ReferenceEquals(issues, null)) + { + if (issues.TryGetValue((uint)layerIndex, out var issueList)) + { + foreach (var issue in issueList.Where(issue => issue.Type == LayerIssue.IssueType.ResinTrap)) + { + using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels))) + { + CvInvoke.DrawContours(image, + vec, + -1, + new MCvScalar(255), + -1); + } + + /*CvInvoke.DrawContours(image, + new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels)), + -1, + new MCvScalar(255), + 2);*/ + } + } + } + + if (repairIslands) + { + using (Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), + new Point(-1, -1))) + { + if (closingIterations > 0) + { + CvInvoke.MorphologyEx(image, image, MorphOp.Close, kernel, new Point(-1, -1), + (int) closingIterations, BorderType.Default, new MCvScalar()); + } + + if (openingIterations > 0) + { + CvInvoke.MorphologyEx(image, image, MorphOp.Open, kernel, new Point(-1, -1), + (int) closingIterations, BorderType.Default, new MCvScalar()); + } + } + } + + layer.LayerMat = image; + lock (progress.Mutex) + { + progress++; + } + } + }); + + progress.Token.ThrowIfCancellationRequested(); + } + + public void ToolPattern(uint startLayerIndex, uint endLayerIndex, OperationPattern settings, OperationProgress progress = null) + { + if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress.Reset("Pattern", endLayerIndex - startLayerIndex + 1); + + Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + + this[layerIndex].ToolPattern(settings); + + lock (progress.Mutex) + { + progress++; + } + }); + + _boundingRectangle = Rectangle.Empty; + + progress.Token.ThrowIfCancellationRequested(); + + if (settings.Anchor == Anchor.None) return; + MutateMove(startLayerIndex, endLayerIndex, new OperationMove(BoundingRectangle, 0, 0, settings.Anchor), progress); + + } + + /// + /// Desmodify all layers + /// + public void Desmodify() + { + for (uint i = 0; i < Count; i++) + { + Layers[i].IsModified = false; + } + } + + /// + /// Clone this object + /// + /// + public LayerManager Clone() + { + LayerManager layerManager = new LayerManager(Count); + foreach (var layer in this) + { + layerManager[layer.Index] = layer.Clone(); + } + + return layerManager; + } + + + #endregion + } +} diff --git a/UVtools.Core/LayerManager.cs b/UVtools.Core/LayerManager.cs deleted file mode 100644 index 86abf92..0000000 --- a/UVtools.Core/LayerManager.cs +++ /dev/null @@ -1,2081 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.Collections; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Emgu.CV; -using Emgu.CV.CvEnum; -using Emgu.CV.Structure; -using Emgu.CV.Util; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - - #region LayerIssue Class - - public class IslandDetectionConfiguration - { - /// - /// Gets if the detection is enabled - /// - public bool Enabled { get; set; } = true; - - /// - /// Gets or sets the binary threshold, all pixels below this value will turn in black, otherwise white - /// Set to 0 to disable this operation - /// - public byte BinaryThreshold { get; set; } = 1; - - /// - /// Gets the required area size (x*y) to consider process a island (0-255) - /// - public byte RequiredAreaToProcessCheck { get; set; } = 1; - - /// - /// Gets the required brightness for check a pixel under a island (0-255) - /// - public byte RequiredPixelBrightnessToProcessCheck { get; set; } = 10; - - /// - /// Gets the required number of pixels to support a island and discard it as a issue (0-255) - /// - public byte RequiredPixelsToSupport { get; set; } = 10; - - /// - /// Gets the required brightness of supporting pixels to count as a valid support (0-255) - /// - public byte RequiredPixelBrightnessToSupport { get; set; } = 150; - } - - public class ResinTrapDetectionConfiguration - { - /// - /// Gets if the detection is enabled - /// - public bool Enabled { get; set; } = true; - - /// - /// Gets or sets the binary threshold, all pixels below this value will turn in black, otherwise white - /// Set to 0 to disable this operation - /// - public byte BinaryThreshold { get; set; } = 127; - - /// - /// Gets the required area size (x*y) to consider process a hollow area (0-255) - /// - public byte RequiredAreaToProcessCheck { get; set; } = 1; - - /// - /// Gets the number of black pixels required to consider a drain - /// - public byte RequiredBlackPixelsToDrain { get; set; } = 10; - - /// - /// Gets the maximum pixel brightness to be a drain pixel (0-150) - /// - public byte MaximumPixelBrightnessToDrain { get; set; } = 30; - } - - - public class LayerIssue : IEnumerable - { - public enum IssueType : byte - { - Island, - ResinTrap, - TouchingBound, - //HoleSandwich, - } - - /// - /// Gets the parent layer - /// - public Layer Layer { get; } - - /// - /// Gets the issue type associated - /// - public IssueType Type { get; } - - /// - /// Gets the pixels containing the issue - /// - public Point[] Pixels { get; } - - /// - /// Gets the bounding rectangle of the pixel area - /// - public Rectangle BoundingRectangle { get; } - - /// - /// Gets the X coordinate for the first point, -1 if doesn't exists - /// - public int X => HaveValidPoint ? Pixels[0].X : -1; - - /// - /// Gets the Y coordinate for the first point, -1 if doesn't exists - /// - public int Y => HaveValidPoint ? Pixels[0].Y : -1; - - /// - /// Gets the XY point for first point - /// - public Point Point => HaveValidPoint ? Pixels[0] : new Point(-1, -1); - - /// - /// Gets the number of pixels on this issue - /// - public uint Size { - get - { - if (Type == IssueType.ResinTrap && !BoundingRectangle.IsEmpty) - { - return (uint) (BoundingRectangle.Width * BoundingRectangle.Height); - } - - if (ReferenceEquals(Pixels, null)) return 0; - return (uint) Pixels.Length; - } - } - - /// - /// Check if this issue have a valid start point to show - /// - public bool HaveValidPoint => !ReferenceEquals(Pixels, null) && Pixels.Length > 0; - - public LayerIssue(Layer layer, IssueType type, Point[] pixels = null, Rectangle boundingRectangle = new Rectangle()) - { - Layer = layer; - Type = type; - Pixels = pixels; - BoundingRectangle = boundingRectangle; - } - - public Point this[uint index] => Pixels[index]; - - public Point this[int index] => Pixels[index]; - - public IEnumerator GetEnumerator() - { - return ((IEnumerable)Pixels).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public override string ToString() - { - return $"{nameof(Type)}: {Type}, Layer: {Layer.Index}, {nameof(X)}: {X}, {nameof(Y)}: {Y}, {nameof(Size)}: {Size}"; - } - } - #endregion - - #region LayerHollowArea - - public class LayerHollowArea : IEnumerable - { - public enum AreaType : byte - { - Unknown = 0, - Trap, - Drain - } - /// - /// Gets area pixels - /// - public Point[] Contour { get; } - - public System.Drawing.Rectangle BoundingRectangle { get; } - - public AreaType Type { get; set; } = AreaType.Unknown; - - public bool Processed { get; set; } - - #region Indexers - public Point this[uint index] - { - get => index < Contour.Length ? Contour[index] : Point.Empty; - set => Contour[index] = value; - } - - public Point this[int index] - { - get => index < Contour.Length ? Contour[index] : Point.Empty; - set => Contour[index] = value; - } - - public Point this[uint x, uint y] - { - get - { - for (uint i = 0; i < Contour.Length; i++) - { - if (Contour[i].X == x && Contour[i].Y == y) return Contour[i]; - } - return Point.Empty; - } - } - - public Point this[int x, int y] => this[(uint) x, (uint)y]; - - public Point this[Point point] => this[point.X, point.Y]; - - #endregion - - public IEnumerator GetEnumerator() - { - return ((IEnumerable)Contour).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public LayerHollowArea() - { - } - - public LayerHollowArea(Point[] contour, System.Drawing.Rectangle boundingRectangle, AreaType type = AreaType.Unknown) - { - Contour = contour; - BoundingRectangle = boundingRectangle; - Type = type; - } - } - #endregion - - #region Layer Class - /// - /// Represent a Layer - /// - public class Layer : IEquatable, IEquatable - { - #region Properties - - /// - /// Gets the parent layer manager - /// - public LayerManager ParentLayerManager { get; set; } - - /// - /// Gets the number of non zero pixels on this layer image - /// - public uint NonZeroPixelCount { get; private protected set; } - - /// - /// Gets the bounding rectangle for the image area - /// - public Rectangle BoundingRectangle { get; private protected set; } = Rectangle.Empty; - - /// - /// Gets the layer index - /// - public uint Index { get; } - - /// - /// Gets or sets the exposure time in raw value - /// - public float ExposureTime { get; set; } - - /// - /// Gets or sets the layer position on Z in raw value - /// - public float PositionZ { get; set; } - - private byte[] _compressedBytes; - /// - /// Gets or sets layer image compressed data - /// - public byte[] CompressedBytes - { - get => LayerManager.DecompressLayer(_compressedBytes); - set - { - _compressedBytes = LayerManager.CompressLayer(value); - IsModified = true; - if(!ReferenceEquals(ParentLayerManager, null)) - ParentLayerManager.BoundingRectangle = Rectangle.Empty; - } - } - - /// - /// Gets the original filename, null if no filename attached with layer - /// - public string Filename { get; set; } - - /// - /// Gets if layer has been modified - /// - public bool IsModified { get; set; } - - /// - /// Gets or sets a new image instance - /// - public Mat LayerMat - { - get - { - Mat mat = new Mat(); - CvInvoke.Imdecode(CompressedBytes, ImreadModes.Grayscale, mat); - return mat; - } - set - { - using (var vector = new VectorOfByte()) - { - CvInvoke.Imencode(".png", value, vector); - CompressedBytes = vector.ToArray(); - - GetBoundingRectangle(value, true); - } - } - } - - /// - /// Gets a new Brg image instance - /// - public Mat BrgMat - { - get - { - Mat mat = LayerMat; - CvInvoke.CvtColor(mat, mat, ColorConversion.Gray2Bgr); - return mat; - } - } - - #endregion - - #region Constructor - public Layer(uint index, byte[] compressedBytes, string filename = null, LayerManager pararentLayerManager = null) - { - ParentLayerManager = pararentLayerManager; - Index = index; - Filename = filename ?? $"Layer{index}.png"; - CompressedBytes = compressedBytes; - IsModified = false; - /*if (compressedBytes.Length > 0) - { - GetBoundingRectangle(); - }*/ - } - - public Layer(uint index, Mat layerMat, string filename = null, LayerManager pararentLayerManager = null) : this(index, new byte[0], filename, pararentLayerManager) - { - LayerMat = layerMat; - IsModified = false; - } - - - public Layer(uint index, Stream stream, string filename = null, LayerManager pararentLayerManager = null) : this(index, stream.ToArray(), filename, pararentLayerManager) - { } - #endregion - - #region Equatables - - public static bool operator ==(Layer obj1, Layer obj2) - { - return obj1.Equals(obj2); - } - - public static bool operator !=(Layer obj1, Layer obj2) - { - return !obj1.Equals(obj2); - } - - public static bool operator >(Layer obj1, Layer obj2) - { - return obj1.Index > obj2.Index; - } - - public static bool operator <(Layer obj1, Layer obj2) - { - return obj1.Index < obj2.Index; - } - - public static bool operator >=(Layer obj1, Layer obj2) - { - return obj1.Index >= obj2.Index; - } - - public static bool operator <=(Layer obj1, Layer obj2) - { - return obj1.Index <= obj2.Index; - } - - public bool Equals(uint other) - { - return Index == other; - } - - public bool Equals(Layer other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return Equals(_compressedBytes, other._compressedBytes); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((Layer)obj); - } - - public override int GetHashCode() - { - return (_compressedBytes != null ? _compressedBytes.GetHashCode() : 0); - } - - private sealed class IndexRelationalComparer : IComparer - { - public int Compare(Layer x, Layer y) - { - if (ReferenceEquals(x, y)) return 0; - if (ReferenceEquals(null, y)) return 1; - if (ReferenceEquals(null, x)) return -1; - return x.Index.CompareTo(y.Index); - } - } - - public static IComparer IndexComparer { get; } = new IndexRelationalComparer(); - #endregion - - #region Formaters - public override string ToString() - { - return $"{nameof(Index)}: {Index}, {nameof(Filename)}: {Filename}, {nameof(IsModified)}: {IsModified}"; - } - #endregion - - #region Methods - - public Rectangle GetBoundingRectangle(Mat mat = null, bool reCalculate = false) - { - if (NonZeroPixelCount > 0 && !reCalculate) - { - return BoundingRectangle; - } - bool needDispose = false; - if (ReferenceEquals(mat, null)) - { - mat = LayerMat; - needDispose = true; - } - - using (var nonZeroMat = new Mat()) - { - CvInvoke.FindNonZero(mat, nonZeroMat); - NonZeroPixelCount = (uint)nonZeroMat.Rows / 2; - BoundingRectangle = CvInvoke.BoundingRectangle(nonZeroMat); - } - - - if(needDispose) mat.Dispose(); - - return BoundingRectangle; - } - - public Layer PreviousLayer() - { - if (ReferenceEquals(ParentLayerManager, null) || Index == 0) - return null; - - return ParentLayerManager[Index - 1]; - } - - public Layer NextLayer() - { - if (ReferenceEquals(ParentLayerManager, null) || Index >= ParentLayerManager.Count - 1) - return null; - - return ParentLayerManager[Index + 1]; - } - - /// - /// Gets all islands start pixel location for this layer - /// https://www.geeksforgeeks.org/find-number-of-islands/ - /// - /// holding all islands coordinates - public List GetIssues(uint requiredPixelsToSupportIsland = 5) - { - if (requiredPixelsToSupportIsland == 0) - requiredPixelsToSupportIsland = 1; - - // These arrays are used to - // get row and column numbers - // of 8 neighbors of a given cell - List result = new List(); - List pixels = new List(); - - - - var mat = LayerMat; - var bytes = mat.GetPixelSpan(); - - - - var previousLayerImage = PreviousLayer()?.LayerMat; - var previousBytes = previousLayerImage?.GetBytes(); - - - /*var nextLayerImage = NextLayer()?.Image; - byte[] nextBytes = null; - if (!ReferenceEquals(nextLayerImage, null)) - { - if (nextLayerImage.TryGetSinglePixelSpan(out var nextPixelSpan)) - { - nextBytes = MemoryMarshal.AsBytes(nextPixelSpan).ToArray(); - } - }*/ - - // Make a bool array to - // mark visited cells. - // Initially all cells - // are unvisited - bool[,] visited = new bool[mat.Width, mat.Height]; - - // Initialize count as 0 and - // traverse through the all - // cells of given matrix - //uint count = 0; - - // Island checker - sbyte[] rowNbr = { -1, -1, -1, 0, 0, 1, 1, 1 }; - sbyte[] colNbr = { -1, 0, 1, -1, 1, -1, 0, 1 }; - const uint minPixel = 10; - const uint minPixelForSupportIsland = 200; - int pixelIndex; - uint islandSupportingPixels; - if (Index > 0) - { - for (int y = 0; y < mat.Height; y++) - { - for (int x = 0; x < mat.Width; x++) - { - pixelIndex = y * mat.Width + x; - - /*if (bytes[pixelIndex] == 0 && previousBytes?[pixelIndex] == byte.MaxValue && - nextBytes?[pixelIndex] == byte.MaxValue) - { - result.Add(new LayerIssue(this, LayerIssue.IssueType.HoleSandwich, new []{new Point(x, y)})); - }*/ - - if (bytes[pixelIndex] > minPixel && !visited[x, y]) - { - // If a cell with value 1 is not - // visited yet, then new island - // found, Visit all cells in this - // island and increment island count - pixels.Clear(); - pixels.Add(new Point(x, y)); - islandSupportingPixels = previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0; - - int minX = x; - int maxX = x; - int minY = y; - int maxY = y; - - int x2; - int y2; - - - Queue queue = new Queue(); - queue.Enqueue(new Point(x, y)); - // Mark this cell as visited - visited[x, y] = true; - - while (queue.Count > 0) - { - var point = queue.Dequeue(); - y2 = point.Y; - x2 = point.X; - for (byte k = 0; k < 8; k++) - { - //if (isSafe(y2 + rowNbr[k], x2 + colNbr[k])) - var tempy2 = y2 + rowNbr[k]; - var tempx2 = x2 + colNbr[k]; - pixelIndex = tempy2 * mat.Width + tempx2; - if (tempy2 >= 0 && - tempy2 < mat.Height && - tempx2 >= 0 && tempx2 < mat.Width && - bytes[pixelIndex] >= minPixel && - !visited[tempx2, tempy2]) - { - visited[tempx2, tempy2] = true; - point = new Point(tempx2, tempy2); - pixels.Add(point); - queue.Enqueue(point); - - minX = Math.Min(minX, tempx2); - maxX = Math.Max(maxX, tempx2); - minY = Math.Min(minY, tempy2); - maxY = Math.Max(maxY, tempy2); - - islandSupportingPixels += previousBytes[pixelIndex] >= minPixelForSupportIsland ? 1u : 0; - } - } - } - //count++; - - if (islandSupportingPixels >= requiredPixelsToSupportIsland) - continue; // Not a island, bounding is strong - if (islandSupportingPixels > 0 && pixels.Count < requiredPixelsToSupportIsland && - islandSupportingPixels >= Math.Max(1, pixels.Count / 2)) continue; // Not a island - result.Add(new LayerIssue(this, LayerIssue.IssueType.Island, pixels.ToArray(), new Rectangle(minX, minY, maxX-minX, maxY-minY))); - } - } - } - } - - pixels.Clear(); - - // TouchingBounds Checker - for (int x = 0; x < mat.Width; x++) // Check Top and Bottom bounds - { - if (bytes[x] >= 200) // Top - { - pixels.Add(new Point(x, 0)); - } - - if (bytes[mat.Width * mat.Height - mat.Width + x] >= 200) // Bottom - { - pixels.Add(new Point(x, mat.Height-1)); - } - } - - for (int y = 0; y < mat.Height; y++) // Check Left and Right bounds - { - if (bytes[y * mat.Width] >= 200) // Left - { - pixels.Add(new Point(0, y)); - } - - if (bytes[y * mat.Width + mat.Width - 1] >= 200) // Right - { - pixels.Add(new Point(mat.Width-1, y)); - } - } - - if (pixels.Count > 0) - { - result.Add(new LayerIssue(this, LayerIssue.IssueType.TouchingBound, pixels.ToArray())); - } - - pixels.Clear(); - - return result; - } - - public void MutateMove(OperationMove move) - { - using (var layer = LayerMat) - { - if (move.ImageWidth == 0) move.ImageWidth = (uint) layer.Width; - if (move.ImageHeight == 0) move.ImageHeight = (uint) layer.Height; - - /*layer.Transform(1.0, 1.0, move.MarginLeft - move.MarginRight, move.MarginTop-move.MarginBottom); - LayerMat = layer;*/ - using (var layerRoi = new Mat(layer, move.SrcRoi)) - { - using (var dstLayer = layer.CloneBlank()) - { - using (var dstRoi = new Mat(dstLayer, move.DstRoi)) - { - layerRoi.CopyTo(dstRoi); - LayerMat = dstLayer; - } - } - } - } - } - - - public void MutateResize(double xScale, double yScale) - { - using (var mat = LayerMat) - { - mat.TransformFromCenter(xScale, yScale); - LayerMat = mat; - } - } - - public void MutateFlip(FlipType flipType, bool makeCopy = true) - { - using (var mat = LayerMat) - { - if (makeCopy) - { - using (Mat dst = new Mat()) - { - CvInvoke.Flip(mat, dst, flipType); - var spanSrc = mat.GetPixelSpan(); - var spanDst = dst.GetPixelSpan(); - for (int i = 0; i < spanSrc.Length; i++) - { - if (spanDst[i] == 0) continue; - spanSrc[i] = spanDst[i]; - } - - LayerMat = mat; - - } - } - else - { - CvInvoke.Flip(mat, mat, flipType); - } - - LayerMat = mat; - } - } - - public void MutateRotate(double angle = 90.0, Inter interpolation = Inter.Linear) - { - using (var mat = LayerMat) - { - var halfWidth = mat.Width / 2.0f; - var halfHeight = mat.Height / 2.0f; - using (var translateTransform = new Matrix(2, 3)) - { - CvInvoke.GetRotationMatrix2D(new PointF(halfWidth, halfHeight), angle, 1.0, translateTransform); - /*var rect = new RotatedRect(PointF.Empty, mat.Size, (float) angle).MinAreaRect(); - translateTransform[0, 2] += rect.Width / 2.0 - mat.Cols / 2.0; - translateTransform[0, 2] += rect.Height / 2.0 - mat.Rows / 2.0;*/ - - /* var abs_cos = Math.Abs(translateTransform[0, 0]); - var abs_sin = Math.Abs(translateTransform[0, 1]); - - var bound_w = mat.Height * abs_sin + mat.Width * abs_cos; - var bound_h = mat.Height * abs_cos + mat.Width * abs_sin; - - translateTransform[0, 2] += bound_w / 2 - halfWidth; - translateTransform[1, 2] += bound_h / 2 - halfHeight;*/ - - - CvInvoke.WarpAffine(mat, mat, translateTransform, mat.Size, interpolation); - } - - LayerMat = mat; - } - } - - public void MutateSolidify() - { - using (Mat mat = LayerMat) - { - using (Mat filteredMat = new Mat()) - { - CvInvoke.Threshold(mat, filteredMat, 254, 255, ThresholdType.Binary); // Clean AA - - using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint()) - { - using (Mat hierarchy = new Mat()) - { - CvInvoke.FindContours(filteredMat, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple); - var arr = hierarchy.GetData(); - for (int i = 0; i < contours.Size; i++) - { - if ((int) arr.GetValue(0, i, 2) != -1 || (int) arr.GetValue(0, i, 3) == -1) continue; - CvInvoke.DrawContours(mat, contours, i, new MCvScalar(255), -1); - } - } - } - } - - LayerMat = mat; - } - } - - public void MutateErode(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if(anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.Erode(dst, dst, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateDilate(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.Dilate(dst, dst, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateOpen(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.MorphologyEx(dst, dst, MorphOp.Open, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateClose(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.MorphologyEx(dst, dst, MorphOp.Close, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateGradient(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.MorphologyEx(dst, dst, MorphOp.Gradient, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutatePyrDownUp(BorderType borderType = BorderType.Reflect101) - { - using (Mat dst = LayerMat) - { - CvInvoke.PyrDown(dst, dst, borderType); - CvInvoke.PyrUp(dst, dst, borderType); - LayerMat = dst; - } - } - - public void MutateMedianBlur(int aperture = 1) - { - using (Mat dst = LayerMat) - { - CvInvoke.MedianBlur(dst, dst, aperture); - LayerMat = dst; - } - } - - public void MutateGaussianBlur(Size size = default, int sigmaX = 0, int sigmaY = 0, BorderType borderType = BorderType.Reflect101) - { - if(size.IsEmpty) size = new Size(5, 5); - - using (Mat dst = LayerMat) - { - CvInvoke.GaussianBlur(dst, dst, size, sigmaX, sigmaY, borderType); - LayerMat = dst; - } - } - - public void ToolPattern(OperationPattern settings) - { - using (var layer = LayerMat) - { - using (var layerRoi = new Mat(layer, settings.SrcRoi)) - { - using (var dstLayer = layer.CloneBlank()) - { - for (ushort col = 0; col < settings.Cols; col++) - { - for (ushort row = 0; row < settings.Rows; row++) - { - using (var dstRoi = new Mat(dstLayer, settings.GetRoi(col, row))) - { - layerRoi.CopyTo(dstRoi); - } - } - } - - LayerMat = dstLayer; - } - } - } - } - - - - public Layer Clone() - { - return new Layer(Index, CompressedBytes, Filename, ParentLayerManager); - } - - - - #endregion - } - #endregion - - #region LayerManager Class - public class LayerManager : IEnumerable - { - #region Enums - public enum Mutate : byte - { - Move, - Resize, - Flip, - Rotate, - Solidify, - //LayerSmash, - Erode, - Dilate, - Opening, - Closing, - Gradient, - TopHat, - BlackHat, - HitMiss, - PyrDownUp, - SmoothMedian, - SmoothGaussian, - } - #endregion - - #region Properties - /// - /// Layers List - /// - public Layer[] Layers { get; } - - private Rectangle _boundingRectangle = Rectangle.Empty; - public Rectangle BoundingRectangle - { - get => GetBoundingRectangle(); - set => _boundingRectangle = value; - } - - /// - /// Gets the layers count - /// - public uint Count => (uint) Layers.Length; - - /// - /// Gets if any layer got modified, otherwise false - /// - public bool IsModified - { - get - { - for (uint i = 0; i < Count; i++) - { - if (Layers[i].IsModified) return true; - } - return false; - } - } - - - #endregion - - #region Constructors - public LayerManager(uint layerCount) - { - Layers = new Layer[layerCount]; - } - #endregion - - #region Indexers - public Layer this[uint index] - { - get => Layers[index]; - set => AddLayer(index, value); - } - - public Layer this[int index] - { - get => Layers[index]; - set => AddLayer((uint) index, value); - } - - public Layer this[long index] - { - get => Layers[index]; - set => AddLayer((uint) index, value); - } - - #endregion - - #region Numerators - public IEnumerator GetEnumerator() - { - return ((IEnumerable)Layers).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - #endregion - - #region Static Methods - /// - /// Compress a layer from a - /// - /// to compress - /// Compressed byte array - public static byte[] CompressLayer(Stream input) - { - return CompressLayer(input.ToArray()); - } - - /// - /// Compress a layer from a byte array - /// - /// byte array to compress - /// Compressed byte array - public static byte[] CompressLayer(byte[] input) - { - return input; - /*using (MemoryStream output = new MemoryStream()) - { - using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal)) - { - dstream.Write(input, 0, input.Length); - } - return output.ToArray(); - }*/ - } - - /// - /// Decompress a layer from a byte array - /// - /// byte array to decompress - /// Decompressed byte array - public static byte[] DecompressLayer(byte[] input) - { - return input; - /*using (MemoryStream ms = new MemoryStream(input)) - { - using (MemoryStream output = new MemoryStream()) - { - using (DeflateStream dstream = new DeflateStream(ms, CompressionMode.Decompress)) - { - dstream.CopyTo(output); - } - return output.ToArray(); - } - }*/ - } - #endregion - - #region Methods - - public Rectangle GetBoundingRectangle(OperationProgress progress = null) - { - if (!_boundingRectangle.IsEmpty) return _boundingRectangle; - _boundingRectangle = this[0].BoundingRectangle; - if (_boundingRectangle.IsEmpty) // Safe checking - { - progress?.Reset(OperationProgress.StatusOptimizingBounds, Count); - Parallel.For(0, Count, layerIndex => - { - if (!ReferenceEquals(progress, null) && progress.Token.IsCancellationRequested) - { - return; - } - - this[layerIndex].GetBoundingRectangle(); - - if (ReferenceEquals(progress, null)) return; - lock (progress.Mutex) - { - progress++; - } - }); - _boundingRectangle = this[0].BoundingRectangle; - - if (!ReferenceEquals(progress, null) && progress.Token.IsCancellationRequested) - { - _boundingRectangle = Rectangle.Empty; - progress.Token.ThrowIfCancellationRequested(); - } - - } - - progress?.Reset(OperationProgress.StatusCalculatingBounds, Count); - for (int i = 1; i < Count; i++) - { - _boundingRectangle = Rectangle.Union(_boundingRectangle, this[i].BoundingRectangle); - if (ReferenceEquals(progress, null)) continue; - progress++; - } - - return _boundingRectangle; - } - - /// - /// Add a layer - /// - /// Layer index - /// Layer to add - public void AddLayer(uint index, Layer layer) - { - Layers[index] = layer; - layer.ParentLayerManager = this; - } - - /// - /// Get layer given index - /// - /// Layer index - /// - public Layer GetLayer(uint index) - { - return Layers[index]; - } - - public void MutateMove(uint startLayerIndex, uint endLayerIndex, OperationMove move, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Moving", endLayerIndex - startLayerIndex + 1); - - if (move.SrcRoi == Rectangle.Empty) move.SrcRoi = GetBoundingRectangle(progress); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - - this[layerIndex].MutateMove(move); - - lock (progress.Mutex) - { - progress++; - } - }); - - _boundingRectangle = Rectangle.Empty; - - progress.Token.ThrowIfCancellationRequested(); - - - } - - /// - /// Resizes layer images in x and y factor, starting at 1 = 100% - /// - /// Layer index to start - /// Layer index to end - /// X factor, starts at 1 - /// Y factor, starts at 1 - /// Fade X/Y towards 100% - public void MutateResize(uint startLayerIndex, uint endLayerIndex, double x, double y, bool isFade, OperationProgress progress = null) - { - if (x == 1.0 && y == 1.0) return; - - if(ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Resizing", endLayerIndex - startLayerIndex + 1); - - double xSteps = Math.Abs(x - 1.0) / (endLayerIndex - startLayerIndex); - double ySteps = Math.Abs(y - 1.0) / (endLayerIndex - startLayerIndex); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - var newX = x; - var newY = y; - if (isFade) - { - if (newX != 1.0) - { - - //maxIteration = Math.Max(iterationsStart, iterationsEnd); - - newX = (float)(newX < 1.0 - ? newX + (layerIndex - startLayerIndex) * xSteps - : newX - (layerIndex - startLayerIndex) * xSteps); - - // constrain - //iterations = Math.Min(Math.Max(1, iterations), maxIteration); - } - - if (y != 1.0) - { - - //maxIteration = Math.Max(iterationsStart, iterationsEnd); - - newY = (float)(newY < 1.0 - ? newY + (layerIndex - startLayerIndex) * ySteps - : newY - (layerIndex - startLayerIndex) * ySteps); - - // constrain - //iterations = Math.Min(Math.Max(1, iterations), maxIteration); - } - } - - lock (progress.Mutex) - { - progress++; - } - - if (newX == 1.0 && newY == 1.0) return; - - this[layerIndex].MutateResize(newX, newY); - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateFlip(uint startLayerIndex, uint endLayerIndex, FlipType flipType, bool makeCopy = false, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Fliping", endLayerIndex - startLayerIndex + 1); - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].MutateFlip(flipType, makeCopy); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateRotate(uint startLayerIndex, uint endLayerIndex, double angle, Inter interpolation = Inter.Linear, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Rotating", endLayerIndex - startLayerIndex+1); - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].MutateRotate(angle, interpolation); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateSolidify(uint startLayerIndex, uint endLayerIndex, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Solidifing", endLayerIndex - startLayerIndex+1); - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].MutateSolidify(); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - private void MutateGetVarsIterationFade(uint startLayerIndex, uint endLayerIndex, int iterationsStart, int iterationsEnd, ref bool isFade, out int iterationSteps, out int maxIteration) - { - iterationSteps = 0; - maxIteration = 0; - isFade = isFade && startLayerIndex != endLayerIndex && iterationsStart != iterationsEnd; - if (!isFade) return; - iterationSteps = (int)Math.Abs((double)(iterationsStart - iterationsEnd) / (endLayerIndex - startLayerIndex)); - maxIteration = Math.Max(iterationsStart, iterationsEnd); - } - - private int MutateGetIterationVar(bool isFade, int iterationsStart, int iterationsEnd, int iterationSteps, int maxIteration, uint startLayerIndex, uint layerIndex) - { - if(!isFade) return iterationsStart; - // calculate iterations based on range - int iterations = (int)(iterationsStart < iterationsEnd - ? iterationsStart + (layerIndex - startLayerIndex) * iterationSteps - : iterationsStart - (layerIndex - startLayerIndex) * iterationSteps); - - // constrain - return Math.Min(Math.Max(1, iterations), maxIteration); - } - - public void MutateErode(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Eroding", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint) layerIndex); - this[layerIndex].MutateErode(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateDilate(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Dilating", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); - this[layerIndex].MutateDilate(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateOpen(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Removing Noise", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); - this[layerIndex].MutateOpen(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateClose(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Gap Closing", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); - this[layerIndex].MutateClose(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateGradient(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Gradient", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); - this[layerIndex].MutateGradient(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutatePyrDownUp(uint startLayerIndex, uint endLayerIndex, BorderType borderType = BorderType.Default, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("PryDownUp", endLayerIndex - startLayerIndex+1); - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].MutatePyrDownUp(borderType); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateMedianBlur(uint startLayerIndex, uint endLayerIndex, int aperture = 1, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Bluring", endLayerIndex - startLayerIndex+1); - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].MutateMedianBlur(aperture); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateGaussianBlur(uint startLayerIndex, uint endLayerIndex, Size size = default, int sigmaX = 0, int sigmaY = 0, BorderType borderType = BorderType.Reflect101, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Bluring", endLayerIndex - startLayerIndex+1); - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].MutateGaussianBlur(size, sigmaX, sigmaY, borderType); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public ConcurrentDictionary> GetAllIssues( - IslandDetectionConfiguration islandConfig = null, ResinTrapDetectionConfiguration resinTrapConfig = null, - OperationProgress progress = null) - { - if(ReferenceEquals(islandConfig, null)) islandConfig = new IslandDetectionConfiguration(); - if(ReferenceEquals(resinTrapConfig, null)) resinTrapConfig = new ResinTrapDetectionConfiguration(); - if(ReferenceEquals(progress, null)) progress = new OperationProgress(); - - const byte minTouchingBondsPixelColor = 200; - - var result = new ConcurrentDictionary>(); - var layerHollowAreas = new ConcurrentDictionary>(); - - bool islandsFinished = false; - - progress.Reset(OperationProgress.StatusIslands, Count); - - Parallel.Invoke(() => - { - if (!islandConfig.Enabled) - { - islandsFinished = true; - return; - } - // Detect contours - Parallel.ForEach(this, - //new ParallelOptions{MaxDegreeOfParallelism = 1}, - layer => - { - if (progress.Token.IsCancellationRequested) return; - using (var image = layer.LayerMat) - { - int step = image.Step; - var span = image.GetPixelSpan(); - - // TouchingBounds Checker - List pixels = new List(); - for (int x = 0; x < image.Width; x++) // Check Top and Bottom bounds - { - if (span[x] >= minTouchingBondsPixelColor) // Top - { - pixels.Add(new Point(x, 0)); - } - - if (span[step * image.Height - step + x] >= minTouchingBondsPixelColor) // Bottom - { - pixels.Add(new Point(x, image.Height - 1)); - } - } - - for (int y = 0; y < image.Height; y++) // Check Left and Right bounds - { - if (span[y * step] >= minTouchingBondsPixelColor) // Left - { - pixels.Add(new Point(0, y)); - } - - if (span[y * step + step - 1] >= minTouchingBondsPixelColor) // Right - { - pixels.Add(new Point(step - 1, y)); - } - } - - if (pixels.Count > 0) - { - result.TryAdd(layer.Index, new List - { - new LayerIssue(layer, LayerIssue.IssueType.TouchingBound, pixels.ToArray()) - }); - } - - - if (layer.Index == 0) return; // No islands for layer 0 - - VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); - Mat hierarchy = new Mat(); - - if (islandConfig.BinaryThreshold > 0) - { - using (var thresholdImage = new Mat()) - { - CvInvoke.Threshold(image, thresholdImage, 1, 255, ThresholdType.Binary); - CvInvoke.FindContours(thresholdImage, contours, hierarchy, RetrType.Ccomp, - ChainApproxMethod.ChainApproxSimple); - } - } - else - { - CvInvoke.FindContours(image, contours, hierarchy, RetrType.Ccomp, - ChainApproxMethod.ChainApproxSimple); - } - - var arr = hierarchy.GetData(); - // - //hierarchy[i][0]: the index of the next contour of the same level - //hierarchy[i][1]: the index of the previous contour of the same level - //hierarchy[i][2]: the index of the first child - //hierarchy[i][3]: the index of the parent - // - - Mat previousImage = null; - Span previousSpan = null; - for (int i = 0; i < contours.Size; i++) - { - - if ((int) arr.GetValue(0, i, 2) == -1 && (int) arr.GetValue(0, i, 3) != -1) continue; - var rect = CvInvoke.BoundingRectangle(contours[i]); - if (rect.GetArea() < islandConfig.RequiredAreaToProcessCheck) - continue; - - if (ReferenceEquals(previousImage, null)) - { - previousImage = this[layer.Index - 1].LayerMat; - previousSpan = previousImage.GetPixelSpan(); - } - - List points = new List(); - uint pixelsSupportingIsland = 0; - - using (Mat contourImage = image.CloneBlank()) - { - CvInvoke.DrawContours(contourImage, contours, i, new MCvScalar(255), -1); - var contourImageSpan = contourImage.GetPixelSpan(); - - for (int y = rect.Y; y < rect.Bottom; y++) - { - for (int x = rect.X; x < rect.Right; x++) - { - int pixel = step * y + x; - if (span[pixel] < islandConfig.RequiredPixelBrightnessToProcessCheck) - continue; // Low brightness, ignore - if (contourImageSpan[pixel] != 255) - continue; // Not inside contour, ignore - - //if (CvInvoke.PointPolygonTest(contours[i], new PointF(x, y), false) < 0) continue; // Out of contour SLOW! - //Debug.WriteLine($"Layer: {layer.Index}, Coutour: {i}, X:{x} Y:{y}"); - points.Add(new Point(x, y)); - - if (previousSpan[pixel] >= islandConfig.RequiredPixelBrightnessToSupport) - { - pixelsSupportingIsland++; - } - } - } - } - - - if (points.Count == 0) continue; - if (pixelsSupportingIsland >= islandConfig.RequiredPixelsToSupport) - continue; // Not a island, bounding is strong, i think... - if (pixelsSupportingIsland > 0 && points.Count < islandConfig.RequiredPixelsToSupport && - pixelsSupportingIsland >= Math.Max(1, points.Count / 2)) - continue; // Not a island, but maybe weak bounding... - - - var issue = new LayerIssue(layer, LayerIssue.IssueType.Island, points.ToArray(), rect); - result.AddOrUpdate(layer.Index, new List {issue}, - (layerIndex, list) => - { - list.Add(issue); - return list; - }); - } - - contours.Dispose(); - hierarchy.Dispose(); - previousImage?.Dispose(); - } - - lock (progress.Mutex) - { - progress++; - } - }); - islandsFinished = true; - }, () => - { - if (!resinTrapConfig.Enabled) return; - // Detect contours - Parallel.ForEach(this, - //new ParallelOptions{MaxDegreeOfParallelism = 1}, - layer => - { - if (progress.Token.IsCancellationRequested) return; - using (var image = layer.LayerMat) - { - if (resinTrapConfig.BinaryThreshold > 0) - { - CvInvoke.Threshold(image, image, resinTrapConfig.BinaryThreshold, 255, ThresholdType.Binary); - } - - var listHollowArea = new List(); - - using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint()) - { - using (Mat hierarchy = new Mat()) - { - - CvInvoke.FindContours(image, contours, hierarchy, RetrType.Ccomp, - ChainApproxMethod.ChainApproxSimple); - - var arr = hierarchy.GetData(); - // - //hierarchy[i][0]: the index of the next contour of the same level - //hierarchy[i][1]: the index of the previous contour of the same level - //hierarchy[i][2]: the index of the first child - //hierarchy[i][3]: the index of the parent - // - - for (int i = 0; i < contours.Size; i++) - { - if ((int) arr.GetValue(0, i, 2) != -1 || (int) arr.GetValue(0, i, 3) == -1) - continue; - var rect = CvInvoke.BoundingRectangle(contours[i]); - if(rect.GetArea() < resinTrapConfig.RequiredAreaToProcessCheck) continue; - - listHollowArea.Add(new LayerHollowArea(contours[i].ToArray(), - rect, - layer.Index == Count - 1 - ? LayerHollowArea.AreaType.Drain - : LayerHollowArea.AreaType.Unknown)); - - if (listHollowArea.Count > 0) - layerHollowAreas.TryAdd(layer.Index, listHollowArea); - } - } - } - } - }); - - - for (uint layerIndex = 0; layerIndex < Count - 1; layerIndex++) // Last layers, always drains - { - if (progress.Token.IsCancellationRequested) break; - if (!layerHollowAreas.TryGetValue(layerIndex, out var areas)) - continue; // No hollow areas in this layer, ignore - - byte areaCount = 0; - //foreach (var area in areas) - - Parallel.ForEach(from t in areas where t.Type == LayerHollowArea.AreaType.Unknown select t, area => - { - if (progress.Token.IsCancellationRequested) return; - if (area.Type != LayerHollowArea.AreaType.Unknown) return; // processed, ignore - area.Type = LayerHollowArea.AreaType.Trap; - - areaCount++; - - List linkedAreas = new List(); - - for (sbyte dir = 1; dir >= -1 && area.Type != LayerHollowArea.AreaType.Drain; dir -= 2) - //Parallel.ForEach(new sbyte[] {1, -1}, new ParallelOptions {MaxDegreeOfParallelism = 2}, dir => - { - Queue queue = new Queue(); - queue.Enqueue(area); - area.Processed = false; - int nextLayerIndex = (int) layerIndex; - while (queue.Count > 0 && area.Type != LayerHollowArea.AreaType.Drain) - { - if (progress.Token.IsCancellationRequested) return; - - LayerHollowArea checkArea = queue.Dequeue(); - if (checkArea.Processed) continue; - checkArea.Processed = true; - nextLayerIndex += dir; - - if (nextLayerIndex < 0 || nextLayerIndex >= Count) - break; // Exhausted layers - bool haveNextAreas = layerHollowAreas.TryGetValue((uint) nextLayerIndex, out var nextAreas); - Dictionary intersectingAreas = new Dictionary(); - - if (islandsFinished) - { - progress.Reset(OperationProgress.StatusResinTraps, Count, (uint) nextLayerIndex); - } - - using (var image = this[nextLayerIndex].LayerMat) - { - var span = image.GetPixelSpan(); - using (var emguImage = image.CloneBlank()) - { - using(var vec = new VectorOfVectorOfPoint(new VectorOfPoint(checkArea.Contour))) - { - CvInvoke.DrawContours(emguImage, vec, -1, new MCvScalar(255), -1); - } - - using (var intersectingAreasMat = image.CloneBlank()) - { - if (haveNextAreas) - { - foreach (var nextArea in nextAreas) - { - if (!checkArea.BoundingRectangle.IntersectsWith( - nextArea.BoundingRectangle)) continue; - intersectingAreas.Add(intersectingAreas.Count + 1, nextArea); - using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(nextArea.Contour))) - { - CvInvoke.DrawContours(intersectingAreasMat, vec, -1, - new MCvScalar(intersectingAreas.Count), -1); - } - } - } - - //Debug.WriteLine($"Area Count: {areaCount} | Next Areas: {intersectingAreas.Count} | Layer: {layerIndex} | Next Layer: {nextLayerIndex} | Dir: {dir}"); - - bool exitPixelLoop = false; - uint blackCount = 0; - - var spanContour = emguImage.GetPixelSpan(); - var spanIntersect = intersectingAreasMat.GetPixelSpan(); - for (int y = checkArea.BoundingRectangle.Y; - y < checkArea.BoundingRectangle.Bottom && - area.Type != LayerHollowArea.AreaType.Drain && !exitPixelLoop; - y++) - { - int pixelPos = image.GetPixelPos(checkArea.BoundingRectangle.X, y) - 1; - for (int x = checkArea.BoundingRectangle.X; - x < checkArea.BoundingRectangle.Right && - area.Type != LayerHollowArea.AreaType.Drain && !exitPixelLoop; - x++) - { - pixelPos++; - - if (spanContour[pixelPos] != 255) continue; // No contour - if (span[pixelPos] > resinTrapConfig.MaximumPixelBrightnessToDrain) continue; // Threshold to ignore white area - blackCount++; - - if (intersectingAreas.Count > 0) // Have areas, can be on same area path or not - { - byte i = spanIntersect[pixelPos]; - if (i == 0 || !intersectingAreas.ContainsKey(i)) // Black pixels - continue; - - //Debug.WriteLine($"BlackCount: {blackCount}, pixel color: {i}, layerindex: {layerIndex}"); - - if (intersectingAreas[i].Type == LayerHollowArea.AreaType.Drain) // Found a drain, stop query - { - area.Type = LayerHollowArea.AreaType.Drain; - exitPixelLoop = true; - } - else - { - queue.Enqueue(intersectingAreas[i]); - } - - linkedAreas.Add(intersectingAreas[i]); - intersectingAreas.Remove(i); - if (intersectingAreas.Count == 0) // Intersection areas sweep end, quit this path - { - exitPixelLoop = true; - break; - } - - //break; - - // Old Way - - /*foreach (var nextAreaCheck in intersectingAreas) - { - using (var vec = new VectorOfPoint(nextAreaCheck.Value.Contour)) - { - //Debug.WriteLine(CvInvoke.PointPolygonTest(vec, new PointF(x, y), false)); - if (CvInvoke.PointPolygonTest(vec, new PointF(x, y), false) < 0) continue; - } - - if (nextAreaCheck.Value.Type == LayerHollowArea.AreaType.Drain) // Found a drain, stop query - { - area.Type = LayerHollowArea.AreaType.Drain; - exitPixelLoop = true; - } - else - { - queue.Enqueue(nextAreaCheck.Value); - } - - linkedAreas.Add(nextAreaCheck.Value); - intersectingAreas.Remove(nextAreaCheck.Key); - if (intersectingAreas.Count == 0) - { - haveNextAreas = false; - exitPixelLoop = true; - } - //exitPixelLoop = true; - break; - - }*/ - } - else if (blackCount > Math.Min(checkArea.Contour.Length / 2, resinTrapConfig.RequiredBlackPixelsToDrain)) // Black pixel without next areas = Drain - { - area.Type = LayerHollowArea.AreaType.Drain; - exitPixelLoop = true; - break; - } - } // X loop - } // Y loop - - if (queue.Count == 0 && blackCount > Math.Min(checkArea.Contour.Length / 2, resinTrapConfig.RequiredBlackPixelsToDrain)) - { - area.Type = LayerHollowArea.AreaType.Drain; - } - - } // Dispose intersecting image - } // Dispose emgu image - } // Dispose image - } // Areas loop - } // Dir layer loop - - foreach (var linkedArea in linkedAreas) // Update linked areas - { - linkedArea.Type = area.Type; - } - }); - } - }); - - if (progress.Token.IsCancellationRequested) return result; - - for (uint layerIndex = 0; layerIndex < Count; layerIndex++) - { - if (!layerHollowAreas.TryGetValue(layerIndex, out var list)) continue; - if (list.Count == 0) continue; - foreach (var issue in - from area - in list - where area.Type == LayerHollowArea.AreaType.Trap - select new LayerIssue(this[layerIndex], LayerIssue.IssueType.ResinTrap, area.Contour, area.BoundingRectangle)) - { - result.AddOrUpdate(layerIndex, new List {issue}, (u, listIssues) => - { - listIssues.Add(issue); - return listIssues; - }); - } - } - - return result; - } - - public void RepairLayers(uint layerStart, uint layerEnd, uint closingIterations = 1, uint openingIterations = 1, - bool repairIslands = true, bool repairResinTraps = true, Dictionary> issues = null, - OperationProgress progress = null) - { - if(ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(OperationProgress.StatusRepairLayers, layerEnd - layerStart + 1); - Parallel.For(layerStart, layerEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - Layer layer = this[layerIndex]; - using (var image = layer.LayerMat) - { - if (repairResinTraps && !ReferenceEquals(issues, null)) - { - if (issues.TryGetValue((uint)layerIndex, out var issueList)) - { - foreach (var issue in issueList.Where(issue => issue.Type == LayerIssue.IssueType.ResinTrap)) - { - using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels))) - { - CvInvoke.DrawContours(image, - vec, - -1, - new MCvScalar(255), - -1); - } - - /*CvInvoke.DrawContours(image, - new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels)), - -1, - new MCvScalar(255), - 2);*/ - } - } - } - - if (repairIslands) - { - using (Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), - new Point(-1, -1))) - { - if (closingIterations > 0) - { - CvInvoke.MorphologyEx(image, image, MorphOp.Close, kernel, new Point(-1, -1), - (int) closingIterations, BorderType.Default, new MCvScalar()); - } - - if (openingIterations > 0) - { - CvInvoke.MorphologyEx(image, image, MorphOp.Open, kernel, new Point(-1, -1), - (int) closingIterations, BorderType.Default, new MCvScalar()); - } - } - } - - layer.LayerMat = image; - lock (progress.Mutex) - { - progress++; - } - } - }); - - progress.Token.ThrowIfCancellationRequested(); - } - - public void ToolPattern(uint startLayerIndex, uint endLayerIndex, OperationPattern settings, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Pattern", endLayerIndex - startLayerIndex + 1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - - this[layerIndex].ToolPattern(settings); - - lock (progress.Mutex) - { - progress++; - } - }); - - _boundingRectangle = Rectangle.Empty; - - progress.Token.ThrowIfCancellationRequested(); - - if (settings.Anchor == Anchor.None) return; - MutateMove(startLayerIndex, endLayerIndex, new OperationMove(BoundingRectangle, 0, 0, settings.Anchor), progress); - - } - - /// - /// Desmodify all layers - /// - public void Desmodify() - { - for (uint i = 0; i < Count; i++) - { - Layers[i].IsModified = false; - } - } - - /// - /// Clone this object - /// - /// - public LayerManager Clone() - { - LayerManager layerManager = new LayerManager(Count); - foreach (var layer in this) - { - layerManager[layer.Index] = layer.Clone(); - } - - return layerManager; - } - - - #endregion - } - #endregion -} diff --git a/UVtools.Core/MatBytes.cs b/UVtools.Core/MatBytes.cs deleted file mode 100644 index 1dbb46a..0000000 --- a/UVtools.Core/MatBytes.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Drawing; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Emgu.CV; -using Emgu.CV.CvEnum; - -namespace UVtools.Core -{ - public sealed class MatBytes : IDisposable - { - private bool m_IsDisposed; - /// - /// Gets the byte structure of this Mat - /// - public byte[] Bytes; - - /// - /// Gets the - /// - public Mat Mat { get; } - - /// - /// Gets the for the allocated bytes - /// - public GCHandle Handle { get; } - - public byte this[int index] - { - get => Bytes[index]; - set => Bytes[index] = value; - } - - public byte this[int x, int y] - { - get => Bytes[y * Mat.Width + x]; - set => Bytes[y * Mat.Width + x] = value; - } - - public MatBytes(Mat mat) : this(mat.Size, (byte) mat.NumberOfChannels, mat.Depth) - { - var s = mat.DataPointer; - - } - - public MatBytes(Size size, byte channels = 1, DepthType depth = DepthType.Cv8U) - { - Bytes = new byte[size.Width * size.Height * channels]; - Handle = GCHandle.Alloc(Bytes, GCHandleType.Pinned); - Mat = new Mat(size, depth, channels, Handle.AddrOfPinnedObject(), size.Width * channels); - } - - public MatBytes(Size size, out byte[] bytes, byte channels = 1, DepthType depth = DepthType.Cv8U) : this(size, channels, depth) - { - bytes = Bytes; - } - - public void Free() - { - Handle.Free(); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool isDisposing) - { - if (m_IsDisposed) - return; - - if (isDisposing) - { - Mat?.Dispose(); - Free(); - } - m_IsDisposed = true; - } - - } -} diff --git a/UVtools.Core/OperationMove.cs b/UVtools.Core/OperationMove.cs deleted file mode 100644 index 8707be2..0000000 --- a/UVtools.Core/OperationMove.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Text; - -namespace UVtools.Core -{ - public enum Anchor : byte - { - TopLeft, TopCenter, TopRight, - MiddleLeft, MiddleCenter, MiddleRight, - BottomLeft, BottomCenter, BottomRight, - None - } - - public class OperationMove - { - public Rectangle SrcRoi { get; set; } - - private Rectangle _dstRoi = Rectangle.Empty; - public Rectangle DstRoi - { - get - { - if(!_dstRoi.IsEmpty) return _dstRoi; - CalculateDstRoi(); - - return _dstRoi; - } - } - - public void CalculateDstRoi() - { - _dstRoi.Size = SrcRoi.Size; - - switch (Anchor) - { - case Anchor.TopLeft: - _dstRoi.Location = new Point(0, 0); - break; - case Anchor.TopCenter: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), 0); - break; - case Anchor.TopRight: - _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), 0); - break; - case Anchor.MiddleLeft: - _dstRoi.Location = new Point(0, (int)(ImageHeight / 2 - SrcRoi.Height / 2)); - break; - case Anchor.MiddleCenter: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), (int)(ImageHeight / 2 - SrcRoi.Height / 2)); - break; - case Anchor.MiddleRight: - _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), (int)(ImageHeight / 2 - SrcRoi.Height / 2)); - break; - case Anchor.BottomLeft: - _dstRoi.Location = new Point(0, (int)(ImageHeight - SrcRoi.Height)); - break; - case Anchor.BottomCenter: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), (int)(ImageHeight - SrcRoi.Height)); - break; - case Anchor.BottomRight: - _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), (int)(ImageHeight - SrcRoi.Height)); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - _dstRoi.X += MarginLeft; - _dstRoi.X -= MarginRight; - _dstRoi.Y += MarginTop; - _dstRoi.Y -= MarginBottom; - } - - - public uint ImageWidth { get; set; } - public uint ImageHeight { get; set; } - - public Anchor Anchor { get; set; } - - public int MarginLeft { get; set; } = 0; - public int MarginTop { get; set; } = 0; - public int MarginRight { get; set; } = 0; - public int MarginBottom { get; set; } = 0; - - public OperationMove(Rectangle srcRoi, uint imageWidth = 0, uint imageHeight = 0, Anchor anchor = Anchor.MiddleCenter) - { - SrcRoi = srcRoi; - ImageWidth = imageWidth; - ImageHeight = imageHeight; - Anchor = anchor; - } - } -} diff --git a/UVtools.Core/OperationPattern.cs b/UVtools.Core/OperationPattern.cs deleted file mode 100644 index 0f4f668..0000000 --- a/UVtools.Core/OperationPattern.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System.Drawing; - -namespace UVtools.Core -{ - public class OperationPattern - { - public Anchor Anchor { get; set; } - public Rectangle SrcRoi { get; } - - public uint ImageWidth { get; } - public uint ImageHeight { get; } - - public ushort MarginCol { get; set; } = 0; - public ushort MarginRow { get; set; } = 0; - - public ushort MaxMarginCol => CalculateMarginCol(MaxCols); - public ushort MaxMarginRow => CalculateMarginRow(MaxRows); - - public ushort Cols { get; set; } = 1; - public ushort Rows { get; set; } = 1; - - public ushort MaxCols { get; } - public ushort MaxRows { get; } - - - public OperationPattern(Rectangle srcRoi, uint imageWidth, uint imageHeight) - { - SrcRoi = srcRoi; - ImageWidth = imageWidth; - ImageHeight = imageHeight; - - MaxCols = (ushort) (imageWidth / srcRoi.Width); - MaxRows = (ushort) (imageHeight / srcRoi.Height); - } - - - /*public void CalculateDstRoi() - { - _dstRoi.Size = SrcRoi.Size; - - switch (Anchor) - { - case Anchor.TopLeft: - _dstRoi.Location = new Point(0, 0); - break; - case Anchor.TopCenter: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), 0); - break; - case Anchor.TopRight: - _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), 0); - break; - case Anchor.MiddleLeft: - _dstRoi.Location = new Point(0, (int)(ImageHeight / 2 - SrcRoi.Height / 2)); - break; - case Anchor.MiddleCenter: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), (int)(ImageHeight / 2 - SrcRoi.Height / 2)); - break; - case Anchor.MiddleRight: - _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), (int)(ImageHeight / 2 - SrcRoi.Height / 2)); - break; - case Anchor.BottomLeft: - _dstRoi.Location = new Point(0, (int)(ImageHeight - SrcRoi.Height)); - break; - case Anchor.BottomCenter: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), (int)(ImageHeight - SrcRoi.Height)); - break; - case Anchor.BottomRight: - _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), (int)(ImageHeight - SrcRoi.Height)); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - _dstRoi.X += MarginLeft; - _dstRoi.X -= MarginRight; - _dstRoi.Y += MarginTop; - _dstRoi.Y -= MarginBottom; - }*/ - - - - - /// - /// Fills the plate with maximum cols and rows - /// - public void Fill() - { - Cols = MaxCols; - MarginCol = MaxMarginCol; - - Rows = MaxRows; - MarginRow = MaxMarginRow; - } - - public ushort CalculateMarginCol(ushort cols) - { - return (ushort)((ImageWidth - SrcRoi.Width * cols) / cols); - } - - public ushort CalculateMarginRow(ushort rows) - { - return (ushort)((ImageHeight - SrcRoi.Height * rows) / rows); - } - - public Size CalculatePatternVolume => new Size(Cols * SrcRoi.Width + Cols * MarginCol, Rows * SrcRoi.Height + Rows * MarginRow); - - public Rectangle GetRoi(ushort col, ushort row) - { - var patternVolume = CalculatePatternVolume; - - return new Rectangle(new Point( - (int) (col * SrcRoi.Width + col * MarginCol + (ImageWidth - patternVolume.Width) / 2), - (int) (row * SrcRoi.Height + row * MarginRow + (ImageHeight - patternVolume.Height) / 2)), SrcRoi.Size); - } - } -} diff --git a/UVtools.Core/OperationProgress.cs b/UVtools.Core/OperationProgress.cs deleted file mode 100644 index 8b6559d..0000000 --- a/UVtools.Core/OperationProgress.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Threading; - -namespace UVtools.Core -{ - public sealed class OperationProgress - { - public const string StatusDecodeThumbnails = "Decoding Thumbnails"; - public const string StatusGatherLayers = "Gathering Layers"; - public const string StatusDecodeLayers = "Decoding Layers"; - public const string StatusEncodeLayers = "Encoding Layers"; - public const string StatusWritingFile = "Writing File"; - public const string StatusEncodeGcode = "Encoding GCode"; - - public const string StatusOptimizingBounds = "Gathering Bounds"; - public const string StatusCalculatingBounds = "Calculating Bounds"; - - public const string StatusExtracting = "Extracting"; - - public const string StatusIslands = "Islands"; - public const string StatusResinTraps = "Resin traps"; - public const string StatusRepairLayers = "Repair Layers"; - - public object Mutex = new object(); - - public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); - public CancellationToken Token => TokenSource.Token; - - private bool _canCancel = true; - - public OperationProgress() - { - } - - public OperationProgress(bool canCancel) - { - _canCancel = canCancel; - } - - /// - /// Gets or sets if operation can be cancelled - /// - public bool CanCancel - { - get - { - if (!_canCancel) return _canCancel; - return !Token.IsCancellationRequested && Token.CanBeCanceled && _canCancel; - } - set => _canCancel = value; - } - - /// - /// Gets or sets the item name for the operation - /// - public string ItemName { get; set; } = StatusDecodeLayers; - - /// - /// Gets or sets the number of processed items - /// - public uint ProcessedItems { get; set; } - - /// - /// Gets or sets the total of item count on this operation - /// - public uint ItemCount { get; set; } - - /// - /// Gets the remaining items to be processed - /// - public uint RemainingItems => ItemCount - ProcessedItems; - - public int ProgressStep => (int) Math.Min(ProcessedItems * 100 / ItemCount, 100); - - /// - /// Gets the progress from 0 to 100% - /// - public double ProgressPercent => Math.Round(ProcessedItems * 100.0 / ItemCount, 2); - - public static OperationProgress operator +(OperationProgress progress, uint value) - { - progress.ProcessedItems += value; - return progress; - } - - public static OperationProgress operator ++(OperationProgress progress) - { - progress.ProcessedItems++; - return progress; - } - - public static OperationProgress operator --(OperationProgress progress) - { - progress.ProcessedItems--; - return progress; - } - - public void Reset(string name = "", uint itemCount = 0, uint items = 0) - { - ItemName = name; - ItemCount = itemCount; - ProcessedItems = items; - } - - - public override string ToString() - { - return ItemCount == 0 ? - $"{ProcessedItems} / ? {ItemName}" : - $"{RemainingItems} / {ItemCount} / {ProcessedItems} | {ItemName} | {ProgressPercent:0.00}%"; - } - } -} diff --git a/UVtools.Core/Operations/OperationMove.cs b/UVtools.Core/Operations/OperationMove.cs new file mode 100644 index 0000000..bed0879 --- /dev/null +++ b/UVtools.Core/Operations/OperationMove.cs @@ -0,0 +1,92 @@ +using System; +using System.Drawing; + +namespace UVtools.Core.Operations +{ + public enum Anchor : byte + { + TopLeft, TopCenter, TopRight, + MiddleLeft, MiddleCenter, MiddleRight, + BottomLeft, BottomCenter, BottomRight, + None + } + + public class OperationMove + { + public Rectangle SrcRoi { get; set; } + + private Rectangle _dstRoi = Rectangle.Empty; + public Rectangle DstRoi + { + get + { + if(!_dstRoi.IsEmpty) return _dstRoi; + CalculateDstRoi(); + + return _dstRoi; + } + } + + public void CalculateDstRoi() + { + _dstRoi.Size = SrcRoi.Size; + + switch (Anchor) + { + case Anchor.TopLeft: + _dstRoi.Location = new Point(0, 0); + break; + case Anchor.TopCenter: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), 0); + break; + case Anchor.TopRight: + _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), 0); + break; + case Anchor.MiddleLeft: + _dstRoi.Location = new Point(0, (int)(ImageHeight / 2 - SrcRoi.Height / 2)); + break; + case Anchor.MiddleCenter: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), (int)(ImageHeight / 2 - SrcRoi.Height / 2)); + break; + case Anchor.MiddleRight: + _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), (int)(ImageHeight / 2 - SrcRoi.Height / 2)); + break; + case Anchor.BottomLeft: + _dstRoi.Location = new Point(0, (int)(ImageHeight - SrcRoi.Height)); + break; + case Anchor.BottomCenter: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), (int)(ImageHeight - SrcRoi.Height)); + break; + case Anchor.BottomRight: + _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), (int)(ImageHeight - SrcRoi.Height)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + _dstRoi.X += MarginLeft; + _dstRoi.X -= MarginRight; + _dstRoi.Y += MarginTop; + _dstRoi.Y -= MarginBottom; + } + + + public uint ImageWidth { get; set; } + public uint ImageHeight { get; set; } + + public Anchor Anchor { get; set; } + + public int MarginLeft { get; set; } = 0; + public int MarginTop { get; set; } = 0; + public int MarginRight { get; set; } = 0; + public int MarginBottom { get; set; } = 0; + + public OperationMove(Rectangle srcRoi, uint imageWidth = 0, uint imageHeight = 0, Anchor anchor = Anchor.MiddleCenter) + { + SrcRoi = srcRoi; + ImageWidth = imageWidth; + ImageHeight = imageHeight; + Anchor = anchor; + } + } +} diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs new file mode 100644 index 0000000..9332a7e --- /dev/null +++ b/UVtools.Core/Operations/OperationPattern.cs @@ -0,0 +1,116 @@ +using System.Drawing; + +namespace UVtools.Core.Operations +{ + public class OperationPattern + { + public Anchor Anchor { get; set; } + public Rectangle SrcRoi { get; } + + public uint ImageWidth { get; } + public uint ImageHeight { get; } + + public ushort MarginCol { get; set; } = 0; + public ushort MarginRow { get; set; } = 0; + + public ushort MaxMarginCol => CalculateMarginCol(MaxCols); + public ushort MaxMarginRow => CalculateMarginRow(MaxRows); + + public ushort Cols { get; set; } = 1; + public ushort Rows { get; set; } = 1; + + public ushort MaxCols { get; } + public ushort MaxRows { get; } + + + public OperationPattern(Rectangle srcRoi, uint imageWidth, uint imageHeight) + { + SrcRoi = srcRoi; + ImageWidth = imageWidth; + ImageHeight = imageHeight; + + MaxCols = (ushort) (imageWidth / srcRoi.Width); + MaxRows = (ushort) (imageHeight / srcRoi.Height); + } + + + /*public void CalculateDstRoi() + { + _dstRoi.Size = SrcRoi.Size; + + switch (Anchor) + { + case Anchor.TopLeft: + _dstRoi.Location = new Point(0, 0); + break; + case Anchor.TopCenter: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), 0); + break; + case Anchor.TopRight: + _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), 0); + break; + case Anchor.MiddleLeft: + _dstRoi.Location = new Point(0, (int)(ImageHeight / 2 - SrcRoi.Height / 2)); + break; + case Anchor.MiddleCenter: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), (int)(ImageHeight / 2 - SrcRoi.Height / 2)); + break; + case Anchor.MiddleRight: + _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), (int)(ImageHeight / 2 - SrcRoi.Height / 2)); + break; + case Anchor.BottomLeft: + _dstRoi.Location = new Point(0, (int)(ImageHeight - SrcRoi.Height)); + break; + case Anchor.BottomCenter: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - SrcRoi.Width / 2), (int)(ImageHeight - SrcRoi.Height)); + break; + case Anchor.BottomRight: + _dstRoi.Location = new Point((int)(ImageWidth - SrcRoi.Width), (int)(ImageHeight - SrcRoi.Height)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + _dstRoi.X += MarginLeft; + _dstRoi.X -= MarginRight; + _dstRoi.Y += MarginTop; + _dstRoi.Y -= MarginBottom; + }*/ + + + + + /// + /// Fills the plate with maximum cols and rows + /// + public void Fill() + { + Cols = MaxCols; + MarginCol = MaxMarginCol; + + Rows = MaxRows; + MarginRow = MaxMarginRow; + } + + public ushort CalculateMarginCol(ushort cols) + { + return (ushort)((ImageWidth - SrcRoi.Width * cols) / cols); + } + + public ushort CalculateMarginRow(ushort rows) + { + return (ushort)((ImageHeight - SrcRoi.Height * rows) / rows); + } + + public Size CalculatePatternVolume => new Size(Cols * SrcRoi.Width + Cols * MarginCol, Rows * SrcRoi.Height + Rows * MarginRow); + + public Rectangle GetRoi(ushort col, ushort row) + { + var patternVolume = CalculatePatternVolume; + + return new Rectangle(new Point( + (int) (col * SrcRoi.Width + col * MarginCol + (ImageWidth - patternVolume.Width) / 2), + (int) (row * SrcRoi.Height + row * MarginRow + (ImageHeight - patternVolume.Height) / 2)), SrcRoi.Size); + } + } +} diff --git a/UVtools.Core/Operations/OperationProgress.cs b/UVtools.Core/Operations/OperationProgress.cs new file mode 100644 index 0000000..7c6ed73 --- /dev/null +++ b/UVtools.Core/Operations/OperationProgress.cs @@ -0,0 +1,113 @@ +using System; +using System.Threading; + +namespace UVtools.Core.Operations +{ + public sealed class OperationProgress + { + public const string StatusDecodeThumbnails = "Decoding Thumbnails"; + public const string StatusGatherLayers = "Gathering Layers"; + public const string StatusDecodeLayers = "Decoding Layers"; + public const string StatusEncodeLayers = "Encoding Layers"; + public const string StatusWritingFile = "Writing File"; + public const string StatusEncodeGcode = "Encoding GCode"; + + public const string StatusOptimizingBounds = "Gathering Bounds"; + public const string StatusCalculatingBounds = "Calculating Bounds"; + + public const string StatusExtracting = "Extracting"; + + public const string StatusIslands = "Islands"; + public const string StatusResinTraps = "Resin traps"; + public const string StatusRepairLayers = "Repair Layers"; + + public object Mutex = new object(); + + public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); + public CancellationToken Token => TokenSource.Token; + + private bool _canCancel = true; + + public OperationProgress() + { + } + + public OperationProgress(bool canCancel) + { + _canCancel = canCancel; + } + + /// + /// Gets or sets if operation can be cancelled + /// + public bool CanCancel + { + get + { + if (!_canCancel) return _canCancel; + return !Token.IsCancellationRequested && Token.CanBeCanceled && _canCancel; + } + set => _canCancel = value; + } + + /// + /// Gets or sets the item name for the operation + /// + public string ItemName { get; set; } = StatusDecodeLayers; + + /// + /// Gets or sets the number of processed items + /// + public uint ProcessedItems { get; set; } + + /// + /// Gets or sets the total of item count on this operation + /// + public uint ItemCount { get; set; } + + /// + /// Gets the remaining items to be processed + /// + public uint RemainingItems => ItemCount - ProcessedItems; + + public int ProgressStep => (int) Math.Min(ProcessedItems * 100 / ItemCount, 100); + + /// + /// Gets the progress from 0 to 100% + /// + public double ProgressPercent => Math.Round(ProcessedItems * 100.0 / ItemCount, 2); + + public static OperationProgress operator +(OperationProgress progress, uint value) + { + progress.ProcessedItems += value; + return progress; + } + + public static OperationProgress operator ++(OperationProgress progress) + { + progress.ProcessedItems++; + return progress; + } + + public static OperationProgress operator --(OperationProgress progress) + { + progress.ProcessedItems--; + return progress; + } + + public void Reset(string name = "", uint itemCount = 0, uint items = 0) + { + ItemName = name; + ItemCount = itemCount; + ProcessedItems = items; + } + + + public override string ToString() + { + return ItemCount == 0 ? + $"{ProcessedItems} / ? {ItemName}" : + $"{RemainingItems} / {ItemCount} / {ProcessedItems} | {ItemName} | {ProgressPercent:0.00}%"; + } + } +} diff --git a/UVtools.Core/PHZFile.cs b/UVtools.Core/PHZFile.cs deleted file mode 100644 index bf1b791..0000000 --- a/UVtools.Core/PHZFile.cs +++ /dev/null @@ -1,1367 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -// 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 System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using BinarySerialization; -using Emgu.CV; -using Emgu.CV.CvEnum; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - public class PHZFile : FileFormat - { - #region Constants - private const uint MAGIC_PHZ = 0x9FDA83AE; - private const ushort REPEATRGB15MASK = 0x20; - - private const ushort RLE16EncodingLimit = 0x1000; - #endregion - - #region Sub Classes - #region Header - public class Header - { - - /// - /// Gets a magic number identifying the file type. - /// 0x12fd_0019 for cbddlp - /// 0x12fd_0086 for ctb - /// 0x9FDA83AE for phz - /// - [FieldOrder(0)] public uint Magic { get; set; } = MAGIC_PHZ; - - /// - /// Gets the software version - /// - [FieldOrder(1)] public uint Version { get; set; } = 2; - - /// - /// Gets the layer height setting used at slicing, in millimeters. Actual height used by the machine is in the layer table. - /// - [FieldOrder(2)] public float LayerHeightMilimeter { get; set; } - - /// - /// 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. - /// - [FieldOrder(3)] public float LayerExposureSeconds { get; set; } - - /// - /// Gets the exposure time setting used at slicing, in seconds, for bottom layers. Actual time used by the machine is in the layer table. - /// - [FieldOrder(4)] public float BottomExposureSeconds { get; set; } - - /// - /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig.. - /// - [FieldOrder(5)] public uint BottomLayersCount { get; set; } = 10; - - /// - /// Gets the printer resolution along X axis, in pixels. This information is critical to correctly decoding layer images. - /// - [FieldOrder(6)] public uint ResolutionX { get; set; } - - /// - /// Gets the printer resolution along Y axis, in pixels. This information is critical to correctly decoding layer images. - /// - [FieldOrder(7)] public uint ResolutionY { get; set; } - - /// - /// Gets the file offsets of ImageHeader records describing the larger preview images. - /// - [FieldOrder(8)] public uint PreviewLargeOffsetAddress { get; set; } - - /// - /// Gets the file offset of a table of LayerHeader records giving parameters for each printed layer. - /// - [FieldOrder(9)] public uint LayersDefinitionOffsetAddress { get; set; } - - /// - /// 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. - /// - [FieldOrder(10)] public uint LayerCount { get; set; } - - /// - /// Gets the file offsets of ImageHeader records describing the smaller preview images. - /// - [FieldOrder(11)] public uint PreviewSmallOffsetAddress { get; set; } - - /// - /// Gets the estimated duration of print, in seconds. - /// - [FieldOrder(12)] public uint PrintTime { get; set; } - - /// - /// Gets the records whether this file was generated assuming normal (0) or mirrored (1) image projection. LCD printers are "mirrored" for this purpose. - /// - [FieldOrder(13)] public uint ProjectorType { get; set; } - - /// - /// 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. - /// - [FieldOrder(14)] public uint AntiAliasLevel { get; set; } = 1; - - /// - /// 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. - /// - [FieldOrder(15)] public ushort LightPWM { get; set; } = 255; - - /// - /// 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. - /// - [FieldOrder(16)] public ushort BottomLightPWM { get; set; } = 255; - - [FieldOrder(17)] public uint Padding1 { get; set; } - [FieldOrder(18)] public uint Padding2 { get; set; } - - /// - /// Gets the height of the model described by this file, in millimeters. - /// - [FieldOrder(19)] public float OverallHeightMilimeter { get; set; } - - /// - /// Gets dimensions of the printer’s X output volume, in millimeters. - /// - [FieldOrder(20)] public float BedSizeX { get; set; } - - /// - /// Gets dimensions of the printer’s Y output volume, in millimeters. - /// - [FieldOrder(21)] public float BedSizeY { get; set; } - - /// - /// Gets dimensions of the printer’s Z output volume, in millimeters. - /// - [FieldOrder(22)] public float BedSizeZ { get; set; } - - /// - /// Gets the key used to encrypt layer data, or 0 if encryption is not used. - /// - [FieldOrder(23)] public uint EncryptionKey { get; set; } - - /// - /// 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. - /// - [FieldOrder(24)] public float BottomLightOffDelay { get; set; } = 1; - - /// - /// 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. - /// - [FieldOrder(25)] public float LayerOffTime { get; set; } = 1; - - /// - /// Gets number of layers configured as "bottom." Note that this field appears in both the file header and ExtConfig. - /// - [FieldOrder(26)] public uint BottomLayerCount { get; set; } = 10; - - [FieldOrder(27)] public uint Padding3 { get; set; } - - /// - /// Gets the distance to lift the build platform away from the vat after bottom layers, in millimeters. - /// - [FieldOrder(28)] public float BottomLiftHeight { get; set; } = 5; - - /// - /// Gets the speed at which to lift the build platform away from the vat after bottom layers, in millimeters per minute. - /// - [FieldOrder(29)] public float BottomLiftSpeed { get; set; } = 300; - - /// - /// Gets the distance to lift the build platform away from the vat after normal layers, in millimeters. - /// - [FieldOrder(30)] public float LiftHeight { get; set; } = 5; - - /// - /// Gets the speed at which to lift the build platform away from the vat after normal layers, in millimeters per minute. - /// - [FieldOrder(31)] public float LiftSpeed { get; set; } = 300; - - /// - /// Gets the speed to use when the build platform re-approaches the vat after lift, in millimeters per minute. - /// - [FieldOrder(32)] public float RetractSpeed { get; set; } = 300; - - /// - /// Gets the estimated required resin, measured in milliliters. The volume number is derived from the model. - /// - [FieldOrder(33)] public float VolumeMl { get; set; } - - /// - /// Gets the estimated grams, derived from volume using configured factors for density. - /// - [FieldOrder(34)] public float WeightG { get; set; } - - /// - /// Gets the estimated cost based on currency unit the user had configured. Derived from volume using configured factors for density and cost. - /// - [FieldOrder(35)] public float CostDollars { get; set; } - - [FieldOrder(36)] public uint Padding4 { get; set; } - - /// - /// Gets the machine name offset to a string naming the machine type, and its length in bytes. - /// - [FieldOrder(37)] public uint MachineNameAddress { get; set; } - - /// - /// Gets the machine size in bytes - /// - [FieldOrder(38)] public uint MachineNameSize { get; set; } - - /// - /// 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. - /// - [Ignore] public string MachineName { get; set; } - - [FieldOrder(39)] public uint Padding5 { get; set; } - [FieldOrder(40)] public uint Padding6 { get; set; } - [FieldOrder(41)] public uint Padding7 { get; set; } - [FieldOrder(42)] public uint Padding8 { get; set; } - [FieldOrder(43)] public uint Padding9 { get; set; } - [FieldOrder(44)] public uint Padding10 { get; set; } - - /// - /// Gets the parameter used to control encryption. - /// Not totally understood. 0 for cbddlp files, 0xF for ctb files, 0x1c for phz - /// - [FieldOrder(45)] public uint EncryptionMode { get; set; } = 28; - - /// - /// 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. - /// - [FieldOrder(46)] public uint MysteriousId { get; set; } - - /// - /// 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. - /// - [FieldOrder(47)] public uint AntiAliasLevelInfo { get; set; } - - [FieldOrder(48)] public uint SoftwareVersion { get; set; } = 0x01060300; - - [FieldOrder(49)] public uint Padding11 { get; set; } - [FieldOrder(50)] public uint Padding12 { get; set; } - [FieldOrder(51)] public uint Padding13 { get; set; } - [FieldOrder(52)] public uint Padding14 { get; set; } - [FieldOrder(53)] public uint Padding15 { get; set; } - [FieldOrder(54)] public uint Padding16{ get; set; } - - public override string ToString() - { - return $"{nameof(Magic)}: {Magic}, {nameof(Version)}: {Version}, {nameof(LayerHeightMilimeter)}: {LayerHeightMilimeter}, {nameof(LayerExposureSeconds)}: {LayerExposureSeconds}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {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(AntiAliasLevel)}: {AntiAliasLevel}, {nameof(LightPWM)}: {LightPWM}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(Padding1)}: {Padding1}, {nameof(Padding2)}: {Padding2}, {nameof(OverallHeightMilimeter)}: {OverallHeightMilimeter}, {nameof(BedSizeX)}: {BedSizeX}, {nameof(BedSizeY)}: {BedSizeY}, {nameof(BedSizeZ)}: {BedSizeZ}, {nameof(EncryptionKey)}: {EncryptionKey}, {nameof(BottomLightOffDelay)}: {BottomLightOffDelay}, {nameof(LayerOffTime)}: {LayerOffTime}, {nameof(BottomLayerCount)}: {BottomLayerCount}, {nameof(Padding3)}: {Padding3}, {nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(VolumeMl)}: {VolumeMl}, {nameof(WeightG)}: {WeightG}, {nameof(CostDollars)}: {CostDollars}, {nameof(Padding4)}: {Padding4}, {nameof(MachineNameAddress)}: {MachineNameAddress}, {nameof(MachineNameSize)}: {MachineNameSize}, {nameof(MachineName)}: {MachineName}, {nameof(Padding5)}: {Padding5}, {nameof(Padding6)}: {Padding6}, {nameof(Padding7)}: {Padding7}, {nameof(Padding8)}: {Padding8}, {nameof(Padding9)}: {Padding9}, {nameof(Padding10)}: {Padding10}, {nameof(EncryptionMode)}: {EncryptionMode}, {nameof(MysteriousId)}: {MysteriousId}, {nameof(AntiAliasLevelInfo)}: {AntiAliasLevelInfo}, {nameof(SoftwareVersion)}: {SoftwareVersion}, {nameof(Padding11)}: {Padding11}, {nameof(Padding12)}: {Padding12}, {nameof(Padding13)}: {Padding13}, {nameof(Padding14)}: {Padding14}, {nameof(Padding15)}: {Padding15}, {nameof(Padding16)}: {Padding16}"; - } - } - #endregion - - #region Preview - /// - /// The files contain two preview images. - /// These are shown on the printer display when choosing which file to print, sparing the poor printer from needing to render a 3D image from scratch. - /// - public class Preview - { - /// - /// Gets the X dimension of the preview image, in pixels. - /// - [FieldOrder(0)] public uint ResolutionX { get; set; } - - /// - /// Gets the Y dimension of the preview image, in pixels. - /// - [FieldOrder(1)] public uint ResolutionY { get; set; } - - /// - /// Gets the image offset of the encoded data blob. - /// - [FieldOrder(2)] public uint ImageOffset { get; set; } - - /// - /// Gets the image length in bytes. - /// - [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 Mat Decode(byte[] rawImageData) - { - var image = new Mat(new Size((int)ResolutionX, (int)ResolutionY), DepthType.Cv8U, 3); - var span = image.GetPixelSpan(); - - - int pixel = 0; - for (uint n = 0; n < 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++) - { - span[pixel++] = blue; - span[pixel++] = green; - span[pixel++] = red; - //span[pixel] = new Rgba32(red, green, blue, byte.MaxValue); - } - } - - return image; - } - - public static byte[] Encode(Mat image) - { - List rawData = new List(); - var span = image.GetPixelSpan(); - 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 (int pixel = 0; pixel < span.Length; pixel += image.NumberOfChannels) - { - var ncolor15 = - (span[pixel] >> 3) - | ((span[pixel+1] >> 2) << 5) - | ((span[pixel+2] >> 3) << 11); - - if (ncolor15 == color15) - { - rep++; - if (rep == RLE16EncodingLimit) - { - RleRGB15(); - rep = 0; - } - } - else - { - RleRGB15(); - color15 = (ushort) ncolor15; - rep = 1; - } - } - - RleRGB15(); - - return rawData.ToArray(); - } - - 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 LayerData - { - /// - /// Gets the build platform Z position for this layer, measured in millimeters. - /// - [FieldOrder(0)] public float LayerPositionZ { get; set; } - - /// - /// Gets the exposure time for this layer, in seconds. - /// - [FieldOrder(1)] public float LayerExposure { get; set; } - - /// - /// Gets how long to keep the light off after exposing this layer, in seconds. - /// - [FieldOrder(2)] public float LayerOffTimeSeconds { get; set; } - - /// - /// Gets the layer image offset to encoded layer data, and its length in bytes. - /// - [FieldOrder(3)] public uint DataAddress { get; set; } - - /// - /// Gets the layer image length in bytes. - /// - [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; } - - [Ignore] public byte[] EncodedRle { get; set; } - - [Ignore] public PHZFile Parent { get; set; } - - public LayerData() - { - } - - public LayerData(PHZFile parent, uint layerIndex) - { - Parent = parent; - LayerOffTimeSeconds = layerIndex < parent.HeaderSettings.BottomLayersCount ? parent.HeaderSettings.BottomLightOffDelay : parent.HeaderSettings.LayerOffTime; - LayerExposure = layerIndex < Parent.InitialLayerCount ? Parent.HeaderSettings.BottomExposureSeconds : Parent.HeaderSettings.LayerExposureSeconds; - LayerPositionZ = Parent.GetHeightFromLayer(layerIndex); - } - - public Mat Decode(uint layerIndex, bool consumeData = true) - { - var image = new Mat(new Size((int)Parent.ResolutionX, (int)Parent.ResolutionY), DepthType.Cv8U, 1); - var span = image.GetPixelSpan(); - - if (Parent.HeaderSettings.EncryptionKey > 0) - { - KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex); - EncodedRle = kr.Read(EncodedRle); - } - - int limit = image.Width * image.Height; - int index = 0; - byte lastColor = 0; - - foreach (var code in EncodedRle) - { - if ((code & 0x80) == 0x80) - { - //lastColor = (byte) (code << 1); - // // Convert from 7bpp to 8bpp (extending the last bit) - lastColor = (byte)(((code & 0x7f) << 1) | (code & 1)); - if (lastColor >= 0xfc) - { - // Make 'white' actually white - lastColor = 0xff; - - } - - if (index < limit) - { - span[index] = lastColor; - } - else - { - image.Dispose(); - throw new FileLoadException("Corrupted RLE data."); - } - - index++; - } - else - { - for (uint i = 0; i < code; i++) - { - if (index < limit) - { - span[index] = lastColor; - } - else - { - image.Dispose(); - throw new FileLoadException("Corrupted RLE data."); - } - index++; - } - } - } - - if (consumeData) - EncodedRle = null; - - return image; - } - - public void Encode(Mat image, uint layerIndex) - { - List rawData = new List(); - - //byte color = byte.MaxValue >> 1; - byte color = byte.MaxValue; - uint stride = 0; - - void AddRep() - { - rawData.Add((byte)(color | 0x80)); - stride--; - int done = 0; - while (done < stride) - { - int todo = 0x7d; - - if (stride - done < todo) - { - todo = (int)(stride - done); - } - - rawData.Add((byte)(todo)); - - done += todo; - } - } - - int halfWidth = image.Width / 2; - - //int pixel = 0; - for (int y = 0; y < image.Height; y++) - { - var span = image.GetPixelRowSpan(y); - for (int x = 0; x < span.Length; x++) - { - - var grey7 = (byte)((span[x] >> 1) & 0x7f); - if (grey7 > 0x7c) - { - grey7 = 0x7c; - } - - if (color == byte.MaxValue) - { - color = grey7; - stride = 1; - } - else if (grey7 != color || x == halfWidth) - { - AddRep(); - color = grey7; - stride = 1; - } - else - { - stride++; - } - } - - AddRep(); - color = byte.MaxValue; - } - - - if (Parent.HeaderSettings.EncryptionKey > 0) - { - KeyRing kr = new KeyRing(Parent.HeaderSettings.EncryptionKey, layerIndex); - EncodedRle = kr.Read(rawData).ToArray(); - } - else - { - EncodedRle = rawData.ToArray(); - } - - DataSize = (uint) EncodedRle.Length; - } - - 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 uint Init { get; } - public uint Key { get; private set; } - public uint Index { get; private set; } - - public KeyRing(uint seed, uint layerIndex) - { - seed %= 0x4324; - Init = seed * 0x34a32231; - Key = (layerIndex ^ 0x3fad2212) * seed * 0x4910913d; - } - - public byte Next() - { - byte k = (byte)(Key >> (int)(8 * Index)); - Index++; - - if ((Index & 3) == 0) - { - Key += Init; - Index = 0; - } - - return k; - } - - public List Read(List input) - { - List data = new List(input.Count); - data.AddRange(input.Select(t => (byte)(t ^ 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 Header HeaderSettings { get; protected internal set; } = new Header(); - - public Preview[] Previews { get; protected internal set; } - - public LayerData[] LayersDefinitions { get; private set; } - - public Dictionary LayersHash { get; } = new Dictionary(); - - public override FileFormatType FileType => FileFormatType.Binary; - - public override FileExtension[] FileExtensions { get; } = { - new FileExtension("phz", "Chitubox PHZ Files"), - }; - - public override Type[] ConvertToFormats { get; } = - { - typeof(ChituboxFile), - typeof(ChituboxZipFile), - typeof(PWSFile), - typeof(ZCodexFile), - }; - - public override PrintParameterModifier[] PrintParameterModifiers { get; } = - { - PrintParameterModifier.InitialLayerCount, - PrintParameterModifier.InitialExposureSeconds, - PrintParameterModifier.ExposureSeconds, - - PrintParameterModifier.BottomLayerOffTime, - PrintParameterModifier.LayerOffTime, - PrintParameterModifier.BottomLiftHeight, - PrintParameterModifier.BottomLiftSpeed, - PrintParameterModifier.LiftHeight, - PrintParameterModifier.LiftSpeed, - PrintParameterModifier.RetractSpeed, - - PrintParameterModifier.BottomLightPWM, - PrintParameterModifier.LightPWM, - }; - - public override byte ThumbnailsCount { get; } = 2; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(400, 300), new System.Drawing.Size(200, 125)}; - - public override uint ResolutionX => HeaderSettings.ResolutionX; - - public override uint ResolutionY => HeaderSettings.ResolutionY; - public override byte AntiAliasing => (byte) HeaderSettings.AntiAliasLevelInfo; - - public override float LayerHeight => HeaderSettings.LayerHeightMilimeter; - - public override ushort InitialLayerCount => (ushort)HeaderSettings.BottomLayersCount; - - public override float InitialExposureTime => HeaderSettings.BottomExposureSeconds; - - public override float LayerExposureTime => HeaderSettings.LayerExposureSeconds; - public override float LiftHeight => HeaderSettings.LiftHeight; - public override float LiftSpeed => HeaderSettings.LiftSpeed; - public override float RetractSpeed => HeaderSettings.RetractSpeed; - - public override float PrintTime => HeaderSettings.PrintTime; - - public override float UsedMaterial => (float) Math.Round(HeaderSettings.VolumeMl, 2); - - public override float MaterialCost => (float) Math.Round(HeaderSettings.CostDollars, 2); - - public override string MaterialName => "Unknown"; - public override string MachineName => HeaderSettings.MachineName; - - public override object[] Configs => new object[] { HeaderSettings }; - - #endregion - - #region Constructors - public PHZFile() - { - Previews = new Preview[ThumbnailsCount]; - } - #endregion - - #region Methods - public override void Clear() - { - base.Clear(); - - for (byte i = 0; i < ThumbnailsCount; i++) - { - Previews[i] = new Preview(); - } - - LayersDefinitions = null; - } - - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - base.Encode(fileFullPath, progress); - LayersHash.Clear(); - - /*if (HeaderSettings.EncryptionKey == 0) - { - Random rnd = new Random(); - HeaderSettings.EncryptionKey = (uint)rnd.Next(short.MaxValue, int.MaxValue); - }*/ - - - uint currentOffset = (uint)Helpers.Serializer.SizeOf(HeaderSettings); - LayersDefinitions = new LayerData[HeaderSettings.LayerCount]; - using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) - { - outputFile.Seek((int) currentOffset, SeekOrigin.Begin); - - for (byte i = 0; i < ThumbnailsCount; i++) - { - var image = Thumbnails[i]; - - var bytes = Preview.Encode(image); - - if (bytes.Length == 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)bytes.Length, - }; - - currentOffset += (uint) Helpers.Serializer.SizeOf(preview); - preview.ImageOffset = currentOffset; - - Helpers.SerializeWriteFileStream(outputFile, preview); - - currentOffset += (uint)bytes.Length; - outputFile.WriteBytes(bytes); - } - - if (HeaderSettings.MachineNameSize > 0) - { - HeaderSettings.MachineNameAddress = currentOffset; - var machineBytes = Encoding.ASCII.GetBytes(HeaderSettings.MachineName); - outputFile.Write(machineBytes, 0, machineBytes.Length); - currentOffset += (uint)machineBytes.Length; - } - - Parallel.For(0, LayerCount, /*new ParallelOptions{MaxDegreeOfParallelism = 1},*/ layerIndex => - { - if(progress.Token.IsCancellationRequested) return; - LayerData layer = new LayerData(this, (uint)layerIndex); - using (var image = this[layerIndex].LayerMat) - { - layer.Encode(image, (uint) layerIndex); - LayersDefinitions[layerIndex] = layer; - } - - lock (progress.Mutex) - { - progress++; - } - }); - - progress.Reset(OperationProgress.StatusWritingFile, LayerCount); - - HeaderSettings.LayersDefinitionOffsetAddress = currentOffset; - uint layerDataCurrentOffset = currentOffset + (uint) Helpers.Serializer.SizeOf(LayersDefinitions[0]) * LayerCount; - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - progress.Token.ThrowIfCancellationRequested(); - LayerData layerData = LayersDefinitions[layerIndex]; - LayerData layerDataHash = null; - - if (HeaderSettings.EncryptionKey == 0) - { - string hash = Helpers.ComputeSHA1Hash(layerData.EncodedRle); - if (LayersHash.TryGetValue(hash, out layerDataHash)) - { - layerData.DataAddress = layerDataHash.DataAddress; - layerData.DataSize = layerDataHash.DataSize; - } - else - { - LayersHash.Add(hash, layerData); - } - } - - if (ReferenceEquals(layerDataHash, null)) - { - layerData.DataAddress = layerDataCurrentOffset; - - outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin); - layerDataCurrentOffset += outputFile.WriteBytes(layerData.EncodedRle); - } - - - LayersDefinitions[layerIndex] = layerData; - - outputFile.Seek(currentOffset, SeekOrigin.Begin); - currentOffset += Helpers.SerializeWriteFileStream(outputFile, layerData); - progress++; - } - - outputFile.Seek(0, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - - Debug.WriteLine("Encode Results:"); - Debug.WriteLine(HeaderSettings); - Debug.WriteLine(Previews[0]); - Debug.WriteLine(Previews[1]); - Debug.WriteLine("-End-"); - } - } - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) - { - - //HeaderSettings = Helpers.ByteToType(InputFile); - //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); - HeaderSettings = Helpers.Deserialize
(inputFile); - if (HeaderSettings.Magic != MAGIC_PHZ) - { - throw new FileLoadException("Not a valid PHZ file!", fileFullPath); - } - - HeaderSettings.AntiAliasLevel = 1; - - FileFullPath = fileFullPath; - - - progress.Reset(OperationProgress.StatusDecodeThumbnails, ThumbnailsCount); - 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(inputFile); - - Debug.Write($"Preview {i} -> "); - Debug.WriteLine(Previews[i]); - - inputFile.Seek(Previews[i].ImageOffset, SeekOrigin.Begin); - byte[] rawImageData = new byte[Previews[i].ImageLength]; - inputFile.Read(rawImageData, 0, (int) Previews[i].ImageLength); - - Thumbnails[i] = Previews[i].Decode(rawImageData); - progress++; - } - - if (HeaderSettings.MachineNameAddress > 0 && HeaderSettings.MachineNameSize > 0) - { - inputFile.Seek(HeaderSettings.MachineNameAddress, SeekOrigin.Begin); - byte[] buffer = new byte[HeaderSettings.MachineNameSize]; - inputFile.Read(buffer, 0, (int) HeaderSettings.MachineNameSize); - HeaderSettings.MachineName = Encoding.ASCII.GetString(buffer); - } - - - LayersDefinitions = new LayerData[HeaderSettings.LayerCount]; - - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - - progress.Reset(OperationProgress.StatusGatherLayers, HeaderSettings.LayerCount); - - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - inputFile.Seek(layerOffset, SeekOrigin.Begin); - LayerData layerData = Helpers.Deserialize(inputFile); - layerData.Parent = this; - LayersDefinitions[layerIndex] = layerData; - - layerOffset += (uint) Helpers.Serializer.SizeOf(layerData); - Debug.Write($"LAYER {layerIndex} -> "); - Debug.WriteLine(layerData); - - layerData.EncodedRle = new byte[layerData.DataSize]; - inputFile.Seek(layerData.DataAddress, SeekOrigin.Begin); - inputFile.Read(layerData.EncodedRle, 0, (int) layerData.DataSize); - - progress++; - progress.Token.ThrowIfCancellationRequested(); - } - - LayerManager = new LayerManager(HeaderSettings.LayerCount); - - progress.Reset(OperationProgress.StatusDecodeLayers, HeaderSettings.LayerCount); - - Parallel.For(0, LayerCount, layerIndex => - { - if (progress.Token.IsCancellationRequested) - { - return; - } - - using (var image = LayersDefinitions[layerIndex].Decode((uint) layerIndex, true)) - { - this[layerIndex] = new Layer((uint) layerIndex, image); - } - - lock (progress.Mutex) - { - progress++; - } - }); - } - - progress.Token.ThrowIfCancellationRequested(); - } - - public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) - { - var baseValue = base.GetValueFromPrintParameterModifier(modifier); - if (!ReferenceEquals(baseValue, null)) return baseValue; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) return HeaderSettings.BottomLightOffDelay; - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return HeaderSettings.LayerOffTime; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) return HeaderSettings.BottomLiftHeight; - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) return HeaderSettings.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 (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - // Bottom : others - LayersDefinitions[layerIndex].LayerExposure = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomExposureSeconds : HeaderSettings.LayerExposureSeconds; - LayersDefinitions[layerIndex].LayerOffTimeSeconds = layerIndex < HeaderSettings.BottomLayersCount ? HeaderSettings.BottomLightOffDelay : HeaderSettings.LayerOffTime; - } - } - - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - { - HeaderSettings.BottomLayersCount = - HeaderSettings.BottomLayerCount = value.Convert(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - { - HeaderSettings.BottomExposureSeconds = value.Convert(); - UpdateLayers(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - { - HeaderSettings.LayerExposureSeconds = value.Convert(); - UpdateLayers(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLayerOffTime)) - { - HeaderSettings.BottomLightOffDelay = value.Convert(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) - { - HeaderSettings.LayerOffTime = - HeaderSettings.LayerOffTime = value.Convert(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftHeight)) - { - HeaderSettings.BottomLiftHeight = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLiftSpeed)) - { - HeaderSettings.BottomLiftSpeed = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) - { - HeaderSettings.LiftHeight = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) - { - HeaderSettings.LiftSpeed = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) - { - HeaderSettings.RetractSpeed = value.Convert(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.BottomLightPWM)) - { - HeaderSettings.BottomLightPWM = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LightPWM)) - { - HeaderSettings.LightPWM = value.Convert(); - return true; - } - - return false; - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - if (LayerManager.IsModified) - { - if (!string.IsNullOrEmpty(filePath)) - { - FileFullPath = filePath; - } - Encode(FileFullPath, progress); - return; - } - - if (!string.IsNullOrEmpty(filePath)) - { - File.Copy(FileFullPath, filePath, true); - FileFullPath = filePath; - } - - using (var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) - { - outputFile.Seek(0, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - - /*if (HeaderSettings.MachineNameAddress > 0 && HeaderSettings.MachineNameSize > 0) - { - outputFile.Seek(HeaderSettings.MachineNameAddress, SeekOrigin.Begin); - byte[] buffer = new byte[HeaderSettings.MachineNameSize]; - outputFile.Write(Encoding.ASCII.GetBytes(HeaderSettings.MachineName), 0, (int)HeaderSettings.MachineNameSize); - }*/ - - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - outputFile.Seek(layerOffset, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, LayersDefinitions[layerIndex]); - layerOffset += (uint)Helpers.Serializer.SizeOf(LayersDefinitions[layerIndex]); - } - } - - //Decode(FileFullPath, progress); - } - - public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) - { - if (to == typeof(ChituboxFile)) - { - ChituboxFile file = new ChituboxFile - { - LayerManager = LayerManager, - HeaderSettings - = - { - Version = 2, - BedSizeX = HeaderSettings.BedSizeX, - BedSizeY = HeaderSettings.BedSizeY, - BedSizeZ = HeaderSettings.BedSizeZ, - OverallHeightMilimeter = TotalHeight, - BottomExposureSeconds = InitialExposureTime, - BottomLayersCount = InitialLayerCount, - BottomLightPWM = HeaderSettings.BottomLightPWM, - LayerCount = LayerCount, - LayerExposureSeconds = LayerExposureTime, - LayerHeightMilimeter = LayerHeight, - LayerOffTime = HeaderSettings.LayerOffTime, - LightPWM = HeaderSettings.LightPWM, - PrintTime = HeaderSettings.PrintTime, - ProjectorType = HeaderSettings.ProjectorType, - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - AntiAliasLevel = ValidateAntiAliasingLevel() - }, - PrintParametersSettings = - { - BottomLayerCount = InitialLayerCount, - BottomLiftHeight = HeaderSettings.BottomLiftHeight, - BottomLiftSpeed = HeaderSettings.BottomLiftSpeed, - BottomLightOffDelay = HeaderSettings.BottomLightOffDelay, - CostDollars = MaterialCost, - LiftHeight = HeaderSettings.LiftHeight, - LiftSpeed = HeaderSettings.LiftSpeed, - LightOffDelay = HeaderSettings.LayerOffTime, - RetractSpeed = HeaderSettings.RetractSpeed, - VolumeMl = UsedMaterial, - WeightG = HeaderSettings.WeightG - }, - SlicerInfoSettings = {MachineName = MachineName, MachineNameSize = (uint) MachineName.Length} - }; - - - - - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(ChituboxZipFile)) - { - ChituboxZipFile file = new ChituboxZipFile - { - LayerManager = LayerManager, - HeaderSettings = - { - Filename = Path.GetFileName(FileFullPath), - - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - MachineX = HeaderSettings.BedSizeX, - MachineY = HeaderSettings.BedSizeY, - MachineZ = HeaderSettings.BedSizeZ, - MachineType = MachineName, - ProjectType = HeaderSettings.ProjectorType == 0 ? "Normal" : "LCD_mirror", - - Resin = MaterialName, - Price = MaterialCost, - Weight = HeaderSettings.WeightG, - Volume = UsedMaterial, - Mirror = (byte) (HeaderSettings.ProjectorType == 0 ? 0 : 1), - - - BottomLayerLiftHeight = HeaderSettings.BottomLiftHeight, - LayerLiftHeight = HeaderSettings.LiftHeight, - BottomLayerLiftSpeed = HeaderSettings.BottomLiftSpeed, - LayerLiftSpeed = HeaderSettings.LiftSpeed, - RetractSpeed = HeaderSettings.RetractSpeed, - BottomLayCount = InitialLayerCount, - BottomLayerCount = InitialLayerCount, - BottomLightOffTime = HeaderSettings.BottomLightOffDelay, - LayerLightOffTime = HeaderSettings.LayerOffTime, - BottomLayExposureTime = InitialExposureTime, - BottomLayerExposureTime = InitialExposureTime, - LayerExposureTime = LayerExposureTime, - LayerHeight = LayerHeight, - LayerCount = LayerCount, - AntiAliasing = ValidateAntiAliasingLevel(), - BottomLightPWM = (byte) HeaderSettings.BottomLightPWM, - LayerLightPWM = (byte) HeaderSettings.LightPWM, - - EstimatedPrintTime = PrintTime - }, - }; - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(PWSFile)) - { - PWSFile file = new PWSFile - { - LayerManager = LayerManager, - HeaderSettings = - { - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - LayerHeight = LayerHeight, - LayerExposureTime = LayerExposureTime, - LiftHeight = LiftHeight, - LiftSpeed = LiftSpeed / 60, - RetractSpeed = RetractSpeed / 60, - LayerOffTime = HeaderSettings.LayerOffTime, - BottomLayersCount = InitialLayerCount, - BottomExposureSeconds = InitialExposureTime, - Price = MaterialCost, - Volume = UsedMaterial, - Weight = HeaderSettings.WeightG, - AntiAliasing = ValidateAntiAliasingLevel() - } - }; - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(ZCodexFile)) - { - TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); - ZCodexFile file = new ZCodexFile - { - ResinMetadataSettings = new ZCodexFile.ResinMetadata - { - MaterialId = 2, - Material = MaterialName, - AdditionalSupportLayerTime = 0, - BottomLayersNumber = InitialLayerCount, - BottomLayersTime = (uint)(InitialExposureTime * 1000), - LayerTime = (uint)(LayerExposureTime * 1000), - DisableSettingsChanges = false, - LayerThickness = LayerHeight, - PrintTime = (uint)PrintTime, - TotalLayersCount = LayerCount, - TotalMaterialVolumeUsed = UsedMaterial, - TotalMaterialWeightUsed = UsedMaterial, - }, - UserSettings = new ZCodexFile.UserSettingsdata - { - Printer = MachineName, - BottomLayersCount = InitialLayerCount, - PrintTime = $"{ts.Hours}h {ts.Minutes}m", - LayerExposureTime = (uint)(LayerExposureTime * 1000), - BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), - MaterialId = 2, - LayerThickness = $"{LayerHeight} mm", - AntiAliasing = (byte) (AntiAliasing > 1 ? 1 : 0), - CrossSupportEnabled = 1, - ExposureOffTime = (uint)HeaderSettings.LayerOffTime, - HollowEnabled = 0, - HollowThickness = 0, - InfillDensity = 0, - IsAdvanced = 0, - MaterialType = MaterialName, - MaterialVolume = UsedMaterial, - MaxLayer = LayerCount - 1, - ModelLiftEnabled = 0, - ModelLiftHeight = 0, - RaftEnabled = 0, - RaftHeight = 0, - RaftOffset = 0, - SupportAdditionalExposureEnabled = 0, - SupportAdditionalExposureTime = 0, - XCorrection = 0, - YCorrection = 0, - ZLiftDistance = HeaderSettings.LiftHeight, - ZLiftFeedRate = HeaderSettings.LiftSpeed, - ZLiftRetractRate = HeaderSettings.RetractSpeed, - }, - ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata - { - PrintTime = (uint)PrintTime, - PrinterName = MachineName, - Materials = new List - { - new ZCodexFile.ZCodeMetadata.MaterialsData - { - Name = MaterialName, - ExtruderType = "MAIN", - Id = 0, - Usage = 0, - Temperature = 0 - } - }, - }, - LayerManager = LayerManager - }; - - float usedMaterial = UsedMaterial / LayerCount; - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData - { - Layer = layerIndex, - UsedMaterialVolume = usedMaterial - }); - } - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - return true; - } - - return false; - } - #endregion - } -} diff --git a/UVtools.Core/PWSFile.cs b/UVtools.Core/PWSFile.cs deleted file mode 100644 index 45eb55b..0000000 --- a/UVtools.Core/PWSFile.cs +++ /dev/null @@ -1,1269 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Threading.Tasks; -using BinarySerialization; -using Emgu.CV; -using Emgu.CV.CvEnum; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - public class PWSFile : FileFormat - { - #region Constants - public const byte MarkSize = 12; - public const byte RLE1EncodingLimit = 0x7d; // 125; - public const ushort RLE4EncodingLimit = 0xfff; // 4095; - - // CRC-16-ANSI (aka CRC-16-IMB) Polynomial: x^16 + x^15 + x^2 + 1 - public static readonly int[] CRC16Table = { - 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, - 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, - 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, - 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, - 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, - 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, - 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, - 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, - 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, - 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, - 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, - 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, - 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, - 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, - 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, - 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, - 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, - 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, - 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, - 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, - 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, - 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, - 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, - 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, - 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, - 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, - 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, - 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, - 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, - 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, - 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, - 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040, - }; - - #endregion - - #region Enums - public enum LayerRleFormat - { - PWS, - PW0 - } - #endregion - - #region Sub Classes - - #region FileMark - public class FileMark - { - public const string SectionMarkFile = "ANYCUBIC"; - - private string _mark = SectionMarkFile; - /// - /// Gets the file mark placeholder - /// Fixed to "ANYCUBIC" - /// - [FieldOrder(0)] - [FieldLength(MarkSize)] - public string Mark - { - get => _mark; - set => _mark = value.TrimEnd('\0'); - } - - /// - /// Gets the file format version - /// - [FieldOrder(1)] public uint Version { get; set; } = 1; - - /// - /// Gets the area num - /// - [FieldOrder(2)] public uint AreaNum { get; set; } = 4; - - /// - /// Gets the header start address - /// - [FieldOrder(3)] public uint HeaderAddress { get; set; } - - [FieldOrder(4)] public uint Offset1 { get; set; } - - /// - /// Gets the preview start offset - /// - [FieldOrder(5)] public uint PreviewAddress { get; set; } - - [FieldOrder(6)] public uint Offset2 { get; set; } - - /// - /// Gets the layer definition start address - /// - [FieldOrder(7)] public uint LayerDefinitionAddress { get; set; } - - [FieldOrder(8)] public uint Offset3 { get; set; } - - /// - /// Gets layer image start address - /// - [FieldOrder(9)] public uint LayerImageAddress { get; set; } - - public override string ToString() - { - return $"{nameof(Mark)}: {Mark}, {nameof(Version)}: {Version}, {nameof(AreaNum)}: {AreaNum}, {nameof(HeaderAddress)}: {HeaderAddress}, {nameof(Offset1)}: {Offset1}, {nameof(PreviewAddress)}: {PreviewAddress}, {nameof(Offset2)}: {Offset2}, {nameof(LayerDefinitionAddress)}: {LayerDefinitionAddress}, {nameof(Offset3)}: {Offset3}, {nameof(LayerImageAddress)}: {LayerImageAddress}"; - } - } - #endregion - - #region Section - - public class Section - { - private string _mark; - - /// - /// Gets the section mark placeholder - /// - [FieldOrder(0)] - [FieldLength(MarkSize)] - public string Mark - { - get => _mark; - set => _mark = value.TrimEnd('\0'); - } - - /// - /// Gets the length of this section - /// - [FieldOrder(1)] public uint Length { get; set; } - - public Section() { } - - public Section(string mark, object obj) : this(mark, (uint)Helpers.Serializer.SizeOf(obj)) { } - - public Section(string mark, uint length = 0) - { - Mark = mark; - Length = length; - } - - - public void Validate(string mark, object obj = null) - { - Validate(mark, 0u, obj); - } - - public void Validate(string mark, uint length, object obj = null) - { - if (!Mark.Equals(mark)) - { - throw new FileLoadException($"'{Mark}' section expected, but got '{mark}'"); - } - - if (!ReferenceEquals(obj, null)) - { - length += (uint)Helpers.Serializer.SizeOf(obj); - } - - if (length > 0 && Length != length) - { - throw new FileLoadException($"{Mark} section bytes: expected {Length}, got {length}, difference: {(int)Length - length}"); - } - } - - public override string ToString() => $"{{{nameof(Mark)}: {Mark}, {nameof(Length)}: {Length}}}"; - } - - #endregion - - #region Header - public class Header - { - public const string SectionMark = "HEADER"; - - [Ignore] public Section Section { get; set; } - [FieldOrder(0)] public float PixelSize { get; set; } - [FieldOrder(1)] public float LayerHeight { get; set; } - [FieldOrder(2)] public float LayerExposureTime { get; set; } - [FieldOrder(3)] public float LayerOffTime { get; set; } = 1; - [FieldOrder(4)] public float BottomExposureSeconds { get; set; } - [FieldOrder(5)] public float BottomLayersCount { get; set; } - [FieldOrder(6)] public float LiftHeight { get; set; } = 6; - - /// - /// Gets the lift speed in mm/s - /// - [FieldOrder(7)] public float LiftSpeed { get; set; } = 3; // mm/s - - /// - /// Gets the retract speed in mm/s - /// - [FieldOrder(8)] public float RetractSpeed { get; set; } = 3; // mm/s - [FieldOrder(9)] public float Volume { get; set; } - [FieldOrder(10)] public uint AntiAliasing { get; set; } = 1; - [FieldOrder(11)] public uint ResolutionX { get; set; } - [FieldOrder(12)] public uint ResolutionY { get; set; } - [FieldOrder(13)] public float Weight { get; set; } - [FieldOrder(14)] public float Price { get; set; } - [FieldOrder(15)] public uint ResinType { get; set; } // 0x24 ? - [FieldOrder(16)] public uint PerLayerOverride { get; set; } // bool - [FieldOrder(17)] public uint Offset1 { get; set; } - [FieldOrder(18)] public uint Offset2 { get; set; } - [FieldOrder(19)] public uint Offset3 { get; set; } - - public Header() - { - Section = new Section(SectionMark, this); - } - - public override string ToString() => $"{nameof(Section)}: {Section}, {nameof(PixelSize)}: {PixelSize}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(LayerExposureTime)}: {LayerExposureTime}, {nameof(LayerOffTime)}: {LayerOffTime}, {nameof(BottomExposureSeconds)}: {BottomExposureSeconds}, {nameof(BottomLayersCount)}: {BottomLayersCount}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(Volume)}: {Volume}, {nameof(AntiAliasing)}: {AntiAliasing}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(Weight)}: {Weight}, {nameof(Price)}: {Price}, {nameof(ResinType)}: {ResinType}, {nameof(PerLayerOverride)}: {PerLayerOverride}, {nameof(Offset1)}: {Offset1}, {nameof(Offset2)}: {Offset2}, {nameof(Offset3)}: {Offset3}"; - - public void Validate() - { - Section.Validate(SectionMark, this); - } - } - - #endregion - - #region Preview - - /// - /// The files contain two preview images. - /// These are shown on the printer display when choosing which file to print, sparing the poor printer from needing to render a 3D image from scratch. - /// - public class Preview - { - public const string SectionMark = "PREVIEW"; - [Ignore] public Section Section { get; set; } - - /// - /// Gets the image width, in pixels. - /// - [FieldOrder(1)] public uint Width { get; set; } = 224; - - /// - /// Gets the resolution of the image, in dpi. - /// - [FieldOrder(2)] public uint Resolution { get; set; } = 42; - - /// - /// Gets the image height, in pixels. - /// - [FieldOrder(3)] public uint Height { get; set; } = 168; - - // little-endian 16bit colors, RGB 565 encoded. - //[FieldOrder(4)] - //[FieldLength("Section.Length")] - [Ignore] - public byte[] Data { get; set; } - - public Preview() - { - Section = new Section(SectionMark, this); - } - - public Mat Decode(bool consumeData = true) - { - Mat image = new Mat(new Size((int) Width, (int) Height), DepthType.Cv8U, 3); - var span = image.GetPixelSpan(); - - int pixel = 0; - for (uint i = 0; i < Data.Length; i += 2) - { - ushort color16 = (ushort)(Data[i] + (Data[i+1] << 8)); - var r =(color16 >> 11) & 0x1f; - var g = (color16 >> 5) & 0x3f; - var b = (color16 >> 0) & 0x1f; - - /*span[pixel++] = new Rgba32( - (byte)((r << 3) | (r & 0x7)), - (byte)((g << 2) | (g & 0x3)), - (byte)((b << 3) | (b & 0x7)) - );*/ - span[pixel++] = (byte) ((b << 3) | (b & 0x7)); - span[pixel++] = (byte) ((g << 2) | (g & 0x3)); - span[pixel++] = (byte) ((r << 3) | (r & 0x7)); - } - - if (consumeData) - Data = null; - - return image; - } - - public static Preview Encode(Mat image) - { - var span = image.GetPixelSpan(); - - Preview preview = new Preview - { - Width = (uint) image.Width, - Height = (uint) image.Height, - Resolution = 42, - Data = new byte[image.Width * image.Height * 2] - }; - - int i = 0; - for (int pixel = 0; pixel < span.Length; pixel += image.NumberOfChannels) - { - // BGR - int b = span[pixel] >> 3; - int g = span[pixel+1] >> 2; - int r = span[pixel+2] >> 3; - - - ushort color = (ushort) ((r << 11) | (g << 5) | (b << 0)); - - preview.Data[i++] = (byte) color; - preview.Data[i++] = (byte) (color >> 8); - } - - preview.Section.Length += (uint) preview.Data.Length; - return preview; - } - - public override string ToString() - { - return $"{nameof(Section)}: {Section}, {nameof(Width)}: {Width}, {nameof(Resolution)}: {Resolution}, {nameof(Height)}: {Height}, {nameof(Data)}: {Data}"; - } - - public void Validate(uint size) - { - Section.Validate(SectionMark, size, this); - } - } - - #endregion - - #region Layer - - public class LayerData - { - /// - /// Gets the layer image offset to encoded layer data, and its length in bytes. - /// - [FieldOrder(0)] - public uint DataAddress { get; set; } - - /// - /// Gets the layer image length in bytes. - /// - [FieldOrder(1)] - public uint DataLength { get; set; } - - [FieldOrder(2)] public float LiftHeight { get; set; } - - [FieldOrder(3)] public float LiftSpeed { get; set; } - - /// - /// Gets the exposure time for this layer, in seconds. - /// - [FieldOrder(4)] - public float LayerExposure { get; set; } - - /// - /// Gets the build platform Z position for this layer, measured in millimeters. - /// - [FieldOrder(5)] - public float LayerPositionZ { get; set; } - - [FieldOrder(6)] public float Offset1 { get; set; } - [FieldOrder(7)] public float Offset2 { get; set; } - - [Ignore] public byte[] EncodedRle { get; set; } - [Ignore] public PWSFile Parent { get; set; } - - public LayerData() - { - } - - public LayerData(PWSFile parent, uint layerIndex) - { - Parent = parent; - LiftHeight = Parent.HeaderSettings.LiftHeight; - LiftSpeed = Parent.HeaderSettings.LiftSpeed; - LayerExposure = layerIndex < Parent.InitialLayerCount ? Parent.HeaderSettings.BottomExposureSeconds : Parent.HeaderSettings.LayerExposureTime; - LayerPositionZ = Parent.GetHeightFromLayer(layerIndex); - } - - public Mat Decode(bool consumeData = true) - { - var result = Parent.LayerFormat == LayerRleFormat.PWS ? DecodePWS() : DecodePW0(); - if (consumeData) - EncodedRle = null; - - return result; - } - - public byte[] Encode(Mat image) - { - EncodedRle = Parent.LayerFormat == LayerRleFormat.PWS ? EncodePWS(image) : EncodePW0(image); - return EncodedRle; - } - - private Mat DecodePWS() - { - var image = new Mat(new Size((int) Parent.ResolutionX, (int) Parent.ResolutionY), DepthType.Cv8U, 1); - var span = image.GetPixelSpan(); - - int index = 0; - for (byte bit = 0; bit < Parent.AntiAliasing; bit++) - { - byte bitValue = (byte)(byte.MaxValue / ((1 << Parent.AntiAliasing) - 1) * (1 << bit)); - - int pixel = 0; - for (; index < EncodedRle.Length; index++) - { - // Lower 7 bits is the repeat count for the bit (0..127) - int reps = EncodedRle[index] & 0x7f; - - // We only need to set the non-zero pixels - // High bit is on for white, off for black - if ((EncodedRle[index] & 0x80) != 0) - { - for (int i = 0; i < reps; i++) - { - span[pixel + i] |= bitValue; - } - } - - pixel += reps; - - if (pixel == span.Length) - { - index++; - break; - } - - if (pixel > span.Length) - { - image.Dispose(); - throw new FileLoadException("Error image ran off the end"); - } - } - } - - return image; - } - - public byte[] EncodePWS(Mat image) - { - List rawData = new List(); - var span = image.GetPixelSpan(); - - bool obit; - int rep; - - void AddRep() - { - if (rep <= 0) return; - - byte by = (byte)rep; - - if (obit) - { - by |= 0x80; - //bitsOn += uint(rep) - } - - rawData.Add(by); - } - - for (byte aalevel = 0; aalevel < Parent.AntiAliasing; aalevel++) - { - obit = false; - rep = 0; - - - for (int pixel = 0; pixel < span.Length; pixel++) - { - var nbit = (span[pixel] & (1 << (8 - Parent.AntiAliasing + aalevel))) != 0; - - if (nbit == obit) - { - rep++; - - if (rep == RLE1EncodingLimit) - { - AddRep(); - rep = 0; - } - } - else - { - AddRep(); - obit = nbit; - rep = 1; - } - } - - // Collect stragglers - AddRep(); - } - - DataLength = (uint) rawData.Count; - - return rawData.ToArray(); - } - - private Mat DecodePW0() - { - var image = new Mat(new Size((int)Parent.ResolutionX, (int)Parent.ResolutionY), DepthType.Cv8U, 1); - var span = image.GetPixelSpan(); - - uint n = 0; - for (int index = 0; index < EncodedRle.Length; index++) - { - byte b = EncodedRle[index]; - int code = (b >> 4); - uint reps = (uint) (b & 0xf); - byte color; - switch (code) - { - case 0x0: - color = 0x00; - index++; - reps = reps * 256 + EncodedRle[index]; - break; - case 0xf: - color = 0xff; - index++; - reps = reps * 256 + EncodedRle[index]; - break; - default: - color = (byte) ((code << 4) | code); - break; - } - - color &= 0xff; - - // We only need to set the non-zero pixels - if (color != 0) - { - for (int i = 0; i < reps; i++) - { - span[(int) (n + i)] |= color; - } - } - - n += reps; - - - if (n == span.Length) - { - //index++; - break; - } - - if (n > span.Length) - { - image.Dispose(); - throw new FileLoadException($"Error image ran off the end: {n - reps}({reps}) of {span.Length}"); - } - } - - if (n != span.Length) - { - image.Dispose(); - throw new FileLoadException($"Error image ended short: {n} of {span.Length}"); - } - - return image; - } - - public byte[] EncodePW0(Mat image) - { - List rawData = new List(); - var span = image.GetPixelSpan(); - - int lastColor = -1; - int reps = 0; - - void PutReps() - { - while (reps > 0) - { - int done = reps; - - if (lastColor == 0 || lastColor == 0xf) - { - if (done > RLE4EncodingLimit) - { - done = RLE4EncodingLimit; - } - //more:= []byte{ 0, 0} - //binary.BigEndian.PutUint16(more, uint16(done | (color << 12))) - - //rle = append(rle, more...) - - ushort more = (ushort)(done | (lastColor << 12)); - rawData.Add((byte)(more >> 8)); - rawData.Add((byte)more); - } - else - { - if (done > 0xf) - { - done = 0xf; - } - rawData.Add((byte)(done | lastColor << 4)); - } - - reps -= done; - } - } - - for (int i = 0; i < span.Length; i++) - { - int color = span[i] >> 4; - - if (color == lastColor) - { - reps++; - } - else - { - PutReps(); - lastColor = color; - reps = 1; - } - } - - PutReps(); - - EncodedRle = rawData.ToArray(); - DataLength = (uint)rawData.Count; - - ushort crc = CRCRle4(EncodedRle); - rawData.Add((byte)(crc >> 8)); - rawData.Add((byte)crc); - - return EncodedRle; - } - - public static ushort CRCRle4(byte[] data) - { - ushort crc16 = 0; - for (int i = 0; i < data.Length; i++) - { - crc16 = (ushort) ((crc16 << 8) ^ CRC16Table[((crc16 >> 8) ^ CRC16Table[data[i]]) & 0xff]); - - } - - crc16 = (ushort) ((CRC16Table[crc16 & 0xff] * 0x100) + CRC16Table[(crc16 >> 8) & 0xff]); - - return crc16; - } - - public ushort CRCEncodedRle() - { - return CRCRle4(EncodedRle); - } - } - - #endregion - - #region LayerDefinition - public class LayerDefinition - { - public const string SectionMark = "LAYERDEF"; - - [Ignore] public Section Section { get; set; } = new Section(SectionMark); - - [FieldOrder(0)] public uint LayersCount { get; set; } - - [Ignore] public LayerData[] Layers; - - public LayerDefinition() - { - Section = new Section(SectionMark, this); - } - - public LayerDefinition(uint layersCount) : this() - { - LayersCount = layersCount; - Layers = new LayerData[layersCount]; - } - - [Ignore] - public LayerData this[uint index] - { - get => Layers[index]; - set => Layers[index] = value; - } - - [Ignore] - public LayerData this[int index] - { - get => Layers[index]; - set => Layers[index] = value; - } - - public void Validate() - { - Section.Validate(SectionMark, (uint)(LayersCount * Helpers.Serializer.SizeOf(new LayerData())), this); - } - - public override string ToString() => $"{nameof(Section)}: {Section}, {nameof(LayersCount)}: {LayersCount}"; - } - #endregion - - #endregion - - #region Properties - - public FileMark FileMarkSettings { get; protected internal set; } = new FileMark(); - - public Header HeaderSettings { get; protected internal set; } = new Header(); - - public Preview PreviewSettings { get; protected internal set; } = new Preview(); - - public LayerDefinition LayersDefinition { get; private set; } = new LayerDefinition(); - - public Dictionary LayersHash { get; } = new Dictionary(); - - public override FileFormatType FileType => FileFormatType.Binary; - - public override FileExtension[] FileExtensions { get; } = { - new FileExtension("pws", "Photon Workshop PWS Files"), - new FileExtension("pw0", "Photon Workshop PW0 Files") - }; - - public override Type[] ConvertToFormats { get; } = - { - //typeof(ChituboxZipFile) - //typeof(PHZFile), - //typeof(ZCodexFile), - }; - - public override PrintParameterModifier[] PrintParameterModifiers { get; } = - { - PrintParameterModifier.InitialLayerCount, - PrintParameterModifier.InitialExposureSeconds, - PrintParameterModifier.ExposureSeconds, - - //PrintParameterModifier.BottomLayerOffTime, - PrintParameterModifier.LayerOffTime, - //PrintParameterModifier.BottomLiftHeight, - //PrintParameterModifier.BottomLiftSpeed, - PrintParameterModifier.LiftHeight, - PrintParameterModifier.LiftSpeed, - PrintParameterModifier.RetractSpeed, - }; - - public override byte ThumbnailsCount { get; } = 1; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(224, 168)}; - - public override uint ResolutionX => HeaderSettings.ResolutionX; - - public override uint ResolutionY => HeaderSettings.ResolutionY; - public override byte AntiAliasing => (byte) HeaderSettings.AntiAliasing; - - public override float LayerHeight => HeaderSettings.LayerHeight; - - public override ushort InitialLayerCount => (ushort)HeaderSettings.BottomLayersCount; - - public override float InitialExposureTime => HeaderSettings.BottomExposureSeconds; - - public override float LayerExposureTime => HeaderSettings.LayerExposureTime; - public override float LiftHeight => HeaderSettings.LiftHeight; - public override float LiftSpeed => HeaderSettings.LiftSpeed * 60; - public override float RetractSpeed => HeaderSettings.RetractSpeed * 60; - - public override float PrintTime => 0; - - public override float UsedMaterial => HeaderSettings.Volume; - - public override float MaterialCost => HeaderSettings.Price; - - public override string MaterialName => null; - public override string MachineName => LayerFormat == LayerRleFormat.PWS ? "AnyCubic Photon S" : "AnyCubic Photon Zero"; - - public override object[] Configs => new object[] { FileMarkSettings, HeaderSettings, PreviewSettings, LayersDefinition }; - - public LayerRleFormat LayerFormat => FileFullPath.EndsWith(".pws") ? LayerRleFormat.PWS : LayerRleFormat.PW0; - - #endregion - - #region Constructors - public PWSFile() - { - } - #endregion - - #region Methods - public override void Clear() - { - base.Clear(); - - LayersDefinition = null; - } - - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - base.Encode(fileFullPath, progress); - LayersHash.Clear(); - - LayersDefinition = new LayerDefinition(LayerCount); - - uint currentOffset = FileMarkSettings.HeaderAddress = (uint) Helpers.Serializer.SizeOf(FileMarkSettings); - using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) - { - outputFile.Seek((int) currentOffset, SeekOrigin.Begin); - currentOffset += Helpers.SerializeWriteFileStream(outputFile, HeaderSettings.Section); - currentOffset += Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - - if (CreatedThumbnailsCount > 0) - { - FileMarkSettings.PreviewAddress = currentOffset; - Preview preview = Preview.Encode(Thumbnails[0]); - currentOffset += Helpers.SerializeWriteFileStream(outputFile, preview.Section); - currentOffset += Helpers.SerializeWriteFileStream(outputFile, preview); - currentOffset += outputFile.WriteBytes(preview.Data); - } - - FileMarkSettings.LayerDefinitionAddress = currentOffset; - - Parallel.For(0, LayerCount, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - LayerData layer = new LayerData(this, (uint) layerIndex); - using (var image = this[layerIndex].LayerMat) - { - layer.Encode(image); - LayersDefinition.Layers[layerIndex] = layer; - } - lock (progress.Mutex) - { - progress++; - } - }); - - LayersDefinition.Section.Length += (uint)Helpers.Serializer.SizeOf(LayersDefinition[0]) * LayerCount; - currentOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinition.Section); - uint offsetLayerRle = FileMarkSettings.LayerImageAddress = currentOffset + LayersDefinition.Section.Length; - - currentOffset += Helpers.SerializeWriteFileStream(outputFile, LayersDefinition); - - progress.Reset(OperationProgress.StatusWritingFile, LayerCount); - - foreach (var layer in LayersDefinition.Layers) - { - progress.Token.ThrowIfCancellationRequested(); - string hash = Helpers.ComputeSHA1Hash(layer.EncodedRle); - - if (LayersHash.TryGetValue(hash, out var layerDataHash)) - { - layer.DataAddress = layerDataHash.DataAddress; - layer.DataLength = (uint)layerDataHash.EncodedRle.Length; - } - else - { - LayersHash.Add(hash, layer); - - layer.DataAddress = offsetLayerRle; - - outputFile.Seek(offsetLayerRle, SeekOrigin.Begin); - offsetLayerRle += Helpers.SerializeWriteFileStream(outputFile, layer.EncodedRle); - } - - outputFile.Seek(currentOffset, SeekOrigin.Begin); - currentOffset += Helpers.SerializeWriteFileStream(outputFile, layer); - - progress++; - } - - // Rewind - outputFile.Seek(0, SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, FileMarkSettings); - } - } - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - using (var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read)) - { - - //HeaderSettings = Helpers.ByteToType(InputFile); - //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); - FileMarkSettings = Helpers.Deserialize(inputFile); - - Debug.Write("FileMark -> "); - Debug.WriteLine(FileMarkSettings); - - if (!FileMarkSettings.Mark.Equals(FileMark.SectionMarkFile)) - { - throw new FileLoadException( - $"Invalid Filemark {FileMarkSettings.Mark}, expected {FileMark.SectionMarkFile}", fileFullPath); - } - - if (FileMarkSettings.Version != 1) - { - throw new FileLoadException($"Invalid Version {FileMarkSettings.Version}, expected 1", - fileFullPath); - } - - FileFullPath = fileFullPath; - - inputFile.Seek(FileMarkSettings.HeaderAddress, SeekOrigin.Begin); - //Section sectionHeader = Helpers.Deserialize
(inputFile); - //Debug.Write("SectionHeader -> "); - //Debug.WriteLine(sectionHeader); - - var section = Helpers.Deserialize
(inputFile); - HeaderSettings = Helpers.Deserialize
(inputFile); - HeaderSettings.Section = section; - - - Debug.Write("Header -> "); - Debug.WriteLine(HeaderSettings); - - HeaderSettings.Validate(); - - if (FileMarkSettings.PreviewAddress > 0) - { - inputFile.Seek(FileMarkSettings.PreviewAddress, SeekOrigin.Begin); - - section = Helpers.Deserialize
(inputFile); - PreviewSettings = Helpers.Deserialize(inputFile); - PreviewSettings.Section = section; - Debug.Write("Preview -> "); - Debug.WriteLine(PreviewSettings); - - uint datasize = PreviewSettings.Width * PreviewSettings.Height * 2; - PreviewSettings.Validate(datasize); - - PreviewSettings.Data = new byte[datasize]; - inputFile.ReadBytes(PreviewSettings.Data); - - Thumbnails[0] = PreviewSettings.Decode(true); - } - - inputFile.Seek(FileMarkSettings.LayerDefinitionAddress, SeekOrigin.Begin); - - section = Helpers.Deserialize
(inputFile); - LayersDefinition = Helpers.Deserialize(inputFile); - LayersDefinition.Section = section; - Debug.Write("LayersDefinition -> "); - Debug.WriteLine(LayersDefinition); - - LayerManager = new LayerManager(LayersDefinition.LayersCount); - LayersDefinition.Layers = new LayerData[LayerCount]; - - - LayersDefinition.Validate(); - - for (int i = 0; i < LayerCount; i++) - { - LayersDefinition[i] = Helpers.Deserialize(inputFile); - LayersDefinition[i].Parent = this; - } - - progress.Reset(OperationProgress.StatusGatherLayers, LayerCount); - - for (int i = 0; i < LayerCount; i++) - { - var layer = LayersDefinition[i]; - inputFile.Seek(layer.DataAddress, SeekOrigin.Begin); - layer.EncodedRle = new byte[layer.DataLength]; - inputFile.ReadBytes(layer.EncodedRle); - - progress++; - progress.Token.ThrowIfCancellationRequested(); - } - - progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); - - Parallel.For(0, LayerCount, layerIndex => - { - if (progress.Token.IsCancellationRequested) - { - return; - } - - using (var image = LayersDefinition[(uint) layerIndex].Decode()) - { - this[layerIndex] = new Layer((uint) layerIndex, image); - } - - lock (progress.Mutex) - { - progress++; - } - }); - } - - progress.Token.ThrowIfCancellationRequested(); - } - - public override object GetValueFromPrintParameterModifier(PrintParameterModifier modifier) - { - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) return HeaderSettings.LayerOffTime; - - var baseValue = base.GetValueFromPrintParameterModifier(modifier); - return baseValue; - } - - public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) - { - void UpdateLayers() - { - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - // Bottom : others - LayersDefinition[layerIndex].LayerExposure = layerIndex < HeaderSettings.BottomLayersCount - ? HeaderSettings.BottomExposureSeconds - : HeaderSettings.LayerExposureTime; - } - } - - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - { - HeaderSettings.BottomLayersCount = - HeaderSettings.BottomLayersCount = value.Convert(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - { - HeaderSettings.BottomExposureSeconds = value.Convert(); - UpdateLayers(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - { - HeaderSettings.LayerExposureTime = value.Convert(); - UpdateLayers(); - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.LayerOffTime)) - { - HeaderSettings.LayerOffTime = value.Convert(); - UpdateLayers(); - return true; - } - - - if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) - { - HeaderSettings.LiftHeight = value.Convert(); - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) - { - HeaderSettings.LiftSpeed = value.Convert() / 60f; - UpdateLayers(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) - { - HeaderSettings.RetractSpeed = value.Convert() / 60f; - UpdateLayers(); - return true; - } - - return false; - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - if (LayerManager.IsModified) - { - if (!string.IsNullOrEmpty(filePath)) - { - FileFullPath = filePath; - } - Encode(FileFullPath, progress); - return; - } - - - if (!string.IsNullOrEmpty(filePath)) - { - File.Copy(FileFullPath, filePath, true); - FileFullPath = filePath; - } - - using (var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write)) - { - outputFile.Seek(FileMarkSettings.HeaderAddress+Helpers.Serializer.SizeOf(HeaderSettings.Section), SeekOrigin.Begin); - Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); - - - outputFile.Seek(FileMarkSettings.LayerDefinitionAddress + Helpers.Serializer.SizeOf(HeaderSettings.Section) + Helpers.Serializer.SizeOf(LayersDefinition), SeekOrigin.Begin); - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - Helpers.SerializeWriteFileStream(outputFile, LayersDefinition[layerIndex]); - } - } - - //Decode(FileFullPath, progress); - } - - public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) - { - /*if (to == typeof(PHZFile)) - { - PHZFile file = new PHZFile - { - LayerManager = LayerManager - }; - - - file.HeaderSettings.Version = 2; - file.HeaderSettings.BedSizeX = HeaderSettings.BedSizeX; - file.HeaderSettings.BedSizeY = HeaderSettings.BedSizeY; - file.HeaderSettings.BedSizeZ = HeaderSettings.BedSizeZ; - file.HeaderSettings.OverallHeightMilimeter = TotalHeight; - file.HeaderSettings.BottomExposureSeconds = InitialExposureTime; - file.HeaderSettings.BottomLayersCount = InitialLayerCount; - file.HeaderSettings.BottomLightPWM = HeaderSettings.BottomLightPWM; - file.HeaderSettings.LayerCount = LayerCount; - file.HeaderSettings.LayerExposureSeconds = LayerExposureTime; - file.HeaderSettings.LayerHeightMilimeter = LayerHeight; - file.HeaderSettings.LayerOffTime = HeaderSettings.LayerOffTime; - file.HeaderSettings.LightPWM = HeaderSettings.LightPWM; - file.HeaderSettings.PrintTime = HeaderSettings.PrintTime; - file.HeaderSettings.ProjectorType = HeaderSettings.ProjectorType; - file.HeaderSettings.ResolutionX = ResolutionX; - file.HeaderSettings.ResolutionY = ResolutionY; - - file.HeaderSettings.BottomLayerCount = InitialLayerCount; - file.HeaderSettings.BottomLiftHeight = PrintParametersSettings.BottomLiftHeight; - file.HeaderSettings.BottomLiftSpeed = PrintParametersSettings.BottomLiftSpeed; - file.HeaderSettings.BottomLightOffDelay = PrintParametersSettings.BottomLightOffDelay; - file.HeaderSettings.CostDollars = MaterialCost; - file.HeaderSettings.LiftHeight = PrintParametersSettings.LiftHeight; - file.HeaderSettings.LiftingSpeed = PrintParametersSettings.LiftingSpeed; - file.HeaderSettings.LayerOffTime = HeaderSettings.LayerOffTime; - file.HeaderSettings.RetractSpeed = PrintParametersSettings.RetractSpeed; - file.HeaderSettings.VolumeMl = UsedMaterial; - file.HeaderSettings.WeightG = PrintParametersSettings.WeightG; - - file.HeaderSettings.MachineName = MachineName; - file.HeaderSettings.MachineNameSize = (uint) MachineName.Length; - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(ZCodexFile)) - { - TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); - ZCodexFile file = new ZCodexFile - { - ResinMetadataSettings = new ZCodexFile.ResinMetadata - { - MaterialId = 2, - Material = MaterialName, - AdditionalSupportLayerTime = 0, - BottomLayersNumber = InitialLayerCount, - BottomLayersTime = (uint)(InitialExposureTime * 1000), - LayerTime = (uint)(LayerExposureTime * 1000), - DisableSettingsChanges = false, - LayerThickness = LayerHeight, - PrintTime = (uint)PrintTime, - TotalLayersCount = LayerCount, - TotalMaterialVolumeUsed = UsedMaterial, - TotalMaterialWeightUsed = UsedMaterial, - }, - UserSettings = new ZCodexFile.UserSettingsdata - { - Printer = MachineName, - BottomLayersCount = InitialLayerCount, - PrintTime = $"{ts.Hours}h {ts.Minutes}m", - LayerExposureTime = (uint)(LayerExposureTime * 1000), - BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), - MaterialId = 2, - LayerThickness = $"{LayerHeight} mm", - AntiAliasing = 0, - CrossSupportEnabled = 1, - ExposureOffTime = (uint) HeaderSettings.LayerOffTime, - HollowEnabled = 0, - HollowThickness = 0, - InfillDensity = 0, - IsAdvanced = 0, - MaterialType = MaterialName, - MaterialVolume = UsedMaterial, - MaxLayer = LayerCount - 1, - ModelLiftEnabled = 0, - ModelLiftHeight = 0, - RaftEnabled = 0, - RaftHeight = 0, - RaftOffset = 0, - SupportAdditionalExposureEnabled = 0, - SupportAdditionalExposureTime = 0, - XCorrection = 0, - YCorrection = 0, - ZLiftDistance = PrintParametersSettings.LiftHeight, - ZLiftFeedRate = PrintParametersSettings.LiftingSpeed, - ZLiftRetractRate = PrintParametersSettings.RetractSpeed, - }, - ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata - { - PrintTime = (uint)PrintTime, - PrinterName = MachineName, - Materials = new List - { - new ZCodexFile.ZCodeMetadata.MaterialsData - { - Name = MaterialName, - ExtruderType = "MAIN", - Id = 0, - Usage = 0, - Temperature = 0 - } - }, - }, - LayerManager = LayerManager - }; - - float usedMaterial = UsedMaterial / LayerCount; - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData - { - Layer = layerIndex, - UsedMaterialVolume = usedMaterial - }); - } - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - return true; - } - */ - return false; - } - #endregion - } -} diff --git a/UVtools.Core/SL1File.cs b/UVtools.Core/SL1File.cs deleted file mode 100644 index e92f193..0000000 --- a/UVtools.Core/SL1File.cs +++ /dev/null @@ -1,971 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using Emgu.CV; -using Emgu.CV.CvEnum; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - public class SL1File : FileFormat - { - #region Constants - - public const string Keyword_AntiAliasing = "AntiAliasing"; - public const string Keyword_BottomLightOffDelay = "BottomLightOffDelay"; - public const string Keyword_LayerOffTime = "LayerOffTime"; - public const string Keyword_LightOffDelay = "LightOffDelay"; - public const string Keyword_BottomLiftHeight = "BottomLiftHeight"; - public const string Keyword_BottomLiftSpeed = "BottomLiftSpeed"; - public const string Keyword_LiftHeight = "LiftHeight"; - public const string Keyword_LiftSpeed = "LiftSpeed"; - public const string Keyword_RetractSpeed = "RetractSpeed"; - public const string Keyword_BottomLightPWM = "BottomLightPWM"; - public const string Keyword_LightPWM = "LightPWM"; - #endregion - - #region Sub Classes - - #region Printer - public class Printer - { - #region Printer - public string InheritsCummulative { get; set; } - public string PrinterSettingsId { get; set; } - public string PrinterTechnology { get; set; } - public string PrinterModel { get; set; } - public string PrinterVariant { get; set; } - public string PrinterVendor { get; set; } - public string DefaultSlaMaterialProfile { get; set; } - public string DefaultSlaPrintProfile { get; set; } - public string PrinterNotes { get; set; } - public string Thumbnails { get; set; } - #endregion - - #region Size and Coordinates - public string BedCustomModel { get; set; } - public string BedCustomTexture { get; set; } - public string BedShape { get; set; } - public float MaxPrintHeight { get; set; } - #endregion - - #region Display - public float DisplayWidth { get; set; } - public float DisplayHeight { get; set; } - public uint DisplayPixelsX { get; set; } - public uint DisplayPixelsY { get; set; } - public string DisplayOrientation { get; set; } - public bool DisplayMirrorX { get; set; } - public bool DisplayMirrorY { get; set; } - #endregion - - #region Tilt - public float FastTiltTime { get; set; } - public float SlowTiltTime { get; set; } - public float AreaFill { get; set; } - #endregion - - #region Corrections - public string RelativeCorrection { get; set; } - public float AbsoluteCorrection { get; set; } - public float ElefantFootCompensation { get; set; } - public float ElefantFootMinWidth { get; set; } - public float GammaCorrection { get; set; } - - #endregion - - #region Exposure - - public float MinExposureTime { get; set; } - public float MaxExposureTime { get; set; } - public float MinInitialExposureTime { get; set; } - public float MaxInitialExposureTime { get; set; } - - #endregion - - #region Overrides - public override string ToString() - { - return $"{nameof(PrinterSettingsId)}: {PrinterSettingsId}, {nameof(PrinterTechnology)}: {PrinterTechnology}, {nameof(PrinterModel)}: {PrinterModel}, {nameof(PrinterVariant)}: {PrinterVariant}, {nameof(PrinterVendor)}: {PrinterVendor}, {nameof(DefaultSlaMaterialProfile)}: {DefaultSlaMaterialProfile}, {nameof(DefaultSlaPrintProfile)}: {DefaultSlaPrintProfile}, {nameof(PrinterNotes)}: {PrinterNotes}, {nameof(Thumbnails)}: {Thumbnails}, {nameof(BedCustomModel)}: {BedCustomModel}, {nameof(BedCustomTexture)}: {BedCustomTexture}, {nameof(BedShape)}: {BedShape}, {nameof(MaxPrintHeight)}: {MaxPrintHeight}, {nameof(DisplayWidth)}: {DisplayWidth}, {nameof(DisplayHeight)}: {DisplayHeight}, {nameof(DisplayPixelsX)}: {DisplayPixelsX}, {nameof(DisplayPixelsY)}: {DisplayPixelsY}, {nameof(DisplayOrientation)}: {DisplayOrientation}, {nameof(DisplayMirrorX)}: {DisplayMirrorX}, {nameof(DisplayMirrorY)}: {DisplayMirrorY}, {nameof(FastTiltTime)}: {FastTiltTime}, {nameof(SlowTiltTime)}: {SlowTiltTime}, {nameof(AreaFill)}: {AreaFill}, {nameof(RelativeCorrection)}: {RelativeCorrection}, {nameof(AbsoluteCorrection)}: {AbsoluteCorrection}, {nameof(ElefantFootCompensation)}: {ElefantFootCompensation}, {nameof(ElefantFootMinWidth)}: {ElefantFootMinWidth}, {nameof(GammaCorrection)}: {GammaCorrection}, {nameof(MinExposureTime)}: {MinExposureTime}, {nameof(MaxExposureTime)}: {MaxExposureTime}, {nameof(MinInitialExposureTime)}: {MinInitialExposureTime}, {nameof(MaxInitialExposureTime)}: {MaxInitialExposureTime}"; - } - #endregion - } - #endregion - - #region Material - public class Material - { - #region Material - public string MaterialVendor { get; set; } - public string MaterialType { get; set; } - public string SlaMaterialSettingsId { get; set; } - public float BottleCost { get; set; } - public ushort BottleVolume { get; set; } - public float BottleWeight { get; set; } - public float MaterialDensity { get; set; } - public string MaterialNotes { get; set; } - - #endregion - - #region Layers - - public float InitialLayerHeight { get; set; } - #endregion - - #region Exposure - - public float ExposureTime { get; set; } - public float InitialExposureTime { get; set; } - #endregion - - #region Corrections - public string MaterialCorrection { get; set; } - - #endregion - - #region Dependencies - - public string CompatiblePrintersConditionCummulative { get; set; } - public string CompatiblePrintsConditionCummulative { get; set; } - - #endregion - - #region Overrides - public override string ToString() - { - return $"{nameof(MaterialVendor)}: {MaterialVendor}, {nameof(MaterialType)}: {MaterialType}, {nameof(SlaMaterialSettingsId)}: {SlaMaterialSettingsId}, {nameof(BottleCost)}: {BottleCost}, {nameof(BottleVolume)}: {BottleVolume}, {nameof(BottleWeight)}: {BottleWeight}, {nameof(MaterialDensity)}: {MaterialDensity}, {nameof(MaterialNotes)}: {MaterialNotes}, {nameof(InitialLayerHeight)}: {InitialLayerHeight}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(InitialExposureTime)}: {InitialExposureTime}, {nameof(MaterialCorrection)}: {MaterialCorrection}, {nameof(CompatiblePrintersConditionCummulative)}: {CompatiblePrintersConditionCummulative}, {nameof(CompatiblePrintsConditionCummulative)}: {CompatiblePrintsConditionCummulative}"; - } - #endregion - } - #endregion - - #region Print - - public class Print - { - #region Print - public string SlaPrintSettingsId { get; set; } - #endregion - - #region Layers - - public float LayerHeight { get; set; } - public byte FadedLayers { get; set; } - #endregion - - #region Supports - public bool SupportsEnable { get; set; } - - - public float SupportHeadFrontDiameter { get; set; } - public float SupportHeadPenetration { get; set; } - public float SupportHeadWidth { get; set; } - - public byte SupportPillarWideningFactor { set; get; } - public float SupportPillarDiameter { get; set; } - public float SupportMaxBridgesOnPillar { get; set; } - public string SupportPillarConnectionMode { get; set; } - public bool SupportBuildplateOnly { get; set; } - public float SupportBaseDiameter { get; set; } - public float SupportBaseHeight { get; set; } - public float SupportBaseSafetyDistance { get; set; } - public bool PadAroundObject { get; set; } - public float SupportObjectElevation { get; set; } - - - public ushort SupportCriticalAngle { get; set; } - public float SupportMaxBridgeLength { get; set; } - public float SupportMaxPillarLinkDistance { get; set; } - - - public byte SupportPointsDensityRelative { get; set; } - public float SupportPointsMinimalDistance { get; set; } - - #endregion - - #region Pad - - public bool PadEnable { set; get; } - public float PadWallThickness { set; get; } - public float PadWallHeight { set; get; } - public float PadBrimSize { set; get; } - public float PadMaxMergeDistance { set; get; } - public float PadWallSlope { set; get; } - //public float PadAroundObject { set; get; } - public bool PadAroundObjectEverywhere { set; get; } - public float PadObjectGap { set; get; } - public float PadObjectConnectorStride { set; get; } - public float PadObjectConnectorWidth { set; get; } - public float PadObjectConnectorPenetration { set; get; } - #endregion - - #region Hollowing - public bool HollowingEnable { set; get; } - public float HollowingMinThickness { set; get; } - public float HollowingQuality { set; get; } - public float HollowingClosingDistance { set; get; } - #endregion - - #region Advanced - public float SliceClosingRadius { set; get; } - #endregion - - #region Output - public string OutputFilenameFormat { set; get; } - #endregion - - #region Dependencies - public string CompatiblePrintsCondition { set; get; } - #endregion - - #region Overrides - public override string ToString() - { - return $"{nameof(SlaPrintSettingsId)}: {SlaPrintSettingsId}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(FadedLayers)}: {FadedLayers}, {nameof(SupportsEnable)}: {SupportsEnable}, {nameof(SupportHeadFrontDiameter)}: {SupportHeadFrontDiameter}, {nameof(SupportHeadPenetration)}: {SupportHeadPenetration}, {nameof(SupportHeadWidth)}: {SupportHeadWidth}, {nameof(SupportPillarWideningFactor)}: {SupportPillarWideningFactor}, {nameof(SupportPillarDiameter)}: {SupportPillarDiameter}, {nameof(SupportMaxBridgesOnPillar)}: {SupportMaxBridgesOnPillar}, {nameof(SupportPillarConnectionMode)}: {SupportPillarConnectionMode}, {nameof(SupportBuildplateOnly)}: {SupportBuildplateOnly}, {nameof(SupportBaseDiameter)}: {SupportBaseDiameter}, {nameof(SupportBaseHeight)}: {SupportBaseHeight}, {nameof(SupportBaseSafetyDistance)}: {SupportBaseSafetyDistance}, {nameof(PadAroundObject)}: {PadAroundObject}, {nameof(SupportObjectElevation)}: {SupportObjectElevation}, {nameof(SupportCriticalAngle)}: {SupportCriticalAngle}, {nameof(SupportMaxBridgeLength)}: {SupportMaxBridgeLength}, {nameof(SupportMaxPillarLinkDistance)}: {SupportMaxPillarLinkDistance}, {nameof(SupportPointsDensityRelative)}: {SupportPointsDensityRelative}, {nameof(SupportPointsMinimalDistance)}: {SupportPointsMinimalDistance}, {nameof(PadEnable)}: {PadEnable}, {nameof(PadWallThickness)}: {PadWallThickness}, {nameof(PadWallHeight)}: {PadWallHeight}, {nameof(PadBrimSize)}: {PadBrimSize}, {nameof(PadMaxMergeDistance)}: {PadMaxMergeDistance}, {nameof(PadWallSlope)}: {PadWallSlope}, {nameof(PadAroundObjectEverywhere)}: {PadAroundObjectEverywhere}, {nameof(PadObjectGap)}: {PadObjectGap}, {nameof(PadObjectConnectorStride)}: {PadObjectConnectorStride}, {nameof(PadObjectConnectorWidth)}: {PadObjectConnectorWidth}, {nameof(PadObjectConnectorPenetration)}: {PadObjectConnectorPenetration}, {nameof(HollowingEnable)}: {HollowingEnable}, {nameof(HollowingMinThickness)}: {HollowingMinThickness}, {nameof(HollowingQuality)}: {HollowingQuality}, {nameof(HollowingClosingDistance)}: {HollowingClosingDistance}, {nameof(SliceClosingRadius)}: {SliceClosingRadius}, {nameof(OutputFilenameFormat)}: {OutputFilenameFormat}, {nameof(CompatiblePrintsCondition)}: {CompatiblePrintsCondition}"; - } - #endregion - } - - #endregion - - #region OutputConfig - - public class OutputConfig - { - public string Action { get; set; } - public string JobDir { get; set; } - public float ExpTime { get; set; } - public float ExpTimeFirst { get; set; } - public string FileCreationTimestamp { get; set; } - public float LayerHeight { get; set; } - public string MaterialName { get; set; } - public byte NumFade { get; set; } - public ushort NumFast { get; set; } - public byte NumSlow { get; set; } - public string PrintProfile { get; set; } - public float PrintTime { get; set; } - public string PrinterModel { get; set; } - public string PrinterProfile { get; set; } - public string PrinterVariant { get; set; } - public string PrusaSlicerVersion { get; set; } - public float UsedMaterial { get; set; } - - public override string ToString() - { - return $"{nameof(Action)}: {Action}, {nameof(JobDir)}: {JobDir}, {nameof(ExpTime)}: {ExpTime}, {nameof(ExpTimeFirst)}: {ExpTimeFirst}, {nameof(FileCreationTimestamp)}: {FileCreationTimestamp}, {nameof(LayerHeight)}: {LayerHeight}, {nameof(MaterialName)}: {MaterialName}, {nameof(NumFade)}: {NumFade}, {nameof(NumFast)}: {NumFast}, {nameof(NumSlow)}: {NumSlow}, {nameof(PrintProfile)}: {PrintProfile}, {nameof(PrintTime)}: {PrintTime}, {nameof(PrinterModel)}: {PrinterModel}, {nameof(PrinterProfile)}: {PrinterProfile}, {nameof(PrinterVariant)}: {PrinterVariant}, {nameof(PrusaSlicerVersion)}: {PrusaSlicerVersion}, {nameof(UsedMaterial)}: {UsedMaterial}"; - } - } - - #endregion - - #endregion - - #region Properties - public Printer PrinterSettings { get; private set; } - - public Material MaterialSettings { get; private set; } - - public Print PrintSettings { get; private set; } - - public OutputConfig OutputConfigSettings { get; private set; } - - public Statistics Statistics { get; } = new Statistics(); - - - public override FileFormatType FileType => FileFormatType.Archive; - - public override FileExtension[] FileExtensions { get; } = { - new FileExtension("sl1", "PrusaSlicer SL1 Files") - }; - - public override Type[] ConvertToFormats { get; } = - { - typeof(ChituboxFile), - typeof(ChituboxZipFile), - typeof(PWSFile), - typeof(PHZFile), - typeof(ZCodexFile), - typeof(CWSFile), - }; - - public override PrintParameterModifier[] PrintParameterModifiers { get; } = { - PrintParameterModifier.InitialLayerCount, - PrintParameterModifier.InitialExposureSeconds, - PrintParameterModifier.ExposureSeconds, - }; - - public override byte ThumbnailsCount { get; } = 2; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = { new System.Drawing.Size(400, 400), new System.Drawing.Size(800, 480) }; - //public override Image[] Thumbnails { get; set; } - - public override uint ResolutionX => PrinterSettings.DisplayPixelsX; - - public override uint ResolutionY => PrinterSettings.DisplayPixelsY; - public override byte AntiAliasing => (byte) (PrinterSettings.GammaCorrection > 0 ? LookupCustomValue(Keyword_AntiAliasing, 4) : 1); - - public override float LayerHeight => OutputConfigSettings.LayerHeight; - - public override ushort InitialLayerCount => OutputConfigSettings.NumFade; - - public override float InitialExposureTime => OutputConfigSettings.ExpTimeFirst; - - public override float LayerExposureTime => OutputConfigSettings.ExpTime; - - public override float LiftHeight { get; } = 0; - - public override float LiftSpeed { get; } = 0; - - public override float RetractSpeed { get; } = 0; - - public override float PrintTime => OutputConfigSettings.PrintTime; - - public override float UsedMaterial => OutputConfigSettings.UsedMaterial; - - public override float MaterialCost => (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.BottleCost / MaterialSettings.BottleVolume, 2); - - public override string MaterialName => OutputConfigSettings.MaterialName; - - public override string MachineName => PrinterSettings.PrinterSettingsId; - - public override object[] Configs => new object[] { PrinterSettings, MaterialSettings, PrintSettings, OutputConfigSettings }; - #endregion - - #region Overrides - public override string ToString() - { - return $"{nameof(FileFullPath)}: {FileFullPath}, {nameof(MaterialSettings)}: {MaterialSettings}, {nameof(PrintSettings)}: {PrintSettings}, {nameof(OutputConfigSettings)}: {OutputConfigSettings}, {nameof(Statistics)}: {Statistics}, {nameof(LayerCount)}: {LayerCount}, {nameof(TotalHeight)}: {TotalHeight}"; - } - - #endregion - - #region Contructors - public SL1File() { } - #endregion - - #region Static Methods - public static string IniKeyToMemberName(string keyName) - { - string memberName = string.Empty; - string[] objs = keyName.Split('_'); - return objs.Aggregate(memberName, (current, obj) => current + obj.FirstCharToUpper()); - } - - public static string MemberNameToIniKey(string memberName) - { - string iniKey = char.ToLowerInvariant(memberName[0]).ToString(); - for (var i = 1; i < memberName.Length; i++) - { - iniKey += char.IsUpper(memberName[i]) - ? $"_{char.ToLowerInvariant(memberName[i])}" - : memberName[i].ToString(); - } - - - if (iniKey.EndsWith("_")) - iniKey.Remove(iniKey.Length - 1); - - return iniKey; - } - - - #endregion - - #region Methods - public override void Clear() - { - base.Clear(); - Statistics.Clear(); - } - - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - throw new NotImplementedException(); - } - - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - FileFullPath = fileFullPath; - - PrinterSettings = new Printer(); - MaterialSettings = new Material(); - PrintSettings = new Print(); - OutputConfigSettings = new OutputConfig(); - - Statistics.ExecutionTime.Restart(); - - using (var inputFile = ZipFile.OpenRead(FileFullPath)) - { - - foreach (ZipArchiveEntry entity in inputFile.Entries) - { - if (!entity.Name.EndsWith(".ini")) continue; - using (StreamReader streamReader = new StreamReader(entity.Open())) - { - string line = null; - while ((line = streamReader.ReadLine()) != null) - { - string[] keyValue = line.Split(new[] {'='}, 2); - if (keyValue.Length < 2) continue; - keyValue[0] = keyValue[0].Trim(); - keyValue[1] = keyValue[1].Trim(); - - var fieldName = IniKeyToMemberName(keyValue[0]); - bool foundMember = false; - - foreach (var obj in Configs) - { - var attribute = obj.GetType().GetProperty(fieldName); - if (ReferenceEquals(attribute, null)) continue; - Helpers.SetPropertyValue(attribute, obj, keyValue[1]); - Statistics.ImplementedKeys.Add(keyValue[0]); - foundMember = true; - } - - if (!foundMember) - { - Statistics.MissingKeys.Add(keyValue[0]); - } - } - } - } - - LayerManager = new LayerManager((uint) (OutputConfigSettings.NumSlow + OutputConfigSettings.NumFast)); - - progress.ItemCount = LayerCount; - - foreach (ZipArchiveEntry entity in inputFile.Entries) - { - if (!entity.Name.EndsWith(".png")) continue; - if (entity.Name.StartsWith("thumbnail")) - { - using (Stream stream = entity.Open()) - { - Mat image = new Mat(); - CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, image); - 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++; - - continue; - } - - // - .png - 5 numbers - string layerStr = entity.Name.Substring(entity.Name.Length - 4 - 5, 5); - uint iLayer = uint.Parse(layerStr); - LayerManager[iLayer] = new Layer(iLayer, entity.Open(), entity.Name); - progress.ProcessedItems++; - } - } - - LayerManager.GetBoundingRectangle(progress); - - Statistics.ExecutionTime.Stop(); - - Debug.WriteLine(Statistics); - } - - /*public override Image GetLayerImage(uint layerIndex) - { - //Stopwatch sw = Stopwatch.StartNew(); - var image = Image.Load(DecompressLayer(Layers[layerIndex])); - //Debug.WriteLine(sw.ElapsedMilliseconds); - - return layerIndex >= LayerCount ? null : image; - //return layerIndex >= LayerCount ? null : Image.Load(LayerEntries[(int)layerIndex].Open()); - //return layerIndex >= LayerCount ? null : Image.Load(DecompressLayer(Layers[layerIndex])); - }*/ - - public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) - { - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - { - PrintSettings.FadedLayers = - OutputConfigSettings.NumFade = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - { - MaterialSettings.InitialExposureTime = - OutputConfigSettings.ExpTimeFirst = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - { - MaterialSettings.ExposureTime = - OutputConfigSettings.ExpTime = value.Convert(); - return true; - } - - return false; - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - if (!string.IsNullOrEmpty(filePath)) - { - File.Copy(FileFullPath, filePath, true); - FileFullPath = filePath; - - } - - using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) - { - - //InputFile.CreateEntry("Modified"); - using (TextWriter tw = new StreamWriter(outputFile.PutFileContent("config.ini", string.Empty, ZipArchiveMode.Update).Open())) - { - var properties = OutputConfigSettings.GetType() - .GetProperties(BindingFlags.Public | BindingFlags.Instance); - - foreach (var property in properties) - { - var name = char.ToLowerInvariant(property.Name[0]) + property.Name.Substring(1); - tw.WriteLine($"{name} = {property.GetValue(OutputConfigSettings)}"); - } - - tw.Close(); - } - - using (TextWriter tw = new StreamWriter(outputFile.PutFileContent("prusaslicer.ini", string.Empty, ZipArchiveMode.Update).Open())) - { - foreach (var config in Configs) - { - if (ReferenceEquals(config, OutputConfigSettings)) - continue; - - var properties = config.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); - - foreach (var property in properties) - { - tw.WriteLine($"{MemberNameToIniKey(property.Name)} = {property.GetValue(config)}"); - } - } - - tw.Close(); - } - - foreach (var layer in this) - { - if (!layer.IsModified) continue; - outputFile.PutFileContent(layer.Filename, layer.CompressedBytes, ZipArchiveMode.Update); - layer.IsModified = false; - } - } - - //Decode(FileFullPath, progress); - - } - - public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) - { - if (!IsValid) return false; - - if (to == typeof(ChituboxFile)) - { - ChituboxFile defaultFormat = (ChituboxFile)FindByType(typeof(ChituboxFile)); - ChituboxFile file = new ChituboxFile - { - LayerManager = LayerManager, - HeaderSettings = - { - Version = 2, - BedSizeX = PrinterSettings.DisplayWidth, - BedSizeY = PrinterSettings.DisplayHeight, - BedSizeZ = PrinterSettings.MaxPrintHeight, - OverallHeightMilimeter = TotalHeight, - BottomExposureSeconds = InitialExposureTime, - BottomLayersCount = InitialLayerCount, - BottomLightPWM = LookupCustomValue(Keyword_BottomLightPWM, defaultFormat.HeaderSettings.BottomLightPWM), - LayerCount = LayerCount, - LayerExposureSeconds = LayerExposureTime, - LayerHeightMilimeter = LayerHeight, - LayerOffTime = LookupCustomValue(Keyword_LayerOffTime, defaultFormat.HeaderSettings.LayerOffTime), - LightPWM = LookupCustomValue(Keyword_LightPWM, defaultFormat.HeaderSettings.LightPWM), - PrintTime = (uint) OutputConfigSettings.PrintTime, - ProjectorType = PrinterSettings.DisplayMirrorX || PrinterSettings.DisplayMirrorY ? 1u : 0u, - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - AntiAliasLevel = ValidateAntiAliasingLevel() - }, - PrintParametersSettings = - { - BottomLayerCount = PrintSettings.FadedLayers, - BottomLiftHeight = LookupCustomValue(Keyword_BottomLiftHeight, - defaultFormat.PrintParametersSettings.BottomLiftHeight), - BottomLiftSpeed = LookupCustomValue(Keyword_BottomLiftSpeed, - defaultFormat.PrintParametersSettings.BottomLiftSpeed), - BottomLightOffDelay = LookupCustomValue(Keyword_BottomLightOffDelay, - defaultFormat.PrintParametersSettings.BottomLightOffDelay), - CostDollars = MaterialCost, - LiftHeight = LookupCustomValue(Keyword_LiftHeight, - defaultFormat.PrintParametersSettings.LiftHeight), - LiftSpeed = LookupCustomValue(Keyword_LiftSpeed, - defaultFormat.PrintParametersSettings.LiftSpeed), - LightOffDelay = LookupCustomValue(Keyword_LightOffDelay, - defaultFormat.PrintParametersSettings.LightOffDelay), - RetractSpeed = LookupCustomValue(Keyword_RetractSpeed, - defaultFormat.PrintParametersSettings.RetractSpeed), - VolumeMl = UsedMaterial, - WeightG = (float) Math.Round( - OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 2) - }, - SlicerInfoSettings = {MachineName = MachineName, MachineNameSize = (uint) MachineName.Length} - }; - - - if (LookupCustomValue("FLIP_XY", false, true)) - { - file.HeaderSettings.ResolutionX = PrinterSettings.DisplayPixelsY; - file.HeaderSettings.ResolutionY = PrinterSettings.DisplayPixelsX; - } - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(ChituboxZipFile)) - { - ChituboxZipFile defaultFormat = (ChituboxZipFile)FindByType(typeof(ChituboxZipFile)); - ChituboxZipFile file = new ChituboxZipFile - { - LayerManager = LayerManager, - HeaderSettings = - { - Filename = Path.GetFileName(FileFullPath), - - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - MachineX = PrinterSettings.DisplayWidth, - MachineY = PrinterSettings.DisplayHeight, - MachineZ = PrinterSettings.MaxPrintHeight, - MachineType = MachineName, - ProjectType = PrinterSettings.DisplayMirrorX || PrinterSettings.DisplayMirrorY ? "LCD_mirror" : "Normal", - - Resin = MaterialName, - Price = MaterialCost, - Weight = (float) Math.Round(UsedMaterial * MaterialSettings.MaterialDensity, 2), - Volume = UsedMaterial, - Mirror = (byte) (PrinterSettings.DisplayMirrorX || PrinterSettings.DisplayMirrorY ? 1 : 0), - - - BottomLayerLiftHeight = LookupCustomValue(Keyword_BottomLiftHeight, defaultFormat.HeaderSettings.BottomLayerLiftHeight), - LayerLiftHeight = LookupCustomValue(Keyword_LiftHeight, defaultFormat.HeaderSettings.LayerLiftHeight), - BottomLayerLiftSpeed = LookupCustomValue(Keyword_BottomLiftSpeed, defaultFormat.HeaderSettings.BottomLayerLiftSpeed), - LayerLiftSpeed = LookupCustomValue(Keyword_LiftSpeed, defaultFormat.HeaderSettings.LayerLiftSpeed), - RetractSpeed = LookupCustomValue(Keyword_RetractSpeed, defaultFormat.HeaderSettings.RetractSpeed), - BottomLayCount = InitialLayerCount, - BottomLayerCount = InitialLayerCount, - BottomLightOffTime = LookupCustomValue(Keyword_BottomLightOffDelay, defaultFormat.HeaderSettings.BottomLightOffTime), - LayerLightOffTime = LookupCustomValue(Keyword_LightOffDelay, defaultFormat.HeaderSettings.LayerLightOffTime), - BottomLayExposureTime = InitialExposureTime, - BottomLayerExposureTime = InitialExposureTime, - LayerExposureTime = LayerExposureTime, - LayerHeight = LayerHeight, - LayerCount = LayerCount, - AntiAliasing = ValidateAntiAliasingLevel(), - BottomLightPWM = LookupCustomValue(Keyword_BottomLightPWM, defaultFormat.HeaderSettings.BottomLightPWM), - LayerLightPWM = LookupCustomValue(Keyword_LightPWM, defaultFormat.HeaderSettings.LayerLightPWM), - - EstimatedPrintTime = PrintTime - }, - }; - - - if (LookupCustomValue("FLIP_XY", false, true)) - { - file.HeaderSettings.ResolutionX = PrinterSettings.DisplayPixelsY; - file.HeaderSettings.ResolutionY = PrinterSettings.DisplayPixelsX; - } - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(PWSFile)) - { - PWSFile defaultFormat = (PWSFile)FindByType(typeof(PWSFile)); - PWSFile file = new PWSFile - { - LayerManager = LayerManager, - HeaderSettings = - { - ResolutionX = ResolutionX, - ResolutionY = ResolutionY, - LayerHeight = LayerHeight, - LayerExposureTime = LayerExposureTime, - LiftHeight = LookupCustomValue(Keyword_LiftHeight, defaultFormat.HeaderSettings.LiftHeight), - LiftSpeed = LookupCustomValue(Keyword_LiftSpeed, defaultFormat.HeaderSettings.LiftSpeed) / 60, - RetractSpeed = LookupCustomValue(Keyword_RetractSpeed, defaultFormat.HeaderSettings.RetractSpeed) / 60, - LayerOffTime = LookupCustomValue(Keyword_LayerOffTime, defaultFormat.HeaderSettings.LayerOffTime), - BottomLayersCount = InitialLayerCount, - BottomExposureSeconds = InitialExposureTime, - Price = MaterialCost, - Volume = UsedMaterial, - Weight = (float) Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 2), - AntiAliasing = ValidateAntiAliasingLevel() - } - }; - - - if (LookupCustomValue("FLIP_XY", false, true)) - { - file.HeaderSettings.ResolutionX = PrinterSettings.DisplayPixelsY; - file.HeaderSettings.ResolutionY = PrinterSettings.DisplayPixelsX; - } - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(PHZFile)) - { - PHZFile defaultFormat = (PHZFile)FindByType(typeof(PHZFile)); - PHZFile file = new PHZFile - { - LayerManager = LayerManager, - HeaderSettings = - { - Version = 2, - BedSizeX = PrinterSettings.DisplayWidth, - BedSizeY = PrinterSettings.DisplayHeight, - BedSizeZ = PrinterSettings.MaxPrintHeight, - OverallHeightMilimeter = TotalHeight, - BottomExposureSeconds = MaterialSettings.InitialExposureTime, - BottomLayersCount = PrintSettings.FadedLayers, - BottomLightPWM = LookupCustomValue(Keyword_BottomLightPWM, defaultFormat.HeaderSettings.BottomLightPWM), - LayerCount = LayerCount, - LayerExposureSeconds = MaterialSettings.ExposureTime, - LayerHeightMilimeter = PrintSettings.LayerHeight, - LayerOffTime = LookupCustomValue(Keyword_LayerOffTime, defaultFormat.HeaderSettings.LayerOffTime), - LightPWM = LookupCustomValue(Keyword_LightPWM, defaultFormat.HeaderSettings.LightPWM), - PrintTime = (uint) OutputConfigSettings.PrintTime, - ProjectorType = PrinterSettings.DisplayMirrorX || PrinterSettings.DisplayMirrorY ? 1u : 0u, - ResolutionX = PrinterSettings.DisplayPixelsX, - ResolutionY = PrinterSettings.DisplayPixelsY, - BottomLayerCount = PrintSettings.FadedLayers, - BottomLiftHeight = LookupCustomValue(Keyword_BottomLiftHeight, defaultFormat.HeaderSettings.BottomLiftHeight), - BottomLiftSpeed = LookupCustomValue(Keyword_BottomLiftSpeed, defaultFormat.HeaderSettings.BottomLiftSpeed), - BottomLightOffDelay = LookupCustomValue(Keyword_BottomLightOffDelay, defaultFormat.HeaderSettings.BottomLightOffDelay), - CostDollars = MaterialCost, - LiftHeight = LookupCustomValue(Keyword_LiftHeight, defaultFormat.HeaderSettings.LiftHeight), - LiftSpeed = LookupCustomValue(Keyword_LiftSpeed, defaultFormat.HeaderSettings.LiftSpeed), - RetractSpeed = LookupCustomValue(Keyword_RetractSpeed, defaultFormat.HeaderSettings.RetractSpeed), - VolumeMl = OutputConfigSettings.UsedMaterial, - WeightG = (float)Math.Round(OutputConfigSettings.UsedMaterial * MaterialSettings.MaterialDensity, 2), - MachineName = MachineName, - MachineNameSize = (uint)MachineName.Length, - AntiAliasLevelInfo = ValidateAntiAliasingLevel() - } - }; - - if (LookupCustomValue("FLIP_XY", false, true)) - { - file.HeaderSettings.ResolutionX = ResolutionY; - file.HeaderSettings.ResolutionY = ResolutionX; - } - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - - return true; - } - - if (to == typeof(ZCodexFile)) - { - ZCodexFile defaultFormat = (ZCodexFile)FindByType(typeof(ZCodexFile)); - TimeSpan ts = new TimeSpan(0, 0, (int)PrintTime); - ZCodexFile file = new ZCodexFile - { - ResinMetadataSettings = new ZCodexFile.ResinMetadata - { - MaterialId = 2, - Material = MaterialName, - AdditionalSupportLayerTime = 0, - BottomLayersNumber = InitialLayerCount, - BottomLayersTime = (uint)(InitialExposureTime*1000), - LayerTime = (uint)(LayerExposureTime * 1000), - DisableSettingsChanges = false, - LayerThickness = LayerHeight, - PrintTime = (uint)PrintTime, - TotalLayersCount = LayerCount, - TotalMaterialVolumeUsed = UsedMaterial, - TotalMaterialWeightUsed = UsedMaterial, - }, - UserSettings = new ZCodexFile.UserSettingsdata - { - Printer = MachineName, - BottomLayersCount = InitialLayerCount, - PrintTime = $"{ts.Hours}h {ts.Minutes}m", - LayerExposureTime = (uint)(LayerExposureTime * 1000), - BottomLayerExposureTime = (uint)(InitialExposureTime * 1000), - MaterialId = 2, - LayerThickness = $"{LayerHeight} mm", - AntiAliasing = (byte) (ValidateAntiAliasingLevel() > 1 ? 1 : 0), - CrossSupportEnabled = 1, - ExposureOffTime = LookupCustomValue(Keyword_LayerOffTime, defaultFormat.UserSettings.ExposureOffTime) * 1000, - HollowEnabled = PrintSettings.HollowingEnable ? (byte)1 : (byte)0, - HollowThickness = PrintSettings.HollowingMinThickness, - InfillDensity = 0, - IsAdvanced = 0, - MaterialType = MaterialName, - MaterialVolume = UsedMaterial, - MaxLayer = LayerCount-1, - ModelLiftEnabled = PrintSettings.SupportObjectElevation > 0 ? (byte)1 : (byte)0, - ModelLiftHeight = PrintSettings.SupportObjectElevation, - RaftEnabled = PrintSettings.SupportBaseHeight > 0 ? (byte)1 : (byte)0, - RaftHeight = PrintSettings.SupportBaseHeight, - RaftOffset = 0, - SupportAdditionalExposureEnabled = 0, - SupportAdditionalExposureTime = 0, - XCorrection = PrinterSettings.AbsoluteCorrection, - YCorrection = PrinterSettings.AbsoluteCorrection, - ZLiftDistance = (float)Math.Round(LookupCustomValue(Keyword_LiftHeight, defaultFormat.UserSettings.ZLiftDistance), 2), - ZLiftFeedRate = (float)Math.Round(LookupCustomValue(Keyword_LiftSpeed, defaultFormat.UserSettings.ZLiftFeedRate), 2), - ZLiftRetractRate = (float)Math.Round(LookupCustomValue(Keyword_RetractSpeed, defaultFormat.UserSettings.ZLiftRetractRate), 2), - }, - ZCodeMetadataSettings = new ZCodexFile.ZCodeMetadata - { - PrintTime = (uint)PrintTime, - PrinterName = MachineName, - Materials = new List - { - new ZCodexFile.ZCodeMetadata.MaterialsData - { - Name = MaterialName, - ExtruderType = "MAIN", - Id = 0, - Usage = 0, - Temperature = 0 - } - }, - }, - LayerManager = LayerManager - }; - - float usedMaterial = UsedMaterial / LayerCount; - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - file.ResinMetadataSettings.Layers.Add(new ZCodexFile.ResinMetadata.LayerData - { - Layer = layerIndex, - UsedMaterialVolume = usedMaterial - }); - } - - file.SetThumbnails(Thumbnails); - file.Encode(fileFullPath, progress); - return true; - } - - if (to == typeof(CWSFile)) - { - CWSFile defaultFormat = (CWSFile)FindByType(typeof(CWSFile)); - CWSFile file = new CWSFile - { - LayerManager = LayerManager - }; - - file.SliceSettings.Xppm = file.OutputSettings.PixPermmX = (float) Math.Round(LookupCustomValue("Xppm", file.SliceSettings.Xppm), 3); - file.SliceSettings.Yppm = file.OutputSettings.PixPermmY = (float) Math.Round(LookupCustomValue("Yppm", file.SliceSettings.Xppm), 3); - file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort)ResolutionX; - file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort)ResolutionY; - file.SliceSettings.Thickness = file.OutputSettings.LayerThickness = LayerHeight; - file.SliceSettings.LayersNum = file.OutputSettings.LayersNum = LayerCount; - file.SliceSettings.HeadLayersNum = file.OutputSettings.NumberBottomLayers = InitialLayerCount; - file.SliceSettings.LayersExpoMs = file.OutputSettings.LayerTime = (uint) LayerExposureTime * 1000; - file.SliceSettings.HeadLayersExpoMs = file.OutputSettings.BottomLayersTime = (uint) InitialExposureTime * 1000; - file.SliceSettings.WaitBeforeExpoMs = LookupCustomValue("WaitBeforeExpoMs", file.SliceSettings.WaitBeforeExpoMs); - file.SliceSettings.LiftDistance = file.OutputSettings.LiftDistance = (float) Math.Round(LookupCustomValue(Keyword_LiftHeight, file.SliceSettings.LiftDistance), 2); - file.SliceSettings.LiftUpSpeed = file.OutputSettings.ZLiftFeedRate = file.OutputSettings.ZBottomLiftFeedRate = (float) Math.Round(LookupCustomValue(Keyword_LiftSpeed, file.SliceSettings.LiftUpSpeed), 2); - file.SliceSettings.LiftDownSpeed = file.OutputSettings.ZLiftRetractRate = (float) Math.Round(LookupCustomValue(Keyword_RetractSpeed, file.SliceSettings.LiftDownSpeed), 2); - file.SliceSettings.LiftWhenFinished = LookupCustomValue("LiftWhenFinished", file.SliceSettings.LiftWhenFinished); - - file.OutputSettings.BlankingLayerTime = LookupCustomValue("BlankingLayerTime", file.OutputSettings.BlankingLayerTime); - //file.OutputSettings.RenderOutlines = false; - //file.OutputSettings.OutlineWidthInset = 0; - //file.OutputSettings.OutlineWidthOutset = 0; - file.OutputSettings.RenderOutlines = false; - //file.OutputSettings.TiltValue = 0; - //file.OutputSettings.UseMainliftGCodeTab = false; - //file.OutputSettings.AntiAliasing = 0; - //file.OutputSettings.AntiAliasingValue = 0; - file.OutputSettings.FlipX = PrinterSettings.DisplayMirrorX; - file.OutputSettings.FlipY = PrinterSettings.DisplayMirrorY; - file.OutputSettings.AntiAliasingValue = ValidateAntiAliasingLevel(); - file.OutputSettings.AntiAliasing = file.OutputSettings.AntiAliasingValue > 1; - - - - if (LookupCustomValue("FLIP_XY", false, true)) - { - file.SliceSettings.Xres = file.OutputSettings.XResolution = (ushort) ResolutionY; - file.SliceSettings.Yres = file.OutputSettings.YResolution = (ushort) ResolutionX; - } - - file.Encode(fileFullPath, progress); - - return true; - } - - return false; - } - - public T LookupCustomValue(string name, T defaultValue, bool existsOnly = false) - { - if (string.IsNullOrEmpty(PrinterSettings.PrinterNotes)) return defaultValue; - string result = string.Empty; - if(!existsOnly) - name += '_'; - - int index = PrinterSettings.PrinterNotes.IndexOf(name, StringComparison.Ordinal); - int startIndex = index + name.Length; - - if (index < 0 || PrinterSettings.PrinterNotes.Length < startIndex) return defaultValue; - if (existsOnly) return "true".Convert(); - for (int i = startIndex; i < PrinterSettings.PrinterNotes.Length; i++) - { - char c = PrinterSettings.PrinterNotes[i]; - if (!char.IsLetterOrDigit(c) && c != '.') - { - break; - } - - result += c; - } - - return string.IsNullOrWhiteSpace(result) ? defaultValue : result.Convert(); - } - - #endregion - } -} diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 153a0b2..7fddd8e 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ https://github.com/sn4k3/UVtools https://github.com/sn4k3/UVtools MSLA/DLP, file analysis, repair, conversion and manipulation - 0.6.1.0 + 0.6.1.1 Copyright © 2020 PTRTECH UVtools.png @@ -23,10 +23,6 @@ true - - - - True @@ -36,7 +32,6 @@ True - diff --git a/UVtools.Core/ZCodexFile.cs b/UVtools.Core/ZCodexFile.cs deleted file mode 100644 index cf7803e..0000000 --- a/UVtools.Core/ZCodexFile.cs +++ /dev/null @@ -1,447 +0,0 @@ -/* - * GNU AFFERO GENERAL PUBLIC LICENSE - * Version 3, 19 November 2007 - * Copyright (C) 2007 Free Software Foundation, Inc. - * Everyone is permitted to copy and distribute verbatim copies - * of this license document, but changing it is not allowed. - */ - -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Text; -using System.Text.RegularExpressions; -using Emgu.CV; -using Emgu.CV.CvEnum; -using Emgu.CV.Util; -using Newtonsoft.Json; -using UVtools.Core.Extensions; - -namespace UVtools.Core -{ - public class ZCodexFile : FileFormat - { - #region Constants - - private const string GCodeStart = "G28\nG21\nG91\nM17\n"; - private const string GCodeKeywordSlice = ""; - private const string GCodeKeywordDelayBlank = ""; - private const string GCodeKeywordDelayModel = ""; - private const string GCodeKeywordDelaySupportPart = ""; - private const string GCodeKeywordDelaySupportFull = ""; - private const string FolderImages = "ResinSlicesData"; - private const string FolderImageName = "Slice"; - #endregion - - #region Sub Classes - - public class ResinMetadata - { - public class LayerData - { - public uint Layer { get; set; } - public float UsedMaterialVolume { get; set; } - - } - - public string Guid { get; set; } = "07452AC2-7494-4576-BA60-BFEA8815F917"; - public string Material { get; set; } - public uint MaterialId { get; set; } - public float LayerThickness { get; set; } - public uint PrintTime { get; set; } - public uint LayerTime { get; set; } - public uint BottomLayersTime { get; set; } - public uint AdditionalSupportLayerTime { get; set; } - public ushort BottomLayersNumber { get; set; } - public uint BlankingLayerTime { get; set; } - public float TotalMaterialVolumeUsed { get; set; } - public float TotalMaterialWeightUsed { get; set; } - public uint TotalLayersCount { get; set; } - public bool DisableSettingsChanges { get; set; } - - public List Layers { get; set; } = new List(); - } - - public class UserSettingsdata - { - public uint MaxLayer { get; set; } - public string PrintTime { get; set; } - public float MaterialVolume { get; set; } - public byte IsAdvanced { get; set; } - public string Printer { get; set; } = "Zortrax Inkspire"; - public string MaterialType { get; set; } - public uint MaterialId { get; set; } - public string LayerThickness { get; set; } - public byte RaftEnabled { get; set; } - public float RaftHeight { get; set; } - public float RaftOffset { get; set; } - public byte ModelLiftEnabled { get; set; } - public float ModelLiftHeight { get; set; } - public byte CrossSupportEnabled { get; set; } - public uint LayerExposureTime { get; set; } - //public uint LayerThicknessesDisplayTime { get; set; } arr - public uint ExposureOffTime { get; set; } = 5; - public uint BottomLayerExposureTime { get; set; } - public uint BottomLayersCount { get; set; } - public byte SupportAdditionalExposureEnabled { get; set; } - public uint SupportAdditionalExposureTime { get; set; } - public float ZLiftDistance { get; set; } = 5; - public float ZLiftRetractRate { get; set; } = 100; - public float ZLiftFeedRate { get; set; } = 100; - public byte AntiAliasing { get; set; } = 0; - public float XCorrection { get; set; } - public float YCorrection { get; set; } - public byte HollowEnabled { get; set; } - public float HollowThickness { get; set; } - public byte InfillDensity { get; set; } - } - - public class ZCodeMetadata - { - public class MaterialsData - { - public string ExtruderType { get; set; } - public uint Id { get; set; } - public string Name { get; set; } - public uint Usage { get; set; } - public uint Temperature { get; set; } - } - - public string ZCodexVersion { get; set; } = "2.0.0.0"; - public string SoftwareVersion { get; set; } = "2.12.2.0"; - public string MinFirmwareVersion { get; set; } = "20013"; - public uint PrinterModelEnumId { get; set; } = 40; - public string PrinterName { get; set; } = "Inkspire"; - public List Materials { get; set; } - public byte HeatbedTemperature { get; set; } - public byte ChamberTemperature { get; set; } - public uint CommandCount { get; set; } - public uint PrintTime { get; set; } - public float NozzleDiameter { get; set; } - public string PrintBoundingBox { get; set; } - public string Pauses { get; set; } - public string MaterialUsages { get; set; } - } - - public class LayerData - { - public int SupportLayerFileIndex { get; set; } = -1; - public int LayerFileIndex { get; set; } = -1; - public ZipArchiveEntry SupportLayerEntry { get; set; } - public ZipArchiveEntry LayerEntry { get; set; } - - public bool HaveSupportLayer => !ReferenceEquals(SupportLayerEntry, null); - } - - #endregion - - #region Properties - public ResinMetadata ResinMetadataSettings { get; set; } = new ResinMetadata(); - public UserSettingsdata UserSettings { get; set; } = new UserSettingsdata(); - public ZCodeMetadata ZCodeMetadataSettings { get; set; } = new ZCodeMetadata(); - - public List LayersSettings { get; } = new List(); - - public override FileFormatType FileType => FileFormatType.Archive; - - public override FileExtension[] FileExtensions { get; } = { - new FileExtension("zcodex", "Z-Suite ZCodex Files") - }; - - public override Type[] ConvertToFormats { get; } = null; - - public override PrintParameterModifier[] PrintParameterModifiers { get; } = { - PrintParameterModifier.InitialLayerCount, - PrintParameterModifier.InitialExposureSeconds, - PrintParameterModifier.ExposureSeconds, - - - PrintParameterModifier.LiftHeight, - PrintParameterModifier.RetractSpeed, - PrintParameterModifier.LiftSpeed, - }; - - public override byte ThumbnailsCount { get; } = 1; - - public override System.Drawing.Size[] ThumbnailsOriginalSize { get; } = {new System.Drawing.Size(320, 180)}; - - public override uint ResolutionX => 1440; - - public override uint ResolutionY => 2560; - public override byte AntiAliasing => UserSettings.AntiAliasing; - - public override float LayerHeight => ResinMetadataSettings.LayerThickness; - - public override ushort InitialLayerCount => ResinMetadataSettings.BottomLayersNumber; - - public override float InitialExposureTime => UserSettings.BottomLayerExposureTime / 1000; - - public override float LayerExposureTime => UserSettings.LayerExposureTime / 1000; - public override float LiftHeight => UserSettings.ZLiftDistance; - - public override float LiftSpeed => UserSettings.ZLiftFeedRate; - - public override float RetractSpeed => UserSettings.ZLiftRetractRate; - - public override float PrintTime => ResinMetadataSettings.PrintTime; - - public override float UsedMaterial => ResinMetadataSettings.TotalMaterialVolumeUsed; - - public override float MaterialCost => 0; - - public override string MaterialName => ResinMetadataSettings.Material; - - public override string MachineName => ZCodeMetadataSettings.PrinterName; - - public override object[] Configs => new[] {(object) ResinMetadataSettings, UserSettings, ZCodeMetadataSettings}; - #endregion - - #region Methods - - public override void Clear() - { - base.Clear(); - LayersSettings.Clear(); - } - - public override void Encode(string fileFullPath, OperationProgress progress = null) - { - base.Encode(fileFullPath, progress); - using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) - { - outputFile.PutFileContent("ResinMetadata", JsonConvert.SerializeObject(ResinMetadataSettings), ZipArchiveMode.Create); - outputFile.PutFileContent("UserSettingsData", JsonConvert.SerializeObject(UserSettings), ZipArchiveMode.Create); - outputFile.PutFileContent("ZCodeMetadata", JsonConvert.SerializeObject(ZCodeMetadataSettings), ZipArchiveMode.Create); - - if (CreatedThumbnailsCount > 0) - { - using (Stream stream = outputFile.CreateEntry("Preview.png").Open()) - { - var vec = new VectorOfByte(); - CvInvoke.Imencode(".png", Thumbnails[0], vec); - stream.WriteBytes(vec.ToArray()); - stream.Close(); - } - } - - GCode = new StringBuilder(GCodeStart); - - for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) - { - progress.Token.ThrowIfCancellationRequested(); - - GCode.AppendLine($"{GCodeKeywordSlice} {layerIndex}"); - GCode.AppendLine($"G1 Z{LiftHeight} F{LiftSpeed}"); - GCode.AppendLine($"G1 Z-{LiftHeight - LayerHeight} F{RetractSpeed}"); - GCode.AppendLine(GCodeKeywordDelayBlank); - GCode.AppendLine("M106 S255"); - GCode.AppendLine(GCodeKeywordDelayModel); - GCode.AppendLine("M106 S0"); - - - var layerimagePath = $"{FolderImages}/{FolderImageName}{layerIndex:D5}.png"; - using (Stream stream = outputFile.CreateEntry(layerimagePath).Open()) - { - //image.Save(stream, Helpers.PngEncoder); - var byteArr = this[layerIndex].CompressedBytes; - stream.Write(byteArr, 0, byteArr.Length); - stream.Close(); - } - - progress++; - } - - GCode.AppendLine($"G1 Z40.0 F{UserSettings.ZLiftFeedRate}"); - GCode.AppendLine("M18"); - - outputFile.PutFileContent("ResinGCodeData", GCode.ToString(), ZipArchiveMode.Create); - } - } - - public override void Decode(string fileFullPath, OperationProgress progress = null) - { - base.Decode(fileFullPath, progress); - - FileFullPath = fileFullPath; - using (var inputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Read)) - { - var entry = inputFile.GetEntry("ResinMetadata"); - if (ReferenceEquals(entry, null)) - { - Clear(); - throw new FileLoadException("ResinMetadata not found", fileFullPath); - } - - ResinMetadataSettings = Helpers.JsonDeserializeObject(entry.Open()); - - entry = inputFile.GetEntry("UserSettingsData"); - if (ReferenceEquals(entry, null)) - { - Clear(); - throw new FileLoadException("UserSettingsData not found", fileFullPath); - } - - UserSettings = Helpers.JsonDeserializeObject(entry.Open()); - - entry = inputFile.GetEntry("ZCodeMetadata"); - if (ReferenceEquals(entry, null)) - { - Clear(); - throw new FileLoadException("ZCodeMetadata not found", fileFullPath); - } - - ZCodeMetadataSettings = Helpers.JsonDeserializeObject(entry.Open()); - - entry = inputFile.GetEntry("ResinGCodeData"); - if (ReferenceEquals(entry, null)) - { - Clear(); - throw new FileLoadException("ResinGCodeData not found", fileFullPath); - } - - LayerManager = new LayerManager(ResinMetadataSettings.TotalLayersCount); - GCode = new StringBuilder(); - using (TextReader tr = new StreamReader(entry.Open())) - { - string line; - int layerIndex = 0; - int layerFileIndex = 0; - string layerimagePath = null; - while (!ReferenceEquals(line = tr.ReadLine(), null)) - { - GCode.AppendLine(line); - if (line.StartsWith(GCodeKeywordSlice)) - { - layerFileIndex = int.Parse(line.Substring(GCodeKeywordSlice.Length)); - layerimagePath = $"{FolderImages}/{FolderImageName}{layerFileIndex:D5}.png"; - if (LayersSettings.Count - 1 < layerIndex) LayersSettings.Add(new LayerData()); - continue; - } - - if (line.StartsWith(GCodeKeywordDelaySupportPart)) - { - LayersSettings[layerIndex].SupportLayerFileIndex = layerFileIndex; - LayersSettings[layerIndex].SupportLayerEntry = inputFile.GetEntry(layerimagePath); - continue; - } - - if (line.StartsWith(GCodeKeywordDelaySupportFull) || line.StartsWith(GCodeKeywordDelayModel)) - { - LayersSettings[layerIndex].LayerFileIndex = layerFileIndex; - LayersSettings[layerIndex].LayerEntry = inputFile.GetEntry(layerimagePath); - this[layerIndex] = new Layer((uint) layerIndex, LayersSettings[layerIndex].LayerEntry.Open(), LayersSettings[layerIndex].LayerEntry.Name); - layerIndex++; - continue; - } - } - - tr.Close(); - } - - entry = inputFile.GetEntry("Preview.png"); - if (!ReferenceEquals(entry, null)) - { - using (Stream stream = entry.Open()) - { - - CvInvoke.Imdecode(stream.ToArray(), ImreadModes.AnyColor, Thumbnails[0]); - stream.Close(); - } - } - } - - LayerManager.GetBoundingRectangle(progress); - } - - public override bool SetValueFromPrintParameterModifier(PrintParameterModifier modifier, string value) - { - if (ReferenceEquals(modifier, PrintParameterModifier.InitialLayerCount)) - { - UserSettings.BottomLayersCount = - ResinMetadataSettings.BottomLayersNumber = value.Convert(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) - { - ResinMetadataSettings.BottomLayersTime = - UserSettings.BottomLayerExposureTime = value.Convert()*1000; - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) - { - ResinMetadataSettings.LayerTime = - UserSettings.LayerExposureTime = value.Convert()*1000; - return true; - } - - if (ReferenceEquals(modifier, PrintParameterModifier.LiftHeight)) - { - UserSettings.ZLiftDistance = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.LiftSpeed)) - { - UserSettings.ZLiftFeedRate = value.Convert(); - UpdateGCode(); - return true; - } - if (ReferenceEquals(modifier, PrintParameterModifier.RetractSpeed)) - { - UserSettings.ZLiftRetractRate = value.Convert(); - UpdateGCode(); - return true; - } - - return false; - } - - public override void SaveAs(string filePath = null, OperationProgress progress = null) - { - if (!string.IsNullOrEmpty(filePath)) - { - File.Copy(FileFullPath, filePath, true); - FileFullPath = filePath; - - } - - using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) - { - outputFile.PutFileContent("ResinMetadata", JsonConvert.SerializeObject(ResinMetadataSettings), ZipArchiveMode.Update); - outputFile.PutFileContent("UserSettingsData", JsonConvert.SerializeObject(UserSettings), ZipArchiveMode.Update); - outputFile.PutFileContent("ZCodeMetadata", JsonConvert.SerializeObject(ZCodeMetadataSettings), ZipArchiveMode.Update); - outputFile.PutFileContent("ResinGCodeData", GCode.ToString(), ZipArchiveMode.Update); - - foreach (var layer in this) - { - if (!layer.IsModified) continue; - outputFile.PutFileContent(layer.Filename, layer.CompressedBytes, ZipArchiveMode.Update); - layer.IsModified = false; - } - } - - //Decode(FileFullPath, progress); - } - - public override bool Convert(Type to, string fileFullPath, OperationProgress progress = null) - { - throw new NotImplementedException(); - } - - private void UpdateGCode() - { - var gcode = GCode.ToString(); - gcode = Regex.Replace(gcode, @"Z[+]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", - $"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.ZLiftRetractRate}"); - - GCode.Clear(); - GCode.Append(gcode); - - } - #endregion - } -} diff --git a/UVtools.GUI/Forms/FrmAbout.cs b/UVtools.GUI/Forms/FrmAbout.cs index baeebdc..846dba8 100644 --- a/UVtools.GUI/Forms/FrmAbout.cs +++ b/UVtools.GUI/Forms/FrmAbout.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Reflection; using System.Windows.Forms; using UVtools.Core; +using UVtools.Core.FileFormats; namespace UVtools.GUI.Forms { diff --git a/UVtools.GUI/Forms/FrmInputBox.cs b/UVtools.GUI/Forms/FrmInputBox.cs index 7d014dc..83328ed 100644 --- a/UVtools.GUI/Forms/FrmInputBox.cs +++ b/UVtools.GUI/Forms/FrmInputBox.cs @@ -10,6 +10,7 @@ using System; using System.Globalization; using System.Windows.Forms; using UVtools.Core; +using UVtools.Core.FileFormats; namespace UVtools.GUI.Forms { diff --git a/UVtools.GUI/Forms/FrmLoading.cs b/UVtools.GUI/Forms/FrmLoading.cs index 9774a9f..10ebd38 100644 --- a/UVtools.GUI/Forms/FrmLoading.cs +++ b/UVtools.GUI/Forms/FrmLoading.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Threading.Tasks; using System.Windows.Forms; using UVtools.Core; +using UVtools.Core.Operations; namespace UVtools.GUI.Forms { diff --git a/UVtools.GUI/Forms/FrmMutationMove.cs b/UVtools.GUI/Forms/FrmMutationMove.cs index 42ebbc0..4ac6a04 100644 --- a/UVtools.GUI/Forms/FrmMutationMove.cs +++ b/UVtools.GUI/Forms/FrmMutationMove.cs @@ -10,6 +10,7 @@ using System; using System.Drawing; using System.Windows.Forms; using UVtools.Core; +using UVtools.Core.Operations; namespace UVtools.GUI.Forms { diff --git a/UVtools.GUI/Forms/FrmSettings.cs b/UVtools.GUI/Forms/FrmSettings.cs index e2b4d1b..b385aba 100644 --- a/UVtools.GUI/Forms/FrmSettings.cs +++ b/UVtools.GUI/Forms/FrmSettings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using UVtools.Core; +using UVtools.Core.FileFormats; using UVtools.GUI.Properties; namespace UVtools.GUI.Forms diff --git a/UVtools.GUI/Forms/FrmToolPattern.cs b/UVtools.GUI/Forms/FrmToolPattern.cs index 49e690d..34cc9c1 100644 --- a/UVtools.GUI/Forms/FrmToolPattern.cs +++ b/UVtools.GUI/Forms/FrmToolPattern.cs @@ -10,6 +10,7 @@ using System; using System.Drawing; using System.Windows.Forms; using UVtools.Core; +using UVtools.Core.Operations; namespace UVtools.GUI.Forms { diff --git a/UVtools.GUI/FrmMain.Designer.cs b/UVtools.GUI/FrmMain.Designer.cs index 75ad320..b28e316 100644 --- a/UVtools.GUI/FrmMain.Designer.cs +++ b/UVtools.GUI/FrmMain.Designer.cs @@ -138,7 +138,7 @@ this.btnNextLayer = new System.Windows.Forms.Button(); this.lbMaxLayer = new System.Windows.Forms.Label(); this.panel1 = new System.Windows.Forms.Panel(); - this.lbLayerActual = new System.Windows.Forms.Label(); + this.lbActualLayer = new System.Windows.Forms.Label(); this.tbLayer = new System.Windows.Forms.TrackBar(); this.lbInitialLayer = new System.Windows.Forms.Label(); this.panel2 = new System.Windows.Forms.Panel(); @@ -1266,7 +1266,7 @@ // // panel1 // - this.panel1.Controls.Add(this.lbLayerActual); + this.panel1.Controls.Add(this.lbActualLayer); this.panel1.Controls.Add(this.tbLayer); this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; this.panel1.Location = new System.Drawing.Point(3, 85); @@ -1276,13 +1276,13 @@ // // lbLayerActual // - this.lbLayerActual.AutoSize = true; - this.lbLayerActual.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.lbLayerActual.Location = new System.Drawing.Point(3, 248); - this.lbLayerActual.Name = "lbLayerActual"; - this.lbLayerActual.Size = new System.Drawing.Size(15, 15); - this.lbLayerActual.TabIndex = 9; - this.lbLayerActual.Text = "?"; + this.lbActualLayer.AutoSize = true; + this.lbActualLayer.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.lbActualLayer.Location = new System.Drawing.Point(3, 248); + this.lbActualLayer.Name = "lbActualLayer"; + this.lbActualLayer.Size = new System.Drawing.Size(15, 15); + this.lbActualLayer.TabIndex = 9; + this.lbActualLayer.Text = "?"; // // tbLayer // @@ -1520,7 +1520,7 @@ private System.Windows.Forms.Button btnNextLayer; private System.Windows.Forms.Button btnPreviousLayer; private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.Label lbLayerActual; + private System.Windows.Forms.Label lbActualLayer; private System.Windows.Forms.Panel panel2; private System.Windows.Forms.Button btnFirstLayer; private System.Windows.Forms.Button btnLastLayer; diff --git a/UVtools.GUI/FrmMain.cs b/UVtools.GUI/FrmMain.cs index b40adbf..c2e9dd9 100644 --- a/UVtools.GUI/FrmMain.cs +++ b/UVtools.GUI/FrmMain.cs @@ -22,6 +22,8 @@ using Emgu.CV.Structure; using Emgu.CV.Util; using UVtools.Core; using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; +using UVtools.Core.Operations; using UVtools.GUI.Forms; using UVtools.GUI.Properties; @@ -332,10 +334,10 @@ namespace UVtools.GUI ZoomToFit(); } - lbLayerActual.Location = new Point(lbLayerActual.Location.X, + lbActualLayer.Location = new Point(lbActualLayer.Location.X, Math.Max(1, Math.Min(tbLayer.Height - 40, - (int)(tbLayer.Height - tbLayer.Value * ((float)tbLayer.Height / tbLayer.Maximum)) - lbLayerActual.Height / 2) + (int)(tbLayer.Height - tbLayer.Value * ((float)tbLayer.Height / tbLayer.Maximum)) - lbActualLayer.Height / 2) )); } @@ -1294,11 +1296,11 @@ namespace UVtools.GUI DisableGUI(); FrmLoading.SetDescription($"Converting {Path.GetFileName(SlicerFile.FileFullPath)} to {Path.GetExtension(dialog.FileName)}"); - Task task = Task.Factory.StartNew(() => + Task task = Task.Factory.StartNew(() => { try { - SlicerFile.Convert(fileFormat, dialog.FileName, FrmLoading.RestartProgress()); + return SlicerFile.Convert(fileFormat, dialog.FileName, FrmLoading.RestartProgress()); } catch (OperationCanceledException) { @@ -1315,9 +1317,11 @@ namespace UVtools.GUI EnableGUI(true); }); } + + return false; }); - if (FrmLoading.ShowDialog() == DialogResult.OK) + if (FrmLoading.ShowDialog() == DialogResult.OK && task.Result) { if (MessageBox.Show($"Convertion is completed: {Path.GetFileName(dialog.FileName)} in {FrmLoading.StopWatch.ElapsedMilliseconds / 1000}s\n" + "Do you want open the converted file in a new window?", @@ -1486,7 +1490,7 @@ namespace UVtools.GUI UpdateIssuesInfo(); lbMaxLayer.Text = - lbLayerActual.Text = + lbActualLayer.Text = lbInitialLayer.Text = "???"; lvProperties.BeginUpdate(); lvProperties.Items.Clear(); @@ -1902,6 +1906,8 @@ namespace UVtools.GUI btnLastLayer.Enabled = btnNextLayer.Enabled = layerNum < SlicerFile.LayerCount - 1; btnFirstLayer.Enabled = btnPreviousLayer.Enabled = layerNum > 0; + var layer = SlicerFile[ActualLayer]; + try { // OLD @@ -2123,17 +2129,17 @@ namespace UVtools.GUI watch.Stop(); tsLayerPreviewTime.Text = $"{watch.ElapsedMilliseconds}ms"; //lbLayers.Text = $"{SlicerFile.GetHeightFromLayer(layerNum)} / {SlicerFile.TotalHeight}mm\n{layerNum} / {SlicerFile.LayerCount-1}\n{percent}%"; - lbLayerActual.Text = $"{SlicerFile.GetHeightFromLayer(ActualLayer)}mm\n{ActualLayer}\n{percent}%"; - lbLayerActual.Location = new Point(lbLayerActual.Location.X, + lbActualLayer.Text = $"{layer.PositionZ}mm\n{ActualLayer}\n{percent}%"; + lbActualLayer.Location = new Point(lbActualLayer.Location.X, Math.Max(1, - Math.Min(tbLayer.Height- lbLayerActual.Height, - (int)(tbLayer.Height - tbLayer.Value * ((float)tbLayer.Height / tbLayer.Maximum)) - lbLayerActual.Height/2) + Math.Min(tbLayer.Height- lbActualLayer.Height, + (int)(tbLayer.Height - tbLayer.Value * ((float)tbLayer.Height / tbLayer.Maximum)) - lbActualLayer.Height/2) )); pbLayers.Value = percent; - lbLayerActual.Invalidate(); - lbLayerActual.Update(); - lbLayerActual.Refresh(); + lbActualLayer.Invalidate(); + lbActualLayer.Update(); + lbActualLayer.Refresh(); pbLayer.Invalidate(); pbLayer.Update(); pbLayer.Refresh(); diff --git a/UVtools.GUI/Program.cs b/UVtools.GUI/Program.cs index 675c1fa..45ebbf6 100644 --- a/UVtools.GUI/Program.cs +++ b/UVtools.GUI/Program.cs @@ -14,6 +14,7 @@ using System.Windows.Forms; using ApplicationManagement; using Emgu.CV; using UVtools.Core; +using UVtools.Core.FileFormats; using UVtools.GUI.Forms; namespace UVtools.GUI diff --git a/UVtools.GUI/Properties/AssemblyInfo.cs b/UVtools.GUI/Properties/AssemblyInfo.cs index 2fea0eb..1998be3 100644 --- a/UVtools.GUI/Properties/AssemblyInfo.cs +++ b/UVtools.GUI/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.6.1.0")] -[assembly: AssemblyFileVersion("0.6.1.0")] +[assembly: AssemblyVersion("0.6.1.1")] +[assembly: AssemblyFileVersion("0.6.1.1")] -- cgit v1.2.3