mirror of
https://github.com/riegera2412/UVtools.git
synced 2026-07-08 17:42:31 +02:00
v3.3.0
- **Shortcuts:** - (Add) **Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from layer - (Add) **Alt + Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from all layers - (Add) **Ctrl + Delete:** While on layer preview, will remove the current layer - (Add) **Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in layer - (Add) **Alt + Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in all layers - (Add) **Ctrl + Insert:** While on layer preview, will clone the current layer - (Add) **Home:** While on layer preview will go to first layer - (Add) **End:** While on layer preview will go to last layer - (Add) **Page up:** While on layer preview will skip +10 layers - (Add) **Page down:** While on layer preview will skip -10 layers - (Add) Tool - Lithophane: Generate lithophane from a picture - (Fix) Pixel arithmetic: When run with masks it produce a incorrect outcome - (Fix) CXDLP: Layer area table miscalculation, causing slow down prints
This commit is contained in:
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## 10/04/2022 - v3.3.0
|
||||
|
||||
- **Shortcuts:**
|
||||
- (Add) **Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from layer
|
||||
- (Add) **Alt + Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from all layers
|
||||
- (Add) **Ctrl + Delete:** While on layer preview, will remove the current layer
|
||||
- (Add) **Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in layer
|
||||
- (Add) **Alt + Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in all layers
|
||||
- (Add) **Ctrl + Insert:** While on layer preview, will clone the current layer
|
||||
- (Add) **Home:** While on layer preview will go to first layer
|
||||
- (Add) **End:** While on layer preview will go to last layer
|
||||
- (Add) **Page up:** While on layer preview will skip +10 layers
|
||||
- (Add) **Page down:** While on layer preview will skip -10 layers
|
||||
- (Add) Tool - Lithophane: Generate lithophane from a picture
|
||||
- (Fix) Pixel arithmetic: When run with masks it produce a incorrect outcome
|
||||
- (Fix) CXDLP: Layer area table miscalculation, causing slow down prints
|
||||
|
||||
## 06/04/2022 - v3.2.2
|
||||
|
||||
- **Settings:**
|
||||
|
||||
+14
-22
@@ -1,23 +1,15 @@
|
||||
- **Settings:**
|
||||
- (Add) Remove source file after automatic conversion (#444)
|
||||
- (Add) Remove source file after manual conversion (#444)
|
||||
- (Add) **Average resin bottle cost:** The average cost per one resin bottle of 1000ml.
|
||||
Used to calculate the material cost when the file lacks that information.
|
||||
Use 0 to disable this feature and only show the cost if file have that information.
|
||||
If this value is changed, you need to reload the current file to update the cost.
|
||||
- (Change) Move "Expand and show tool descriptions by default" to From `General` to `Tools` tab (Setting will reset to default)
|
||||
- **File formats:**
|
||||
- (Add) Property `StartingMaterialMilliliters`: Gets the starting material milliliters when the file was loaded
|
||||
- (Add) Property `StartingMaterialCost`: Gets the starting material cost when the file was loaded
|
||||
- (Add) Property `MaterialMilliliterCost`: Gets the material cost per one milliliter
|
||||
- (Improvement) Update `MaterialCost` when `MaterialMilliliters` changes (#449)
|
||||
- **Raft relief:**
|
||||
- (Add) Linked lines: Remove the raft, keep supports and link them with lines
|
||||
- (Improvement) Change the supports detection parameters to be more effective and precise on detect the starting layer
|
||||
- (Fix) Brightness percentage not getting updated
|
||||
- (Fix) Remove anti-aliased edges from Tabs
|
||||
- (Improvement) Core: Minor clean-up
|
||||
- (Fix) Issue repair error when 'Auto repair layers and issues on file load' is enabled (#446)
|
||||
- (Fix) UI: Selecting a object with ROI when flip is verically, will cause 1px up-shift on the preview
|
||||
- (Fix) macOS permission error due loss of attributes on the build script, WSL have bug with mv, using ln&rm instead
|
||||
- **Shortcuts:**
|
||||
- (Add) **Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from layer
|
||||
- (Add) **Alt + Delete:** While on layer preview and with roi or mask(s) selected, will remove the selected area from all layers
|
||||
- (Add) **Ctrl + Delete:** While on layer preview, will remove the current layer
|
||||
- (Add) **Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in layer
|
||||
- (Add) **Alt + Insert:** While on layer preview and with roi or mask(s) selected, will keep only the selected area in all layers
|
||||
- (Add) **Ctrl + Insert:** While on layer preview, will clone the current layer
|
||||
- (Add) **Home:** While on layer preview will go to first layer
|
||||
- (Add) **End:** While on layer preview will go to last layer
|
||||
- (Add) **Page up:** While on layer preview will skip +10 layers
|
||||
- (Add) **Page down:** While on layer preview will skip -10 layers
|
||||
- (Add) Tool - Lithophane: Generate lithophane from a picture
|
||||
- (Fix) Pixel arithmetic: When run with masks it produce a incorrect outcome
|
||||
- (Fix) CXDLP: Layer area table miscalculation, causing slow down prints
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ typedef struct {
|
||||
|
||||
typedef struct() {
|
||||
uint32 layerArea <fgcolor=cBlack, bgcolor=cWhite>;
|
||||
uint32 layerPointNum <fgcolor=cBlack, bgcolor=cWhite>;
|
||||
uint32 layerLineCount <fgcolor=cBlack, bgcolor=cWhite>;
|
||||
|
||||
layerPointsData pD()[layerPointNum];
|
||||
layerPointsData pD()[layerLineCount];
|
||||
ubyte CR_LF2[2] <fgcolor=cBlack, bgcolor=cRed>;
|
||||
} layerData;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using UVtools.Core.Extensions;
|
||||
|
||||
namespace UVtools.Core.EmguCV;
|
||||
@@ -21,7 +22,7 @@ namespace UVtools.Core.EmguCV;
|
||||
/// <summary>
|
||||
/// A contour cache for OpenCV
|
||||
/// </summary>
|
||||
public class EmguContour : IReadOnlyCollection<Point>, IDisposable
|
||||
public class EmguContour : IReadOnlyCollection<Point>, IDisposable, IComparable<EmguContour>, IComparer<EmguContour>
|
||||
{
|
||||
#region Constants
|
||||
|
||||
@@ -257,4 +258,43 @@ public class EmguContour : IReadOnlyCollection<Point>, IDisposable
|
||||
_moments?.Dispose();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Equality
|
||||
|
||||
protected bool Equals(EmguContour other)
|
||||
{
|
||||
if (Count != other.Count) return false;
|
||||
return _points.ToArray().SequenceEqual(other.ToArray());
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((EmguContour) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _points.GetHashCode();
|
||||
}
|
||||
|
||||
public int CompareTo(EmguContour? other)
|
||||
{
|
||||
if (ReferenceEquals(this, other)) return 0;
|
||||
if (ReferenceEquals(null, other)) return 1;
|
||||
return _area.CompareTo(other._area);
|
||||
}
|
||||
|
||||
public int Compare(EmguContour? x, EmguContour? y)
|
||||
{
|
||||
if (ReferenceEquals(x, y)) return 0;
|
||||
if (ReferenceEquals(null, y)) return 1;
|
||||
if (ReferenceEquals(null, x)) return -1;
|
||||
return x._area.CompareTo(y._area);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Emgu.CV.CvEnum;
|
||||
using UVtools.Core.Extensions;
|
||||
|
||||
namespace UVtools.Core.EmguCV;
|
||||
@@ -38,6 +39,7 @@ public class EmguContours : IReadOnlyList<EmguContour>, IDisposable
|
||||
|
||||
public int Count => _contours.Length;
|
||||
|
||||
public readonly int[,] Hierarchy = new int[0,0];
|
||||
|
||||
|
||||
public EmguContour this[int index] => _contours[index];
|
||||
@@ -51,6 +53,21 @@ public class EmguContours : IReadOnlyList<EmguContour>, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public EmguContours(VectorOfVectorOfPoint vectorOfPointsOfPoints, int[,] hierarchy) : this(vectorOfPointsOfPoints)
|
||||
{
|
||||
Hierarchy = hierarchy;
|
||||
}
|
||||
|
||||
public EmguContours(IInputOutputArray mat, RetrType mode = RetrType.List, ChainApproxMethod method = ChainApproxMethod.ChainApproxSimple, Point offset = default)
|
||||
{
|
||||
using var contours = mat.FindContours(out Hierarchy, mode, method, offset);
|
||||
_contours = new EmguContour[contours.Size];
|
||||
for (int i = 0; i < _contours.Length; i++)
|
||||
{
|
||||
_contours[i] = new EmguContour(contours[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public (int Index, EmguContour Contour, double Distance)[][] CalculateCentroidDistances(bool includeOwn = false, bool sortByDistance = true)
|
||||
{
|
||||
var items = new (int Index, EmguContour Contour, double Distance)[Count][];
|
||||
@@ -258,6 +275,24 @@ public class EmguContours : IReadOnlyList<EmguContour>, IDisposable
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the largest contour area from a contour list
|
||||
/// </summary>
|
||||
/// <param name="contours">Contour list</param>
|
||||
/// <returns></returns>
|
||||
public static double GetLargestContourArea(VectorOfVectorOfPoint contours)
|
||||
{
|
||||
var vectorSize = contours.Size;
|
||||
if (vectorSize == 0) return 0;
|
||||
|
||||
double result = 0;
|
||||
for (var i = 0; i < vectorSize; i++)
|
||||
{
|
||||
result = Math.Max(result, CvInvoke.ContourArea(contours[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets contours real area for a group of contours
|
||||
/// </summary>
|
||||
@@ -305,8 +340,8 @@ public class EmguContours : IReadOnlyList<EmguContour>, IDisposable
|
||||
using var contour2Mat = EmguExtensions.InitMat(totalRect.Size);
|
||||
|
||||
var inverseOffset = new Point(-totalRect.X, -totalRect.Y);
|
||||
CvInvoke.DrawContours(contour1Mat, contour1, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
|
||||
CvInvoke.DrawContours(contour2Mat, contour2, -1, EmguExtensions.WhiteColor, -1, Emgu.CV.CvEnum.LineType.EightConnected, null, int.MaxValue, inverseOffset);
|
||||
CvInvoke.DrawContours(contour1Mat, contour1, -1, EmguExtensions.WhiteColor, -1, LineType.EightConnected, null, int.MaxValue, inverseOffset);
|
||||
CvInvoke.DrawContours(contour2Mat, contour2, -1, EmguExtensions.WhiteColor, -1, LineType.EightConnected, null, int.MaxValue, inverseOffset);
|
||||
|
||||
CvInvoke.BitwiseAnd(contour1Mat, contour2Mat, contour1Mat);
|
||||
|
||||
|
||||
@@ -32,27 +32,33 @@ public enum LayerRangeSelection : byte
|
||||
Last
|
||||
}
|
||||
|
||||
public enum FlipDirection : byte
|
||||
/// <summary>
|
||||
/// Flip direction, same as <see cref="FlipType"/>, but add None which must be taken care/ignored on code
|
||||
/// </summary>
|
||||
public enum FlipDirection : sbyte
|
||||
{
|
||||
None,
|
||||
Horizontally,
|
||||
Vertically,
|
||||
Both,
|
||||
None = sbyte.MinValue,
|
||||
Horizontally = FlipType.Horizontal,
|
||||
Vertically = FlipType.Vertical,
|
||||
Both = FlipType.Both,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotate direction, same as <see cref="RotateFlags"/>, but add None which must be taken care/ignored on code
|
||||
/// </summary>
|
||||
public enum RotateDirection : sbyte
|
||||
{
|
||||
[Description("None")]
|
||||
None = -1,
|
||||
/// <summary>Rotate 90 degrees clockwise (0)</summary>
|
||||
[Description("Rotate 90º CW")]
|
||||
Rotate90Clockwise = 0,
|
||||
Rotate90Clockwise = RotateFlags.Rotate90Clockwise,
|
||||
/// <summary>Rotate 180 degrees clockwise (1)</summary>
|
||||
[Description("Rotate 180º")]
|
||||
Rotate180 = 1,
|
||||
Rotate180 = RotateFlags.Rotate180,
|
||||
/// <summary>Rotate 270 degrees clockwise (2)</summary>
|
||||
[Description("Rotate 90º CCW")]
|
||||
Rotate90CounterClockwise = 2,
|
||||
Rotate90CounterClockwise = RotateFlags.Rotate90CounterClockwise,
|
||||
}
|
||||
|
||||
public enum Anchor : byte
|
||||
@@ -118,6 +124,7 @@ public enum RemoveSourceFileAction : byte
|
||||
Prompt
|
||||
}
|
||||
|
||||
/*
|
||||
public static class Enumerations
|
||||
{
|
||||
public static FlipType ToOpenCVFlipType(FlipDirection flip)
|
||||
@@ -143,4 +150,4 @@ public static class Enumerations
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(rotate), rotate, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}*/
|
||||
@@ -440,6 +440,9 @@ public static class EmguExtensions
|
||||
{
|
||||
return CvInvoke.Imencode(".png", mat);
|
||||
}
|
||||
|
||||
public static Point GetCenterPoint(this Mat mat) => new(mat.Width / 2, mat.Height / 2);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Create methods
|
||||
@@ -457,20 +460,28 @@ public static class EmguExtensions
|
||||
return src.CreateMask(vec);
|
||||
}
|
||||
|
||||
public static Mat TrimByBounds(this Mat src)
|
||||
public static Mat CropByBounds(this Mat src, bool cloneInsteadRoi = false)
|
||||
{
|
||||
var rect = CvInvoke.BoundingRectangle(src);
|
||||
if (rect.Size == Size.Empty) return src.New();
|
||||
if (src.Size == rect.Size) return src.Clone();
|
||||
using var roi = src.Roi(rect);
|
||||
return roi.Clone();
|
||||
if (src.Size == rect.Size) return cloneInsteadRoi ? src.Roi(src.Size) : src.Clone();
|
||||
var roi = src.Roi(rect);
|
||||
|
||||
if (cloneInsteadRoi)
|
||||
{
|
||||
var clone = roi.Clone();
|
||||
roi.Dispose();
|
||||
return clone;
|
||||
}
|
||||
|
||||
return roi;
|
||||
}
|
||||
|
||||
public static void TrimByBounds(this Mat src, Mat dst)
|
||||
public static void CropByBounds(this Mat src, Mat dst)
|
||||
{
|
||||
var mat = src.TrimByBounds();
|
||||
CvInvoke.Swap(mat, dst);
|
||||
src.Dispose();
|
||||
using var mat = src.CropByBounds();
|
||||
dst.Create(mat.Rows, mat.Cols, mat.Depth, mat.NumberOfChannels);
|
||||
src.CopyTo(dst);
|
||||
}
|
||||
|
||||
|
||||
@@ -918,6 +929,18 @@ public static class EmguExtensions
|
||||
xTrans + (src.Width - src.Width * xScale) / 2.0,
|
||||
yTrans + (src.Height - src.Height * yScale) / 2.0, dstSize, interpolation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resize source mat proportional to a scale
|
||||
/// </summary>
|
||||
/// <param name="src"></param>
|
||||
/// <param name="scale"></param>
|
||||
/// <param name="interpolation"></param>
|
||||
public static void Resize(this Mat src, double scale, Inter interpolation = Inter.Linear)
|
||||
{
|
||||
if (scale == 1) return;
|
||||
CvInvoke.Resize(src, src, new Size((int) (src.Width * scale), (int) (src.Height * scale)), 0, 0, interpolation);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Draw Methods
|
||||
|
||||
@@ -19,7 +19,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Emgu.CV.CvEnum;
|
||||
using UVtools.Core.Converters;
|
||||
using UVtools.Core.EmguCV;
|
||||
using UVtools.Core.Extensions;
|
||||
using UVtools.Core.Layers;
|
||||
using UVtools.Core.Objects;
|
||||
@@ -720,16 +722,10 @@ public class CXDLPFile : FileFormat
|
||||
//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];
|
||||
outputFile.WriteBytes(BitExtensions.ToBytesBigEndian((uint)Math.Round(this[layerIndex].BoundingRectangleMillimeters.Area()*1000)));
|
||||
//preLayers[layerIndex] = new(layer.NonZeroPixelCount);
|
||||
}
|
||||
//Helpers.SerializeWriteFileStream(outputFile, preLayers);
|
||||
//Helpers.SerializeWriteFileStream(outputFile, pageBreak);
|
||||
|
||||
var layerAreaPosition = outputFile.Position;
|
||||
outputFile.Seek(4 * LayerCount, SeekOrigin.Current);
|
||||
outputFile.WriteBytes(pageBreak);
|
||||
|
||||
if (HeaderSettings.Version >= 3)
|
||||
@@ -737,7 +733,9 @@ public class CXDLPFile : FileFormat
|
||||
Helpers.SerializeWriteFileStream(outputFile, SlicerInfoV3Settings);
|
||||
}
|
||||
|
||||
var layerLargestContourArea = new uint[LayerCount];
|
||||
var layerBytes = new List<byte>[LayerCount];
|
||||
var pixelArea = PixelArea;
|
||||
foreach (var batch in BatchLayersIndexes())
|
||||
{
|
||||
Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex =>
|
||||
@@ -745,6 +743,10 @@ public class CXDLPFile : FileFormat
|
||||
var layer = this[layerIndex];
|
||||
using (var mat = layer.LayerMat)
|
||||
{
|
||||
using var contours = mat.FindContours(RetrType.External);
|
||||
layerLargestContourArea[layerIndex] = (uint)(EmguContours.GetLargestContourArea(contours) * pixelArea * 1000);
|
||||
//Debug.WriteLine($"Area: {contourArea} ({contourArea * PixelArea * 1000}) BR: {max.Bounds.Area()} ({max.Bounds.Area() * PixelArea * 1000})");
|
||||
|
||||
var span = mat.GetDataByteSpan();
|
||||
|
||||
layerBytes[layerIndex] = new();
|
||||
@@ -783,9 +785,7 @@ public class CXDLPFile : FileFormat
|
||||
}
|
||||
}
|
||||
|
||||
layerBytes[layerIndex].InsertRange(0, LayerDef.GetHeaderBytes(
|
||||
(uint)Math.Round(layer.BoundingRectangleMillimeters.Area() * 1000),
|
||||
lineCount));
|
||||
layerBytes[layerIndex].InsertRange(0, LayerDef.GetHeaderBytes(layerLargestContourArea[layerIndex], lineCount));
|
||||
layerBytes[layerIndex].AddRange(pageBreak);
|
||||
}
|
||||
|
||||
@@ -799,75 +799,14 @@ public class CXDLPFile : FileFormat
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*Parallel.For(0, LayerCount, CoreSettings.ParallelOptions,
|
||||
//new ParallelOptions{MaxDegreeOfParallelism = 1},
|
||||
layerIndex =>
|
||||
// Write layer largest contour area (mm^2 * 1000)
|
||||
outputFile.Seek(layerAreaPosition, SeekOrigin.Begin);
|
||||
foreach (var area in layerLargestContourArea)
|
||||
{
|
||||
if (progress.Token.IsCancellationRequested) return;
|
||||
//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;
|
||||
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));
|
||||
//layerLines.Add(new LayerLine((ushort)startY, (ushort)(y - 1), (ushort)x, lastColor));
|
||||
//Debug.WriteLine(layerLines[^1]);
|
||||
}
|
||||
|
||||
startY = color == 0 ? -1 : y;
|
||||
|
||||
lastColor = color;
|
||||
}
|
||||
|
||||
if (startY >= 0)
|
||||
{
|
||||
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());
|
||||
//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();
|
||||
});
|
||||
|
||||
progress.Reset(OperationProgress.StatusWritingFile, LayerCount);
|
||||
for (int layerIndex = 0; layerIndex < LayerCount; layerIndex++)
|
||||
{
|
||||
progress.Token.ThrowIfCancellationRequested();
|
||||
//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++;
|
||||
}*/
|
||||
outputFile.WriteUIntBigEndian(area);
|
||||
}
|
||||
|
||||
outputFile.Seek(0, SeekOrigin.End);
|
||||
Helpers.SerializeWriteFileStream(outputFile, FooterSettings);
|
||||
|
||||
progress.Reset("Calculating checksum");
|
||||
@@ -986,7 +925,7 @@ public class CXDLPFile : FileFormat
|
||||
|
||||
linesBytes[layerIndex] = null!;
|
||||
|
||||
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
|
||||
_layers[layerIndex] = new Layer((uint)layerIndex, mat, this);
|
||||
}
|
||||
|
||||
progress.LockAndIncrement();
|
||||
|
||||
@@ -1488,6 +1488,17 @@ public class Layer : BindableBase, IEquatable<Layer>, IEquatable<uint>
|
||||
_materialMilliliters = _materialMilliliters,
|
||||
};*/
|
||||
}
|
||||
|
||||
public Layer[] Clone(uint times)
|
||||
{
|
||||
var layers = new Layer[times];
|
||||
for (int i = 0; i < times; i++)
|
||||
{
|
||||
layers[i] = Clone();
|
||||
}
|
||||
|
||||
return layers;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Static Methods
|
||||
|
||||
@@ -170,12 +170,12 @@ public class MatCacheManager : IDisposable
|
||||
|
||||
if (Flip != FlipDirection.None)
|
||||
{
|
||||
CvInvoke.Flip(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], Enumerations.ToOpenCVFlipType(Flip));
|
||||
CvInvoke.Flip(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], (FlipType)Flip);
|
||||
}
|
||||
|
||||
if (Rotate != RotateDirection.None)
|
||||
{
|
||||
CvInvoke.Rotate(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], Enumerations.ToOpenCVRotateFlags(Rotate));
|
||||
CvInvoke.Rotate(MatCache[currentCacheIndex][0], MatCache[currentCacheIndex][0], (RotateFlags) Rotate);
|
||||
}
|
||||
|
||||
if (StripAntiAliasing)
|
||||
|
||||
@@ -671,7 +671,7 @@ public sealed class OperationCalibrateElephantFoot : Operation
|
||||
{
|
||||
var flip = SlicerFile.DisplayMirror;
|
||||
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
|
||||
}
|
||||
|
||||
// Preview
|
||||
|
||||
@@ -2238,7 +2238,7 @@ public sealed class OperationCalibrateExposureFinder : Operation
|
||||
{
|
||||
var flip = SlicerFile.DisplayMirror;
|
||||
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
||||
new OperationFlip(SlicerFile) { FlipDirection = Enumerations.ToOpenCVFlipType(flip) }.Execute(progress);
|
||||
new OperationFlip(SlicerFile) { FlipDirection = (FlipType)flip }.Execute(progress);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -467,7 +467,7 @@ public sealed class OperationCalibrateGrayscale : Operation
|
||||
{
|
||||
var flip = SlicerFile.DisplayMirror;
|
||||
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
|
||||
}
|
||||
|
||||
return layers;
|
||||
|
||||
@@ -387,7 +387,7 @@ public sealed class OperationCalibrateStressTower : Operation
|
||||
{
|
||||
var flip = SlicerFile.DisplayMirror;
|
||||
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
|
||||
}
|
||||
|
||||
return layers;
|
||||
|
||||
@@ -687,7 +687,7 @@ public sealed class OperationCalibrateTolerance : Operation
|
||||
{
|
||||
var flip = SlicerFile.DisplayMirror;
|
||||
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
|
||||
}
|
||||
|
||||
return layers;
|
||||
|
||||
@@ -722,7 +722,7 @@ public sealed class OperationCalibrateXYZAccuracy : Operation
|
||||
{
|
||||
var flip = SlicerFile.DisplayMirror;
|
||||
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, Enumerations.ToOpenCVFlipType(flip)));
|
||||
Parallel.ForEach(layers, CoreSettings.ParallelOptions, mat => CvInvoke.Flip(mat, mat, (FlipType)flip));
|
||||
}
|
||||
|
||||
return layers;
|
||||
|
||||
@@ -239,12 +239,12 @@ public sealed class OperationLayerExportGif : Operation
|
||||
|
||||
if (_flipDirection != FlipDirection.None)
|
||||
{
|
||||
CvInvoke.Flip(matRoi, matRoi, Enumerations.ToOpenCVFlipType(_flipDirection));
|
||||
CvInvoke.Flip(matRoi, matRoi, (FlipType)_flipDirection);
|
||||
}
|
||||
|
||||
if (_rotateDirection != RotateDirection.None)
|
||||
{
|
||||
CvInvoke.Rotate(matRoi, matRoi, Enumerations.ToOpenCVRotateFlags(_rotateDirection));
|
||||
CvInvoke.Rotate(matRoi, matRoi, (RotateFlags)_rotateDirection);
|
||||
}
|
||||
|
||||
if (_renderLayerCount)
|
||||
|
||||
@@ -175,12 +175,12 @@ public sealed class OperationLayerExportHeatMap : Operation
|
||||
|
||||
if (_flipDirection != FlipDirection.None)
|
||||
{
|
||||
CvInvoke.Flip(sumMat, sumMat, Enumerations.ToOpenCVFlipType(_flipDirection));
|
||||
CvInvoke.Flip(sumMat, sumMat, (FlipType)_flipDirection);
|
||||
}
|
||||
|
||||
if (_rotateDirection != RotateDirection.None)
|
||||
{
|
||||
CvInvoke.Rotate(sumMat, sumMat, Enumerations.ToOpenCVRotateFlags(_rotateDirection));
|
||||
CvInvoke.Rotate(sumMat, sumMat, (RotateFlags)_rotateDirection);
|
||||
}
|
||||
|
||||
if (_cropByRoi && HaveROI)
|
||||
|
||||
@@ -191,12 +191,12 @@ public sealed class OperationLayerExportImage : Operation
|
||||
|
||||
if (_flipDirection != FlipDirection.None)
|
||||
{
|
||||
CvInvoke.Flip(matRoi, matRoi, Enumerations.ToOpenCVFlipType(_flipDirection));
|
||||
CvInvoke.Flip(matRoi, matRoi, (FlipType)_flipDirection);
|
||||
}
|
||||
|
||||
if (_rotateDirection != RotateDirection.None)
|
||||
{
|
||||
CvInvoke.Rotate(matRoi, matRoi, Enumerations.ToOpenCVRotateFlags(_rotateDirection));
|
||||
CvInvoke.Rotate(matRoi, matRoi, (RotateFlags)_rotateDirection);
|
||||
}
|
||||
|
||||
var filename = SlicerFile[layerIndex].FormatFileName(_filename, _padLayerIndex ? SlicerFile.LayerDigits : byte.MinValue, IndexStartNumber.Zero, string.Empty);
|
||||
|
||||
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
* 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.Concurrent;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using UVtools.Core.Extensions;
|
||||
using UVtools.Core.FileFormats;
|
||||
using UVtools.Core.Layers;
|
||||
|
||||
namespace UVtools.Core.Operations;
|
||||
|
||||
[Serializable]
|
||||
public class OperationLithophane : Operation
|
||||
{
|
||||
#region Enum
|
||||
|
||||
public enum LithophaneBaseType : byte
|
||||
{
|
||||
None,
|
||||
Square,
|
||||
Model
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Members
|
||||
private decimal _layerHeight;
|
||||
private ushort _bottomLayerCount;
|
||||
private decimal _bottomExposure;
|
||||
private decimal _normalExposure;
|
||||
|
||||
private string? _filePath;
|
||||
private RotateDirection _rotate = RotateDirection.None;
|
||||
private bool _mirror;
|
||||
private bool _invertColor;
|
||||
private decimal _resizeFactor = 100;
|
||||
private bool _enhanceContrast;
|
||||
private sbyte _brightnessGain;
|
||||
private byte _gapClosingIterations = 4;
|
||||
private byte _removeNoiseIterations = 4;
|
||||
private byte _gaussianBlur;
|
||||
private byte _startThresholdRange = 1;
|
||||
private byte _endThresholdRange = byte.MaxValue;
|
||||
private decimal _baseThickness = 2;
|
||||
private LithophaneBaseType _baseType = LithophaneBaseType.Square;
|
||||
private ushort _baseMargin = 80;
|
||||
private decimal _lithophaneHeight = 3;
|
||||
private bool _oneLayerPerThreshold;
|
||||
private bool _enableAntiAliasing = true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
public override LayerRangeSelection StartLayerRangeSelection => LayerRangeSelection.None;
|
||||
public override string IconClass => "fas fa-portrait";
|
||||
public override string Title => "Lithophane";
|
||||
public override string Description =>
|
||||
"Generate lithophane from a picture.\n" +
|
||||
"Note: The current opened file will be overwritten with this lithophane, use a dummy or a not needed file.";
|
||||
|
||||
public override string ConfirmationText =>
|
||||
"generate the lithophane?";
|
||||
|
||||
public override string ProgressTitle =>
|
||||
"Generating lithophane";
|
||||
|
||||
public override string ProgressAction => "Threshold levels";
|
||||
|
||||
public override string? ValidateInternally()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (string.IsNullOrWhiteSpace(_filePath))
|
||||
{
|
||||
sb.AppendLine("The selected file is empty");
|
||||
}
|
||||
else if(!File.Exists(_filePath))
|
||||
{
|
||||
sb.AppendLine("The selected file does not exists");
|
||||
}
|
||||
|
||||
if (_startThresholdRange > _endThresholdRange)
|
||||
{
|
||||
sb.AppendLine("Start threshold can't be higher than end threshold");
|
||||
}
|
||||
|
||||
using var mat = GetSourceMat();
|
||||
if (mat is null)
|
||||
{
|
||||
sb.AppendLine("Unable to generate the mat from source file, is it a valid image file?");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SlicerFile.ResolutionX < mat.Width * _resizeFactor / 100 || SlicerFile.ResolutionY < mat.Height * _resizeFactor / 100)
|
||||
{
|
||||
//int differenceX = (int)SlicerFile.ResolutionX - mat.Width;
|
||||
//int differenceY = (int)SlicerFile.ResolutionY - mat.Height;
|
||||
var scaleX = SlicerFile.ResolutionX * 100f / mat.Width;
|
||||
var scaleY = SlicerFile.ResolutionY * 100f / mat.Height;
|
||||
var maxScale = Math.Min(scaleX, scaleY);
|
||||
|
||||
sb.AppendLine($"The printer resolution is not enough to accomodate the lithophane image, please scale down to a maximum of {maxScale:F0}%");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var result = $"{(FileExists ? $"{Path.GetFileName(_filePath)} ({Math.Abs(GetHashCode())})" : $"Lithophane {Math.Abs(GetHashCode())}")}";
|
||||
if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}";
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public OperationLithophane() { }
|
||||
|
||||
public OperationLithophane(FileFormat slicerFile) : base(slicerFile)
|
||||
{
|
||||
if (_layerHeight <= 0) _layerHeight = (decimal)SlicerFile.LayerHeight;
|
||||
if (_bottomExposure <= 0) _bottomExposure = (decimal)SlicerFile.BottomExposureTime;
|
||||
if (_normalExposure <= 0) _normalExposure = (decimal)SlicerFile.ExposureTime;
|
||||
if (_bottomLayerCount <= 0) _bottomLayerCount = SlicerFile.BottomLayerCount;
|
||||
_mirror = SlicerFile.DisplayMirror != FlipDirection.None;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
public decimal LayerHeight
|
||||
{
|
||||
get => _layerHeight;
|
||||
set => RaiseAndSetIfChanged(ref _layerHeight, Layer.RoundHeight(value));
|
||||
}
|
||||
|
||||
public ushort BottomLayerCount
|
||||
{
|
||||
get => _bottomLayerCount;
|
||||
set => RaiseAndSetIfChanged(ref _bottomLayerCount, value);
|
||||
}
|
||||
|
||||
public decimal BottomExposure
|
||||
{
|
||||
get => _bottomExposure;
|
||||
set => RaiseAndSetIfChanged(ref _bottomExposure, Math.Round(value, 2));
|
||||
}
|
||||
|
||||
public decimal NormalExposure
|
||||
{
|
||||
get => _normalExposure;
|
||||
set => RaiseAndSetIfChanged(ref _normalExposure, Math.Round(value, 2));
|
||||
}
|
||||
|
||||
public string? FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
set => RaiseAndSetIfChanged(ref _filePath, value);
|
||||
}
|
||||
|
||||
public bool FileExists => !string.IsNullOrWhiteSpace(_filePath) && File.Exists(_filePath);
|
||||
|
||||
public RotateDirection Rotate
|
||||
{
|
||||
get => _rotate;
|
||||
set => RaiseAndSetIfChanged(ref _rotate, value);
|
||||
}
|
||||
|
||||
public bool Mirror
|
||||
{
|
||||
get => _mirror;
|
||||
set => RaiseAndSetIfChanged(ref _mirror, value);
|
||||
}
|
||||
|
||||
public bool InvertColor
|
||||
{
|
||||
get => _invertColor;
|
||||
set => RaiseAndSetIfChanged(ref _invertColor, value);
|
||||
}
|
||||
|
||||
public decimal ResizeFactor
|
||||
{
|
||||
get => _resizeFactor;
|
||||
set => RaiseAndSetIfChanged(ref _resizeFactor, Math.Max(1, value));
|
||||
}
|
||||
|
||||
public bool EnhanceContrast
|
||||
{
|
||||
get => _enhanceContrast;
|
||||
set => RaiseAndSetIfChanged(ref _enhanceContrast, value);
|
||||
}
|
||||
|
||||
public sbyte BrightnessGain
|
||||
{
|
||||
get => _brightnessGain;
|
||||
set => RaiseAndSetIfChanged(ref _brightnessGain, value);
|
||||
}
|
||||
|
||||
public byte GapClosingIterations
|
||||
{
|
||||
get => _gapClosingIterations;
|
||||
set => RaiseAndSetIfChanged(ref _gapClosingIterations, value);
|
||||
}
|
||||
|
||||
public byte RemoveNoiseIterations
|
||||
{
|
||||
get => _removeNoiseIterations;
|
||||
set => RaiseAndSetIfChanged(ref _removeNoiseIterations, value);
|
||||
}
|
||||
|
||||
public byte GaussianBlur
|
||||
{
|
||||
get => _gaussianBlur;
|
||||
set => RaiseAndSetIfChanged(ref _gaussianBlur, value);
|
||||
}
|
||||
|
||||
public byte StartThresholdRange
|
||||
{
|
||||
get => _startThresholdRange;
|
||||
set => RaiseAndSetIfChanged(ref _startThresholdRange, Math.Max((byte)1, value));
|
||||
}
|
||||
|
||||
public byte EndThresholdRange
|
||||
{
|
||||
get => _endThresholdRange;
|
||||
set => RaiseAndSetIfChanged(ref _endThresholdRange, Math.Max((byte)1, value));
|
||||
}
|
||||
|
||||
public decimal BaseThickness
|
||||
{
|
||||
get => _baseThickness;
|
||||
set => RaiseAndSetIfChanged(ref _baseThickness, Math.Max(0, value));
|
||||
}
|
||||
|
||||
public LithophaneBaseType BaseType
|
||||
{
|
||||
get => _baseType;
|
||||
set => RaiseAndSetIfChanged(ref _baseType, value);
|
||||
}
|
||||
|
||||
public ushort BaseMargin
|
||||
{
|
||||
get => _baseMargin;
|
||||
set => RaiseAndSetIfChanged(ref _baseMargin, value);
|
||||
}
|
||||
|
||||
public decimal LithophaneHeight
|
||||
{
|
||||
get => _lithophaneHeight;
|
||||
set => RaiseAndSetIfChanged(ref _lithophaneHeight, Math.Max(0.01m, value));
|
||||
}
|
||||
|
||||
public bool OneLayerPerThreshold
|
||||
{
|
||||
get => _oneLayerPerThreshold;
|
||||
set => RaiseAndSetIfChanged(ref _oneLayerPerThreshold, value);
|
||||
}
|
||||
|
||||
public bool EnableAntiAliasing
|
||||
{
|
||||
get => _enableAntiAliasing;
|
||||
set => RaiseAndSetIfChanged(ref _enableAntiAliasing, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equality
|
||||
|
||||
protected bool Equals(OperationLithophane other)
|
||||
{
|
||||
return _layerHeight == other._layerHeight && _bottomLayerCount == other._bottomLayerCount && _bottomExposure == other._bottomExposure && _normalExposure == other._normalExposure && _filePath == other._filePath && _rotate == other._rotate && _mirror == other._mirror && _invertColor == other._invertColor && _enhanceContrast == other._enhanceContrast && _resizeFactor == other._resizeFactor && _brightnessGain == other._brightnessGain && _gapClosingIterations == other._gapClosingIterations && _removeNoiseIterations == other._removeNoiseIterations && _gaussianBlur == other._gaussianBlur && _startThresholdRange == other._startThresholdRange && _endThresholdRange == other._endThresholdRange && _baseThickness == other._baseThickness && _baseType == other._baseType && _baseMargin == other._baseMargin && _lithophaneHeight == other._lithophaneHeight && _oneLayerPerThreshold == other._oneLayerPerThreshold && _enableAntiAliasing == other._enableAntiAliasing;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((OperationLithophane) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = new HashCode();
|
||||
hashCode.Add(_layerHeight);
|
||||
hashCode.Add(_bottomLayerCount);
|
||||
hashCode.Add(_bottomExposure);
|
||||
hashCode.Add(_normalExposure);
|
||||
hashCode.Add(_filePath);
|
||||
hashCode.Add((int) _rotate);
|
||||
hashCode.Add(_mirror);
|
||||
hashCode.Add(_invertColor);
|
||||
hashCode.Add(_enhanceContrast);
|
||||
hashCode.Add(_resizeFactor);
|
||||
hashCode.Add(_brightnessGain);
|
||||
hashCode.Add(_gapClosingIterations);
|
||||
hashCode.Add(_removeNoiseIterations);
|
||||
hashCode.Add(_gaussianBlur);
|
||||
hashCode.Add(_startThresholdRange);
|
||||
hashCode.Add(_endThresholdRange);
|
||||
hashCode.Add(_baseThickness);
|
||||
hashCode.Add((int) _baseType);
|
||||
hashCode.Add(_baseMargin);
|
||||
hashCode.Add(_lithophaneHeight);
|
||||
hashCode.Add(_oneLayerPerThreshold);
|
||||
hashCode.Add(_enableAntiAliasing);
|
||||
return hashCode.ToHashCode();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
public Mat? GetSourceMat()
|
||||
{
|
||||
if (!FileExists) return null;
|
||||
try
|
||||
{
|
||||
var mat = CvInvoke.Imread(_filePath, ImreadModes.Grayscale);
|
||||
if (_invertColor) CvInvoke.BitwiseNot(mat, mat);
|
||||
mat = mat.CropByBounds();
|
||||
return mat.Size == Size.Empty ? null : mat;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Mat? GetTargetMat()
|
||||
{
|
||||
var mat = GetSourceMat();
|
||||
if (mat is null) return null;
|
||||
|
||||
if (_resizeFactor != 100) mat.Resize((double)_resizeFactor / 100.0);
|
||||
|
||||
if (_enhanceContrast) CvInvoke.EqualizeHist(mat, mat);
|
||||
if (_brightnessGain != 0)
|
||||
{
|
||||
using var mask = mat.NewSetTo(new MCvScalar(Math.Abs(_brightnessGain)));
|
||||
if(_brightnessGain > 0) CvInvoke.Add(mat, mask, mat, mat);
|
||||
else CvInvoke.Subtract(mat, mask, mat, mat);
|
||||
}
|
||||
|
||||
if (_gaussianBlur > 0)
|
||||
{
|
||||
var ksize = 1 + _gaussianBlur * 2;
|
||||
CvInvoke.GaussianBlur(mat, mat, new Size(ksize, ksize), 0);
|
||||
}
|
||||
|
||||
if (_removeNoiseIterations > 0) CvInvoke.MorphologyEx(mat, mat, MorphOp.Open, EmguExtensions.Kernel3x3Rectangle, new Point(-1, -1), _removeNoiseIterations, BorderType.Reflect101, default);
|
||||
if (_gapClosingIterations > 0) CvInvoke.MorphologyEx(mat, mat, MorphOp.Close, EmguExtensions.Kernel3x3Rectangle, new Point(-1, -1), _gapClosingIterations, BorderType.Reflect101, default);
|
||||
|
||||
if (_rotate != RotateDirection.None) CvInvoke.Rotate(mat, mat, (RotateFlags) _rotate);
|
||||
if (_mirror)
|
||||
{
|
||||
var flip = SlicerFile.DisplayMirror;
|
||||
if (flip == FlipDirection.None) flip = FlipDirection.Horizontally;
|
||||
CvInvoke.Flip(mat, mat, (FlipType)flip);
|
||||
}
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
protected override bool ExecuteInternally(OperationProgress progress)
|
||||
{
|
||||
using var mat = GetTargetMat();
|
||||
if (mat is null) return false;
|
||||
|
||||
var layersBag = new ConcurrentDictionary<byte, Layer>();
|
||||
progress.Reset("Threshold levels", byte.MaxValue);
|
||||
Parallel.For(_startThresholdRange, _endThresholdRange, CoreSettings.GetParallelOptions(progress), threshold =>
|
||||
{
|
||||
using var thresholdMat = new Mat();
|
||||
CvInvoke.Threshold(mat, thresholdMat, threshold, byte.MaxValue, ThresholdType.Binary);
|
||||
if (CvInvoke.CountNonZero(thresholdMat) == 0) return;
|
||||
|
||||
if (_enableAntiAliasing)
|
||||
{
|
||||
CvInvoke.GaussianBlur(thresholdMat, thresholdMat, new Size(3, 3), 0);
|
||||
}
|
||||
|
||||
using var layerMat = EmguExtensions.InitMat(SlicerFile.Resolution);
|
||||
thresholdMat.CopyToCenter(layerMat);
|
||||
layersBag.TryAdd((byte)threshold, new Layer(layerMat, SlicerFile));
|
||||
progress.LockAndIncrement();
|
||||
});
|
||||
|
||||
var thresholdLayers = layersBag.OrderBy(pair => pair.Key).Select(pair => pair.Value).ToArray();
|
||||
|
||||
if (!_oneLayerPerThreshold)
|
||||
{
|
||||
var layerIncrementF = thresholdLayers.Length * _layerHeight / Math.Max(_layerHeight, _lithophaneHeight);
|
||||
if (layerIncrementF >= 2)
|
||||
{
|
||||
var layerIncrement = (uint) layerIncrementF;
|
||||
var indexes = new int[(int)Math.Ceiling(thresholdLayers.Length / (float)layerIncrement)];
|
||||
var newLayers = new Layer[indexes.Length];
|
||||
var count = 0;
|
||||
for (int index = 0; index < thresholdLayers.Length; index++)
|
||||
{
|
||||
if (index % layerIncrement != 0) continue;
|
||||
newLayers[count] = thresholdLayers[index];
|
||||
indexes[count++] = index;
|
||||
|
||||
}
|
||||
|
||||
progress.ResetNameAndProcessed("Packed layers");
|
||||
Parallel.ForEach(indexes, CoreSettings.GetParallelOptions(progress), i =>
|
||||
{
|
||||
progress.LockAndIncrement();
|
||||
using var mat = thresholdLayers[i].LayerMat;
|
||||
for (int index = i+1; index < i + layerIncrement && index < thresholdLayers.Length; index++)
|
||||
{
|
||||
using var nextMat = thresholdLayers[index].LayerMat;
|
||||
CvInvoke.Max(mat, nextMat, mat);
|
||||
progress.LockAndIncrement();
|
||||
}
|
||||
|
||||
thresholdLayers[i].LayerMat = mat;
|
||||
});
|
||||
|
||||
|
||||
thresholdLayers = newLayers;
|
||||
}
|
||||
else if (layerIncrementF < 1)
|
||||
{
|
||||
var layerIncrement = (uint)(1/layerIncrementF);
|
||||
if (layerIncrement > 1)
|
||||
{
|
||||
progress.Reset("Packed layers");
|
||||
var newLayers = new Layer[thresholdLayers.Length * layerIncrement];
|
||||
for (int i = 0; i < thresholdLayers.Length; i++)
|
||||
{
|
||||
var layer = thresholdLayers[i];
|
||||
var newIndex = i * layerIncrement;
|
||||
newLayers[newIndex] = layer;
|
||||
for (int x = 1; x < layerIncrement; x++)
|
||||
{
|
||||
newLayers[++newIndex] = layer.Clone();
|
||||
}
|
||||
|
||||
}
|
||||
thresholdLayers = newLayers;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_baseType != LithophaneBaseType.None && _baseThickness > 0)
|
||||
{
|
||||
int baseLayerCount = (int)(_baseThickness / _layerHeight);
|
||||
var newLayers = new Layer[thresholdLayers.Length + baseLayerCount];
|
||||
using var baseMat = SlicerFile.CreateMat();
|
||||
|
||||
switch (_baseType)
|
||||
{
|
||||
case LithophaneBaseType.Square:
|
||||
{
|
||||
var rectangle = new Rectangle(
|
||||
baseMat.Width / 2 - mat.Width / 2 - _baseMargin / 2,
|
||||
baseMat.Height / 2 - mat.Height / 2 - _baseMargin / 2,
|
||||
mat.Width + _baseMargin,
|
||||
mat.Height + _baseMargin);
|
||||
CvInvoke.Rectangle(baseMat, rectangle, EmguExtensions.WhiteColor, -1, _enableAntiAliasing ? LineType.AntiAlias : LineType.EightConnected);
|
||||
break;
|
||||
}
|
||||
case LithophaneBaseType.Model:
|
||||
{
|
||||
using var dilatedMat = new Mat();
|
||||
CvInvoke.Threshold(mat, dilatedMat, 1, byte.MaxValue, ThresholdType.Binary);
|
||||
CvInvoke.Dilate(dilatedMat, dilatedMat, EmguExtensions.Kernel3x3Rectangle, new Point(-1, -1), _baseMargin, BorderType.Reflect101, default);
|
||||
dilatedMat.CopyToCenter(baseMat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var baseLayer = new Layer(baseMat, SlicerFile);
|
||||
newLayers[0] = baseLayer;
|
||||
for (int i = 1; i < baseLayerCount; i++)
|
||||
{
|
||||
newLayers[i] = baseLayer.Clone();
|
||||
}
|
||||
Array.Copy(thresholdLayers, 0, newLayers, baseLayerCount, thresholdLayers.Length);
|
||||
thresholdLayers = newLayers;
|
||||
}
|
||||
|
||||
SlicerFile.SuppressRebuildPropertiesWork(() =>
|
||||
{
|
||||
SlicerFile.LayerHeight = (float) _layerHeight;
|
||||
SlicerFile.BottomLayerCount = _bottomLayerCount;
|
||||
SlicerFile.BottomExposureTime = (float) _bottomExposure;
|
||||
SlicerFile.ExposureTime = (float) _normalExposure;
|
||||
|
||||
SlicerFile.Layers = thresholdLayers;
|
||||
}, true);
|
||||
|
||||
|
||||
using var bgrMat = new Mat();
|
||||
CvInvoke.CvtColor(mat, bgrMat, ColorConversion.Gray2Bgr);
|
||||
int baseLine = 0;
|
||||
var textSize = CvInvoke.GetTextSize("UVtools Lithophane", FontFace.HersheyDuplex, 2, 3, ref baseLine);
|
||||
CvInvoke.PutText(bgrMat, "UVtools Lithophane", new Point(bgrMat.Width / 2 - textSize.Width / 2, 60), FontFace.HersheyDuplex, 2, new MCvScalar(255, 27, 245), 3);
|
||||
SlicerFile.SetThumbnails(bgrMat);
|
||||
|
||||
return !progress.Token.IsCancellationRequested;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -866,7 +866,7 @@ public class OperationPixelArithmetic : Operation
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(_ignoreAreaOperator));
|
||||
}
|
||||
ApplyMask(originalRoi, target);
|
||||
ApplyMask(original, mat);
|
||||
|
||||
SlicerFile[layerIndex].LayerMat = mat;
|
||||
|
||||
|
||||
@@ -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>3.2.2</Version>
|
||||
<Version>3.3.0</Version>
|
||||
<Copyright>Copyright © 2020 PTRTECH</Copyright>
|
||||
<PackageIcon>UVtools.png</PackageIcon>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<?define ComponentRules="OneToOne"?>
|
||||
<!-- SourceDir instructs IsWiX the location of the directory that contains files for this merge module -->
|
||||
<?define SourceDir="..\publish\UVtools_win-x64_v3.2.2"?>
|
||||
<?define SourceDir="..\publish\UVtools_win-x64_v3.3.0"?>
|
||||
<Module Id="UVtools" Language="1033" Version="1.0.0.0">
|
||||
<Package Id="12aaa1cf-ff06-4a02-abd5-2ac01ac4f83b" Manufacturer="PTRTECH" InstallerVersion="200" Keywords="MSLA, DLP" Description="MSLA/DLP, file analysis, repair, conversion and manipulation" InstallScope="perMachine" Platform="x64" />
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:i="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="UVtools.WPF.Controls.Tools.ToolLithophaneControl">
|
||||
<Grid ColumnDefinitions="Auto,10,350">
|
||||
<StackPanel Spacing="10">
|
||||
<Grid
|
||||
RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto"
|
||||
ColumnDefinitions="Auto,10,190,20,Auto,10,190">
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Layer height:"/>
|
||||
<NumericUpDown Grid.Row="0" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_mm"
|
||||
Increment="0.01"
|
||||
Minimum="0.01"
|
||||
Maximum="0.30"
|
||||
FormatString="F3"
|
||||
Value="{Binding Operation.LayerHeight}"/>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Text="Bottom layer count:"/>
|
||||
<NumericUpDown Grid.Row="0" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_layers"
|
||||
Increment="1"
|
||||
Minimum="1"
|
||||
Maximum="1000"
|
||||
Value="{Binding Operation.BottomLayerCount}"/>
|
||||
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Bottom exposure:"/>
|
||||
<NumericUpDown Grid.Row="2" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_s"
|
||||
Increment="0.5"
|
||||
Minimum="0.1"
|
||||
Maximum="200"
|
||||
FormatString="F2"
|
||||
Value="{Binding Operation.BottomExposure}"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Text="Normal exposure:"/>
|
||||
<NumericUpDown Grid.Row="2" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_s"
|
||||
Increment="0.5"
|
||||
Minimum="0.1"
|
||||
Maximum="200"
|
||||
FormatString="F2"
|
||||
Value="{Binding Operation.NormalExposure}"/>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Image:"/>
|
||||
|
||||
<Grid Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="5"
|
||||
ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
IsReadOnly="True"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Operation.FilePath}"/>
|
||||
<Button Grid.Column="1"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{Binding SelectFile}"
|
||||
i:Attached.Icon="fas fa-file-import"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Rotate:"/>
|
||||
|
||||
<ComboBox Grid.Row="6" Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Items="{Binding Operation.Rotate, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
|
||||
SelectedItem="{Binding Operation.Rotate, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
|
||||
|
||||
<StackPanel Grid.Row="6" Grid.Column="4" Grid.ColumnSpan="3"
|
||||
Orientation="Horizontal" Spacing="20">
|
||||
<CheckBox VerticalAlignment="Center"
|
||||
IsChecked="{Binding Operation.Mirror}"
|
||||
Content="Mirror"/>
|
||||
|
||||
<CheckBox VerticalAlignment="Center"
|
||||
IsChecked="{Binding Operation.InvertColor}"
|
||||
Content="Invert color"/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Grid.Row="8" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Resize:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="8" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_percent"
|
||||
Minimum="1"
|
||||
Maximum="900"
|
||||
Increment="1"
|
||||
FormatString="F2"
|
||||
Value="{Binding Operation.ResizeFactor}"/>
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Brightness gain:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="10" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_sun"
|
||||
Minimum="-128"
|
||||
Maximum="127"
|
||||
Increment="1"
|
||||
Value="{Binding Operation.BrightnessGain}"/>
|
||||
|
||||
<CheckBox Grid.Row="10" Grid.Column="4" Grid.ColumnSpan="3"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding Operation.EnhanceContrast}"
|
||||
Content="Enhance contrast"/>
|
||||
|
||||
<TextBlock Grid.Row="12" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Remove noise:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="12" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Minimum="0"
|
||||
Maximum="255"
|
||||
Increment="1"
|
||||
Value="{Binding Operation.RemoveNoiseIterations}"/>
|
||||
|
||||
<TextBlock Grid.Row="12" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Text="Gap closing:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="12" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Minimum="0"
|
||||
Maximum="255"
|
||||
Increment="1"
|
||||
Value="{Binding Operation.GapClosingIterations}"/>
|
||||
|
||||
<TextBlock Grid.Row="14" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Gaussian blur:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="14" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Minimum="0"
|
||||
Maximum="255"
|
||||
Increment="1"
|
||||
Value="{Binding Operation.GaussianBlur}"/>
|
||||
|
||||
<TextBlock Grid.Row="16" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Start threshold:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="16" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_sun"
|
||||
Minimum="1"
|
||||
Maximum="255"
|
||||
Increment="1"
|
||||
Value="{Binding Operation.StartThresholdRange}"/>
|
||||
|
||||
<TextBlock Grid.Row="16" Grid.Column="4"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Text="End threshold:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="16" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_sun"
|
||||
Minimum="1"
|
||||
Maximum="255"
|
||||
Increment="1"
|
||||
Value="{Binding Operation.EndThresholdRange}"/>
|
||||
|
||||
<TextBlock Grid.Row="18" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Base type:"/>
|
||||
|
||||
<ComboBox Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
Items="{Binding Operation.BaseType, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}"
|
||||
SelectedItem="{Binding Operation.BaseType, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/>
|
||||
|
||||
<TextBlock Grid.Row="20" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="Base thickness:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="20" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_mm"
|
||||
Minimum="0"
|
||||
Maximum="255"
|
||||
Increment="1"
|
||||
FormatString="F2"
|
||||
Value="{Binding Operation.BaseThickness}"/>
|
||||
|
||||
<TextBlock Grid.Row="20" Grid.Column="4"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Text="Base margin:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="20" Grid.Column="6"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Minimum="0"
|
||||
Maximum="65535"
|
||||
Increment="1"
|
||||
Value="{Binding Operation.BaseMargin}"/>
|
||||
|
||||
<TextBlock Grid.Row="22" Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
IsEnabled="{Binding !Operation.OneLayerPerThreshold}"
|
||||
Text="Lithophane height:"/>
|
||||
|
||||
<NumericUpDown Grid.Row="22" Grid.Column="2"
|
||||
Classes="ValueLabel ValueLabel_px"
|
||||
Minimum="0"
|
||||
Maximum="10000"
|
||||
Increment="1"
|
||||
IsEnabled="{Binding !Operation.OneLayerPerThreshold}"
|
||||
Value="{Binding Operation.LithophaneHeight}"/>
|
||||
|
||||
<CheckBox Grid.Row="22" Grid.Column="4" Grid.ColumnSpan="3"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding Operation.OneLayerPerThreshold}"
|
||||
Content="One layer per threshold level"/>
|
||||
|
||||
<CheckBox Grid.Row="24" Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding Operation.EnableAntiAliasing}"
|
||||
Content="Enable Anti-Aliasing"/>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Vertical" Spacing="10">
|
||||
<Image Stretch="Uniform"
|
||||
Source="{Binding PreviewImage}"/>
|
||||
|
||||
<TextBlock Text="{Binding PreviewImage.Size, StringFormat=Size: {0}}" HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.Timers;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using UVtools.Core.Operations;
|
||||
using UVtools.WPF.Extensions;
|
||||
using UVtools.WPF.Windows;
|
||||
|
||||
namespace UVtools.WPF.Controls.Tools
|
||||
{
|
||||
public partial class ToolLithophaneControl : ToolControl
|
||||
{
|
||||
public OperationLithophane Operation => BaseOperation as OperationLithophane;
|
||||
|
||||
private readonly Timer _timer;
|
||||
|
||||
private Bitmap _previewImage;
|
||||
public Bitmap PreviewImage
|
||||
{
|
||||
get => _previewImage;
|
||||
set => RaiseAndSetIfChanged(ref _previewImage, value);
|
||||
}
|
||||
|
||||
public ToolLithophaneControl()
|
||||
{
|
||||
BaseOperation = new OperationLithophane(SlicerFile);
|
||||
if (!ValidateSpawn()) return;
|
||||
InitializeComponent();
|
||||
|
||||
_timer = new Timer(20)
|
||||
{
|
||||
AutoReset = false
|
||||
};
|
||||
_timer.Elapsed += (sender, e) => Dispatcher.UIThread.InvokeAsync(UpdatePreview);
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void Callback(ToolWindow.Callbacks callback)
|
||||
{
|
||||
if (App.SlicerFile is null) return;
|
||||
switch (callback)
|
||||
{
|
||||
case ToolWindow.Callbacks.Init:
|
||||
case ToolWindow.Callbacks.Loaded:
|
||||
Operation.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
_timer.Stop();
|
||||
_timer.Start();
|
||||
};
|
||||
_timer.Stop();
|
||||
_timer.Start();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePreview()
|
||||
{
|
||||
using var mat = Operation.GetTargetMat();
|
||||
_previewImage?.Dispose();
|
||||
PreviewImage = mat?.ToBitmap();
|
||||
}
|
||||
|
||||
public async void SelectFile()
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
AllowMultiple = false,
|
||||
Filters = Helpers.ImagesFileFilter,
|
||||
};
|
||||
|
||||
var files = await dialog.ShowAsync(ParentWindow);
|
||||
if (files is null || files.Length == 0) return;
|
||||
|
||||
Operation.FilePath = files[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,13 @@ using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using Emgu.CV.Util;
|
||||
using MessageBox.Avalonia.Enums;
|
||||
using UVtools.AvaloniaControls;
|
||||
using UVtools.Core;
|
||||
using UVtools.Core.EmguCV;
|
||||
using UVtools.Core.Extensions;
|
||||
using UVtools.Core.Layers;
|
||||
using UVtools.Core.Operations;
|
||||
using UVtools.Core.PixelEditor;
|
||||
using UVtools.WPF.Extensions;
|
||||
using UVtools.WPF.Structures;
|
||||
@@ -798,6 +800,18 @@ public partial class MainWindow
|
||||
ActualLayer = SliderMaximumValue;
|
||||
}
|
||||
|
||||
public void GoUpLayers(uint layers)
|
||||
{
|
||||
if (!IsFileLoaded) return;
|
||||
ActualLayer = Math.Min(SlicerFile.LastLayerIndex, ActualLayer + layers);
|
||||
}
|
||||
|
||||
public void GoDownLayers(uint layers)
|
||||
{
|
||||
if (!IsFileLoaded) return;
|
||||
ActualLayer = (uint)Math.Max(0, (int)ActualLayer - layers);
|
||||
}
|
||||
|
||||
public void GoMassLayer(string which)
|
||||
{
|
||||
if (!IsFileLoaded) return;
|
||||
@@ -1884,41 +1898,141 @@ public partial class MainWindow
|
||||
|
||||
private void LayerImageBox_KeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Up)
|
||||
switch (e.Key)
|
||||
{
|
||||
GoNextLayer();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key == Key.Down)
|
||||
{
|
||||
GoPreviousLayer();
|
||||
e.Handled = true;
|
||||
return;
|
||||
case Key.Up:
|
||||
GoNextLayer();
|
||||
e.Handled = true;
|
||||
return;
|
||||
case Key.Down:
|
||||
GoPreviousLayer();
|
||||
e.Handled = true;
|
||||
return;
|
||||
case Key.PageUp:
|
||||
GoUpLayers(10);
|
||||
e.Handled = true;
|
||||
return;
|
||||
case Key.PageDown:
|
||||
GoDownLayers(10);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void LayerImageBox_KeyUp(object? sender, KeyEventArgs e)
|
||||
private async void LayerImageBox_KeyUp(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
switch (e.Key)
|
||||
{
|
||||
if (e.KeyModifiers == KeyModifiers.Shift)
|
||||
case Key.Escape:
|
||||
{
|
||||
ClearROI();
|
||||
if (e.KeyModifiers == KeyModifiers.Shift)
|
||||
{
|
||||
ClearROI();
|
||||
}
|
||||
/*else if(e.KeyModifiers == KeyModifiers.Alt)
|
||||
{
|
||||
ClearMask();
|
||||
}*/
|
||||
else
|
||||
{
|
||||
ClearROIAndMask();
|
||||
}
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
/*else if(e.KeyModifiers == KeyModifiers.Alt)
|
||||
case Key.Insert:
|
||||
{
|
||||
ClearMask();
|
||||
}*/
|
||||
else
|
||||
{
|
||||
ClearROIAndMask();
|
||||
if (e.KeyModifiers == KeyModifiers.Control)
|
||||
{
|
||||
if (await this.MessageBoxQuestion($"Are you sure you want to clone the current layer {_actualLayer}?",
|
||||
"Clone the current layer?") != ButtonResult.Yes) return;
|
||||
|
||||
var operationLayerClone = new OperationLayerClone(SlicerFile);
|
||||
operationLayerClone.SelectCurrentLayer(_actualLayer);
|
||||
await RunOperation(operationLayerClone);
|
||||
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ROI == Rectangle.Empty && _maskPoints.Count == 0) return;
|
||||
var operation = new OperationPixelArithmetic(SlicerFile)
|
||||
{
|
||||
Operator = OperationPixelArithmetic.PixelArithmeticOperators.KeepRegion,
|
||||
ROI = ROI,
|
||||
MaskPoints = _maskPoints.ToArray()
|
||||
};
|
||||
|
||||
string layerRange;
|
||||
if (e.KeyModifiers == KeyModifiers.Alt)
|
||||
{
|
||||
operation.SelectAllLayers();
|
||||
layerRange = $"within all {SlicerFile.LayerCount} layers";
|
||||
}
|
||||
else
|
||||
{
|
||||
operation.SelectCurrentLayer(ActualLayer);
|
||||
layerRange = $"in the current {ActualLayer} layer";
|
||||
}
|
||||
|
||||
if (await this.MessageBoxQuestion($"Are you sure you want to keep only the selected region/mask(s) {layerRange}?",
|
||||
"Keep only selected region/mask(s)?") != ButtonResult.Yes) return;
|
||||
await RunOperation(operation);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
e.Handled = true;
|
||||
return;
|
||||
case Key.Delete:
|
||||
{
|
||||
if (e.KeyModifiers == KeyModifiers.Control)
|
||||
{
|
||||
if (await this.MessageBoxQuestion($"Are you sure you want to remove the current layer {_actualLayer}?",
|
||||
"Remove the current layer?") != ButtonResult.Yes) return;
|
||||
|
||||
var operationLayerRemove = new OperationLayerRemove(SlicerFile);
|
||||
operationLayerRemove.SelectCurrentLayer(_actualLayer);
|
||||
await RunOperation(operationLayerRemove);
|
||||
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ROI == Rectangle.Empty && _maskPoints.Count == 0) return;
|
||||
var operation = new OperationPixelArithmetic(SlicerFile)
|
||||
{
|
||||
Operator = OperationPixelArithmetic.PixelArithmeticOperators.DiscardRegion,
|
||||
ROI = ROI,
|
||||
MaskPoints = _maskPoints.ToArray()
|
||||
};
|
||||
|
||||
string layerRange;
|
||||
if (e.KeyModifiers == KeyModifiers.Alt)
|
||||
{
|
||||
operation.SelectAllLayers();
|
||||
layerRange = $"within all {SlicerFile.LayerCount} layers";
|
||||
}
|
||||
else
|
||||
{
|
||||
operation.SelectCurrentLayer(ActualLayer);
|
||||
layerRange = $"in the current {ActualLayer} layer";
|
||||
}
|
||||
|
||||
if (await this.MessageBoxQuestion($"Are you sure you want to discard the selected region/mask(s) {layerRange}?",
|
||||
"Discard selected region/mask(s)?") != ButtonResult.Yes) return;
|
||||
await RunOperation(operation);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
case Key.Home:
|
||||
GoFirstLayer();
|
||||
e.Handled = true;
|
||||
return;
|
||||
case Key.End:
|
||||
GoLastLayer();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ((e.KeyModifiers & KeyModifiers.Control) != 0)
|
||||
{
|
||||
if (e.Key is Key.LeftShift or Key.RightShift || (e.KeyModifiers & KeyModifiers.Shift) != 0) // Ctrl + Shift
|
||||
@@ -1968,6 +2082,7 @@ public partial class MainWindow
|
||||
if (e.Key == Key.B)
|
||||
{
|
||||
SelectModelVolumeRoi();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,10 @@ public partial class MainWindow : WindowEx
|
||||
Tag = new OperationTimelapse(),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Tag = new OperationLithophane(),
|
||||
},
|
||||
new()
|
||||
{
|
||||
Tag = new OperationScripting(),
|
||||
},
|
||||
|
||||
@@ -47,6 +47,7 @@ public class OperationProfiles //: IList<Operation>
|
||||
[XmlElement(typeof(OperationRaiseOnPrintFinish))]
|
||||
[XmlElement(typeof(OperationChangeResolution))]
|
||||
[XmlElement(typeof(OperationTimelapse))]
|
||||
[XmlElement(typeof(OperationLithophane))]
|
||||
[XmlElement(typeof(OperationScripting))]
|
||||
|
||||
[XmlElement(typeof(OperationLayerExportGif))]
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<Version>3.2.2</Version>
|
||||
<Version>3.3.0</Version>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<PackageIcon>UVtools.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
|
||||
Reference in New Issue
Block a user