- **PCB Exposure:**
   - (Add) Gerber file extensions: gko, gtl, gto, gts, gbl, gbo, gbs, gml
   - (Fix) Trailing zeros suppression mode and improve the parsing of the coordinate string (#492)
   - (Fix) Allow coordinates without number `XYD0*` to indicate `X=0` and `Y=0`
   - (Fix) Do not try to fetch apertures lower than index 10 when a `D02` (closed shutter) is found alone
This commit is contained in:
Tiago Conceição
2022-06-25 02:02:59 +01:00
parent 67fbec1fff
commit 9711bb7474
9 changed files with 117 additions and 85 deletions
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## 25/06/2022 - v3.5.2
- **PCB Exposure:**
- (Add) Gerber file extensions: gko, gtl, gto, gts, gbl, gbo, gbs, gml
- (Fix) Trailing zeros suppression mode and improve the parsing of the coordinate string (#492)
- (Fix) Allow coordinates without number `XYD0*` to indicate `X=0` and `Y=0`
- (Fix) Do not try to fetch apertures lower than index 10 when a `D02` (closed shutter) is found alone
## 24/06/2022 - v3.5.1
- **PCB Exposure:**
+4 -8
View File
@@ -1,10 +1,6 @@
- **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
- (Add) Gerber file extensions: gko, gtl, gto, gts, gbl, gbo, gbs, gml
- (Fix) Trailing zeros suppression mode and improve the parsing of the coordinate string (#492)
- (Fix) Allow coordinates without number `XYD0*` to indicate `X=0` and `Y=0`
- (Fix) Do not try to fetch apertures lower than index 10 when a `D02` (closed shutter) is found alone
+16
View File
@@ -7,6 +7,22 @@
*/
namespace UVtools.Core.Gerber;
public enum GerberZerosSuppressionType : byte
{
/// <summary>
/// Do not omit zeros
/// </summary>
NoSuppression,
/// <summary>
/// Omit left zeros
/// </summary>
Leading,
/// <summary>
/// Omit right zeros
/// </summary>
Trail
}
public enum GerberPositionType : byte
{
Absolute,
+64 -71
View File
@@ -13,30 +13,35 @@ 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;
namespace UVtools.Core.Gerber;
/// <summary>
/// https://www.ucamco.com/files/downloads/file_en/456/gerber-layer-format-specification-revision-2022-02_en.pdf?ac97011bf6bce9aaf0b1aac43d84b05f
/// </summary>
public class GerberDocument
{
#region Properties
public GerberZerosSuppressionType ZerosSuppressionType { get; set; } = GerberZerosSuppressionType.NoSuppression;
public GerberPositionType PositionType { get; set; } = GerberPositionType.Absolute;
public GerberUnitType UnitType { get; set; } = GerberUnitType.Millimeter;
public GerberPolarityType Polarity { get; set; } = GerberPolarityType.Dark;
public GerberMoveType MoveType { get; set; } = GerberMoveType.Linear;
public bool LeadingZeroOmitted { get; set; } = true;
public byte CoordinateXIntegers { get; set; } = 3;
public byte CoordinateXFractionalDigits { get; set; } = 6;
public byte CoordinateXLength => (byte)(CoordinateXIntegers + CoordinateXFractionalDigits);
public byte CoordinateYIntegers { get; set; } = 3;
public byte CoordinateYFractionalDigits { get; set; } = 6;
public byte CoordinateYLength => (byte)(CoordinateYIntegers + CoordinateYFractionalDigits);
public Dictionary<int, Aperture> Apertures { get; } = new();
public Dictionary<string, Macro> Macros { get; } = new();
@@ -101,11 +106,11 @@ public class GerberDocument
{
// %FSLAX34Y34*%
// 0123456789
document.LeadingZeroOmitted = line[3] switch
document.ZerosSuppressionType = line[3] switch
{
'L' => true,
'T' => false,
_ => document.LeadingZeroOmitted
'L' => GerberZerosSuppressionType.Leading,
'T' => GerberZerosSuppressionType.Trail,
_ => document.ZerosSuppressionType
};
document.PositionType = line[4] switch
{
@@ -113,6 +118,7 @@ public class GerberDocument
'I' => GerberPositionType.Relative,
_ => document.PositionType
};
if (document.PositionType == GerberPositionType.Relative) throw new NotImplementedException("Relative positions are not implemented yet.\nPlease use Absolute position for now.");
if (line[5] != 'X') continue;
if (byte.TryParse(line[6].ToString(), out var x1)) document.CoordinateXIntegers = x1;
if (byte.TryParse(line[7].ToString(), out var x2)) document.CoordinateXFractionalDigits = x2;
@@ -191,6 +197,7 @@ public class GerberDocument
continue;
}
// Aperture selector
if (line[0] == 'D')
{
var matchD = Regex.Match(line, @"D(\d+)");
@@ -198,15 +205,17 @@ 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 (line[0] == 'X' || line[0] == 'Y')
{
var matchX = Regex.Match(line, @"X-?(\d+)");
var matchY = Regex.Match(line, @"Y-?(\d+)");
var matchX = Regex.Match(line, @"X-?(\d+)?");
var matchY = Regex.Match(line, @"Y-?(\d+)?");
var matchD = Regex.Match(line, @"D(\d+)");
double nowX = 0;
@@ -215,25 +224,20 @@ public class GerberDocument
if (!matchD.Success || matchD.Groups.Count < 2) continue;
if (!int.TryParse(matchD.Groups[1].Value, out var d)) continue;
if (matchX.Success && matchX.Groups.Count >= 2)
if (matchX.Success)
{
if (double.TryParse(matchX.Groups[1].Value, out nowX))
if (matchX.Groups.Count >= 2)
{
if (nowX != 0)
var valueStr = document.ZerosSuppressionType switch
{
if (document.CoordinateXFractionalDigits > matchX.Groups[1].Value.Length) // Leading zero omitted
{
double.TryParse($"0.{matchX.Groups[1].Value}", out nowX);
}
else
{
var integers = matchX.Groups[1].Value[..^document.CoordinateXFractionalDigits];
var fraction = matchX.Groups[1].Value.Substring(matchX.Groups[1].Value.Length - document.CoordinateXFractionalDigits, document.CoordinateXFractionalDigits);
double.TryParse($"{integers}.{fraction}", out nowX);
}
GerberZerosSuppressionType.Trail => matchX.Groups[1].Value.PadRight(document.CoordinateXLength, '0'),
_ => matchX.Groups[1].Value.PadLeft(document.CoordinateXLength, '0'),
};
nowX = document.GetMillimeters(nowX);
}
var integers = valueStr[..document.CoordinateXIntegers];
var fraction = valueStr[document.CoordinateXIntegers..];
double.TryParse($"{integers}.{fraction}", out nowX);
nowX = document.GetMillimeters(nowX);
}
}
else
@@ -241,24 +245,21 @@ public class GerberDocument
nowX = currentX;
}
if (matchY.Success && matchY.Groups.Count >= 2)
if (matchY.Success)
{
if (double.TryParse(matchY.Groups[1].Value, out nowY))
if (matchY.Groups.Count >= 2)
{
if (nowY != 0)
var valueStr = document.ZerosSuppressionType switch
{
if (document.CoordinateYFractionalDigits > matchY.Groups[1].Value.Length) // Leading zero omitted
{
double.TryParse($"0.{matchY.Groups[1].Value}", out nowY);
}
else
{
var integers = matchY.Groups[1].Value[..^document.CoordinateYFractionalDigits];
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);
}
GerberZerosSuppressionType.Trail => matchY.Groups[1].Value.PadRight(document.CoordinateYLength, '0'),
_ => matchY.Groups[1].Value.PadLeft(document.CoordinateYLength, '0'),
};
var integers = valueStr[..document.CoordinateYIntegers];
var fraction = valueStr[document.CoordinateYIntegers..];
double.TryParse($"{integers}.{fraction}", out nowY);
nowY = document.GetMillimeters(nowY);
}
}
else
@@ -289,46 +290,38 @@ public class GerberDocument
{
if (document.MoveType is GerberMoveType.Arc or GerberMoveType.ArcCounterClockwise)
{
double xOffset = 0;
double yOffset = 0;
var matchI = Regex.Match(line, @"I(-?\d+)");
var matchJ = Regex.Match(line, @"J(-?\d+)");
if(!matchI.Success || !matchJ.Success || matchI.Groups.Count < 2 || matchJ.Groups.Count < 2) continue;
if (double.TryParse(matchI.Groups[1].Value, out var xOffset))
var valueStr = document.ZerosSuppressionType switch
{
if (xOffset != 0)
{
if (document.CoordinateXFractionalDigits > matchI.Groups[1].Value.Length) // Leading zero omitted
{
double.TryParse($"0.{matchI.Groups[1].Value}", out xOffset);
}
else
{
var integers = matchI.Groups[1].Value[..^document.CoordinateXFractionalDigits];
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);
}
}
GerberZerosSuppressionType.Trail => matchI.Groups[1].Value.PadRight(document.CoordinateXLength, '0'),
_ => matchI.Groups[1].Value.PadLeft(document.CoordinateXLength, '0'),
};
if (double.TryParse(matchJ.Groups[1].Value, out var yOffset))
var integers = valueStr[..document.CoordinateXIntegers];
var fraction = valueStr[document.CoordinateXIntegers..];
double.TryParse($"{integers}.{fraction}", out xOffset);
xOffset = document.GetMillimeters(xOffset);
valueStr = document.ZerosSuppressionType switch
{
if(yOffset != 0)
{
if (document.CoordinateYFractionalDigits > matchJ.Groups[1].Value.Length) // Leading zero omitted
{
double.TryParse($"0.{matchJ.Groups[1].Value}", out yOffset);
}
else
{
var integers = matchJ.Groups[1].Value[..^document.CoordinateYFractionalDigits];
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);
}
}
GerberZerosSuppressionType.Trail => matchJ.Groups[1].Value.PadRight(document.CoordinateYLength, '0'),
_ => matchJ.Groups[1].Value.PadLeft(document.CoordinateYLength, '0'),
};
integers = valueStr[..document.CoordinateYIntegers];
fraction = valueStr[document.CoordinateYIntegers..];
double.TryParse($"{integers}.{fraction}", out yOffset);
yOffset = document.GetMillimeters(yOffset);
if (currentX == nowX && currentY == nowY) // Closed circle
{
@@ -11,6 +11,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using Emgu.CV;
using Emgu.CV.CvEnum;
@@ -25,6 +26,22 @@ namespace UVtools.Core.Operations;
[Serializable]
public class OperationPCBExposure : Operation
{
#region
public static string[] ValidExtensions => new[]
{
"gbr", // Gerber
"gko", // Board outline layer
"gtl", // Top layer
"gto", // Top silkscreen layer
"gts", // Top solder mask layer
"gbl", // Bottom layer
"gbo", // Bottom silkscreen layer
"gbs", // Bottom solder mask layer
"gml", // Mechanical layer
};
#endregion
#region Members
private RangeObservableCollection<ValueDescription> _files = new();
@@ -189,7 +206,8 @@ public class OperationPCBExposure : Operation
var tmpPath = PathExtensions.GetTemporaryDirectory($"{About.Software}.");
foreach (var entry in zip.Entries)
{
if(!entry.Name.EndsWith(".gbr")) continue;
if(!ValidExtensions.Any(extension => entry.Name.EndsWith($".{extension}", StringComparison.InvariantCultureIgnoreCase))) continue;
var filePath = entry.ImprovedExtractToFile(tmpPath, false);
if (!string.IsNullOrEmpty(filePath))
{
+1 -1
View File
@@ -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.1</Version>
<Version>3.5.2</Version>
<Copyright>Copyright © 2020 PTRTECH</Copyright>
<PackageIcon>UVtools.png</PackageIcon>
<Platforms>AnyCPU;x64</Platforms>
+1 -1
View File
@@ -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.1"?>
<?define SourceDir="..\publish\UVtools_win-x64_v3.5.2"?>
<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">
@@ -92,7 +92,8 @@ namespace UVtools.WPF.Controls.Tools
{
return;
}
if (!_selectedFile.ValueAsString.EndsWith(".gbr", StringComparison.OrdinalIgnoreCase) || !File.Exists(_selectedFile.ValueAsString)) return;
if (!OperationPCBExposure.ValidExtensions.Any(extension => _selectedFile.ValueAsString.EndsWith($".{extension}", StringComparison.InvariantCultureIgnoreCase)) || !File.Exists(_selectedFile.ValueAsString)) return;
using var mat = Operation.GetMat(_selectedFile.ValueAsString);
using var matCropped = mat.CropByBounds(20);
_previewImage?.Dispose();
@@ -115,7 +116,7 @@ namespace UVtools.WPF.Controls.Tools
new()
{
Name = "Gerber files",
Extensions = new List<string>{ "gbr" }
Extensions = new List<string>(OperationPCBExposure.ValidExtensions)
},
},
};
+1 -1
View File
@@ -12,7 +12,7 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<Version>3.5.1</Version>
<Version>3.5.2</Version>
<Platforms>AnyCPU;x64</Platforms>
<PackageIcon>UVtools.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>