Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sn4k3/UVtools.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTiago Conceição <Tiago_caza@hotmail.com>2021-06-12 05:56:20 +0300
committerTiago Conceição <Tiago_caza@hotmail.com>2021-06-12 05:56:20 +0300
commit5d61e95331329a842c8547c04f40c68bcc922693 (patch)
treeb1f23594e678af6ec4c379d85282c5c374d306cf
parent470f5ec1cf87fff04ed88044e3b3beeeabd55609 (diff)
v2.13.3v2.13.3
- **File formats:** - (Add) CXDLP v2 - (Improved) GR1, MDLP, CXDLP decode and encode performance and memory optimization - (Remove) CXDLP v1 from available formats - (Add) Pixel editor - Drawing: New brushes of shapes/polygons - (Upgrade) .NET from 5.0.6 to 5.0.7 - (Fix) When there are issues on the list, executing any operation will navigate to the last layer - (Fix) PrusaSlicer printer: Rename "Creality HALOT-SKY CL-60" to "Creality HALOT-ONE CL-60"
-rw-r--r--CHANGELOG.md11
-rw-r--r--CREDITS.md3
-rw-r--r--PrusaSlicer/printer/Creality HALOT-ONE CL-60.ini (renamed from PrusaSlicer/printer/Creality HALOT-SKY CL-60.ini)2
-rw-r--r--Scripts/ImportPrusaSlicerData.bat4
-rw-r--r--Scripts/cxdlp_v2.bt11
-rw-r--r--UVtools.Core/Extensions/BitExtensions.cs73
-rw-r--r--UVtools.Core/Extensions/DrawingExtensions.cs126
-rw-r--r--UVtools.Core/Extensions/EmguExtensions.cs7
-rw-r--r--UVtools.Core/Extensions/FileStreamExtensions.cs7
-rw-r--r--UVtools.Core/Extensions/PointExtensions.cs24
-rw-r--r--UVtools.Core/FileFormats/CXDLPFile.cs325
-rw-r--r--UVtools.Core/FileFormats/CXDLPv1File.cs774
-rw-r--r--UVtools.Core/FileFormats/ChituboxFile.cs7
-rw-r--r--UVtools.Core/FileFormats/FileFormat.cs1
-rw-r--r--UVtools.Core/FileFormats/GR1File.cs152
-rw-r--r--UVtools.Core/FileFormats/MDLPFile.cs150
-rw-r--r--UVtools.Core/Helpers.cs10
-rw-r--r--UVtools.Core/Layer/Layer.cs20
-rw-r--r--UVtools.Core/Layer/LayerIssue.cs19
-rw-r--r--UVtools.Core/Layer/LayerManager.cs20
-rw-r--r--UVtools.Core/Objects/RangeObservableCollection.cs2
-rw-r--r--UVtools.Core/Operations/OperationRepairLayers.cs181
-rw-r--r--UVtools.Core/PixelEditor/PixelDrawing.cs27
-rw-r--r--UVtools.Core/UVtools.Core.csproj2
-rw-r--r--UVtools.InstallerMM/UVtools.InstallerMM.wxs8
-rw-r--r--UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs2
-rw-r--r--UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml52
-rw-r--r--UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs2
-rw-r--r--UVtools.WPF/MainWindow.Issues.cs5
-rw-r--r--UVtools.WPF/MainWindow.LayerPreview.cs53
-rw-r--r--UVtools.WPF/MainWindow.PixelEditor.cs114
-rw-r--r--UVtools.WPF/MainWindow.axaml52
-rw-r--r--UVtools.WPF/MainWindow.axaml.cs2
-rw-r--r--UVtools.WPF/UVtools.WPF.csproj2
-rw-r--r--UVtools.WPF/UserSettings.cs7
-rw-r--r--UVtools.WPF/Windows/SettingsWindow.axaml9
-rw-r--r--UVtools.WPF/Windows/ToolWindow.axaml.cs23
38 files changed, 1888 insertions, 403 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a5be9d..e04a2f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
# Changelog
+## 12/06/2021 - v2.13.3
+
+- **File formats:**
+ - (Add) CXDLP v2
+ - (Improved) GR1, MDLP, CXDLP decode and encode performance and memory optimization
+ - (Remove) CXDLP v1 from available formats
+- (Add) Pixel editor - Drawing: New brushes of shapes/polygons
+- (Upgrade) .NET from 5.0.6 to 5.0.7
+- (Fix) When there are issues on the list, executing any operation will navigate to the last layer
+- (Fix) PrusaSlicer printer: Rename "Creality HALOT-SKY CL-60" to "Creality HALOT-ONE CL-60"
+
## 06/06/2021 - v2.13.2
- (Upgrade) AvaloniaUI from 0.10.5 to 0.10.6
diff --git a/CREDITS.md b/CREDITS.md
index acefb73..8b26865 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -54,4 +54,5 @@
* Dennis Hansen
* Evert Goor
* James Kao
-* Finn Newick \ No newline at end of file
+* Finn Newick
+* Thomas Wilbert \ No newline at end of file
diff --git a/PrusaSlicer/printer/Creality HALOT-SKY CL-60.ini b/PrusaSlicer/printer/Creality HALOT-ONE CL-60.ini
index 0a013c2..7e49667 100644
--- a/PrusaSlicer/printer/Creality HALOT-SKY CL-60.ini
+++ b/PrusaSlicer/printer/Creality HALOT-ONE CL-60.ini
@@ -1,4 +1,4 @@
-# generated by PrusaSlicer 2.3.0+win64 on 2021-04-14 at 04:57:50 UTC
+# generated by PrusaSlicer 2.3.1+win64 on 2021-06-08 at 21:10:32 UTC
absolute_correction = 0
area_fill = 50
bed_custom_model =
diff --git a/Scripts/ImportPrusaSlicerData.bat b/Scripts/ImportPrusaSlicerData.bat
index 525dfcf..4fe6b5d 100644
--- a/Scripts/ImportPrusaSlicerData.bat
+++ b/Scripts/ImportPrusaSlicerData.bat
@@ -58,8 +58,8 @@ SET files[47]=Wanhao CGR Mono.ini
SET files[48]=Creality LD-002R.ini
SET files[49]=Creality LD-002H.ini
SET files[50]=Creality LD-006.ini
-SET files[51]=Creality HALOT-SKY CL-89.ini
-SET files[52]=Creality HALOT-SKY CL-60.ini
+SET files[51]=Creality HALOT-ONE CL-60.ini
+SET files[52]=Creality HALOT-SKY CL-89.ini
SET files[53]=Voxelab Polaris 5.5.ini
SET files[54]=Voxelab Proxima 6.ini
SET files[55]=Voxelab Ceres 8.9.ini
diff --git a/Scripts/cxdlp_v2.bt b/Scripts/cxdlp_v2.bt
index aaccf46..0c27e76 100644
--- a/Scripts/cxdlp_v2.bt
+++ b/Scripts/cxdlp_v2.bt
@@ -4,6 +4,11 @@
// File: Creality
// Authors: Julien Delnatte
//------------------------------------------------
+// CHANGELOG:
+// Add uint16 unknown after header (Set to 2)
+// Add uint32 modelSize and BYTE model[modelSize] after unknown
+// Add byte offset[64] after resolutionY (Zeros)
+
typedef struct {
BitfieldDisablePadding(); ushort startY:13; ushort endY:13; ushort x:14;
@@ -39,6 +44,8 @@ struct HEADER {
uint16 resolutionX <fgcolor=cBlack, bgcolor=cWhite>;
uint16 resolutionY <fgcolor=cBlack, bgcolor=cWhite>;
+ byte offset[64];
+
rgbPreviewImageRawData preview(116*116*2);
BYTE rn0[2] <fgcolor=cBlack, bgcolor=cRed>;
@@ -46,9 +53,7 @@ struct HEADER {
BYTE rn1[2] <fgcolor=cBlack, bgcolor=cRed>;
rgbPreviewImageRawData preview2(290*290*2);
- BYTE rn2[2] <fgcolor=cBlack, bgcolor=cRed>;
-
- byte offset[64];
+ BYTE rn2[2] <fgcolor=cBlack, bgcolor=cRed>;
uint32 plateformXLength <fgcolor=cBlack, bgcolor=cWhite>;
wchar_t plateformX[plateformXLength/2];
diff --git a/UVtools.Core/Extensions/BitExtensions.cs b/UVtools.Core/Extensions/BitExtensions.cs
index 7df1876..8cb7dbd 100644
--- a/UVtools.Core/Extensions/BitExtensions.cs
+++ b/UVtools.Core/Extensions/BitExtensions.cs
@@ -11,5 +11,78 @@ namespace UVtools.Core.Extensions
{
public static ushort ToUShortLittleEndian(byte byte1, byte byte2) => (ushort)(byte1 + (byte2 << 8));
public static ushort ToUShortBigEndian(byte byte1, byte byte2) => (ushort)((byte1 << 8) + byte2);
+
+ public static ushort ToUShortLittleEndian(byte[] buffer, int offset = 0)
+ => (ushort)(buffer[offset] + (buffer[offset+1] << 8));
+ public static ushort ToUShortBigEndian(byte[] buffer, int offset = 0)
+ => (ushort)((buffer[offset] << 8) + buffer[offset+1]);
+
+ public static uint ToUIntLittleEndian(byte byte1, byte byte2, byte byte3, byte byte4)
+ => (uint)(byte1 + (byte2 << 8) + (byte3 << 16) + (byte4 << 24));
+ public static uint ToUIntBigEndian(byte byte1, byte byte2, byte byte3, byte byte4)
+ => (uint)((byte1 << 24) + (byte1 << 16) + (byte1 << 8) + byte2);
+
+ public static uint ToUIntLittleEndian(byte[] buffer, int offset = 0)
+ => (uint)(buffer[offset] + (buffer[offset + 1] << 8) + (buffer[offset + 2] << 16) + (buffer[offset + 3] << 24));
+ public static uint ToUIntBigEndian(byte[] buffer, int offset = 0)
+ => (uint)((buffer[offset] << 24) + (buffer[offset+1] << 16) + (buffer[offset+2] << 8) + buffer[offset+3]);
+
+ public static byte[] ToBytesLittleEndian(ushort value)
+ {
+ var bytes = new byte[2];
+ ToBytesLittleEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesLittleEndian(ushort value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)value;
+ buffer[offset + 1] = (byte)(value >> 8);
+ }
+
+ public static byte[] ToBytesBigEndian(ushort value)
+ {
+ var bytes = new byte[2];
+ ToBytesBigEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesBigEndian(ushort value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)(value >> 8);
+ buffer[offset + 1] = (byte)value;
+ }
+
+ public static byte[] ToBytesLittleEndian(uint value)
+ {
+ var bytes = new byte[4];
+ ToBytesLittleEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesLittleEndian(uint value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)value;
+ buffer[offset + 1] = (byte)(value >> 8);
+ buffer[offset + 2] = (byte)(value >> 16);
+ buffer[offset + 3] = (byte)(value >> 24);
+ }
+
+ public static byte[] ToBytesBigEndian(uint value)
+ {
+ var bytes = new byte[4];
+ ToBytesBigEndian(value, bytes);
+ return bytes;
+ }
+
+ public static void ToBytesBigEndian(uint value, byte[] buffer, uint offset = 0)
+ {
+ buffer[offset] = (byte)(value >> 24);
+ buffer[offset + 1] = (byte)(value >> 16);
+ buffer[offset + 2] = (byte)(value >> 8);
+ buffer[offset + 3] = (byte)value;
+ }
+
+
}
}
diff --git a/UVtools.Core/Extensions/DrawingExtensions.cs b/UVtools.Core/Extensions/DrawingExtensions.cs
index db5ec69..d8f4385 100644
--- a/UVtools.Core/Extensions/DrawingExtensions.cs
+++ b/UVtools.Core/Extensions/DrawingExtensions.cs
@@ -21,88 +21,23 @@ namespace UVtools.Core.Extensions
return Color.FromArgb(r, g, b);
}
- public static double CalculateSideLength(int sides, int radius)
+ public static double CalculatePolygonSideLengthFromRadius(double radius, int sides)
{
return 2 * radius * Math.Sin(Math.PI / sides);
}
- /*public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0)
+ public static double CalculatePolygonVerticalLengthFromRadius(double radius, int sides)
{
- if (sides < 3)
- throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
-
-
- // Fix rotation
- switch (sides)
- {
- case 3:
- startingAngle += 90;
- break;
- case 4:
- startingAngle += 45;
- break;
- case 5:
- startingAngle += 22.5;
- break;
- }
-
- var points = new Point[sides];
- var step = 360.0 / sides;
- int i = 0;
- for (var angle = startingAngle; angle < startingAngle + 360.0; angle += step) //go in a circle
- {
- if (i == sides) break; // Fix floating problem
- double radians = angle * Math.PI / 180.0;
- points[i++] = new(
- (int) Math.Round(Math.Cos(radians) * radius + center.X),
- (int) Math.Round(Math.Sin(-radians) * radius + center.Y)
- );
- }
-
- return points;
- }*/
+ return radius * Math.Cos(Math.PI / sides);
+ }
- /*public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0)
+ public static double CalculatePolygonRadiusFromSideLength(double length, int sides)
{
- startingAngle = -45;
- if (sides < 3)
- throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
-
- var vertex = new Point[sides];
- //var deg = (180.0 * (sides - 2)) / sides + startingAngle;
- var deg = ((180.0 * (sides - 2) / sides) - 180) / 2 + startingAngle;
- var step = 360.0 / sides;
- var rad = deg * (Math.PI / 180);
-
- double nSinDeg = Math.Sin(rad);
- double nCosDeg = Math.Cos(rad);
-
- vertex[0] = center;
- //vertex[0].X += radius;
- vertex[0].Y -= radius;
- //vertex[0].X += (int)Math.Cos(deg) / 2 *radius;
- //vertex[0].Y -= (int)Math.Sin(deg) / 2 *radius;
- int length = (int)Math.Round(CalculateSideLength(sides, radius));
-
- for (int i = 1; i < vertex.Length; i++)
- {
- vertex[i] = new(
- (int)Math.Round(vertex[i - 1].X - nCosDeg * length),
- (int)Math.Round(vertex[i - 1].Y - nSinDeg * length));
-
-
- //recalculate the degree for the next vertex
- deg -= step;
- rad = deg * (Math.PI / 180);
-
- nSinDeg = Math.Sin(rad);
- nCosDeg = Math.Cos(rad);
-
- }
- return vertex;
- }*/
+ var theta = 360.0 / sides;
+ return length / (2 * Math.Cos((90 - theta / 2) * Math.PI / 180.0));
+ }
- public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0)
+ public static Point[] GetPolygonVertices(int sides, int radius, Point center, double startingAngle = 0, bool flipHorizontally = false, bool flipVertically = false)
{
if (sides < 3)
throw new ArgumentException("Polygons can't have less than 3 sides...", nameof(sides));
@@ -110,18 +45,23 @@ namespace UVtools.Core.Extensions
var vertices = new Point[sides];
double deg = 360.0 / sides;//calculate the rotation angle
- //double a = radius * Math.Cos(Math.PI / sides);//calculate vertical length
- //double s = CalculateSideLength(sides, radius);//calculate the side length
var rad = Math.PI / 180.0;
+ var x0 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + startingAngle) * rad);
+ var y0 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + startingAngle) * rad);
+
+ var x1 = center.X + radius * Math.Cos(-(((180 - deg) / 2) + deg + startingAngle) * rad);
+ var y1 = center.Y - radius * Math.Sin(-(((180 - deg) / 2) + deg + startingAngle) * rad);
+
vertices[0] = new(
- (int) Math.Round(center.X + radius * Math.Cos(-(((180 - deg) / 2) + startingAngle) * rad)),
- (int) Math.Round(center.Y - radius * Math.Sin(-(((180 - deg) / 2) + startingAngle) * rad)));
+ (int) Math.Round(x0),
+ (int) Math.Round(y0)
+ );
vertices[1] = new(
- (int) Math.Round(center.X + radius * Math.Cos(-(((180 - deg) / 2) + deg + startingAngle) * rad)),
- (int) Math.Round(center.Y - radius * Math.Sin(-(((180 - deg) / 2) + deg + startingAngle) * rad)
- ));
+ (int) Math.Round(x1),
+ (int) Math.Round(y1)
+ );
for (int i = 0; i < sides - 2; i++)
{
@@ -129,11 +69,31 @@ namespace UVtools.Core.Extensions
double dcosrot = Math.Cos((deg * (i + 1)) * rad);
vertices[i + 2] = new(
- (int)Math.Round(center.X + dcosrot * (vertices[1].X - center.X) - dsinrot * (vertices[1].Y - center.Y)),
- (int)Math.Round(center.Y + dsinrot * (vertices[1].X - center.X) + dcosrot * (vertices[1].Y - center.Y))
+ (int)Math.Round(center.X + dcosrot * (x1 - center.X) - dsinrot * (y1 - center.Y)),
+ (int)Math.Round(center.Y + dsinrot * (x1 - center.X) + dcosrot * (y1 - center.Y))
);
}
+ if (flipHorizontally)
+ {
+ var startX = center.X - radius;
+ var endX = center.X + radius;
+ for (int i = 0; i < sides; i++)
+ {
+ vertices[i].X = endX - (vertices[i].X - startX);
+ }
+ }
+
+ if (flipVertically)
+ {
+ var startY = center.Y - radius;
+ var endY = center.Y + radius;
+ for (int i = 0; i < sides; i++)
+ {
+ vertices[i].Y = endY - (vertices[i].Y - startY);
+ }
+ }
+
return vertices;
}
}
diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs
index 1963528..0399fa6 100644
--- a/UVtools.Core/Extensions/EmguExtensions.cs
+++ b/UVtools.Core/Extensions/EmguExtensions.cs
@@ -608,7 +608,8 @@ namespace UVtools.Core.Extensions
/// <param name="startingAngle"></param>
/// <param name="thickness"></param>
/// <param name="lineType"></param>
- public static void DrawPolygon(this Mat src, int sides, int radius, Point center, MCvScalar color, double startingAngle = 0, int thickness = -1, LineType lineType = LineType.EightConnected)
+ /// <param name="flip"></param>
+ public static void DrawPolygon(this Mat src, int sides, int radius, Point center, MCvScalar color, double startingAngle = 0, int thickness = -1, LineType lineType = LineType.EightConnected, FlipType flip = FlipType.None)
{
if (sides == 1)
{
@@ -625,7 +626,9 @@ namespace UVtools.Core.Extensions
return;
}
- var points = DrawingExtensions.GetPolygonVertices(sides, radius, center, startingAngle);
+ var points = DrawingExtensions.GetPolygonVertices(sides, radius, center, startingAngle,
+ (flip & FlipType.Horizontal) != 0, (flip & FlipType.Vertical) != 0);
+
if (thickness <= 0)
{
using var vec = new VectorOfPoint(points);
diff --git a/UVtools.Core/Extensions/FileStreamExtensions.cs b/UVtools.Core/Extensions/FileStreamExtensions.cs
index 4e13028..b0370f2 100644
--- a/UVtools.Core/Extensions/FileStreamExtensions.cs
+++ b/UVtools.Core/Extensions/FileStreamExtensions.cs
@@ -17,6 +17,13 @@ namespace UVtools.Core.Extensions
return (uint)fs.Read(bytes, offset, bytes.Length);
}
+ public static byte[] ReadBytes(this FileStream fs, int length, int offset = 0)
+ {
+ var buffer = new byte[length];
+ fs.Read(buffer, offset, length);
+ return buffer;
+ }
+
public static uint WriteStream(this FileStream fs, MemoryStream stream, int offset = 0)
{
return fs.WriteBytes(stream.ToArray(), offset);
diff --git a/UVtools.Core/Extensions/PointExtensions.cs b/UVtools.Core/Extensions/PointExtensions.cs
index ac81e26..01bcc96 100644
--- a/UVtools.Core/Extensions/PointExtensions.cs
+++ b/UVtools.Core/Extensions/PointExtensions.cs
@@ -42,13 +42,35 @@ namespace UVtools.Core.Extensions
return new((float) x, (float) y);
}
- public static Point Half(this Point point)=> new(point.X / 2, point.Y / 2);
+ public static void Rotate(Point[] points, double angleDegree, Point pivot = default)
+ {
+ for (int i = 0; i < points.Length; i++)
+ {
+ points[i] = points[i].Rotate(angleDegree, pivot);
+ }
+ }
+
+ public static void Rotate(PointF[] points, double angleDegree, PointF pivot = default)
+ {
+ for (int i = 0; i < points.Length; i++)
+ {
+ points[i] = points[i].Rotate(angleDegree, pivot);
+ }
+ }
+
+ public static Point OffsetBy(this Point point, int value)=> new(point.X + value, point.Y + value);
+ public static Point OffsetBy(this Point point, int x, int y) => new(point.X + x, point.Y + y);
+ public static Point OffsetBy(this Point point, Point other) => new(point.X + other.X, point.Y + other.Y);
+
+ public static Point Half(this Point point) => new(point.X / 2, point.Y / 2);
public static PointF Half(this PointF point) => new(point.X / 2, point.Y / 2);
public static Size ToSize(this Point point) => new(point.X, point.Y);
public static SizeF ToSize(this PointF point) => new(point.X, point.Y);
+
+
}
}
diff --git a/UVtools.Core/FileFormats/CXDLPFile.cs b/UVtools.Core/FileFormats/CXDLPFile.cs
index 305cd08..4e88af8 100644
--- a/UVtools.Core/FileFormats/CXDLPFile.cs
+++ b/UVtools.Core/FileFormats/CXDLPFile.cs
@@ -6,8 +6,6 @@
* of this license document, but changing it is not allowed.
*/
-// https://github.com/cbiffle/catibo/blob/master/doc/cbddlp-ctb.adoc
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -21,6 +19,7 @@ using BinarySerialization;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
+using MoreLinq;
using UVtools.Core.Extensions;
using UVtools.Core.Operations;
@@ -31,15 +30,14 @@ namespace UVtools.Core.FileFormats
#region Constants
private const byte HEADER_SIZE = 9; // CXSW3DV2
private const string HEADER_VALUE = "CXSW3DV2";
-
-
- private const uint SlicerInfoAddress = 4 + HEADER_SIZE + 6 + 290 * 290 * 4 + 116 * 116 * 2 + 6;
#endregion
#region Sub Classes
#region Header
public sealed class Header
{
+ private string _printerModel = "CL-89";
+
/// <summary>
/// Gets the size of the header
/// </summary>
@@ -55,26 +53,68 @@ namespace UVtools.Core.FileFormats
[SerializeAs(SerializedType.TerminatedString)]
public string HeaderValue { get; set; } = HEADER_VALUE;
+ [FieldOrder(2)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort Unknown { get; set; } = 2;
+
+ /// <summary>
+ /// Gets the size of the printer model
+ /// </summary>
+ [FieldOrder(3)]
+ [FieldEndianness(Endianness.Big)]
+ public uint PrinterModelSize { get; set; } = 6;
+
+ /// <summary>
+ /// Gets the printer model
+ /// </summary>
+ /*[FieldOrder(4)]
+ [FieldLength(nameof(PrinterModelSize), BindingMode = BindingMode.OneWay)]
+ [SerializeAs(SerializedType.TerminatedString)]
+ public string PrinterModel
+ {
+ get => _printerModel;
+ set
+ {
+ _printerModel = value;
+ PrinterModelSize = string.IsNullOrEmpty(value) ? 0 : (uint)value.Length+1;
+ }
+ }*/
+
+ [FieldOrder(4)]
+ [FieldLength(nameof(PrinterModelSize))]
+ public byte[] PrinterModelArray { get; set; }
+
+ [Ignore]
+ public string PrinterModel
+ {
+ get => Encoding.ASCII.GetString(PrinterModelArray).TrimEnd(char.MinValue);
+ set => PrinterModelArray = Encoding.ASCII.GetBytes(value+char.MinValue);
+ }
+
/// <summary>
/// Gets the number of records in the layer table
/// </summary>
- [FieldOrder(2)]
+ [FieldOrder(5)]
[FieldEndianness(Endianness.Big)]
public ushort LayerCount { get; set; }
/// <summary>
/// Gets the printer resolution along X axis, in pixels. This information is critical to correctly decoding layer images.
/// </summary>
- [FieldOrder(3)]
+ [FieldOrder(6)]
[FieldEndianness(Endianness.Big)]
public ushort ResolutionX { get; set; }
/// <summary>
/// Gets the printer resolution along Y axis, in pixels. This information is critical to correctly decoding layer images.
/// </summary>
- [FieldOrder(4)]
+ [FieldOrder(7)]
[FieldEndianness(Endianness.Big)]
public ushort ResolutionY { get; set; }
+
+ [FieldOrder(8)]
+ [FieldLength(64)]
+ public byte[] Offset { get; set; } = new byte[64];
public void Validate()
{
@@ -83,12 +123,17 @@ namespace UVtools.Core.FileFormats
throw new FileLoadException("Not a valid CXDLP file!");
}
}
+
+ public override string ToString()
+ {
+ return $"{nameof(HeaderSize)}: {HeaderSize}, {nameof(HeaderValue)}: {HeaderValue}, {nameof(Unknown)}: {Unknown}, {nameof(PrinterModelSize)}: {PrinterModelSize}, {nameof(PrinterModelArray)}: {PrinterModelArray}, {nameof(PrinterModel)}: {PrinterModel}, {nameof(LayerCount)}: {LayerCount}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}, {nameof(Offset)}: {Offset}";
+ }
}
#endregion
#region SlicerInfo
- // Address: 363337
+ // Address: 363407
public sealed class SlicerInfo
{
[FieldOrder(0)]
@@ -158,6 +203,11 @@ namespace UVtools.Core.FileFormats
[FieldOrder(16)]
[FieldEndianness(Endianness.Big)]
public ushort LightPWM { get; set; } = 255;
+
+ public override string ToString()
+ {
+ return $"{nameof(DisplayWidthDataSize)}: {DisplayWidthDataSize}, {nameof(DisplayWidthBytes)}: {DisplayWidthBytes}, {nameof(DisplayHeightDataSize)}: {DisplayHeightDataSize}, {nameof(DisplayHeightBytes)}: {DisplayHeightBytes}, {nameof(LayerHeightDataSize)}: {LayerHeightDataSize}, {nameof(LayerHeightBytes)}: {LayerHeightBytes}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(BottomExposureTime)}: {BottomExposureTime}, {nameof(BottomLayers)}: {BottomLayers}, {nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(LightPWM)}: {LightPWM}";
+ }
}
#endregion
@@ -186,6 +236,14 @@ namespace UVtools.Core.FileFormats
[FieldOrder(2)] [FieldCount(nameof(LineCount))] public LayerLine[] Lines { get; set; }
[FieldOrder(3)] public PageBreak PageBreak { get; set; } = new();
+ public static byte[] GetHeaderBytes(uint unknown, uint lineCount)
+ {
+ var bytes = new byte[8];
+ BitExtensions.ToBytesBigEndian(unknown, bytes);
+ BitExtensions.ToBytesBigEndian(lineCount, bytes, 4);
+ return bytes;
+ }
+
public LayerDef() { }
public LayerDef(uint unknown, uint lineCount, LayerLine[] lines)
@@ -212,6 +270,18 @@ namespace UVtools.Core.FileFormats
[Ignore] public ushort StartX => (ushort)(((Coordinates[3] << 8) + Coordinates[4]) & 0x3FFF); // 14 bits
[Ignore] public ushort Length => (ushort) (EndY - StartY);
+ public static byte[] GetBytes(ushort startY, ushort endY, ushort startX, byte gray)
+ {
+ var bytes = new byte[CoordinateCount + 1];
+ bytes[0] = (byte)((startY >> 5) & 0xFF);
+ bytes[1] = (byte)(((startY << 3) + (endY >> 10)) & 0xFF);
+ bytes[2] = (byte)((endY >> 2) & 0xFF);
+ bytes[3] = (byte)(((endY << 6) + (startX >> 8)) & 0xFF);
+ bytes[4] = (byte)startX;
+ bytes[5] = gray;
+ return bytes;
+ }
+
public LayerLine() { }
public LayerLine(ushort startY, ushort endY, ushort startX, byte gray)
@@ -226,10 +296,17 @@ namespace UVtools.Core.FileFormats
StartX = startX;*/
Gray = gray;
}
+
+ public override string ToString()
+ {
+ return $"{nameof(Gray)}: {Gray}, {nameof(StartY)}: {StartY}, {nameof(EndY)}: {EndY}, {nameof(StartX)}: {StartX}, {nameof(Length)}: {Length}";
+ }
}
public sealed class PageBreak
{
+ public static byte[] Bytes => new byte[] {0x0D, 0x0A};
+
[FieldOrder(0)] public byte Line { get; set; } = 0x0D;
[FieldOrder(1)] public byte Break { get; set; } = 0x0A;
}
@@ -460,6 +537,12 @@ namespace UVtools.Core.FileFormats
set => base.LightPWM = (byte) (SlicerInfoSettings.LightPWM = value);
}
+ public override string MachineName
+ {
+ get => HeaderSettings.PrinterModel;
+ set => base.MachineName = HeaderSettings.PrinterModel = value;
+ }
+
public override object[] Configs => new object[] { HeaderSettings, SlicerInfoSettings, FooterSettings };
#endregion
@@ -473,8 +556,18 @@ namespace UVtools.Core.FileFormats
{
using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write);
+ if (ResolutionX == 2560 && ResolutionY == 1620)
+ {
+ MachineName = "CL-60";
+ }
+ else if (ResolutionX == 3840 && ResolutionY == 2400)
+ {
+ MachineName = "CL-89";
+ }
+
+ var pageBreak = PageBreak.Bytes;
+
Helpers.SerializeWriteFileStream(outputFile, HeaderSettings);
- var pageBreak = new PageBreak();
byte[][] previews = new byte[ThumbnailsOriginalSize.Length][];
for (int i = 0; i < ThumbnailsOriginalSize.Length; i++)
@@ -509,30 +602,103 @@ namespace UVtools.Core.FileFormats
for (int i = 0; i < ThumbnailsOriginalSize.Length; i++)
{
Helpers.SerializeWriteFileStream(outputFile, previews[i]);
- Helpers.SerializeWriteFileStream(outputFile, pageBreak);
+ outputFile.WriteBytes(pageBreak);
+ //Helpers.SerializeWriteFileStream(outputFile, pageBreak);
}
Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings);
progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
- var preLayers = new PreLayer[LayerCount];
- var layerDefs = new LayerDef[LayerCount];
+ //var preLayers = new PreLayer[LayerCount];
+ //var layerDefs = new LayerDef[LayerCount];
+ //var layersStreams = new MemoryStream[LayerCount];
+
for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
- var layer = this[layerIndex];
- preLayers[layerIndex] = new(layer.NonZeroPixelCount);
+ //var layer = this[layerIndex];
+ outputFile.WriteBytes(BitExtensions.ToBytesBigEndian(this[layerIndex].NonZeroPixelCount));
+ //preLayers[layerIndex] = new(layer.NonZeroPixelCount);
}
- Helpers.SerializeWriteFileStream(outputFile, preLayers);
- Helpers.SerializeWriteFileStream(outputFile, pageBreak);
+ //Helpers.SerializeWriteFileStream(outputFile, preLayers);
+ //Helpers.SerializeWriteFileStream(outputFile, pageBreak);
+ outputFile.WriteBytes(pageBreak);
- Parallel.For(0, LayerCount, layerIndex =>
+ var range = Enumerable.Range(0, (int) LayerCount);
+
+ var layerBytes = new List<byte>[LayerCount];
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
+ {
+ progress.Token.ThrowIfCancellationRequested();
+
+ Parallel.ForEach(batch, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ var layer = this[layerIndex];
+ using var mat = layer.LayerMat;
+ var span = mat.GetDataByteSpan();
+
+ layerBytes[layerIndex] = new();
+
+ uint lineCount = 0;
+
+ for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++)
+ {
+ int y = layer.BoundingRectangle.Y;
+ int startY = -1;
+ byte lastColor = 0;
+ for (; y < layer.BoundingRectangle.Bottom; y++)
+ {
+ int pos = mat.GetPixelPos(x, y);
+ byte color = span[pos];
+
+ if (lastColor == color && color != 0) continue;
+
+ if (startY >= 0)
+ {
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ lineCount++;
+ }
+
+ startY = color == 0 ? -1 : y;
+
+ lastColor = color;
+ }
+
+ if (startY >= 0)
+ {
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ lineCount++;
+ }
+ }
+
+ layerBytes[layerIndex].InsertRange(0, LayerDef.GetHeaderBytes(layer.NonZeroPixelCount, lineCount));
+ layerBytes[layerIndex].AddRange(pageBreak);
+
+ progress.LockAndIncrement();
+ });
+
+ progress.Token.ThrowIfCancellationRequested();
+
+ foreach (var layerIndex in batch)
+ {
+ outputFile.WriteBytes(layerBytes[layerIndex].ToArray());
+ layerBytes[layerIndex] = null;
+ }
+ }
+
+
+ /*Parallel.For(0, LayerCount,
+ //new ParallelOptions{MaxDegreeOfParallelism = 1},
+ layerIndex =>
{
if (progress.Token.IsCancellationRequested) return;
- List<LayerLine> layerLines = new();
+ //List<LayerLine> layerLines = new();
var layer = this[layerIndex];
using var mat = layer.LayerMat;
var span = mat.GetDataByteSpan();
+ layerBytes[layerIndex] = new();
+
for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++)
{
int y = layer.BoundingRectangle.Y;
@@ -547,7 +713,9 @@ namespace UVtools.Core.FileFormats
if (startY >= 0)
{
- layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ //layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ //Debug.WriteLine(layerLines[^1]);
}
startY = color == 0 ? -1 : y;
@@ -557,11 +725,19 @@ namespace UVtools.Core.FileFormats
if (startY >= 0)
{
- layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ //layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ //Debug.WriteLine(layerLines[^1]);
}
}
- layerDefs[layerIndex] = new LayerDef(layer.NonZeroPixelCount, (uint)layerLines.Count, layerLines.ToArray());
+ //layerDefs[layerIndex] = new LayerDef(layer.NonZeroPixelCount, (uint)layerLines.Count, layerLines.ToArray());
+ //var layerDef = new LayerDef(layer.NonZeroPixelCount, (uint)layerLines.Count, layerLines.ToArray());
+ //layersStreams[layerIndex] = new MemoryStream();
+ //Helpers.Serializer.Serialize(layersStreams[layerIndex], layerDef);
+
+ //layerBytes[layerIndex].InsertRange(0, LayerDef.GetHeaderBytes(layer.NonZeroPixelCount, (uint) layerBytes[layerIndex].Count));
+ //layerBytes[layerIndex].AddRange(PageBreak.Bytes);
progress.LockAndIncrement();
});
@@ -570,9 +746,14 @@ namespace UVtools.Core.FileFormats
for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
progress.Token.ThrowIfCancellationRequested();
- Helpers.SerializeWriteFileStream(outputFile, layerDefs[layerIndex]);
+ //Helpers.SerializeWriteFileStream(outputFile, layerDefs[layerIndex]);
+ //outputFile.WriteStream(layersStreams[layerIndex]);
+ //layersStreams[layerIndex].Dispose();
+ outputFile.WriteBytes(LayerDef.GetHeaderBytes(this[layerIndex].NonZeroPixelCount, (uint)layerBytes[layerIndex].Count));
+ outputFile.WriteBytes(layerBytes[layerIndex].ToArray());
+ outputFile.WriteBytes(pageBreak);
progress++;
- }
+ }*/
Helpers.SerializeWriteFileStream(outputFile, FooterSettings);
@@ -587,6 +768,8 @@ namespace UVtools.Core.FileFormats
HeaderSettings = Helpers.Deserialize<Header>(inputFile);
HeaderSettings.Validate();
+ Debug.WriteLine(HeaderSettings);
+
byte[][] previews = new byte[ThumbnailsOriginalSize.Length][];
for (int i = 0; i < ThumbnailsOriginalSize.Length; i++)
{
@@ -618,45 +801,79 @@ namespace UVtools.Core.FileFormats
SlicerInfoSettings = Helpers.Deserialize<SlicerInfo>(inputFile);
+ Debug.WriteLine(SlicerInfoSettings);
LayerManager.Init(HeaderSettings.LayerCount);
- progress.ItemCount = LayerCount;
- var preLayers = new PreLayer[LayerCount];
+ inputFile.Seek(LayerCount * 4 + 2, SeekOrigin.Current); // Skip pre layers
+ progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
+ /*var preLayers = new PreLayer[LayerCount];
for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
progress.Token.ThrowIfCancellationRequested();
preLayers[layerIndex] = Helpers.Deserialize<PreLayer>(inputFile);
- }
+ progress++;
+ }*/
- inputFile.Seek(2, SeekOrigin.Current);
- var layerDefs = new LayerDef[LayerCount];
- for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ //inputFile.Seek(2, SeekOrigin.Current);
+ var range = Enumerable.Range(0, (int)LayerCount);
+
+ var linesBytes = new byte[LayerCount][];
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
{
progress.Token.ThrowIfCancellationRequested();
- layerDefs[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile);
- progress++;
+
+ foreach (var layerIndex in batch)
+ {
+ inputFile.Seek(4, SeekOrigin.Current);
+ var lineCount = BitExtensions.ToUIntBigEndian(inputFile.ReadBytes(4));
+
+ linesBytes[layerIndex] = new byte[lineCount * 6];
+ inputFile.ReadBytes(linesBytes[layerIndex]);
+ inputFile.Seek(2, SeekOrigin.Current);
+
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ Parallel.ForEach(batch, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ using var mat = EmguExtensions.InitMat(Resolution);
+
+ for (int i = 0; i < linesBytes[layerIndex].Length; i++)
+ {
+ LayerLine line = new()
+ {
+ Coordinates =
+ {
+ [0] = linesBytes[layerIndex][i++],
+ [1] = linesBytes[layerIndex][i++],
+ [2] = linesBytes[layerIndex][i++],
+ [3] = linesBytes[layerIndex][i++],
+ [4] = linesBytes[layerIndex][i++]
+ },
+ Gray = linesBytes[layerIndex][i]
+ };
+
+ CvInvoke.Line(mat, new Point(line.StartX, line.StartY), new Point(line.StartX, line.EndY), new MCvScalar(line.Gray));
+ }
+
+ linesBytes[layerIndex] = null;
+
+ this[layerIndex] = new Layer((uint)layerIndex, mat, this);
+
+ progress.LockAndIncrement();
+ });
}
- /*using TextWriter file = new StreamWriter("D:\\dump.txt");
+ progress.Token.ThrowIfCancellationRequested();
+
+ /*var layerDefs = new LayerDef[LayerCount];
for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
{
- file.WriteLine($"Layer: {layerIndex}");
- for (int lineIndex = 0; lineIndex < layerDefs[layerIndex].LineCount; lineIndex++)
- {
- file.WriteLine($"\tLine: {lineIndex}");
- var line = layerDefs[layerIndex].Lines[lineIndex];
- file.WriteLine($"\t\tb1: {line.Coordinates[0]}");
- file.WriteLine($"\t\tb2: {line.Coordinates[1]}");
- file.WriteLine($"\t\tb3: {line.Coordinates[2]}");
- file.WriteLine($"\t\tb4: {line.Coordinates[3]}");
- file.WriteLine($"\t\tb5: {line.Coordinates[4]}");
- file.WriteLine($"\t\tstartY: {line.StartY}");
- file.WriteLine($"\t\tendY: {line.EndY}");
- file.WriteLine($"\t\tstartX: {line.StartX}");
- file.WriteLine($"\t\tgray: {line.Gray}");
- }
+ progress.Token.ThrowIfCancellationRequested();
+ layerDefs[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile);
+ progress++;
}
- file.Close();*/
progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
Parallel.For(0, LayerCount, layerIndex =>
@@ -670,7 +887,7 @@ namespace UVtools.Core.FileFormats
this[layerIndex] = new Layer((uint)layerIndex, mat, this);
progress.LockAndIncrement();
- });
+ });*/
FooterSettings = Helpers.Deserialize<Footer>(inputFile);
FooterSettings.Validate();
@@ -694,8 +911,14 @@ namespace UVtools.Core.FileFormats
FileFullPath = filePath;
}
+ var offset = Helpers.Serializer.SizeOf(HeaderSettings);
+ foreach (var size in ThumbnailsOriginalSize)
+ {
+ offset += size.Area() * 2 + 2; // + page break
+ }
+
using var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write);
- outputFile.Seek(SlicerInfoAddress, SeekOrigin.Begin);
+ outputFile.Seek(offset, SeekOrigin.Begin);
Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings);
}
diff --git a/UVtools.Core/FileFormats/CXDLPv1File.cs b/UVtools.Core/FileFormats/CXDLPv1File.cs
new file mode 100644
index 0000000..cf3a2a9
--- /dev/null
+++ b/UVtools.Core/FileFormats/CXDLPv1File.cs
@@ -0,0 +1,774 @@
+/*
+ * GNU AFFERO GENERAL PUBLIC LICENSE
+ * Version 3, 19 November 2007
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ * Everyone is permitted to copy and distribute verbatim copies
+ * of this license document, but changing it is not allowed.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Drawing;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using BinarySerialization;
+using Emgu.CV;
+using Emgu.CV.CvEnum;
+using Emgu.CV.Structure;
+using MoreLinq;
+using UVtools.Core.Extensions;
+using UVtools.Core.Operations;
+
+namespace UVtools.Core.FileFormats
+{
+ public class CXDLPv1File : FileFormat
+ {
+ #region Constants
+ private const byte HEADER_SIZE = 9; // CXSW3DV2
+ private const string HEADER_VALUE = "CXSW3DV2";
+ #endregion
+
+ #region Sub Classes
+ #region Header
+ public sealed class Header
+ {
+ /// <summary>
+ /// Gets the size of the header
+ /// </summary>
+ [FieldOrder(0)]
+ [FieldEndianness(Endianness.Big)]
+ public uint HeaderSize { get; set; } = HEADER_SIZE;
+
+ /// <summary>
+ /// Gets the header name
+ /// </summary>
+ [FieldOrder(1)]
+ [FieldLength(HEADER_SIZE)]
+ [SerializeAs(SerializedType.TerminatedString)]
+ public string HeaderValue { get; set; } = HEADER_VALUE;
+
+ /// <summary>
+ /// Gets the number of records in the layer table
+ /// </summary>
+ [FieldOrder(2)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort LayerCount { get; set; }
+
+ /// <summary>
+ /// Gets the printer resolution along X axis, in pixels. This information is critical to correctly decoding layer images.
+ /// </summary>
+ [FieldOrder(3)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort ResolutionX { get; set; }
+
+ /// <summary>
+ /// Gets the printer resolution along Y axis, in pixels. This information is critical to correctly decoding layer images.
+ /// </summary>
+ [FieldOrder(4)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort ResolutionY { get; set; }
+
+ public void Validate()
+ {
+ if (HeaderSize != HEADER_SIZE || HeaderValue != HEADER_VALUE)
+ {
+ throw new FileLoadException("Not a valid CXDLP file!");
+ }
+ }
+
+ public override string ToString()
+ {
+ return $"{nameof(HeaderSize)}: {HeaderSize}, {nameof(HeaderValue)}: {HeaderValue}, {nameof(LayerCount)}: {LayerCount}, {nameof(ResolutionX)}: {ResolutionX}, {nameof(ResolutionY)}: {ResolutionY}";
+ }
+ }
+
+ #endregion
+
+ #region SlicerInfo
+ // Address: 363407
+ public sealed class SlicerInfo
+ {
+ [FieldOrder(0)]
+ [FieldEndianness(Endianness.Big)]
+ public uint DisplayWidthDataSize { get; set; } = 20;
+
+ [FieldOrder(1)]
+ [FieldLength(nameof(DisplayWidthDataSize))]
+ public byte[] DisplayWidthBytes { get; set; }
+
+ [FieldOrder(2)]
+ [FieldEndianness(Endianness.Big)]
+ public uint DisplayHeightDataSize { get; set; } = 20;
+
+ [FieldOrder(3)]
+ [FieldLength(nameof(DisplayHeightDataSize))]
+ public byte[] DisplayHeightBytes { get; set; }
+
+ [FieldOrder(4)]
+ [FieldEndianness(Endianness.Big)]
+ public uint LayerHeightDataSize { get; set; } = 16;
+
+ [FieldOrder(5)]
+ [FieldLength(nameof(LayerHeightDataSize))]
+ public byte[] LayerHeightBytes { get; set; }
+
+ [FieldOrder(6)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort LightOffDelay { get; set; }
+
+ [FieldOrder(7)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort ExposureTime { get; set; }
+
+ [FieldOrder(8)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort BottomExposureTime { get; set; }
+
+ [FieldOrder(9)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort BottomLayers { get; set; }
+
+ [FieldOrder(10)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort BottomLiftHeight { get; set; }
+
+ [FieldOrder(11)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort BottomLiftSpeed { get; set; }
+
+ [FieldOrder(12)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort LiftHeight { get; set; }
+
+ [FieldOrder(13)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort LiftSpeed { get; set; }
+
+ [FieldOrder(14)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort RetractSpeed { get; set; }
+
+ [FieldOrder(15)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort BottomLightPWM { get; set; } = 255;
+
+ [FieldOrder(16)]
+ [FieldEndianness(Endianness.Big)]
+ public ushort LightPWM { get; set; } = 255;
+
+ public override string ToString()
+ {
+ return $"{nameof(DisplayWidthDataSize)}: {DisplayWidthDataSize}, {nameof(DisplayWidthBytes)}: {DisplayWidthBytes}, {nameof(DisplayHeightDataSize)}: {DisplayHeightDataSize}, {nameof(DisplayHeightBytes)}: {DisplayHeightBytes}, {nameof(LayerHeightDataSize)}: {LayerHeightDataSize}, {nameof(LayerHeightBytes)}: {LayerHeightBytes}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(BottomExposureTime)}: {BottomExposureTime}, {nameof(BottomLayers)}: {BottomLayers}, {nameof(BottomLiftHeight)}: {BottomLiftHeight}, {nameof(BottomLiftSpeed)}: {BottomLiftSpeed}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(BottomLightPWM)}: {BottomLightPWM}, {nameof(LightPWM)}: {LightPWM}";
+ }
+ }
+ #endregion
+
+ #region Layer Def
+
+ public sealed class PreLayer
+ {
+ [FieldOrder(0)]
+ [FieldEndianness(Endianness.Big)]
+ public uint Unknown { get; set; }
+
+ public PreLayer()
+ {
+ }
+
+ public PreLayer(uint unknown)
+ {
+ Unknown = unknown;
+ }
+ }
+
+ public sealed class LayerDef
+ {
+ [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public uint Unknown { get; set; }
+ [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public uint LineCount { get; set; }
+ [FieldOrder(2)] [FieldCount(nameof(LineCount))] public LayerLine[] Lines { get; set; }
+ [FieldOrder(3)] public PageBreak PageBreak { get; set; } = new();
+
+ public static byte[] GetHeaderBytes(uint unknown, uint lineCount)
+ {
+ var bytes = new byte[8];
+ BitExtensions.ToBytesBigEndian(unknown, bytes);
+ BitExtensions.ToBytesBigEndian(lineCount, bytes, 4);
+ return bytes;
+ }
+
+ public LayerDef() { }
+
+ public LayerDef(uint unknown, uint lineCount, LayerLine[] lines)
+ {
+ Unknown = unknown;
+ LineCount = lineCount;
+ Lines = lines;
+ }
+ }
+
+ public sealed class LayerLine
+ {
+ public const byte CoordinateCount = 5;
+ [FieldOrder(0)] [FieldCount(CoordinateCount)] public byte[] Coordinates { get; set; } = new byte[CoordinateCount];
+ //[FieldOrder(0)] [FieldEndianness(Endianness.Big)] [FieldBitLength(13)] public ushort StartY { get; set; }
+ //[FieldOrder(1)] [FieldEndianness(Endianness.Big)] [FieldBitLength(13)] public ushort EndY { get; set; }
+ //[FieldOrder(2)] [FieldEndianness(Endianness.Big)] [FieldBitLength(14)] public ushort StartX { get; set; }
+ [FieldOrder(1)] public byte Gray { get; set; }
+
+ [Ignore] public ushort StartY => (ushort)((((Coordinates[0] << 8) + Coordinates[1]) >> 3) & 0x1FFF); // 13 bits
+
+ [Ignore] public ushort EndY => (ushort)((((Coordinates[1] << 16) + (Coordinates[2] << 8) + Coordinates[3]) >> 6) & 0x1FFF); // 13 bits
+
+ [Ignore] public ushort StartX => (ushort)(((Coordinates[3] << 8) + Coordinates[4]) & 0x3FFF); // 14 bits
+ [Ignore] public ushort Length => (ushort)(EndY - StartY);
+
+ public static byte[] GetBytes(ushort startY, ushort endY, ushort startX, byte gray)
+ {
+ var bytes = new byte[CoordinateCount + 1];
+ bytes[0] = (byte)((startY >> 5) & 0xFF);
+ bytes[1] = (byte)(((startY << 3) + (endY >> 10)) & 0xFF);
+ bytes[2] = (byte)((endY >> 2) & 0xFF);
+ bytes[3] = (byte)(((endY << 6) + (startX >> 8)) & 0xFF);
+ bytes[4] = (byte)startX;
+ bytes[5] = gray;
+ return bytes;
+ }
+
+ public LayerLine() { }
+
+ public LayerLine(ushort startY, ushort endY, ushort startX, byte gray)
+ {
+ Coordinates[0] = (byte)((startY >> 5) & 0xFF);
+ Coordinates[1] = (byte)(((startY << 3) + (endY >> 10)) & 0xFF);
+ Coordinates[2] = (byte)((endY >> 2) & 0xFF);
+ Coordinates[3] = (byte)(((endY << 6) + (startX >> 8)) & 0xFF);
+ Coordinates[4] = (byte)startX;
+ /*StartY = startY;
+ EndY = endY;
+ StartX = startX;*/
+ Gray = gray;
+ }
+
+ public override string ToString()
+ {
+ return $"{nameof(Gray)}: {Gray}, {nameof(StartY)}: {StartY}, {nameof(EndY)}: {EndY}, {nameof(StartX)}: {StartX}, {nameof(Length)}: {Length}";
+ }
+ }
+
+ public sealed class PageBreak
+ {
+ public static byte[] Bytes => new byte[] { 0x0D, 0x0A };
+
+ [FieldOrder(0)] public byte Line { get; set; } = 0x0D;
+ [FieldOrder(1)] public byte Break { get; set; } = 0x0A;
+ }
+
+ #endregion
+
+ #region Footer
+ public sealed class Footer
+ {
+ /// <summary>
+ /// Gets the size of the header
+ /// </summary>
+ [FieldOrder(0)]
+ [FieldEndianness(Endianness.Big)]
+ public uint FooterSize { get; set; } = HEADER_SIZE;
+
+ /// <summary>
+ /// Gets the header name
+ /// </summary>
+ [FieldOrder(1)]
+ [FieldLength(HEADER_SIZE)]
+ [SerializeAs(SerializedType.TerminatedString)]
+ public string FooterValue { get; set; } = HEADER_VALUE;
+
+ [FieldOrder(2)]
+ [FieldEndianness(Endianness.Big)]
+ public uint Unknown { get; set; } = 7;
+
+ public void Validate()
+ {
+ if (FooterSize != HEADER_SIZE || FooterValue != HEADER_VALUE)
+ {
+ throw new FileLoadException("Not a valid CXDLP file!");
+ }
+ }
+ }
+ #endregion
+
+ #endregion
+
+ #region Properties
+
+ public Header HeaderSettings { get; protected internal set; } = new();
+ public SlicerInfo SlicerInfoSettings { get; protected internal set; } = new();
+ public Footer FooterSettings { get; protected internal set; } = new();
+
+ public override FileFormatType FileType => FileFormatType.Binary;
+
+ public override FileExtension[] FileExtensions { get; } = {
+ new("v1.cxdlp", "Creality CXDLP v1"),
+ };
+
+ public override PrintParameterModifier[] PrintParameterModifiers { get; } =
+ {
+ PrintParameterModifier.BottomLayerCount,
+ PrintParameterModifier.BottomExposureSeconds,
+ PrintParameterModifier.ExposureSeconds,
+
+ PrintParameterModifier.BottomLiftHeight,
+ PrintParameterModifier.BottomLiftSpeed,
+ PrintParameterModifier.LiftHeight,
+ PrintParameterModifier.LiftSpeed,
+ PrintParameterModifier.RetractSpeed,
+ PrintParameterModifier.LightOffDelay,
+
+ PrintParameterModifier.BottomLightPWM,
+ PrintParameterModifier.LightPWM,
+ };
+
+ public override Size[] ThumbnailsOriginalSize { get; } =
+ {
+ new(116, 116),
+ new(290, 290),
+ new(290, 290)
+ };
+
+ public override uint ResolutionX
+ {
+ get => HeaderSettings.ResolutionX;
+ set
+ {
+ HeaderSettings.ResolutionX = (ushort)value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override uint ResolutionY
+ {
+ get => HeaderSettings.ResolutionY;
+ set
+ {
+ HeaderSettings.ResolutionY = (ushort)value;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float DisplayWidth
+ {
+ get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray()));
+ set
+ {
+ string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture);
+ SlicerInfoSettings.DisplayWidthDataSize = (uint)(str.Length * 2);
+ var data = new byte[SlicerInfoSettings.DisplayWidthDataSize];
+ for (var i = 0; i < str.Length; i++)
+ {
+ data[i * 2 + 1] = System.Convert.ToByte(str[i]);
+ }
+
+ SlicerInfoSettings.DisplayWidthBytes = data;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override float DisplayHeight
+ {
+ get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray()));
+ set
+ {
+ string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture);
+ SlicerInfoSettings.DisplayHeightDataSize = (uint)(str.Length * 2);
+ var data = new byte[SlicerInfoSettings.DisplayHeightDataSize];
+ for (var i = 0; i < str.Length; i++)
+ {
+ data[i * 2 + 1] = System.Convert.ToByte(str[i]);
+ }
+
+ SlicerInfoSettings.DisplayHeightBytes = data;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override byte AntiAliasing
+ {
+ get => 8;
+ set { }
+ }
+
+ public override float LayerHeight
+ {
+ get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray()));
+ set
+ {
+ string str = Layer.RoundHeight(value).ToString(CultureInfo.InvariantCulture);
+ SlicerInfoSettings.LayerHeightDataSize = (uint)(str.Length * 2);
+ var data = new byte[SlicerInfoSettings.LayerHeightDataSize];
+ for (var i = 0; i < str.Length; i++)
+ {
+ data[i * 2 + 1] = System.Convert.ToByte(str[i]);
+ }
+
+ SlicerInfoSettings.LayerHeightBytes = data;
+ RaisePropertyChanged();
+ }
+ }
+
+ public override uint LayerCount
+ {
+ get => base.LayerCount;
+ set => base.LayerCount = HeaderSettings.LayerCount = (ushort)base.LayerCount;
+ }
+
+ public override ushort BottomLayerCount
+ {
+ get => SlicerInfoSettings.BottomLayers;
+ set => base.BottomLayerCount = SlicerInfoSettings.BottomLayers = value;
+ }
+
+ public override float BottomExposureTime
+ {
+ get => SlicerInfoSettings.BottomExposureTime;
+ set => base.BottomExposureTime = SlicerInfoSettings.BottomExposureTime = (ushort)value;
+ }
+
+ public override float ExposureTime
+ {
+ get => SlicerInfoSettings.ExposureTime;
+ set => base.ExposureTime = SlicerInfoSettings.ExposureTime = (ushort)value;
+ }
+
+ public override float BottomLiftHeight
+ {
+ get => SlicerInfoSettings.BottomLiftHeight;
+ set => base.BottomLiftHeight = SlicerInfoSettings.BottomLiftHeight = (ushort)value;
+ }
+
+ public override float LiftHeight
+ {
+ get => SlicerInfoSettings.LiftHeight;
+ set => base.LiftHeight = SlicerInfoSettings.LiftHeight = (ushort)value;
+ }
+
+ public override float BottomLiftSpeed
+ {
+ get => SlicerInfoSettings.BottomLiftSpeed;
+ set => base.BottomLiftSpeed = SlicerInfoSettings.BottomLiftSpeed = (ushort)value;
+ }
+
+ public override float LiftSpeed
+ {
+ get => SlicerInfoSettings.LiftSpeed;
+ set => base.LiftSpeed = SlicerInfoSettings.LiftSpeed = (ushort)value;
+ }
+
+ public override float RetractSpeed
+ {
+ get => SlicerInfoSettings.RetractSpeed;
+ set => base.RetractSpeed = SlicerInfoSettings.RetractSpeed = (ushort)value;
+ }
+
+ public override float BottomLightOffDelay => SlicerInfoSettings.LightOffDelay;
+
+ public override float LightOffDelay
+ {
+ get => SlicerInfoSettings.LightOffDelay;
+ set => base.LightOffDelay = SlicerInfoSettings.LightOffDelay = (ushort)value;
+ }
+
+ public override byte BottomLightPWM
+ {
+ get => (byte)SlicerInfoSettings.BottomLightPWM;
+ set => base.BottomLightPWM = (byte)(SlicerInfoSettings.BottomLightPWM = value);
+ }
+
+ public override byte LightPWM
+ {
+ get => (byte)SlicerInfoSettings.LightPWM;
+ set => base.LightPWM = (byte)(SlicerInfoSettings.LightPWM = value);
+ }
+
+ public override object[] Configs => new object[] { HeaderSettings, SlicerInfoSettings, FooterSettings };
+
+ #endregion
+
+ #region Constructors
+ #endregion
+
+ #region Methods
+
+ protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
+ {
+ using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write);
+
+ if (ResolutionX == 2560 && ResolutionY == 1620)
+ {
+ MachineName = "CL-60";
+ }
+ else if (ResolutionX == 3840 && ResolutionY == 2400)
+ {
+ MachineName = "CL-89";
+ }
+
+ var pageBreak = PageBreak.Bytes;
+
+ Helpers.SerializeWriteFileStream(outputFile, HeaderSettings);
+
+ byte[][] previews = new byte[ThumbnailsOriginalSize.Length][];
+ for (int i = 0; i < ThumbnailsOriginalSize.Length; i++)
+ {
+ previews[i] = new byte[ThumbnailsOriginalSize[i].Area() * 2];
+ }
+ // Previews
+ Parallel.For(0, previews.Length, previewIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ if (Thumbnails[previewIndex] is null) return;
+ var span = Thumbnails[previewIndex].GetDataByteSpan();
+ int index = 0;
+ for (int i = 0; i < span.Length; i += 3)
+ {
+ byte b = span[i];
+ byte g = span[i + 1];
+ byte r = span[i + 2];
+
+ ushort rgb15 = (ushort)(((r >> 3) << 11) | ((g >> 2) << 5) | ((b >> 3) << 0));
+
+ previews[previewIndex][index++] = (byte)(rgb15 >> 8);
+ previews[previewIndex][index++] = (byte)(rgb15 & 0xff);
+ }
+
+ if (index != previews[previewIndex].Length)
+ {
+ throw new FileLoadException($"Preview encode incomplete encode, expected: {previews[previewIndex].Length}, encoded: {index}");
+ }
+ });
+
+ for (int i = 0; i < ThumbnailsOriginalSize.Length; i++)
+ {
+ Helpers.SerializeWriteFileStream(outputFile, previews[i]);
+ outputFile.WriteBytes(pageBreak);
+ }
+ Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings);
+
+ progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
+
+
+ for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
+ {
+ outputFile.WriteBytes(BitExtensions.ToBytesBigEndian(this[layerIndex].NonZeroPixelCount));
+ }
+ outputFile.WriteBytes(pageBreak);
+
+ var range = Enumerable.Range(0, (int)LayerCount);
+
+ var layerBytes = new List<byte>[LayerCount];
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
+ {
+ progress.Token.ThrowIfCancellationRequested();
+
+ Parallel.ForEach(batch, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ var layer = this[layerIndex];
+ using var mat = layer.LayerMat;
+ var span = mat.GetDataByteSpan();
+
+ layerBytes[layerIndex] = new();
+
+ uint lineCount = 0;
+
+ for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++)
+ {
+ int y = layer.BoundingRectangle.Y;
+ int startY = -1;
+ byte lastColor = 0;
+ for (; y < layer.BoundingRectangle.Bottom; y++)
+ {
+ int pos = mat.GetPixelPos(x, y);
+ byte color = span[pos];
+
+ if (lastColor == color && color != 0) continue;
+
+ if (startY >= 0)
+ {
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ lineCount++;
+ }
+
+ startY = color == 0 ? -1 : y;
+
+ lastColor = color;
+ }
+
+ if (startY >= 0)
+ {
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
+ lineCount++;
+ }
+ }
+
+ layerBytes[layerIndex].InsertRange(0, LayerDef.GetHeaderBytes(layer.NonZeroPixelCount, lineCount));
+ layerBytes[layerIndex].AddRange(pageBreak);
+
+ progress.LockAndIncrement();
+ });
+
+ progress.Token.ThrowIfCancellationRequested();
+
+ foreach (var layerIndex in batch)
+ {
+ outputFile.WriteBytes(layerBytes[layerIndex].ToArray());
+ layerBytes[layerIndex] = null;
+ }
+ }
+
+
+ Helpers.SerializeWriteFileStream(outputFile, FooterSettings);
+
+ Debug.WriteLine("Encode Results:");
+ Debug.WriteLine(HeaderSettings);
+ Debug.WriteLine(SlicerInfoSettings);
+ Debug.WriteLine("-End-");
+ }
+
+ protected override void DecodeInternally(string fileFullPath, OperationProgress progress)
+ {
+ using var inputFile = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read);
+ HeaderSettings = Helpers.Deserialize<Header>(inputFile);
+ HeaderSettings.Validate();
+
+ Debug.WriteLine(HeaderSettings);
+
+ byte[][] previews = new byte[ThumbnailsOriginalSize.Length][];
+ for (int i = 0; i < ThumbnailsOriginalSize.Length; i++)
+ {
+ previews[i] = new byte[ThumbnailsOriginalSize[i].Area() * 2];
+ inputFile.ReadBytes(previews[i]);
+ inputFile.Seek(2, SeekOrigin.Current);
+ }
+
+ Parallel.For(0, previews.Length, previewIndex =>
+ {
+ var mat = new Mat(ThumbnailsOriginalSize[previewIndex], DepthType.Cv8U, 3);
+ var span = mat.GetDataByteSpan();
+
+ int spanIndex = 0;
+ for (int i = 0; i < previews[previewIndex].Length; i += 2)
+ {
+ ushort rgb15 = (ushort)((ushort)(previews[previewIndex][i + 0] << 8) | previews[previewIndex][i + 1]);
+ byte r = (byte)((rgb15 >> 11) << 3);
+ byte g = (byte)((rgb15 >> 5) << 2);
+ byte b = (byte)((rgb15 >> 0) << 3);
+
+ span[spanIndex++] = b;
+ span[spanIndex++] = g;
+ span[spanIndex++] = r;
+ }
+
+ Thumbnails[previewIndex] = mat;
+ });
+
+
+ SlicerInfoSettings = Helpers.Deserialize<SlicerInfo>(inputFile);
+ Debug.WriteLine(SlicerInfoSettings);
+
+ LayerManager.Init(HeaderSettings.LayerCount);
+ inputFile.Seek(LayerCount * 4 + 2, SeekOrigin.Current); // Skip pre layers
+
+
+ progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
+
+ var range = Enumerable.Range(0, (int)LayerCount);
+
+ var linesBytes = new byte[LayerCount][];
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
+ {
+ progress.Token.ThrowIfCancellationRequested();
+
+ foreach (var layerIndex in batch)
+ {
+ inputFile.Seek(4, SeekOrigin.Current);
+ var lineCount = BitExtensions.ToUIntBigEndian(inputFile.ReadBytes(4));
+
+ linesBytes[layerIndex] = new byte[lineCount * 6];
+ inputFile.ReadBytes(linesBytes[layerIndex]);
+ inputFile.Seek(2, SeekOrigin.Current);
+
+ progress.Token.ThrowIfCancellationRequested();
+ }
+
+ Parallel.ForEach(batch, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ using var mat = EmguExtensions.InitMat(Resolution);
+
+ for (int i = 0; i < linesBytes[layerIndex].Length; i++)
+ {
+ LayerLine line = new()
+ {
+ Coordinates =
+ {
+ [0] = linesBytes[layerIndex][i++],
+ [1] = linesBytes[layerIndex][i++],
+ [2] = linesBytes[layerIndex][i++],
+ [3] = linesBytes[layerIndex][i++],
+ [4] = linesBytes[layerIndex][i++]
+ },
+ Gray = linesBytes[layerIndex][i]
+ };
+
+ CvInvoke.Line(mat, new Point(line.StartX, line.StartY), new Point(line.StartX, line.EndY), new MCvScalar(line.Gray));
+ }
+
+ linesBytes[layerIndex] = null;
+
+ this[layerIndex] = new Layer((uint)layerIndex, mat, this);
+
+ progress.LockAndIncrement();
+ });
+ }
+
+ progress.Token.ThrowIfCancellationRequested();
+
+ FooterSettings = Helpers.Deserialize<Footer>(inputFile);
+ FooterSettings.Validate();
+ }
+
+ public override void SaveAs(string filePath = null, OperationProgress progress = null)
+ {
+ if (RequireFullEncode)
+ {
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ FileFullPath = filePath;
+ }
+ Encode(FileFullPath, progress);
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(filePath))
+ {
+ File.Copy(FileFullPath, filePath, true);
+ FileFullPath = filePath;
+ }
+
+ var offset = Helpers.Serializer.SizeOf(HeaderSettings);
+ foreach (var size in ThumbnailsOriginalSize)
+ {
+ offset += size.Area() * 2 + 2; // + page break
+ }
+
+ using var outputFile = new FileStream(FileFullPath, FileMode.Open, FileAccess.Write);
+ outputFile.Seek(offset, SeekOrigin.Begin);
+ Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings);
+ }
+
+ #endregion
+ }
+}
diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs
index 470bbb9..4e0cbc6 100644
--- a/UVtools.Core/FileFormats/ChituboxFile.cs
+++ b/UVtools.Core/FileFormats/ChituboxFile.cs
@@ -380,6 +380,13 @@ namespace UVtools.Core.FileFormats
var image = new Mat(new Size((int) ResolutionX, (int) ResolutionY), DepthType.Cv8U, 3);
var span = image.GetBytePointer();
+ /*var previewSize = ResolutionX * ResolutionY * 2;
+ if (previewSize != rawImageData.Length)
+ {
+ throw new FileLoadException($"Thumbnail out of size, expecting {previewSize} bytes, got {rawImageData.Length}");
+ return null;
+ }*/
+
int pixel = 0;
for (int n = 0; n < rawImageData.Length; n++)
{
diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs
index c667d84..322073b 100644
--- a/UVtools.Core/FileFormats/FileFormat.cs
+++ b/UVtools.Core/FileFormats/FileFormat.cs
@@ -214,6 +214,7 @@ namespace UVtools.Core.FileFormats
new ZCodexFile(), // zcodex
new MDLPFile(), // MKS v1
new GR1File(), // GR1 Workshop
+ //new CXDLPv1File(), // Creality Box v1
new CXDLPFile(), // Creality Box
new LGSFile(), // LGS, LGS30
new VDAFile(), // VDA
diff --git a/UVtools.Core/FileFormats/GR1File.cs b/UVtools.Core/FileFormats/GR1File.cs
index da26561..b9b64ec 100644
--- a/UVtools.Core/FileFormats/GR1File.cs
+++ b/UVtools.Core/FileFormats/GR1File.cs
@@ -6,8 +6,6 @@
* of this license document, but changing it is not allowed.
*/
-// https://github.com/cbiffle/catibo/blob/master/doc/cbddlp-ctb.adoc
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -20,6 +18,7 @@ using System.Threading.Tasks;
using BinarySerialization;
using Emgu.CV;
using Emgu.CV.CvEnum;
+using MoreLinq.Extensions;
using UVtools.Core.Extensions;
using UVtools.Core.Operations;
@@ -111,6 +110,15 @@ namespace UVtools.Core.FileFormats
//[FieldOrder(3)] [FieldEndianness(Endianness.Big)] public byte Gray { get; set; }
+ public static byte[] GetBytes(ushort startY, ushort endY, ushort startX)
+ {
+ var bytes = new byte[6];
+ BitExtensions.ToBytesBigEndian(startY, bytes);
+ BitExtensions.ToBytesBigEndian(endY, bytes, 2);
+ BitExtensions.ToBytesBigEndian(startX, bytes, 4);
+ return bytes;
+ }
+
public LayerLine()
{ }
@@ -124,11 +132,13 @@ namespace UVtools.Core.FileFormats
public sealed class PageBreak
{
+ public static byte[] Bytes => new byte[] {0x0D, 0x0A};
[FieldOrder(0)] [FieldEndianness(Endianness.Big)] public byte Line { get; set; } = 0x0D;
[FieldOrder(1)] [FieldEndianness(Endianness.Big)] public byte Break { get; set; } = 0x0A;
}
#endregion
+
#endregion
#region Properties
@@ -332,7 +342,7 @@ namespace UVtools.Core.FileFormats
protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
{
using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write);
- var pageBreak = new PageBreak();
+ var pageBreak = PageBreak.Bytes;
Helpers.SerializeWriteFileStream(outputFile, HeaderSettings);
@@ -369,59 +379,74 @@ namespace UVtools.Core.FileFormats
for (int i = 0; i < ThumbnailsOriginalSize.Length; i++)
{
Helpers.SerializeWriteFileStream(outputFile, previews[i]);
- Helpers.SerializeWriteFileStream(outputFile, pageBreak);
+ outputFile.WriteBytes(pageBreak);
}
Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings);
progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
- var layerDefs = new LayerDef[LayerCount];
- Parallel.For(0, LayerCount, layerIndex =>
+
+ var range = Enumerable.Range(0, (int)LayerCount);
+
+ var layerBytes = new List<byte>[LayerCount];
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
{
- if (progress.Token.IsCancellationRequested) return;
- List<LayerLine> layerLines = new();
- var layer = this[layerIndex];
- using var mat = layer.LayerMat;
- var span = mat.GetDataByteSpan();
+ progress.Token.ThrowIfCancellationRequested();
- for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++)
+ Parallel.ForEach(batch, layerIndex =>
{
- int y = layer.BoundingRectangle.Y;
- int startY = -1;
- for (; y < layer.BoundingRectangle.Bottom; y++)
+ if (progress.Token.IsCancellationRequested) return;
+ var layer = this[layerIndex];
+ using var mat = layer.LayerMat;
+ var span = mat.GetDataByteSpan();
+
+ layerBytes[layerIndex] = new();
+
+ uint lineCount = 0;
+
+ for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++)
{
- int pos = mat.GetPixelPos(x, y);
- if (span[pos] < 128) // Black pixel
+ int y = layer.BoundingRectangle.Y;
+ int startY = -1;
+ for (; y < layer.BoundingRectangle.Bottom; y++)
{
- if(startY == -1) continue; // Keep ignoring
- layerLines.Add(new LayerLine((ushort) startY, (ushort) (y-1), (ushort) x));
- startY = -1;
+ int pos = mat.GetPixelPos(x, y);
+ if (span[pos] < 128) // Black pixel
+ {
+ if (startY == -1) continue; // Keep ignoring
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x));
+ startY = -1;
+ lineCount++;
+ }
+ else
+ {
+ if (startY >= 0) continue; // Keep sum
+ startY = y;
+ }
}
- else // White pixel
+
+ if (startY >= 0)
{
- if (startY >= 0) continue; // Keep sum
- startY = y;
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x));
+ lineCount++;
}
}
- if (startY >= 0)
- {
- layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort) x));
- }
- }
+ layerBytes[layerIndex].InsertRange(0, BitExtensions.ToBytesBigEndian(lineCount));
+ layerBytes[layerIndex].AddRange(pageBreak);
- layerDefs[layerIndex] = new LayerDef((uint) layerLines.Count, layerLines.ToArray());
+ progress.LockAndIncrement();
+ });
- progress.LockAndIncrement();
- });
-
- progress.Reset(OperationProgress.StatusWritingFile, LayerCount);
- for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
progress.Token.ThrowIfCancellationRequested();
- Helpers.SerializeWriteFileStream(outputFile, layerDefs[layerIndex]);
- progress++;
+
+ foreach (var layerIndex in batch)
+ {
+ outputFile.WriteBytes(layerBytes[layerIndex].ToArray());
+ layerBytes[layerIndex] = null;
+ }
}
+
Helpers.SerializeWriteFileStream(outputFile, HeaderSettings);
Debug.WriteLine("Encode Results:");
@@ -473,28 +498,49 @@ namespace UVtools.Core.FileFormats
SlicerInfoSettings = Helpers.Deserialize<SlicerInfo>(inputFile);
LayerManager.Init(SlicerInfoSettings.LayerCount);
- progress.ItemCount = LayerCount;
- LayerDef[] layerDefs = new LayerDef[LayerCount];
- for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
- progress.Token.ThrowIfCancellationRequested();
- layerDefs[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile);
- progress++;
- }
+
progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
- Parallel.For(0, LayerCount, layerIndex =>
+
+ var range = Enumerable.Range(0, (int)LayerCount);
+
+ var linesBytes = new byte[LayerCount][];
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
{
- if (progress.Token.IsCancellationRequested) return;
- using var mat = EmguExtensions.InitMat(Resolution);
- foreach (var line in layerDefs[layerIndex].Lines)
+ progress.Token.ThrowIfCancellationRequested();
+
+ foreach (var layerIndex in batch)
{
- CvInvoke.Line(mat, new Point(line.StartX, line.StartY), new Point(line.StartX, line.EndY), EmguExtensions.WhiteColor);
+ var lineCount = BitExtensions.ToUIntBigEndian(inputFile.ReadBytes(4));
+
+ linesBytes[layerIndex] = new byte[lineCount * 6];
+ inputFile.ReadBytes(linesBytes[layerIndex]);
+ inputFile.Seek(2, SeekOrigin.Current);
+
+ progress.Token.ThrowIfCancellationRequested();
}
- this[layerIndex] = new Layer((uint) layerIndex, mat, this);
- progress.LockAndIncrement();
- });
+ Parallel.ForEach(batch, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ using var mat = EmguExtensions.InitMat(Resolution);
+
+ for (int i = 0; i < linesBytes[layerIndex].Length; i++)
+ {
+ var startY = BitExtensions.ToUShortBigEndian(linesBytes[layerIndex][i++], linesBytes[layerIndex][i++]);
+ var endY = BitExtensions.ToUShortBigEndian(linesBytes[layerIndex][i++], linesBytes[layerIndex][i++]);
+ var startX = BitExtensions.ToUShortBigEndian(linesBytes[layerIndex][i++], linesBytes[layerIndex][i]);
+
+ CvInvoke.Line(mat, new Point(startX, startY), new Point(startX, endY), EmguExtensions.WhiteColor);
+ }
+
+ linesBytes[layerIndex] = null;
+
+ this[layerIndex] = new Layer((uint)layerIndex, mat, this);
+
+ progress.LockAndIncrement();
+ });
+ }
HeaderSettings = Helpers.Deserialize<Header>(inputFile);
if (HeaderSettings.HeaderValue != Header.HEADER_VALUE)
diff --git a/UVtools.Core/FileFormats/MDLPFile.cs b/UVtools.Core/FileFormats/MDLPFile.cs
index 0b86e11..21b61cd 100644
--- a/UVtools.Core/FileFormats/MDLPFile.cs
+++ b/UVtools.Core/FileFormats/MDLPFile.cs
@@ -6,8 +6,6 @@
* of this license document, but changing it is not allowed.
*/
-// https://github.com/cbiffle/catibo/blob/master/doc/cbddlp-ctb.adoc
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -20,6 +18,7 @@ using System.Threading.Tasks;
using BinarySerialization;
using Emgu.CV;
using Emgu.CV.CvEnum;
+using MoreLinq;
using UVtools.Core.Extensions;
using UVtools.Core.Operations;
@@ -113,6 +112,15 @@ namespace UVtools.Core.FileFormats
[FieldOrder(1)] [FieldEndianness(Endianness.Big)] public ushort EndY { get; set; }
[FieldOrder(2)] [FieldEndianness(Endianness.Big)] public ushort StartX { get; set; }
+ public static byte[] GetBytes(ushort StartY, ushort EndY, ushort StartX)
+ {
+ var bytes = new byte[6];
+ BitExtensions.ToBytesBigEndian(StartY, bytes);
+ BitExtensions.ToBytesBigEndian(EndY, bytes, 2);
+ BitExtensions.ToBytesBigEndian(StartX, bytes, 4);
+ return bytes;
+ }
+
public LayerLine()
{ }
@@ -127,11 +135,13 @@ namespace UVtools.Core.FileFormats
public sealed class PageBreak
{
+ public static byte[] Bytes => new byte[] {0x0D, 0x0A};
[FieldOrder(0)] [FieldEndianness(Endianness.Big)] public byte Line { get; set; } = 0x0D;
[FieldOrder(1)] [FieldEndianness(Endianness.Big)] public byte Break { get; set; } = 0x0A;
}
#endregion
+
#endregion
#region Properties
@@ -298,7 +308,7 @@ namespace UVtools.Core.FileFormats
protected override void EncodeInternally(string fileFullPath, OperationProgress progress)
{
using var outputFile = new FileStream(fileFullPath, FileMode.Create, FileAccess.Write);
- var pageBreak = new PageBreak();
+ var pageBreak = PageBreak.Bytes;
Helpers.SerializeWriteFileStream(outputFile, HeaderSettings);
@@ -335,57 +345,70 @@ namespace UVtools.Core.FileFormats
for (int i = 0; i < ThumbnailsOriginalSize.Length; i++)
{
Helpers.SerializeWriteFileStream(outputFile, previews[i]);
- Helpers.SerializeWriteFileStream(outputFile, pageBreak);
+ outputFile.WriteBytes(pageBreak);
}
Helpers.SerializeWriteFileStream(outputFile, SlicerInfoSettings);
progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount);
- var layerDefs = new LayerDef[LayerCount];
- Parallel.For(0, LayerCount, layerIndex =>
+ var range = Enumerable.Range(0, (int)LayerCount);
+
+ var layerBytes = new List<byte>[LayerCount];
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
{
- if (progress.Token.IsCancellationRequested) return;
- List<LayerLine> layerLines = new();
- var layer = this[layerIndex];
- using var mat = layer.LayerMat;
- var span = mat.GetDataByteSpan();
+ progress.Token.ThrowIfCancellationRequested();
- for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++)
+ Parallel.ForEach(batch, layerIndex =>
{
- int y = layer.BoundingRectangle.Y;
- int startY = -1;
- for (; y < layer.BoundingRectangle.Bottom; y++)
+ if (progress.Token.IsCancellationRequested) return;
+ var layer = this[layerIndex];
+ using var mat = layer.LayerMat;
+ var span = mat.GetDataByteSpan();
+
+ layerBytes[layerIndex] = new();
+
+ uint lineCount = 0;
+
+ for (int x = layer.BoundingRectangle.X; x < layer.BoundingRectangle.Right; x++)
{
- int pos = mat.GetPixelPos(x, y);
- if (span[pos] < 128) // Black pixel
+ int y = layer.BoundingRectangle.Y;
+ int startY = -1;
+ for (; y < layer.BoundingRectangle.Bottom; y++)
{
- if(startY == -1) continue; // Keep ignoring
- layerLines.Add(new LayerLine((ushort) startY, (ushort) (y-1), (ushort) x));
- startY = -1;
+ int pos = mat.GetPixelPos(x, y);
+ if (span[pos] < 128) // Black pixel
+ {
+ if (startY == -1) continue; // Keep ignoring
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x));
+ startY = -1;
+ lineCount++;
+ }
+ else
+ {
+ if (startY >= 0) continue; // Keep sum
+ startY = y;
+ }
}
- else // White pixel
+
+ if (startY >= 0)
{
- if (startY >= 0) continue; // Keep sum
- startY = y;
+ layerBytes[layerIndex].AddRange(LayerLine.GetBytes((ushort)startY, (ushort)(y - 1), (ushort)x));
+ lineCount++;
}
}
- if (startY >= 0)
- {
- layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort) x));
- }
- }
-
- layerDefs[layerIndex] = new LayerDef((uint) layerLines.Count, layerLines.ToArray());
+ layerBytes[layerIndex].InsertRange(0, BitExtensions.ToBytesBigEndian(lineCount));
+ layerBytes[layerIndex].AddRange(pageBreak);
- progress.LockAndIncrement();
- });
+ progress.LockAndIncrement();
+ });
- progress.Reset(OperationProgress.StatusWritingFile, LayerCount);
- for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
progress.Token.ThrowIfCancellationRequested();
- Helpers.SerializeWriteFileStream(outputFile, layerDefs[layerIndex]);
- progress++;
+
+ foreach (var layerIndex in batch)
+ {
+ outputFile.WriteBytes(layerBytes[layerIndex].ToArray());
+ layerBytes[layerIndex] = null;
+ }
}
Helpers.SerializeWriteFileStream(outputFile, HeaderSettings);
@@ -438,28 +461,49 @@ namespace UVtools.Core.FileFormats
SlicerInfoSettings = Helpers.Deserialize<SlicerInfo>(inputFile);
LayerManager.Init(SlicerInfoSettings.LayerCount);
- progress.ItemCount = LayerCount;
- LayerDef[] layerDefs = new LayerDef[LayerCount];
- for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
- {
- progress.Token.ThrowIfCancellationRequested();
- layerDefs[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile);
- progress++;
- }
+
progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount);
- Parallel.For(0, LayerCount, layerIndex =>
+
+ var range = Enumerable.Range(0, (int)LayerCount);
+
+ var linesBytes = new byte[LayerCount][];
+ foreach (var batch in range.Batch(Environment.ProcessorCount * 10))
{
- if (progress.Token.IsCancellationRequested) return;
- using var mat = EmguExtensions.InitMat(Resolution);
- foreach (var line in layerDefs[layerIndex].Lines)
+ progress.Token.ThrowIfCancellationRequested();
+
+ foreach (var layerIndex in batch)
{
- CvInvoke.Line(mat, new Point(line.StartX, line.StartY), new Point(line.StartX, line.EndY), EmguExtensions.WhiteColor);
+ var lineCount = BitExtensions.ToUIntBigEndian(inputFile.ReadBytes(4));
+
+ linesBytes[layerIndex] = new byte[lineCount * 6];
+ inputFile.ReadBytes(linesBytes[layerIndex]);
+ inputFile.Seek(2, SeekOrigin.Current);
+
+ progress.Token.ThrowIfCancellationRequested();
}
- this[layerIndex] = new Layer((uint) layerIndex, mat, this);
- progress.LockAndIncrement();
- });
+ Parallel.ForEach(batch, layerIndex =>
+ {
+ if (progress.Token.IsCancellationRequested) return;
+ using var mat = EmguExtensions.InitMat(Resolution);
+
+ for (int i = 0; i < linesBytes[layerIndex].Length; i++)
+ {
+ var startY = BitExtensions.ToUShortBigEndian(linesBytes[layerIndex][i++], linesBytes[layerIndex][i++]);
+ var endY = BitExtensions.ToUShortBigEndian(linesBytes[layerIndex][i++], linesBytes[layerIndex][i++]);
+ var startX = BitExtensions.ToUShortBigEndian(linesBytes[layerIndex][i++], linesBytes[layerIndex][i]);
+
+ CvInvoke.Line(mat, new Point(startX, startY), new Point(startX, endY), EmguExtensions.WhiteColor);
+ }
+
+ linesBytes[layerIndex] = null;
+
+ this[layerIndex] = new Layer((uint)layerIndex, mat, this);
+
+ progress.LockAndIncrement();
+ });
+ }
HeaderSettings = Helpers.Deserialize<Header>(inputFile);
HeaderSettings.Validate();
diff --git a/UVtools.Core/Helpers.cs b/UVtools.Core/Helpers.cs
index b4b1dab..53c8b90 100644
--- a/UVtools.Core/Helpers.cs
+++ b/UVtools.Core/Helpers.cs
@@ -41,19 +41,17 @@ namespace UVtools.Core
public static uint SerializeWriteFileStream(FileStream fs, object value, int offset = 0)
{
- using MemoryStream stream = Serialize(value);
+ using var stream = Serialize(value);
return fs.WriteStream(stream, offset);
}
public static T JsonDeserializeObject<T>(Stream stream)
{
- using (TextReader tr = new StreamReader(stream))
- {
- return JsonConvert.DeserializeObject<T>(tr.ReadToEnd());
- }
+ using TextReader tr = new StreamReader(stream);
+ return JsonConvert.DeserializeObject<T>(tr.ReadToEnd());
}
- public static SHA1CryptoServiceProvider SHA1 { get; } = new SHA1CryptoServiceProvider();
+ public static SHA1CryptoServiceProvider SHA1 { get; } = new();
public static string ComputeSHA1Hash(byte[] input)
{
return Convert.ToBase64String(SHA1.ComputeHash(input));
diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs
index fb58af7..c7aca95 100644
--- a/UVtools.Core/Layer/Layer.cs
+++ b/UVtools.Core/Layer/Layer.cs
@@ -625,6 +625,7 @@ namespace UVtools.Core
return changed;
}
+ /*
/// <summary>
/// Gets all islands start pixel location for this layer
/// https://www.geeksforgeeks.org/find-number-of-islands/
@@ -651,17 +652,6 @@ namespace UVtools.Core
var previousLayerImage = PreviousLayer()?.LayerMat;
var previousBytes = previousLayerImage?.GetBytes();
-
- /*var nextLayerImage = NextLayer()?.Image;
- byte[] nextBytes = null;
- if (!ReferenceEquals(nextLayerImage, null))
- {
- if (nextLayerImage.TryGetSinglePixelSpan(out var nextPixelSpan))
- {
- nextBytes = MemoryMarshal.AsBytes(nextPixelSpan).ToArray();
- }
- }*/
-
// Make a bool array to
// mark visited cells.
// Initially all cells
@@ -688,12 +678,6 @@ namespace UVtools.Core
{
pixelIndex = y * mat.Width + x;
- /*if (bytes[pixelIndex] == 0 && previousBytes?[pixelIndex] == byte.MaxValue &&
- nextBytes?[pixelIndex] == byte.MaxValue)
- {
- result.Add(new LayerIssue(this, LayerIssue.IssueType.HoleSandwich, new []{new Point(x, y)}));
- }*/
-
if (bytes[pixelIndex] > minPixel && !visited[x, y])
{
// If a cell with value 1 is not
@@ -798,7 +782,7 @@ namespace UVtools.Core
pixels.Clear();
return result;
- }
+ }*/
public Layer Clone()
{
diff --git a/UVtools.Core/Layer/LayerIssue.cs b/UVtools.Core/Layer/LayerIssue.cs
index 812479f..8b46e51 100644
--- a/UVtools.Core/Layer/LayerIssue.cs
+++ b/UVtools.Core/Layer/LayerIssue.cs
@@ -11,7 +11,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
-using System.Runtime.InteropServices.ComTypes;
using UVtools.Core.Objects;
namespace UVtools.Core
@@ -106,6 +105,12 @@ namespace UVtools.Core
{
Enabled = enabled;
}
+
+ public IslandDetectionConfiguration Clone()
+ {
+ var clone = MemberwiseClone() as IslandDetectionConfiguration;
+ return clone;
+ }
}
/// <summary>
@@ -307,7 +312,7 @@ namespace UVtools.Core
return (uint)(BoundingRectangle.Width * BoundingRectangle.Height);
}
- if (ReferenceEquals(Pixels, null)) return 0;
+ if (Pixels is null) return 0;
return (uint)Pixels.Length;
}
}
@@ -315,9 +320,9 @@ namespace UVtools.Core
/// <summary>
/// Check if this issue have a valid start point to show
/// </summary>
- public bool HaveValidPoint => !ReferenceEquals(Pixels, null) && Pixels.Length > 0;
+ public bool HaveValidPoint => Pixels?.Length > 0;
- public LayerIssue(Layer layer, IssueType type, Point[] pixels = null, Rectangle boundingRectangle = new Rectangle())
+ public LayerIssue(Layer layer, IssueType type, Point[] pixels = null, Rectangle boundingRectangle = default)
{
Layer = layer;
Type = type;
@@ -346,19 +351,19 @@ namespace UVtools.Core
public bool Equals(LayerIssue other)
{
- if (ReferenceEquals(null, other)) return false;
+ if (other is null) return false;
if (ReferenceEquals(this, other)) return true;
return Layer.Index == other.Layer.Index
&& Type == other.Type
&& PixelsCount == other.PixelsCount
- && !(Pixels is null) && !(other.Pixels is null) && Pixels.SequenceEqual(other.Pixels)
+ && Pixels is not null && other.Pixels is not null && Pixels.SequenceEqual(other.Pixels)
//&& BoundingRectangle.Equals(other.BoundingRectangle)
;
}
public override bool Equals(object obj)
{
- if (ReferenceEquals(null, obj)) return false;
+ if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((LayerIssue) obj);
diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs
index 054807e..b9585c3 100644
--- a/UVtools.Core/Layer/LayerManager.cs
+++ b/UVtools.Core/Layer/LayerManager.cs
@@ -10,7 +10,6 @@ using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
@@ -728,7 +727,7 @@ namespace UVtools.Core
Mat[] cachedLayers = new Mat[LayerCount];
const uint cacheCount = 300;
- bool IsIgnored(LayerIssue issue) => !(ignoredIssues is null) && ignoredIssues.Count > 0 && ignoredIssues.Contains(issue);
+ bool IsIgnored(LayerIssue issue) => ignoredIssues is not null && ignoredIssues.Count > 0 && ignoredIssues.Contains(issue);
bool AddIssue(LayerIssue issue)
{
if (IsIgnored(issue)) return false;
@@ -931,7 +930,7 @@ namespace UVtools.Core
var result = new ConcurrentBag<LayerIssue>();
var layerHollowAreas = new ConcurrentDictionary<uint, List<LayerHollowArea>>();
- bool IsIgnored(LayerIssue issue) => !(ignoredIssues is null) && ignoredIssues.Count > 0 && ignoredIssues.Contains(issue);
+ bool IsIgnored(LayerIssue issue) => ignoredIssues is not null && ignoredIssues.Count > 0 && ignoredIssues.Contains(issue);
bool AddIssue(LayerIssue issue)
{
@@ -1115,9 +1114,10 @@ namespace UVtools.Core
islandImage = image;
}
- using (Mat labels = new())
- using (Mat stats = new())
- using (Mat centroids = new())
+ using (
+ Mat labels = new(),
+ stats = new(),
+ centroids = new())
{
var numLabels = CvInvoke.ConnectedComponentsWithStats(islandImage, labels, stats,
centroids,
@@ -1620,9 +1620,11 @@ namespace UVtools.Core
continue;
}
- switch (operationDrawing.BrushShape)
+ mat.DrawPolygon((byte)operationDrawing.BrushShape, operationDrawing.BrushSize / 2, operationDrawing.Location,
+ new MCvScalar(operationDrawing.Brightness), operationDrawing.RotationAngle, operationDrawing.Thickness, operationDrawing.LineType);
+ /*switch (operationDrawing.BrushShape)
{
- case PixelDrawing.BrushShapeType.Rectangle:
+ case PixelDrawing.BrushShapeType.Square:
CvInvoke.Rectangle(mat, operationDrawing.Rectangle, new MCvScalar(operationDrawing.Brightness), operationDrawing.Thickness, operationDrawing.LineType);
break;
case PixelDrawing.BrushShapeType.Circle:
@@ -1631,7 +1633,7 @@ namespace UVtools.Core
break;
default:
throw new ArgumentOutOfRangeException();
- }
+ }*/
}
else if (operation.OperationType == PixelOperation.PixelOperationType.Text)
{
diff --git a/UVtools.Core/Objects/RangeObservableCollection.cs b/UVtools.Core/Objects/RangeObservableCollection.cs
index 9e529d8..c6996ce 100644
--- a/UVtools.Core/Objects/RangeObservableCollection.cs
+++ b/UVtools.Core/Objects/RangeObservableCollection.cs
@@ -420,7 +420,7 @@
return;
}
- if (!(collection is IList<T> list))
+ if (collection is not IList<T> list)
list = new List<T>(collection);
using (BlockReentrancy())
diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs
index 0eb6318..9227bef 100644
--- a/UVtools.Core/Operations/OperationRepairLayers.cs
+++ b/UVtools.Core/Operations/OperationRepairLayers.cs
@@ -9,9 +9,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Emgu.CV;
@@ -19,7 +21,6 @@ using Emgu.CV.CvEnum;
using Emgu.CV.Util;
using UVtools.Core.Extensions;
using UVtools.Core.FileFormats;
-using UVtools.Core.Objects;
namespace UVtools.Core.Operations
{
@@ -32,8 +33,10 @@ namespace UVtools.Core.Operations
private bool _removeEmptyLayers = true;
private ushort _removeIslandsBelowEqualPixelCount = 5;
private ushort _removeIslandsRecursiveIterations = 4;
+ private ushort _attachIslandsBelowLayers = 2;
private uint _gapClosingIterations = 1;
private uint _noiseRemovalIterations;
+
#endregion
#region Overrides
@@ -102,6 +105,12 @@ namespace UVtools.Core.Operations
set => RaiseAndSetIfChanged(ref _removeIslandsRecursiveIterations, value);
}
+ public ushort AttachIslandsBelowLayers
+ {
+ get => _attachIslandsBelowLayers;
+ set => RaiseAndSetIfChanged(ref _attachIslandsBelowLayers, value);
+ }
+
public uint GapClosingIterations
{
get => _gapClosingIterations;
@@ -126,21 +135,20 @@ namespace UVtools.Core.Operations
protected override bool ExecuteInternally(OperationProgress progress)
{
// Remove islands
- if (!ReferenceEquals(Issues, null)
- && !ReferenceEquals(IslandDetectionConfig, null)
- && RepairIslands
- && RemoveIslandsBelowEqualPixelCount > 0
- && RemoveIslandsRecursiveIterations != 1)
+ if (Issues is not null
+ && IslandDetectionConfig is not null
+ && _repairIslands
+ && _removeIslandsBelowEqualPixelCount > 0
+ && _removeIslandsRecursiveIterations != 1)
{
progress.Reset("Removed recursive islands");
- ushort limit = RemoveIslandsRecursiveIterations == 0
+ ushort limit = _removeIslandsRecursiveIterations == 0
? ushort.MaxValue
- : RemoveIslandsRecursiveIterations;
+ : _removeIslandsRecursiveIterations;
var recursiveIssues = Issues;
- ConcurrentBag<uint> islandsToRecompute = null;
-
- var islandConfig = IslandDetectionConfig;
+ var islandsToRecompute = new ConcurrentBag<uint>();
+ var islandConfig = IslandDetectionConfig.Clone();
var overhangConfig = new OverhangDetectionConfiguration(false);
var touchingBoundsConfig = new TouchingBoundDetectionConfiguration(false);
var printHeightConfig = new PrintHeightDetectionConfiguration(false);
@@ -148,7 +156,7 @@ namespace UVtools.Core.Operations
var emptyLayersConfig = false;
islandConfig.Enabled = true;
- islandConfig.RequiredAreaToProcessCheck = (ushort) Math.Floor(RemoveIslandsBelowEqualPixelCount / 2m);
+ islandConfig.RequiredAreaToProcessCheck = (ushort) Math.Floor(_removeIslandsBelowEqualPixelCount / 2.0);
for (uint i = 0; i < limit; i++)
{
@@ -169,7 +177,8 @@ namespace UVtools.Core.Operations
.GroupBy(issue => issue.LayerIndex);
if (!issuesGroup.Any()) break; // Nothing to process
- islandsToRecompute = new ConcurrentBag<uint>();
+
+ islandsToRecompute.Clear();
Parallel.ForEach(issuesGroup, group =>
{
if (progress.Token.IsCancellationRequested) return;
@@ -193,17 +202,127 @@ namespace UVtools.Core.Operations
layer.LayerMat = image;
});
+ // Remove from main list due the replicate below repair
+ Issues.RemoveAll(issue => issue.Type == LayerIssue.IssueType.Island && issue.Pixels.Length <= RemoveIslandsBelowEqualPixelCount);
+
if (islandsToRecompute.IsEmpty) break; // No more leftovers
}
}
+ if (_repairIslands && _attachIslandsBelowLayers > 0)
+ {
+ var islandsToProcess = Issues;
+
+ if (islandsToProcess is null)
+ {
+ var islandConfig = IslandDetectionConfig.Clone();
+ var overhangConfig = new OverhangDetectionConfiguration(false);
+ var touchingBoundsConfig = new TouchingBoundDetectionConfiguration(false);
+ var printHeightConfig = new PrintHeightDetectionConfiguration(false);
+ var resinTrapsConfig = new ResinTrapDetectionConfiguration(false);
+ var emptyLayersConfig = false;
+
+ islandConfig.Enabled = true;
+
+ islandsToProcess = SlicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapsConfig, touchingBoundsConfig, printHeightConfig, emptyLayersConfig, null, progress);
+ }
+
+ var issuesGroup =
+ islandsToProcess
+ .Where(issue => issue.Type == LayerIssue.IssueType.Island)
+ .GroupBy(issue => issue.LayerIndex);
+
+
+ progress.Reset("Attempt to attach islands below", (uint) islandsToProcess.Count);
+ Parallel.ForEach(issuesGroup, group =>
+ {
+ using var mat = SlicerFile[group.Key].LayerMat;
+ var matSpan = mat.GetDataByteSpan();
+ var matCache = new Dictionary<uint, Mat>();
+ var matCacheModified = new Dictionary<uint, bool>();
+ var startLayer = Math.Max(0, (int)group.Key - 2);
+ var lowestPossibleLayer = (uint)Math.Max(0, (int)group.Key - 1 - _attachIslandsBelowLayers);
+
+ for (var layerIndex = startLayer+1; layerIndex >= lowestPossibleLayer; layerIndex--)
+ {
+ Debug.WriteLine(layerIndex);
+ Monitor.Enter(SlicerFile[layerIndex].Mutex);
+ matCache.Add((uint) layerIndex, SlicerFile[layerIndex].LayerMat);
+ matCacheModified.Add((uint) layerIndex, false);
+ }
+
+ foreach (var issue in group)
+ {
+ int foundAt = startLayer == 0 ? 0 : - 1;
+ var requiredSupportingPixels = Math.Max(1, issue.PixelsCount * IslandDetectionConfig.RequiredPixelsToSupportMultiplier);
+
+ for (var layerIndex = startLayer; layerIndex >= lowestPossibleLayer && foundAt < 0; layerIndex--)
+ {
+ uint pixelsSupportingIsland = 0;
+
+ unsafe
+ {
+ var span = matCache[(uint) layerIndex].GetBytePointer();
+ foreach (var point in issue.Pixels)
+ {
+ if (span[mat.GetPixelPos(point)] <
+ IslandDetectionConfig.RequiredPixelBrightnessToSupport)
+ {
+ continue;
+ }
+
+ pixelsSupportingIsland++;
+
+ if (pixelsSupportingIsland >= requiredSupportingPixels)
+ {
+ foundAt = layerIndex + 1;
+ break;
+ }
+ }
+ }
+ }
+
+ // Copy pixels
+ if (foundAt >= 0)
+ {
+ for (var layerIndex = startLayer + 1; layerIndex >= foundAt; layerIndex--)
+ {
+ matCacheModified[(uint) layerIndex] = true;
+ unsafe
+ {
+ var span = matCache[(uint) layerIndex].GetBytePointer();
+ foreach (var point in issue.Pixels)
+ {
+ var pos = mat.GetPixelPos(point);
+ span[pos] = (byte) Math.Min(span[pos] + matSpan[pos], byte.MaxValue);
+ }
+ }
+ }
+ }
+
+ progress.LockAndIncrement();
+ }
+
+ foreach (var dict in matCache)
+ {
+ if (matCacheModified[dict.Key])
+ {
+ SlicerFile[dict.Key].LayerMat = dict.Value;
+ }
+ dict.Value.Dispose();
+ Monitor.Exit(SlicerFile[dict.Key].Mutex);
+ }
+ });
+
+ }
+
progress.Reset(ProgressAction, LayerRangeCount);
- if (RepairIslands || RepairResinTraps)
+ if (_repairIslands || _repairResinTraps)
{
Parallel.For(LayerIndexStart, LayerIndexEnd, layerIndex =>
{
if (progress.Token.IsCancellationRequested) return;
- Layer layer = SlicerFile[layerIndex];
+ var layer = SlicerFile[layerIndex];
Mat image = null;
void initImage()
@@ -213,7 +332,7 @@ namespace UVtools.Core.Operations
if (Issues is not null)
{
- if (RepairIslands && RemoveIslandsBelowEqualPixelCount > 0 && RemoveIslandsRecursiveIterations == 1)
+ if (_repairIslands && _removeIslandsBelowEqualPixelCount > 0 && _removeIslandsRecursiveIterations == 1)
{
Span<byte> bytes = null;
foreach (var issue in Issues)
@@ -221,7 +340,7 @@ namespace UVtools.Core.Operations
if (
issue.LayerIndex != layerIndex ||
issue.Type != LayerIssue.IssueType.Island ||
- issue.Pixels.Length > RemoveIslandsBelowEqualPixelCount) continue;
+ issue.Pixels.Length > _removeIslandsBelowEqualPixelCount) continue;
initImage();
if (bytes == null)
@@ -232,21 +351,9 @@ namespace UVtools.Core.Operations
bytes[image.GetPixelPos(issuePixel)] = 0;
}
}
- /*if (issues.TryGetValue((uint)layerIndex, out var issueList))
- {
- var bytes = image.GetPixelSpan<byte>();
- foreach (var issue in issueList.Where(issue =>
- issue.Type == LayerIssue.IssueType.Island && issue.Pixels.Length <= removeIslandsBelowEqualPixels))
- {
- foreach (var issuePixel in issue.Pixels)
- {
- bytes[image.GetPixelPos(issuePixel)] = 0;
- }
- }
- }*/
}
- if (RepairResinTraps)
+ if (_repairResinTraps)
{
foreach (var issue in Issues.Where(issue => issue.LayerIndex == layerIndex && issue.Type == LayerIssue.IssueType.ResinTrap))
{
@@ -261,25 +368,25 @@ namespace UVtools.Core.Operations
}
}
- if (RepairIslands && (GapClosingIterations > 0 || NoiseRemovalIterations > 0))
+ if (_repairIslands && (_gapClosingIterations > 0 || _noiseRemovalIterations > 0))
{
initImage();
using Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3),
new Point(-1, -1));
- if (GapClosingIterations > 0)
+ if (_gapClosingIterations > 0)
{
CvInvoke.MorphologyEx(image, image, MorphOp.Close, kernel, new Point(-1, -1),
- (int)GapClosingIterations, BorderType.Default, default);
+ (int)_gapClosingIterations, BorderType.Default, default);
}
- if (NoiseRemovalIterations > 0)
+ if (_noiseRemovalIterations > 0)
{
CvInvoke.MorphologyEx(image, image, MorphOp.Open, kernel, new Point(-1, -1),
- (int)NoiseRemovalIterations, BorderType.Default, default);
+ (int)_noiseRemovalIterations, BorderType.Default, default);
}
}
- if (!ReferenceEquals(image, null))
+ if (image is not null)
{
layer.LayerMat = image;
image.Dispose();
@@ -289,7 +396,7 @@ namespace UVtools.Core.Operations
});
}
- if (RemoveEmptyLayers)
+ if (_removeEmptyLayers)
{
List<uint> removeLayers = new();
for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++)
diff --git a/UVtools.Core/PixelEditor/PixelDrawing.cs b/UVtools.Core/PixelEditor/PixelDrawing.cs
index 45cbb9e..9aa8269 100644
--- a/UVtools.Core/PixelEditor/PixelDrawing.cs
+++ b/UVtools.Core/PixelEditor/PixelDrawing.cs
@@ -13,16 +13,28 @@ namespace UVtools.Core.PixelEditor
{
public class PixelDrawing : PixelOperation
{
- private BrushShapeType _brushShape = BrushShapeType.Rectangle;
+ private BrushShapeType _brushShape = BrushShapeType.Square;
private ushort _brushSize = 1;
private short _thickness = -1;
private byte _removePixelBrightness;
+ private double _rotationAngle;
public const byte MinRectangleBrush = 1;
public const byte MinCircleBrush = 7;
public enum BrushShapeType : byte
{
- Rectangle = 0,
- Circle
+ //Mask = 0,
+ Line = 1,
+ Triangle = 3,
+ Square = 4,
+ Pentagon = 5,
+ Hexagon = 6,
+ Heptagon = 7,
+ Octagon = 8,
+ Nonagon = 9,
+ Decagon = 10,
+ Hendecagon = 11,
+ Dodecagon = 12,
+ Circle = 100,
}
public override PixelOperationType OperationType => PixelOperationType.Drawing;
@@ -42,6 +54,12 @@ namespace UVtools.Core.PixelEditor
}
}
+ public double RotationAngle
+ {
+ get => _rotationAngle;
+ set => RaiseAndSetIfChanged(ref _rotationAngle, Math.Round(value, 2));
+ }
+
public ushort BrushSize
{
get => _brushSize;
@@ -77,9 +95,10 @@ namespace UVtools.Core.PixelEditor
}
- public PixelDrawing(uint layerIndex, Point location, LineType lineType, BrushShapeType brushShape, ushort brushSize, short thickness, byte removePixelBrightness, byte pixelBrightness, bool isAdd) : base(layerIndex, location, lineType, pixelBrightness)
+ public PixelDrawing(uint layerIndex, Point location, LineType lineType, BrushShapeType brushShape, double rotationAngle, ushort brushSize, short thickness, byte removePixelBrightness, byte pixelBrightness, bool isAdd) : base(layerIndex, location, lineType, pixelBrightness)
{
_brushShape = brushShape;
+ _rotationAngle = rotationAngle;
_brushSize = brushSize;
_thickness = thickness;
_removePixelBrightness = removePixelBrightness;
diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj
index f6a08d3..a11e5bd 100644
--- a/UVtools.Core/UVtools.Core.csproj
+++ b/UVtools.Core/UVtools.Core.csproj
@@ -10,7 +10,7 @@
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl>
<Description>MSLA/DLP, file analysis, calibration, repair, conversion and manipulation</Description>
- <Version>2.13.2</Version>
+ <Version>2.13.3</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
diff --git a/UVtools.InstallerMM/UVtools.InstallerMM.wxs b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
index cd32d11..e924a15 100644
--- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs
+++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs
@@ -308,8 +308,8 @@
<Component Id="owc6F31DEF09608AA645959666A4CB7FBBC" Guid="c94570a5-5bb4-a53f-e12f-9581bddf7d08">
<File Id="owf6F31DEF09608AA645959666A4CB7FBBC" Source="$(var.SourceDir)\mscordaccore.dll" KeyPath="yes" />
</Component>
- <Component Id="owc4E313F8BFFE35360B32FE794558D37F8" Guid="3a8b626a-4506-c48d-46b6-b63a59616c3c">
- <File Id="owf4E313F8BFFE35360B32FE794558D37F8" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_5.0.621.22011.dll" KeyPath="yes" />
+ <Component Id="owc628E325F4699E7AEC74011370E41ACEE" Guid="1965c775-7da4-0dcd-7aa2-4bda54a68382">
+ <File Id="owf628E325F4699E7AEC74011370E41ACEE" Source="$(var.SourceDir)\mscordaccore_amd64_amd64_5.0.721.25508.dll" KeyPath="yes" />
</Component>
<Component Id="owc5FC34571A1AE47A011FC6C2A95B00DA6" Guid="2591451e-0fe5-ccad-abf6-1d1097251253">
<File Id="owf5FC34571A1AE47A011FC6C2A95B00DA6" Source="$(var.SourceDir)\mscordbi.dll" KeyPath="yes" />
@@ -897,8 +897,8 @@
<Component Id="owc1C3DE0BEF10A5791E37D4C3D97AED5E1" Guid="b1851a14-1200-d448-1acd-b229bf6ac918">
<File Id="owf1C3DE0BEF10A5791E37D4C3D97AED5E1" Source="$(var.SourceDir)\Assets\PrusaSlicer\printer\AnyCubic Photon.ini" KeyPath="yes" />
</Component>
- <Component Id="owcFE63AE2337EB232D5C139086B8A44942" Guid="02bb8e1d-14a7-f786-5cb9-e7b37337918b">
- <File Id="owfFE63AE2337EB232D5C139086B8A44942" Source="$(var.SourceDir)\Assets\PrusaSlicer\printer\Creality HALOT-SKY CL-60.ini" KeyPath="yes" />
+ <Component Id="owc64BDFB521E8E34CAE3547B3D1B8F3996" Guid="71d86e2a-c6b7-884a-65ff-2260e4edd56f">
+ <File Id="owf64BDFB521E8E34CAE3547B3D1B8F3996" Source="$(var.SourceDir)\Assets\PrusaSlicer\printer\Creality HALOT-ONE CL-60.ini" KeyPath="yes" />
</Component>
<Component Id="owc88F58B3D3A8E53CEFC3FE84F6C20FAB8" Guid="8f66ab9b-8ff1-f324-9ec5-5cbb688dd5f6">
<File Id="owf88F58B3D3A8E53CEFC3FE84F6C20FAB8" Source="$(var.SourceDir)\Assets\PrusaSlicer\printer\Creality HALOT-SKY CL-89.ini" KeyPath="yes" />
diff --git a/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs b/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs
index d4c9d89..3cdd6f6 100644
--- a/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs
+++ b/UVtools.WPF/Controls/Calibrators/CalibrateElephantFootControl.axaml.cs
@@ -75,7 +75,7 @@ namespace UVtools.WPF.Controls.Calibrators
{
Operation.ErodeKernel.Matrix = _kernelCtrl.GetMatrix();
Operation.ErodeKernel.Anchor = _kernelCtrl.Anchor;
- return !(Operation.ErodeKernel.Matrix is null);
+ return Operation.ErodeKernel.Matrix is not null;
}
public void UpdatePreview()
diff --git a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs
index dbb6600..baa6316 100644
--- a/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolLayerImportControl.axaml.cs
@@ -89,7 +89,7 @@ namespace UVtools.WPF.Controls.Tools
FilesListBox = this.Find<ListBox>("FilesListBox");
FilesListBox.DoubleTapped += (sender, args) =>
{
- if (!(FilesListBox.SelectedItem is ValueDescription file)) return;
+ if (FilesListBox.SelectedItem is not ValueDescription file) return;
App.StartProcess(file.ValueAsString);
};
FilesListBox.KeyUp += (sender, e) =>
diff --git a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml
index fcc7036..84378c0 100644
--- a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml
+++ b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml
@@ -35,15 +35,17 @@
</StackPanel>
<Grid ColumnDefinitions="Auto,10,150,5,Auto"
- RowDefinitions="Auto,10,Auto">
+ RowDefinitions="Auto,10,Auto,10,Auto">
<!-- Remove islands equal or smaller than -->
<TextBlock
Grid.Row="0"
Grid.Column="0"
VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.RepairIslands}"
ToolTip.Tip="The pixel area threshold above which islands will not be removed by this repair.
- &#x0a;Islands remaining after repair will require supports to be added manually."
+&#x0a;Islands remaining after repair will require supports to be added manually.
+&#x0a;NOTE: This repair method requires the issues to be manually computed before hand."
Text="Remove islands equal or smaller than:"/>
<NumericUpDown
@@ -52,9 +54,11 @@
Increment="1"
Minimum="0"
Maximum="65535"
+ IsEnabled="{Binding Operation.RepairIslands}"
ToolTip.Tip="The pixel area threshold above which islands will not be removed by this repair.
-&#x0a;Islands remaining after repair will require supports to be added manually."
+&#x0a;Islands remaining after repair will require supports to be added manually.
+&#x0a;NOTE: This repair method requires the issues to be manually computed before hand."
Value="{Binding Operation.RemoveIslandsBelowEqualPixelCount}"
/>
@@ -62,6 +66,7 @@
Grid.Row="0"
Grid.Column="4"
VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.RepairIslands}"
Text="px"/>
@@ -70,10 +75,11 @@
Grid.Row="2"
Grid.Column="0"
VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.RepairIslands}"
ToolTip.Tip="If the removal of an island in the current layer results in a new island being introduce in the layer above, the island in the layer above will also be automatically removed.
&#x0a;This process will repeat for up to the number of layers specified. Set to 0 to repeat until there are no more valid islands to remove.
-&#x0a;
&#x0a;NOTE: Use with caution as this can remove large portions of your model if proper supports have not been added beforehand.
+&#x0a;This repair method requires the issues to be manually computed before hand.
&#x0a;Using this function with high values can be extremely slow depending on resolution and issue count."
Text="Recursively remove islands for up to:"/>
@@ -83,22 +89,54 @@
Increment="1"
Minimum="0"
Maximum="65535"
-
+ IsEnabled="{Binding Operation.RepairIslands}"
ToolTip.Tip="If the removal of an island in the current layer results in a new island being introduce in the layer above, the island in the layer above will also be automatically removed.
&#x0a;This process will repeat for up to the number of layers specified. Set to 0 to repeat until there are no more valid islands to remove.
-&#x0a;
&#x0a;NOTE: Use with caution as this can remove large portions of your model if proper supports have not been added beforehand.
+&#x0a;This repair method requires the issues to be manually computed before hand.
&#x0a;Using this function with high values can be extremely slow depending on resolution and issue count."
Value="{Binding Operation.RemoveIslandsRecursiveIterations}"
/>
-
<TextBlock
Grid.Row="2"
Grid.Column="4"
VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.RepairIslands}"
Text="layers"/>
+
+ <!-- Attempt to attach islands below -->
+ <TextBlock
+ Grid.Row="4"
+ Grid.Column="0"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.RepairIslands}"
+ ToolTip.Tip="Attempt to attach the islands down to n layers when found a safe mass below to support it.
+&#x0a;NOTE: Use with caution and with a low value, this can mostly be used when there are some missed support layers in between as observed from the issues.
+&#x0a;This repair method requires the issues to be manually computed before hand.
+&#x0a;Use 0 to disable this repair method."
+ Text="Attempt to attach islands down to:"/>
+
+ <NumericUpDown
+ Grid.Row="4"
+ Grid.Column="2"
+ Increment="1"
+ Minimum="0"
+ Maximum="65535"
+ IsEnabled="{Binding Operation.RepairIslands}"
+ ToolTip.Tip="Attempt to attach the islands down to n layers when found a safe mass below to support it.
+&#x0a;NOTE: Use with caution and with a low value, this can mostly be used when there are some missed support layers in between as observed from the issues.
+&#x0a;This repair method requires the issues to be manually computed before hand.
+&#x0a;Use 0 to disable this repair method."
+ Value="{Binding Operation.AttachIslandsBelowLayers}"/>
+
+ <TextBlock
+ Grid.Row="4"
+ Grid.Column="4"
+ VerticalAlignment="Center"
+ IsEnabled="{Binding Operation.RepairIslands}"
+ Text="layers"/>
</Grid>
diff --git a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs
index 6b038b4..a285344 100644
--- a/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs
+++ b/UVtools.WPF/Controls/Tools/ToolRepairLayersControl.axaml.cs
@@ -26,6 +26,7 @@ namespace UVtools.WPF.Controls.Tools
RemoveEmptyLayers = UserSettings.Instance.LayerRepair.RemoveEmptyLayers,
RemoveIslandsBelowEqualPixelCount = UserSettings.Instance.LayerRepair.RemoveIslandsBelowEqualPixels,
RemoveIslandsRecursiveIterations = UserSettings.Instance.LayerRepair.RemoveIslandsRecursiveIterations,
+ AttachIslandsBelowLayers = UserSettings.Instance.LayerRepair.AttachIslandsBelowLayers,
GapClosingIterations = UserSettings.Instance.LayerRepair.ClosingIterations,
NoiseRemovalIterations = UserSettings.Instance.LayerRepair.OpeningIterations,
};
@@ -37,6 +38,7 @@ namespace UVtools.WPF.Controls.Tools
Operation.RemoveEmptyLayers = UserSettings.Instance.LayerRepair.RemoveEmptyLayers;
Operation.RemoveIslandsBelowEqualPixelCount = UserSettings.Instance.LayerRepair.RemoveIslandsBelowEqualPixels;
Operation.RemoveIslandsRecursiveIterations = UserSettings.Instance.LayerRepair.RemoveIslandsRecursiveIterations;
+ Operation.AttachIslandsBelowLayers = UserSettings.Instance.LayerRepair.AttachIslandsBelowLayers;
Operation.GapClosingIterations = UserSettings.Instance.LayerRepair.ClosingIterations;
Operation.NoiseRemovalIterations = UserSettings.Instance.LayerRepair.OpeningIterations;
}
diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs
index 403f17a..0e9def1 100644
--- a/UVtools.WPF/MainWindow.Issues.cs
+++ b/UVtools.WPF/MainWindow.Issues.cs
@@ -335,14 +335,15 @@ namespace UVtools.WPF
private void IssuesGridOnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
+ if (DataContext is null) return;
+
if (IssuesGrid.SelectedItem is not LayerIssue issue)
{
ShowLayer();
return;
}
- if (issue.Type == LayerIssue.IssueType.TouchingBound || issue.Type == LayerIssue.IssueType.EmptyLayer ||
- (issue.X == -1 && issue.Y == -1))
+ if (issue.Type is LayerIssue.IssueType.TouchingBound or LayerIssue.IssueType.EmptyLayer || (issue.X == -1 && issue.Y == -1))
{
ZoomToFit();
}
diff --git a/UVtools.WPF/MainWindow.LayerPreview.cs b/UVtools.WPF/MainWindow.LayerPreview.cs
index c0a304a..d5789b9 100644
--- a/UVtools.WPF/MainWindow.LayerPreview.cs
+++ b/UVtools.WPF/MainWindow.LayerPreview.cs
@@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
-using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
@@ -24,7 +23,6 @@ using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;
-using Emgu.CV.XImgproc;
using UVtools.Core;
using UVtools.Core.Extensions;
using UVtools.Core.PixelEditor;
@@ -991,9 +989,11 @@ namespace UVtools.WPF
continue;
}
- switch (operationDrawing.BrushShape)
+ LayerCache.ImageBgr.DrawPolygon((byte)operationDrawing.BrushShape, operationDrawing.BrushSize / 2, operationDrawing.Location,
+ new MCvScalar(color.B, color.G, color.R), operationDrawing.RotationAngle, operationDrawing.Thickness, operationDrawing.LineType);
+ /*switch (operationDrawing.BrushShape)
{
- case PixelDrawing.BrushShapeType.Rectangle:
+ case PixelDrawing.BrushShapeType.Square:
CvInvoke.Rectangle(LayerCache.ImageBgr, operationDrawing.Rectangle,
new MCvScalar(color.B, color.G, color.R), operationDrawing.Thickness,
operationDrawing.LineType);
@@ -1005,7 +1005,7 @@ namespace UVtools.WPF
break;
default:
throw new ArgumentOutOfRangeException();
- }
+ }*/
}
else if (operation.OperationType == PixelOperation.PixelOperationType.Text)
{
@@ -1861,10 +1861,45 @@ namespace UVtools.WPF
if (DrawingPixelDrawing.BrushSize > 1)
{
- cursor = EmguExtensions.InitMat(new Size(DrawingPixelDrawing.BrushSize, DrawingPixelDrawing.BrushSize), 4);
- switch (DrawingPixelDrawing.BrushShape)
+ if ((byte)DrawingPixelDrawing.BrushShape >= 1)
+ {
+ int cursorSize = DrawingPixelDrawing.BrushSize;
+ if (DrawingPixelDrawing.Thickness > 1)
+ {
+ cursorSize += DrawingPixelDrawing.Thickness;
+ }
+ cursor = EmguExtensions.InitMat(new Size(cursorSize, cursorSize), 4);
+
+ cursor.DrawPolygon((byte) DrawingPixelDrawing.BrushShape, DrawingPixelDrawing.BrushSize / 2, cursor.Size.ToPoint().Half(),
+ _pixelEditorCursorColor, DrawingPixelDrawing.RotationAngle, DrawingPixelDrawing.Thickness, DrawingPixelDrawing.LineType);
+
+ if (DrawingPixelDrawing.BrushShape != PixelDrawing.BrushShapeType.Circle)
+ {
+ if (_showLayerImageFlipped)
+ {
+ var flipType = FlipType.None;
+ if (_showLayerImageFlippedHorizontally)
+ flipType |= FlipType.Horizontal;
+ if (_showLayerImageFlippedVertically)
+ flipType |= FlipType.Vertical;
+
+ if (flipType != FlipType.None)
+ CvInvoke.Flip(cursor, cursor, flipType);
+ }
+
+ if (_showLayerImageRotated)
+ {
+ CvInvoke.Rotate(cursor, cursor,
+ _showLayerImageRotateCcwDirection
+ ? RotateFlags.Rotate90CounterClockwise
+ : RotateFlags.Rotate90Clockwise);
+ }
+ }
+ }
+
+ /*switch (DrawingPixelDrawing.BrushShape)
{
- case PixelDrawing.BrushShapeType.Rectangle:
+ case PixelDrawing.BrushShapeType.Square:
CvInvoke.Rectangle(cursor,
new Rectangle(Point.Empty, new Size(DrawingPixelDrawing.BrushSize, DrawingPixelDrawing.BrushSize)),
_pixelEditorCursorColor, DrawingPixelDrawing.Thickness, DrawingPixelDrawing.LineType);
@@ -1889,7 +1924,7 @@ namespace UVtools.WPF
1, DrawingPixelDrawing.LineType
);
break;
- }
+ }*/
}
break;
case PixelOperation.PixelOperationType.Text:
diff --git a/UVtools.WPF/MainWindow.PixelEditor.cs b/UVtools.WPF/MainWindow.PixelEditor.cs
index 43be784..6652947 100644
--- a/UVtools.WPF/MainWindow.PixelEditor.cs
+++ b/UVtools.WPF/MainWindow.PixelEditor.cs
@@ -25,6 +25,7 @@ using UVtools.Core;
using UVtools.Core.Extensions;
using UVtools.Core.PixelEditor;
using UVtools.WPF.Extensions;
+using DrawingExtensions = UVtools.Core.Extensions.DrawingExtensions;
namespace UVtools.WPF
{
@@ -182,7 +183,7 @@ namespace UVtools.WPF
for (uint layerIndex = minLayer; layerIndex <= maxLayer; layerIndex++)
{
var operationDrawing = new PixelDrawing(layerIndex, realLocation, DrawingPixelDrawing.LineType,
- DrawingPixelDrawing.BrushShape, DrawingPixelDrawing.BrushSize, DrawingPixelDrawing.Thickness, DrawingPixelDrawing.RemovePixelBrightness, DrawingPixelDrawing.PixelBrightness, isAdd);
+ DrawingPixelDrawing.BrushShape, DrawingPixelDrawing.RotationAngle, DrawingPixelDrawing.BrushSize, DrawingPixelDrawing.Thickness, DrawingPixelDrawing.RemovePixelBrightness, DrawingPixelDrawing.PixelBrightness, isAdd);
//if (PixelHistory.Contains(operation)) continue;
AddDrawing(operationDrawing);
@@ -195,26 +196,57 @@ namespace UVtools.WPF
if (operationDrawing.BrushSize == 1)
{
- unsafe
+ /*unsafe
{
using var framebuffer = bitmap.Lock();
var data = (uint*)framebuffer.Address.ToPointer();
data[bitmap.GetPixelPos(location)] =
color.ToUint32();
- }
-
- LayerImageBox.InvalidateArrange();
+ }*/
+
+ LayerCache.Canvas.DrawPoint(location.X, location.Y, new SKColor(color.ToUint32()));
+
+
+ LayerImageBox.InvalidateVisual();
// LayerCache.ImageBgr.SetByte(operationDrawing.Location.X, operationDrawing.Location.Y,
// new[] { color.B, color.G, color.R });
continue;
}
+ int halfBrush = operationDrawing.BrushSize / 2;
switch (operationDrawing.BrushShape)
{
- case PixelDrawing.BrushShapeType.Rectangle:
-
- int shiftPos = operationDrawing.BrushSize / 2;
- LayerCache.Canvas.DrawRect(location.X - shiftPos, location.Y - shiftPos,
+ case PixelDrawing.BrushShapeType.Line:
+ Point point1 = new(location.X - halfBrush, location.Y);
+ Point point2 = new(location.X + halfBrush, location.Y);
+ point1 = point1.Rotate(operationDrawing.RotationAngle, location);
+ point2 = point2.Rotate(operationDrawing.RotationAngle, location);
+
+ if (_showLayerImageRotated)
+ {
+ if (_showLayerImageRotateCcwDirection)
+ {
+ point1 = point1.Rotate(90, location);
+ point2 = point2.Rotate(90, location);
+ }
+ else
+ {
+ point1 = point1.Rotate(-90, location);
+ point2 = point2.Rotate(-90, location);
+ }
+ }
+
+ LayerCache.Canvas.DrawLine(point1.X, point1.Y, point2.X, point2.Y, new SKPaint
+ {
+ IsAntialias = operationDrawing.LineType == LineType.AntiAlias,
+ Color = new SKColor(color.ToUint32()),
+ IsStroke = operationDrawing.Thickness >= 0,
+ StrokeWidth = operationDrawing.Thickness,
+ StrokeCap = SKStrokeCap.Round
+ });
+ break;
+ /*case PixelDrawing.BrushShapeType.Square:
+ LayerCache.Canvas.DrawRect(location.X - halfBrush, location.Y - halfBrush,
operationDrawing.BrushSize,
operationDrawing.BrushSize,
new SKPaint
@@ -227,10 +259,8 @@ namespace UVtools.WPF
/*CvInvoke.Rectangle(LayerCache.ImageBgr, GetTransposedRectangle(operationDrawing.Rectangle),
new MCvScalar(color.B, color.G, color.R), operationDrawing.Thickness,
operationDrawing.LineType);*/
- break;
+ //break;
case PixelDrawing.BrushShapeType.Circle:
-
-
LayerCache.Canvas.DrawCircle(location.X, location.Y, operationDrawing.BrushSize / 2f,
new SKPaint
{
@@ -239,13 +269,69 @@ namespace UVtools.WPF
IsStroke = operationDrawing.Thickness >= 0,
StrokeWidth = operationDrawing.Thickness
});
-
+
/*CvInvoke.Circle(LayerCache.ImageBgr, location, operationDrawing.BrushSize / 2,
new MCvScalar(color.B, color.G, color.R), operationDrawing.Thickness,
operationDrawing.LineType);*/
break;
default:
- throw new ArgumentOutOfRangeException();
+ var angle = operationDrawing.RotationAngle;
+ if (_showLayerImageRotated)
+ {
+ if (!_showLayerImageFlipped || _showLayerImageFlippedHorizontally && _showLayerImageFlippedVertically)
+ {
+ if (_showLayerImageRotateCcwDirection)
+ {
+ angle -= 90;
+ }
+ else
+ {
+ angle += 90;
+ }
+ }
+ else
+ {
+ if (_showLayerImageRotateCcwDirection)
+ {
+ angle += 90;
+ }
+ else
+ {
+ angle -= 90;
+ }
+ }
+ }
+
+
+ var vertices = DrawingExtensions.GetPolygonVertices((byte) operationDrawing.BrushShape,
+ operationDrawing.BrushSize / 2, location, angle, _showLayerImageFlipped && _showLayerImageFlippedHorizontally, _showLayerImageFlipped && _showLayerImageFlippedVertically);
+
+ //if(angle % 360 != 0) PointExtensions.Rotate(vertices, angle, location);
+
+ var path = new SKPath();
+ path.MoveTo(vertices[0].X, vertices[0].Y);
+ for (var i = 1; i < vertices.Length; i++)
+ {
+ path.LineTo(vertices[i].X, vertices[i].Y);
+ }
+ path.Close();
+
+ LayerCache.Canvas.DrawPath(path, new SKPaint
+ {
+ IsAntialias = operationDrawing.LineType == LineType.AntiAlias,
+ Color = new SKColor(color.ToUint32()),
+ IsStroke = operationDrawing.Thickness >= 0,
+ StrokeWidth = operationDrawing.Thickness
+ });
+ /*LayerCache.Canvas.DrawPoints(SKPointMode.Polygon, points,
+ new SKPaint
+ {
+ IsAntialias = operationDrawing.LineType == LineType.AntiAlias,
+ Color = new SKColor(color.ToUint32()),
+ IsStroke = operationDrawing.Thickness >= 0,
+ StrokeWidth = operationDrawing.Thickness
+ });*/
+ break;
}
LayerImageBox.InvalidateVisual();
//RefreshLayerImage();
diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml
index 1eedf95..ecc8aa8 100644
--- a/UVtools.WPF/MainWindow.axaml
+++ b/UVtools.WPF/MainWindow.axaml
@@ -823,7 +823,7 @@
</Border>
<Grid
- RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto"
+ RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto"
ColumnDefinitions="Auto,10,130,5,40">
<TextBlock
@@ -853,12 +853,30 @@
SelectedItem="{Binding DrawingPixelDrawing.BrushShape}"/>
<TextBlock
- Grid.Row="4"
+ Grid.Row="4"
+ Grid.Column="0"
+ VerticalAlignment="Center"
+ Text="Rotation angle:" />
+ <NumericUpDown
+ Grid.Row="4"
+ Grid.Column="2"
+ FormatString="F2"
+ Minimum="-360"
+ Maximum="360"
+ Value="{Binding DrawingPixelDrawing.RotationAngle}"/>
+ <TextBlock
+ Grid.Row="4"
+ Grid.Column="4"
+ VerticalAlignment="Center"
+ Text="º" />
+
+ <TextBlock
+ Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Text="Brush diameter:" />
<NumericUpDown
- Grid.Row="4"
+ Grid.Row="6"
Grid.Column="2"
Minimum="1"
Maximum="4000"
@@ -866,18 +884,18 @@
HorizontalAlignment="Stretch"
Value="{Binding DrawingPixelDrawing.BrushSize}"/>
<TextBlock
- Grid.Row="4"
+ Grid.Row="6"
Grid.Column="4"
VerticalAlignment="Center"
Text="px" />
<TextBlock
- Grid.Row="6"
+ Grid.Row="8"
Grid.Column="0"
VerticalAlignment="Center"
Text="Thickness:" />
<NumericUpDown
- Grid.Row="6"
+ Grid.Row="8"
Grid.Column="2"
Minimum="-1"
Maximum="255"
@@ -885,18 +903,18 @@
HorizontalAlignment="Stretch"
Value="{Binding DrawingPixelDrawing.Thickness}"/>
<TextBlock
- Grid.Row="6"
+ Grid.Row="8"
Grid.Column="4"
VerticalAlignment="Center"
Text="px" />
<TextBlock
- Grid.Row="8"
+ Grid.Row="10"
Grid.Column="0"
VerticalAlignment="Center"
Text="Remove pixel brightness:" />
<NumericUpDown
- Grid.Row="8"
+ Grid.Row="10"
Grid.Column="2"
Minimum="0"
Maximum="255"
@@ -904,18 +922,18 @@
HorizontalAlignment="Stretch"
Value="{Binding DrawingPixelDrawing.RemovePixelBrightness}"/>
<TextBlock
- Grid.Row="8"
+ Grid.Row="10"
Grid.Column="4"
VerticalAlignment="Center"
Text="{Binding DrawingPixelDrawing.RemovePixelBrightnessPercent, StringFormat=\{0:0\}%}" />
<TextBlock
- Grid.Row="10"
+ Grid.Row="12"
Grid.Column="0"
VerticalAlignment="Center"
Text="Add pixel brightness:" />
<NumericUpDown
- Grid.Row="10"
+ Grid.Row="12"
Grid.Column="2"
Minimum="1"
Maximum="255"
@@ -923,18 +941,18 @@
HorizontalAlignment="Stretch"
Value="{Binding DrawingPixelDrawing.PixelBrightness}"/>
<TextBlock
- Grid.Row="10"
+ Grid.Row="12"
Grid.Column="4"
VerticalAlignment="Center"
Text="{Binding DrawingPixelDrawing.PixelBrightnessPercent, StringFormat=\{0:0\}%}" />
<TextBlock
- Grid.Row="12"
+ Grid.Row="14"
Grid.Column="0"
VerticalAlignment="Center"
Text="Layers depth below:" />
<NumericUpDown
- Grid.Row="12"
+ Grid.Row="14"
Grid.Column="2"
Grid.ColumnSpan="3"
Minimum="0"
@@ -944,12 +962,12 @@
<TextBlock
- Grid.Row="14"
+ Grid.Row="16"
Grid.Column="0"
VerticalAlignment="Center"
Text="Layers depth above:" />
<NumericUpDown
- Grid.Row="14"
+ Grid.Row="16"
Grid.Column="2"
Grid.ColumnSpan="3"
Minimum="0"
diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs
index 67fc9b7..37a3d02 100644
--- a/UVtools.WPF/MainWindow.axaml.cs
+++ b/UVtools.WPF/MainWindow.axaml.cs
@@ -623,7 +623,7 @@ namespace UVtools.WPF
if (e.Handled
|| !IsFileLoaded
|| LayerImageBox.IsPanning
- || !(LayerImageBox.TrackerImage is null)
+ || LayerImageBox.TrackerImage is not null
|| LayerImageBox.Cursor == StaticControls.CrossCursor
|| LayerImageBox.Cursor == StaticControls.HandCursor
|| LayerImageBox.SelectionMode == AdvancedImageBox.SelectionModes.Rectangle
diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj
index 85c39b9..e53c7c8 100644
--- a/UVtools.WPF/UVtools.WPF.csproj
+++ b/UVtools.WPF/UVtools.WPF.csproj
@@ -12,7 +12,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
- <Version>2.13.2</Version>
+ <Version>2.13.3</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs
index 38ebee4..23dab0c 100644
--- a/UVtools.WPF/UserSettings.cs
+++ b/UVtools.WPF/UserSettings.cs
@@ -1070,6 +1070,7 @@ namespace UVtools.WPF
private bool _removeEmptyLayers = true;
private ushort _removeIslandsBelowEqualPixels = 5;
private ushort _removeIslandsRecursiveIterations = 4;
+ private ushort _attachIslandsBelowLayers = 2;
private byte _closingIterations = 2;
private byte _openingIterations = 0;
@@ -1103,6 +1104,12 @@ namespace UVtools.WPF
set => RaiseAndSetIfChanged(ref _removeIslandsRecursiveIterations, value);
}
+ public ushort AttachIslandsBelowLayers
+ {
+ get => _attachIslandsBelowLayers;
+ set => RaiseAndSetIfChanged(ref _attachIslandsBelowLayers, value);
+ }
+
public byte ClosingIterations
{
get => _closingIterations;
diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml
index 3ff30f1..48ede09 100644
--- a/UVtools.WPF/Windows/SettingsWindow.axaml
+++ b/UVtools.WPF/Windows/SettingsWindow.axaml
@@ -1387,6 +1387,15 @@
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,0" Spacing="10">
+ <NumericUpDown
+ Value="{Binding Settings.LayerRepair.AttachIslandsBelowLayers}"
+ Width="70"
+ Minimum="0"
+ Maximum="65535"/>
+ <TextBlock VerticalAlignment="Center" Text="Attempt to attach islands down to this layers (0 = Disabled)"/>
+ </StackPanel>
+
+ <StackPanel Orientation="Horizontal" Margin="10,0" Spacing="10">
<NumericUpDown
Value="{Binding Settings.LayerRepair.ClosingIterations}"
Width="70"
diff --git a/UVtools.WPF/Windows/ToolWindow.axaml.cs b/UVtools.WPF/Windows/ToolWindow.axaml.cs
index 60e0ff4..2d1da9c 100644
--- a/UVtools.WPF/Windows/ToolWindow.axaml.cs
+++ b/UVtools.WPF/Windows/ToolWindow.axaml.cs
@@ -1,9 +1,6 @@
using System;
using System.Collections.ObjectModel;
-using System.Diagnostics;
using System.Drawing;
-using System.IO;
-using System.Xml.Serialization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
@@ -147,7 +144,7 @@ namespace UVtools.WPF.Windows
get => _layerIndexEnd;
set
{
- if (!(ToolControl?.BaseOperation is null))
+ if (ToolControl?.BaseOperation is not null)
{
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None;
ToolControl.BaseOperation.LayerIndexEnd = value;
@@ -178,14 +175,14 @@ namespace UVtools.WPF.Windows
{
LayerIndexStart = 0;
LayerIndexEnd = MaximumLayerIndex;
- if(!(ToolControl is null))
+ if(ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.All;
}
public void SelectCurrentLayer()
{
LayerIndexStart = LayerIndexEnd = App.MainWindow.ActualLayer;
- if (!(ToolControl is null))
+ if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Current;
}
@@ -193,7 +190,7 @@ namespace UVtools.WPF.Windows
{
LayerIndexEnd = App.MainWindow.ActualLayer;
LayerIndexStart = 0;
- if (!(ToolControl is null))
+ if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None;
}
@@ -201,7 +198,7 @@ namespace UVtools.WPF.Windows
{
LayerIndexStart = App.MainWindow.ActualLayer;
LayerIndexEnd = SlicerFile.LastLayerIndex;
- if (!(ToolControl is null))
+ if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.None;
}
@@ -209,7 +206,7 @@ namespace UVtools.WPF.Windows
{
LayerIndexStart = 0;
LayerIndexEnd = SlicerFile.BottomLayerCount-1u;
- if (!(ToolControl is null))
+ if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Bottom;
}
@@ -217,21 +214,21 @@ namespace UVtools.WPF.Windows
{
LayerIndexStart = SlicerFile.BottomLayerCount;
LayerIndexEnd = MaximumLayerIndex;
- if (!(ToolControl is null))
+ if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Normal;
}
public void SelectFirstLayer()
{
LayerIndexStart = LayerIndexEnd = 0;
- if (!(ToolControl is null))
+ if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.First;
}
public void SelectLastLayer()
{
LayerIndexStart = LayerIndexEnd = MaximumLayerIndex;
- if (!(ToolControl is null))
+ if (ToolControl is not null)
ToolControl.BaseOperation.LayerRangeSelection = Enumerations.LayerRangeSelection.Last;
}
@@ -401,7 +398,7 @@ namespace UVtools.WPF.Windows
{
var name = string.IsNullOrWhiteSpace(_profileText) ? null : _profileText.Trim();
var operation = OperationProfiles.FindByName(ToolControl.BaseOperation, name);
- if (!(operation is null))
+ if (operation is not null)
{
if (await this.MessageBoxQuestion(
$"A profile with same name or settings already exists.\nDo you want to overwrite:\n{operation}",