diff options
26 files changed, 325 insertions, 110 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 714d4f4..47408b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 24/06/2022 - v3.5.1 + +- **PCB Exposure:** + - (Fix) Able to omit X or Y coordinate and use last known value in place + - (Fix) Reusing macros in apertures was causing to use the last aperture values for all previous apertures that share the same macro name + - (Fix) Implement the inch measurement (%MOIN*%) + - (Fix) Crash when redo the operation (Ctrl + Shift + Z) +- **UI - Issue list:** + - (Add) Context menu when right click issues to select an action + - (Add) Option to solidify suction cups when right click on the issue + - (Improvement) Better confirmation text when click on remove issue(s) with detailed list of actions + ## 19/06/2022 - v3.5.0 - **PCB Exposure:** diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9c7b302..0dd9480 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,17 +1,10 @@ - **PCB Exposure:** - - (Add) Able to select multiple files and create a layer per file or merge them into one layer - - (Add) Able to import files from a zip file - - (Improvement) Round pixel coordinates and line thickness - - (Improvement) Better position precision for primitives - - (Improvement) Disable the ok button if no files were selected - - (Change) Do not auto mirror based on printer lcd mirror type - - (Fix) Limit line thickness to 1px minimum - - (Fix) Allow leading zero omit from XY coordinates (#492) - - (Fix) Mirror option was shifting the board position -- (Add) Calibrate - Blooming effect: Generates test models with various strategies and increments to measure the blooming effect -- (Add) Setting: Issues - Default order by, changes the default order on the issues list (#482) -- (Improvement) CTBv4 and encrypted: Fetch `BottomWaitTimes` virtual property from first bottom layer that has at least 2 pixels (#483) -- (Fix) Linux: Enable desktop integration for AppImages (#490) -- (Fix) Extracting zip contents inside folders would cause a error and not extract those contents -- (Upgrade) AvaloniaUI from 0.10.14 to 0.10.15 [Fixes auto-size problems] + - (Fix) Able to omit X or Y coordinate and use last known value in place + - (Fix) Reusing macros in apertures was causing to use the last aperture values for all previous apertures that share the same macro name + - (Fix) Implement the inch measurement (%MOIN*%) + - (Fix) Crash when redo the operation (Ctrl + Shift + Z) +- **UI - Issue list:** + - (Add) Context menu when right click issues to select an action + - (Add) Option to solidify suction cups when right click on the issue + - (Improvement) Better confirmation text when click on remove issue(s) with detailed list of actions diff --git a/UVtools.Core/Gerber/Apertures/Aperture.cs b/UVtools.Core/Gerber/Apertures/Aperture.cs index 68cb3c6..39bff36 100644 --- a/UVtools.Core/Gerber/Apertures/Aperture.cs +++ b/UVtools.Core/Gerber/Apertures/Aperture.cs @@ -13,6 +13,7 @@ using System.Text.RegularExpressions; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; +using UVtools.Core.Extensions; namespace UVtools.Core.Gerber.Apertures; @@ -29,13 +30,24 @@ public abstract class Aperture /// </summary> public string Name { get; set; } = string.Empty; + public GerberDocument Document { get; set; } = null!; + #endregion protected Aperture() { } - protected Aperture(int index) { Index = index; } - protected Aperture(string name) { Name = name; } - protected Aperture(int index, string name) : this(index) { Name = name; } + protected Aperture(GerberDocument document, int index) + { + Document = document; + Index = index; + } + + protected Aperture(GerberDocument document, string name) + { + Document = document; + Name = name; + } + protected Aperture(GerberDocument document, int index, string name) : this(document, index) { Name = name; } public abstract void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected); @@ -47,13 +59,12 @@ public abstract class Aperture if (!int.TryParse(match.Groups[1].Value, out var index)) return null; //if (!char.TryParse(match.Groups[2].Value, out var type)) return null; - switch (match.Groups[2].Value) { case "C": { if (!double.TryParse(match.Groups[3].Value, out var diameter)) return null; - return new CircleAperture(index, diameter); + return new CircleAperture(document, index, diameter); } case "O": { @@ -62,7 +73,7 @@ public abstract class Aperture if (!float.TryParse(split[0], out var width)) return null; if (!float.TryParse(split[1], out var height)) return null; - return new EllipseAperture(index, width, height); + return new EllipseAperture(document, index, width, height); } case "R": { @@ -71,7 +82,7 @@ public abstract class Aperture if (!float.TryParse(split[0], out var width)) return null; if (!float.TryParse(split[1], out var height)) return null; - return new RectangleAperture(index, width, height); + return new RectangleAperture(document, index, width, height); } case "P": { @@ -80,11 +91,12 @@ public abstract class Aperture if (!double.TryParse(split[0], out var diameter)) return null; if (!ushort.TryParse(split[1], out var vertices)) return null; - return new PolygonAperture(index, diameter, vertices); + return new PolygonAperture(document, index, diameter, vertices); } default: // macro { if (!document.Macros.TryGetValue(match.Groups[2].Value, out var macro)) return null; + macro = macro.Clone(); var parseLine = line.TrimEnd('%', '*'); var commaIndex = parseLine.IndexOf(',')+1; if (commaIndex == 0) return null; @@ -92,10 +104,10 @@ public abstract class Aperture var args = new[] {"0"}.Concat(parseLine.Split('X', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).ToArray(); foreach (var primitive in macro) { - primitive.ParseExpressions(args); + primitive.ParseExpressions(document, args); } - return new MacroAperture(index, macro); + return new MacroAperture(document, index, macro); } } } diff --git a/UVtools.Core/Gerber/Apertures/CircleAperture.cs b/UVtools.Core/Gerber/Apertures/CircleAperture.cs index 2f00787..0071b0f 100644 --- a/UVtools.Core/Gerber/Apertures/CircleAperture.cs +++ b/UVtools.Core/Gerber/Apertures/CircleAperture.cs @@ -6,7 +6,6 @@ * of this license document, but changing it is not allowed. */ -using System; using System.Drawing; using Emgu.CV; using Emgu.CV.CvEnum; @@ -22,11 +21,11 @@ public class CircleAperture : Aperture #endregion #region Constructor - public CircleAperture() : base("Circle") { } + public CircleAperture(GerberDocument document) : base(document, "Circle") { } - public CircleAperture(int index, double diameter) : base(index, "Circle") + public CircleAperture(GerberDocument document, int index, double diameter) : base(document, index, "Circle") { - Diameter = diameter; + Diameter = document.GetMillimeters(diameter); } #endregion diff --git a/UVtools.Core/Gerber/Apertures/EllipseAperture.cs b/UVtools.Core/Gerber/Apertures/EllipseAperture.cs index 7ac6f2e..c2d967e 100644 --- a/UVtools.Core/Gerber/Apertures/EllipseAperture.cs +++ b/UVtools.Core/Gerber/Apertures/EllipseAperture.cs @@ -20,16 +20,16 @@ public class EllipseAperture : Aperture #endregion #region Constructor - public EllipseAperture() : base("Ellipse") { } + public EllipseAperture(GerberDocument document) : base(document, "Ellipse") { } - public EllipseAperture(int index, float width, float height) : base(index, "Ellipse") + public EllipseAperture(GerberDocument document, int index, float width, float height) : this(document, index, new SizeF(width, height)) { - Axes = new SizeF(width, height); + } - public EllipseAperture(int index, SizeF axes) : base(index, "Ellipse") + public EllipseAperture(GerberDocument document, int index, SizeF axes) : base(document, index, "Ellipse") { - Axes = axes; + Axes = document.GetMillimeters(axes); } #endregion diff --git a/UVtools.Core/Gerber/Apertures/MacroAperture.cs b/UVtools.Core/Gerber/Apertures/MacroAperture.cs index 1f4cea1..af403cb 100644 --- a/UVtools.Core/Gerber/Apertures/MacroAperture.cs +++ b/UVtools.Core/Gerber/Apertures/MacroAperture.cs @@ -21,9 +21,9 @@ public class MacroAperture : Aperture #endregion #region Constructor - public MacroAperture() : base("Macro") { } + public MacroAperture(GerberDocument document) : base(document, "Macro") { } - public MacroAperture(int index, Macro macro) : base(index, "Macro") + public MacroAperture(GerberDocument document, int index, Macro macro) : base(document, index, "Macro") { Macro = macro; } @@ -31,9 +31,10 @@ public class MacroAperture : Aperture public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { - foreach (var macro in Macro) + foreach (var primitive in Macro) { - macro.DrawFlashD3(mat, xyPpmm, at, color, lineType); + //if(primitive.Name == "Comment") continue; + primitive.DrawFlashD3(mat, xyPpmm, at, color, lineType); } } }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs index e10c1c2..0b8bb9a 100644 --- a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs +++ b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs @@ -23,11 +23,11 @@ public class PolygonAperture : Aperture #endregion #region Constructor - public PolygonAperture() : base("Polygon") { } + public PolygonAperture(GerberDocument document) : base(document, "Polygon") { } - public PolygonAperture(int index, double diameter, ushort vertices) : base(index, "Polygon") + public PolygonAperture(GerberDocument document, int index, double diameter, ushort vertices) : base(document, index, "Polygon") { - Diameter = diameter; + Diameter = document.GetMillimeters(diameter); Vertices = vertices; } #endregion diff --git a/UVtools.Core/Gerber/Apertures/RectangleAperture.cs b/UVtools.Core/Gerber/Apertures/RectangleAperture.cs index 5dbd5a2..3be7bc4 100644 --- a/UVtools.Core/Gerber/Apertures/RectangleAperture.cs +++ b/UVtools.Core/Gerber/Apertures/RectangleAperture.cs @@ -21,16 +21,14 @@ public class RectangleAperture : Aperture #endregion #region Constructor - public RectangleAperture() : base("Rectangle") { } + public RectangleAperture(GerberDocument document) : base(document, "Rectangle") { } - public RectangleAperture(int index, float width, float height) : base(index, "Rectangle") - { - Size = new SizeF(width, height); - } + public RectangleAperture(GerberDocument document, int index, float width, float height) : this(document, index, new SizeF(width, height)) + { } - public RectangleAperture(int index, SizeF size) : base(index, "Rectangle") + public RectangleAperture(GerberDocument document, int index, SizeF size) : base(document, index, "Rectangle") { - Size = size; + Size = document.GetMillimeters(size); } #endregion diff --git a/UVtools.Core/Gerber/GerberDocument.cs b/UVtools.Core/Gerber/GerberDocument.cs index e150e24..3bd3887 100644 --- a/UVtools.Core/Gerber/GerberDocument.cs +++ b/UVtools.Core/Gerber/GerberDocument.cs @@ -13,6 +13,7 @@ using System.IO; using System.Text.RegularExpressions; using Emgu.CV; using Emgu.CV.CvEnum; +using Emgu.CV.Structure; using Emgu.CV.Util; using UVtools.Core.Extensions; using UVtools.Core.Gerber.Apertures; @@ -166,6 +167,8 @@ public class GerberDocument using var vec = new VectorOfPoint(regionPoints.ToArray()); CvInvoke.FillPoly(mat, vec, document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected); } + //CvInvoke.Imshow("G37", mat); + //CvInvoke.WaitKey(); regionPoints.Clear(); continue; } @@ -228,9 +231,15 @@ public class GerberDocument var fraction = matchX.Groups[1].Value.Substring(matchX.Groups[1].Value.Length - document.CoordinateXFractionalDigits, document.CoordinateXFractionalDigits); double.TryParse($"{integers}.{fraction}", out nowX); } + + nowX = document.GetMillimeters(nowX); } } } + else + { + nowX = currentX; + } if (matchY.Success && matchY.Groups.Count >= 2) { @@ -248,9 +257,14 @@ public class GerberDocument var fraction = matchY.Groups[1].Value.Substring(matchY.Groups[1].Value.Length - document.CoordinateYFractionalDigits, document.CoordinateYFractionalDigits); double.TryParse($"{integers}.{fraction}", out nowY); } + nowY = document.GetMillimeters(nowY); } } } + else + { + nowY = currentY; + } if (insideRegion) { @@ -294,7 +308,7 @@ public class GerberDocument var fraction = matchI.Groups[1].Value.Substring(matchI.Groups[1].Value.Length - document.CoordinateXFractionalDigits, document.CoordinateXFractionalDigits); double.TryParse($"{integers}.{fraction}", out xOffset); } - + xOffset = document.GetMillimeters(xOffset); } } @@ -312,6 +326,7 @@ public class GerberDocument var fraction = matchJ.Groups[1].Value.Substring(matchJ.Groups[1].Value.Length - document.CoordinateYFractionalDigits, document.CoordinateYFractionalDigits); double.TryParse($"{integers}.{fraction}", out yOffset); } + yOffset = document.GetMillimeters(yOffset); } } @@ -354,6 +369,8 @@ public class GerberDocument { currentAperture.DrawFlashD3(mat, xyPpmm, new PointF((float) nowX, (float) nowY), document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected); + //CvInvoke.Imshow("G37", mat); + //CvInvoke.WaitKey(); } } @@ -366,6 +383,30 @@ public class GerberDocument return document; } + public float GetMillimeters(float size) + { + if (UnitType == GerberUnitType.Millimeter) return size; + return size * 25.4f; + } + + public double GetMillimeters(double size) + { + if (UnitType == GerberUnitType.Millimeter) return size; + return size * 25.4; + } + + public SizeF GetMillimeters(SizeF size) + { + if (UnitType == GerberUnitType.Millimeter) return size; + return new SizeF(size.Width * 25.4f, size.Height * 25.4f); + } + + public PointF GetMillimeters(PointF point) + { + if (UnitType == GerberUnitType.Millimeter) return point; + return new PointF(point.X * 25.4f, point.Y * 25.4f); + } + public static Point PositionMmToPx(PointF atMm, SizeF xyPpmm) => new((int)Math.Round(atMm.X * xyPpmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atMm.Y * xyPpmm.Height, MidpointRounding.AwayFromZero)); diff --git a/UVtools.Core/Gerber/Macro.cs b/UVtools.Core/Gerber/Macro.cs index 6d9a013..6a5d3c1 100644 --- a/UVtools.Core/Gerber/Macro.cs +++ b/UVtools.Core/Gerber/Macro.cs @@ -89,6 +89,17 @@ public class Macro : IReadOnlyList<Primitive> } } + public Macro Clone() + { + var macro = new Macro(Name); + foreach (var primitive in Primitives) + { + macro.Primitives.Add(primitive.Clone()); + } + + return macro; + } + public static Macro? Parse(string line) { diff --git a/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs b/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs index 5ac51fd..6e6d751 100644 --- a/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs @@ -6,7 +6,6 @@ * of this license document, but changing it is not allowed. */ -using System; using System.Data; using System.Drawing; using System.Text.RegularExpressions; @@ -104,7 +103,7 @@ public class CenterLinePrimitive : Primitive //CvInvoke.Rectangle(mat, rectangle, color, -1, lineType); } - public override void ParseExpressions(params string[] args) + public override void ParseExpressions(GerberDocument document, params string[] args) { string csharpExp, result; float num; @@ -126,6 +125,7 @@ public class CenterLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out var val)) Width = val; } + Width = document.GetMillimeters(Width); if (float.TryParse(HeightExpression, out num)) Height = num; else @@ -135,6 +135,7 @@ public class CenterLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out var val)) Height = val; } + Height = document.GetMillimeters(Height); if (float.TryParse(CenterXExpression, out num)) CenterX = num; else @@ -144,6 +145,7 @@ public class CenterLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) CenterX = num; } + CenterX = document.GetMillimeters(CenterX); if (float.TryParse(CenterYExpression, out num)) CenterY = num; else @@ -153,6 +155,7 @@ public class CenterLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) CenterY = num; } + CenterY = document.GetMillimeters(CenterY); if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; else diff --git a/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs b/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs index 601cf0f..5364e0d 100644 --- a/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs @@ -92,7 +92,7 @@ public class CirclePrimitive : Primitive GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()), color, -1, lineType); } - public override void ParseExpressions(params string[] args) + public override void ParseExpressions(GerberDocument document, params string[] args) { string csharpExp, result; float num; @@ -114,6 +114,7 @@ public class CirclePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) Diameter = num; } + Diameter = document.GetMillimeters(Diameter); if (float.TryParse(CenterXExpression, out num)) CenterX = num; else @@ -123,6 +124,7 @@ public class CirclePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) CenterX = num; } + CenterX = document.GetMillimeters(CenterX); if (float.TryParse(CenterYExpression, out num)) CenterY = num; else @@ -132,6 +134,7 @@ public class CirclePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) CenterY = num; } + CenterY = document.GetMillimeters(CenterY); if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; diff --git a/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs b/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs index 25a9b07..a909911 100644 --- a/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs @@ -50,7 +50,7 @@ public class CommentPrimitive : Primitive } - public override void ParseExpressions(params string[] args) + public override void ParseExpressions(GerberDocument document, params string[] args) { } }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs b/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs index 93a505a..80ecfaf 100644 --- a/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Data; using System.Drawing; +using System.Linq; using System.Text.RegularExpressions; using Emgu.CV; using Emgu.CV.CvEnum; @@ -94,7 +95,7 @@ public class OutlinePrimitive : Primitive CvInvoke.FillPoly(mat, vec, color, lineType); } - public override void ParseExpressions(params string[] args) + public override void ParseExpressions(GerberDocument document, params string[] args) { string csharpExp, result; float num; @@ -125,7 +126,7 @@ public class OutlinePrimitive : Primitive } else { - coordinates.Add(new PointF(x.Value, num)); + coordinates.Add(document.GetMillimeters(new PointF(x.Value, num))); x = null; } } @@ -143,4 +144,12 @@ public class OutlinePrimitive : Primitive IsParsed = true; } + + public override Primitive Clone() + { + var primitive = MemberwiseClone() as OutlinePrimitive; + primitive!.CoordinatesExpression = primitive.CoordinatesExpression.ToArray(); + primitive.Coordinates = primitive.Coordinates.ToArray(); + return primitive; + } }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs index 1657336..2bb7289 100644 --- a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs @@ -6,7 +6,6 @@ * of this license document, but changing it is not allowed. */ -using System; using System.Data; using System.Drawing; using System.Text.RegularExpressions; @@ -101,7 +100,7 @@ public class PolygonPrimitive : Primitive color, 0, -1, lineType); } - public override void ParseExpressions(params string[] args) + public override void ParseExpressions(GerberDocument document, params string[] args) { string csharpExp, result; float num; @@ -132,6 +131,7 @@ public class PolygonPrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) CenterX = num; } + CenterX = document.GetMillimeters(CenterX); if (float.TryParse(CenterYExpression, out num)) CenterY = num; else @@ -141,6 +141,7 @@ public class PolygonPrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) CenterY = num; } + CenterY = document.GetMillimeters(CenterY); if (float.TryParse(DiameterExpression, out num)) Diameter = num; else @@ -150,7 +151,7 @@ public class PolygonPrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) Diameter = num; } - + Diameter = document.GetMillimeters(Diameter); if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; else diff --git a/UVtools.Core/Gerber/Primitives/Primitive.cs b/UVtools.Core/Gerber/Primitives/Primitive.cs index 9658a24..0a7740f 100644 --- a/UVtools.Core/Gerber/Primitives/Primitive.cs +++ b/UVtools.Core/Gerber/Primitives/Primitive.cs @@ -6,6 +6,7 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Drawing; using Emgu.CV; using Emgu.CV.CvEnum; @@ -26,5 +27,7 @@ public abstract class Primitive public abstract void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected); - public abstract void ParseExpressions(params string[] args); + public abstract void ParseExpressions(GerberDocument document, params string[] args); + + public virtual Primitive Clone() => (Primitive)MemberwiseClone(); }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs b/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs index 94f712a..9916a1d 100644 --- a/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs @@ -6,7 +6,6 @@ * of this license document, but changing it is not allowed. */ -using System; using System.Data; using System.Drawing; using System.Text.RegularExpressions; @@ -112,7 +111,7 @@ public class VectorLinePrimitive : Primitive //CvInvoke.Rectangle(mat, rectangle, color, -1, lineType); } - public override void ParseExpressions(params string[] args) + public override void ParseExpressions(GerberDocument document, params string[] args) { string csharpExp, result; float num; @@ -134,6 +133,7 @@ public class VectorLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out var val)) LineWidth = val; } + LineWidth = document.GetMillimeters(LineWidth); if (float.TryParse(StartXExpression, out num)) StartX = num; else @@ -143,6 +143,7 @@ public class VectorLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) StartX = num; } + StartX = document.GetMillimeters(StartX); if (float.TryParse(EndXExpression, out num)) EndX = num; else @@ -152,6 +153,7 @@ public class VectorLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) EndX = num; } + EndX = document.GetMillimeters(EndX); if (float.TryParse(StartYExpression, out num)) StartY = num; else @@ -161,6 +163,7 @@ public class VectorLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) StartY = num; } + StartY = document.GetMillimeters(StartY); if (float.TryParse(EndYExpression, out num)) EndY = num; else @@ -170,7 +173,7 @@ public class VectorLinePrimitive : Primitive result = exp.Compute(csharpExp, null).ToString()!; if (float.TryParse(result, out num)) EndY = num; } - + EndY = document.GetMillimeters(EndY); if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; else diff --git a/UVtools.Core/Layers/MainIssue.cs b/UVtools.Core/Layers/MainIssue.cs index fbc9ec9..8b7cf7e 100644 --- a/UVtools.Core/Layers/MainIssue.cs +++ b/UVtools.Core/Layers/MainIssue.cs @@ -30,6 +30,15 @@ public class MainIssue : IReadOnlyList<Issue> //HoleSandwich, } + public bool IsIsland => Type == IssueType.Island; + public bool IsOverhang => Type == IssueType.Overhang; + public bool IsResinTrap => Type == IssueType.ResinTrap; + public bool IsSuctionCup => Type == IssueType.SuctionCup; + public bool IsTouchingBound => Type == IssueType.TouchingBound; + public bool IsPrintHeight => Type == IssueType.PrintHeight; + public bool IsEmptyLayer => Type == IssueType.EmptyLayer; + public bool IsDebug => Type == IssueType.Debug; + /// <summary> /// Gets the issue type associated /// </summary> diff --git a/UVtools.Core/Operations/OperationPCBExposure.cs b/UVtools.Core/Operations/OperationPCBExposure.cs index 7df3085..96fd82d 100644 --- a/UVtools.Core/Operations/OperationPCBExposure.cs +++ b/UVtools.Core/Operations/OperationPCBExposure.cs @@ -313,6 +313,12 @@ public class OperationPCBExposure : Operation if (mergeMat is not null) { using var croppedMat = mergeMat.CropByBounds(20); + /*if (_mirror) + { + var flip = SlicerFile.DisplayMirror; + if (flip == FlipDirection.None) flip = FlipDirection.Horizontally; + CvInvoke.Flip(croppedMat, croppedMat, (FlipType)flip); + }*/ using var bgrMat = new Mat(); CvInvoke.CvtColor(croppedMat, bgrMat, ColorConversion.Gray2Bgr); SlicerFile.SetThumbnails(bgrMat); diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs index ba2426e..c3e68e3 100644 --- a/UVtools.Core/Operations/OperationSolidify.cs +++ b/UVtools.Core/Operations/OperationSolidify.cs @@ -34,7 +34,7 @@ public sealed class OperationSolidify : Operation #region Overrides - public override string IconClass => "fa-solid fa-circle"; + public override string IconClass => "fa-solid fa-diamond"; public override string Title => "Solidify"; public override string Description => diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 1b93a05..eb96200 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>3.5.0</Version> + <Version>3.5.1</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 848eea0..03b3cc7 100644 --- a/UVtools.InstallerMM/UVtools.InstallerMM.wxs +++ b/UVtools.InstallerMM/UVtools.InstallerMM.wxs @@ -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.5.0"?> + <?define SourceDir="..\publish\UVtools_win-x64_v3.5.1"?> <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"> diff --git a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs index f729167..4bcebac 100644 --- a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs @@ -77,7 +77,7 @@ namespace UVtools.WPF.Controls.Tools }; _timer.Stop(); _timer.Start(); - ParentWindow.ButtonOkEnabled = Operation.FileCount > 0; + if(ParentWindow is not null) ParentWindow.ButtonOkEnabled = Operation.FileCount > 0; Operation.Files.CollectionChanged += (sender, e) => ParentWindow.ButtonOkEnabled = Operation.FileCount > 0; break; } diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index 6141baf..08435ff 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -115,20 +115,65 @@ public partial class MainWindow return retValue; }*/ - public async void OnClickIssueRemove() + public async void RemoveRepairIssues(IEnumerable<MainIssue> issues, bool promptConfirmation = true, bool suctionCupDrill = true) { - if (IssuesGrid.SelectedItems.Count == 0) return; + if (!issues.Any()) return; + + uint emptyLayers = 0; + uint islands = 0; + uint resinTraps = 0; + uint suctionCups = 0; + foreach (MainIssue mainIssue in issues) + { + switch (mainIssue.Type) + { + case MainIssue.IssueType.Island: + islands++; + break; + case MainIssue.IssueType.Overhang: + // No action + break; + case MainIssue.IssueType.ResinTrap: + resinTraps++; + break; + case MainIssue.IssueType.SuctionCup: + suctionCups++; + break; + case MainIssue.IssueType.TouchingBound: + // No action + break; + case MainIssue.IssueType.PrintHeight: + // No action + break; + case MainIssue.IssueType.EmptyLayer: + emptyLayers++; + break; + case MainIssue.IssueType.Debug: + // No action + break; + default: + throw new ArgumentOutOfRangeException(); + } + } - if (await this.MessageBoxQuestion($"Are you sure you want to remove all selected {IssuesGrid.SelectedItems.Count} issues?\n\n" + - "Warning: Removing an island can cause other issues to appear if there is material present in the layers above it.\n" + - "Always check previous and next layers before performing an island removal.", $"Remove {IssuesGrid.SelectedItems.Count} Issues?") != ButtonResult.Yes) return; + if (emptyLayers == 0 && islands == 0 && resinTraps == 0 && suctionCups == 0) return; + + if (promptConfirmation && await this.MessageBoxQuestion( + $"Are you sure you want to remove and/or repair all selected {issues.Count()} issues?\n\n" + + "If any, this option will:\n" + + (emptyLayers > 0 ? $"- Remove {emptyLayers} empty layer(s)\n" : string.Empty) + + (islands > 0 ? $"- Remove {emptyLayers} island(s)\n" : string.Empty) + + (resinTraps > 0 ? $"- Fill/solidify {resinTraps} resin trap(s)\n" : string.Empty) + + (suctionCups > 0 ? $"- Drill {suctionCups} suction cup(s) at it's center\n" : string.Empty) + + "\nWarning: Removing an island can cause other issues to appear if there is material present in the layers above it.\n" + + "Always check previous and next layers before performing an island removal.", $"Remove {issues.Count()} Issues?") != ButtonResult.Yes) return; var processParallelIssues = new Dictionary<uint, List<Issue>>(); var processSuctionCups = new List<MainIssue>(); var layersToRemove = new List<uint>(); - foreach (MainIssue mainIssue in IssuesGrid.SelectedItems) + foreach (MainIssue mainIssue in issues) { switch (mainIssue.Type) { @@ -148,6 +193,22 @@ public partial class MainWindow } continue; case MainIssue.IssueType.SuctionCup: + if (!suctionCupDrill) + { + foreach (var issue in mainIssue) + { + // Islands and resin traps + if (!processParallelIssues.TryGetValue(issue.LayerIndex, out var issueList)) + { + issueList = new List<Issue>(); + processParallelIssues.Add(issue.LayerIndex, issueList); + } + + issueList.Add(issue); + + } + continue; + } if(mainIssue.StartLayerIndex == 0) continue; processSuctionCups.Add(mainIssue); continue; @@ -192,7 +253,7 @@ public partial class MainWindow edited = true; } - else if (issue.Type == MainIssue.IssueType.ResinTrap) + else if (issue.Type == MainIssue.IssueType.ResinTrap || (issue.Type == MainIssue.IssueType.SuctionCup && !suctionCupDrill)) { var issueOfContours = (IssueOfContours)issue; using var contours = new VectorOfVectorOfPoint(issueOfContours.Contours); @@ -219,13 +280,13 @@ public partial class MainWindow OperationLayerRemove.RemoveLayers(SlicerFile, layersToRemove); } - issueRemoveList.AddRange(SlicerFile.IssueManager.DrillSuctionCupsForIssues(processSuctionCups, UserSettings.Instance.LayerRepair.SuctionCupsVentHole, Progress)); + if(suctionCupDrill) issueRemoveList.AddRange(SlicerFile.IssueManager.DrillSuctionCupsForIssues(processSuctionCups, UserSettings.Instance.LayerRepair.SuctionCupsVentHole, Progress)); } catch (Exception ex) { Dispatcher.UIThread.InvokeAsync(async () => - await this.MessageBoxError(ex.ToString(), "Removal failed")); + await this.MessageBoxError(ex.ToString(), "Removal/repair failed")); return false; } @@ -245,8 +306,14 @@ public partial class MainWindow // Update GUI - foreach (MainIssue issue in IssuesGrid.SelectedItems) + foreach (MainIssue issue in issues) { + if (issue.IsSuctionCup && !suctionCupDrill) + { + issueRemoveList.Add(issue); + continue; + } + if (issue.Type is not MainIssue.IssueType.Island and not MainIssue.IssueType.ResinTrap @@ -256,7 +323,7 @@ public partial class MainWindow issueRemoveList.Add(issue); - if (issue.Type == MainIssue.IssueType.Island) + if (issue.IsIsland) { var nextLayer = issue.StartLayerIndex + 1; if (nextLayer >= SlicerFile.LayerCount) continue; @@ -289,6 +356,41 @@ public partial class MainWindow CanSave = true; } + public void OnClickIssueRemove() + { + RemoveRepairIssues(IssuesGrid.SelectedItems.OfType<MainIssue>(), true); + } + + public void SelectedIssuesIslandRemove() + { + if (IssuesGrid.SelectedItem is null) return; + RemoveRepairIssues(IssuesGrid.SelectedItems.OfType<MainIssue>().Where(mainIssue => mainIssue.IsIsland), false); + } + + public void SelectedIssuesResinTrapSolidify() + { + if (IssuesGrid.SelectedItem is null) return; + RemoveRepairIssues(IssuesGrid.SelectedItems.OfType<MainIssue>().Where(mainIssue => mainIssue.IsResinTrap), false); + } + + public void SelectedIssuesSuctionCupDrill() + { + if (IssuesGrid.SelectedItem is null) return; + RemoveRepairIssues(IssuesGrid.SelectedItems.OfType<MainIssue>().Where(mainIssue => mainIssue.IsSuctionCup), false); + } + + public void SelectedIssuesSuctionCupSolidify() + { + if (IssuesGrid.SelectedItem is null) return; + RemoveRepairIssues(IssuesGrid.SelectedItems.OfType<MainIssue>().Where(mainIssue => mainIssue.IsSuctionCup), false, false); + } + + public void SelectedIssuesEmptyLayerRemove() + { + if (IssuesGrid.SelectedItem is null) return; + RemoveRepairIssues(IssuesGrid.SelectedItems.OfType<MainIssue>().Where(mainIssue => mainIssue.IsEmptyLayer), false); + } + public async void OnClickIssueIgnore() { if ((_globalModifiers & KeyModifiers.Alt) != 0) diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 7534f69..ed04fb6 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -236,7 +236,7 @@ <WrapPanel Orientation="Horizontal" VerticalAlignment="Center"> - <TextBlock Text="{Binding SlicerFile.LayerHeight, StringFormat=Layer height: \{0\}mm}"/> + <TextBlock Text="{Binding SlicerFile.LayerHeight, StringFormat=Layer height: {0}mm}"/> <TextBlock IsVisible="{Binding SlicerFile.CanUseBottomLayerCount}" Text=" | "/> <TextBlock IsVisible="{Binding SlicerFile.CanUseBottomLayerCount}" @@ -266,28 +266,28 @@ <TextBlock IsVisible="{Binding SlicerFile.PrintTimeHours}" Text=" | "/> <TextBlock IsVisible="{Binding SlicerFile.PrintTimeHours}" - Text="{Binding SlicerFile.PrintTimeString, StringFormat=Print time: \{0\}}"/> + Text="{Binding SlicerFile.PrintTimeString, StringFormat=Print time: {0}}"/> <TextBlock IsVisible="{Binding SlicerFile.MaterialMilliliters}" Text=" | "/> <TextBlock IsVisible="{Binding SlicerFile.MaterialMilliliters}" - Text="{Binding SlicerFile.MaterialMilliliters, StringFormat=Used material: \{0\}ml}"/> + Text="{Binding SlicerFile.MaterialMilliliters, StringFormat=Used material: {0}ml}"/> <TextBlock IsVisible="{Binding SlicerFile.MaterialCost}" Text=" | "/> - <TextBlock IsVisible="{Binding SlicerFile.MaterialCost}" Text="{Binding SlicerFile.MaterialCost, StringFormat=Material cost: \{0\}€}"/> + <TextBlock IsVisible="{Binding SlicerFile.MaterialCost}" Text="{Binding SlicerFile.MaterialCost, StringFormat=Material cost: {0}€}"/> <TextBlock IsVisible="{Binding SlicerFile.MaterialName, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Text=" | "/> <TextBlock IsVisible="{Binding SlicerFile.MaterialName, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" - Text="{Binding SlicerFile.MaterialName, StringFormat=Material: \{0\}}"/> + Text="{Binding SlicerFile.MaterialName, StringFormat=Material: {0}}"/> <TextBlock IsVisible="{Binding SlicerFile.MachineName, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" Text=" | "/> <TextBlock IsVisible="{Binding SlicerFile.MachineName, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" - Text="{Binding SlicerFile.MachineName, StringFormat=Machine: \{0\}}"/> + Text="{Binding SlicerFile.MachineName, StringFormat=Machine: {0}}"/> </WrapPanel> </Border> @@ -385,13 +385,13 @@ <TextBlock VerticalAlignment="Center" - Text="{Binding SlicerProperties.Count, StringFormat=Properties: \{0\}}"/> + Text="{Binding SlicerProperties.Count, StringFormat=Properties: {0}}"/> <TextBlock VerticalAlignment="Center" Text="|"/> <TextBlock VerticalAlignment="Center" - Text="{Binding SlicerFile.Configs.Length, StringFormat=Groups: \{0\}}"/> + Text="{Binding SlicerFile.Configs.Length, StringFormat=Groups: {0}}"/> </StackPanel> @@ -510,12 +510,12 @@ <Grid RowDefinitions="Auto,*"> <StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="5"> <TextBlock - Text="{Binding GCodeLines, StringFormat=Lines: \{0\}}" + Text="{Binding GCodeLines, StringFormat=Lines: {0}}" VerticalAlignment="Center"/> <TextBlock Text="|" VerticalAlignment="Center"/> - <TextBlock Text="{Binding #GCodeText.Text.Length, StringFormat=Chars: \{0\}}" + <TextBlock Text="{Binding #GCodeText.Text.Length, StringFormat=Chars: {0}}" VerticalAlignment="Center"/> </StackPanel> @@ -640,19 +640,17 @@ </StackPanel> - <StackPanel - Grid.Row="0" - Orientation="Horizontal" - Spacing="2" - HorizontalAlignment="Right" - VerticalAlignment="Center"> + <StackPanel Grid.Row="0" + Orientation="Horizontal" + Spacing="2" + HorizontalAlignment="Right" + VerticalAlignment="Center"> - <Button - IsEnabled="{Binding IsFileLoaded}" - ToolTip.Tip="Attempt to repair issues" - VerticalAlignment="Stretch" - i:Attached.Icon="fa-solid fa-toolbox" - Command="{Binding OnClickRepairIssues}"/> + <Button IsEnabled="{Binding IsFileLoaded}" + ToolTip.Tip="Attempt to repair issues" + VerticalAlignment="Stretch" + i:Attached.Icon="fa-solid fa-toolbox" + Command="{Binding OnClickRepairIssues}"/> <uc:ButtonWithIcon VerticalAlignment="Stretch" ToolTip.Tip="Compute Issues. @@ -736,7 +734,18 @@ IsReadOnly="True" ClipboardCopyMode="None" Items="{Binding IssuesGridItems}"> - <DataGrid.Columns> + <DataGrid.ContextMenu> + <ContextMenu IsVisible="{Binding #IssuesGrid.SelectedItem, Converter={x:Static ObjectConverters.IsNotNull}}"> + <MenuItem i:MenuItem.Icon="fa-solid fa-eye-slash" Command="{Binding OnClickIssueIgnore}" Header="Ignore issue(s)"/> + <Separator/> + <MenuItem i:MenuItem.Icon="fa-solid fa-trash-can" Command="{Binding SelectedIssuesIslandRemove}" IsVisible="{Binding #IssuesGrid.SelectedItem.IsIsland}" Header="Remove island(s)"/> + <MenuItem i:MenuItem.Icon="fa-solid fa-diamond" Command="{Binding SelectedIssuesResinTrapSolidify}" IsVisible="{Binding #IssuesGrid.SelectedItem.IsResinTrap}" Header="Fill/solidify resin trap(s)"/> + <MenuItem i:MenuItem.Icon="fa-solid fa-circle-dot" Command="{Binding SelectedIssuesSuctionCupDrill}" IsVisible="{Binding #IssuesGrid.SelectedItem.IsSuctionCup}" Header="Drill suction cup(s)"/> + <MenuItem i:MenuItem.Icon="fa-solid fa-diamond" Command="{Binding SelectedIssuesSuctionCupSolidify}" IsVisible="{Binding #IssuesGrid.SelectedItem.IsSuctionCup}" Header="Fill/solidify suction cup(s)"/> + <MenuItem i:MenuItem.Icon="fa-solid fa-trash-can" Command="{Binding SelectedIssuesEmptyLayerRemove}" IsVisible="{Binding #IssuesGrid.SelectedItem.IsEmptyLayer}" Header="Remove empty layer(s)"/> + </ContextMenu> + </DataGrid.ContextMenu> + <DataGrid.Columns> <DataGridTextColumn Header="Type" Binding="{Binding Type}" @@ -1003,7 +1012,7 @@ Grid.Row="10" Grid.Column="4" VerticalAlignment="Center" - Text="{Binding DrawingPixelDrawing.RemovePixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="{Binding DrawingPixelDrawing.RemovePixelBrightnessPercent, StringFormat={}{0:0}%}" /> <TextBlock Grid.Row="12" @@ -1022,7 +1031,7 @@ Grid.Row="12" Grid.Column="4" VerticalAlignment="Center" - Text="{Binding DrawingPixelDrawing.PixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="{Binding DrawingPixelDrawing.PixelBrightnessPercent, StringFormat={}{0:0}%}" /> <TextBlock Grid.Row="14" @@ -1187,7 +1196,7 @@ Grid.Row="16" Grid.Column="4" VerticalAlignment="Center" - Text="{Binding DrawingPixelText.RemovePixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="{Binding DrawingPixelText.RemovePixelBrightnessPercent, StringFormat={}{0:0}%}" /> <TextBlock Grid.Row="18" @@ -1205,7 +1214,7 @@ Grid.Row="18" Grid.Column="4" VerticalAlignment="Center" - Text="{Binding DrawingPixelText.PixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="{Binding DrawingPixelText.PixelBrightnessPercent, StringFormat={}{0:0}%}" /> <TextBlock @@ -1269,7 +1278,7 @@ Grid.Row="0" Grid.Column="4" VerticalAlignment="Center" - Text="{Binding DrawingPixelEraser.PixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="{Binding DrawingPixelEraser.PixelBrightnessPercent, StringFormat={}{0:0}%}" /> <TextBlock Grid.Row="2" @@ -1365,7 +1374,7 @@ Grid.Row="6" Grid.Column="4" VerticalAlignment="Center" - Text="{Binding DrawingPixelSupport.PixelBrightnessPercent, StringFormat=\{0:0\}%}" /> + Text="{Binding DrawingPixelSupport.PixelBrightnessPercent, StringFormat={}{0:0}%}" /> </Grid> </StackPanel> @@ -1434,7 +1443,7 @@ <uc:ButtonWithIcon IsEnabled="{Binding Drawings.Count}" Icon="fa-solid fa-check" Spacing="5" - Text="{Binding Drawings.Count, StringFormat=Apply \{0\} operations}" + Text="{Binding Drawings.Count, StringFormat=Apply {0} operations}" Command="{Binding DrawModifications}" CommandParameter="false"/> </StackPanel> @@ -2072,7 +2081,7 @@ <TextBlock ToolTip.Tip="Layer preview computation time." Margin="5,0,0,0" - VerticalAlignment="Center" Text="{Binding ShowLayerRenderMs, StringFormat=\{0\}ms}"/> + VerticalAlignment="Center" Text="{Binding ShowLayerRenderMs, StringFormat={}{0}ms}"/> </WrapPanel> </Grid> </Grid> @@ -2095,7 +2104,7 @@ Margin="10" Text="{Binding Progress.Title}"/> <TextBlock Grid.Row="1" - Margin="10,0,10,10" Text="{Binding Progress.ElapsedTimeStr, StringFormat=Elapsed Time: \{0\}}"/> + Margin="10,0,10,10" Text="{Binding Progress.ElapsedTimeStr, StringFormat=Elapsed Time: {0}}"/> <TextBlock Grid.Row="2" Margin="10,0,10,10" Text="{Binding Progress.Description}" HorizontalAlignment="Center"/> diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 8a10c97..e962877 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>3.5.0</Version> + <Version>3.5.1</Version> <Platforms>AnyCPU;x64</Platforms> <PackageIcon>UVtools.png</PackageIcon> <PackageReadmeFile>README.md</PackageReadmeFile> |