From 82f29a7928a21cee772d97ba558230b88faee371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Concei=C3=A7=C3=A3o?= Date: Wed, 27 May 2020 05:07:42 +0100 Subject: [PATCH] v0.4 * (Add) CWS file format * (Add) Nova3D Elfin printer * (Add) Zoom and pan functions to layer image * (Add) Pixel editor to add or remove pixels * (Add) Outline layer showing only borders * (Add) Image mutators, Erode, Dilate, PyrDownUp, Smooth * (Add) Task to save operation * (Add) Printers can be installed from GUI Menu -> About -> Install printers into PrusaSlicer * (Improvement) Layer Management * (Improvement) Faster Save and Save As operation * (Fix) Bad layer image when converting SL1 to PHZ * (Fix) Corrected EncryptionMode for PHZ files * (Fix) Save As can change file extension * (Fix) Save As no longer reload file * (Fix) SL1 files not accepting float numbers for exposures * (Fix) SL1 files was calculating the wrong layer count when using slow layer settings * (Fix) Modifiers can't accept float values * (Fix) Sonic Mini prints mirroed * (Fix) Layer resolution shows wrong values --- CHANGELOG.md | 22 + ImportPrinters.bat | 19 + PrusaSL1Reader/About.cs | 1 + PrusaSL1Reader/CWSFile.cs | 444 +++++++++++++++ PrusaSL1Reader/ChituboxFile.cs | 27 +- .../Extensions/ZipArchiveExtensions.cs | 49 +- PrusaSL1Reader/FileFormat.cs | 127 +++-- PrusaSL1Reader/Helpers.cs | 48 ++ PrusaSL1Reader/IFileFormat.cs | 42 +- PrusaSL1Reader/LayerManager.cs | 325 +++++++++++ PrusaSL1Reader/PHZFile.cs | 78 ++- PrusaSL1Reader/PrusaSL1Reader.csproj | 6 +- PrusaSL1Reader/SL1File.cs | 250 +++++---- PrusaSL1Reader/UniversalLayer.cs | 92 ---- PrusaSL1Reader/ZCodexFile.cs | 73 +-- PrusaSL1Viewer/EmguExtensions.cs | 24 + PrusaSL1Viewer/FrmInputBox.Designer.cs | 27 +- PrusaSL1Viewer/FrmMain.Designer.cs | 221 ++++++-- PrusaSL1Viewer/FrmMain.cs | 376 ++++++++++++- PrusaSL1Viewer/FrmMain.resx | 44 +- PrusaSL1Viewer/ImageSharpExtensions.cs | 12 + PrusaSL1Viewer/Images/CNCMachine-16x16.png | Bin 0 -> 375 bytes PrusaSL1Viewer/Images/Geometry-16x16.png | Bin 0 -> 360 bytes PrusaSL1Viewer/Images/pixel-16x16.png | Bin 0 -> 591 bytes PrusaSL1Viewer/Images/search-16x16.png | Bin 0 -> 50607 bytes PrusaSL1Viewer/License-LGPL.txt | 506 ++++++++++++++++++ PrusaSL1Viewer/Properties/AssemblyInfo.cs | 4 +- .../Properties/Resources.Designer.cs | 40 ++ PrusaSL1Viewer/Properties/Resources.resx | 24 +- PrusaSL1Viewer/PrusaSL1Viewer.csproj | 30 +- PrusaSL1Viewer/UniversalLayerExtensions.cs | 28 - PrusaSL1Viewer/packages.config | 8 +- PrusaSlicer/printer/Nova3D Elfin.ini | 37 ++ PrusaSlicer/printer/Phrozen Sonic Mini.ini | 4 +- README.md | 13 +- 35 files changed, 2405 insertions(+), 596 deletions(-) create mode 100644 ImportPrinters.bat create mode 100644 PrusaSL1Reader/CWSFile.cs create mode 100644 PrusaSL1Reader/LayerManager.cs delete mode 100644 PrusaSL1Reader/UniversalLayer.cs create mode 100644 PrusaSL1Viewer/EmguExtensions.cs create mode 100644 PrusaSL1Viewer/Images/CNCMachine-16x16.png create mode 100644 PrusaSL1Viewer/Images/Geometry-16x16.png create mode 100644 PrusaSL1Viewer/Images/pixel-16x16.png create mode 100644 PrusaSL1Viewer/Images/search-16x16.png create mode 100644 PrusaSL1Viewer/License-LGPL.txt delete mode 100644 PrusaSL1Viewer/UniversalLayerExtensions.cs create mode 100644 PrusaSlicer/printer/Nova3D Elfin.ini diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d41e23..37ef90a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## 27/05/2020 - v0.4 - Beta + +* (Add) CWS file format +* (Add) Nova3D Elfin printer +* (Add) Zoom and pan functions to layer image +* (Add) Pixel editor to add or remove pixels +* (Add) Outline layer showing only borders +* (Add) Image mutators, Erode, Dilate, PyrDownUp, Smooth +* (Add) Task to save operation +* (Add) Printers can be installed from GUI Menu -> About -> Install printers into PrusaSlicer +* (Improvement) Layer Management +* (Improvement) Faster Save and Save As operation +* (Fix) Bad layer image when converting SL1 to PHZ +* (Fix) Corrected EncryptionMode for PHZ files +* (Fix) Save As can change file extension +* (Fix) Save As no longer reload file +* (Fix) SL1 files not accepting float numbers for exposures +* (Fix) SL1 files was calculating the wrong layer count when using slow layer settings +* (Fix) Modifiers can't accept float values +* (Fix) Sonic Mini prints mirroed +* (Fix) Layer resolution shows wrong values + ## 21/05/2020 - v0.3.3.1 - Beta * (Fix) Unable to convert Chitubox or PHZ files when enconter repeated layer images diff --git a/ImportPrinters.bat b/ImportPrinters.bat new file mode 100644 index 00000000..e44eacce --- /dev/null +++ b/ImportPrinters.bat @@ -0,0 +1,19 @@ +@echo off +SET DIR=%~dp0 +SET INPUT_DIR=%AppData%\PrusaSlicer\printer +SET OUTPUT_DIR=%~dp0PrusaSlicer\printer + +SET files[0]=EPAX X1.ini +SET files[1]=Phrozen Sonic Mini.ini +SET files[2]=Zortrax Inkspire.ini +SET files[3]=Nova3D Elfin.ini + +echo PrusaSlicer Printers Instalation +echo This will replace printers, all changes will be discarded +echo %INPUT_DIR% +echo %OUTPUT_DIR% + +for /F "tokens=2 delims==" %%s in ('set files[') do xcopy /y "%INPUT_DIR%\%%s" "%OUTPUT_DIR%" + +REM xcopy /i /e /y %INPUT_DIR% %OUTPUT_DIR% +pause \ No newline at end of file diff --git a/PrusaSL1Reader/About.cs b/PrusaSL1Reader/About.cs index f4bdc127..ad6592d0 100644 --- a/PrusaSL1Reader/About.cs +++ b/PrusaSL1Reader/About.cs @@ -10,6 +10,7 @@ namespace PrusaSL1Reader { public static class About { + public static string Software = "PrusaSL1Viewer"; public static string Author = "Tiago Conceição"; public static string Company = "PTRTECH"; public static string Website = "https://github.com/sn4k3/PrusaSL1Viewer"; diff --git a/PrusaSL1Reader/CWSFile.cs b/PrusaSL1Reader/CWSFile.cs new file mode 100644 index 00000000..1897f01b --- /dev/null +++ b/PrusaSL1Reader/CWSFile.cs @@ -0,0 +1,444 @@ +/* + * 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 PrusaSL1Reader.Extensions; + +namespace PrusaSL1Reader +{ + public class CWSFile : FileFormat + { + #region Constants + + public const string GCodeStart = "G28{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 = "{0}M18 ;Disable Motors{0}" + + "M106 SO{0}" + + "G1 Z{1}{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"; + } + + 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", "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, + }; + + 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 float LayerHeight => SliceSettings.Thickness; + + public override uint LayerCount => SliceSettings.LayersNum; + + 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) + { + base.Encode(fileFullPath); + using (ZipArchive 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)}"); + } + } + + + for(uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + LayerManager.Layer layer = this[layerIndex]; + var layerimagePath = $"{Path.GetFileNameWithoutExtension(fileFullPath)}{layer.Index:D4}.png"; + outputFile.PutFileContent(layerimagePath, layer.RawData); + } + + UpdateGCode(); + outputFile.PutFileContent($"{Path.GetFileNameWithoutExtension(fileFullPath)}.gcode", GCode.ToString()); + } + } + + public override void Decode(string fileFullPath) + { + base.Decode(fileFullPath); + + 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(LayerCount); + + foreach (var zipArchiveEntry in inputFile.Entries) + { + if (!zipArchiveEntry.Name.EndsWith(".png")) continue; + + // - .png - 4 numbers + string layerStr = zipArchiveEntry.Name.Substring(zipArchiveEntry.Name.Length - 4 - 4, 4); + uint iLayer = uint.Parse(layerStr); + LayerManager[iLayer] = new LayerManager.Layer(iLayer, zipArchiveEntry.Open(), zipArchiveEntry.Name); + } + + } + } + + 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; + } + + return false; + } + + public override void SaveAs(string filePath = 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()); + + foreach (var layer in this) + { + if (!layer.IsModified) continue; + outputFile.PutFileContent(layer.Filename, layer.RawData); + layer.IsModified = false; + } + } + + //Decode(FileFullPath); + } + + public override bool Convert(Type to, string fileFullPath) + { + 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.AppendFormat($"{0}{GCodeStart}", Environment.NewLine); + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + LayerManager.Layer layer = this[layerIndex]; + GCode.AppendLine($"{GCodeKeywordSlice} {layerIndex}"); + GCode.AppendLine("M106 S255"); + GCode.AppendLine($"{GCodeKeywordDelay} " + (layerIndex < InitialLayerCount ? 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} " + (layerIndex < InitialLayerCount ? 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/PrusaSL1Reader/ChituboxFile.cs b/PrusaSL1Reader/ChituboxFile.cs index 0e853b01..f671fe34 100644 --- a/PrusaSL1Reader/ChituboxFile.cs +++ b/PrusaSL1Reader/ChituboxFile.cs @@ -719,7 +719,7 @@ void rleRGB15() { Layer layer = new Layer(); Layer layerHash = null; - var image = GetLayerImage(layerIndex); + var image = this[layerIndex].Image; rawData = IsCbtFile ? EncodeCbtImage(image, layerIndex) : EncodeCbddlpImage(image); var byteArr = rawData.ToArray(); @@ -1014,15 +1014,11 @@ public override void Decode(string fileFullPath) } } - Layers = new byte[LayerCount][]; + LayerManager = new LayerManager(LayerCount); Parallel.For(0, LayerCount, layerIndex => { var image = IsCbtFile ? DecodeCbtImage((uint) layerIndex) : DecodeCbddlpImage((uint) layerIndex); - using (var ms = new MemoryStream()) - { - image.Save(ms, Helpers.PngEncoder); - Layers[layerIndex] = CompressLayer(ms.ToArray()); - } + this[layerIndex] = new LayerManager.Layer((uint)layerIndex, image); }); /*byte[,][] rleArr = new byte[LayerCount, HeaderSettings.AntiAliasLevel][]; @@ -1361,6 +1357,17 @@ void UpdateLayers() public override void SaveAs(string filePath = null) { + if (LayerManager.IsModified) + { + if (!string.IsNullOrEmpty(filePath)) + { + FileFullPath = filePath; + } + Encode(FileFullPath); + return; + } + + if (!string.IsNullOrEmpty(filePath)) { File.Copy(FileFullPath, filePath, true); @@ -1393,7 +1400,7 @@ public override void SaveAs(string filePath = null) outputFile.Close(); } - Decode(FileFullPath); + //Decode(FileFullPath); } public override bool Convert(Type to, string fileFullPath) @@ -1402,7 +1409,7 @@ public override bool Convert(Type to, string fileFullPath) { PHZFile file = new PHZFile { - Layers = Layers + LayerManager = LayerManager }; @@ -1513,7 +1520,7 @@ public override bool Convert(Type to, string fileFullPath) } }, }, - Layers = Layers + LayerManager = LayerManager }; float usedMaterial = UsedMaterial / LayerCount; diff --git a/PrusaSL1Reader/Extensions/ZipArchiveExtensions.cs b/PrusaSL1Reader/Extensions/ZipArchiveExtensions.cs index 9363569b..6f7b8743 100644 --- a/PrusaSL1Reader/Extensions/ZipArchiveExtensions.cs +++ b/PrusaSL1Reader/Extensions/ZipArchiveExtensions.cs @@ -280,41 +280,54 @@ public static void AddToArchive(string archiveFullName, } /// - /// Create a file into archive and write content to it + /// Get or put a file into archive + /// + /// + /// Filename to create + /// Created + public static ZipArchiveEntry GetPutFile(this ZipArchive input, string filename) + { + return input.GetEntry(filename) ?? input.CreateEntry(filename); + } + + /// + /// Create or update a file into archive and write content to it /// /// /// Filename to create /// Content to write /// Created - public static ZipArchiveEntry PutFileContent(this ZipArchive input, string filename, string content, bool deleteFirst = true) + public static ZipArchiveEntry PutFileContent(this ZipArchive input, string filename, string content) { - if(deleteFirst) input.GetEntry(filename)?.Delete(); + ZipArchiveEntry entry = input.GetEntry(filename) ?? input.CreateEntry(filename); - var entry = input.CreateEntry(filename); - if (ReferenceEquals(content, null)) return entry; - using (TextWriter tw = new StreamWriter(entry.Open())) + if (string.IsNullOrEmpty(content)) return entry; + Stream stream = entry.Open(); + stream.SetLength(0); + using (TextWriter tw = new StreamWriter(stream)) { tw.Write(content); tw.Close(); } - return entry; } - public static ZipArchiveEntry PutFileContent(this ZipArchive input, string filename, Stream content, bool deleteFirst = true) + /// + /// Create or update a file into archive and write content to it + /// + /// + /// Filename to create + /// Content to write + /// Created + public static ZipArchiveEntry PutFileContent(this ZipArchive input, string filename, byte[] content) { - if (deleteFirst) input.GetEntry(filename)?.Delete(); - var entry = input.CreateEntry(filename); - if (ReferenceEquals(content, null)) return entry; - using (StreamWriter tw = new StreamWriter(entry.Open())) - { - tw.Write(content); - tw.Close(); - } + ZipArchiveEntry entry = input.GetEntry(filename) ?? input.CreateEntry(filename); + if (ReferenceEquals(content, null)) return entry; + Stream stream = entry.Open(); + stream.SetLength(0); + stream.Write(content, 0, content.Length); return entry; } - - } } diff --git a/PrusaSL1Reader/FileFormat.cs b/PrusaSL1Reader/FileFormat.cs index 150d5299..432447da 100644 --- a/PrusaSL1Reader/FileFormat.cs +++ b/PrusaSL1Reader/FileFormat.cs @@ -6,8 +6,11 @@ * of this license document, but changing it is not allowed. */ using System; +using System.Collections; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using PrusaSL1Reader.Extensions; using SixLabors.ImageSharp; @@ -20,7 +23,7 @@ namespace PrusaSL1Reader /// /// Slicer representation /// - public abstract class FileFormat : IFileFormat, IDisposable, IEquatable + public abstract class FileFormat : IFileFormat, IDisposable, IEquatable, IEnumerable { #region Enums @@ -130,6 +133,7 @@ public override string ToString() new ChituboxFile(), // cbddlp, cbt, photon new PHZFile(), // phz new ZCodexFile(), // zcodex + new CWSFile(), // CWS }; /// @@ -250,8 +254,12 @@ public byte CreatedThumbnailsCount { public abstract Size[] ThumbnailsOriginalSize { get; } public Image[] Thumbnails { get; set; } - - public byte[][] Layers { get; set; } + public LayerManager LayerManager { get; set; } + + /// + /// Gets if any layer got modified + /// + public bool ModifiedLayers => LayerManager.IsModified; public abstract uint ResolutionX { get; } @@ -285,7 +293,7 @@ public byte CreatedThumbnailsCount { public abstract string MachineName { get; } - public virtual string GCode { get; set; } + public StringBuilder GCode { get; set; } public abstract object[] Configs { get; } @@ -299,6 +307,38 @@ protected FileFormat() } #endregion + #region Indexers + public LayerManager.Layer this[int index] + { + get => LayerManager[index]; + set => LayerManager[index] = value; + } + + public LayerManager.Layer this[uint index] + { + get => LayerManager[index]; + set => LayerManager[index] = value; + } + + public LayerManager.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) { @@ -328,7 +368,9 @@ public void Dispose() public virtual void Clear() { FileFullPath = null; - Layers = null; + LayerManager = null; + GCode = null; + if (!ReferenceEquals(Thumbnails, null)) { for (int i = 0; i < ThumbnailsCount; i++) @@ -336,6 +378,8 @@ public virtual void Clear() Thumbnails[i]?.Dispose(); } } + + } public void FileValidation(string fileFullPath) @@ -354,7 +398,7 @@ public void FileValidation(string 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)); + return FileExtensions.Any(fileExtension => fileExtension.Extension.Equals(extension, StringComparison.InvariantCultureIgnoreCase)); } public string GetFileExtensions(string prepend = ".", string separator = ", ") @@ -399,41 +443,7 @@ public void SetThumbnails(Image image) Thumbnails[i] = image.Clone(); } } - - public byte[] CompressLayer(Stream input) - { - return CompressLayer(input.ToArray()); - } - - public 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(); - }*/ - } - - public 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(); - } - }*/ - } - + public virtual void Encode(string fileFullPath) { if (File.Exists(fileFullPath)) @@ -528,14 +538,23 @@ public virtual void Extract(string path, bool genericConfigExtract = true, bool i++; } - Parallel.For(0, LayerCount, layerIndex => { - var byteArr = GetLayer((uint) layerIndex); - using (FileStream stream = File.Create(Path.Combine(path, $"Layer{layerIndex}.png"), byteArr.Length)) - { - stream.Write(byteArr, 0, byteArr.Length); - stream.Close(); - } - }); + Parallel.ForEach(this, (layer) => + { + var byteArr = layer.RawData; + using (FileStream stream = File.Create(Path.Combine(path, $"Layer{layer.Index}.png"), byteArr.Length)) + { + stream.Write(byteArr, 0, byteArr.Length); + stream.Close(); + } + }); + /* 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); @@ -548,16 +567,6 @@ public virtual void Extract(string path, bool genericConfigExtract = true, bool } } - public virtual byte[] GetLayer(uint layerIndex) - { - return layerIndex >= LayerCount ? null : DecompressLayer(Layers[layerIndex]); - } - - public virtual Image GetLayerImage(uint layerIndex) - { - return layerIndex >= LayerCount ? null : Image.Load(GetLayer(layerIndex)); - } - public virtual float GetHeightFromLayer(uint layerNum) { return (float)Math.Round(layerNum * LayerHeight, 2); diff --git a/PrusaSL1Reader/Helpers.cs b/PrusaSL1Reader/Helpers.cs index 4b8f95cd..416f1c59 100644 --- a/PrusaSL1Reader/Helpers.cs +++ b/PrusaSL1Reader/Helpers.cs @@ -7,10 +7,15 @@ */ using System; +using System.Globalization; using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; using System.Security.Cryptography; using BinarySerialization; using Newtonsoft.Json; +using PrusaSL1Reader.Extensions; +using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; @@ -33,6 +38,12 @@ public static class Helpers /// Gets a white color of /// public static L8 L8White { get; } = new L8(255); + public static L8 L8Black { get; } = new L8(0); + + public static byte[] ImageL8ToBytes(Image image) + { + return image.TryGetSinglePixelSpan(out var pixelSpan) ? MemoryMarshal.AsBytes(pixelSpan).ToArray() : null; + } /*public static T ByteToType(BinaryReader reader) { @@ -105,5 +116,42 @@ public static string ComputeSHA1Hash(byte[] input) { return Convert.ToBase64String(SHA1.ComputeHash(input)); } + + public static bool SetPropertyValue(PropertyInfo attribute, object obj, string value) + { + var name = attribute.PropertyType.Name.ToLower(); + switch (name) + { + case "string": + attribute.SetValue(obj, value.Convert()); + return true; + case "boolean": + if(char.IsDigit(value[0])) + attribute.SetValue(obj, !value.Equals(0)); + else + attribute.SetValue(obj, value.Equals("True", StringComparison.InvariantCultureIgnoreCase)); + return true; + case "byte": + attribute.SetValue(obj, value.Convert()); + return true; + case "uint16": + attribute.SetValue(obj, value.Convert()); + return true; + case "uint32": + attribute.SetValue(obj, value.Convert()); + return true; + case "single": + attribute.SetValue(obj, (float)Math.Round(float.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 3)); + return true; + case "double": + attribute.SetValue(obj, Math.Round(double.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 3)); + return true; + case "decimal": + attribute.SetValue(obj, Math.Round(decimal.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 3)); + return true; + default: + throw new Exception($"Data type '{name}' not recognized, contact developer."); + } + } } } diff --git a/PrusaSL1Reader/IFileFormat.cs b/PrusaSL1Reader/IFileFormat.cs index 62303a39..1bd8c80a 100644 --- a/PrusaSL1Reader/IFileFormat.cs +++ b/PrusaSL1Reader/IFileFormat.cs @@ -9,6 +9,7 @@ using System; using System.Drawing; using System.IO; +using System.Text; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; @@ -79,8 +80,8 @@ public interface IFileFormat /// /// Gets the cached layers into compressed bytes /// - byte[][] Layers { get; set; } - + LayerManager LayerManager { get; set; } + /// /// Gets the image width resolution /// @@ -165,7 +166,7 @@ public interface IFileFormat /// /// Gets the GCode, returns null if not supported /// - string GCode { get; set; } + StringBuilder GCode { get; set; } /// /// Get all configuration objects with properties and values @@ -224,27 +225,6 @@ public interface IFileFormat /// Image to set void SetThumbnails(Image images); - /// - /// Compress a layer from a - /// - /// to compress - /// Compressed byte array - byte[] CompressLayer(Stream input); - - /// - /// Compress a layer from a byte array - /// - /// byte array to compress - /// Compressed byte array - byte[] CompressLayer(byte[] input); - - /// - /// Decompress a layer from a byte array - /// - /// byte array to decompress - /// Decompressed byte array - byte[] DecompressLayer(byte[] input); - /// /// Encode to an output file /// @@ -284,20 +264,6 @@ public interface IFileFormat /// void Extract(string path, bool genericConfigExtract = true, bool genericLayersExtract = true); - /// - /// Gets a byte array from layer - /// - /// The layer index - /// Returns a byte array - byte[] GetLayer(uint layerIndex); - - /// - /// Gets a image from layer - /// - /// The layer index - /// Returns a image - Image GetLayerImage(uint layerIndex); - /// /// Get height in mm from layer height /// diff --git a/PrusaSL1Reader/LayerManager.cs b/PrusaSL1Reader/LayerManager.cs new file mode 100644 index 00000000..be858552 --- /dev/null +++ b/PrusaSL1Reader/LayerManager.cs @@ -0,0 +1,325 @@ +/* + * 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.IO; +using PrusaSL1Reader.Extensions; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace PrusaSL1Reader +{ + public class LayerManager : IEnumerable + { + #region Layer Class + /// + /// Represent a Layer + /// + public class Layer : IEquatable, IEquatable + { + #region Properties + /// + /// Gets the layer index + /// + public uint Index { get; } + + private byte[] _rawData; + /// + /// Gets or sets layer image compressed data + /// + public byte[] RawData + { + get => DecompressLayer(_rawData); + set { _rawData = CompressLayer(value); IsModified = true; } + } + + /// + /// 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 Image Image + { + get => SixLabors.ImageSharp.Image.Load(RawData); + set + { + using (MemoryStream stream = new MemoryStream()) + { + value.Save(stream, Helpers.PngEncoder); + RawData = stream.ToArray(); + } + } + } + + #endregion + + #region Constructor + public Layer(uint index, byte[] rawData, string filename = null) + { + Index = index; + RawData = rawData; + Filename = filename ?? $"Layer{index}.png"; + IsModified = false; + } + + public Layer(uint index, Image image, string filename = null) : this(index, new byte[0], filename) + { + Image = image; + IsModified = false; + } + + + public Layer(uint index, Stream stream, string filename = null) : this(index, stream.ToArray(), filename) + { } + #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(_rawData, other._rawData); + } + + 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 (_rawData != null ? _rawData.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(Filename)}: {Filename}, {nameof(IsModified)}: {IsModified}"; + } + #endregion + + #region Methods + public Layer Clone() + { + return new Layer(Index, RawData, Filename); + } + #endregion + } + #endregion + + #region Properties + /// + /// Layers List + /// + public Layer[] Layers { get; } + + /// + /// 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 => Layers[index] = value; + } + public Layer this[int index] + { + get => Layers[index]; + set => Layers[index] = value; + } + public Layer this[long index] + { + get => Layers[index]; + set => Layers[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 + /// + /// 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/PrusaSL1Reader/PHZFile.cs b/PrusaSL1Reader/PHZFile.cs index c1596942..5d7371e1 100644 --- a/PrusaSL1Reader/PHZFile.cs +++ b/PrusaSL1Reader/PHZFile.cs @@ -246,7 +246,7 @@ public class Header /// Gets the parameter used to control encryption. /// Not totally understood. 0 for cbddlp files, 0xF for ctb files. /// - [FieldOrder(45)] public uint EncryptionMode { get; set; } = 0xF; + [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. @@ -506,7 +506,13 @@ public override void Encode(string fileFullPath) base.Encode(fileFullPath); 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 Layer[HeaderSettings.LayerCount, HeaderSettings.AntiAliasLevel]; using (var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write)) @@ -630,7 +636,7 @@ void rleRGB15() { Layer layer = new Layer(); Layer layerHash = null; - var image = GetLayerImage(layerIndex); + var image = this[layerIndex].Image; rawData = EncodePhzImage(image, layerIndex); var byteArr = rawData.ToArray(); @@ -708,7 +714,7 @@ void AddRep() var pixelRowSpan = image.GetPixelRowSpan(y); for (int x = 0; x < image.Width; x++) { - var grey7 = (byte)((pixelRowSpan[x].PackedValue >> 1) & 0x7c); + var grey7 = (byte)((pixelRowSpan[x].PackedValue >> 1) & 0x7f); if (color == byte.MaxValue) { @@ -844,46 +850,12 @@ public override void Decode(string fileFullPath) } } - Layers = new byte[LayerCount][]; + LayerManager = new LayerManager(LayerCount); Parallel.For(0, LayerCount, layerIndex => { var image = DecodePhzImage((uint) layerIndex); - using (var ms = new MemoryStream()) - { - image.Save(ms, Helpers.PngEncoder); - Layers[layerIndex] = CompressLayer(ms.ToArray()); - } + this[layerIndex] = new LayerManager.Layer((uint) layerIndex, image); }); - - /*byte[,][] rleArr = new byte[LayerCount, HeaderSettings.AntiAliasLevel][]; - for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - //byte[][] rleArr = new byte[HeaderSettings.AntiAliasLevel][]; - for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) - { - Layer layer = LayersDefinitions[layerIndex, aaIndex]; - inputFile.Seek(layer.DataAddress, SeekOrigin.Begin); - rleArr[layerIndex, aaIndex] = new byte[(int)layer.DataSize]; - inputFile.Read(rleArr[layerIndex, aaIndex], 0, (int)layer.DataSize); - } - - var image = IsCbtFile ? DecodeCbtImage(rleArr[0], layerIndex) : DecodeCbddlpImage(rleArr, layerIndex); - using (var ms = new MemoryStream()) - { - image.Save(ms, Helpers.PngEncoder); - Layers[layerIndex] = ms.ToArray(); - } - //} - - /*for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) - { - var image = IsCbtFile ? DecodeCbtImage(layerIndex) : DecodeCbddlpImage(layerIndex); - using (var ms = new MemoryStream()) - { - image.Save(ms, Helpers.BmpEncoder); - Layers[layerIndex] = CompressLayer(ms.ToArray()); - } - }*/ } private Image DecodePhzImage(uint layerIndex) @@ -903,13 +875,13 @@ private Image DecodePhzImage(uint layerIndex) image.TryGetSinglePixelSpan(out var span); - for (var n = 0; n < rawImageData.Length; n++) + foreach (var code in rawImageData) { - byte code = rawImageData[n]; - - if ((code & 0x80) != 0) + if ((code & 0x80) == 0x80) { - lastColor = (byte) (code << 1); + //lastColor = (byte) (code << 1); + // // Convert from 7bpp to 8bpp (extending the last bit) + lastColor = (byte) (((code & 0x7f) << 1) | (code & 1)); if (index < limit) { span[index].PackedValue = lastColor; @@ -1052,6 +1024,16 @@ void UpdateLayers() public override void SaveAs(string filePath = null) { + if (LayerManager.IsModified) + { + if (!string.IsNullOrEmpty(filePath)) + { + FileFullPath = filePath; + } + Encode(FileFullPath); + return; + } + if (!string.IsNullOrEmpty(filePath)) { File.Copy(FileFullPath, filePath, true); @@ -1084,7 +1066,7 @@ public override void SaveAs(string filePath = null) outputFile.Close(); } - Decode(FileFullPath); + //Decode(FileFullPath); } public override bool Convert(Type to, string fileFullPath) @@ -1093,7 +1075,7 @@ public override bool Convert(Type to, string fileFullPath) { ChituboxFile file = new ChituboxFile { - Layers = Layers + LayerManager = LayerManager }; @@ -1204,7 +1186,7 @@ public override bool Convert(Type to, string fileFullPath) } }, }, - Layers = Layers + LayerManager = LayerManager }; float usedMaterial = UsedMaterial / LayerCount; diff --git a/PrusaSL1Reader/PrusaSL1Reader.csproj b/PrusaSL1Reader/PrusaSL1Reader.csproj index b4910848..92d9c60c 100644 --- a/PrusaSL1Reader/PrusaSL1Reader.csproj +++ b/PrusaSL1Reader/PrusaSL1Reader.csproj @@ -7,9 +7,9 @@ https://github.com/sn4k3/PrusaSL1Viewer https://github.com/sn4k3/PrusaSL1Viewer - 0.3.3.1 - 0.3.3.0 - 0.3.3.1 + 0.4.0.0 + 0.4.4.0 + 0.4 Open, view, edit, extract and convert DLP/SLA files generated from Slicers diff --git a/PrusaSL1Reader/SL1File.cs b/PrusaSL1Reader/SL1File.cs index deff50e2..b41823db 100644 --- a/PrusaSL1Reader/SL1File.cs +++ b/PrusaSL1Reader/SL1File.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; @@ -110,8 +109,8 @@ public class Material #region Exposure - public byte ExposureTime { get; set; } - public ushort InitialExposureTime { get; set; } + public float ExposureTime { get; set; } + public float InitialExposureTime { get; set; } #endregion #region Corrections @@ -230,8 +229,8 @@ public class OutputConfig { public string Action { get; set; } public string JobDir { get; set; } - public byte ExpTime { get; set; } - public ushort ExpTimeFirst { 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; } @@ -257,8 +256,6 @@ public override string ToString() #endregion #region Properties - public ZipArchive InputFile { get; private set; } - public Printer PrinterSettings { get; private set; } public Material MaterialSettings { get; private set; } @@ -281,6 +278,7 @@ public override string ToString() typeof(ChituboxFile), typeof(PHZFile), typeof(ZCodexFile), + typeof(CWSFile), }; public override PrintParameterModifier[] PrintParameterModifiers { get; } = { @@ -300,7 +298,7 @@ public override string ToString() public override float LayerHeight => OutputConfigSettings.LayerHeight; - public override uint LayerCount => OutputConfigSettings.NumFast; + public override uint LayerCount => (uint) (OutputConfigSettings.NumFast + OutputConfigSettings.NumSlow); public override ushort InitialLayerCount => OutputConfigSettings.NumFade; @@ -368,43 +366,13 @@ public static string MemberNameToIniKey(string memberName) return iniKey; } - public static bool SetValue(PropertyInfo attribute, object obj, string value) - { - var name = attribute.PropertyType.Name.ToLower(); - switch (name) - { - case "string": - attribute.SetValue(obj, value.Convert()); - return true; - case "boolean": - attribute.SetValue(obj, !value.Equals(0)); - return true; - case "byte": - attribute.SetValue(obj, value.Convert()); - return true; - case "uint16": - attribute.SetValue(obj, value.Convert()); - return true; - case "single": - attribute.SetValue(obj, (float)Math.Round(float.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 2)); - return true; - case "double": - attribute.SetValue(obj, Math.Round(double.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 2)); - return true; - case "decimal": - attribute.SetValue(obj, (decimal)Math.Round(decimal.Parse(value, CultureInfo.InvariantCulture.NumberFormat), 2)); - return true; - default: - throw new Exception($"Data type '{name}' not recognized, contact developer."); - } - } + #endregion #region Methods public override void Clear() { base.Clear(); - InputFile?.Dispose(); Statistics.Clear(); } @@ -427,97 +395,71 @@ public override void Decode(string fileFullPath) Statistics.ExecutionTime.Restart(); - InputFile = ZipFile.OpenRead(FileFullPath); - - foreach (ZipArchiveEntry entity in InputFile.Entries) + using (var inputFile = ZipFile.OpenRead(FileFullPath)) { - if (!entity.Name.EndsWith(".ini")) continue; - using (StreamReader streamReader = new StreamReader(entity.Open())) + + foreach (ZipArchiveEntry entity in inputFile.Entries) { - string line = null; - while ((line = streamReader.ReadLine()) != null) + if (!entity.Name.EndsWith(".ini")) continue; + using (StreamReader streamReader = new StreamReader(entity.Open())) { - string[] keyValue = line.Split(new[] {'='}, 2); - if (keyValue.Length < 2) continue; - keyValue[0] = keyValue[0].Trim(); - keyValue[1] = keyValue[1].Trim(); + 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; + var fieldName = IniKeyToMemberName(keyValue[0]); + bool foundMember = false; - foreach (var obj in Configs) - { - var attribute = obj.GetType().GetProperty(fieldName); - if (ReferenceEquals(attribute, null)) continue; - SetValue(attribute, obj, keyValue[1]); - Statistics.ImplementedKeys.Add(keyValue[0]); - foundMember = true; - } + 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]); + if (!foundMember) + { + Statistics.MissingKeys.Add(keyValue[0]); + } } } } - } - - Layers = new byte[LayerCount][]; - uint iLayer = 0; + LayerManager = new LayerManager(LayerCount); - /*Parallel.ForEach(InputFile.Entries, (entity) => - { - if (!entity.Name.EndsWith(".png")) return; - if (entity.Name.StartsWith("thumbnail")) + foreach (ZipArchiveEntry entity in inputFile.Entries) { - using (Stream stream = entity.Open()) + if (!entity.Name.EndsWith(".png")) continue; + if (entity.Name.StartsWith("thumbnail")) { - var image = Image.Load(stream); - byte thumbnailIndex = - (byte)(image.Width == ThumbnailsOriginalSize[(int)FileThumbnailSize.Small].Width && - image.Height == ThumbnailsOriginalSize[(int)FileThumbnailSize.Small].Height - ? FileThumbnailSize.Small - : FileThumbnailSize.Large); - Thumbnails[thumbnailIndex] = image; - stream.Close(); - } - - //thumbnailIndex++; - - return; - } + using (Stream stream = entity.Open()) + { + var image = Image.Load(stream); + byte thumbnailIndex = + (byte) (image.Width == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Width && + image.Height == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Height + ? FileThumbnailSize.Small + : FileThumbnailSize.Large); + Thumbnails[thumbnailIndex] = image; + stream.Close(); + } - string stripName = entity.Name.Remove(0, OutputConfigSettings.JobDir.Length); - stripName = stripName.Remove(stripName.Length - 4); - Debug.WriteLine(uint.Parse(stripName)); + //thumbnailIndex++; - Layers[uint.Parse(stripName)] = CompressLayer(entity.Open()); - });*/ - - foreach (ZipArchiveEntry entity in InputFile.Entries) - { - if (!entity.Name.EndsWith(".png")) continue; - if (entity.Name.StartsWith("thumbnail")) - { - using (Stream stream = entity.Open()) - { - var image = Image.Load(stream); - byte thumbnailIndex = - (byte) (image.Width == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Width && - image.Height == ThumbnailsOriginalSize[(int) FileThumbnailSize.Small].Height - ? FileThumbnailSize.Small - : FileThumbnailSize.Large); - Thumbnails[thumbnailIndex] = image; - stream.Close(); + continue; } - //thumbnailIndex++; - - continue; + // - .png - 5 numbers + string layerStr = entity.Name.Substring(entity.Name.Length - 4 - 5, 5); + uint iLayer = uint.Parse(layerStr); + LayerManager[iLayer] = new LayerManager.Layer(iLayer, entity.Open(), entity.Name); } - - Layers[iLayer++] = CompressLayer(entity.Open()); } Statistics.ExecutionTime.Stop(); @@ -547,13 +489,13 @@ public override bool SetValueFromPrintParameterModifier(PrintParameterModifier m if (ReferenceEquals(modifier, PrintParameterModifier.InitialExposureSeconds)) { MaterialSettings.InitialExposureTime = - OutputConfigSettings.ExpTimeFirst = value.Convert(); + OutputConfigSettings.ExpTimeFirst = value.Convert(); return true; } if (ReferenceEquals(modifier, PrintParameterModifier.ExposureSeconds)) { MaterialSettings.ExposureTime = - OutputConfigSettings.ExpTime = value.Convert(); + OutputConfigSettings.ExpTime = value.Convert(); return true; } @@ -562,7 +504,6 @@ public override bool SetValueFromPrintParameterModifier(PrintParameterModifier m public override void SaveAs(string filePath = null) { - InputFile.Dispose(); if (!string.IsNullOrEmpty(filePath)) { File.Copy(FileFullPath, filePath, true); @@ -570,13 +511,11 @@ public override void SaveAs(string filePath = null) } - using (InputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) + using (var outputFile = ZipFile.Open(FileFullPath, ZipArchiveMode.Update)) { //InputFile.CreateEntry("Modified"); - InputFile.GetEntry("config.ini")?.Delete(); - var entry = InputFile.CreateEntry("config.ini"); - using (TextWriter tw = new StreamWriter(entry.Open())) + using (TextWriter tw = new StreamWriter(outputFile.PutFileContent("config.ini", string.Empty).Open())) { var properties = OutputConfigSettings.GetType() .GetProperties(BindingFlags.Public | BindingFlags.Instance); @@ -590,9 +529,7 @@ public override void SaveAs(string filePath = null) tw.Close(); } - InputFile.GetEntry("prusaslicer.ini")?.Delete(); - entry = InputFile.CreateEntry("prusaslicer.ini"); - using (TextWriter tw = new StreamWriter(entry.Open())) + using (TextWriter tw = new StreamWriter(outputFile.PutFileContent("prusaslicer.ini", string.Empty).Open())) { foreach (var config in Configs) { @@ -609,6 +546,13 @@ public override void SaveAs(string filePath = null) tw.Close(); } + + foreach (var layer in this) + { + if (!layer.IsModified) continue; + outputFile.PutFileContent(layer.Filename, layer.RawData); + layer.IsModified = false; + } } Decode(FileFullPath); @@ -621,7 +565,10 @@ public override bool Convert(Type to, string fileFullPath) if (to == typeof(ChituboxFile)) { - ChituboxFile file = new ChituboxFile(); + ChituboxFile file = new ChituboxFile + { + LayerManager = LayerManager + }; file.HeaderSettings.Version = 2; @@ -657,8 +604,6 @@ public override bool Convert(Type to, string fileFullPath) file.SlicerInfoSettings.MachineName = MachineName; file.SlicerInfoSettings.MachineNameSize = (uint)MachineName.Length; - file.Layers = Layers; - if (LookupCustomValue("FLIP_XY", false, true)) { file.HeaderSettings.ResolutionX = PrinterSettings.DisplayPixelsY; @@ -675,7 +620,7 @@ public override bool Convert(Type to, string fileFullPath) { PHZFile file = new PHZFile { - Layers = Layers + LayerManager = LayerManager }; @@ -716,8 +661,8 @@ public override bool Convert(Type to, string fileFullPath) if (LookupCustomValue("FLIP_XY", false, true)) { - file.HeaderSettings.ResolutionX = ResolutionX; - file.HeaderSettings.ResolutionY = ResolutionY; + file.HeaderSettings.ResolutionX = ResolutionY; + file.HeaderSettings.ResolutionY = ResolutionX; } file.SetThumbnails(Thumbnails); @@ -794,7 +739,7 @@ public override bool Convert(Type to, string fileFullPath) } }, }, - Layers = Layers + LayerManager = LayerManager }; float usedMaterial = UsedMaterial / LayerCount; @@ -812,6 +757,53 @@ public override bool Convert(Type to, string fileFullPath) return true; } + if (to == 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("LiftDistance", file.SliceSettings.LiftDistance), 2); + file.SliceSettings.LiftUpSpeed = file.OutputSettings.ZLiftFeedRate = file.OutputSettings.ZBottomLiftFeedRate = (float) Math.Round(LookupCustomValue("LiftUpSpeed", file.SliceSettings.LiftUpSpeed), 2); + file.SliceSettings.LiftDownSpeed = file.OutputSettings.ZLiftRetractRate = (float) Math.Round(LookupCustomValue("LiftDownSpeed", 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; + + + + 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); + + return true; + } + return false; } diff --git a/PrusaSL1Reader/UniversalLayer.cs b/PrusaSL1Reader/UniversalLayer.cs deleted file mode 100644 index cf24e05a..00000000 --- a/PrusaSL1Reader/UniversalLayer.cs +++ /dev/null @@ -1,92 +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; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.PixelFormats; - -namespace PrusaSL1Reader -{ - public class UniversalLayer : List - { - /// - /// Represents a line, only white pixels - /// - public class LayerLine - { - /// - /// Gets the x start position - /// - public uint X { get; } - - /// - /// Gets the x end position - /// - public uint X2 => X + Length; - - /// - /// Gets the y position - /// - public uint Y { get; } - - /// - /// Number of pixels to fill - /// - public uint Length { get; } - - public LayerLine(uint x, uint y, uint length) - { - X = x; - Y = y; - Length = length; - } - } - - public List Lines { get; } = new List(); - - public UniversalLayer(Image image) - { - AddFromImage(image); - } - - public void AddFromImage(Image image) - { - for (int y = 0; y < image.Height; y++) - { - var span = image.GetPixelRowSpan(y); - for (int x = 0; x < image.Width; x++) - { - if(span[x].PackedValue < 125) continue; - int startX = x; - while (++x < image.Width) - { - if (span[x].PackedValue < 125 || x == (image.Width-1)) - { - Add(new LayerLine((uint)startX, (uint)y, (uint)(x-startX))); - } - } - } - } - } - - public Image ToImage(int resolutionX, int resolutionY) - { - Image image = new Image(resolutionX, resolutionY); - - foreach (var line in Lines) - { - var span = image.GetPixelRowSpan((int)line.Y); - for (uint i = line.X; i <= line.X2; i++) - { - span[(int)i] = Helpers.L8White; - } - } - return image; - } - } -} diff --git a/PrusaSL1Reader/ZCodexFile.cs b/PrusaSL1Reader/ZCodexFile.cs index 081dea1f..03449a01 100644 --- a/PrusaSL1Reader/ZCodexFile.cs +++ b/PrusaSL1Reader/ZCodexFile.cs @@ -7,15 +7,13 @@ */ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.IO.Compression; -using System.Runtime.CompilerServices; +using System.Text; using System.Text.RegularExpressions; using Newtonsoft.Json; using PrusaSL1Reader.Extensions; using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; namespace PrusaSL1Reader @@ -124,7 +122,7 @@ public class MaterialsData public string MaterialUsages { get; set; } } - public class Layer + public class LayerData { public int SupportLayerFileIndex { get; set; } = -1; public int LayerFileIndex { get; set; } = -1; @@ -141,7 +139,7 @@ public class Layer public UserSettingsdata UserSettings { get; set; } public ZCodeMetadata ZCodeMetadataSettings { get; set; } - public List LayersSettings { get; } = new List(); + public List LayersSettings { get; } = new List(); public override FileFormatType FileType => FileFormatType.Archive; @@ -195,8 +193,6 @@ public class Layer public override string MachineName => ZCodeMetadataSettings.PrinterName; - public override string GCode { get; set; } - public override object[] Configs => new[] {(object) ResinMetadataSettings, UserSettings, ZCodeMetadataSettings}; #endregion @@ -206,7 +202,6 @@ public override void Clear() { base.Clear(); LayersSettings.Clear(); - GCode = null; } public override void Encode(string fileFullPath) @@ -215,9 +210,9 @@ public override void Encode(string fileFullPath) using (ZipArchive outputFile = ZipFile.Open(fileFullPath, ZipArchiveMode.Create)) { - outputFile.PutFileContent("ResinMetadata", JsonConvert.SerializeObject(ResinMetadataSettings), false); - outputFile.PutFileContent("UserSettingsData", JsonConvert.SerializeObject(UserSettings), false); - outputFile.PutFileContent("ZCodeMetadata", JsonConvert.SerializeObject(ZCodeMetadataSettings), false); + outputFile.PutFileContent("ResinMetadata", JsonConvert.SerializeObject(ResinMetadataSettings)); + outputFile.PutFileContent("UserSettingsData", JsonConvert.SerializeObject(UserSettings)); + outputFile.PutFileContent("ZCodeMetadata", JsonConvert.SerializeObject(ZCodeMetadataSettings)); if (CreatedThumbnailsCount > 0) { @@ -228,31 +223,33 @@ public override void Encode(string fileFullPath) } } - GCode = GCodeStart; + GCode = new StringBuilder(GCodeStart); for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { - GCode += $"{GCodeKeywordSlice} {layerIndex}\n" + - $"G1 Z{UserSettings.ZLiftDistance} F{UserSettings.ZLiftRetractRate}\n" + - $"G1 Z-{UserSettings.ZLiftDistance - LayerHeight} F{UserSettings.ZLiftFeedRate}\n" + - $"{GCodeKeywordDelayBlank}\n" + - "M106 S255\n" + - $"{GCodeKeywordDelayModel}\n" + - "M106 S0\n"; + 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 = GetLayer(layerIndex); + var byteArr = this[layerIndex].RawData; stream.Write(byteArr, 0, byteArr.Length); stream.Close(); } } - GCode += $"G1 Z40.0 F{UserSettings.ZLiftFeedRate}\n" + - "M18\n"; - outputFile.PutFileContent("ResinGCodeData", GCode, false); + GCode.AppendLine($"G1 Z40.0 F{UserSettings.ZLiftFeedRate}"); + GCode.AppendLine("M18"); + + outputFile.PutFileContent("ResinGCodeData", GCode.ToString()); } } @@ -297,23 +294,22 @@ public override void Decode(string fileFullPath) throw new FileLoadException("ResinGCodeData not found", fileFullPath); } - Layers = new byte[LayerCount][]; - GCode = string.Empty; + LayerManager = new LayerManager(LayerCount); + GCode = new StringBuilder(); using (TextReader tr = new StreamReader(entry.Open())) { string line; int layerIndex = 0; int layerFileIndex = 0; string layerimagePath = null; - uint iLayer = 0; while (!ReferenceEquals(line = tr.ReadLine(), null)) { - GCode += line + Environment.NewLine; + 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 Layer()); + if (LayersSettings.Count - 1 < layerIndex) LayersSettings.Add(new LayerData()); continue; } @@ -328,7 +324,7 @@ public override void Decode(string fileFullPath) { LayersSettings[layerIndex].LayerFileIndex = layerFileIndex; LayersSettings[layerIndex].LayerEntry = inputFile.GetEntry(layerimagePath); - Layers[iLayer++] = CompressLayer(LayersSettings[layerIndex].LayerEntry.Open()); + this[layerIndex] = new LayerManager.Layer((uint) layerIndex, LayersSettings[layerIndex].LayerEntry.Open(), LayersSettings[layerIndex].LayerEntry.Name); layerIndex++; continue; } @@ -406,10 +402,17 @@ public override void SaveAs(string filePath = null) outputFile.PutFileContent("ResinMetadata", JsonConvert.SerializeObject(ResinMetadataSettings)); outputFile.PutFileContent("UserSettingsData", JsonConvert.SerializeObject(UserSettings)); outputFile.PutFileContent("ZCodeMetadata", JsonConvert.SerializeObject(ZCodeMetadataSettings)); - outputFile.PutFileContent("ResinGCodeData", GCode); + outputFile.PutFileContent("ResinGCodeData", GCode.ToString()); + + foreach (var layer in this) + { + if (!layer.IsModified) continue; + outputFile.PutFileContent(layer.Filename, layer.RawData); + layer.IsModified = false; + } } - Decode(FileFullPath); + //Decode(FileFullPath); } public override bool Convert(Type to, string fileFullPath) @@ -419,12 +422,16 @@ public override bool Convert(Type to, string fileFullPath) private void UpdateGCode() { - GCode = Regex.Replace(GCode, @"Z[+]?([0-9]*\.[0-9]+|[0-9]+) F[+]?([0-9]*\.[0-9]+|[0-9]+)", + 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]+)", + 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/PrusaSL1Viewer/EmguExtensions.cs b/PrusaSL1Viewer/EmguExtensions.cs new file mode 100644 index 00000000..3abda843 --- /dev/null +++ b/PrusaSL1Viewer/EmguExtensions.cs @@ -0,0 +1,24 @@ +/* + * 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 Emgu.CV; +using Emgu.CV.Structure; +using PrusaSL1Reader; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace PrusaSL1Viewer +{ + public static class EmguExtensions + { + public static Image ToImageSharpL8(this Image image) + { + return Image.LoadPixelData(image.Bytes, image.Width, image.Height); + } + } +} diff --git a/PrusaSL1Viewer/FrmInputBox.Designer.cs b/PrusaSL1Viewer/FrmInputBox.Designer.cs index b7550474..67b37796 100644 --- a/PrusaSL1Viewer/FrmInputBox.Designer.cs +++ b/PrusaSL1Viewer/FrmInputBox.Designer.cs @@ -40,19 +40,21 @@ private void InitializeComponent() // // lbDescription // - this.lbDescription.AutoSize = true; + this.lbDescription.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); this.lbDescription.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lbDescription.Location = new System.Drawing.Point(18, 14); + this.lbDescription.Location = new System.Drawing.Point(13, 14); this.lbDescription.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.lbDescription.Name = "lbDescription"; - this.lbDescription.Size = new System.Drawing.Size(89, 20); + this.lbDescription.Size = new System.Drawing.Size(495, 128); this.lbDescription.TabIndex = 0; this.lbDescription.Text = "Description"; // // lbCurrentValue // this.lbCurrentValue.AutoSize = true; - this.lbCurrentValue.Location = new System.Drawing.Point(20, 72); + this.lbCurrentValue.Location = new System.Drawing.Point(13, 150); this.lbCurrentValue.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.lbCurrentValue.Name = "lbCurrentValue"; this.lbCurrentValue.Size = new System.Drawing.Size(111, 20); @@ -64,17 +66,17 @@ private void InitializeComponent() this.tbCurrentValue.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.tbCurrentValue.CausesValidation = false; - this.tbCurrentValue.Location = new System.Drawing.Point(140, 68); + this.tbCurrentValue.Location = new System.Drawing.Point(132, 147); this.tbCurrentValue.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); this.tbCurrentValue.Name = "tbCurrentValue"; this.tbCurrentValue.ReadOnly = true; - this.tbCurrentValue.Size = new System.Drawing.Size(307, 26); + this.tbCurrentValue.Size = new System.Drawing.Size(376, 26); this.tbCurrentValue.TabIndex = 2; // // label1 // this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(20, 111); + this.label1.Location = new System.Drawing.Point(13, 189); this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(89, 20); @@ -85,10 +87,11 @@ private void InitializeComponent() // this.numNewValue.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); - this.numNewValue.Location = new System.Drawing.Point(140, 108); + this.numNewValue.DecimalPlaces = 2; + this.numNewValue.Location = new System.Drawing.Point(132, 187); this.numNewValue.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); this.numNewValue.Name = "numNewValue"; - this.numNewValue.Size = new System.Drawing.Size(309, 26); + this.numNewValue.Size = new System.Drawing.Size(378, 26); this.numNewValue.TabIndex = 4; this.numNewValue.ValueChanged += new System.EventHandler(this.ValueChanged); // @@ -98,7 +101,7 @@ private void InitializeComponent() this.btnModify.Enabled = false; this.btnModify.Image = global::PrusaSL1Viewer.Properties.Resources.Ok_24x24; this.btnModify.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.btnModify.Location = new System.Drawing.Point(139, 148); + this.btnModify.Location = new System.Drawing.Point(193, 227); this.btnModify.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); this.btnModify.Name = "btnModify"; this.btnModify.Size = new System.Drawing.Size(150, 48); @@ -114,7 +117,7 @@ private void InitializeComponent() this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.btnCancel.Image = global::PrusaSL1Viewer.Properties.Resources.Cancel_24x24; this.btnCancel.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.btnCancel.Location = new System.Drawing.Point(298, 148); + this.btnCancel.Location = new System.Drawing.Point(359, 227); this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); this.btnCancel.Name = "btnCancel"; this.btnCancel.Size = new System.Drawing.Size(150, 48); @@ -129,7 +132,7 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.CancelButton = this.btnCancel; - this.ClientSize = new System.Drawing.Size(466, 211); + this.ClientSize = new System.Drawing.Size(527, 298); this.Controls.Add(this.btnModify); this.Controls.Add(this.btnCancel); this.Controls.Add(this.numNewValue); diff --git a/PrusaSL1Viewer/FrmMain.Designer.cs b/PrusaSL1Viewer/FrmMain.Designer.cs index 13c428d5..b9b49034 100644 --- a/PrusaSL1Viewer/FrmMain.Designer.cs +++ b/PrusaSL1Viewer/FrmMain.Designer.cs @@ -44,24 +44,36 @@ private void InitializeComponent() this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.menuFileExit = new System.Windows.Forms.ToolStripMenuItem(); this.menuEdit = new System.Windows.Forms.ToolStripMenuItem(); + this.menuMutate = new System.Windows.Forms.ToolStripMenuItem(); this.viewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.menuAboutWebsite = new System.Windows.Forms.ToolStripMenuItem(); - this.menuAboutDonate = new System.Windows.Forms.ToolStripMenuItem(); - this.menuAboutAbout = new System.Windows.Forms.ToolStripMenuItem(); + this.menuHelpWebsite = new System.Windows.Forms.ToolStripMenuItem(); + this.menuHelpDonate = new System.Windows.Forms.ToolStripMenuItem(); + this.menuHelpAbout = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator10 = new System.Windows.Forms.ToolStripSeparator(); + this.menuHelpInstallPrinters = new System.Windows.Forms.ToolStripMenuItem(); this.statusBar = new System.Windows.Forms.StatusStrip(); this.sbLayers = new System.Windows.Forms.VScrollBar(); this.mainTable = new System.Windows.Forms.TableLayoutPanel(); this.splitContainer1 = new System.Windows.Forms.SplitContainer(); this.lbLayers = new System.Windows.Forms.Label(); this.scCenter = new System.Windows.Forms.SplitContainer(); - this.pbLayer = new System.Windows.Forms.PictureBox(); + this.pbLayer = new Cyotek.Windows.Forms.ImageBox(); this.tsLayer = new System.Windows.Forms.ToolStrip(); this.tsLayerImageExport = new System.Windows.Forms.ToolStripButton(); this.tsLayerResolution = new System.Windows.Forms.ToolStripLabel(); this.tsLayerImageRotate = new System.Windows.Forms.ToolStripButton(); this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); this.tsLayerImageLayerDifference = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator6 = new System.Windows.Forms.ToolStripSeparator(); + this.tsLayerPreviewTime = new System.Windows.Forms.ToolStripLabel(); + this.toolStripSeparator7 = new System.Windows.Forms.ToolStripSeparator(); + this.tsLayerImageLayerOutline = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator9 = new System.Windows.Forms.ToolStripSeparator(); + this.tsLayerImagePixelEdit = new System.Windows.Forms.ToolStripButton(); + this.toolStripSeparator8 = new System.Windows.Forms.ToolStripSeparator(); + this.tsLayerImageZoomLabel = new System.Windows.Forms.ToolStripLabel(); + this.tsLayerImageZoomValueLabel = new System.Windows.Forms.ToolStripLabel(); this.pbLayers = new System.Windows.Forms.ProgressBar(); this.tabControlLeft = new System.Windows.Forms.TabControl(); this.tbpThumbnailsAndInfo = new System.Windows.Forms.TabPage(); @@ -89,8 +101,6 @@ private void InitializeComponent() this.tsGcodeLabelChars = new System.Windows.Forms.ToolStripLabel(); this.tsGCodeButtonSave = new System.Windows.Forms.ToolStripButton(); this.imageList16x16 = new System.Windows.Forms.ImageList(this.components); - this.tsLayerPreviewTime = new System.Windows.Forms.ToolStripLabel(); - this.toolStripSeparator6 = new System.Windows.Forms.ToolStripSeparator(); this.menu.SuspendLayout(); this.mainTable.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); @@ -101,7 +111,6 @@ private void InitializeComponent() this.scCenter.Panel1.SuspendLayout(); this.scCenter.Panel2.SuspendLayout(); this.scCenter.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pbLayer)).BeginInit(); this.tsLayer.SuspendLayout(); this.tabControlLeft.SuspendLayout(); this.tbpThumbnailsAndInfo.SuspendLayout(); @@ -121,6 +130,7 @@ private void InitializeComponent() this.menu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.fileToolStripMenuItem, this.menuEdit, + this.menuMutate, this.viewToolStripMenuItem, this.helpToolStripMenuItem}); this.menu.Location = new System.Drawing.Point(0, 0); @@ -216,8 +226,7 @@ private void InitializeComponent() this.menuFileExtract.Enabled = false; this.menuFileExtract.Image = global::PrusaSL1Viewer.Properties.Resources.Extract_object_16x16; this.menuFileExtract.Name = "menuFileExtract"; - this.menuFileExtract.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Alt) - | System.Windows.Forms.Keys.E))); + this.menuFileExtract.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.E))); this.menuFileExtract.Size = new System.Drawing.Size(261, 22); this.menuFileExtract.Text = "&Extract"; this.menuFileExtract.Click += new System.EventHandler(this.ItemClicked); @@ -251,6 +260,12 @@ private void InitializeComponent() this.menuEdit.Size = new System.Drawing.Size(39, 20); this.menuEdit.Text = "&Edit"; // + // menuMutate + // + this.menuMutate.Name = "menuMutate"; + this.menuMutate.Size = new System.Drawing.Size(57, 20); + this.menuMutate.Text = "&Mutate"; + // // viewToolStripMenuItem // this.viewToolStripMenuItem.Name = "viewToolStripMenuItem"; @@ -261,40 +276,55 @@ private void InitializeComponent() // helpToolStripMenuItem // this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.menuAboutWebsite, - this.menuAboutDonate, - this.menuAboutAbout}); + this.menuHelpWebsite, + this.menuHelpDonate, + this.menuHelpAbout, + this.toolStripSeparator10, + this.menuHelpInstallPrinters}); this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20); this.helpToolStripMenuItem.Text = "&Help"; // - // menuAboutWebsite + // menuHelpWebsite // - this.menuAboutWebsite.Image = global::PrusaSL1Viewer.Properties.Resources.Global_Network_icon_16x16; - this.menuAboutWebsite.Name = "menuAboutWebsite"; - this.menuAboutWebsite.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) + this.menuHelpWebsite.Image = global::PrusaSL1Viewer.Properties.Resources.Global_Network_icon_16x16; + this.menuHelpWebsite.Name = "menuHelpWebsite"; + this.menuHelpWebsite.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Shift) | System.Windows.Forms.Keys.W))); - this.menuAboutWebsite.Size = new System.Drawing.Size(193, 22); - this.menuAboutWebsite.Text = "&Website"; - this.menuAboutWebsite.Click += new System.EventHandler(this.ItemClicked); + this.menuHelpWebsite.Size = new System.Drawing.Size(229, 22); + this.menuHelpWebsite.Text = "&Website"; + this.menuHelpWebsite.Click += new System.EventHandler(this.ItemClicked); + // + // menuHelpDonate // - // menuAboutDonate + this.menuHelpDonate.Image = global::PrusaSL1Viewer.Properties.Resources.Donate_16x16; + this.menuHelpDonate.Name = "menuHelpDonate"; + this.menuHelpDonate.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.D))); + this.menuHelpDonate.Size = new System.Drawing.Size(229, 22); + this.menuHelpDonate.Text = "&Donate"; + this.menuHelpDonate.Click += new System.EventHandler(this.ItemClicked); // - this.menuAboutDonate.Image = global::PrusaSL1Viewer.Properties.Resources.Donate_16x16; - this.menuAboutDonate.Name = "menuAboutDonate"; - this.menuAboutDonate.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.D))); - this.menuAboutDonate.Size = new System.Drawing.Size(193, 22); - this.menuAboutDonate.Text = "&Donate"; - this.menuAboutDonate.Click += new System.EventHandler(this.ItemClicked); + // menuHelpAbout // - // menuAboutAbout + this.menuHelpAbout.Image = global::PrusaSL1Viewer.Properties.Resources.Button_Info_16x16; + this.menuHelpAbout.Name = "menuHelpAbout"; + this.menuHelpAbout.ShortcutKeys = System.Windows.Forms.Keys.F1; + this.menuHelpAbout.Size = new System.Drawing.Size(229, 22); + this.menuHelpAbout.Text = "&About"; + this.menuHelpAbout.Click += new System.EventHandler(this.ItemClicked); // - this.menuAboutAbout.Image = global::PrusaSL1Viewer.Properties.Resources.Button_Info_16x16; - this.menuAboutAbout.Name = "menuAboutAbout"; - this.menuAboutAbout.ShortcutKeys = System.Windows.Forms.Keys.F1; - this.menuAboutAbout.Size = new System.Drawing.Size(193, 22); - this.menuAboutAbout.Text = "&About"; - this.menuAboutAbout.Click += new System.EventHandler(this.ItemClicked); + // toolStripSeparator10 + // + this.toolStripSeparator10.Name = "toolStripSeparator10"; + this.toolStripSeparator10.Size = new System.Drawing.Size(226, 6); + // + // menuHelpInstallPrinters + // + this.menuHelpInstallPrinters.Image = global::PrusaSL1Viewer.Properties.Resources.CNCMachine_16x16; + this.menuHelpInstallPrinters.Name = "menuHelpInstallPrinters"; + this.menuHelpInstallPrinters.Size = new System.Drawing.Size(229, 22); + this.menuHelpInstallPrinters.Text = "Instal printers into PrusaSlicer"; + this.menuHelpInstallPrinters.Click += new System.EventHandler(this.ItemClicked); // // statusBar // @@ -386,14 +416,17 @@ private void InitializeComponent() // // pbLayer // - this.pbLayer.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; this.pbLayer.Dock = System.Windows.Forms.DockStyle.Fill; + this.pbLayer.GridScale = Cyotek.Windows.Forms.ImageBoxGridScale.Large; this.pbLayer.Location = new System.Drawing.Point(0, 25); this.pbLayer.Name = "pbLayer"; + this.pbLayer.PanMode = Cyotek.Windows.Forms.ImageBoxPanMode.Left; + this.pbLayer.ShowPixelGrid = true; this.pbLayer.Size = new System.Drawing.Size(1095, 677); - this.pbLayer.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; - this.pbLayer.TabIndex = 5; - this.pbLayer.TabStop = false; + this.pbLayer.TabIndex = 7; + this.pbLayer.Zoomed += new System.EventHandler(this.pbLayer_Zoomed); + this.pbLayer.MouseMove += new System.Windows.Forms.MouseEventHandler(this.pbLayer_MouseMove); + this.pbLayer.MouseUp += new System.Windows.Forms.MouseEventHandler(this.pbLayer_MouseUp); // // tsLayer // @@ -405,7 +438,14 @@ private void InitializeComponent() this.toolStripSeparator5, this.tsLayerImageLayerDifference, this.toolStripSeparator6, - this.tsLayerPreviewTime}); + this.tsLayerPreviewTime, + this.toolStripSeparator7, + this.tsLayerImageLayerOutline, + this.toolStripSeparator9, + this.tsLayerImagePixelEdit, + this.toolStripSeparator8, + this.tsLayerImageZoomLabel, + this.tsLayerImageZoomValueLabel}); this.tsLayer.Location = new System.Drawing.Point(0, 0); this.tsLayer.Name = "tsLayer"; this.tsLayer.Size = new System.Drawing.Size(1095, 25); @@ -458,11 +498,79 @@ private void InitializeComponent() this.tsLayerImageLayerDifference.Image = global::PrusaSL1Viewer.Properties.Resources.layers_16x16; this.tsLayerImageLayerDifference.ImageTransparentColor = System.Drawing.Color.Magenta; this.tsLayerImageLayerDifference.Name = "tsLayerImageLayerDifference"; - this.tsLayerImageLayerDifference.Size = new System.Drawing.Size(112, 22); - this.tsLayerImageLayerDifference.Text = "Layer Difference"; + this.tsLayerImageLayerDifference.Size = new System.Drawing.Size(81, 22); + this.tsLayerImageLayerDifference.Text = "Difference"; this.tsLayerImageLayerDifference.ToolTipText = "Show layer differences where daker pixels were also present on previous layer and" + " the white pixels the difference between previous and current layer."; this.tsLayerImageLayerDifference.Click += new System.EventHandler(this.ItemClicked); + // + // toolStripSeparator6 + // + this.toolStripSeparator6.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.toolStripSeparator6.Name = "toolStripSeparator6"; + this.toolStripSeparator6.Size = new System.Drawing.Size(6, 25); + // + // tsLayerPreviewTime + // + this.tsLayerPreviewTime.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.tsLayerPreviewTime.Name = "tsLayerPreviewTime"; + this.tsLayerPreviewTime.Size = new System.Drawing.Size(77, 22); + this.tsLayerPreviewTime.Text = "Preview Time"; + this.tsLayerPreviewTime.ToolTipText = "Layer Resolution"; + // + // toolStripSeparator7 + // + this.toolStripSeparator7.Name = "toolStripSeparator7"; + this.toolStripSeparator7.Size = new System.Drawing.Size(6, 25); + // + // tsLayerImageLayerOutline + // + this.tsLayerImageLayerOutline.CheckOnClick = true; + this.tsLayerImageLayerOutline.Image = global::PrusaSL1Viewer.Properties.Resources.Geometry_16x16; + this.tsLayerImageLayerOutline.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tsLayerImageLayerOutline.Name = "tsLayerImageLayerOutline"; + this.tsLayerImageLayerOutline.Size = new System.Drawing.Size(66, 22); + this.tsLayerImageLayerOutline.Text = "Outline"; + this.tsLayerImageLayerOutline.ToolTipText = "Show layer outlines only"; + this.tsLayerImageLayerOutline.Click += new System.EventHandler(this.ItemClicked); + // + // toolStripSeparator9 + // + this.toolStripSeparator9.Name = "toolStripSeparator9"; + this.toolStripSeparator9.Size = new System.Drawing.Size(6, 25); + // + // tsLayerImagePixelEdit + // + this.tsLayerImagePixelEdit.CheckOnClick = true; + this.tsLayerImagePixelEdit.Image = global::PrusaSL1Viewer.Properties.Resources.pixel_16x16; + this.tsLayerImagePixelEdit.ImageTransparentColor = System.Drawing.Color.Magenta; + this.tsLayerImagePixelEdit.Name = "tsLayerImagePixelEdit"; + this.tsLayerImagePixelEdit.Size = new System.Drawing.Size(75, 22); + this.tsLayerImagePixelEdit.Text = "Pixel Edit"; + this.tsLayerImagePixelEdit.ToolTipText = "Edit layer image pixels (Righ click to add pixel and SHIFT + Right click to remov" + + "e pixel)\r\nRed pixels are removed pixels\r\nGreen pixels are added pixels"; + // + // toolStripSeparator8 + // + this.toolStripSeparator8.Name = "toolStripSeparator8"; + this.toolStripSeparator8.Size = new System.Drawing.Size(6, 25); + // + // tsLayerImageZoomLabel + // + this.tsLayerImageZoomLabel.Image = global::PrusaSL1Viewer.Properties.Resources.search_16x16; + this.tsLayerImageZoomLabel.Name = "tsLayerImageZoomLabel"; + this.tsLayerImageZoomLabel.Size = new System.Drawing.Size(58, 22); + this.tsLayerImageZoomLabel.Text = "Zoom:"; + this.tsLayerImageZoomLabel.ToolTipText = "Layer image zoom level, use mouse scroll to zoom in/out into image\r\nCtrl + 0 to s" + + "cale to fit"; + // + // tsLayerImageZoomValueLabel + // + this.tsLayerImageZoomValueLabel.Name = "tsLayerImageZoomValueLabel"; + this.tsLayerImageZoomValueLabel.Size = new System.Drawing.Size(38, 22); + this.tsLayerImageZoomValueLabel.Text = "100 %"; + this.tsLayerImageZoomValueLabel.ToolTipText = "Layer image zoom level, use mouse scroll to zoom in/out into image\r\nCtrl + 0 to s" + + "cale to fit"; // // pbLayers // @@ -741,20 +849,6 @@ private void InitializeComponent() this.imageList16x16.Images.SetKeyName(1, "PhotoInfo-16x16.png"); this.imageList16x16.Images.SetKeyName(2, "GCode-16x16.png"); // - // tsLayerPreviewTime - // - this.tsLayerPreviewTime.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; - this.tsLayerPreviewTime.Name = "tsLayerPreviewTime"; - this.tsLayerPreviewTime.Size = new System.Drawing.Size(77, 22); - this.tsLayerPreviewTime.Text = "Preview Time"; - this.tsLayerPreviewTime.ToolTipText = "Layer Resolution"; - // - // toolStripSeparator6 - // - this.toolStripSeparator6.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; - this.toolStripSeparator6.Name = "toolStripSeparator6"; - this.toolStripSeparator6.Size = new System.Drawing.Size(6, 25); - // // FrmMain // this.AllowDrop = true; @@ -783,7 +877,6 @@ private void InitializeComponent() this.scCenter.Panel2.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.scCenter)).EndInit(); this.scCenter.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.pbLayer)).EndInit(); this.tsLayer.ResumeLayout(false); this.tsLayer.PerformLayout(); this.tabControlLeft.ResumeLayout(false); @@ -816,16 +909,15 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem menuFileExit; private System.Windows.Forms.StatusStrip statusBar; private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem menuAboutWebsite; - private System.Windows.Forms.ToolStripMenuItem menuAboutDonate; - private System.Windows.Forms.ToolStripMenuItem menuAboutAbout; + private System.Windows.Forms.ToolStripMenuItem menuHelpWebsite; + private System.Windows.Forms.ToolStripMenuItem menuHelpDonate; + private System.Windows.Forms.ToolStripMenuItem menuHelpAbout; private System.Windows.Forms.VScrollBar sbLayers; private System.Windows.Forms.TableLayoutPanel mainTable; private System.Windows.Forms.SplitContainer splitContainer1; private System.Windows.Forms.Label lbLayers; private System.Windows.Forms.ToolStripMenuItem menuEdit; private System.Windows.Forms.SplitContainer scCenter; - private System.Windows.Forms.PictureBox pbLayer; private System.Windows.Forms.ProgressBar pbLayers; private System.Windows.Forms.ToolStripMenuItem viewToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem menuFileClose; @@ -871,6 +963,17 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripButton tsLayerImageLayerDifference; private System.Windows.Forms.ToolStripSeparator toolStripSeparator6; private System.Windows.Forms.ToolStripLabel tsLayerPreviewTime; + private Cyotek.Windows.Forms.ImageBox pbLayer; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator7; + private System.Windows.Forms.ToolStripLabel tsLayerImageZoomLabel; + private System.Windows.Forms.ToolStripLabel tsLayerImageZoomValueLabel; + private System.Windows.Forms.ToolStripButton tsLayerImagePixelEdit; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator8; + private System.Windows.Forms.ToolStripButton tsLayerImageLayerOutline; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator9; + private System.Windows.Forms.ToolStripMenuItem menuMutate; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator10; + private System.Windows.Forms.ToolStripMenuItem menuHelpInstallPrinters; } } diff --git a/PrusaSL1Viewer/FrmMain.cs b/PrusaSL1Viewer/FrmMain.cs index 3c010508..d5a965ff 100644 --- a/PrusaSL1Viewer/FrmMain.cs +++ b/PrusaSL1Viewer/FrmMain.cs @@ -7,22 +7,53 @@ */ using System; using System.Collections; +using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; using System.IO; using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; +using Emgu.CV.Structure; using PrusaSL1Reader; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using Color = System.Drawing.Color; +using Point = System.Drawing.Point; namespace PrusaSL1Viewer { public partial class FrmMain : Form { + #region Enums + public enum eMutate + { + Erode, + Dilate, + PyrDownUp, + SmoothMedian, + SmoothGaussian + } + #endregion + #region Properties + + public static readonly Dictionary MutateDescriptions = new Dictionary + { + {eMutate.Erode, "Erodes image using a 3x3 rectangular structuring element.\n" + + "Erosion are applied several (iterations) times"}, + {eMutate.Dilate, "Dilates image using a 3x3 rectangular structuring element.\n" + + "Dilation are applied several (iterations) times"}, + {eMutate.PyrDownUp, "Performs downsampling step of Gaussian pyramid decomposition.\n" + + "First it convolves image with the specified filter and then downsamples the image by rejecting even rows and columns.\n" + + "After performs up-sampling step of Gaussian pyramid decomposition\n" + + "First it upsamples image by injecting even zero rows and columns and then convolves result with the specified filter multiplied by 4 for interpolation"}, + {eMutate.SmoothMedian, "Finding median of size neighborhood"}, + {eMutate.SmoothGaussian, "Perform Gaussian Smoothing"} + }; + public FrmLoading FrmLoading { get; } public static FileFormat SlicerFile { @@ -31,6 +62,7 @@ public static FileFormat SlicerFile } public uint ActualLayer => (uint)(sbLayers.Maximum - sbLayers.Value); + public Image ActualLayerImage { get; private set; } #endregion @@ -46,6 +78,16 @@ public FrmMain() DragEnter += (s, e) => { if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy; }; DragDrop += (s, e) => { ProcessFile((string[])e.Data.GetData(DataFormats.FileDrop)); }; + + foreach (eMutate mutate in (eMutate[])Enum.GetValues(typeof(eMutate))) + { + var item = new ToolStripMenuItem(mutate.ToString()) + { + ToolTipText = MutateDescriptions[mutate], Tag = mutate, AutoToolTip = true + }; + item.Click += ItemClicked; + menuMutate.DropDownItems.Add(item); + } } #endregion @@ -68,6 +110,19 @@ protected override void OnKeyUp(KeyEventArgs e) e.Handled = true; return; } + + if ((e.KeyCode == Keys.NumPad0 || e.KeyCode == Keys.D0) && e.Control) + { + pbLayer.ZoomToFit(); + e.Handled = true; + return; + } + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + pbLayer.ZoomToFit(); } #endregion @@ -131,7 +186,33 @@ private void ItemClicked(object sender, EventArgs e) if (ReferenceEquals(sender, menuFileSave)) { - SlicerFile.Save(); + DisableGUI(); + FrmLoading.SetDescription($"Saving {Path.GetFileName(SlicerFile.FileFullPath)}"); + + Task task = Task.Factory.StartNew(() => + { + bool result = false; + try + { + SlicerFile.Save(); + result = true; + } + catch (Exception) + { + } + finally + { + Invoke((MethodInvoker)delegate { + // Running on the UI thread + EnableGUI(true); + }); + } + + return result; + }); + + FrmLoading.ShowDialog(); + menuFileSave.Enabled = menuFileSaveAs.Enabled = false; return; @@ -141,16 +222,44 @@ private void ItemClicked(object sender, EventArgs e) { using (SaveFileDialog dialog = new SaveFileDialog()) { - dialog.Filter = SlicerFile.FileFilter; + var ext = Path.GetExtension(SlicerFile.FileFullPath); + dialog.Filter = $"{ext.Remove(0, 1)} files (*{ext})|*{ext}"; dialog.AddExtension = true; dialog.FileName = $"{Path.GetFileNameWithoutExtension(SlicerFile.FileFullPath)}_copy"; if (dialog.ShowDialog() == DialogResult.OK) { - SlicerFile.SaveAs(dialog.FileName); + DisableGUI(); + FrmLoading.SetDescription($"Saving {Path.GetFileName(dialog.FileName)}"); + + Task task = Task.Factory.StartNew(() => + { + bool result = false; + try + { + SlicerFile.SaveAs(dialog.FileName); + result = true; + } + catch (Exception) + { + } + finally + { + Invoke((MethodInvoker)delegate { + // Running on the UI thread + EnableGUI(true); + }); + } + + return result; + }); + + FrmLoading.ShowDialog(); + menuFileSave.Enabled = menuFileSaveAs.Enabled = false; - ProcessFile(dialog.FileName); + UpdateTitle(); + //ProcessFile(dialog.FileName); } } @@ -236,7 +345,7 @@ private void ItemClicked(object sender, EventArgs e) return; } - // Edit + // Edit & mutate if (!ReferenceEquals(item.Tag, null)) { if (item.Tag.GetType() == typeof(FileFormat.PrintParameterModifier)) @@ -264,25 +373,32 @@ private void ItemClicked(object sender, EventArgs e) return; } + + if (item.Tag.GetType() == typeof(eMutate)) + { + eMutate mutate = (eMutate)item.Tag; + MutateLayers(mutate); + return; + } } // View // About - if (ReferenceEquals(sender, menuAboutAbout)) + if (ReferenceEquals(sender, menuHelpAbout)) { Program.FrmAbout.ShowDialog(); return; } - if (ReferenceEquals(sender, menuAboutWebsite)) + if (ReferenceEquals(sender, menuHelpWebsite)) { Process.Start(About.Website); return; } - if (ReferenceEquals(sender, menuAboutDonate)) + if (ReferenceEquals(sender, menuHelpDonate)) { MessageBox.Show( "All my work here is given for free (OpenSource), it took some hours to build, test and polish the program.\n" + @@ -290,6 +406,60 @@ private void ItemClicked(object sender, EventArgs e) "A browser window will be open and forward to my paypal address after you click 'OK'.\nHappy Printing!", "Donation", MessageBoxButtons.OK, MessageBoxIcon.Information); Process.Start(About.Donate); + return; + } + + if (ReferenceEquals(sender, menuHelpInstallPrinters)) + { + string printerFolder = $"{Application.StartupPath}{Path.DirectorySeparatorChar}PrusaSlicer{Path.DirectorySeparatorChar}printer"; + try + { + string[] profiles = Directory.GetFiles(printerFolder); + string profilesNames = String.Empty; + + foreach (var profile in profiles) + { + profilesNames += $"{Path.GetFileNameWithoutExtension(profile)}\n"; + } + + var result = MessageBox.Show( + "This action will install following printer profiles into PrusaSlicer:\n" + + "---------------\n" + + profilesNames + + "---------------\n" + + "Click 'Yes' to override all profiles\n" + + "Click 'No' to install only missing profiles without override\n" + + "Clock 'Abort' to cancel this operation", + "Install printers into PrusaSlicer", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); + + + if (result == DialogResult.Abort) + { + return; + } + + bool overwrite = result == DialogResult.Yes; + string targetFolder = $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}PrusaSlicer{Path.DirectorySeparatorChar}printer"; + + foreach (var profile in profiles) + { + string targetFile = $"{targetFolder}{Path.DirectorySeparatorChar}{Path.GetFileName(profile)}"; + if (!overwrite && File.Exists(targetFile)) continue; + File.Copy(profile, targetFile, overwrite); + } + + MessageBox.Show( + "Printers were installed.\nRestart PrusaSlicer and check if printers are present.", + "Operation Completed", MessageBoxButtons.OK, MessageBoxIcon.Information); + + } + catch (Exception exception) + { + MessageBox.Show(exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + + return; } } @@ -453,7 +623,7 @@ private void ItemClicked(object sender, EventArgs e) /************************ * Layer Menu * ***********************/ - if (ReferenceEquals(sender, tsLayerImageRotate) || ReferenceEquals(sender, tsLayerImageLayerDifference)) + if (ReferenceEquals(sender, tsLayerImageRotate) || ReferenceEquals(sender, tsLayerImageLayerDifference) || ReferenceEquals(sender, tsLayerImageLayerOutline)) { sbLayers_ValueChanged(sbLayers, null); return; @@ -585,10 +755,10 @@ void Clear() menuFileConvert.DropDownItems.Clear(); menuEdit.DropDownItems.Clear(); - /*foreach (ToolStripItem item in menuEdit.DropDownItems) + foreach (ToolStripItem item in menuMutate.DropDownItems) { item.Enabled = false; - }*/ + } foreach (ToolStripItem item in tsThumbnails.Items) { @@ -618,7 +788,8 @@ void Clear() menuFileConvert.Enabled = sbLayers.Enabled = pbLayers.Enabled = - menuEdit.Enabled = + menuEdit.Enabled = + menuMutate.Enabled = false; @@ -739,6 +910,11 @@ void ProcessFile(string fileName) tsThumbnailsNext.Enabled = SlicerFile.CreatedThumbnailsCount > 1; AdjustThumbnailSplitter(); } + + foreach (ToolStripItem item in menuMutate.DropDownItems) + { + item.Enabled = true; + } foreach (ToolStripItem item in tsLayer.Items) { item.Enabled = true; @@ -756,10 +932,11 @@ void ProcessFile(string fileName) sbLayers.Enabled = pbLayers.Enabled = - menuEdit.Enabled = + menuEdit.Enabled = + menuMutate.Enabled = true; - if (!string.IsNullOrEmpty(SlicerFile.GCode)) + if (!ReferenceEquals(SlicerFile.GCode, null)) { tabControlLeft.TabPages.Add(tbpGCode); } @@ -772,12 +949,18 @@ void ProcessFile(string fileName) sbLayers.Value = sbLayers.Maximum; tabControlLeft.SelectedIndex = 0; + tsLayerResolution.Text = $"{{Width={SlicerFile.ResolutionX}, Height={SlicerFile.ResolutionY}}}"; RefreshInfo(); - tsLayerResolution.Text = pbLayer.Image?.PhysicalDimension.ToString() ?? string.Empty; + pbLayer.ZoomToFit(); + UpdateTitle(); + } + + void UpdateTitle() + { Text = $"{FrmAbout.AssemblyTitle} Version: {FrmAbout.AssemblyVersion} File: {Path.GetFileName(SlicerFile.FileFullPath)}"; } @@ -830,9 +1013,9 @@ void RefreshInfo() tsPropertiesLabelCount.Text = $"Properties: {lvProperties.Items.Count}"; tsPropertiesLabelGroups.Text = $"Groups: {lvProperties.Groups.Count}"; - if (!string.IsNullOrEmpty(SlicerFile.GCode)) + if (!ReferenceEquals(SlicerFile.GCode, null)) { - tbGCode.Text = SlicerFile.GCode; + tbGCode.Text = SlicerFile.GCode.ToString(); tsGCodeLabelLines.Text = $"Lines: {tbGCode.Lines.Length}"; tsGcodeLabelChars.Text = $"Chars: {tbGCode.Text.Length}"; } @@ -861,12 +1044,21 @@ void ShowLayer(uint layerNum) Stopwatch watch = Stopwatch.StartNew(); - var image = SlicerFile.GetLayerImage(layerNum); - if (tsLayerImageLayerDifference.Checked) + var image = SlicerFile[layerNum].Image; + ActualLayerImage = image.Clone(); + + if (tsLayerImageLayerOutline.Checked) + { + Emgu.CV.Image grayscale = image.ToEmguImage(); + grayscale = grayscale.Canny(100, 200, 3, true); + image = grayscale.ToImageSharpL8(); + grayscale.Dispose(); + } + else if (tsLayerImageLayerDifference.Checked) { if (layerNum > 0) { - var previousImage = SlicerFile.GetLayerImage(layerNum - 1); + var previousImage = SlicerFile[layerNum-1].Image; //var nextImage = SlicerFile.GetLayerImage(layerNum+1); @@ -960,5 +1152,149 @@ void AdjustThumbnailSplitter() scLeft.SplitterDistance = Math.Min(pbThumbnail.Image.Height + 5, 400); } #endregion + + private void pbLayer_Zoomed(object sender, Cyotek.Windows.Forms.ImageBoxZoomEventArgs e) + { + tsLayerImageZoomValueLabel.Text = $"{e.NewZoom} %"; + } + + void DrawPixel(bool isAdd, Point location, Color color, L8 pixelL8) + { + var point = pbLayer.PointToImage(location); + int x = point.X; + int y = point.Y; + + if (tsLayerImageRotate.Checked) + { + x = point.Y; + y = ActualLayerImage.Height - 1 - point.X; + } + + + if (isAdd && ActualLayerImage[x, y].PackedValue > byte.MaxValue / 2) + { + return; + } + if (!isAdd && ActualLayerImage[x, y].PackedValue < byte.MaxValue / 2) + { + return; + } + + ActualLayerImage[x, y] = pixelL8; + Bitmap bmp = pbLayer.Image as Bitmap; + bmp.SetPixel(point.X, point.Y, color); + + /*if (bmp.GetPixel(point.X, point.Y).GetBrightness() == returnif) return; + bmp.SetPixel(point.X, point.Y, color); + ActualLayerImage[point.X, point.Y] = pixelL8; + var newImage = ActualLayerImage.Clone(); + if (tsLayerImageRotate.Checked) + { + newImage.Mutate(mut => mut.Rotate(RotateMode.Rotate270)); + } + SlicerFile[ActualLayer].Image = newImage;*/ + pbLayer.Invalidate(); + menuFileSave.Enabled = menuFileSaveAs.Enabled = true; + } + + private void pbLayer_MouseUp(object sender, MouseEventArgs e) + { + if (!tsLayerImagePixelEdit.Checked || (e.Button & MouseButtons.Right) == 0) return; + if (!pbLayer.IsPointInImage(e.Location)) return; + + + if (Control.ModifierKeys == Keys.Shift) + { + DrawPixel(false, e.Location, Color.DarkRed, Helpers.L8Black); + } + else + { + DrawPixel(true, e.Location, Color.Green, Helpers.L8White); + } + + SlicerFile[ActualLayer].Image = ActualLayerImage; + } + + private void pbLayer_MouseMove(object sender, MouseEventArgs e) + { + if (!tsLayerImagePixelEdit.Checked || (e.Button & MouseButtons.Right) == 0) return; + if (!pbLayer.IsPointInImage(e.Location)) return; + + if (Control.ModifierKeys == Keys.Shift) + { + DrawPixel(false, e.Location, Color.DarkRed, Helpers.L8Black); + return; + } + + DrawPixel(true, e.Location, Color.Green, Helpers.L8White); + } + + public void MutateLayers(eMutate type) + { + decimal value = 0; + using (FrmInputBox inputBox = new FrmInputBox($"Mutate - {type}", MutateDescriptions[type], 0)) + { + if (inputBox.ShowDialog() != DialogResult.OK) return; + value = inputBox.NewValue; + if (value == 0) return; + } + + DisableGUI(); + FrmLoading.SetDescription($"Mutating - {type}"); + + Task task = Task.Factory.StartNew(() => + { + bool result = false; + try + { + Parallel.ForEach(SlicerFile, (layer) => + { + var image = layer.Image; + var imageEgmu = image.ToEmguImage(); + switch (type) + { + case eMutate.Erode: + imageEgmu = imageEgmu.Erode((int) value); + break; + case eMutate.Dilate: + imageEgmu = imageEgmu.Dilate((int) value); + break; + case eMutate.PyrDownUp: + imageEgmu = imageEgmu.PyrDown().PyrUp(); + break; + case eMutate.SmoothMedian: + imageEgmu = imageEgmu.SmoothMedian((int) value); + break; + case eMutate.SmoothGaussian: + imageEgmu = imageEgmu.SmoothGaussian((int)value); + break; + } + layer.Image = imageEgmu.ToImageSharpL8(); + imageEgmu.Dispose(); + }); + result = true; + } + catch (Exception ex) + { + MessageBox.Show($"{ex.Message}\nPlease try diferent values for the mutation", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + Invoke((MethodInvoker)delegate { + // Running on the UI thread + EnableGUI(true); + }); + } + + return result; + }); + + FrmLoading.ShowDialog(); + + ShowLayer(ActualLayer); + + menuFileSave.Enabled = + menuFileSaveAs.Enabled = true; + } } } diff --git a/PrusaSL1Viewer/FrmMain.resx b/PrusaSL1Viewer/FrmMain.resx index 89f675e0..ece3314a 100644 --- a/PrusaSL1Viewer/FrmMain.resx +++ b/PrusaSL1Viewer/FrmMain.resx @@ -143,37 +143,37 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAADg - CAAAAk1TRnQBSQFMAgEBAwEAATgBAAE4AQABEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA + CAAAAk1TRnQBSQFMAgEBAwEAASABAQEgAQEBEAEAARABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFA AwABEAMAAQEBAAEgBgABEFYAA1ABowNSAakDUgGpA1IBqQNSAakDUgGpA1IBqQNSAakDUgGpA1IBqQNS - AakDUgGpA1IBqQNQAaOEAANVAbQDVgHHAy8BSQMAAQEDGwEmAxwBJwMcAScDHAEnAxwBJwMcAScDHAEn + AakDUgGpA1IBqQNQAaOEAANVAbQDWQHHAy8BSQMAAQEDGwEmAxwBJwMcAScDHAEnAxwBJwMcAScDHAEn AxwBJwMcAScDHAEnAxwBJwMCAQMEAANSAakwAANSAakQAAMnAToDMAFMAzABTAMwAUwDMAFMAzABTAMw AUwDMAFMAzABTAMnATpPAAH/AwAB/wNDAXcDKQE+AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/AwAB/wMA Af8DAAH/AwAB/wMAAf8DMgFRBAADUgGpBAADUAGdA1MBqgNTAaoDUwGqA1MBqgNTAaoDUwGqA1ABnQwA - A1IBqRAAA04B+wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A04B+0wAA1EBogNVAbYDKgFA + A1IBqRAAA04B+wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/A04B+0wAA1EBogNWAbYDKgFA BAADEAEVAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxABFggAA1IBqQQAA1ABnQNT - AaoDUwGqAx8BLBwAA1IBqRAAA0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/0wA - AwoBDgMRARcDAAEBOAADUgGpMAADUgGpEAADSAH/A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/wNI - Af8DSAH/TAADKQH0AwAB/wM+AWwDDgETA0IBdgNDAXcDQwF3A0MBdwNDAXcDQwF3A0MBdwNDAXcDQwF3 + AaoDUwGqAx8BLBwAA1IBqRAAAysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/0wA + AwoBDgMRARcDAAEBOAADUgGpMAADUgGpEAADKwH/AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/wMr + Af8DKwH/TAADUgH0AwAB/wM+AWwDDgETA0IBdgNDAXcDQwF3A0MBdwNDAXcDQwF3A0MBdwNDAXcDQwF3 A0MBdwNCAXYDFAEbBAADUgGpAyIBMgNSAakDUgGpA1IBqQNSAakDUgGpA1IBqQNSAakDUgGpA1IBqQNS - AakDIgEyA1IBqRAAA0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/08AAf4DAAH/ - A0MBdwMeASsDVwHFA1YBxwNWAccDVgHHA1YBxwNWAccDVgHHA1YBxwNWAccDVgHHA1YBxgMmATkEAANS - AakDNAFVAzQBVSAAAzQBVQM0AVUDUgGpEAADSAH/A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/wNI - Af8DSAH/TAADMwFTAzwBZwMUARw4AANSAakDNAFVAzQBVQNGAYADUgGpA1IBqQNSAakDUgGpA1IBqQNS - AakDRQF/AzQBVQM0AVUDUgGpEAADSAH/A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/ + AakDIgEyA1IBqRAAAysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/08AAf4DAAH/ + A0MBdwMeASsDVwHFA1kBxwNZAccDWQHHA1kBxwNZAccDWQHHA1kBxwNZAccDWQHHA1gBxgMmATkEAANS + AakDNAFVAzQBVSAAAzQBVQM0AVUDUgGpEAADKwH/AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/wMr + Af8DKwH/TAADMwFTAzwBZwMUARw4AANSAakDNAFVAzQBVQNGAYADUgGpA1IBqQNSAakDUgGpA1IBqQNS + AakDRQF/AzQBVQM0AVUDUgGpEAADKwH/AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/ TAADMwFTAzwBZwMUARw4AANSAakDNAFVAzQBVQM/AW4DMgFQEAADJwE7A0QBfAM0AVUDNAFVA1IBqRAA - A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0gB/08AAf4DAAH/A0MBdwMfASwDVwHF - A1YBxwNWAccDVgHHA1YBxwNWAccDVgHHA1YBxwNWAccDVgHHA1YBxgMmATkEAANSAakDNAFVAzQBVQMF - AQcDVQG1AxEBFwNSAakDKQE+BAADUAGfAxEBFwM0AVUDNAFVA1IBqRAAA0gB/wNIAf8DSAH/A0gB/wNI - Af8DSAH/A0gB/wNIAf8DSAH/A0gB/0wAAykB9AMAAf8DPgFsAw4BEwNCAXUDQwF3A0MBdwNDAXcDQwF3 + AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/wMrAf8DKwH/AysB/08AAf4DAAH/A0MBdwMfASwDVwHF + A1kBxwNZAccDWQHHA1kBxwNZAccDWQHHA1kBxwNZAccDWQHHA1gBxgMmATkEAANSAakDNAFVAzQBVQMF + AQcDVQG1AxEBFwNSAakDKQE+BAADUAGfAxEBFwM0AVUDNAFVA1IBqRAAAysB/wMrAf8DKwH/AysB/wMr + Af8DKwH/AysB/wMrAf8DKwH/AysB/0wAA1IB9AMAAf8DPgFsAw4BEwNCAXUDQwF3A0MBdwNDAXcDQwF3 A0MBdwNDAXcDQwF3A0MBdwNDAXcDQwF3AxQBGwQAA1IBqQM0AVUDNAFVBAADPAFoA1YBvgMjATQDVQG1 - AxIBGQNRAaAEAAM0AVUDNAFVA1IBqRAAA0gB/wNIAf8DSAH/A0gB/wNIAf8DSAH/A0wB/wNRAf8DUQH/ - A1wB30wAAwoBDgMRARcDAAEBOAADUgGpAzQBVQM0AVUDAAEBAy0BRgMKAQ4EAAM5AV8DWAHOAygBPAQA - AzQBVQM0AVUDUgGpEAADSAH/A4IB/wNnAf8DUQH/A0gB/wNIAf8DWwH/A2IB/wNeAd8DFwEgTAADUQGi - A1UBtgMqAUAEAAMQARUDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEAEWCAADUgGp - AzQBVQM0AVUDMwFTA1IBpgNKAYwHAAEBA0cBgwgAAzQBVQM0AVUDUgGpEAADSAH/A5kB/wOFAf8DdAH/ - A0gB/wNIAf8DWwH/A14B3wMXASBTAAH/AwAB/wNDAXcDKQE+AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/ + AxIBGQNRAaAEAAM0AVUDNAFVA1IBqRAAAysB/wMrAf8DKwH/AysB/wMrAf8DKwH/Ay8B/wM0Af8DNAH/ + A1wB30wAAwoBDgMRARcDAAEBOAADUgGpAzQBVQM0AVUDAAEBAy0BRgMKAQ4EAAM5AV8DXAHOAygBPAQA + AzQBVQM0AVUDUgGpEAADKwH/A4IB/wNKAf8DNAH/AysB/wMrAf8DPgH/A0UB/wNcAd8DFwEgTAADUQGi + A1YBtgMqAUAEAAMQARUDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEQEXAxEBFwMRARcDEAEWCAADUgGp + AzQBVQM0AVUDMwFTA1IBpgNKAYwHAAEBA0cBgwgAAzQBVQM0AVUDUgGpEAADKwH/A5kB/wOFAf8DVwH/ + AysB/wMrAf8DPgH/A1wB3wMXASBTAAH/AwAB/wNDAXcDKQE+AwAB/wMAAf8DAAH/AwAB/wMAAf8DAAH/ AwAB/wMAAf8DAAH/AwAB/wMAAf8DMgFRBAADUgGpAzQBVQM0AVUDEQEXA1ABngMkATYUAAM0AVUDNAFV - A1IBqRAAA1AB+wNTAf8DUwH/A1MB/wNIAf8DSAH/A1wB3wMXASBUAANVAbQDVgHHAy8BSQMAAQEDGwEm + A1IBqRAAA1AB+wM2Af8DNgH/AzYB/wMrAf8DKwH/A1wB3wMXASBUAANVAbQDWQHHAy8BSQMAAQEDGwEm AxwBJwMcAScDHAEnAxwBJwMcAScDHAEnAxwBJwMcAScDHAEnAxwBJwMCAQMEAANSAakDIgEyA1IBqQNS AakDUgGpA1IBqQNSAakDUgGpA1IBqQNSAakDUgGpA1IBqQMiATIDUgGpEAADIAEuAykBPwMpAT8DKQE/ AykBPwMpAT8DEQEXnAADUAGjA1IBqQNSAakDUgGpA1IBqQNSAakDUgGpA1IBqQNSAakDUgGpA1IBqQNS diff --git a/PrusaSL1Viewer/ImageSharpExtensions.cs b/PrusaSL1Viewer/ImageSharpExtensions.cs index 0c92ba57..813d5b13 100644 --- a/PrusaSL1Viewer/ImageSharpExtensions.cs +++ b/PrusaSL1Viewer/ImageSharpExtensions.cs @@ -6,8 +6,11 @@ * of this license document, but changing it is not allowed. */ using System.IO; +using Emgu.CV; +using Emgu.CV.Structure; using PrusaSL1Reader; using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; namespace PrusaSL1Viewer { @@ -26,6 +29,15 @@ public static System.Drawing.Bitmap ToBitmap(this Image image) } } + public static Image ToEmguImage(this Image image) + { + return + new Image(image.Width, image.Height) + { + Bytes = Helpers.ImageL8ToBytes(image) + }; + } + /*public static Image ToImageSharpImage(this System.Drawing.Bitmap bitmap) where TPixel : struct, IPixel { using (var memoryStream = new MemoryStream()) diff --git a/PrusaSL1Viewer/Images/CNCMachine-16x16.png b/PrusaSL1Viewer/Images/CNCMachine-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..dbafe4d95eb37d3afdeb5432138e37458a9a62a2 GIT binary patch literal 375 zcmV--0f_#IP)xoG8x2RGEg?TK@1ip-G#C+T}NF8 zVsf3q4OjU7yWcB4dM|hX`qbV3eV*U@-19sa@=6uP9H!B&?+@6)7Cyaz3ZWJ!=*Kp$ zP(&Y=6tY+@Acl2K$T-iV*heegg97Z0DZS+e3rOnu8dh+I5%rpIhXFiUzz3#qj2s^E zs=+E9@sd1>!wkHP~yoVXBs#Co- zt?(z{8mCyTHo(rgS3koIn7|P>5Dy9%(+0L9>jYR0;uiU!fI7TjS@*#WaO+Qi)uxW7 z%PU>mVwnJ&)2w@t^aE^S8a3YkD}hNrpaIWlsUW~s8u0_>bsCjc!Vj>2<9HPTZ{MvS VQ`Vd6_%Z+h002ovPDHLkV1gtGq6+{3 literal 0 HcmV?d00001 diff --git a/PrusaSL1Viewer/Images/Geometry-16x16.png b/PrusaSL1Viewer/Images/Geometry-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..db04a86537a0c1c531f555b08b9daa4ae3818358 GIT binary patch literal 360 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 + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + + + diff --git a/PrusaSL1Viewer/Properties/AssemblyInfo.cs b/PrusaSL1Viewer/Properties/AssemblyInfo.cs index 19fd0f58..75ba680c 100644 --- a/PrusaSL1Viewer/Properties/AssemblyInfo.cs +++ b/PrusaSL1Viewer/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.3.3.1")] -[assembly: AssemblyFileVersion("0.3.3.1")] +[assembly: AssemblyVersion("0.4.0.0")] +[assembly: AssemblyFileVersion("0.4.0.0")] diff --git a/PrusaSL1Viewer/Properties/Resources.Designer.cs b/PrusaSL1Viewer/Properties/Resources.Designer.cs index ec8a4d63..ec18b715 100644 --- a/PrusaSL1Viewer/Properties/Resources.Designer.cs +++ b/PrusaSL1Viewer/Properties/Resources.Designer.cs @@ -100,6 +100,16 @@ internal static System.Drawing.Bitmap Cancel_32x32 { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap CNCMachine_16x16 { + get { + object obj = ResourceManager.GetObject("CNCMachine-16x16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -170,6 +180,16 @@ internal static System.Drawing.Bitmap File_Refresh_16x16 { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Geometry_16x16 { + get { + object obj = ResourceManager.GetObject("Geometry-16x16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -220,6 +240,16 @@ internal static System.Drawing.Bitmap Open_16x16 { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap pixel_16x16 { + get { + object obj = ResourceManager.GetObject("pixel-16x16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -260,6 +290,16 @@ internal static System.Drawing.Bitmap SaveAs_16x16 { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap search_16x16 { + get { + object obj = ResourceManager.GetObject("search-16x16", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/PrusaSL1Viewer/Properties/Resources.resx b/PrusaSL1Viewer/Properties/Resources.resx index 73b5d9e2..869850b6 100644 --- a/PrusaSL1Viewer/Properties/Resources.resx +++ b/PrusaSL1Viewer/Properties/Resources.resx @@ -124,8 +124,8 @@ ..\Images\File-Refresh-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Images\Open-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Images\Ok-24x24.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Images\layers-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -136,11 +136,14 @@ ..\Images\Global-Network-icon-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Images\search-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Images\Cancel-24x24.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Images\Ok-24x24.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Images\SaveAs-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a ..\Images\Back-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -154,6 +157,9 @@ ..\Images\Convert-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Images\Open-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Images\Button-Info-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -163,6 +169,12 @@ ..\Images\Extract-object-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Images\Geometry-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Images\pixel-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\PrusaSL1Viewer.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -178,7 +190,7 @@ ..\Images\Cancel-32x32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Images\SaveAs-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Images\CNCMachine-16x16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/PrusaSL1Viewer/PrusaSL1Viewer.csproj b/PrusaSL1Viewer/PrusaSL1Viewer.csproj index 12b991eb..56579646 100644 --- a/PrusaSL1Viewer/PrusaSL1Viewer.csproj +++ b/PrusaSL1Viewer/PrusaSL1Viewer.csproj @@ -1,6 +1,6 @@  - + Debug @@ -60,6 +60,15 @@ ..\packages\BinarySerializer.8.5.1\lib\net46\BinarySerializer.dll + + ..\packages\CyotekImageBox.1.3.0-Alpha1\lib\net20\Cyotek.Windows.Forms.ImageBox.dll + + + ..\packages\Emgu.CV.runtime.windows.4.2.0.3662\lib\net461\Emgu.CV.UI.dll + + + ..\packages\Emgu.CV.4.2.0.3662\lib\netstandard2.0\Emgu.CV.World.NetStandard.dll + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll @@ -116,7 +125,7 @@ True - ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0-preview.3.20214.6\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0-preview.4.20251.6\lib\net45\System.Runtime.CompilerServices.Unsafe.dll ..\packages\System.Runtime.Extensions.4.3.1\lib\net462\System.Runtime.Extensions.dll @@ -128,6 +137,9 @@ + + ..\packages\ZedGraph.5.1.7\lib\net35-Client\ZedGraph.dll + @@ -160,10 +172,10 @@ ImageBox.cs + - FrmAbout.cs @@ -244,6 +256,11 @@ + + + + + @@ -260,12 +277,15 @@ - xcopy /y $(ProjectDir)..\LICENSE $(ProjectDir)$(OutDir) + xcopy /y $(ProjectDir)..\LICENSE $(ProjectDir)$(OutDir) +xcopy /i /e /y $(ProjectDir)..\PrusaSlicer $(ProjectDir)$(OutDir)\PrusaSlicer This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + + \ No newline at end of file diff --git a/PrusaSL1Viewer/UniversalLayerExtensions.cs b/PrusaSL1Viewer/UniversalLayerExtensions.cs deleted file mode 100644 index 7c16638f..00000000 --- a/PrusaSL1Viewer/UniversalLayerExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using PrusaSL1Reader; -using SixLabors.ImageSharp; - -namespace PrusaSL1Viewer -{ - public static class UniversalLayerExtensions - { - public static Bitmap ToBitmap(this UniversalLayer layer, int width, int height) - { - Bitmap buffer = new Bitmap(width, height);//set the size of the image - Graphics gfx = Graphics.FromImage(buffer);//set the graphics to draw on the image - - foreach (var line in layer) - { - gfx.DrawRectangle(Pens.White, line.X, line.Y, line.Length, 1); - } - - return buffer; - } - } -} diff --git a/PrusaSL1Viewer/packages.config b/PrusaSL1Viewer/packages.config index c7353449..10e849a1 100644 --- a/PrusaSL1Viewer/packages.config +++ b/PrusaSL1Viewer/packages.config @@ -1,7 +1,10 @@  - + + + + @@ -17,8 +20,9 @@ - + + \ No newline at end of file diff --git a/PrusaSlicer/printer/Nova3D Elfin.ini b/PrusaSlicer/printer/Nova3D Elfin.ini new file mode 100644 index 00000000..26207579 --- /dev/null +++ b/PrusaSlicer/printer/Nova3D Elfin.ini @@ -0,0 +1,37 @@ +# generated by PrusaSlicer 2.2.0+win64 on 2020-05-27 at 03:07:44 UTC +absolute_correction = 0 +area_fill = 50 +bed_custom_model = +bed_custom_texture = +bed_shape = 0x0,130x0,130x70,0x70 +default_sla_material_profile = Prusa Orange Tough 0.05 +default_sla_print_profile = 0.05 Normal +display_height = 66.04 +display_mirror_x = 0 +display_mirror_y = 0 +display_orientation = portrait +display_pixels_x = 2560 +display_pixels_y = 1440 +display_width = 120.96 +elefant_foot_compensation = 0.2 +elefant_foot_min_width = 0.2 +fast_tilt_time = 5 +gamma_correction = 1 +inherits = Original Prusa SL1 +max_exposure_time = 120 +max_initial_exposure_time = 300 +max_print_height = 150 +min_exposure_time = 1 +min_initial_exposure_time = 1 +print_host = +printer_model = SL1 +printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_SL1\nPRINTER_VENDOR_NOVA3D\nPRINTER_MODEL_ELFIN\nSTART_CUSTOM_VALUES\nXppm_19.324\nYppm_19.324\nWaitBeforeExpoMs_2000\nLiftDistance_4\nLiftUpSpeed_120\nLiftDownSpeed_120\nLiftWhenFinished_80\nBlankingLayerTime_0\nEND_CUSTOM_VALUES +printer_settings_id = +printer_technology = SLA +printer_variant = default +printer_vendor = +printhost_apikey = +printhost_cafile = +relative_correction = 1,1 +slow_tilt_time = 8 +thumbnails = 400x400,800x480 diff --git a/PrusaSlicer/printer/Phrozen Sonic Mini.ini b/PrusaSlicer/printer/Phrozen Sonic Mini.ini index 15860132..89a571bf 100644 --- a/PrusaSlicer/printer/Phrozen Sonic Mini.ini +++ b/PrusaSlicer/printer/Phrozen Sonic Mini.ini @@ -1,4 +1,4 @@ -# generated by PrusaSlicer 2.2.0+win64 on 2020-05-19 at 02:50:50 UTC +# generated by PrusaSlicer 2.2.0+win64 on 2020-05-26 at 15:23:26 UTC absolute_correction = 0 area_fill = 50 bed_custom_model = @@ -7,7 +7,7 @@ bed_shape = 0x0,120x0,120x66,0x66 default_sla_material_profile = Prusa Orange Tough 0.05 default_sla_print_profile = 0.05 Normal display_height = 66.04 -display_mirror_x = 1 +display_mirror_x = 0 display_mirror_y = 0 display_orientation = portrait display_pixels_x = 1920 diff --git a/README.md b/README.md index 9d957f28..909d7100 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,12 @@ But also, i need victims for test subject. Proceed at your own risk! ## Known Formats * SL1 (Prusa SL1) +* Photon * CBDDLP (Chitubox DLP) * CBT (Chitubox) -* Photon +* PHZ * ZCodex +* CWS ## Install and configure printer under PrusaSlicer @@ -55,13 +57,8 @@ But also, i need victims for test subject. Proceed at your own risk! 1. Start and configure PrusaSlicer (Wizard) * Choose SL1 printer 1. Close PrusaSlicer -1. On different operating systems open: - * Windows: "%AppData%\PrusaSlicer" - * macOS: "/Users/[Username]/Library/Application Support/PrusaSlicer" - * Linux: - * Stable build: "~/.PrusaSlicer/" - * Alpha build: "~/.PrusaSlicer-alpha/" -1. Copy GitHub PrusaSlicer subfolder ("printer") to the folder above +1. Open PrusaSL1Viewer +1. Under Menu click -> About -> Install printers into PrusaSlicer 1. Open PrusaSlicer and check if profiles are there 1. To clean up interface remove printers that you will not use (OPTIONAL) 1. Duplicate or create your printer and tune the values if required