diff options
author | Tiago Conceição <Tiago_caza@hotmail.com> | 2022-07-29 20:10:48 +0300 |
---|---|---|
committer | Tiago Conceição <Tiago_caza@hotmail.com> | 2022-07-29 20:10:48 +0300 |
commit | 0805133048ae2661901dedbf72232ba5f503cfb6 (patch) | |
tree | b255aef81847e720332080344c514ee5855bcf6f | |
parent | 315ce3bc0dfc7a950138473f66ac83e0fa119f6c (diff) |
v3.5.6v3.5.6
- **Tools**
- **PCB Exposure:**
- (Add) Able to choose the size midpoint rounding method (#520)
- (Fix) Allow to flash alone D03 commands (#520)
- (Fix) Correct line thickness to have at least 1px error (#523)
- (Improvement) Layer arithmetic: Use ; to split and start a new arithmetic operation
- (Add) Cmd: Convert command now allow to pass 'auto' as target type to auto convert specific files, valid for SL1 files configured with FILEFORMAT_xxx (#522)
- (Add) GCode: Command to sync and wait for movement completion [Only enabled for cws format] (#514)
- (Add) VDT: Transition layer count
- (Upgrade) AvaloniaUI from 0.10.16 to 0.10.17
34 files changed, 1115 insertions, 284 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index e3e9a8c..35c6894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 29/07/2022 - v3.5.6 + +- **Tools** + - **PCB Exposure:** + - (Add) Able to choose the size midpoint rounding method (#520) + - (Fix) Allow to flash alone D03 commands (#520) + - (Fix) Correct line thickness to have at least 1px error (#523) + - (Improvement) Layer arithmetic: Use ; to split and start a new arithmetic operation +- (Add) Cmd: Convert command now allow to pass 'auto' as target type to auto convert specific files, valid for SL1 files configured with FILEFORMAT_xxx (#522) +- (Add) GCode: Command to sync and wait for movement completion [Only enabled for cws format] (#514) +- (Add) VDT: Transition layer count +- (Upgrade) AvaloniaUI from 0.10.16 to 0.10.17 + ## 18/07/2022 - v3.5.5 - **File formats:** diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 762eac8..9fe95e0 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,11 @@ -- **File formats:** - - (Add) `LayerImageType`: Gets the layer image data type used on this file format - - (Improvement) jxs, rgb.cws and xml.cws: Improve the layer image read/write performance by a significant amount - - (Fix) xml.cws: Wanhao printers need 32 bit png instead of 8 bit png (#514) +- **Tools** + - **PCB Exposure:** + - (Add) Able to choose the size midpoint rounding method (#520) + - (Fix) Allow to flash alone D03 commands (#520) + - (Fix) Correct line thickness to have at least 1px error (#523) + - (Improvement) Layer arithmetic: Use ; to split and start a new arithmetic operation +- (Add) Cmd: Convert command now allow to pass 'auto' as target type to auto convert specific files, valid for SL1 files configured with FILEFORMAT_xxx (#522) +- (Add) GCode: Command to sync and wait for movement completion [Only enabled for cws format] (#514) +- (Add) VDT: Transition layer count +- (Upgrade) AvaloniaUI from 0.10.16 to 0.10.17 diff --git a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj index b177036..a47c56e 100644 --- a/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj +++ b/UVtools.AvaloniaControls/UVtools.AvaloniaControls.csproj @@ -38,7 +38,7 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Avalonia" Version="0.10.16" /> + <PackageReference Include="Avalonia" Version="0.10.17" /> </ItemGroup> <ItemGroup> diff --git a/UVtools.Cmd/Symbols/ConvertCommand.cs b/UVtools.Cmd/Symbols/ConvertCommand.cs index 89a4fdb..66a46d2 100644 --- a/UVtools.Cmd/Symbols/ConvertCommand.cs +++ b/UVtools.Cmd/Symbols/ConvertCommand.cs @@ -7,6 +7,7 @@ */ using System; using System.CommandLine; +using System.Globalization; using System.IO; using System.Linq; using UVtools.Core.Extensions; @@ -21,7 +22,7 @@ internal static class ConvertCommand var command = new Command("convert", "Convert input file into a output file format by a known type or extension") { GlobalArguments.InputFileArgument, - new Argument<string>("target-type/ext", "Target format type or extension"), + new Argument<string>("target-type/ext", "Target format type or extension. Use 'auto' for SL1 files with specified FILEFORMAT_xxx"), GlobalArguments.OutputFileArgument, new Option<ushort>(new[] {"-v", "--version"}, "Sets the file format version"), @@ -30,6 +31,42 @@ internal static class ConvertCommand command.SetHandler((FileInfo inputFile, string targetTypeExt, FileInfo? outputFile, ushort version, bool noOverwrite) => { + if (string.Equals(targetTypeExt, "auto", StringComparison.InvariantCultureIgnoreCase)) + { + using var testFile = Program.OpenInputFile(inputFile, FileFormat.FileDecodeType.Partial); + string? convertFileExtension; + switch (testFile) + { + case SL1File sl1File: + convertFileExtension = sl1File.LookupCustomValue<string>(SL1File.Keyword_FileFormat, null); + break; + case VDTFile vdtFile: + if (string.IsNullOrWhiteSpace(vdtFile.ManifestFile.Machine.UVToolsConvertTo) || vdtFile.ManifestFile.Machine.UVToolsConvertTo == "None") + convertFileExtension = vdtFile.LookupCustomValue<string>(SL1File.Keyword_FileFormat, null); + else + convertFileExtension = vdtFile.ManifestFile.Machine.UVToolsConvertTo; + break; + default: + Program.WriteLineError($"The file '{testFile.Filename}' is not a valid candidate for auto conversion. Please specify the target format instead."); + return; + } + + if (string.IsNullOrWhiteSpace(convertFileExtension)) + { + Program.WriteLineError($"The file '{testFile.Filename}' does not specify a target format, unable to guess. Please specify the target format instead."); + return; + } + + convertFileExtension = convertFileExtension.ToLower(CultureInfo.InvariantCulture); + var fileExtension = FileFormat.FindExtension(convertFileExtension); + if (fileExtension is null) + { + Program.WriteLineError($"Unable to find a valid target type from '{convertFileExtension}' extension."); + } + targetTypeExt = fileExtension!.GetFileFormat()!.GetType().Name; + outputFile = new FileInfo(Path.Combine(testFile.DirectoryPath!, $"{testFile.FilenameNoExt}.{convertFileExtension}")); + } + var targetType = FileFormat.FindByType(targetTypeExt); if (targetType is null) { diff --git a/UVtools.Cmd/UVtools.Cmd.csproj b/UVtools.Cmd/UVtools.Cmd.csproj index edd17c2..448c116 100644 --- a/UVtools.Cmd/UVtools.Cmd.csproj +++ b/UVtools.Cmd/UVtools.Cmd.csproj @@ -5,7 +5,7 @@ <TargetFramework>net6.0</TargetFramework> <AssemblyName>UVtoolsCmd</AssemblyName> <ApplicationIcon>UVtools.ico</ApplicationIcon> - <Version>1.0.1</Version> + <Version>1.0.2</Version> <Authors>Tiago Conceição, sn4k3</Authors> <Company>PTRTECH</Company> <PackageLicenseFile>LICENSE</PackageLicenseFile> diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index 4b75aea..bb12f39 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -997,6 +997,49 @@ public static class EmguExtensions #region Draw Methods /// <summary> + /// Correct openCV thickness which always results larger than specified + /// </summary> + /// <param name="thickness">Thickness to correct</param> + /// <returns></returns> + public static int CorrectThickness(int thickness) + { + if (thickness < 3) return thickness; + return thickness - 1; + } + + public static void DrawLineAccurate(this Mat src, Point pt1, Point pt2, MCvScalar color, int thickness, LineType lineType = LineType.EightConnected) + { + /*var deltaX = pt2.X - pt1.X; + var deltaY = pt2.Y - pt1.Y; + var deg = Math.Atan2(deltaY, deltaX) * (180 / Math.PI); + src.DrawRotatedRectangle( + new Size(Math.Abs(deltaX), thickness), + new Point(pt1.X + deltaX / 2, pt1.Y + deltaY / 2), + color, (int)deg, -1, lineType);*/ + + if (thickness >= 3) + { + thickness--; + /*var lastNumber = thickness % 10; + switch (lastNumber) + { + case 1: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + thickness--; + break; + }*/ + } + + CvInvoke.Line(src, pt1, pt2, color, thickness, lineType); + } + + /// <summary> /// Draw a rotated square around a center point /// </summary> /// <param name="src"></param> diff --git a/UVtools.Core/FileFormats/CWSFile.cs b/UVtools.Core/FileFormats/CWSFile.cs index 808a8a3..91417d5 100644 --- a/UVtools.Core/FileFormats/CWSFile.cs +++ b/UVtools.Core/FileFormats/CWSFile.cs @@ -581,6 +581,7 @@ public class CWSFile : FileFormat }; GCode.CommandShowImageM6054.Set(";<Slice>", "{0}"); GCode.CommandWaitG4.Set(";<Delay>", "{0}"); + GCode.CommandSyncMovements.Enabled = true; } #endregion diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 27dce29..322cfd1 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -384,6 +384,7 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor //new CXDLPv1File(), // Creality Box v1 new CXDLPFile(), // Creality Box new LGSFile(), // LGS, LGS30 + //new OSFFile(), // OSF new FlashForgeSVGXFile(), // SVGX new GenericZIPFile(), // Generic zip files new VDAFile(), // VDA @@ -2792,11 +2793,11 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable<FileFor get => _materialMilliliters; set { - if (value <= 0) + if (value <= 0) // Recalculate { value = (float)Math.Round(this.Where(layer => layer is not null).Sum(layer => layer.MaterialMilliliters), 3); } - else + else // Set from value { value = (float)Math.Round(value, 3); } diff --git a/UVtools.Core/FileFormats/OSFFile.cs b/UVtools.Core/FileFormats/OSFFile.cs new file mode 100644 index 0000000..2fb3f7f --- /dev/null +++ b/UVtools.Core/FileFormats/OSFFile.cs @@ -0,0 +1,565 @@ +/* + * 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 BinarySerialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Threading.Tasks; +using UVtools.Core.Converters; +using UVtools.Core.Extensions; +using UVtools.Core.Layers; +using UVtools.Core.Objects; +using UVtools.Core.Operations; + +namespace UVtools.Core.FileFormats; + +public class OSFFile : FileFormat +{ + #region Sub Classes + + #region Header + + public class Header + { + [FieldOrder(0)] [FieldEndianness(Endianness.Big)] public uint HeaderLength { get; set; } = 350001; + [FieldOrder(1)] [FieldEndianness(Endianness.Big)] public ushort Version { get; set; } = 4; + [FieldOrder(2)] [FieldEndianness(Endianness.Big)] public byte ImageLog { get; set; } = 2; + + /// <summary> + /// 148 * 80 * 2 + /// </summary> + //[FieldOrder(3)] public Uint24BitBigEndian Preview1Length { get; set; } = new(23680); + [FieldOrder(3)] [FieldEndianness(Endianness.Big)] [FieldBitLength(24)] public uint Preview1Length { get; set; } = 23680; + + /// <summary> + /// RGB565 + /// </summary> + //[FieldOrder(4)] [FieldLength(nameof(Preview1Length.Value))] public byte[] Preview1Data { get; set; } + + /*/// <summary> + /// 300 * 140 * 2 + /// </summary> + [FieldOrder(5)][FieldBitLength(24)] public uint Preview2Length { get; set; } = 84000;*/ + + /*/// <summary> + /// RGB565 + /// </summary> + [FieldOrder(6)] [FieldLength(nameof(Preview2Length))] public byte[] Preview2Data { get; set; }*/ + + + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort ResolutionX { get; set; } + [FieldOrder(11)] [FieldEndianness(Endianness.Big)] public ushort ResolutionY { get; set; } + [FieldOrder(12)] [FieldEndianness(Endianness.Big)] public ushort PixelUmMagnified100Times { get; set; } + + /// <summary> + /// (0x00 not mirrored, 0x01 X-axis mirroring, 0x02 Y-axis mirroring, 0x03 XY-axis mirroring) + /// </summary> + [FieldOrder(13)] [FieldEndianness(Endianness.Big)] public byte Mirror { get; set; } + [FieldOrder(14)] [FieldEndianness(Endianness.Big)] public byte BottomLightPWM { get; set; } = DefaultBottomLightPWM; + [FieldOrder(15)] [FieldEndianness(Endianness.Big)] public byte LightPWM { get; set; } = DefaultLightPWM; + [FieldOrder(16)] [FieldEndianness(Endianness.Big)] public byte AntiAliasEnabled { get; set; } + [FieldOrder(17)] [FieldEndianness(Endianness.Big)] public byte DistortionEnabled { get; set; } + [FieldOrder(18)] [FieldEndianness(Endianness.Big)] public byte DelayedExposureActivationEnabled { get; set; } + [FieldOrder(19)] [FieldEndianness(Endianness.Big)] public uint LayerCount { get; set; } + [FieldOrder(20)] [FieldEndianness(Endianness.Big)] public ushort NumberParameterSets { get; set; } = 1; + [FieldOrder(21)] [FieldEndianness(Endianness.Big)] public uint LastLayerIndex { set; get; } + [FieldOrder(22)] [FieldEndianness(Endianness.Big)] public uint LayerHeightUmMagnified100Times { set; get; } + [FieldOrder(23)] [FieldEndianness(Endianness.Big)] public byte BottomLayerCount { set; get; } + [FieldOrder(24)] [FieldEndianness(Endianness.Big)] public uint ExposureTimeMagnified100Times { set; get; } + [FieldOrder(25)] [FieldEndianness(Endianness.Big)] public uint BottomExposureTimeMagnified100Times { set; get; } + [FieldOrder(26)] [FieldEndianness(Endianness.Big)] public uint SupportDelayTimeMagnified100Times { set; get; } + [FieldOrder(27)] [FieldEndianness(Endianness.Big)] public uint BottomSupportDelayTimeMagnified100Times { set; get; } + [FieldOrder(28)] [FieldEndianness(Endianness.Big)] public byte TransitionLayers { set; get; } + /// <summary> + /// (0x00 linear transition) + /// </summary> + [FieldOrder(29)] [FieldEndianness(Endianness.Big)] public byte TransitionType { set; get; } + /* + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint TransitionLayerIntervalTimeDifferenceMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint WaitTimeAfterCureMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint WaitTimeAfterLiftMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint WaitTimeBeforeCureMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint BottomLiftHeightSlowMagnified1000Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint BottomLiftHeightTotalMagnified1000Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint LiftHeightSlowMagnified1000Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint LiftHeightTotalMagnified1000Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint BottomRetractHeightTotalMagnified1000Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint RetractHeightSlowMagnified1000Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public uint RetractHeightTotalMagnified1000Times { set; get; } + + /// <summary> + /// (0x00: S-shaped acceleration, 0x01: T-shaped acceleration, Default Value: S-shaped acceleration, currently only supports S-shaped acceleration) + /// </summary> + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public byte AccelerationType { set; get; } + + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort BottomLiftSpeedStartMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort BottomLiftSpeedSlowMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort BottomLiftSpeedFastMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public byte BottomLiftAccelerationChange { set; get; } // 5 + + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort LiftSpeedStartMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort LiftSpeedSlowMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort LiftSpeedFastMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public byte LiftAccelerationChange { set; get; } // 5 + + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort BottomRetractSpeedStartMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort BottomRetractSpeedSlowMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort BottomRetractFastMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public byte BottomRetractAccelerationChange { set; get; } // 5 + + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort RetractSpeedStartMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort RetractSpeedSlowMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public ushort RetractFastMagnified100Times { set; get; } + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public byte RetractAccelerationChange { set; get; } // 5 + + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] [FieldCount(23)] public byte[] Reserved { set; get; } = new byte[23]; // 23 + [FieldOrder(10)] [FieldEndianness(Endianness.Big)] public byte ProtocolType { set; get; } // 0 + */ + + } + + #endregion + + #region LayerDef + + public class LayerDef + { + [Ignore] public LGSFile Parent { get; set; } = null!; + + [FieldOrder(0)] + public uint DataSize { get; set; } + + [FieldOrder(1)] + [FieldLength(nameof(DataSize))] + public byte[] EncodedRle { get; set; } = null!; + + public LayerDef() { } + + public LayerDef(LGSFile parent) + { + Parent = parent; + } + + public unsafe byte[] Encode(Mat mat) + { + List<byte> rawData = new(); + List<byte> chunk = new(); + + if (Parent.HeaderSettings.PrinterModel is 4000 or 4500) + { + CvInvoke.Rotate(mat, mat, RotateFlags.Rotate90Clockwise); + } + + var spanMat = mat.GetBytePointer(); + var imageLength = mat.GetLength(); + + uint span = 0; + byte lc = 0; + + void addSpan(){ + chunk.Clear(); + for (; span > 0; span >>= 4) { + chunk.Insert(0, (byte)((byte)(span & 0xf) | (lc & 0xf0))); + } + rawData.AddRange(chunk.ToArray()); + } + + for (int i = 0; i < imageLength; i++) + { + byte c = (byte) (spanMat[i] & 0xf0); + + if (c == lc) + { + span++; + } + else + { + addSpan(); + span = 1; + } + + lc = c; + } + + addSpan(); + EncodedRle = rawData.ToArray(); + DataSize = (uint) EncodedRle.Length; + + if (Parent.HeaderSettings.PrinterModel is 4000 or 4500) + { + CvInvoke.Rotate(mat, mat, RotateFlags.Rotate90CounterClockwise); + } + + return EncodedRle; + } + + public Mat Decode(bool consumeRle = true) + { + // lgs10/30 --------> + // lgs120/4k From Y bottom to top Y + var mat = EmguExtensions.InitMat(Parent.HeaderSettings.PrinterModel is 4000 or 4500 ? Parent.Resolution.Exchange() : Parent.Resolution); + //var matSpan = mat.GetBytePointer(); + var imageLength = mat.GetLength(); + + int pixelPos = 0; + + for (var i = 0; i < EncodedRle.Length; i++) + { + var b = EncodedRle[i]; + byte colorNibble = (byte)(b >> 4); + byte color = (byte)(colorNibble << 4 | colorNibble); + int repeat = b & 0xf; + + while (i + 1 < EncodedRle.Length && (EncodedRle[i + 1] >> 4) == colorNibble) + { + i++; + repeat = (repeat << 4) | (EncodedRle[i] & 0xf); + } + + if (pixelPos >= imageLength) + { + throw new FileLoadException($"Too much buffer, expected: {imageLength}, got: {pixelPos}"); + } + + mat.FillSpan(ref pixelPos, repeat, color); + + //if (repeat <= 0) continue; + /*while (repeat-- > 0) + { + matSpan[pixel++] = color; + }*/ + + } + + if (pixelPos != imageLength) + { + throw new FileLoadException($"Incomplete buffer, expected: {imageLength}, got: {pixelPos}"); + } + + if (consumeRle) + EncodedRle = null!; + + if (Parent.HeaderSettings.PrinterModel is 4000 or 4500) + { + CvInvoke.Rotate(mat, mat, RotateFlags.Rotate90CounterClockwise); + } + + return mat; + } + } + #endregion + + #endregion + + #region Properties + + public Header HeaderSettings { get; protected internal set; } = new(); + public override FileFormatType FileType => FileFormatType.Binary; + + public override FileExtension[] FileExtensions { get; } = { + new (typeof(OSFFile), "osf", "Vlare Open File Format"), + }; + + public override PrintParameterModifier[]? PrintParameterModifiers { get; } = + { + PrintParameterModifier.BottomLayerCount, + + PrintParameterModifier.BottomLightOffDelay, + PrintParameterModifier.LightOffDelay, + + PrintParameterModifier.BottomExposureTime, + PrintParameterModifier.ExposureTime, + + PrintParameterModifier.BottomLiftHeight, + PrintParameterModifier.BottomLiftSpeed, + PrintParameterModifier.LiftHeight, + PrintParameterModifier.LiftSpeed, + + }; + + public override Size[]? ThumbnailsOriginalSize { get; } = + { + new(148, 80), + new(300, 140), + new(208, 116), + new(404, 240), + }; + + public override uint ResolutionX + { + get => HeaderSettings.ResolutionX; + set + { + HeaderSettings.ResolutionX = (ushort) value; + RaisePropertyChanged(); + } + } + + public override uint ResolutionY + { + get => (uint)HeaderSettings.ResolutionY; + set + { + HeaderSettings.ResolutionY = (ushort)value; + RaisePropertyChanged(); + } + } + + + + public override FlipDirection DisplayMirror + { + get => HeaderSettings.Mirror switch + { + 1 => FlipDirection.Horizontally, + 2 => FlipDirection.Vertically, + 3 => FlipDirection.Both, + _ => FlipDirection.None + }; + set + { + HeaderSettings.Mirror = value switch + { + FlipDirection.None => 0, + FlipDirection.Horizontally => 1, + FlipDirection.Vertically => 2, + FlipDirection.Both => 3, + _ => throw new ArgumentOutOfRangeException(nameof(value), value, null) + }; + } + } + + public override float LayerHeight + { + get => Layer.RoundHeight(HeaderSettings.LayerHeightUmMagnified100Times / 100000f); + set + { + HeaderSettings.LayerHeightUmMagnified100Times = (ushort)(value * 100000); + RaisePropertyChanged(); + } + } + + public override uint LayerCount + { + get => base.LayerCount; + set + { + base.LayerCount = HeaderSettings.LayerCount = base.LayerCount; + HeaderSettings.LastLayerIndex = HeaderSettings.LayerCount - 1; + } + } + + public override ushort BottomLayerCount + { + get => HeaderSettings.BottomLayerCount; + set + { + HeaderSettings.BottomLayerCount = (byte)value; + base.BottomLayerCount = value; + } + } + + /*public override float BottomLightOffDelay + { + get => TimeConverter.MillisecondsToSeconds(HeaderSettings.BottomLightOffDelayMs); + set + { + HeaderSettings.BottomLightOffDelayMs = TimeConverter.SecondsToMilliseconds(value); + base.BottomLightOffDelay = value; + } + } + + public override float LightOffDelay + { + get => TimeConverter.MillisecondsToSeconds(HeaderSettings.LightOffDelayMs); + set + { + HeaderSettings.LightOffDelayMs = TimeConverter.SecondsToMilliseconds(value); + base.LightOffDelay = value; + } + } + + public override float BottomWaitTimeBeforeCure + { + get => base.BottomWaitTimeBeforeCure; + set + { + SetBottomLightOffDelay(value); + base.BottomWaitTimeBeforeCure = value; + } + } + + public override float WaitTimeBeforeCure + { + get => base.WaitTimeBeforeCure; + set + { + SetNormalLightOffDelay(value); + base.WaitTimeBeforeCure = value; + } + } + + public override float BottomExposureTime + { + get => TimeConverter.MillisecondsToSeconds(HeaderSettings.BottomExposureTimeMs); + set + { + HeaderSettings.BottomExposureTimeMs = TimeConverter.SecondsToMilliseconds(value); + base.BottomExposureTime = value; + } + } + + public override float ExposureTime + { + get => TimeConverter.MillisecondsToSeconds(HeaderSettings.ExposureTimeMs); + set + { + HeaderSettings.ExposureTimeMs = TimeConverter.SecondsToMilliseconds(value); + base.ExposureTime = value; + } + } + + public override float BottomLiftHeight + { + get => HeaderSettings.BottomLiftHeight; + set => base.BottomLiftHeight = HeaderSettings.BottomLiftHeight = value; + } + + public override float LiftHeight + { + get => HeaderSettings.LiftHeight; + set => base.LiftHeight = HeaderSettings.LiftHeight = value; + } + + public override float BottomLiftSpeed + { + get => HeaderSettings.BottomLiftSpeed; + set => base.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed = HeaderSettings.BottomLiftSpeed_ = value; + } + + public override float LiftSpeed + { + get => HeaderSettings.LiftSpeed; + set => base.LiftSpeed = HeaderSettings.LiftSpeed = HeaderSettings.LiftSpeed_ = value; + } + */ + public override float BottomRetractSpeed => RetractSpeed; + + public override float RetractSpeed => LiftSpeed; + + + public override object[] Configs => new object[] { HeaderSettings }; + + #endregion + + #region Constructors + public OSFFile() + { + } + #endregion + + #region Methods + + protected override void EncodeInternally(OperationProgress progress) + { + using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Create, FileAccess.Write); + outputFile.WriteSerialize(HeaderSettings); + outputFile.WriteBytes(EncodeImage(DATATYPE_RGB565_BE, Thumbnails[0]!)); + + + progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); + var layerData = new LayerDef[LayerCount]; + + foreach (var batch in BatchLayersIndexes()) + { + Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex => + { + /*using (var mat = this[layerIndex].LayerMat) + { + layerData[layerIndex] = new LayerDef(this); + layerData[layerIndex].Encode(mat); + }*/ + progress.LockAndIncrement(); + }); + + foreach (var layerIndex in batch) + { + progress.ThrowIfCancellationRequested(); + outputFile.WriteSerialize(layerData[layerIndex]); + layerData[layerIndex].EncodedRle = null!; // Free this + } + } + + + progress.ItemName = "Saving layers"; + progress.ProcessedItems = 0; + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + progress.ThrowIfCancellationRequested(); + outputFile.WriteSerialize(layerData[layerIndex]); + progress++; + } + + Debug.WriteLine("Encode Results:"); + Debug.WriteLine(HeaderSettings); + Debug.WriteLine("-End-"); + } + + protected override void DecodeInternally(OperationProgress progress) + { + using var inputFile = new FileStream(FileFullPath!, FileMode.Open, FileAccess.Read); + HeaderSettings = Helpers.Deserialize<Header>(inputFile); + + //var previewSize = HeaderSettings.PreviewSizeX * HeaderSettings.PreviewSizeY * 2; + //var previewData = inputFile.ReadBytes(previewSize); + //Thumbnails[0] = DecodeImage(DATATYPE_RGB565_BE, previewData, HeaderSettings.PreviewSizeX, HeaderSettings.PreviewSizeY); + + + Init(HeaderSettings.LayerCount, DecodeType == FileDecodeType.Partial); + var layerData = new LayerDef[LayerCount]; + + if (DecodeType == FileDecodeType.Full) + { + progress.Reset(OperationProgress.StatusDecodeLayers, LayerCount); + foreach (var batch in BatchLayersIndexes()) + { + foreach (var layerIndex in batch) + { + progress.ThrowIfCancellationRequested(); + + layerData[layerIndex] = Helpers.Deserialize<LayerDef>(inputFile); + } + + Parallel.ForEach(batch, CoreSettings.GetParallelOptions(progress), layerIndex => + { + using var mat = layerData[layerIndex].Decode(); + _layers[layerIndex] = new Layer((uint)layerIndex, mat, this); + + progress.LockAndIncrement(); + }); + } + } + + RebuildLayersProperties(); + } + + protected override void PartialSaveInternally(OperationProgress progress) + { + using var outputFile = new FileStream(TemporaryOutputFileFullPath, FileMode.Open, FileAccess.Write); + outputFile.Seek(0, SeekOrigin.Begin); + Helpers.SerializeWriteFileStream(outputFile, HeaderSettings); + } + + #endregion +}
\ No newline at end of file diff --git a/UVtools.Core/FileFormats/VDTFile.cs b/UVtools.Core/FileFormats/VDTFile.cs index bf82ad7..b82145b 100644 --- a/UVtools.Core/FileFormats/VDTFile.cs +++ b/UVtools.Core/FileFormats/VDTFile.cs @@ -14,7 +14,6 @@ using System.IO; using System.IO.Compression; using System.Text.Json; using System.Text.Json.Serialization; -using System.Xml; using UVtools.Core.Extensions; using UVtools.Core.Layers; using UVtools.Core.Operations; @@ -100,7 +99,8 @@ public class VDTFile : FileFormat public sealed class VDTPrint { [JsonPropertyName("layer_thickness")] public float LayerHeight { get; set; } - [JsonPropertyName("bottom_layers")] public ushort BottomLayers { get; set; } = DefaultBottomLayerCount; + [JsonPropertyName("bottom_layers")] public ushort BottomLayerCount { get; set; } = DefaultBottomLayerCount; + [JsonPropertyName("transition_layer")] public ushort TransitionLayerCount { get; set; } = DefaultTransitionLayerCount; [JsonPropertyName("bottom_light_off_delay")] public float BottomLightOffDelay { get; set; } [JsonPropertyName("light_off_delay")] public float LightOffDelay { get; set; } [JsonPropertyName("bottom_wait_time_before_cure")] public float BottomWaitTimeBeforeCure { get; set; } @@ -203,6 +203,7 @@ public class VDTFile : FileFormat public override PrintParameterModifier[]? PrintParameterModifiers { get; } = { PrintParameterModifier.BottomLayerCount, + PrintParameterModifier.TransitionLayerCount, PrintParameterModifier.BottomLightOffDelay, PrintParameterModifier.LightOffDelay, @@ -378,8 +379,16 @@ public class VDTFile : FileFormat public override ushort BottomLayerCount { - get => ManifestFile.Print.BottomLayers; - set => base.BottomLayerCount = ManifestFile.Print.BottomLayers = value; + get => ManifestFile.Print.BottomLayerCount; + set => base.BottomLayerCount = ManifestFile.Print.BottomLayerCount = value; + } + + public override TransitionLayerTypes TransitionLayerType => TransitionLayerTypes.Software; + + public override ushort TransitionLayerCount + { + get => ManifestFile.Print.TransitionLayerCount; + set => base.TransitionLayerCount = ManifestFile.Print.TransitionLayerCount = (ushort)Math.Min(value, MaximumPossibleTransitionLayerCount); } public override float BottomLightOffDelay diff --git a/UVtools.Core/GCode/GCodeBuilder.cs b/UVtools.Core/GCode/GCodeBuilder.cs index 1cacb93..04b0fe6 100644 --- a/UVtools.Core/GCode/GCodeBuilder.cs +++ b/UVtools.Core/GCode/GCodeBuilder.cs @@ -40,7 +40,10 @@ public class GCodeBuilder : BindableBase public GCodeCommand CommandMoveG0 { get; } = new("G0", "Z{0} F{1}", "Move Z"); public GCodeCommand CommandMoveG1 { get; } = new("G1", "Z{0} F{1}", "Move Z"); + public GCodeCommand CommandSyncMovements { get; } = new("G4", "P0", "Sync movements", false); + public GCodeCommand CommandWaitG4 { get; } = new("G4", "P{0}", "Delay"); + public GCodeCommand CommandShowImageM6054 = new("M6054", "\"{0}\"", "Show image"); public GCodeCommand CommandClearImage = new(";<Slice> Blank"); // Clear image public GCodeCommand CommandTurnLEDM106 { get; } = new("M106", "S{0}", "Turn LED"); @@ -321,6 +324,7 @@ public class GCodeBuilder : BindableBase AppendLightOffM106(); AppendClearImage(); AppendHomeZG28(); + AppendSyncMovements(); AppendLineIfCanComment(EndStartGCodeComments); AppendLine(); } @@ -376,6 +380,8 @@ public class GCodeBuilder : BindableBase var time = ConvertFromSeconds(seconds); if (seconds > 0) AppendWaitG4($"0{time}", "Sync movement"); } + + AppendSyncMovements(); } } } @@ -403,6 +409,7 @@ public class GCodeBuilder : BindableBase } } + AppendSyncMovements(); AppendMotorsOff(); AppendLineIfCanComment(EndEndGCodeComments); } @@ -450,6 +457,11 @@ public class GCodeBuilder : BindableBase AppendLine(CommandHomeG28); } + public void AppendSyncMovements() + { + AppendLine(CommandSyncMovements); + } + public void AppendMoveGx(float z, float feedRate) { if(_layerMoveCommand == GCodeMoveCommands.G0) @@ -490,6 +502,8 @@ public class GCodeBuilder : BindableBase AppendWaitG4($"0{time}", "Sync movement"); } + AppendSyncMovements(); + if (waitAfterLift > 0) { AppendWaitG4(waitAfterLift, "Wait after lift"); @@ -510,6 +524,8 @@ public class GCodeBuilder : BindableBase AppendWaitG4($"0{time}", "Sync movement"); } + AppendSyncMovements(); + if (waitAfterRetract > 0) { AppendWaitG4(waitAfterRetract, "Wait after retract"); @@ -547,6 +563,8 @@ public class GCodeBuilder : BindableBase AppendWaitG4($"0{time}", "Sync movement"); } + AppendSyncMovements(); + if (waitAfterLift > 0) { AppendWaitG4(waitAfterLift, "Wait after lift"); @@ -567,6 +585,8 @@ public class GCodeBuilder : BindableBase AppendWaitG4($"0{time}", "Sync movement"); } + AppendSyncMovements(); + if (waitAfterRetract > 0) { AppendWaitG4(waitAfterRetract, "Wait after retract"); diff --git a/UVtools.Core/Gerber/Apertures/Aperture.cs b/UVtools.Core/Gerber/Apertures/Aperture.cs index 071ef7f..84aee08 100644 --- a/UVtools.Core/Gerber/Apertures/Aperture.cs +++ b/UVtools.Core/Gerber/Apertures/Aperture.cs @@ -49,7 +49,7 @@ public abstract class Aperture } 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); + public abstract void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected); public static Aperture? Parse(string line, GerberDocument document) { diff --git a/UVtools.Core/Gerber/Apertures/CircleAperture.cs b/UVtools.Core/Gerber/Apertures/CircleAperture.cs index 0071b0f..219def1 100644 --- a/UVtools.Core/Gerber/Apertures/CircleAperture.cs +++ b/UVtools.Core/Gerber/Apertures/CircleAperture.cs @@ -10,7 +10,6 @@ using System.Drawing; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; -using UVtools.Core.Extensions; namespace UVtools.Core.Gerber.Apertures; @@ -29,10 +28,10 @@ public class CircleAperture : Aperture } #endregion - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { - CvInvoke.Circle(mat, - GerberDocument.PositionMmToPx(at, xyPpmm), - GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()), color, -1, lineType); + CvInvoke.Circle(mat, + Document.PositionMmToPx(at), + Document.SizeMmToPx(Diameter / 2), color, -1, lineType); } }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/Apertures/EllipseAperture.cs b/UVtools.Core/Gerber/Apertures/EllipseAperture.cs index c2d967e..e65e43c 100644 --- a/UVtools.Core/Gerber/Apertures/EllipseAperture.cs +++ b/UVtools.Core/Gerber/Apertures/EllipseAperture.cs @@ -33,11 +33,11 @@ public class EllipseAperture : Aperture } #endregion - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { - CvInvoke.Ellipse(mat, - GerberDocument.PositionMmToPx(at, xyPpmm), - GerberDocument.SizeMmToPx(Axes.Width / 2, Axes.Height / 2, xyPpmm), + CvInvoke.Ellipse(mat, + Document.PositionMmToPx(at), + Document.SizeMmToPx(Axes.Width / 2, Axes.Height / 2), 0, 0, 360, color, -1, lineType); } }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/Apertures/MacroAperture.cs b/UVtools.Core/Gerber/Apertures/MacroAperture.cs index af403cb..55878d8 100644 --- a/UVtools.Core/Gerber/Apertures/MacroAperture.cs +++ b/UVtools.Core/Gerber/Apertures/MacroAperture.cs @@ -29,12 +29,12 @@ public class MacroAperture : Aperture } #endregion - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { foreach (var primitive in Macro) { //if(primitive.Name == "Comment") continue; - primitive.DrawFlashD3(mat, xyPpmm, at, color, lineType); + primitive.DrawFlashD3(mat, 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 0b8bb9a..1be1578 100644 --- a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs +++ b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs @@ -33,8 +33,8 @@ public class PolygonAperture : Aperture #endregion - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { - mat.DrawPolygon(Vertices, GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()), GerberDocument.PositionMmToPx(at, xyPpmm), color, 0, -1, lineType); + mat.DrawPolygon(Vertices, Document.SizeMmToPx(Diameter / 2), Document.PositionMmToPx(at), color, 0, -1, lineType); } }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/Apertures/RectangleAperture.cs b/UVtools.Core/Gerber/Apertures/RectangleAperture.cs index 3be7bc4..d49dde5 100644 --- a/UVtools.Core/Gerber/Apertures/RectangleAperture.cs +++ b/UVtools.Core/Gerber/Apertures/RectangleAperture.cs @@ -32,10 +32,9 @@ public class RectangleAperture : Aperture } #endregion - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, - LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { at = new PointF(Math.Max(0, at.X - Size.Width / 2), Math.Max(0, at.Y - Size.Height / 2)); - CvInvoke.Rectangle(mat, new Rectangle(GerberDocument.PositionMmToPx(at, xyPpmm), GerberDocument.SizeMmToPx(Size, xyPpmm)), color, -1, lineType); + CvInvoke.Rectangle(mat, new Rectangle(Document.PositionMmToPx(at), Document.SizeMmToPx(Size)), color, -1, lineType); } }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/Enumerations.cs b/UVtools.Core/Gerber/Enumerations.cs index d4ba962..01ce449 100644 --- a/UVtools.Core/Gerber/Enumerations.cs +++ b/UVtools.Core/Gerber/Enumerations.cs @@ -5,6 +5,10 @@ * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. */ + +using System; +using System.ComponentModel; + namespace UVtools.Core.Gerber; public enum GerberZerosSuppressionType : byte @@ -50,4 +54,22 @@ public enum GerberMoveType : byte Arc, // G03 ArcCounterClockwise +} + +public enum GerberMidpointRounding +{ + [Description("To even: The strategy of rounding to the nearest number, and when a number is halfway between two others, it's rounded toward the nearest even number.")] + ToEven = MidpointRounding.ToEven, + + [Description("Away from zero: The strategy of rounding to the nearest number, and when a number is halfway between two others, it's rounded toward the nearest number that's away from zero.")] + AwayFromZero = MidpointRounding.AwayFromZero, + + [Description("To zero: The strategy of directed rounding toward zero, with the result closest to and no greater in magnitude than the infinitely precise result.")] + ToZero = MidpointRounding.ToZero, + + [Description("To negative inifity: The strategy of downwards-directed rounding, with the result closest to and no greater than the infinitely precise result.")] + ToNegativeInfinity = MidpointRounding.ToNegativeInfinity, + + [Description("To positive inifity: The strategy of upwards-directed rounding, with the result closest to and no less than the infinitely precise result.")] + ToPositiveInfinity = MidpointRounding.ToPositiveInfinity }
\ No newline at end of file diff --git a/UVtools.Core/Gerber/GerberDocument.cs b/UVtools.Core/Gerber/GerberDocument.cs index 2e20db4..eca46fb 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; @@ -31,6 +32,7 @@ public class GerberDocument public GerberUnitType UnitType { get; set; } = GerberUnitType.Millimeter; public GerberPolarityType Polarity { get; set; } = GerberPolarityType.Dark; public GerberMoveType MoveType { get; set; } = GerberMoveType.Linear; + public GerberMidpointRounding SizeMidpointRounding { get; set; } = GerberMidpointRounding.AwayFromZero; public byte CoordinateXIntegers { get; set; } = 3; public byte CoordinateXFractionalDigits { get; set; } = 6; @@ -45,6 +47,10 @@ public class GerberDocument public Dictionary<int, Aperture> Apertures { get; } = new(); public Dictionary<string, Macro> Macros { get; } = new(); + public SizeF XYppmm { get; init; } + + public MCvScalar PolarityColor => Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor; + #endregion @@ -56,10 +62,14 @@ public class GerberDocument { } - public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, bool enableAntialiasing = false) + public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntialiasing = false) { using var file = new StreamReader(filePath); - var document = new GerberDocument(); + var document = new GerberDocument + { + SizeMidpointRounding = sizeMidpointRounding, + XYppmm = xyPpmm + }; int FSlength = "%FSLAX46Y46*%".Length; int MOlength = "%MOMM*%".Length; @@ -197,7 +207,7 @@ public class GerberDocument if (regionPoints.Count > 0) { 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.FillPoly(mat, vec, document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected); } //CvInvoke.Imshow("G37", mat); //CvInvoke.WaitKey(); @@ -207,7 +217,7 @@ public class GerberDocument if (line.StartsWith("%AM")) { - currentMacro = Macro.Parse(line); + currentMacro = Macro.Parse(document, line); if (currentMacro is null) continue; document.Macros.Add(currentMacro.Name, currentMacro); //document.Apertures.Add(aperture.Index, aperture); @@ -231,14 +241,15 @@ public class GerberDocument if (!int.TryParse(matchD.Groups[1].Value, out var d)) continue; - if(d < 10) continue; - if (!document.Apertures.TryGetValue(d, out currentAperture)) continue; - - continue; + if (d >= 10) + { + document.Apertures.TryGetValue(d, out currentAperture); + continue; + } } - if (line[0] == 'X' || line[0] == 'Y') + if (line[0] == 'X' || line[0] == 'Y' || line[0] == 'D') { var matchX = Regex.Match(line, @"X-?(\d+)?"); var matchY = Regex.Match(line, @"Y-?(\d+)?"); @@ -300,12 +311,12 @@ public class GerberDocument if (regionPoints.Count > 0) { 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.FillPoly(mat, vec, document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected); } regionPoints.Clear(); } - var pt = PositionMmToPx(nowX, nowY, xyPpmm); + var pt = document.PositionMmToPx(nowX, nowY); if (regionPoints.Count == 0 || (regionPoints.Count > 0 && regionPoints[^1] != pt)) regionPoints.Add(pt); } else if(currentAperture is not null) @@ -352,10 +363,10 @@ public class GerberDocument if (currentX == nowX && currentY == nowY) // Closed circle { CvInvoke.Ellipse(mat, - PositionMmToPx(nowX + xOffset, nowY + yOffset, xyPpmm), - SizeMmToPx(Math.Abs(xOffset), Math.Abs(xOffset), xyPpmm), - 0, 0, 360.0, document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, - SizeMmToPx(circleAperture.Diameter, xyPpmm.Max()), + document.PositionMmToPx(nowX + xOffset, nowY + yOffset), + document.SizeMmToPx(Math.Abs(xOffset), Math.Abs(xOffset)), + 0, 0, 360.0, document.PolarityColor, + document.SizeMmToPx(circleAperture.Diameter), enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected ); } @@ -365,7 +376,7 @@ public class GerberDocument throw new NotImplementedException("Partial arcs are not yet implemented (G03)\nTo be fixed in the future..."); /*CvInvoke.Ellipse(mat, new Point((int)((nowX + xOffset) * xyPpmm.Width), (int)((currentY) * xyPpmm.Height)), new Size((int)(Math.Abs(xOffset) * xyPpmm.Width), (int)(Math.Abs(yOffset) * xyPpmm.Width)), - 0, Math.Abs(currentY - nowY), 360.0 / Math.Abs(currentX - nowX), document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, + 0, Math.Abs(currentY - nowY), 360.0 / Math.Abs(currentX - nowX), document.PolarityColor, (int)(circleAperture.Diameter * xyPpmm.Max()), enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected );*/ @@ -375,19 +386,35 @@ public class GerberDocument else { CvInvoke.Line(mat, - PositionMmToPx(currentX, currentY, xyPpmm), + document.PositionMmToPx(currentX, currentY), + document.PositionMmToPx(nowX, nowY), + document.PolarityColor, + EmguExtensions.CorrectThickness(document.SizeMmToPx(circleAperture.Diameter)), + enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected); + /*mat.DrawLineAccurate(PositionMmToPx(currentX, currentY, xyPpmm), PositionMmToPx(nowX, nowY, xyPpmm), - document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, + document.PolarityColor SizeMmToPx(circleAperture.Diameter, xyPpmm.Max()), - enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected); + enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected);*/ + + /*CvInvoke.DrawContours(mat, new VectorOfVectorOfPoint(new[] + { + new [] + { + PositionMmToPx(currentX, currentY, xyPpmm), + PositionMmToPx(nowX, nowY, xyPpmm), + } + }), -1, document.PolarityColor, SizeMmToPx(circleAperture.Diameter, xyPpmm.Max()));*/ + //CvInvoke.Imshow("Line", mat); + //CvInvoke.WaitKey(); } } } else if (d == 3) { - currentAperture.DrawFlashD3(mat, xyPpmm, new PointF((float) nowX, (float) nowY), - document.Polarity == GerberPolarityType.Dark ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected); + currentAperture.DrawFlashD3(mat, new PointF((float) nowX, (float) nowY), + document.PolarityColor, enableAntialiasing ? LineType.AntiAlias : LineType.EightConnected); //CvInvoke.Imshow("G37", mat); //CvInvoke.WaitKey(); } @@ -426,32 +453,38 @@ public class GerberDocument return new PointF(point.X * (float)UnitExtensions.InchToMillimeter, point.Y * (float)UnitExtensions.InchToMillimeter); } - 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)); + public Point PositionMmToPx(PointF atMm) + => new((int)Math.Round(atMm.X * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atMm.Y * XYppmm.Height, MidpointRounding.AwayFromZero)); + + public Point PositionMmToPx(double atXmm, double atYmm) + => new((int)Math.Round(atXmm * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atYmm * XYppmm.Height, MidpointRounding.AwayFromZero)); + + public Point PositionMmToPx(float atXmm, float atYmm) + => new((int)Math.Round(atXmm * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atYmm * XYppmm.Height, MidpointRounding.AwayFromZero)); - public static Point PositionMmToPx(double atXmm, double atYmm, SizeF xyPpmm) - => new((int)Math.Round(atXmm * xyPpmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atYmm * xyPpmm.Height, MidpointRounding.AwayFromZero)); + public Size SizeMmToPx(SizeF sizeMm) + => new((int)Math.Max(1, Math.Round(sizeMm.Width * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)), + (int)Math.Max(1, Math.Round(sizeMm.Height * XYppmm.Height, (MidpointRounding)SizeMidpointRounding))); - public static Point PositionMmToPx(float atXmm, float atYmm, SizeF xyPpmm) - => new((int)Math.Round(atXmm * xyPpmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atYmm * xyPpmm.Height, MidpointRounding.AwayFromZero)); + public Size SizeMmToPx(double sizeXmm, double sizeYmm) + => new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)), + (int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height, (MidpointRounding)SizeMidpointRounding))); - public static Size SizeMmToPx(SizeF sizeMm, SizeF xyPpmm) - => new((int)Math.Max(1, Math.Round(sizeMm.Width * xyPpmm.Width, MidpointRounding.AwayFromZero)), - (int)Math.Max(1, Math.Round(sizeMm.Height * xyPpmm.Height, MidpointRounding.AwayFromZero))); + public Size SizeMmToPx(float sizeXmm, float sizeYmm) + => new((int)Math.Max(1, Math.Round(sizeXmm * XYppmm.Width, (MidpointRounding)SizeMidpointRounding)), + (int)Math.Max(1, Math.Round(sizeYmm * XYppmm.Height, (MidpointRounding)SizeMidpointRounding))); - public static Size SizeMmToPx(double sizeXmm, double sizeYmm, SizeF xyPpmm) - => new((int)Math.Max(1, Math.Round(sizeXmm * xyPpmm.Width, MidpointRounding.AwayFromZero)), - (int)Math.Max(1, Math.Round(sizeYmm * xyPpmm.Height, MidpointRounding.AwayFromZero))); + public int SizeMmToPx(float sizeMm) + => (int) Math.Max(1, Math.Round(sizeMm * XYppmm.Max(), (MidpointRounding)SizeMidpointRounding)); - public static Size SizeMmToPx(float sizeXmm, float sizeYmm, SizeF xyPpmm) - => new((int)Math.Max(1, Math.Round(sizeXmm * xyPpmm.Width, MidpointRounding.AwayFromZero)), - (int)Math.Max(1, Math.Round(sizeYmm * xyPpmm.Height, MidpointRounding.AwayFromZero))); + public int SizeMmToPx(double sizeMm) + => (int)Math.Max(1, Math.Round(sizeMm * XYppmm.Max(), (MidpointRounding)SizeMidpointRounding)); - public static int SizeMmToPx(float sizeMm, float ppmm) - => (int) Math.Max(1, Math.Round(sizeMm * ppmm, MidpointRounding.AwayFromZero)); + public int SizeMmToPxOverride(float sizeMm, float ppmm) + => (int)Math.Max(1, Math.Round(sizeMm * ppmm, (MidpointRounding)SizeMidpointRounding)); - public static int SizeMmToPx(double sizeMm, float ppmm) - => (int)Math.Max(1, Math.Round(sizeMm * ppmm, MidpointRounding.AwayFromZero)); + public int SizeMmToPxOverride(double sizeMm, float ppmm) + => (int)Math.Max(1, Math.Round(sizeMm * ppmm, (MidpointRounding)SizeMidpointRounding)); } diff --git a/UVtools.Core/Gerber/Macro.cs b/UVtools.Core/Gerber/Macro.cs index 6a5d3c1..aa1b721 100644 --- a/UVtools.Core/Gerber/Macro.cs +++ b/UVtools.Core/Gerber/Macro.cs @@ -18,6 +18,8 @@ public class Macro : IReadOnlyList<Primitive> { #region Properties + public GerberDocument Document { get; init; } + /// <summary> /// Gets the macro name /// </summary> @@ -26,9 +28,12 @@ public class Macro : IReadOnlyList<Primitive> public List<Primitive> Primitives { get; } = new(); #endregion - public Macro() { } + public Macro(GerberDocument document) + { + Document = document; + } - public Macro(string name) + public Macro(GerberDocument document, string name) : this(document) { Name = name; } @@ -40,7 +45,7 @@ public class Macro : IReadOnlyList<Primitive> // 0 Comment: A comment string if (line[0] == '0') { - if(line.Length > 2) Primitives.Add(new CommentPrimitive(line[2..])); + if(line.Length > 2) Primitives.Add(new CommentPrimitive(Document, line[2..])); return; } @@ -51,7 +56,7 @@ public class Macro : IReadOnlyList<Primitive> // 1 Circle: Exposure, Diameter, Center X, Center Y[, Rotation] case CirclePrimitive.Code: { - var primitive = new CirclePrimitive(commaSplit[1], commaSplit[2], commaSplit[3], commaSplit[4]); + var primitive = new CirclePrimitive(Document, commaSplit[1], commaSplit[2], commaSplit[3], commaSplit[4]); if (commaSplit.Length > 5) primitive.RotationExpression = commaSplit[5]; Primitives.Add(primitive); break; @@ -59,7 +64,7 @@ public class Macro : IReadOnlyList<Primitive> // 20 Vector Line: Exposure, Width, Start X, Start Y, End X, End Y, Rotation case VectorLinePrimitive.Code: { - var primitive = new VectorLinePrimitive(commaSplit[1], commaSplit[2], commaSplit[3], commaSplit[4], commaSplit[5], commaSplit[6]); + var primitive = new VectorLinePrimitive(Document, commaSplit[1], commaSplit[2], commaSplit[3], commaSplit[4], commaSplit[5], commaSplit[6]); if (commaSplit.Length > 7) primitive.RotationExpression = commaSplit[7]; Primitives.Add(primitive); break; @@ -67,7 +72,7 @@ public class Macro : IReadOnlyList<Primitive> // 21 Center Line: Exposure, Width, Hight, Center X, Center Y, Rotation case CenterLinePrimitive.Code: { - var primitive = new CenterLinePrimitive(commaSplit[1], commaSplit[2], commaSplit[3], commaSplit[4], commaSplit[5]); + var primitive = new CenterLinePrimitive(Document, commaSplit[1], commaSplit[2], commaSplit[3], commaSplit[4], commaSplit[5]); if (commaSplit.Length > 6) primitive.RotationExpression = commaSplit[6]; Primitives.Add(primitive); break; @@ -75,13 +80,13 @@ public class Macro : IReadOnlyList<Primitive> // 4 Outline: Exposure, # vertices, Start X, Start Y, Subsequent points..., Rotation case OutlinePrimitive.Code: { - Primitives.Add(new OutlinePrimitive(commaSplit[1], commaSplit[3..^1], commaSplit[^1])); + Primitives.Add(new OutlinePrimitive(Document, commaSplit[1], commaSplit[3..^1], commaSplit[^1])); break; } // 5 Outline: Exposure, # vertices, Start X, Start Y, Subsequent points..., Rotation case PolygonPrimitive.Code: { - var primitive = new PolygonPrimitive(commaSplit[1], commaSplit[2], commaSplit[3], commaSplit[4], commaSplit[5]); + var primitive = new PolygonPrimitive(Document, commaSplit[1], commaSplit[2], commaSplit[3], commaSplit[4], commaSplit[5]); if (commaSplit.Length > 6) primitive.RotationExpression = commaSplit[6]; Primitives.Add(primitive); break; @@ -91,7 +96,7 @@ public class Macro : IReadOnlyList<Primitive> public Macro Clone() { - var macro = new Macro(Name); + var macro = new Macro(Document, Name); foreach (var primitive in Primitives) { macro.Primitives.Add(primitive.Clone()); @@ -101,12 +106,12 @@ public class Macro : IReadOnlyList<Primitive> } - public static Macro? Parse(string line) + public static Macro? Parse(GerberDocument document, string line) { var match = Regex.Match(line, @"\%AM(\S+)\*"); if (!match.Success || match.Groups.Count < 2) return null; - return new Macro(match.Groups[1].Value); + return new Macro(document, match.Groups[1].Value); } public IEnumerator<Primitive> GetEnumerator() diff --git a/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs b/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs index 6e6d751..8b3c3b6 100644 --- a/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs @@ -74,9 +74,9 @@ public class CenterLinePrimitive : Primitive public float Rotation { get; set; } = 0; #endregion - protected CenterLinePrimitive() { } + protected CenterLinePrimitive(GerberDocument document) : base(document) { } - public CenterLinePrimitive(string exposureExpression, string widthExpression = "0", string heightExpression = "0", string centerXExpression = "0", string centerYExpression = "0", string rotationExpression = "0") + public CenterLinePrimitive(GerberDocument document, string exposureExpression, string widthExpression = "0", string heightExpression = "0", string centerXExpression = "0", string centerYExpression = "0", string rotationExpression = "0") : base(document) { ExposureExpression = exposureExpression; WidthExpression = widthExpression; @@ -87,8 +87,7 @@ public class CenterLinePrimitive : Primitive } - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, - LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { if (!IsParsed) return; if (Width <= 0 || Height <= 0) return; @@ -97,9 +96,9 @@ public class CenterLinePrimitive : Primitive else if(color.V0 == 0) color = EmguExtensions.WhiteColor; var halfWidth = Width / 2; - var pt1 = GerberDocument.PositionMmToPx(at.X + CenterX - halfWidth, at.Y + CenterY, xyPpmm); - var pt2 = GerberDocument.PositionMmToPx(at.X + CenterX + halfWidth, at.Y + CenterY, xyPpmm); - CvInvoke.Line(mat, pt1, pt2, color, GerberDocument.SizeMmToPx(Height, xyPpmm.Height), lineType); + var pt1 = Document.PositionMmToPx(at.X + CenterX - halfWidth, at.Y + CenterY); + var pt2 = Document.PositionMmToPx(at.X + CenterX + halfWidth, at.Y + CenterY); + CvInvoke.Line(mat, pt1, pt2, color, EmguExtensions.CorrectThickness(Document.SizeMmToPxOverride(Height, Document.XYppmm.Height)), lineType); //CvInvoke.Rectangle(mat, rectangle, color, -1, lineType); } diff --git a/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs b/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs index 5364e0d..a06b627 100644 --- a/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs @@ -67,9 +67,9 @@ public class CirclePrimitive : Primitive public float Rotation { get; set; } = 0; #endregion - protected CirclePrimitive() { } + protected CirclePrimitive(GerberDocument document) : base(document) { } - public CirclePrimitive(string exposureExpression = "1", string diameterExpression = "0", string centerXExpression = "0", string centerYExpression = "0", string rotationExpression = "0") + public CirclePrimitive(GerberDocument document, string exposureExpression = "1", string diameterExpression = "0", string centerXExpression = "0", string centerYExpression = "0", string rotationExpression = "0") : base(document) { ExposureExpression = exposureExpression; DiameterExpression = diameterExpression; @@ -78,7 +78,7 @@ public class CirclePrimitive : Primitive RotationExpression = rotationExpression; } - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { if (!IsParsed) return; @@ -88,8 +88,8 @@ public class CirclePrimitive : Primitive else if (color.V0 == 0) color = EmguExtensions.WhiteColor; CvInvoke.Circle(mat, - GerberDocument.PositionMmToPx(at.X + CenterX, at.Y + CenterY, xyPpmm), - GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()), color, -1, lineType); + Document.PositionMmToPx(at.X + CenterX, at.Y + CenterY), + Document.SizeMmToPx(Diameter / 2), color, -1, lineType); } public override void ParseExpressions(GerberDocument document, params string[] args) diff --git a/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs b/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs index a909911..a6539ec 100644 --- a/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs @@ -33,19 +33,19 @@ public class CommentPrimitive : Primitive public string Comment { get; set; } = string.Empty; #endregion - public CommentPrimitive() + public CommentPrimitive(GerberDocument document) : base(document) { IsParsed = true; } - public CommentPrimitive(string comment) + public CommentPrimitive(GerberDocument document, string comment) : base(document) { Comment = comment; IsParsed = true; } - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { } diff --git a/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs b/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs index 80ecfaf..08b17ee 100644 --- a/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs @@ -66,9 +66,9 @@ public class OutlinePrimitive : Primitive public float Rotation { get; set; } = 0; #endregion - protected OutlinePrimitive() { } + protected OutlinePrimitive(GerberDocument document) : base(document) { } - public OutlinePrimitive(string exposureExpression, string[] coordinatesExpression, string rotationExpression) + public OutlinePrimitive(GerberDocument document, string exposureExpression, string[] coordinatesExpression, string rotationExpression) : base(document) { ExposureExpression = exposureExpression; CoordinatesExpression = coordinatesExpression; @@ -76,7 +76,7 @@ public class OutlinePrimitive : Primitive } - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { if (Coordinates.Length < 3) return; @@ -86,7 +86,7 @@ public class OutlinePrimitive : Primitive var points = new List<Point>(); for (int i = 0; i < Coordinates.Length-1; i++) { - var pt = GerberDocument.PositionMmToPx(at.X + Coordinates[i].X, at.Y + Coordinates[i].Y, xyPpmm); + var pt = Document.PositionMmToPx(at.X + Coordinates[i].X, at.Y + Coordinates[i].Y); if(i > 0 && points[i-1] == pt) continue; // Prevent duplicates points.Add(pt); } diff --git a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs index 2bb7289..1566b75 100644 --- a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs @@ -72,9 +72,9 @@ public class PolygonPrimitive : Primitive public float Rotation { get; set; } = 0; #endregion - protected PolygonPrimitive() { } + protected PolygonPrimitive(GerberDocument document) : base(document) { } - public PolygonPrimitive(string exposureExpression, string verticesCountExpression, string centerXExpression = "0", string centerYExpression = "0", string diameterExpression = "0", string rotationExpression = "0") + public PolygonPrimitive(GerberDocument document, string exposureExpression, string verticesCountExpression, string centerXExpression = "0", string centerYExpression = "0", string diameterExpression = "0", string rotationExpression = "0") : base(document) { ExposureExpression = exposureExpression; VerticesCountExpression = verticesCountExpression; @@ -84,7 +84,7 @@ public class PolygonPrimitive : Primitive RotationExpression = rotationExpression; } - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { if (!IsParsed) return; @@ -94,9 +94,9 @@ public class PolygonPrimitive : Primitive else if (color.V0 == 0) color = EmguExtensions.WhiteColor; - mat.DrawPolygon(VerticesCount, - GerberDocument.SizeMmToPx(Diameter / 2, xyPpmm.Max()), - GerberDocument.PositionMmToPx(at.X + CenterX, at.Y + CenterY, xyPpmm), + mat.DrawPolygon(VerticesCount, + Document.SizeMmToPx(Diameter / 2), + Document.PositionMmToPx(at.X + CenterX, at.Y + CenterY), color, 0, -1, lineType); } diff --git a/UVtools.Core/Gerber/Primitives/Primitive.cs b/UVtools.Core/Gerber/Primitives/Primitive.cs index 0a7740f..75a20a7 100644 --- a/UVtools.Core/Gerber/Primitives/Primitive.cs +++ b/UVtools.Core/Gerber/Primitives/Primitive.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; @@ -21,11 +20,18 @@ public abstract class Primitive public bool IsParsed { get; protected set; } = false; + public GerberDocument Document { get; init; } + #endregion - protected Primitive() { } + //protected Primitive() { } + + protected Primitive(GerberDocument document) + { + Document = document; + } - public abstract void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected); + public abstract void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected); public abstract void ParseExpressions(GerberDocument document, params string[] args); diff --git a/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs b/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs index 9916a1d..624e4ad 100644 --- a/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs @@ -83,9 +83,9 @@ public class VectorLinePrimitive : Primitive public float Rotation { get; set; } = 0; #endregion - protected VectorLinePrimitive() { } + protected VectorLinePrimitive(GerberDocument document) : base(document) { } - public VectorLinePrimitive(string exposureExpression, string lineWidthExpression, string startXExpression, string startYExpression, string endXExpression, string endYExpression, string rotationExpression = "0") + public VectorLinePrimitive(GerberDocument document, string exposureExpression, string lineWidthExpression, string startXExpression, string startYExpression, string endXExpression, string endYExpression, string rotationExpression = "0") : base(document) { ExposureExpression = exposureExpression; LineWidthExpression = lineWidthExpression; @@ -96,7 +96,7 @@ public class VectorLinePrimitive : Primitive RotationExpression = rotationExpression; } - public override void DrawFlashD3(Mat mat, SizeF xyPpmm, PointF at, MCvScalar color, + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { if (!IsParsed) return; @@ -105,9 +105,9 @@ public class VectorLinePrimitive : Primitive if (Exposure == 0) color = EmguExtensions.BlackColor; else if (color.V0 == 0) color = EmguExtensions.WhiteColor; - var pt1 = GerberDocument.PositionMmToPx(at.X + StartX, at.Y + StartY, xyPpmm); - var pt2 = GerberDocument.PositionMmToPx(at.X + EndX, at.Y + EndY, xyPpmm); - CvInvoke.Line(mat, pt1, pt2, color, GerberDocument.SizeMmToPx(LineWidth, xyPpmm.Height), lineType); + var pt1 = Document.PositionMmToPx(at.X + StartX, at.Y + StartY); + var pt2 = Document.PositionMmToPx(at.X + EndX, at.Y + EndY); + CvInvoke.Line(mat, pt1, pt2, color, EmguExtensions.CorrectThickness(Document.SizeMmToPxOverride(LineWidth, Document.XYppmm.Height)), lineType); //CvInvoke.Rectangle(mat, rectangle, color, -1, lineType); } diff --git a/UVtools.Core/Objects/UInt24BigEndian.cs b/UVtools.Core/Objects/UInt24BigEndian.cs new file mode 100644 index 0000000..9515ea3 --- /dev/null +++ b/UVtools.Core/Objects/UInt24BigEndian.cs @@ -0,0 +1,59 @@ +/* + * 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 BinarySerialization; +using UVtools.Core.Extensions; + +namespace UVtools.Core.Objects; + +/// <summary> +/// A string that always end with 0x00 if not null +/// It contains the string length as uint +/// </summary> +public sealed class UInt24BigEndian +{ + [FieldOrder(0)] [FieldCount(3)] public byte[] Bytes { get; set; } = new byte[3]; + + + [Ignore] + public uint Value + { + get => BitExtensions.ToUIntBigEndian(0, Bytes[0], Bytes[1], Bytes[2]); + set + { + var bytes = BitExtensions.ToBytesBigEndian(value); + Bytes[0] = bytes[1]; + Bytes[1] = bytes[2]; + Bytes[2] = bytes[3]; + } + } + + public UInt24BigEndian() { } + + public UInt24BigEndian(uint value) + { + Value = value; + } + + public override string ToString() => Value.ToString(); + + private bool Equals(UInt24BigEndian other) + { + return Value == other.Value; + } + + public override bool Equals(object? obj) + { + return ReferenceEquals(this, obj) || obj is UInt24BigEndian other && Equals(other); + } + + public override int GetHashCode() + { + return (int)Value; + } +}
\ No newline at end of file diff --git a/UVtools.Core/Operations/OperationLayerArithmetic.cs b/UVtools.Core/Operations/OperationLayerArithmetic.cs index b8ddb40..7e027a7 100644 --- a/UVtools.Core/Operations/OperationLayerArithmetic.cs +++ b/UVtools.Core/Operations/OperationLayerArithmetic.cs @@ -9,8 +9,8 @@ using Emgu.CV; using System; using System.Collections.Generic; -using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Serialization; using UVtools.Core.FileFormats; @@ -51,6 +51,16 @@ public class OperationLayerArithmetic : Operation Operator = layerArithmeticOperator; } } + + public sealed class ArithmeticOperationGroup + { + public List<uint> SetLayers { get; set; } = new(); + public List<ArithmeticOperation> Operations { get; set; } = new(); + + public bool IsValid => + SetLayers.Count > 0 && Operations.Count > 0 && + !(SetLayers.Count == 1 && Operations.Count == 1 && SetLayers[0] == Operations[0].LayerIndex); + } #endregion #region Overrides @@ -70,7 +80,8 @@ public class OperationLayerArithmetic : Operation "Example 2: 10,11,12 = 11+12-10*5 Same as: 10:12 = 11+12-10*5\n" + "On example 1 the layer 10 will be set with the result of layer 10 plus layer 11.\n" + "On example 2 the layers 10,11,12 will be set with the result of layer 11 plus 12 minus 10 all multiplied by layer 5.\n\n" + - "Note: Calculation are made sequential, math order rules wont apply here."; + "Note: Calculation are made sequential, math order rules wont apply here.\n" + + "Use ; to split and start a new arithmetic operation."; public override string ConfirmationText => $"perform this arithmetic operation"; @@ -87,10 +98,10 @@ public class OperationLayerArithmetic : Operation sb.AppendLine("The sentence is empty."); else if(!Parse()) sb.AppendLine("Unable to parse the sentence, malformed or incomplete."); - else if (SetLayers.Count == 0) + /*else if (SetLayers.Count == 0) sb.AppendLine("No layers to assign."); - else if (Operations.Count == 0) - sb.AppendLine("No operations to perform."); + else if (Core.Operations.Count == 0) + sb.AppendLine("No operations to perform.");*/ else if (!IsValid) sb.AppendLine("The operation will have no impact and will not be performed."); @@ -112,13 +123,9 @@ public class OperationLayerArithmetic : Operation set => RaiseAndSetIfChanged(ref _sentence, value); } [XmlIgnore] - public List<ArithmeticOperation> Operations { get; private set; } = new(); - - [XmlIgnore] - public List<uint> SetLayers { get; private set; } = new(); + public List<ArithmeticOperationGroup> Operations { get; private set; } = new(); - public bool IsValid => SetLayers.Count > 0 && Operations.Count > 0 && - !(SetLayers.Count == 1 && Operations.Count == 1 && SetLayers[0] == Operations[0].LayerIndex); + public bool IsValid => Operations.Count > 0; #endregion #region Constructor @@ -134,109 +141,99 @@ public class OperationLayerArithmetic : Operation public bool Parse() { if (string.IsNullOrEmpty(_sentence)) return false; - SetLayers.Clear(); Operations.Clear(); - var splitSentence = _sentence.Split('='); - var operations = splitSentence[0]; - if (splitSentence.Length >= 2) + + var sentences = Regex.Replace(_sentence, @"\s+", string.Empty) + .Split(';', StringSplitOptions.RemoveEmptyEntries); + + foreach (var sentence in sentences) { - operations = splitSentence[1]; - var setLayers = splitSentence[0].Replace(" ", string.Empty).Split(',', StringSplitOptions.TrimEntries); - foreach (var layer in setLayers) + var group = new ArithmeticOperationGroup(); + var splitSentence = sentence.Split('='); + var operations = splitSentence[0]; + if (splitSentence.Length >= 2) { - var rangeSplit = layer.Split(':', StringSplitOptions.TrimEntries); - if (rangeSplit.Length > 1) + operations = splitSentence[1]; + var setLayers = splitSentence[0].Split(',', StringSplitOptions.RemoveEmptyEntries); + foreach (var layer in setLayers) { - uint.TryParse(rangeSplit[0].Trim(), out var startLayer); - if (!uint.TryParse(rangeSplit[1].Trim(), out var endLayer)) endLayer = SlicerFile.LastLayerIndex; - for (var index = startLayer; index <= endLayer; index++) + var rangeSplit = layer.Split(':', StringSplitOptions.RemoveEmptyEntries); + if (rangeSplit.Length > 1) { - if (SetLayers.Contains(index)) continue; - SetLayers.Add(index); + uint.TryParse(rangeSplit[0], out var startLayer); + if (!uint.TryParse(rangeSplit[1], out var endLayer)) endLayer = SlicerFile.LastLayerIndex; + endLayer = Math.Min(endLayer, SlicerFile.LastLayerIndex); + for (var index = startLayer; index <= endLayer; index++) + { + if (group.SetLayers.Contains(index)) continue; + group.SetLayers.Add(index); + } + continue; } - continue; - } - if (!uint.TryParse(layer.Trim(), out var layerIndex)) continue; - if (SetLayers.Contains(layerIndex)) continue; - SetLayers.Add(layerIndex); + if (!uint.TryParse(layer, out var layerIndex)) continue; + if (group.SetLayers.Contains(layerIndex)) continue; + group.SetLayers.Add(layerIndex); + } } - } - SetLayers = SetLayers.Where(layerIndex => layerIndex <= SlicerFile.LastLayerIndex).ToList(); - SetLayers.Sort(); + group.SetLayers.Sort(); - operations = operations.Replace(" ", string.Empty); - if (string.IsNullOrWhiteSpace(operations)) return false; + if (string.IsNullOrWhiteSpace(operations)) return false; - string layerIndexStr = string.Empty; - foreach (char c in operations) - { - if (c >= '0' && c <= '9') + string layerIndexStr = string.Empty; + foreach (char c in operations) { - layerIndexStr += c; - continue; - } - - LayerArithmeticOperators op = LayerArithmeticOperators.None; - switch (c) - { - case '+': - op = LayerArithmeticOperators.Add; - break; - case '-': - op = LayerArithmeticOperators.Subtract; - break; - case '*': - op = LayerArithmeticOperators.Multiply; - break; - case '/': - op = LayerArithmeticOperators.Divide; - break; - case '&': - op = LayerArithmeticOperators.BitwiseAnd; - break; - case '|': - op = LayerArithmeticOperators.BitwiseOr; - break; - case '^': - op = LayerArithmeticOperators.BitwiseXor; - break; - case '$': - op = LayerArithmeticOperators.AbsDiff; - break; - } + if (c is >= '0' and <= '9') + { + layerIndexStr += c; + continue; + } - if (op == LayerArithmeticOperators.None // No valid operator - || string.IsNullOrWhiteSpace(layerIndexStr) // Started with a operator instead of layer - ) continue; + var op = c switch + { + '+' => LayerArithmeticOperators.Add, + '-' => LayerArithmeticOperators.Subtract, + '*' => LayerArithmeticOperators.Multiply, + '/' => LayerArithmeticOperators.Divide, + '&' => LayerArithmeticOperators.BitwiseAnd, + '|' => LayerArithmeticOperators.BitwiseOr, + '^' => LayerArithmeticOperators.BitwiseXor, + '$' => LayerArithmeticOperators.AbsDiff, + _ => LayerArithmeticOperators.None + }; + + if (op == LayerArithmeticOperators.None // No valid operator + || string.IsNullOrWhiteSpace(layerIndexStr) // Started with a operator instead of layer + ) continue; + + + if (uint.TryParse(layerIndexStr, out var layerIndex) && layerIndex <= SlicerFile.LastLayerIndex) + { + group.Operations.Add(new ArithmeticOperation(layerIndex, op)); + } + // Reset layer string + layerIndexStr = string.Empty; + } - if (uint.TryParse(layerIndexStr, out var layerIndex)) + // Append the left over + if (!string.IsNullOrWhiteSpace(layerIndexStr)) { - Operations.Add(new ArithmeticOperation(layerIndex, op)); + if (uint.TryParse(layerIndexStr, out var layerIndex) && layerIndex <= SlicerFile.LastLayerIndex) + { + group.Operations.Add(new ArithmeticOperation(layerIndex, LayerArithmeticOperators.None)); + } } - // Reset layer string - layerIndexStr = string.Empty; - } - - // Append the left over - if (!string.IsNullOrWhiteSpace(layerIndexStr)) - { - if (uint.TryParse(layerIndexStr, out var layerIndex)) + //if (Operations.Count == 0) return false; + if (group.SetLayers.Count == 0 && group.Operations.Count > 0) { - Operations.Add(new ArithmeticOperation(layerIndex, LayerArithmeticOperators.None)); + group.SetLayers.Add(group.Operations[0].LayerIndex); } - } - Operations = Operations.Where(op => op.LayerIndex <= SlicerFile.LastLayerIndex).ToList(); - - //if (Operations.Count == 0) return false; - if (SetLayers.Count == 0 && Operations.Count > 0) - { - SetLayers.Add(Operations[0].LayerIndex); + Operations.Add(group); } return true; @@ -246,65 +243,72 @@ public class OperationLayerArithmetic : Operation { if (!IsValid) return false; - using var result = SlicerFile[Operations[0].LayerIndex].LayerMat; - using var resultRoi = GetRoiOrDefault(result); - using var imageMask = GetMask(resultRoi); - - progress.ItemCount = (uint) Operations.Count; - for (int i = 1; i < Operations.Count; i++) + foreach (var operation in Operations) { + if(!operation.IsValid) continue; + progress.ThrowIfCancellationRequested(); - using var image = SlicerFile[Operations[i].LayerIndex].LayerMat; - var imageRoi = GetRoiOrDefault(image); - - switch (Operations[i - 1].Operator) + using var result = SlicerFile[operation.Operations[0].LayerIndex].LayerMat; + using var resultRoi = GetRoiOrDefault(result); + using var imageMask = GetMask(resultRoi); + + progress.ItemCount = (uint)operation.Operations.Count; + for (int i = 1; i < operation.Operations.Count; i++) { - case LayerArithmeticOperators.Add: - CvInvoke.Add(resultRoi, imageRoi, resultRoi, imageMask); - break; - case LayerArithmeticOperators.Subtract: - CvInvoke.Subtract(resultRoi, imageRoi, resultRoi, imageMask); - break; - case LayerArithmeticOperators.Multiply: - CvInvoke.Multiply(resultRoi, imageRoi, resultRoi); - break; - case LayerArithmeticOperators.Divide: - CvInvoke.Divide(resultRoi, imageRoi, resultRoi); - break; - case LayerArithmeticOperators.BitwiseAnd: - CvInvoke.BitwiseAnd(resultRoi, imageRoi, resultRoi, imageMask); - break; - case LayerArithmeticOperators.BitwiseOr: - CvInvoke.BitwiseOr(resultRoi, imageRoi, resultRoi, imageMask); - break; - case LayerArithmeticOperators.BitwiseXor: - CvInvoke.BitwiseXor(resultRoi, imageRoi, resultRoi, imageMask); - break; - case LayerArithmeticOperators.AbsDiff: - CvInvoke.AbsDiff(resultRoi, imageRoi, resultRoi); - break; - } + progress.ThrowIfCancellationRequested(); + using var image = SlicerFile[operation.Operations[i].LayerIndex].LayerMat; + var imageRoi = GetRoiOrDefault(image); - progress++; - } + switch (operation.Operations[i - 1].Operator) + { + case LayerArithmeticOperators.Add: + CvInvoke.Add(resultRoi, imageRoi, resultRoi, imageMask); + break; + case LayerArithmeticOperators.Subtract: + CvInvoke.Subtract(resultRoi, imageRoi, resultRoi, imageMask); + break; + case LayerArithmeticOperators.Multiply: + CvInvoke.Multiply(resultRoi, imageRoi, resultRoi); + break; + case LayerArithmeticOperators.Divide: + CvInvoke.Divide(resultRoi, imageRoi, resultRoi); + break; + case LayerArithmeticOperators.BitwiseAnd: + CvInvoke.BitwiseAnd(resultRoi, imageRoi, resultRoi, imageMask); + break; + case LayerArithmeticOperators.BitwiseOr: + CvInvoke.BitwiseOr(resultRoi, imageRoi, resultRoi, imageMask); + break; + case LayerArithmeticOperators.BitwiseXor: + CvInvoke.BitwiseXor(resultRoi, imageRoi, resultRoi, imageMask); + break; + case LayerArithmeticOperators.AbsDiff: + CvInvoke.AbsDiff(resultRoi, imageRoi, resultRoi); + break; + } - progress.Reset("Applied layers", (uint) SetLayers.Count); - Parallel.ForEach(SetLayers, CoreSettings.GetParallelOptions(progress), layerIndex => - { - progress.LockAndIncrement(); - if (Operations.Count == 1 || HaveROIorMask) - { - using var mat = SlicerFile[layerIndex].LayerMat; - var matRoi = GetRoiOrDefault(mat); - resultRoi.CopyTo(matRoi, imageMask); - SlicerFile[layerIndex].LayerMat = mat; - return; + progress++; } - //ApplyMask(mat, resultRoi, imageMask); + progress.Reset("Applied layers", (uint)operation.SetLayers.Count); + Parallel.ForEach(operation.SetLayers, CoreSettings.GetParallelOptions(progress), layerIndex => + { + progress.LockAndIncrement(); + if (operation.Operations.Count == 1 || HaveROIorMask) + { + using var mat = SlicerFile[layerIndex].LayerMat; + var matRoi = GetRoiOrDefault(mat); + resultRoi.CopyTo(matRoi, imageMask); + SlicerFile[layerIndex].LayerMat = mat; + return; + } - SlicerFile[layerIndex].LayerMat = result; - }); + //ApplyMask(mat, resultRoi, imageMask); + + SlicerFile[layerIndex].LayerMat = result; + }); + } + return !progress.Token.IsCancellationRequested; } diff --git a/UVtools.Core/Operations/OperationPCBExposure.cs b/UVtools.Core/Operations/OperationPCBExposure.cs index 45aefce..3b50b45 100644 --- a/UVtools.Core/Operations/OperationPCBExposure.cs +++ b/UVtools.Core/Operations/OperationPCBExposure.cs @@ -26,7 +26,7 @@ namespace UVtools.Core.Operations; [Serializable] public class OperationPCBExposure : Operation { - #region + #region Static public static string[] ValidExtensions => new[] { @@ -48,6 +48,7 @@ public class OperationPCBExposure : Operation private bool _mergeFiles; private decimal _layerHeight; private decimal _exposureTime; + private GerberMidpointRounding _sizeMidpointRounding = GerberMidpointRounding.AwayFromZero; private bool _mirror; private bool _invertColor; private bool _enableAntiAliasing; @@ -101,7 +102,7 @@ public class OperationPCBExposure : Operation public override string ToString() { - var result = $"{string.Join(" / ", _files)} [Exposure: {_exposureTime}s] [Mirror: {_mirror}] [Invert: {_invertColor}]"; + var result = $"{string.Join(" / ", _files)} [Exposure: {_exposureTime}s] [Rounding: {_sizeMidpointRounding}] [Mirror: {_mirror}] [Invert: {_invertColor}]"; if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; return result; } @@ -135,13 +136,6 @@ public class OperationPCBExposure : Operation set => RaiseAndSetIfChanged(ref _mergeFiles, value); } - /*public string? FilePath - { - get => _filePath; - set => RaiseAndSetIfChanged(ref _filePath, value); - } - public bool FileExists => !string.IsNullOrWhiteSpace(_filePath) && File.Exists(_filePath);*/ - public decimal LayerHeight { get => _layerHeight; @@ -153,7 +147,13 @@ public class OperationPCBExposure : Operation get => _exposureTime; set => RaiseAndSetIfChanged(ref _exposureTime, Math.Round(value, 2)); } - + + public GerberMidpointRounding SizeMidpointRounding + { + get => _sizeMidpointRounding; + set => RaiseAndSetIfChanged(ref _sizeMidpointRounding, value); + } + public bool Mirror { get => _mirror; @@ -178,7 +178,7 @@ public class OperationPCBExposure : Operation protected bool Equals(OperationPCBExposure other) { - return _files.Equals(other._files) && _mergeFiles == other._mergeFiles && _layerHeight == other._layerHeight && _exposureTime == other._exposureTime && _mirror == other._mirror && _invertColor == other._invertColor && _enableAntiAliasing == other._enableAntiAliasing; + return _files.Equals(other._files) && _mergeFiles == other._mergeFiles && _layerHeight == other._layerHeight && _exposureTime == other._exposureTime && _sizeMidpointRounding == other._sizeMidpointRounding && _mirror == other._mirror && _invertColor == other._invertColor && _enableAntiAliasing == other._enableAntiAliasing; } public override bool Equals(object? obj) @@ -191,7 +191,7 @@ public class OperationPCBExposure : Operation public override int GetHashCode() { - return HashCode.Combine(_files, _mergeFiles, _layerHeight, _exposureTime, _mirror, _invertColor, _enableAntiAliasing); + return HashCode.Combine(_files, _mergeFiles, _layerHeight, _exposureTime, (int) _sizeMidpointRounding, _mirror, _invertColor, _enableAntiAliasing); } #endregion @@ -242,7 +242,7 @@ public class OperationPCBExposure : Operation { var mat = SlicerFile.CreateMat(); if (!File.Exists(file)) return mat; - GerberDocument.ParseAndDraw(file, mat, SlicerFile.Ppmm, _enableAntiAliasing); + GerberDocument.ParseAndDraw(file, mat, SlicerFile.Ppmm, _sizeMidpointRounding, _enableAntiAliasing); //var boundingRectangle = CvInvoke.BoundingRectangle(mat); //var cropped = mat.Roi(new Size(boundingRectangle.Right, boundingRectangle.Bottom)); diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 7cfb01f..783bd22 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.5</Version> + <Version>3.5.6</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 c96d2f0..afa2e14 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.5"?> + <?define SourceDir="..\publish\UVtools_win-x64_v3.5.6"?> <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 b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml index 204f74d..6a42fe0 100644 --- a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml @@ -63,7 +63,7 @@ MaxHeight="400" Items="{Binding Operation.Files}" /> - <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto" + <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto" ColumnDefinitions="Auto,10,400"> <ToggleSwitch Grid.Row="0" Grid.Column="2" @@ -93,8 +93,18 @@ Maximum="200" FormatString="F2" Value="{Binding Operation.ExposureTime}"/> - - <StackPanel Grid.Row="6" Grid.Column="2" + + <TextBlock Grid.Row="6" Grid.Column="0" + VerticalAlignment="Center" + Text="Size rounding:"/> + + <ComboBox Grid.Row="6" Grid.Column="2" + HorizontalAlignment="Stretch" + Items="{Binding Operation.SizeMidpointRounding, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}" + SelectedItem="{Binding Operation.SizeMidpointRounding, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/> + + + <StackPanel Grid.Row="8" Grid.Column="2" Orientation="Horizontal" Spacing="20"> <CheckBox VerticalAlignment="Center" IsChecked="{Binding Operation.Mirror}" diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 821f61a..988e66a 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.5</Version> + <Version>3.5.6</Version> <Platforms>AnyCPU;x64</Platforms> <PackageIcon>UVtools.png</PackageIcon> <PackageReadmeFile>README.md</PackageReadmeFile> @@ -39,14 +39,14 @@ <NoWarn>1701;1702;</NoWarn> </PropertyGroup> <ItemGroup> - <PackageReference Include="Avalonia" Version="0.10.16" /> - <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.16" /> - <PackageReference Include="Avalonia.Desktop" Version="0.10.16" /> - <PackageReference Include="Avalonia.Diagnostics" Version="0.10.16" /> + <PackageReference Include="Avalonia" Version="0.10.17" /> + <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.17" /> + <PackageReference Include="Avalonia.Desktop" Version="0.10.17" /> + <PackageReference Include="Avalonia.Diagnostics" Version="0.10.17" /> <PackageReference Include="MessageBox.Avalonia" Version="2.0.2" /> - <PackageReference Include="Projektanker.Icons.Avalonia" Version="5.1.0" /> - <PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="5.1.0" /> - <PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="5.1.0" /> + <PackageReference Include="Projektanker.Icons.Avalonia" Version="5.2.0" /> + <PackageReference Include="Projektanker.Icons.Avalonia.FontAwesome" Version="5.2.0" /> + <PackageReference Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="5.2.0" /> <PackageReference Include="ThemeEditor.Controls.ColorPicker" Version="0.10.14" /> </ItemGroup> <ItemGroup> |