diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a26d34..d4efb8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## 01/12/2022 - v3.9.0 + +- **File formats:** + - (Add) File extension: .gktwo.ctb to `ChituboxFile` to be able to convert files for UniFormation GKtwo under special CTB format + - (Add) UniFormation GKtwo compatibility under CTB format, if exporting JXS rename to CTB before open + - (Fix) CTB, CBDDLP, PHOTON, FDG, PHZ: Read and write files larger then 4GB (#608) +- **PCB Exposure:** + - (Add) Offset X/Y to offset the PCB from it origin + - (Add) Allow to toggle between "Show preview image cropped by it bounds" and "Show full preview image (The final result)" + - (Improvement) Use rectangle instead of line for center line primitive (#607) + - (Fix) Implement rotation to polygon and center line primitives (#607) + - (Fix) Macros in a single line was not being parsed (#607) + - (Fix) Invert color per file was not affecting primitives +- **Network printers:** + - (Add) Socket requests with TCP and UDP + - (Add) AnyCubic printer preset (However it can't upload a file) + - (Add) Scripts in request path to allow a first request to fetch data to the final request: + - A script starts with **<\?** and ends with **?>** + - First parameter is the first request to get response content from + - Second parameter is the regex pattern to match content with + - Third parameter is the final request that supports a parameter from regex matching group, eg: **{#1}** is match Group[1] value + - **Example:** <\? getfiles > {0}\/(\d+\.[\da-zA-Z]+), > printfile,{#1} ?> + - (Change) Allow to print a filename without send it when upload request path is empty + - (Fix) Do not show printers with empty requests +- (Change) Default layer compression to Lz4 instead of Png +- (Improvement) Application is now culture aware but set part of `NumberFormat` to the `InvariantCulture.NumberFormat` +- (Improvement) Material cost now show with the current culture currency symbol due previous change +- (Improvement) Better submit of bug reports using sections and forms +- (Improvement) Linux: AppImage now have a help manual with possible arguments and parameters +- (Improvement) macOS: Codesign app on auto-installer and auto-upgrade to bypass arm64 run restriction (#431) +- (Improvement) macOS: Rebuilt arm64 libcvextern.dylib to run with less dependencies (#431) +- (Improvement) macOS: Try to show missing dependencies from openCV (if any) on the error message +- (Fix) UI: layers sorted lexicographically instead of numerically in the issues list view (#611) +- (Fix) PrusaSlicer printer parameters: UniFormation GKtwo + ## 10/11/2022 - v3.8.3 - **UVtoolsCmd:** diff --git a/CREDITS.md b/CREDITS.md index 5ccfaae..e0cd610 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -74,4 +74,5 @@ - Patrick Hofmann - Ajilus - James F Hammond -- Steven Woodward \ No newline at end of file +- Steven Woodward +- Piotr Czerkasow \ No newline at end of file diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 4e64784..92cc23a 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,19 +1,33 @@ -- **UVtoolsCmd:** - - **print-properties:** - - (Change) `-b`, `--base` option to `-a`, `-all` to indicate all properties and sub-properties, now defaults to only show base properties - - (Add) `-r`, `--range` option to prints only the matching layer(s) index(es) in a range - - (Add) `-i`, `--indexes` option to prints only the matching layer(s) index(es) - - (Add) Command: `set-properties Set properties in a file or to it layers with new values` - - (Add) Command: `print-issues Detect and print issues in a file` - - (Add) New option to the `run` command: `-p, --property Set a property with a new value (Compatible with operations only)` - - (Remove) Command: `print-layers` as it has been moved to `print-properties`, use `-r :` to obtain same result as default on `print-layers` -- **Issues:** - - (Fix) Issues groups with only one issue was displaying the wrong area value - - (Fix) Volume incorrectly calculated, resulting in a high value for group of issues - - (Fix) Incorrect calculation of the bounding rectangle for a group of issues -- **Repair layers:** - - (Add) Switch to opt between "Re-detect the selected issues before repair" and "Use and repair the previous detected issues" (Default) - - (Improvement) Do not allow to run the tool if there are no detected issues when the option "Use and repair the previous detected issues" is selected -- (Improvement) Linux: Recompile libcvextern.so on a older system to be able to run on both older and newest system (#603) -- (Upgrade) .NET from 6.0.10 to 6.0.11 +- **File formats:** + - (Add) File extension: .gktwo.ctb to `ChituboxFile` to be able to convert files for UniFormation GKtwo under special CTB format + - (Add) UniFormation GKtwo compatibility under CTB format, if exporting JXS rename to CTB before open + - (Fix) CTB, CBDDLP, PHOTON, FDG, PHZ: Read and write files larger then 4GB (#608) +- **PCB Exposure:** + - (Add) Offset X/Y to offset the PCB from it origin + - (Add) Allow to toggle between "Show preview image cropped by it bounds" and "Show full preview image (The final result)" + - (Improvement) Use rectangle instead of line for center line primitive (#607) + - (Fix) Implement rotation to polygon and center line primitives (#607) + - (Fix) Macros in a single line was not being parsed (#607) + - (Fix) Invert color per file was not affecting primitives +- **Network printers:** + - (Add) Socket requests with TCP and UDP + - (Add) AnyCubic printer preset (However it can't upload a file) + - (Add) Scripts in request path to allow a first request to fetch data to the final request: + - A script starts with **<\?** and ends with **?>** + - First parameter is the first request to get response content from + - Second parameter is the regex pattern to match content with + - Third parameter is the final request that supports a parameter from regex matching group, eg: **{#1}** is match Group[1] value + - **Example:** <\? getfiles > {0}\/(\d+\.[\da-zA-Z]+), > printfile,{#1} ?> + - (Change) Allow to print a filename without send it when upload request path is empty + - (Fix) Do not show printers with empty requests +- (Change) Default layer compression to Lz4 instead of Png +- (Improvement) Application is now culture aware but set part of `NumberFormat` to the `InvariantCulture.NumberFormat` +- (Improvement) Material cost now show with the current culture currency symbol due previous change +- (Improvement) Better submit of bug reports using sections and forms +- (Improvement) Linux: AppImage now have a help manual with possible arguments and parameters +- (Improvement) macOS: Codesign app on auto-installer and auto-upgrade to bypass arm64 run restriction (#431) +- (Improvement) macOS: Rebuilt arm64 libcvextern.dylib to run with less dependencies (#431) +- (Improvement) macOS: Try to show missing dependencies from openCV (if any) on the error message +- (Fix) UI: layers sorted lexicographically instead of numerically in the issues list view (#611) +- (Fix) PrusaSlicer printer parameters: UniFormation GKtwo diff --git a/Scripts/010 Editor/ctb.bt b/Scripts/010 Editor/ctb.bt index fcb67ef..ef89f11 100644 --- a/Scripts/010 Editor/ctb.bt +++ b/Scripts/010 Editor/ctb.bt @@ -163,7 +163,7 @@ struct LAYER_DATA { float LightOffSeconds ; uint DataAddress ; uint DataSize ; - uint Unknown1 ; + uint PageNumber ; uint TableSize ; uint Unknown3 ; uint Unknown4 ; @@ -205,11 +205,11 @@ struct LAYERS { if(header.Version >= 3) { - FSeek(layerData.DataAddress - 84); + FSeek(layerData.PageNumber * 4294967296 + layerData.DataAddress - 84); LAYER_DATAEX layerDataEx; } - FSeek(layerData.DataAddress); + FSeek(layerData.PageNumber * 4294967296 + layerData.DataAddress); LAYER_RLE layerRLE(layerData.DataSize); FSeek(currentPos); } diff --git a/Scripts/010 Editor/ctb_decrypted.bt b/Scripts/010 Editor/ctb_decrypted.bt index b721b1b..8ac6eaf 100644 --- a/Scripts/010 Editor/ctb_decrypted.bt +++ b/Scripts/010 Editor/ctb_decrypted.bt @@ -126,7 +126,7 @@ FSeek(decryptedHeader.LayerTableOffset); struct LAYER_POINTER { uint Offset; - uint Padding1; + uint PageNumber; uint LayerTableSize; uint Padding2; } layerPointers[decryptedHeader.LayerCount]; @@ -137,7 +137,7 @@ struct LAYER_HEADER{ float ExposureTime; float LightOffDelay; uint LayerDataOffset; - uint unknown2; + uint PageNumber; uint LayerDataLength; uint unknown3; uint EncryptedDataOffset; @@ -158,7 +158,7 @@ struct LAYER_HEADER{ struct LAYER { LAYER_HEADER layerHeader; - FSeek(layerHeader.LayerDataOffset); + FSeek(layerHeader.PageNumber * 4294967296 + layerHeader.LayerDataOffset); /* do we have encrypted data? */ if (layerHeader.EncryptedDataLength > 0) { @@ -180,7 +180,7 @@ struct LAYER { struct LAYERS { local uint x; for(x = 0; x < decryptedHeader.LayerCount; x++) { - FSeek(layerPointers[x].Offset); + FSeek(layerPointers[x].PageNumber * 4294967296 + layerPointers[x].Offset); LAYER layer; } } layers; diff --git a/Scripts/010 Editor/ctb_encrypted.bt b/Scripts/010 Editor/ctb_encrypted.bt index 877e904..df1063e 100644 --- a/Scripts/010 Editor/ctb_encrypted.bt +++ b/Scripts/010 Editor/ctb_encrypted.bt @@ -70,7 +70,7 @@ FSeek(curOffset); struct LAYER_POINTER { uint Offset ; - uint Padding1; + uint PageNumber; uint LayerTableSize; uint Padding2; } layerPointers[layerCount]; @@ -81,9 +81,9 @@ struct LAYER_HEADER{ float ExposureTime; float LightOffDelay; uint LayerDataOffset; - uint unknown2; + uint PageNumber; uint LayerDataLength; - ubyte unknown3[4]; + uint Unknown; uint EncryptedDataOffset; uint EncryptedDataLength; float LiftHeight; @@ -102,7 +102,7 @@ struct LAYER_HEADER{ struct LAYER { LAYER_HEADER layerHeader; - FSeek(layerHeader.LayerDataOffset); + FSeek(layerHeader.PageNumber * 4294967296 + layerHeader.LayerDataOffset); /* do we have encrypted data? */ if (layerHeader.EncryptedDataLength > 0) { @@ -124,7 +124,7 @@ struct LAYER { struct LAYERS { local uint x; for(x = 0; x < layerCount; x++) { - FSeek(layerPointers[x].Offset); + FSeek(layerPointers[x].PageNumber * 4294967296 + layerPointers[x].Offset); LAYER layer; } } layers; diff --git a/Scripts/010 Editor/fdg.bt b/Scripts/010 Editor/fdg.bt index d939785..a27f1a8 100644 --- a/Scripts/010 Editor/fdg.bt +++ b/Scripts/010 Editor/fdg.bt @@ -101,7 +101,7 @@ struct LAYER_DATA { float LightOffSeconds ; uint DataAddress ; uint DataSize ; - uint Unknown ; + uint PageNumber ; uint Unknown ; uint Unknown ; uint Unknown ; @@ -119,7 +119,7 @@ struct LAYERS { for( i = 0; i < header.LayerCount; i++ ){ LAYER_DATA layerData ; currentPos = FTell(); - FSeek(layerData.DataAddress); + FSeek(layerData.PageNumber * 4294967296 + layerData.DataAddress); LAYER_RLE layerRLE(layerData.DataSize); FSeek(currentPos); } diff --git a/Scripts/010 Editor/phz.bt b/Scripts/010 Editor/phz.bt index f50bbfd..51ecd45 100644 --- a/Scripts/010 Editor/phz.bt +++ b/Scripts/010 Editor/phz.bt @@ -101,7 +101,7 @@ struct LAYER_DATA { float LightOffSeconds ; uint DataAddress ; uint DataSize ; - uint Unknown ; + uint PageNumber ; uint Unknown ; uint Unknown ; uint Unknown ; @@ -119,7 +119,7 @@ struct LAYERS { for( i = 0; i < header.LayerCount; i++ ){ LAYER_DATA layerData ; currentPos = FTell(); - FSeek(layerData.DataAddress); + FSeek(layerData.PageNumber * 4294967296 + layerData.DataAddress); LAYER_RLE layerRLE(layerData.DataSize); FSeek(currentPos); } diff --git a/Scripts/install-uvtools.sh b/Scripts/install-uvtools.sh index 9a004b2..4ee23c8 100644 --- a/Scripts/install-uvtools.sh +++ b/Scripts/install-uvtools.sh @@ -14,6 +14,11 @@ api_url="https://api.github.com/repos/sn4k3/UVtools/releases/latest" dependencies_url="https://raw.githubusercontent.com/sn4k3/UVtools/master/Scripts/install-dependencies.sh" macOS_least_version='10.15' +if [ "$arch" != "x86_64" -a "$arch" != "arm64" ]; then + echo "Error: Unsupported host arch $arch" + exit -1 +fi + version() { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; } testcmd() { command -v "$1" &> /dev/null; } get_filesize() { @@ -29,19 +34,19 @@ get_filesize() { } downloaduvtools(){ if [ -z "$1" ]; then - echo "Download url was not specified" + echo 'Error: Download url was not specified!' exit -1 fi filename="$(basename "${download_url}")" tmpfile="$(mktemp "${TMPDIR:-/tmp}"/UVtoolsUpdate.XXXXXXXX)" - echo "Downloading: $download_url" + echo "- Downloading: $download_url" curl -L --retry 4 $download_url -o "$tmpfile" download_size="$(curl -sLI $download_url | grep -i Content-Length | awk 'END{print $2}' | tr -d '\r')" if [ -n "$download_size" ]; then - echo "- Validating file" + echo '- Validating file' local filesize="$(get_filesize "$tmpfile")" if [ "$download_size" -ne "$filesize" ]; then echo "Error: File verification failed, expecting $download_size bytes but downloaded $filesize bytes. Please re-run the script." @@ -50,17 +55,12 @@ downloaduvtools(){ fi fi - echo "- Kill instances" + echo '- Kill instances' killall UVtools 2> /dev/null ps -ef | grep '.*dotnet.*UVtools.dll' | grep -v grep | awk '{print $2}' | xargs kill 2> /dev/null sleep 0.5 } -if [ "$arch" != "x86_64" -a "$arch" != "arm64" ]; then - echo "Error: Unsupported host arch $arch" - exit -1 -fi - echo ' _ ___ ___ _ ' echo ' | | | \ \ / / |_ ___ ___ | |___ ' echo ' | | | |\ \ / /| __/ _ \ / _ \| / __|' @@ -91,7 +91,7 @@ if [ -z "$osVariant" ]; then exit -1 fi -echo "- Detected: $osVariant $arch" +echo "- $osVariant $arch" if [ "$osVariant" == "osx" ]; then ############# diff --git a/UVtools.Cmd/Program.cs b/UVtools.Cmd/Program.cs index 7568732..15b1c76 100644 --- a/UVtools.Cmd/Program.cs +++ b/UVtools.Cmd/Program.cs @@ -33,7 +33,8 @@ internal class Program public static async Task Main(params string[] args) { - Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; + + Thread.CurrentThread.CurrentCulture = CoreSettings.OptimalCultureInfo; Args = args; var rootCommand = new RootCommand("MSLA/DLP, file analysis, repair, conversion and manipulation") diff --git a/UVtools.Cmd/UVtools.Cmd.csproj b/UVtools.Cmd/UVtools.Cmd.csproj index 44b2f18..d6585d3 100644 --- a/UVtools.Cmd/UVtools.Cmd.csproj +++ b/UVtools.Cmd/UVtools.Cmd.csproj @@ -1,42 +1,42 @@  - - Exe - net6.0 - UVtoolsCmd - UVtools.ico - 1.0.5 - Tiago Conceição, sn4k3 - PTRTECH - LICENSE - https://github.com/sn4k3/UVtools - UVtools.png - https://github.com/sn4k3/UVtools - MSLA/DLP, file analysis, repair, conversion and manipulation - Copyright © 2020 PTRTECH - Git - true - AnyCPU;x64 - enable - + + Exe + net6.0 + UVtoolsCmd + UVtools.ico + 1.0.5 + Tiago Conceição, sn4k3 + PTRTECH + LICENSE + https://github.com/sn4k3/UVtools + UVtools.png + https://github.com/sn4k3/UVtools + MSLA/DLP, file analysis, repair, conversion and manipulation + Copyright © 2020 PTRTECH + Git + true + AnyCPU;x64 + enable + - - - + + + - - - + + + - - - True - - - - True - - - + + + True + + + + True + + + diff --git a/UVtools.Core/CoreSettings.cs b/UVtools.Core/CoreSettings.cs index 46e86f2..870a7c2 100644 --- a/UVtools.Core/CoreSettings.cs +++ b/UVtools.Core/CoreSettings.cs @@ -9,6 +9,7 @@ using Emgu.CV.Cuda; using System; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -26,6 +27,21 @@ public static class CoreSettings #region Properties + public static CultureInfo OptimalCultureInfo + { + get + { + var cultureInfo = (CultureInfo)CultureInfo.CurrentCulture.Clone(); + cultureInfo.NumberFormat.CurrencyDecimalSeparator = CultureInfo.InvariantCulture.NumberFormat.CurrencyDecimalSeparator; + cultureInfo.NumberFormat.CurrencyGroupSeparator = CultureInfo.InvariantCulture.NumberFormat.CurrencyGroupSeparator; + cultureInfo.NumberFormat.NumberDecimalSeparator = CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator; + cultureInfo.NumberFormat.NumberGroupSeparator = CultureInfo.InvariantCulture.NumberFormat.NumberGroupSeparator; + cultureInfo.NumberFormat.PercentDecimalSeparator = CultureInfo.InvariantCulture.NumberFormat.PercentDecimalSeparator; + cultureInfo.NumberFormat.PercentGroupSeparator = CultureInfo.InvariantCulture.NumberFormat.PercentGroupSeparator; + return cultureInfo; + } + } + /// /// Gets or sets the maximum number of concurrent tasks enabled by this ParallelOptions instance. /// Less or equal to 0 will set to auto number @@ -77,7 +93,7 @@ public static class CoreSettings /// /// Gets or sets the default compression type for layers /// - public static LayerCompressionCodec DefaultLayerCompressionCodec { get; set; } = LayerCompressionCodec.Png; + public static LayerCompressionCodec DefaultLayerCompressionCodec { get; set; } = LayerCompressionCodec.Lz4; /// /// The average resin 1000ml bottle cost, to use when bottle cost is not available. diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index fcc4e50..2080134 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -1074,6 +1074,12 @@ public static class EmguExtensions /// public static void DrawRotatedRectangle(this Mat src, Size size, Point center, MCvScalar color, int angle = 0, int thickness = -1, LineType lineType = LineType.EightConnected) { + if (angle == 0) + { + src.DrawCenteredRectangle(size, center, color, thickness, lineType); + return; + } + var rect = new RotatedRect(center, size, angle); var vertices = rect.GetVertices(); var points = new Point[vertices.Length]; @@ -1097,6 +1103,32 @@ public static class EmguExtensions } } + /// + /// Draw a square around a center point + /// + /// + /// + /// + /// + /// + /// + public static void DrawCenteredSquare(this Mat src, int size, Point center, MCvScalar color, int thickness = -1, LineType lineType = LineType.EightConnected) + => src.DrawCenteredRectangle(new Size(size, size), center, color, thickness, lineType); + + /// + /// Draw a rectangle around a center point + /// + /// + /// + /// + /// + /// + /// + public static void DrawCenteredRectangle(this Mat src, Size size, Point center, MCvScalar color, int thickness = -1, LineType lineType = LineType.EightConnected) + { + CvInvoke.Rectangle(src, new Rectangle(center.OffsetBy(size.Width / -2, size.Height / -2), size), color, thickness, lineType); + } + /// /// Draw a polygon given number of sides and length /// @@ -1113,8 +1145,8 @@ public static class EmguExtensions { if (sides == 1) { - var point1 = new Point(center.X - radius, center.Y); - var point2 = new Point(center.X + radius, center.Y); + var point1 = center with {X = center.X - radius}; + var point2 = center with {X = center.X + radius}; point1 = point1.Rotate(startingAngle, center); point2 = point2.Rotate(startingAngle, center); diff --git a/UVtools.Core/Extensions/ReflectionExtensions.cs b/UVtools.Core/Extensions/ReflectionExtensions.cs index 060fb08..43770e9 100644 --- a/UVtools.Core/Extensions/ReflectionExtensions.cs +++ b/UVtools.Core/Extensions/ReflectionExtensions.cs @@ -28,7 +28,7 @@ public static class ReflectionExtensions { if (Enum.TryParse(attribute.PropertyType, value, true, out var enumValue)) { - attribute.SetValue(obj, Enum.Parse(attribute.PropertyType, enumValue?.ToString() ?? string.Empty)); + attribute.SetValue(obj, enumValue); return true; } @@ -95,25 +95,25 @@ public static class ReflectionExtensions if (attribute.PropertyType == typeof(Half)) { - attribute.SetValue(obj, Half.Parse(value, CultureInfo.InvariantCulture.NumberFormat)); + attribute.SetValue(obj, Half.Parse(value, CultureInfo.InvariantCulture)); return true; } if (attribute.PropertyType == typeof(float)) { - attribute.SetValue(obj, float.Parse(value, CultureInfo.InvariantCulture.NumberFormat)); + attribute.SetValue(obj, float.Parse(value, CultureInfo.InvariantCulture)); return true; } if (attribute.PropertyType == typeof(double)) { - attribute.SetValue(obj, double.Parse(value, CultureInfo.InvariantCulture.NumberFormat)); + attribute.SetValue(obj, double.Parse(value, CultureInfo.InvariantCulture)); return true; } if (attribute.PropertyType == typeof(decimal)) { - attribute.SetValue(obj, decimal.Parse(value, CultureInfo.InvariantCulture.NumberFormat)); + attribute.SetValue(obj, decimal.Parse(value, CultureInfo.InvariantCulture)); return true; } diff --git a/UVtools.Core/FileFormats/CTBEncryptedFile.cs b/UVtools.Core/FileFormats/CTBEncryptedFile.cs index 1c7b462..98ee883 100644 --- a/UVtools.Core/FileFormats/CTBEncryptedFile.cs +++ b/UVtools.Core/FileFormats/CTBEncryptedFile.cs @@ -6,6 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; +using System.Drawing.Printing; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -176,22 +177,23 @@ public class CTBEncryptedFile : FileFormat public class LayerPointer { [FieldOrder(0)] public uint LayerOffset { get; set; } - [FieldOrder(1)] public uint Padding1 { get; set; } // 0 + [FieldOrder(1)] public uint PageNumber { get; set; } [FieldOrder(2)] public uint LayerTableSize { get; set; } = LayerDef.TABLE_SIZE; // always 0x58 [FieldOrder(3)] public uint Padding2 { get; set; } // 0 public override string ToString() { - return $"{nameof(LayerOffset)}: {LayerOffset}, {nameof(Padding1)}: {Padding1}, {nameof(LayerTableSize)}: {LayerTableSize}, {nameof(Padding2)}: {Padding2}"; + return $"{nameof(LayerOffset)}: {LayerOffset}, {nameof(PageNumber)}: {PageNumber}, {nameof(LayerTableSize)}: {LayerTableSize}, {nameof(Padding2)}: {Padding2}"; } public LayerPointer() { } - public LayerPointer(uint layerOffset) + public LayerPointer(long layerOffset) { - LayerOffset = layerOffset; + PageNumber = (uint)(layerOffset / ChituboxFile.PageSize); + LayerOffset = (uint)(layerOffset - ChituboxFile.PageSize * PageNumber); } } @@ -203,8 +205,8 @@ public class CTBEncryptedFile : FileFormat [FieldOrder(1)] public float PositionZ { get; set; } [FieldOrder(2)] public float ExposureTime { get; set; } [FieldOrder(3)] public float LightOffDelay { get; set; } - [FieldOrder(4)] public uint LayerDefOffset { get; set; } - [FieldOrder(5)] public uint Unknown2 { get; set; } + [FieldOrder(4)] public uint LayerDataOffset { get; set; } + [FieldOrder(5)] public uint PageNumber { get; set; } [FieldOrder(6)] public uint DataLength { get; set; } [FieldOrder(7)] public uint Unknown3 { get; set; } [FieldOrder(8)] public uint EncryptedDataOffset { get; set; } @@ -440,7 +442,7 @@ public class CTBEncryptedFile : FileFormat public override string ToString() { - return $"{nameof(TableSize)}: {TableSize}, {nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(LayerDefOffset)}: {LayerDefOffset}, {nameof(Unknown2)}: {Unknown2}, {nameof(DataLength)}: {DataLength}, {nameof(Unknown3)}: {Unknown3}, {nameof(EncryptedDataOffset)}: {EncryptedDataOffset}, {nameof(EncryptedDataLength)}: {EncryptedDataLength}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}, {nameof(RestTimeBeforeLift)}: {RestTimeBeforeLift}, {nameof(RestTimeAfterLift)}: {RestTimeAfterLift}, {nameof(RestTimeAfterRetract)}: {RestTimeAfterRetract}, {nameof(LightPWM)}: {LightPWM}, {nameof(Unknown6)}: {Unknown6}, {nameof(RLEData)}: {RLEData?.Length}"; + return $"{nameof(TableSize)}: {TableSize}, {nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(LayerDataOffset)}: {LayerDataOffset}, {nameof(PageNumber)}: {PageNumber}, {nameof(DataLength)}: {DataLength}, {nameof(Unknown3)}: {Unknown3}, {nameof(EncryptedDataOffset)}: {EncryptedDataOffset}, {nameof(EncryptedDataLength)}: {EncryptedDataLength}, {nameof(LiftHeight)}: {LiftHeight}, {nameof(LiftSpeed)}: {LiftSpeed}, {nameof(LiftHeight2)}: {LiftHeight2}, {nameof(LiftSpeed2)}: {LiftSpeed2}, {nameof(RetractSpeed)}: {RetractSpeed}, {nameof(RetractHeight2)}: {RetractHeight2}, {nameof(RetractSpeed2)}: {RetractSpeed2}, {nameof(RestTimeBeforeLift)}: {RestTimeBeforeLift}, {nameof(RestTimeAfterLift)}: {RestTimeAfterLift}, {nameof(RestTimeAfterRetract)}: {RestTimeAfterRetract}, {nameof(LightPWM)}: {LightPWM}, {nameof(Unknown6)}: {Unknown6}, {nameof(RLEData)}: {RLEData?.Length}"; } } @@ -1188,7 +1190,7 @@ public class CTBEncryptedFile : FileFormat { progress.ThrowIfCancellationRequested(); - inputFile.Seek(LayersPointer[layerIndex].LayerOffset, SeekOrigin.Begin); + inputFile.Seek(LayersPointer[layerIndex].PageNumber * ChituboxFile.PageSize + LayersPointer[layerIndex].LayerOffset, SeekOrigin.Begin); LayersDefinition[layerIndex] = Helpers.Deserialize(inputFile); LayersDefinition[layerIndex].Parent = this; if (DecodeType == FileDecodeType.Full) LayersDefinition[layerIndex].RLEData = inputFile.ReadBytes(LayersDefinition[layerIndex].DataLength); @@ -1207,13 +1209,10 @@ public class CTBEncryptedFile : FileFormat /* Decrypt RLE data here */ var byteBuffer = new byte[layerDef.EncryptedDataLength]; - Array.Copy(layerDef.RLEData!, (int)layerDef.EncryptedDataOffset, byteBuffer, 0, - (int)layerDef.EncryptedDataLength); + Array.Copy(layerDef.RLEData!, (int)layerDef.EncryptedDataOffset, byteBuffer, 0, (int)layerDef.EncryptedDataLength); - byteBuffer = CryptExtensions.AesCryptBytes(byteBuffer, Bigfoot, CipherMode.CBC, - PaddingMode.None, false, CookieMonster); - Array.Copy(byteBuffer, 0, layerDef.RLEData!, layerDef.EncryptedDataOffset, - layerDef.EncryptedDataLength); + byteBuffer = CryptExtensions.AesCryptBytes(byteBuffer, Bigfoot, CipherMode.CBC, PaddingMode.None, false, CookieMonster); + Array.Copy(byteBuffer, 0, layerDef.RLEData!, layerDef.EncryptedDataOffset, layerDef.EncryptedDataLength); } bool isBugged = false; @@ -1243,7 +1242,7 @@ public class CTBEncryptedFile : FileFormat if (buggyLayers.Count == LayerCount) { throw new FileLoadException("Unable to load this file due to Chitubox bug affecting every layer." + - "Please increase the portion of the plate in use and reslice the file."); + "Please increase the portion of the plate in use and re-slice the file."); } for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) @@ -1376,9 +1375,11 @@ public class CTBEncryptedFile : FileFormat { progress.ThrowIfCancellationRequested(); var layerDef = LayersDefinition[layerIndex]; - LayersPointer[layerIndex] = new LayerPointer((uint)outputFile.Position); + LayersPointer[layerIndex] = new LayerPointer(outputFile.Position); - layerDef.LayerDefOffset = LayersPointer[layerIndex].LayerOffset + LayerDef.TABLE_SIZE; + long layerDataOffset = outputFile.Position + LayerDef.TABLE_SIZE; + layerDef.PageNumber = (uint)(layerDataOffset / ChituboxFile.PageSize); + layerDef.LayerDataOffset = (uint)(layerDataOffset - ChituboxFile.PageSize * layerDef.PageNumber); outputFile.WriteSerialize(layerDef); outputFile.WriteBytes(layerDef.RLEData!); progress++; @@ -1425,7 +1426,7 @@ public class CTBEncryptedFile : FileFormat for (uint layerIndex = 0; layerIndex < LayersPointer!.Length; layerIndex++) { LayersDefinition![layerIndex].SetFrom(this[layerIndex]); - outputFile.Seek(LayersPointer[layerIndex].LayerOffset, SeekOrigin.Begin); + outputFile.Seek(LayersPointer[layerIndex].PageNumber * ChituboxFile.PageSize + LayersPointer[layerIndex].LayerOffset, SeekOrigin.Begin); outputFile.WriteSerialize(LayersDefinition[layerIndex]); } } diff --git a/UVtools.Core/FileFormats/CXDLPFile.cs b/UVtools.Core/FileFormats/CXDLPFile.cs index a9cf2d5..9e06bb8 100644 --- a/UVtools.Core/FileFormats/CXDLPFile.cs +++ b/UVtools.Core/FileFormats/CXDLPFile.cs @@ -489,7 +489,7 @@ public class CXDLPFile : FileFormat public override float DisplayWidth { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { var str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); @@ -508,7 +508,7 @@ public class CXDLPFile : FileFormat public override float DisplayHeight { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); @@ -527,7 +527,7 @@ public class CXDLPFile : FileFormat public override float LayerHeight { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Layer.RoundHeight(value).ToString(CultureInfo.InvariantCulture); diff --git a/UVtools.Core/FileFormats/CXDLPv1File.cs b/UVtools.Core/FileFormats/CXDLPv1File.cs index ff39d2f..1baec29 100644 --- a/UVtools.Core/FileFormats/CXDLPv1File.cs +++ b/UVtools.Core/FileFormats/CXDLPv1File.cs @@ -362,7 +362,7 @@ public class CXDLPv1File : FileFormat public override float DisplayWidth { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); @@ -380,7 +380,7 @@ public class CXDLPv1File : FileFormat public override float DisplayHeight { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); @@ -398,7 +398,7 @@ public class CXDLPv1File : FileFormat public override float LayerHeight { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Layer.RoundHeight(value).ToString(CultureInfo.InvariantCulture); diff --git a/UVtools.Core/FileFormats/ChituboxFile.cs b/UVtools.Core/FileFormats/ChituboxFile.cs index eb7c6e9..c761add 100644 --- a/UVtools.Core/FileFormats/ChituboxFile.cs +++ b/UVtools.Core/FileFormats/ChituboxFile.cs @@ -34,6 +34,7 @@ public class ChituboxFile : FileFormat public const uint MAGIC_CBDDLP = 0x12FD0019; // 318570521 public const uint MAGIC_CTB = 0x12FD0086; // 318570630 public const uint MAGIC_CTBv4 = 0x12FD0106; // 318570758 + public const uint MAGIC_CTBv4_GKtwo = 0xFF220810; // 4280420368 public const ushort REPEATRGB15MASK = 0x20; public const byte RLE8EncodingLimit = 0x7d; // 125; @@ -50,6 +51,8 @@ public class ChituboxFile : FileFormat private const ushort CTBv4_DISCLAIMER_SIZE = 320; private const ushort CTBv4_RESERVED_SIZE = 384; + public const long PageSize = 4_294_967_296L; // Page-size for layers, limited to 4GB + #endregion #region Sub Classes @@ -612,7 +615,7 @@ public class ChituboxFile : FileFormat /// Gets the layer image length in bytes. /// [FieldOrder(4)] public uint DataSize { get; set; } - [FieldOrder(5)] public uint Unknown1 { get; set; } + [FieldOrder(5)] public uint PageNumber { get; set; } [FieldOrder(6)] public uint TableSize { get; set; } = TABLE_SIZE; [FieldOrder(7)] public uint Unknown3 { get; set; } [FieldOrder(8)] public uint Unknown4 { get; set; } @@ -956,7 +959,7 @@ public class ChituboxFile : FileFormat public override string ToString() { - return $"{nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffSeconds)}: {LightOffSeconds}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(Unknown1)}: {Unknown1}, {nameof(TableSize)}: {TableSize}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; + return $"{nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffSeconds)}: {LightOffSeconds}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(PageNumber)}: {PageNumber}, {nameof(TableSize)}: {TableSize}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; } } @@ -1073,10 +1076,10 @@ public class ChituboxFile : FileFormat new(typeof(ChituboxFile), "photon", "Chitubox Photon"), new(typeof(ChituboxFile), "cbddlp", "Chitubox CBDDLP"), new(typeof(ChituboxFile), "ctb", "Chitubox CTB"), + new(typeof(ChituboxFile), "gktwo.ctb", "Chitubox CTB for UniFormation GKtwo", false), //new(typeof(ChituboxFile), "v2.ctb", "Chitubox CTBv2"), //new(typeof(ChituboxFile), "v3.ctb", "Chitubox CTBv3"), //new(typeof(ChituboxFile), "v4.ctb", "Chitubox CTBv4", false, false), - //new(typeof(ChituboxFile), "encrypted.ctb", "Chitubox encrypted CTB"), }; public override PrintParameterModifier[]? PrintParameterModifiers @@ -1706,7 +1709,11 @@ public class ChituboxFile : FileFormat } public bool IsCbddlpFile => HeaderSettings.Magic == MAGIC_CBDDLP; - public bool IsCtbFile => HeaderSettings.Magic is MAGIC_CTB or MAGIC_CTBv4; + public bool IsCtbFile => HeaderSettings.Magic is MAGIC_CTB or MAGIC_CTBv4 or MAGIC_CTBv4_GKtwo; + + public bool IsMagicValid => IsValidKnownMagic(HeaderSettings.Magic); + + public static bool IsValidKnownMagic(uint magic) => magic is MAGIC_CBDDLP or MAGIC_CTB or MAGIC_CTBv4 or MAGIC_CTBv4_GKtwo; public bool CanHash => IsCbddlpFile && HeaderSettings.Version <= 2; #endregion @@ -1739,7 +1746,7 @@ public class ChituboxFile : FileFormat { using var fs = new BinaryReader(new FileStream(fileFullPath!, FileMode.Open, FileAccess.Read)); var magic = fs.ReadUInt32(); - return magic is MAGIC_CBDDLP or MAGIC_CTB or MAGIC_CTBv4; + return IsValidKnownMagic(magic); } catch (Exception e) { @@ -1753,7 +1760,7 @@ public class ChituboxFile : FileFormat { if (FileEndsWith(".ctb")) { - if (HeaderSettings.Magic is not MAGIC_CTB and not MAGIC_CTBv4) + if (!IsCtbFile) { HeaderSettings.Magic = MAGIC_CTB; } @@ -1773,6 +1780,11 @@ public class ChituboxFile : FileFormat HeaderSettings.Magic = MAGIC_CTBv4; HeaderSettings.Version = 4; } + else if (FileEndsWith(".gktwo.ctb")) + { + HeaderSettings.Magic = MAGIC_CTBv4_GKtwo; + HeaderSettings.Version = 4; + } } else if (FileEndsWith(".cbddlp") || FileEndsWith(".photon")) { @@ -1905,9 +1917,8 @@ public class ChituboxFile : FileFormat } HeaderSettings.LayersDefinitionOffsetAddress = (uint)outputFile.Position; - uint layerDefSize = (uint)Helpers.Serializer.SizeOf(new LayerDef()); - //uint layerDefCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress; - uint layerDataCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress + layerDefSize * LayerCount * HeaderSettings.AntiAliasLevel; + long layerDefSize = Helpers.Serializer.SizeOf(new LayerDef()); + long layerDataCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress + layerDefSize * LayerCount * HeaderSettings.AntiAliasLevel; var layersHash = new Dictionary(); progress.Reset(OperationProgress.StatusEncodeLayers, LayerCount); @@ -1930,7 +1941,7 @@ public class ChituboxFile : FileFormat foreach (var layerIndex in batch) { - if (layerIndex == 0) layerDefSize = (uint)Helpers.Serializer.SizeOf(LayerDefinitions[0, layerIndex]); + if (layerIndex == 0) layerDefSize = Helpers.Serializer.SizeOf(LayerDefinitions[0, layerIndex]); for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) { progress.ThrowIfCancellationRequested(); @@ -1954,14 +1965,16 @@ public class ChituboxFile : FileFormat if (layerDefHash is null) { - layerDef.DataAddress = layerDataCurrentOffset; + layerDef.PageNumber = (uint)(layerDataCurrentOffset / PageSize); + layerDef.DataAddress = (uint)(layerDataCurrentOffset - PageSize * layerDef.PageNumber); outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin); if (HeaderSettings.Version >= 3) { var layerDataEx = new LayerDefEx(layerDef, this[layerIndex]); - layerDataCurrentOffset += (uint)Helpers.Serializer.SizeOf(layerDataEx); - layerDef.DataAddress = layerDataCurrentOffset; + layerDataCurrentOffset += Helpers.Serializer.SizeOf(layerDataEx); + layerDef.PageNumber = (uint)(layerDataCurrentOffset / PageSize); + layerDef.DataAddress = (uint)(layerDataCurrentOffset - PageSize * layerDef.PageNumber); outputFile.WriteSerialize(layerDataEx); } @@ -2001,7 +2014,7 @@ public class ChituboxFile : FileFormat //HeaderSettings = Helpers.Serializer.Deserialize
(InputFile.ReadBytes(Helpers.Serializer.SizeOf(typeof(Header)))); HeaderSettings = Helpers.Deserialize
(inputFile); - if (HeaderSettings.Magic is not MAGIC_CBDDLP and not MAGIC_CTB and not MAGIC_CTBv4) + if (!IsMagicValid) { throw new FileLoadException($"Not a valid PHOTON nor CBDDLP nor CTB file! Magic Value: {HeaderSettings.Magic}", FileFullPath); } @@ -2112,7 +2125,7 @@ public class ChituboxFile : FileFormat if (HeaderSettings.Version >= 3) { - inputFile.SeekDoWorkAndRewind(layerDef.DataAddress - 84, () => + inputFile.SeekDoWorkAndRewind(layerDef.PageNumber * PageSize + layerDef.DataAddress - 84, () => { layerDefinitionsEx![layerIndex] = Helpers.Deserialize(inputFile); layerDefinitionsEx[layerIndex].LayerDef.Parent = this; @@ -2137,7 +2150,7 @@ public class ChituboxFile : FileFormat { progress.ThrowIfCancellationRequested(); - inputFile.Seek(LayerDefinitions[aaIndex, layerIndex].DataAddress, SeekOrigin.Begin); + inputFile.Seek(LayerDefinitions[aaIndex, layerIndex].PageNumber * PageSize + LayerDefinitions[aaIndex, layerIndex].DataAddress, SeekOrigin.Begin); LayerDefinitions[aaIndex, layerIndex].EncodedRle = inputFile.ReadBytes(LayerDefinitions[aaIndex, layerIndex].DataSize); } } @@ -2179,16 +2192,13 @@ public class ChituboxFile : FileFormat outputFile.WriteSerialize(SlicerInfoSettings); } - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + outputFile.Seek(HeaderSettings.LayersDefinitionOffsetAddress, SeekOrigin.Begin); for (byte aaIndex = 0; aaIndex < HeaderSettings.AntiAliasLevel; aaIndex++) { for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { - var layer = this[layerIndex]; - LayerDefinitions![aaIndex, layerIndex].SetFrom(layer); - - outputFile.Seek(layerOffset, SeekOrigin.Begin); - layerOffset += outputFile.WriteSerialize(LayerDefinitions[aaIndex, layerIndex]); + LayerDefinitions![aaIndex, layerIndex].SetFrom(this[layerIndex]); + outputFile.WriteSerialize(LayerDefinitions[aaIndex, layerIndex]); } } @@ -2196,7 +2206,7 @@ public class ChituboxFile : FileFormat { for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) { - outputFile.Seek(LayerDefinitions![0, layerIndex].DataAddress - 84, SeekOrigin.Begin); + outputFile.Seek(LayerDefinitions![0, layerIndex].PageNumber * PageSize + LayerDefinitions![0, layerIndex].DataAddress - 84, SeekOrigin.Begin); outputFile.WriteSerialize(new LayerDefEx(LayerDefinitions[0, layerIndex], this[layerIndex])); } } diff --git a/UVtools.Core/FileFormats/FDGFile.cs b/UVtools.Core/FileFormats/FDGFile.cs index 5bc0203..f2a7db1 100644 --- a/UVtools.Core/FileFormats/FDGFile.cs +++ b/UVtools.Core/FileFormats/FDGFile.cs @@ -435,7 +435,7 @@ public class FDGFile : FileFormat /// Gets the layer image length in bytes. ///
[FieldOrder(4)] public uint DataSize { get; set; } - [FieldOrder(5)] public uint Unknown1 { get; set; } + [FieldOrder(5)] public uint PageNumber { get; set; } [FieldOrder(6)] public uint Unknown2 { get; set; } = 84; [FieldOrder(7)] public uint Unknown3 { get; set; } [FieldOrder(8)] public uint Unknown4 { get; set; } @@ -611,7 +611,7 @@ public class FDGFile : FileFormat public override string ToString() { - return $"{nameof(LayerPositionZ)}: {LayerPositionZ}, {nameof(LayerExposure)}: {LayerExposure}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; + return $"{nameof(LayerPositionZ)}: {LayerPositionZ}, {nameof(LayerExposure)}: {LayerExposure}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(PageNumber)}: {PageNumber}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; } } #endregion @@ -973,8 +973,8 @@ public class FDGFile : FileFormat var layersHash = new Dictionary(); LayersDefinitions = new LayerDef[HeaderSettings.LayerCount]; HeaderSettings.LayersDefinitionOffsetAddress = (uint)outputFile.Position; - uint layerDefCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress; - uint layerDataCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress + (uint)Helpers.Serializer.SizeOf(new LayerDef()) * LayerCount; + long layerDefCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress; + long layerDataCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress + Helpers.Serializer.SizeOf(new LayerDef()) * LayerCount; foreach (var batch in BatchLayersIndexes()) { @@ -1011,7 +1011,8 @@ public class FDGFile : FileFormat if (layerDefHash is null) { - layerDef.DataAddress = layerDataCurrentOffset; + layerDef.PageNumber = (uint)(layerDataCurrentOffset / ChituboxFile.PageSize); + layerDef.DataAddress = (uint)(layerDataCurrentOffset - ChituboxFile.PageSize * layerDef.PageNumber); outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin); layerDataCurrentOffset += outputFile.WriteBytes(layerDef.EncodedRle!); @@ -1101,7 +1102,7 @@ public class FDGFile : FileFormat if (DecodeType == FileDecodeType.Full) { - inputFile.SeekDoWorkAndRewind(layerDef.DataAddress, + inputFile.SeekDoWorkAndRewind(layerDef.PageNumber * ChituboxFile.PageSize + layerDef.DataAddress, () => { layerDef.EncodedRle = inputFile.ReadBytes(layerDef.DataSize); }); } } @@ -1141,13 +1142,11 @@ public class FDGFile : FileFormat outputFile.Write(Encoding.ASCII.GetBytes(HeaderSettings.MachineName), 0, (int)HeaderSettings.MachineNameSize); }*/ - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + outputFile.Seek(HeaderSettings.LayersDefinitionOffsetAddress, SeekOrigin.Begin); for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) { LayersDefinitions[layerIndex].SetFrom(this[layerIndex]); - outputFile.Seek(layerOffset, SeekOrigin.Begin); outputFile.WriteSerialize(LayersDefinitions[layerIndex]); - layerOffset += (uint)Helpers.Serializer.SizeOf(LayersDefinitions[layerIndex]); } } diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 94e3d6b..0b74d6c 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -624,12 +624,13 @@ public abstract class FileFormat : BindableBase, IDisposable, IEquatable= spaceSplit.Length) break; // No more to see - if (!float.TryParse(spaceSplit[i], out var mmX)) continue; - if (!float.TryParse(spaceSplit[++i], out var mmY)) continue; + if (!float.TryParse(spaceSplit[i], NumberStyles.Float, CultureInfo.InvariantCulture, out var mmX)) continue; + if (!float.TryParse(spaceSplit[++i], NumberStyles.Float, CultureInfo.InvariantCulture, out var mmY)) continue; var mmAbsX = Math.Clamp(halfDisplay.Width + mmX, 0, DisplayWidth); diff --git a/UVtools.Core/FileFormats/GR1File.cs b/UVtools.Core/FileFormats/GR1File.cs index c48d64c..b3945b0 100644 --- a/UVtools.Core/FileFormats/GR1File.cs +++ b/UVtools.Core/FileFormats/GR1File.cs @@ -195,7 +195,7 @@ public class GR1File : FileFormat public override float DisplayWidth { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); @@ -213,7 +213,7 @@ public class GR1File : FileFormat public override float DisplayHeight { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); @@ -233,7 +233,7 @@ public class GR1File : FileFormat public override float LayerHeight { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Layer.RoundHeight(value).ToString(CultureInfo.InvariantCulture); diff --git a/UVtools.Core/FileFormats/MDLPFile.cs b/UVtools.Core/FileFormats/MDLPFile.cs index 9045507..975a68a 100644 --- a/UVtools.Core/FileFormats/MDLPFile.cs +++ b/UVtools.Core/FileFormats/MDLPFile.cs @@ -199,7 +199,7 @@ public class MDLPFile : FileFormat public override float DisplayWidth { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayWidthBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); @@ -217,7 +217,7 @@ public class MDLPFile : FileFormat public override float DisplayHeight { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.DisplayHeightBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Math.Round(value, 2).ToString(CultureInfo.InvariantCulture); @@ -236,7 +236,7 @@ public class MDLPFile : FileFormat public override FlipDirection DisplayMirror { get; set; } public override float LayerHeight { - get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray())); + get => float.Parse(Encoding.ASCII.GetString(SlicerInfoSettings.LayerHeightBytes.Where(b => b != 0).ToArray()), CultureInfo.InvariantCulture); set { string str = Layer.RoundHeight(value).ToString(CultureInfo.InvariantCulture); diff --git a/UVtools.Core/FileFormats/PHZFile.cs b/UVtools.Core/FileFormats/PHZFile.cs index 7058188..28db18a 100644 --- a/UVtools.Core/FileFormats/PHZFile.cs +++ b/UVtools.Core/FileFormats/PHZFile.cs @@ -449,7 +449,7 @@ public class PHZFile : FileFormat /// Gets the layer image length in bytes. /// [FieldOrder(4)] public uint DataSize { get; set; } - [FieldOrder(5)] public uint Unknown1 { get; set; } + [FieldOrder(5)] public uint PageNumber { get; set; } [FieldOrder(6)] public uint Unknown2 { get; set; } [FieldOrder(7)] public uint Unknown3 { get; set; } [FieldOrder(8)] public uint Unknown4 { get; set; } @@ -625,7 +625,7 @@ public class PHZFile : FileFormat public override string ToString() { - return $"{nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(Unknown1)}: {Unknown1}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; + return $"{nameof(PositionZ)}: {PositionZ}, {nameof(ExposureTime)}: {ExposureTime}, {nameof(LightOffDelay)}: {LightOffDelay}, {nameof(DataAddress)}: {DataAddress}, {nameof(DataSize)}: {DataSize}, {nameof(PageNumber)}: {PageNumber}, {nameof(Unknown2)}: {Unknown2}, {nameof(Unknown3)}: {Unknown3}, {nameof(Unknown4)}: {Unknown4}"; } } #endregion @@ -999,8 +999,8 @@ public class PHZFile : FileFormat var layersHash = new Dictionary(); LayersDefinitions = new LayerDef[HeaderSettings.LayerCount]; HeaderSettings.LayersDefinitionOffsetAddress = (uint)outputFile.Position; - uint layerDefCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress; - uint layerDataCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress + (uint)Helpers.Serializer.SizeOf(new LayerDef()) * LayerCount; + long layerDefCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress; + long layerDataCurrentOffset = HeaderSettings.LayersDefinitionOffsetAddress + Helpers.Serializer.SizeOf(new LayerDef()) * LayerCount; foreach (var batch in BatchLayersIndexes()) { @@ -1037,7 +1037,8 @@ public class PHZFile : FileFormat if (layerDefHash is null) { - layerDef.DataAddress = layerDataCurrentOffset; + layerDef.PageNumber = (uint)(layerDataCurrentOffset / ChituboxFile.PageSize); + layerDef.DataAddress = (uint) (layerDataCurrentOffset - ChituboxFile.PageSize * layerDef.PageNumber); outputFile.Seek(layerDataCurrentOffset, SeekOrigin.Begin); layerDataCurrentOffset += outputFile.WriteBytes(layerDef.EncodedRle); @@ -1128,7 +1129,7 @@ public class PHZFile : FileFormat if (DecodeType == FileDecodeType.Full) { - inputFile.SeekDoWorkAndRewind(layerDef.DataAddress, + inputFile.SeekDoWorkAndRewind(layerDef.PageNumber * ChituboxFile.PageSize + layerDef.DataAddress, () => { layerDef.EncodedRle = inputFile.ReadBytes(layerDef.DataSize); }); } } @@ -1164,13 +1165,11 @@ public class PHZFile : FileFormat outputFile.Write(Encoding.ASCII.GetBytes(HeaderSettings.MachineName), 0, (int)HeaderSettings.MachineNameSize); }*/ - uint layerOffset = HeaderSettings.LayersDefinitionOffsetAddress; + outputFile.Seek(HeaderSettings.LayersDefinitionOffsetAddress, SeekOrigin.Begin); for (uint layerIndex = 0; layerIndex < HeaderSettings.LayerCount; layerIndex++) { LayersDefinitions[layerIndex].SetFrom(this[layerIndex]); - outputFile.Seek(layerOffset, SeekOrigin.Begin); outputFile.WriteSerialize(LayersDefinitions[layerIndex]); - layerOffset += (uint)Helpers.Serializer.SizeOf(LayersDefinitions[layerIndex]); } } diff --git a/UVtools.Core/GCode/GCodeBuilder.cs b/UVtools.Core/GCode/GCodeBuilder.cs index a6d98f9..68b087e 100644 --- a/UVtools.Core/GCode/GCodeBuilder.cs +++ b/UVtools.Core/GCode/GCodeBuilder.cs @@ -636,7 +636,7 @@ public class GCodeBuilder : BindableBase public void AppendWaitSyncDelay(string timeStr, string? comment = null) { - if (!float.TryParse(timeStr, out var time)) return; + if (!float.TryParse(timeStr, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var time)) return; if (time < 0) return; AppendLineOverrideComment(CommandWaitSyncDelay, comment, timeStr); } @@ -649,7 +649,7 @@ public class GCodeBuilder : BindableBase public void AppendWaitG4(string timeStr, string? comment = null) { - if (!float.TryParse(timeStr, out var time)) return; + if (!float.TryParse(timeStr, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var time)) return; if (time < 0) return; AppendLineOverrideComment(CommandWaitG4, comment, timeStr); } @@ -1041,7 +1041,7 @@ public class GCodeBuilder : BindableBase layerBlock.WaitSyncDelayDetected = true; continue; // Sync movement delay, skip } - var waitTime = float.Parse(match.Groups[1].Value); + var waitTime = float.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); if (layerBlock.PositionZ.HasValue && //layerBlock.LiftHeight.HasValue && @@ -1120,107 +1120,6 @@ public class GCodeBuilder : BindableBase // Propagate values of left over layer layerBlock.SetLayer(); - - /*for (uint layerIndex = 0; layerIndex < slicerFile.LayerCount; layerIndex++) - { - var layer = slicerFile[layerIndex]; - if(layer is null) continue; - var startStr = CommandShowImageM6054.ToStringWithoutComments(GetShowImageString(layerIndex)); - var endStr = CommandShowImageM6054.ToStringWithoutComments(GetShowImageString(layerIndex+1)); - gcode = gcode.Substring(gcode.IndexOf(startStr, StringComparison.InvariantCultureIgnoreCase) + startStr.Length + 1); - var endStrIndex = gcode.IndexOf(endStr, StringComparison.Ordinal); - var stripGcode = endStrIndex > 0 ? gcode[..endStrIndex] : gcode;//.Trim(' ', '\n', '\r', '\t'); - - float liftHeight = 0;// this allow read back no lifts slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLiftHeight, slicerFile.LiftHeight); - float liftSpeed = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLiftSpeed, slicerFile.LiftSpeed); - float retractSpeed = slicerFile.RetractSpeed; - float lightOffDelay = 0; - byte pwm = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLightPWM, slicerFile.LightPWM); - float exposureTime = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomExposureTime, slicerFile.ExposureTime); - var moveG0Regex = Regex.Matches(stripGcode, CommandMoveG0.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(\d+)"), RegexOptions.IgnoreCase); - var moveG1Regex = Regex.Matches(stripGcode, CommandMoveG1.ToStringWithoutComments(@"([+-]?([0-9]*[.])?[0-9]+)", @"(\d+)"), RegexOptions.IgnoreCase); - var waitG4Regex = Regex.Matches(stripGcode, CommandWaitG4.ToStringWithoutComments(@"(\d+)"), RegexOptions.IgnoreCase); - var pwmM106Regex = Regex.Match(stripGcode, CommandTurnLEDM106.ToStringWithoutComments(@"(\d+)"), RegexOptions.IgnoreCase); - var moveRegex = moveG0Regex.Count > 0 ? moveG0Regex : moveG1Regex; - - if (moveRegex.Count >= 1 && moveRegex[0].Success) - { - float liftPosTemp = float.Parse(moveRegex[0].Groups[1].Value, CultureInfo.InvariantCulture); - float liftSpeedTemp = ConvertToMillimetersPerMinute(float.Parse(moveRegex[0].Groups[3].Value, CultureInfo.InvariantCulture)); - - if (moveRegex.Count >= 2 && moveRegex[1].Success) - { - float retractPos = float.Parse(moveRegex[1].Groups[1].Value, CultureInfo.InvariantCulture); - retractSpeed = ConvertToMillimetersPerMinute(float.Parse(moveRegex[1].Groups[3].Value, CultureInfo.InvariantCulture)); - liftSpeed = liftSpeedTemp; - - switch (positionType) - { - case GCodePositioningTypes.Absolute: - liftHeight = Layer.RoundHeight(liftPosTemp - retractPos); - positionZ = retractPos; - break; - case GCodePositioningTypes.Partial: - liftHeight = liftPosTemp; - positionZ = Layer.RoundHeight(positionZ + liftPosTemp + retractPos); - break; - } - } - else - { - if (liftPosTemp - positionZ <= FileFormat.MaximumLayerHeight) - { - switch (positionType) - { - case GCodePositioningTypes.Absolute: - positionZ = liftPosTemp; - break; - case GCodePositioningTypes.Partial: - positionZ = Layer.RoundHeight(positionZ + liftPosTemp); - break; - } - } - } - } - - if (pwmM106Regex.Success) - { - if (_maxLedPower == byte.MaxValue) - { - pwm = byte.Parse(pwmM106Regex.Groups[1].Value); - } - else - { - ushort pwmValue = ushort.Parse(pwmM106Regex.Groups[1].Value); - pwm = (byte)(pwmValue * byte.MaxValue / _maxLedPower); - } - } - - if (waitG4Regex.Count >= 1 && waitG4Regex[0].Success) - { - lightOffDelay = ConvertToSeconds(float.Parse(waitG4Regex[0].Groups[1].Value, CultureInfo.InvariantCulture)); - - if (waitG4Regex.Count >= 2 && waitG4Regex[1].Success) - { - exposureTime = ConvertToSeconds(float.Parse(waitG4Regex[1].Groups[1].Value, CultureInfo.InvariantCulture)); - } - else // Only one match, meaning light off delay is not present and the only time is the cure time - { - exposureTime = lightOffDelay; - lightOffDelay = slicerFile.GetInitialLayerValueOrNormal(layerIndex, slicerFile.BottomLightOffDelay, slicerFile.LightOffDelay); - } - } - - layer.PositionZ = positionZ; - layer.ExposureTime = exposureTime; - layer.LiftHeight = liftHeight; - layer.LiftSpeed = liftSpeed; - layer.RetractSpeed = retractSpeed; - layer.LightOffDelay = lightOffDelay; - layer.LightPWM = pwm; - } - */ - if (rebuildGlobalTable) { slicerFile.UpdateGlobalPropertiesFromLayers(); diff --git a/UVtools.Core/Gerber/Apertures/Aperture.cs b/UVtools.Core/Gerber/Apertures/Aperture.cs index 84aee08..09190a2 100644 --- a/UVtools.Core/Gerber/Apertures/Aperture.cs +++ b/UVtools.Core/Gerber/Apertures/Aperture.cs @@ -8,6 +8,7 @@ using System; using System.Drawing; +using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using Emgu.CV; @@ -64,7 +65,7 @@ public abstract class Aperture case "C": { if (match.Groups.Count < 4) return null; - if (!double.TryParse(match.Groups[3].Value, out var diameter)) return null; + if (!double.TryParse(match.Groups[3].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var diameter)) return null; return new CircleAperture(document, index, diameter); } case "O": @@ -72,8 +73,8 @@ public abstract class Aperture if (match.Groups.Count < 4) return null; var split = match.Groups[3].Value.Split('X', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (split.Length < 2) return null; - if (!float.TryParse(split[0], out var width)) return null; - if (!float.TryParse(split[1], out var height)) return null; + if (!float.TryParse(split[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var width)) return null; + if (!float.TryParse(split[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var height)) return null; return new EllipseAperture(document, index, width, height); } @@ -82,8 +83,8 @@ public abstract class Aperture if (match.Groups.Count < 4) return null; var split = match.Groups[3].Value.Split('X', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (split.Length < 2) return null; - if (!float.TryParse(split[0], out var width)) return null; - if (!float.TryParse(split[1], out var height)) return null; + if (!float.TryParse(split[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var width)) return null; + if (!float.TryParse(split[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var height)) return null; return new RectangleAperture(document, index, width, height); } @@ -92,8 +93,8 @@ public abstract class Aperture if (match.Groups.Count < 4) return null; var split = match.Groups[3].Value.Split('X', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); if (split.Length < 2) return null; - if (!double.TryParse(split[0], out var diameter)) return null; - if (!ushort.TryParse(split[1], out var vertices)) return null; + if (!double.TryParse(split[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var diameter)) return null; + if (!ushort.TryParse(split[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var vertices)) return null; return new PolygonAperture(document, index, diameter, vertices); } @@ -112,7 +113,7 @@ public abstract class Aperture foreach (var primitive in macro) { - primitive.ParseExpressions(document, args); + primitive.ParseExpressions(args); } return new MacroAperture(document, index, macro); diff --git a/UVtools.Core/Gerber/Apertures/MacroAperture.cs b/UVtools.Core/Gerber/Apertures/MacroAperture.cs index 55878d8..c00174b 100644 --- a/UVtools.Core/Gerber/Apertures/MacroAperture.cs +++ b/UVtools.Core/Gerber/Apertures/MacroAperture.cs @@ -34,7 +34,7 @@ public class MacroAperture : Aperture foreach (var primitive in Macro) { //if(primitive.Name == "Comment") continue; - primitive.DrawFlashD3(mat, at, color, lineType); + primitive.DrawFlashD3(mat, at, lineType); } } } \ No newline at end of file diff --git a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs index 1be1578..19d9298 100644 --- a/UVtools.Core/Gerber/Apertures/PoygonAperture.cs +++ b/UVtools.Core/Gerber/Apertures/PoygonAperture.cs @@ -31,8 +31,7 @@ public class PolygonAperture : Aperture Vertices = vertices; } #endregion - - + public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) { mat.DrawPolygon(Vertices, Document.SizeMmToPx(Diameter / 2), Document.PositionMmToPx(at), color, 0, -1, lineType); diff --git a/UVtools.Core/Gerber/GerberDocument.cs b/UVtools.Core/Gerber/GerberDocument.cs index 3e4e04f..32f3702 100644 --- a/UVtools.Core/Gerber/GerberDocument.cs +++ b/UVtools.Core/Gerber/GerberDocument.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Globalization; using System.IO; using System.Text.RegularExpressions; using Emgu.CV; @@ -50,17 +51,20 @@ public class GerberDocument public SizeF XYppmm { get; init; } + /// + /// Gets or sets the X offset for drawings in millimeters + /// + public float OffsetX { get; set; } + + /// + /// Gets or sets the Y offset for drawings in millimeters + /// + public float OffsetY { get; set; } + /// /// Gets the current polarity as . will affect the return value /// - public MCvScalar PolarityColor => - Polarity == GerberPolarityType.Dark - ? !InversePolarity - ? EmguExtensions.WhiteColor - : EmguExtensions.BlackColor - : !InversePolarity - ? EmguExtensions.BlackColor - : EmguExtensions.WhiteColor; + public MCvScalar PolarityColor => GetPolarityColor(Polarity); /// /// Gets or sets to inverse the polarity on drawing @@ -95,7 +99,6 @@ public class GerberDocument double currentX = 0; double currentY = 0; Aperture? currentAperture = null; - Macro? currentMacro = null; var regionPoints = new List(); bool insideRegion = false; string? line; @@ -105,12 +108,6 @@ public class GerberDocument if (line == string.Empty) continue; if (line.StartsWith("M02")) break; - if (currentMacro is not null && line[0] == '%') - { - currentMacro = null; - continue; - } - var accumulatedLine = line; while (!accumulatedLine.Contains('*') && (line = file.ReadLine()) is not null) { @@ -121,13 +118,6 @@ public class GerberDocument line = accumulatedLine; - if (currentMacro is not null) - { - currentMacro.ParsePrimitive(line); - if (line[^1] == '%') currentMacro = null; - continue; - } - if (line.StartsWith("%MO") && line.Length >= MOlength) { if (line[3] == 'M' && line[4] == 'M') document.UnitType = GerberUnitType.Millimeter; @@ -240,10 +230,26 @@ public class GerberDocument if (line.StartsWith("%AM")) { - currentMacro = Macro.Parse(document, line); - if (currentMacro is null) continue; - document.Macros.Add(currentMacro.Name, currentMacro); - //document.Apertures.Add(aperture.Index, aperture); + accumulatedLine = line.Remove(0, 1); + while (!accumulatedLine.Contains('%') && (line = file.ReadLine()) is not null) + { + line = line.Trim(); + if (line == string.Empty) continue; + accumulatedLine += line; + } + line = accumulatedLine[..^2]; + + var split = line.Split(new[]{'*', '%'}, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + var macro = Macro.Parse(document, split[0]); + if (macro is null) continue; + document.Macros.Add(macro.Name, macro); + + for (int i = 1; i < split.Length; i++) + { + macro.ParsePrimitive(split[i]); + } + continue; } @@ -296,7 +302,7 @@ public class GerberDocument var integers = valueStr[..document.CoordinateXIntegers]; var fraction = valueStr[document.CoordinateXIntegers..]; - double.TryParse($"{integers}.{fraction}", out nowX); + double.TryParse($"{integers}.{fraction}", NumberStyles.Float, CultureInfo.InvariantCulture, out nowX); nowX = document.GetMillimeters(nowX); } } @@ -317,7 +323,7 @@ public class GerberDocument var integers = valueStr[..document.CoordinateYIntegers]; var fraction = valueStr[document.CoordinateYIntegers..]; - double.TryParse($"{integers}.{fraction}", out nowY); + double.TryParse($"{integers}.{fraction}", NumberStyles.Float, CultureInfo.InvariantCulture, out nowY); nowY = document.GetMillimeters(nowY); } @@ -366,7 +372,7 @@ public class GerberDocument var integers = valueStr[..document.CoordinateXIntegers]; var fraction = valueStr[document.CoordinateXIntegers..]; - double.TryParse($"{integers}.{fraction}", out xOffset); + double.TryParse($"{integers}.{fraction}", NumberStyles.Float, CultureInfo.InvariantCulture, out xOffset); xOffset = document.GetMillimeters(xOffset); @@ -379,7 +385,7 @@ public class GerberDocument integers = valueStr[..document.CoordinateYIntegers]; fraction = valueStr[document.CoordinateYIntegers..]; - double.TryParse($"{integers}.{fraction}", out yOffset); + double.TryParse($"{integers}.{fraction}", NumberStyles.Float, CultureInfo.InvariantCulture, out yOffset); yOffset = document.GetMillimeters(yOffset); @@ -449,12 +455,14 @@ public class GerberDocument } } - public static GerberDocument ParseAndDraw(OperationPCBExposure.PCBExposureFile file, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntiAliasing = false) + public static GerberDocument ParseAndDraw(OperationPCBExposure.PCBExposureFile file, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, SizeF offset = default, bool enableAntiAliasing = false) { var document = new GerberDocument { SizeMidpointRounding = sizeMidpointRounding, XYppmm = xyPpmm, + OffsetX = offset.Width, + OffsetY = offset.Height, InversePolarity = file.InvertPolarity, SizeScale = file.SizeScale }; @@ -464,12 +472,14 @@ public class GerberDocument return document; } - public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, bool enableAntiAliasing = false) + public static GerberDocument ParseAndDraw(string filePath, Mat mat, SizeF xyPpmm, GerberMidpointRounding sizeMidpointRounding = GerberMidpointRounding.AwayFromZero, SizeF offset = default, bool enableAntiAliasing = false) { var document = new GerberDocument { SizeMidpointRounding = sizeMidpointRounding, - XYppmm = xyPpmm + XYppmm = xyPpmm, + OffsetX = offset.Width, + OffsetY = offset.Height }; ParseAndDraw(document, filePath, mat, enableAntiAliasing); @@ -477,6 +487,18 @@ public class GerberDocument return document; } + public MCvScalar GetPolarityColor(GerberPolarityType polarity) => polarity == GerberPolarityType.Dark + ? !InversePolarity ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor + : !InversePolarity ? EmguExtensions.BlackColor : EmguExtensions.WhiteColor; + + public MCvScalar GetPolarityColor(bool polarity) => polarity + ? !InversePolarity ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor + : !InversePolarity ? EmguExtensions.BlackColor : EmguExtensions.WhiteColor; + + public MCvScalar GetPolarityColor(int polarity) => polarity > 0 + ? !InversePolarity ? EmguExtensions.WhiteColor : EmguExtensions.BlackColor + : !InversePolarity ? EmguExtensions.BlackColor : EmguExtensions.WhiteColor; + public float GetMillimeters(float size) { if (UnitType == GerberUnitType.Millimeter) return size; @@ -502,13 +524,13 @@ public class GerberDocument } public Point PositionMmToPx(PointF atMm) - => new((int)Math.Round(atMm.X * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round(atMm.Y * XYppmm.Height, MidpointRounding.AwayFromZero)); + => new((int)Math.Round((atMm.X + OffsetX) * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round((atMm.Y + OffsetY) * 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)); + => new((int)Math.Round((atXmm + OffsetX) * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round((atYmm + OffsetY) * 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)); + => new((int)Math.Round((atXmm + OffsetX) * XYppmm.Width, MidpointRounding.AwayFromZero), (int)Math.Round((atYmm + OffsetY) * XYppmm.Height, MidpointRounding.AwayFromZero)); public Size SizeMmToPx(SizeF sizeMm) => new((int)Math.Max(1, Math.Round(sizeMm.Width * XYppmm.Width * SizeScale, (MidpointRounding)SizeMidpointRounding)), @@ -557,7 +579,7 @@ public class GerberDocument { parseLine = parseLine.Substring(4, parseLine.Length-5); var split = parseLine.Split(' '); - location = new PointF(float.Parse(split[0]), float.Parse(split[1])); + location = new PointF(float.Parse(split[0], CultureInfo.InvariantCulture), float.Parse(split[1], CultureInfo.InvariantCulture)); continue; } if (parseLine.StartsWith("(segment ") || parseLine.StartsWith("(gr_line ")) @@ -575,8 +597,8 @@ public class GerberDocument if (!widthMatch.Success || widthMatch.Groups.Count < 2) continue; var startXf = new PointF(float.Parse(startMatch.Groups[1].Value), float.Parse(startMatch.Groups[2].Value)); - var endXf = new PointF(float.Parse(endMatch.Groups[1].Value), float.Parse(endMatch.Groups[2].Value)); - var widthf = float.Parse(widthMatch.Groups[1].Value); + var endXf = new PointF(float.Parse(endMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(endMatch.Groups[2].Value, CultureInfo.InvariantCulture)); + var widthf = float.Parse(widthMatch.Groups[1].Value, CultureInfo.InvariantCulture); var startX = new System.Drawing.Point((int)(startXf.X * pixelsPerMm), (int)(startXf.Y * pixelsPerMm)); var endX = new System.Drawing.Point((int)(endXf.X * pixelsPerMm), (int)(endXf.Y * pixelsPerMm)); @@ -598,12 +620,12 @@ public class GerberDocument var drillMatch = Regex.Match(parseLine, @"\(drill\s+(\S+)\)"); - var atf = new PointF(float.Parse(atMatch.Groups[1].Value), float.Parse(atMatch.Groups[2].Value)); - //var sizef = new SizeF(float.Parse(sizeMatch.Groups[1].Value), float.Parse(sizeMatch.Groups[2].Value)); + var atf = new PointF(float.Parse(atMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(atMatch.Groups[2].Value, CultureInfo.InvariantCulture)); + //var sizef = new SizeF(float.Parse(sizeMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(sizeMatch.Groups[2].Value, CultureInfo.InvariantCulture)); var at = new System.Drawing.Point((int)(atf.X * pixelsPerMm), (int)(atf.Y * pixelsPerMm)); if (!drillMatch.Success || drillMatch.Groups.Count < 2) continue; - var drillf = float.Parse(drillMatch.Groups[1].Value); + var drillf = float.Parse(drillMatch.Groups[1].Value, CultureInfo.InvariantCulture); var drill = (int) (drillf * pixelsPerMm / 2); CvInvoke.Circle(mat, at, drill, EmguExtensions.WhiteColor, -1); @@ -626,11 +648,11 @@ public class GerberDocument var widthMatch = Regex.Match(parseLine, @"\(width\s+(\S+)\)"); if (!widthMatch.Success || widthMatch.Groups.Count < 2) continue; - var atf = new PointF(float.Parse(atMatch.Groups[1].Value), float.Parse(atMatch.Groups[2].Value)); + var atf = new PointF(float.Parse(atMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(atMatch.Groups[2].Value, CultureInfo.InvariantCulture)); var at = new System.Drawing.Point((int)(atf.X * pixelsPerMm), (int)(atf.Y * pixelsPerMm)); - var endf = new PointF(float.Parse(endMatch.Groups[1].Value), float.Parse(endMatch.Groups[2].Value)); + var endf = new PointF(float.Parse(endMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(endMatch.Groups[2].Value, CultureInfo.InvariantCulture)); var radius = (int)(Math.Max(Math.Abs(atf.X - endf.X), Math.Abs(atf.Y - endf.Y)) * pixelsPerMm); - var widthf = float.Parse(widthMatch.Groups[1].Value); + var widthf = float.Parse(widthMatch.Groups[1].Value, CultureInfo.InvariantCulture); var width = (int)(widthf * pixelsPerMm); CvInvoke.Circle(mat, at, radius, EmguExtensions.WhiteColor, width); @@ -656,9 +678,9 @@ public class GerberDocument var widthMatch = Regex.Match(parseLine, @"\(width\s+(\S+)\)"); if (!widthMatch.Success || widthMatch.Groups.Count < 2) continue; - var startf = new PointF(float.Parse(startMatch.Groups[1].Value), float.Parse(startMatch.Groups[2].Value)); - var endf = new PointF(float.Parse(endMatch.Groups[1].Value), float.Parse(endMatch.Groups[2].Value)); - var widthf = float.Parse(widthMatch.Groups[1].Value); + var startf = new PointF(float.Parse(startMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(startMatch.Groups[2].Value, CultureInfo.InvariantCulture)); + var endf = new PointF(float.Parse(endMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(endMatch.Groups[2].Value, CultureInfo.InvariantCulture)); + var widthf = float.Parse(widthMatch.Groups[1].Value, CultureInfo.InvariantCulture); var start = new System.Drawing.Point((int)(startf.X * pixelsPerMm), (int)(startf.Y * pixelsPerMm)); var end = new System.Drawing.Point((int)(endf.X * pixelsPerMm), (int)(endf.Y * pixelsPerMm)); @@ -689,8 +711,8 @@ public class GerberDocument var drillMatch = Regex.Match(parseLine, @"\(drill\s+(\S+)\)"); - var atf = new PointF(float.Parse(atMatch.Groups[1].Value), float.Parse(atMatch.Groups[2].Value)); - var sizef = new SizeF(float.Parse(sizeMatch.Groups[1].Value), float.Parse(sizeMatch.Groups[2].Value)); + var atf = new PointF(float.Parse(atMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(atMatch.Groups[2].Value, CultureInfo.InvariantCulture)); + var sizef = new SizeF(float.Parse(sizeMatch.Groups[1].Value, CultureInfo.InvariantCulture), float.Parse(sizeMatch.Groups[2].Value, CultureInfo.InvariantCulture)); var at = new System.Drawing.Point((int)(location.X * pixelsPerMm + atf.X * pixelsPerMm), (int)(location.Y * pixelsPerMm + atf.Y * pixelsPerMm)); @@ -709,7 +731,7 @@ public class GerberDocument if (drillMatch.Success && drillMatch.Groups.Count >= 2) { - var drillf = float.Parse(drillMatch.Groups[1].Value); + var drillf = float.Parse(drillMatch.Groups[1].Value, CultureInfo.InvariantCulture); var drill = (int)(drillf * pixelsPerMm / 2); drillPoints.Add(new KeyValuePair(at, drill)); diff --git a/UVtools.Core/Gerber/Macro.cs b/UVtools.Core/Gerber/Macro.cs index aa1b721..0658958 100644 --- a/UVtools.Core/Gerber/Macro.cs +++ b/UVtools.Core/Gerber/Macro.cs @@ -42,6 +42,8 @@ public class Macro : IReadOnlyList { line = line.TrimEnd('%', '*'); + if(line.Length == 0) return; + // 0 Comment: A comment string if (line[0] == '0') { @@ -108,7 +110,7 @@ public class Macro : IReadOnlyList public static Macro? Parse(GerberDocument document, string line) { - var match = Regex.Match(line, @"\%AM(\S+)\*"); + var match = Regex.Match(line, @"%?AM([a-zA-Z\d]+)\*?"); if (!match.Success || match.Groups.Count < 2) return null; return new Macro(document, match.Groups[1].Value); diff --git a/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs b/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs index 8b3c3b6..2debcb9 100644 --- a/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CenterLinePrimitive.cs @@ -6,12 +6,13 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Data; using System.Drawing; +using System.Globalization; using System.Text.RegularExpressions; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; using UVtools.Core.Extensions; namespace UVtools.Core.Gerber.Primitives; @@ -87,24 +88,17 @@ public class CenterLinePrimitive : Primitive } - public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, LineType lineType = LineType.EightConnected) { if (!IsParsed) return; if (Width <= 0 || Height <= 0) return; - if (Exposure == 0) color = EmguExtensions.BlackColor; - else if(color.V0 == 0) color = EmguExtensions.WhiteColor; - - var halfWidth = Width / 2; - 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); + mat.DrawRotatedRectangle(Document.SizeMmToPx(Width, Height), Document.PositionMmToPx(at.X + CenterX, at.Y + CenterY), Document.GetPolarityColor(Exposure), (int) Rotation, -1, lineType); } - public override void ParseExpressions(GerberDocument document, params string[] args) + public override void ParseExpressions(params string[] args) { - string csharpExp, result; + string csharpExp; float num; var exp = new DataTable(); @@ -112,57 +106,57 @@ public class CenterLinePrimitive : Primitive else { csharpExp = string.Format(Regex.Replace(ExposureExpression, @"\$(\d+)", "{$1}"), args); - result = exp.Compute(csharpExp, null).ToString()!; - if (byte.TryParse(result, out var val)) Exposure = val; + var temp = exp.Compute(csharpExp, null); + if(temp is not DBNull) Exposure = Convert.ToByte(temp); } - if (float.TryParse(WidthExpression, out num)) Width = num; + if (float.TryParse(WidthExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Width = num; else { csharpExp = Regex.Replace(WidthExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out var val)) Width = val; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Width = Convert.ToSingle(temp); } - Width = document.GetMillimeters(Width); + Width = Document.GetMillimeters(Width); - if (float.TryParse(HeightExpression, out num)) Height = num; + if (float.TryParse(HeightExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Height = num; else { csharpExp = Regex.Replace(HeightExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out var val)) Height = val; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Height = Convert.ToSingle(temp); } - Height = document.GetMillimeters(Height); + Height = Document.GetMillimeters(Height); - if (float.TryParse(CenterXExpression, out num)) CenterX = num; + if (float.TryParse(CenterXExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) CenterX = num; else { csharpExp = Regex.Replace(CenterXExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) CenterX = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) CenterX = Convert.ToSingle(temp); } - CenterX = document.GetMillimeters(CenterX); + CenterX = Document.GetMillimeters(CenterX); - if (float.TryParse(CenterYExpression, out num)) CenterY = num; + if (float.TryParse(CenterYExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) CenterY = num; else { csharpExp = Regex.Replace(CenterYExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) CenterY = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) CenterY = Convert.ToSingle(temp); } - CenterY = document.GetMillimeters(CenterY); + CenterY = Document.GetMillimeters(CenterY); - if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; + if (float.TryParse(RotationExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Rotation = (short)num; else { csharpExp = Regex.Replace(RotationExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) Rotation = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Rotation = Convert.ToSingle(temp); } IsParsed = true; diff --git a/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs b/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs index a06b627..aff6a50 100644 --- a/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CirclePrimitive.cs @@ -6,13 +6,13 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Data; using System.Drawing; +using System.Globalization; using System.Text.RegularExpressions; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; -using UVtools.Core.Extensions; namespace UVtools.Core.Gerber.Primitives; @@ -78,23 +78,19 @@ public class CirclePrimitive : Primitive RotationExpression = rotationExpression; } - public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, - LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, LineType lineType = LineType.EightConnected) { if (!IsParsed) return; if (Diameter <= 0) return; - if (Exposure == 0) color = EmguExtensions.BlackColor; - else if (color.V0 == 0) color = EmguExtensions.WhiteColor; - CvInvoke.Circle(mat, Document.PositionMmToPx(at.X + CenterX, at.Y + CenterY), - Document.SizeMmToPx(Diameter / 2), color, -1, lineType); + Document.SizeMmToPx(Diameter / 2), Document.GetPolarityColor(Exposure), -1, lineType); } - public override void ParseExpressions(GerberDocument document, params string[] args) + public override void ParseExpressions(params string[] args) { - string csharpExp, result; + string csharpExp; float num; var exp = new DataTable(); @@ -102,48 +98,48 @@ public class CirclePrimitive : Primitive else { csharpExp = string.Format(Regex.Replace(ExposureExpression, @"\$(\d+)", "{$1}"), args); - result = exp.Compute(csharpExp, null).ToString()!; - if (byte.TryParse(result, out var val)) Exposure = val; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Exposure = Convert.ToByte(temp); } - if (float.TryParse(DiameterExpression, out num)) Diameter = num; + if (float.TryParse(DiameterExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Diameter = num; else { csharpExp = Regex.Replace(DiameterExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) Diameter = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Diameter = Convert.ToSingle(temp); } - Diameter = document.GetMillimeters(Diameter); + Diameter = Document.GetMillimeters(Diameter); - if (float.TryParse(CenterXExpression, out num)) CenterX = num; + if (float.TryParse(CenterXExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) CenterX = num; else { csharpExp = Regex.Replace(CenterXExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) CenterX = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) CenterX = Convert.ToSingle(temp); } - CenterX = document.GetMillimeters(CenterX); + CenterX = Document.GetMillimeters(CenterX); - if (float.TryParse(CenterYExpression, out num)) CenterY = num; + if (float.TryParse(CenterYExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) CenterY = num; else { csharpExp = Regex.Replace(CenterYExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) CenterY = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) CenterY = Convert.ToSingle(temp); } - CenterY = document.GetMillimeters(CenterY); + CenterY = Document.GetMillimeters(CenterY); - if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; + if (float.TryParse(RotationExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Rotation = (short)num; else { csharpExp = Regex.Replace(RotationExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) Rotation = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Rotation = Convert.ToSingle(temp); } IsParsed = true; diff --git a/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs b/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs index a6539ec..35cdc8a 100644 --- a/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/CommentPrimitive.cs @@ -9,7 +9,6 @@ using System.Drawing; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; namespace UVtools.Core.Gerber.Primitives; @@ -23,6 +22,7 @@ public class CommentPrimitive : Primitive #region Constants public const byte Code = 0; #endregion + #region Properties public override string Name => "Comment"; @@ -45,12 +45,12 @@ public class CommentPrimitive : Primitive } - public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, LineType lineType = LineType.EightConnected) { } - public override void ParseExpressions(GerberDocument document, params string[] args) + public override void ParseExpressions(params string[] args) { } } \ No newline at end of file diff --git a/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs b/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs index 1b88e33..21af733 100644 --- a/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/OutlinePrimitive.cs @@ -10,13 +10,12 @@ using System; using System.Collections.Generic; using System.Data; using System.Drawing; +using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; using Emgu.CV.Util; -using UVtools.Core.Extensions; namespace UVtools.Core.Gerber.Primitives; @@ -76,12 +75,14 @@ public class OutlinePrimitive : Primitive } - public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, LineType lineType = LineType.EightConnected) { if (Coordinates.Length < 3) return; - if (Exposure == 0) color = EmguExtensions.BlackColor; - else if (color.V0 == 0) color = EmguExtensions.WhiteColor; + if (Rotation != 0) + { + throw new NotImplementedException($"{Name} primitive with code {Code} have a rotation value of {Rotation} which is not implemented. Open a issue regarding this problem and provide a sample file to be able to implement rotation correctly on this primitive."); + } var points = new List(); for (int i = 0; i < Coordinates.Length-1; i++) @@ -92,12 +93,12 @@ public class OutlinePrimitive : Primitive } using var vec = new VectorOfPoint(points.ToArray()); - CvInvoke.FillPoly(mat, vec, color, lineType); + CvInvoke.FillPoly(mat, vec, Document.GetPolarityColor(Exposure), lineType); } - public override void ParseExpressions(GerberDocument document, params string[] args) + public override void ParseExpressions(params string[] args) { - string csharpExp, result; + string csharpExp; float num; var exp = new DataTable(); @@ -105,19 +106,19 @@ public class OutlinePrimitive : Primitive else { csharpExp = string.Format(Regex.Replace(ExposureExpression, @"\$(\d+)", "{$1}"), args); - result = exp.Compute(csharpExp, null).ToString()!; - if (byte.TryParse(result, out var val)) Exposure = val; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Exposure = Convert.ToByte(temp); } float? x = null; var coordinates = new List(); foreach (var coordinate in CoordinatesExpression) { - if (!float.TryParse(coordinate, out num)) + if (!float.TryParse(coordinate, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) { csharpExp = string.Format(Regex.Replace(coordinate, @"\$(\d+)", "{$1}"), args); - result = exp.Compute(csharpExp, null).ToString()!; - float.TryParse(result, out num); + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) num = Convert.ToSingle(temp); } if (x is null) @@ -126,20 +127,20 @@ public class OutlinePrimitive : Primitive } else { - coordinates.Add(document.GetMillimeters(new PointF(x.Value, num))); + coordinates.Add(Document.GetMillimeters(new PointF(x.Value, num))); x = null; } } Coordinates = coordinates.ToArray(); - if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; + if (float.TryParse(RotationExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Rotation = (short)num; else { csharpExp = Regex.Replace(RotationExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) Rotation = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Rotation = Convert.ToSingle(temp); } IsParsed = true; diff --git a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs index 1566b75..27d9ac6 100644 --- a/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/PolygonPrimitive.cs @@ -6,12 +6,13 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Data; using System.Drawing; +using System.Globalization; using System.Text.RegularExpressions; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; using UVtools.Core.Extensions; namespace UVtools.Core.Gerber.Primitives; @@ -84,25 +85,20 @@ public class PolygonPrimitive : Primitive RotationExpression = rotationExpression; } - public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, - LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, LineType lineType = LineType.EightConnected) { if (!IsParsed) return; if (Diameter <= 0) return; - if (Exposure == 0) color = EmguExtensions.BlackColor; - else if (color.V0 == 0) color = EmguExtensions.WhiteColor; - - mat.DrawPolygon(VerticesCount, Document.SizeMmToPx(Diameter / 2), Document.PositionMmToPx(at.X + CenterX, at.Y + CenterY), - color, 0, -1, lineType); + Document.GetPolarityColor(Exposure), Rotation, -1, lineType); } - public override void ParseExpressions(GerberDocument document, params string[] args) + public override void ParseExpressions(params string[] args) { - string csharpExp, result; + string csharpExp; float num; var exp = new DataTable(); @@ -110,8 +106,8 @@ public class PolygonPrimitive : Primitive else { csharpExp = string.Format(Regex.Replace(ExposureExpression, @"\$(\d+)", "{$1}"), args); - result = exp.Compute(csharpExp, null).ToString()!; - if (byte.TryParse(result, out var val)) Exposure = val; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Exposure = Convert.ToByte(temp); } if (byte.TryParse(DiameterExpression, out var vertices)) VerticesCount = vertices; @@ -119,47 +115,47 @@ public class PolygonPrimitive : Primitive { csharpExp = Regex.Replace(DiameterExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (byte.TryParse(result, out var vertices1)) VerticesCount = vertices1; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) VerticesCount = Convert.ToByte(temp); } - if (float.TryParse(CenterXExpression, out num)) CenterX = num; + if (float.TryParse(CenterXExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) CenterX = num; else { csharpExp = Regex.Replace(CenterXExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) CenterX = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) CenterX = Convert.ToSingle(temp); } - CenterX = document.GetMillimeters(CenterX); + CenterX = Document.GetMillimeters(CenterX); - if (float.TryParse(CenterYExpression, out num)) CenterY = num; + if (float.TryParse(CenterYExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) CenterY = num; else { csharpExp = Regex.Replace(CenterYExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) CenterY = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) CenterY = Convert.ToSingle(temp); } - CenterY = document.GetMillimeters(CenterY); + CenterY = Document.GetMillimeters(CenterY); - if (float.TryParse(DiameterExpression, out num)) Diameter = num; + if (float.TryParse(DiameterExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Diameter = num; else { csharpExp = Regex.Replace(DiameterExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) Diameter = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Diameter = Convert.ToSingle(temp); } - Diameter = document.GetMillimeters(Diameter); + Diameter = Document.GetMillimeters(Diameter); - if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; + if (float.TryParse(RotationExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Rotation = (short)num; else { csharpExp = Regex.Replace(RotationExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) Rotation = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Rotation = Convert.ToSingle(temp); } IsParsed = true; diff --git a/UVtools.Core/Gerber/Primitives/Primitive.cs b/UVtools.Core/Gerber/Primitives/Primitive.cs index 75a20a7..bedb066 100644 --- a/UVtools.Core/Gerber/Primitives/Primitive.cs +++ b/UVtools.Core/Gerber/Primitives/Primitive.cs @@ -9,7 +9,6 @@ using System.Drawing; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; namespace UVtools.Core.Gerber.Primitives; @@ -31,9 +30,9 @@ public abstract class Primitive Document = document; } - public abstract void DrawFlashD3(Mat mat, PointF at, MCvScalar color, LineType lineType = LineType.EightConnected); + public abstract void DrawFlashD3(Mat mat, PointF at, LineType lineType = LineType.EightConnected); - public abstract void ParseExpressions(GerberDocument document, params string[] args); + public abstract void ParseExpressions(params string[] args); public virtual Primitive Clone() => (Primitive)MemberwiseClone(); } \ No newline at end of file diff --git a/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs b/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs index 624e4ad..7b608a5 100644 --- a/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs +++ b/UVtools.Core/Gerber/Primitives/VectorLinePrimitive.cs @@ -6,12 +6,13 @@ * of this license document, but changing it is not allowed. */ +using System; using System.Data; using System.Drawing; +using System.Globalization; using System.Text.RegularExpressions; using Emgu.CV; using Emgu.CV.CvEnum; -using Emgu.CV.Structure; using UVtools.Core.Extensions; namespace UVtools.Core.Gerber.Primitives; @@ -96,24 +97,25 @@ public class VectorLinePrimitive : Primitive RotationExpression = rotationExpression; } - public override void DrawFlashD3(Mat mat, PointF at, MCvScalar color, - LineType lineType = LineType.EightConnected) + public override void DrawFlashD3(Mat mat, PointF at, LineType lineType = LineType.EightConnected) { if (!IsParsed) return; if (LineWidth <= 0) return; - if (Exposure == 0) color = EmguExtensions.BlackColor; - else if (color.V0 == 0) color = EmguExtensions.WhiteColor; + if (Rotation != 0) + { + throw new NotImplementedException($"{Name} primitive with code {Code} have a rotation value of {Rotation} which is not implemented. Open a issue regarding this problem and provide a sample file to be able to implement rotation correctly on this primitive."); + } 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.Line(mat, pt1, pt2, Document.GetPolarityColor(Exposure), EmguExtensions.CorrectThickness(Document.SizeMmToPxOverride(LineWidth, Document.XYppmm.Height)), lineType); //CvInvoke.Rectangle(mat, rectangle, color, -1, lineType); } - public override void ParseExpressions(GerberDocument document, params string[] args) + public override void ParseExpressions(params string[] args) { - string csharpExp, result; + string csharpExp; float num; var exp = new DataTable(); @@ -121,67 +123,67 @@ public class VectorLinePrimitive : Primitive else { csharpExp = string.Format(Regex.Replace(ExposureExpression, @"\$(\d+)", "{$1}"), args); - result = exp.Compute(csharpExp, null).ToString()!; - if (byte.TryParse(result, out var val)) Exposure = val; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Exposure = Convert.ToByte(temp); } - if (float.TryParse(LineWidthExpression, out num)) LineWidth = num; + if (float.TryParse(LineWidthExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) LineWidth = num; else { csharpExp = Regex.Replace(LineWidthExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out var val)) LineWidth = val; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) LineWidth = Convert.ToSingle(temp); } - LineWidth = document.GetMillimeters(LineWidth); + LineWidth = Document.GetMillimeters(LineWidth); - if (float.TryParse(StartXExpression, out num)) StartX = num; + if (float.TryParse(StartXExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) StartX = num; else { csharpExp = Regex.Replace(StartXExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) StartX = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) StartX = Convert.ToSingle(temp); } - StartX = document.GetMillimeters(StartX); + StartX = Document.GetMillimeters(StartX); - if (float.TryParse(EndXExpression, out num)) EndX = num; + if (float.TryParse(EndXExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) EndX = num; else { csharpExp = Regex.Replace(EndXExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) EndX = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) EndX = Convert.ToSingle(temp); } - EndX = document.GetMillimeters(EndX); + EndX = Document.GetMillimeters(EndX); - if (float.TryParse(StartYExpression, out num)) StartY = num; + if (float.TryParse(StartYExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) StartY = num; else { csharpExp = Regex.Replace(StartYExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) StartY = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) StartY = Convert.ToSingle(temp); } - StartY = document.GetMillimeters(StartY); + StartY = Document.GetMillimeters(StartY); - if (float.TryParse(EndYExpression, out num)) EndY = num; + if (float.TryParse(EndYExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) EndY = num; else { csharpExp = Regex.Replace(EndYExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) EndY = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) EndY = Convert.ToSingle(temp); } - EndY = document.GetMillimeters(EndY); + EndY = Document.GetMillimeters(EndY); - if (float.TryParse(RotationExpression, out num)) Rotation = (short)num; + if (float.TryParse(RotationExpression, NumberStyles.Float, CultureInfo.InvariantCulture, out num)) Rotation = (short)num; else { csharpExp = Regex.Replace(RotationExpression, @"\$(\d+)", "{$1}"); csharpExp = string.Format(csharpExp, args); - result = exp.Compute(csharpExp, null).ToString()!; - if (float.TryParse(result, out num)) Rotation = num; + var temp = exp.Compute(csharpExp, null); + if (temp is not DBNull) Rotation = Convert.ToSingle(temp); } IsParsed = true; diff --git a/UVtools.Core/Network/RemotePrinter.cs b/UVtools.Core/Network/RemotePrinter.cs index 4d63d76..1407114 100644 --- a/UVtools.Core/Network/RemotePrinter.cs +++ b/UVtools.Core/Network/RemotePrinter.cs @@ -70,15 +70,7 @@ public class RemotePrinter : BindableBase set => RaiseAndSetIfChanged(ref _port, value); } - public string HostUrl - { - get - { - var result = $"http://{_host}"; - if (_port > 0) result += $":{_port}"; - return result; - } - } + public string HostUrl => _port > 0 ? $"{_host}:{_port}" :_host ; /// /// Gets or sets the compatible extensions with this device. @@ -206,6 +198,4 @@ public class RemotePrinter : BindableBase } #endregion - - } \ No newline at end of file diff --git a/UVtools.Core/Network/RemotePrinterRequest.cs b/UVtools.Core/Network/RemotePrinterRequest.cs index 1936944..9db4b51 100644 --- a/UVtools.Core/Network/RemotePrinterRequest.cs +++ b/UVtools.Core/Network/RemotePrinterRequest.cs @@ -8,7 +8,13 @@ using System; using System.ComponentModel; +using System.IO; +using System.Net; using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using UVtools.Core.Extensions; using UVtools.Core.Objects; @@ -22,14 +28,20 @@ public class RemotePrinterRequest : BindableBase public enum RequestMethod : byte { - [Description("GET")] + [Description("HTTP GET")] GET, - [Description("POST")] + [Description("HTTP POST")] POST, - [Description("PUT")] - PUT + [Description("HTTP PUT")] + PUT, + + [Description("SOCK TCP")] + TCP, + + [Description("SOCK UDP")] + UDP } public enum RequestType : byte @@ -86,7 +98,8 @@ public class RemotePrinterRequest : BindableBase { value = value.Trim(); if (value[0] == '/') value = value.Remove(0, 1); - if(value[^1] == '/') value = value.Remove(value.Length-1, 1); + if (value[^1] == '/') value = value[..^2]; + value = value.Trim(); } if(!RaiseAndSetIfChanged(ref _path, value)) return; RaisePropertyChanged(nameof(IsValid)); @@ -110,7 +123,6 @@ public class RemotePrinterRequest : BindableBase #endregion - #region Methods /// @@ -120,20 +132,55 @@ public class RemotePrinterRequest : BindableBase /// public string GetFormattedPath(params object?[] parameters) => string.Format(_path, parameters); - public async Task SendRequest(string host, ushort port = 0, OperationProgress? progress = null, string? param1 = null, HttpContent? content = null) + public async Task SendRequest(string host, ushort port = 0, OperationProgress? progress = null, string? param1 = null, string? uploadFilePath = null) { string url = $"http://{host}"; if (port > 0) url += $":{port}"; - progress ??= new(); + progress ??= new OperationProgress(); progress.Title = $"Sending {_method} request to: {url}"; progress.ItemName = "Megabyte(s)"; progress.CanCancel = true; - - if (!string.IsNullOrWhiteSpace(_path)) url += $"/{GetFormattedPath(param1)}"; + if(_path == "{0}") return new HttpResponseMessage(HttpStatusCode.OK); + + string? formattedPathWithParameter = null; + if (!string.IsNullOrWhiteSpace(_path)) + { + if (_path.StartsWith("<$") && _path.EndsWith("$>")) + { + var newPath = _path[2..^2].Trim(); + var split = newPath.Split('>', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (split.Length < 3) throw new ArgumentException($"Path '{_path}' is malformed", nameof(Path)); + + var firstRequest = Clone(); + firstRequest.Path = split[0]; + using var response = await firstRequest.SendRequest(host, port, progress, param1); + if (!response.IsSuccessStatusCode) return response; + + var content = await response.Content.ReadAsStringAsync(progress.Token); + if (content is null) throw new InvalidDataException($"The request to {firstRequest.Path} returned no data"); + + if (!string.IsNullOrWhiteSpace(param1)) split[1] = string.Format(split[1], Regex.Escape(param1)); + + var match = Regex.Match(content, split[1]); + if (!match.Success || match.Groups.Count < 2) throw new InvalidDataException($"Unable to parse any data from the content.\nRegex: {split[1]}\nContent: {content}"); + + for (int i = 1; i < match.Groups.Count; i++) + { + split[2] = split[2].Replace($"{{#{i}}}", match.Groups[i].Value); + } + + formattedPathWithParameter = string.Format(split[2], param1); + } + else + { + formattedPathWithParameter = GetFormattedPath(param1); + } + } + + if (!string.IsNullOrWhiteSpace(_path) && !string.IsNullOrWhiteSpace(formattedPathWithParameter)) url += $"/{formattedPathWithParameter}"; - switch (_method) { case RequestMethod.GET: @@ -143,26 +190,133 @@ public class RemotePrinterRequest : BindableBase } case RequestMethod.POST: { - using var response = await NetworkExtensions.HttpClient.PostAsync(url, content, progress.Token); + if (string.IsNullOrWhiteSpace(uploadFilePath)) return await NetworkExtensions.HttpClient.PostAsync(url, null, progress.Token); + + await using var stream = File.OpenRead(uploadFilePath); + using var httpContent = new StreamContent(stream); + + + progress.ItemCount = (uint) (stream.Length / 1048576); + bool isCopying = true; + try + { + var task = new Task(() => + { + while (isCopying) + { + progress.ProcessedItems = (uint) (stream.Position / 1048576); + Thread.Sleep(200); + } + }); + } + catch (Exception) + { + // ignored + } + + using var response = await NetworkExtensions.HttpClient.PostAsync(url, httpContent, progress.Token); + isCopying = false; return response; + } case RequestMethod.PUT: { - using var response = await NetworkExtensions.HttpClient.PutAsync(url, content, progress.Token); + if (string.IsNullOrWhiteSpace(uploadFilePath)) return await NetworkExtensions.HttpClient.PutAsync(url, null, progress.Token); + + await using var stream = File.OpenRead(uploadFilePath); + using var httpContent = new StreamContent(stream); + + + progress.ItemCount = (uint)(stream.Length / 1048576); + bool isCopying = true; + try + { + var task = new Task(() => + { + while (isCopying) + { + progress.ProcessedItems = (uint)(stream.Position / 1048576); + Thread.Sleep(200); + } + }); + } + catch (Exception) + { + // ignored + } + + using var response = await NetworkExtensions.HttpClient.PutAsync(url, httpContent, progress.Token); + isCopying = false; return response; - } + } + case RequestMethod.TCP: + case RequestMethod.UDP: + { + using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, _method == RequestMethod.TCP ? ProtocolType.Tcp : ProtocolType.Udp) + { + ReceiveTimeout = 10 + }; + var ipAd = IPAddress.Parse(host); + await socket.ConnectAsync(ipAd, port); + + if (string.IsNullOrWhiteSpace(uploadFilePath)) + { + if (string.IsNullOrWhiteSpace(formattedPathWithParameter)) + { + await socket.SendAsync(ReadOnlyMemory.Empty, SocketFlags.None); + } + else + { + var requestBytes = Encoding.UTF8.GetBytes(formattedPathWithParameter); + var bytesSent = 0; + while (bytesSent < requestBytes.Length) + { + bytesSent += await socket.SendAsync(requestBytes.AsMemory(bytesSent), SocketFlags.None); + } + } + } + else // file upload + { + if (string.IsNullOrWhiteSpace(formattedPathWithParameter)) + { + await socket.SendFileAsync(uploadFilePath, progress.Token); + } + else + { + var preRequestBytes = Encoding.UTF8.GetBytes(formattedPathWithParameter); + await socket.SendFileAsync(uploadFilePath, preRequestBytes, ReadOnlyMemory.Empty, TransmitFileOptions.UseDefaultWorkerThread, progress.Token); + } + } + + var responseBytes = new byte[1024]; + var sb = new StringBuilder(); + int bytesReceived; // Receiving 0 bytes means EOF has been reached + while ((bytesReceived = await socket.ReceiveAsync(responseBytes, SocketFlags.None, progress.Token)) > 0) + { + var result = Encoding.UTF8.GetString(responseBytes, 0, bytesReceived); + sb.Append(result); + if (result.EndsWith(",end")) break; + } + + socket.Shutdown(SocketShutdown.Both); + + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(sb.ToString()), + }; + } default: throw new ArgumentOutOfRangeException(nameof(Method)); } } public async Task SendRequest(RemotePrinter remotePrinter, - OperationProgress? progress = null, string? param1 = null, HttpContent? content = null) - => await SendRequest(remotePrinter.Host, remotePrinter.Port, progress, param1, content); + OperationProgress? progress = null, string? param1 = null, string? uploadFilePath = null) + => await SendRequest(remotePrinter.Host, remotePrinter.Port, progress, param1, uploadFilePath); public RemotePrinterRequest Clone() { - return (MemberwiseClone() as RemotePrinterRequest)!; + return (RemotePrinterRequest)MemberwiseClone(); } public override string ToString() diff --git a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs index 4e3906c..22fbe03 100644 --- a/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs +++ b/UVtools.Core/Operations/OperationCalibrateExposureFinder.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Drawing; +using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -577,7 +578,7 @@ public sealed class OperationCalibrateExposureFinder : Operation foreach (var mmStr in split) { if (string.IsNullOrWhiteSpace(mmStr)) continue; - if (!decimal.TryParse(mmStr, out var mm)) continue; + if (!decimal.TryParse(mmStr, NumberStyles.Float, CultureInfo.InvariantCulture, out var mm)) continue; var mmPx = (int)(mm * Ppmm); if (mmPx is <= 0 or > 500) continue; if(holes.Contains(mmPx)) continue; @@ -675,7 +676,7 @@ public sealed class OperationCalibrateExposureFinder : Operation foreach (var mmStr in split) { if (string.IsNullOrWhiteSpace(mmStr)) continue; - if (!decimal.TryParse(mmStr, out var mm)) continue; + if (!decimal.TryParse(mmStr, NumberStyles.Float, CultureInfo.InvariantCulture, out var mm)) continue; var mmPx = (int)(mm * Yppmm); if (mmPx is <= 0 or > 500) continue; if (bars.Contains(mmPx)) continue; @@ -1058,8 +1059,8 @@ public sealed class OperationCalibrateExposureFinder : Operation if (string.IsNullOrWhiteSpace(splitDiameterThickness[0]) || string.IsNullOrWhiteSpace(splitDiameterThickness[1])) continue; - if (!decimal.TryParse(splitDiameterThickness[0], out var diameterMm)) continue; - if (!decimal.TryParse(splitDiameterThickness[1], out var thicknessMm)) continue; + if (!decimal.TryParse(splitDiameterThickness[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var diameterMm)) continue; + if (!decimal.TryParse(splitDiameterThickness[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var thicknessMm)) continue; var diameter = (int)(diameterMm * Ppmm); if (diameterMm is <= 0 or > 500) continue; var thickness = (int)(thicknessMm * Ppmm); diff --git a/UVtools.Core/Operations/OperationPCBExposure.cs b/UVtools.Core/Operations/OperationPCBExposure.cs index 0529085..25fcdb6 100644 --- a/UVtools.Core/Operations/OperationPCBExposure.cs +++ b/UVtools.Core/Operations/OperationPCBExposure.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Drawing; using System.IO; using System.IO.Compression; using System.Linq; @@ -60,6 +61,7 @@ public class OperationPCBExposure : Operation } } #endregion + #region Static public static string[] ValidExtensions => new[] @@ -83,10 +85,13 @@ public class OperationPCBExposure : Operation private decimal _layerHeight; private decimal _exposureTime; private GerberMidpointRounding _sizeMidpointRounding = GerberMidpointRounding.AwayFromZero; + private decimal _offsetX; + private decimal _offsetY; private bool _mirror; private bool _invertColor; private bool _enableAntiAliasing; + #endregion #region Overrides @@ -188,6 +193,18 @@ public class OperationPCBExposure : Operation set => RaiseAndSetIfChanged(ref _sizeMidpointRounding, value); } + public decimal OffsetX + { + get => _offsetX; + set => RaiseAndSetIfChanged(ref _offsetX, value); + } + + public decimal OffsetY + { + get => _offsetY; + set => RaiseAndSetIfChanged(ref _offsetY, value); + } + public bool Mirror { get => _mirror; @@ -212,7 +229,7 @@ public class OperationPCBExposure : Operation protected bool Equals(OperationPCBExposure other) { - 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; + return _files.Equals(other._files) && _mergeFiles == other._mergeFiles && _layerHeight == other._layerHeight && _exposureTime == other._exposureTime && _sizeMidpointRounding == other._sizeMidpointRounding && _offsetX == other._offsetX && _offsetY == other._offsetY && _mirror == other._mirror && _invertColor == other._invertColor && _enableAntiAliasing == other._enableAntiAliasing; } public override bool Equals(object? obj) @@ -225,7 +242,18 @@ public class OperationPCBExposure : Operation public override int GetHashCode() { - return HashCode.Combine(_files, _mergeFiles, _layerHeight, _exposureTime, (int) _sizeMidpointRounding, _mirror, _invertColor, _enableAntiAliasing); + var hashCode = new HashCode(); + hashCode.Add(_files); + hashCode.Add(_mergeFiles); + hashCode.Add(_layerHeight); + hashCode.Add(_exposureTime); + hashCode.Add((int) _sizeMidpointRounding); + hashCode.Add(_offsetX); + hashCode.Add(_offsetY); + hashCode.Add(_mirror); + hashCode.Add(_invertColor); + hashCode.Add(_enableAntiAliasing); + return hashCode.ToHashCode(); } #endregion @@ -286,7 +314,7 @@ public class OperationPCBExposure : Operation { if (!file.Exists) return; - GerberDocument.ParseAndDraw(file, mat, SlicerFile.Ppmm, _sizeMidpointRounding, _enableAntiAliasing); + GerberDocument.ParseAndDraw(file, mat, SlicerFile.Ppmm, _sizeMidpointRounding, new SizeF((float)OffsetX, (float)OffsetY), _enableAntiAliasing); //var boundingRectangle = CvInvoke.BoundingRectangle(mat); //var cropped = mat.Roi(new Size(boundingRectangle.Right, boundingRectangle.Bottom)); diff --git a/UVtools.Core/PixelEditor/PixelDrawing.cs b/UVtools.Core/PixelEditor/PixelDrawing.cs index 6782a09..4dc4a8a 100644 --- a/UVtools.Core/PixelEditor/PixelDrawing.cs +++ b/UVtools.Core/PixelEditor/PixelDrawing.cs @@ -39,8 +39,6 @@ public class PixelDrawing : PixelOperation public override PixelOperationType OperationType => PixelOperationType.Drawing; - public static BrushShapeType[] BrushShapeTypes => (BrushShapeType[])Enum.GetValues(typeof(BrushShapeType)); - public BrushShapeType BrushShape { get => _brushShape; @@ -84,7 +82,7 @@ public class PixelDrawing : PixelOperation public decimal RemovePixelBrightnessPercent => Math.Round(_removePixelBrightness * 100M / 255M, 2); - public bool IsAdd { get; } + public bool IsAdd { get; } public byte Brightness => IsAdd ? _pixelBrightness : _removePixelBrightness; diff --git a/UVtools.Core/Printer/Machine.cs b/UVtools.Core/Printer/Machine.cs index 24d6a50..2eadd17 100644 --- a/UVtools.Core/Printer/Machine.cs +++ b/UVtools.Core/Printer/Machine.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Globalization; using System.IO; using System.Text; using UVtools.Core.Extensions; @@ -390,19 +391,19 @@ namespace UVtools.Core.Printer if (key.StartsWith("display_width")) { - if (!float.TryParse(value, out var displayWidth)) continue; + if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var displayWidth)) continue; machine.DisplayWidth = displayWidth; } if (key.StartsWith("display_height")) { - if (!float.TryParse(value, out var displayHeight)) continue; + if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var displayHeight)) continue; machine.DisplayHeight = displayHeight; } if (key.StartsWith("max_print_height")) { - if (!float.TryParse(value, out var machineZ)) continue; + if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var machineZ)) continue; machine.MachineZ = machineZ; } diff --git a/UVtools.Core/Slicer/LinAlgUtils.cs b/UVtools.Core/Slicer/LinAlgUtils.cs index 2e764c7..af07d6c 100644 --- a/UVtools.Core/Slicer/LinAlgUtils.cs +++ b/UVtools.Core/Slicer/LinAlgUtils.cs @@ -90,6 +90,6 @@ public static class LinAlgUtils // I'm sure there's a better way than converting it to a string and then back to a float, // but that's what I've got right now, so that's what I'm doing. //var strVal = rawVal.ToString($"0.{precision}"); - //return float.Parse(strVal, CultureInfo.InvariantCulture.NumberFormat); + //return float.Parse(strVal, CultureInfo.InvariantCulture); }*/ } \ No newline at end of file diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index b09b529..1d48b58 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ https://github.com/sn4k3/UVtools https://github.com/sn4k3/UVtools MSLA/DLP, file analysis, calibration, repair, conversion and manipulation - 3.8.3 + 3.9.0 Copyright © 2020 PTRTECH UVtools.png AnyCPU;x64 @@ -80,7 +80,7 @@ - + diff --git a/UVtools.Installer/Code/HeatGeneratedFileList.wxs b/UVtools.Installer/Code/HeatGeneratedFileList.wxs index fa4f7cb..fee2c02 100644 --- a/UVtools.Installer/Code/HeatGeneratedFileList.wxs +++ b/UVtools.Installer/Code/HeatGeneratedFileList.wxs @@ -1229,11 +1229,11 @@ - - + + - - + + @@ -1989,8 +1989,8 @@ - - + + diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index e5bd116..12926b3 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -212,32 +212,26 @@ public class App : Application if (string.IsNullOrWhiteSpace(Program.Args[1])) return; if (string.IsNullOrWhiteSpace(Program.Args[2])) return; var category = Program.Args[1]; - var message = $"{Program.Args[2]}\nCategory: {category}"; + var bugDescription = $"{Program.Args[2]}\nCategory: {category}"; + var system = string.Empty; if (Program.Args.Length >= 4 && !string.IsNullOrWhiteSpace(Program.Args[3])) { - message += $"\nFile: {Program.Args[3]}"; + bugDescription += $"\nFile: {Program.Args[3]}"; } - - var bugReportMessageMk = $"# Report\n```\n{message}\n```"; + bugDescription += $"\n\nMachine date time: {DateTime.Now}\n UTC date time: {DateTime.UtcNow}"; try { - var append = $"\n\n# System\n{AboutWindow.GetEssentialInformationStatic()}"; - message += append; - bugReportMessageMk += append; + system = AboutWindow.GetEssentialInformationStatic(); } catch { // ignored } - - var append2 = $"\n\nMachine date time: {DateTime.Now}\n UTC date time: {DateTime.UtcNow}"; - message += append2; - bugReportMessageMk += $"{append2}\n\n# Additional information and Workflow\nComplete with additional information and the workflow that caused this crash with the file as well."; - + try { - Current?.Clipboard?.SetTextAsync(bugReportMessageMk); + Current?.Clipboard?.SetTextAsync(bugDescription); } catch { @@ -245,15 +239,15 @@ public class App : Application } - using var reader = new StringReader(message); + using var reader = new StringReader(bugDescription); desktop.MainWindow = new MessageWindow($"{About.SoftwareWithVersion} - Crash report", "fa-regular fa-frown", - $"{About.Software} crashed due an unexpected {category.ToLowerInvariant()} error.\nYou can report this error if you find necessary.\nFind more details below:\n", - message, + $"{About.Software} crashed due an unexpected {category.ToLowerInvariant()} error.\nYou can report this error if you find necessary.\nFind more details below:\n", + bugDescription, new[] { - MessageWindow.CreateLinkButtonAction("Report", "fa-solid fa-bug", $"https://github.com/sn4k3/UVtools/issues/new?assignees=sn4k3&labels=&template=bug_report.md&title={HttpUtility.UrlEncode($"[Crash] {reader.ReadLine()}")}&body={HttpUtility.UrlEncode("")}", () => Current?.Clipboard?.SetTextAsync(bugReportMessageMk)), - MessageWindow.CreateLinkButtonAction("Help", "fa-solid fa-question", "https://github.com/sn4k3/UVtools/discussions/categories/q-a", () => Current?.Clipboard?.SetTextAsync(bugReportMessageMk)), + MessageWindow.CreateLinkButtonAction("Report", "fa-solid fa-bug", $"https://github.com/sn4k3/UVtools/issues/new?template=bug_report_form.yml&title={HttpUtility.UrlEncode($"[Crash] {reader.ReadLine()}")}&system={HttpUtility.UrlEncode(system)}&bug_description={HttpUtility.UrlEncode($"```\n{bugDescription}\n```")}", () => Current?.Clipboard?.SetTextAsync($"```\n{bugDescription}\n```")), + MessageWindow.CreateLinkButtonAction("Help", "fa-solid fa-question", "https://github.com/sn4k3/UVtools/discussions/categories/q-a", () => Current?.Clipboard?.SetTextAsync($"```\n{bugDescription}\n```")), MessageWindow.CreateButtonAction("Restart", "fa-solid fa-redo-alt", () => SystemAware.StartThisApplication()), MessageWindow.CreateCloseButton("fa-solid fa-sign-out-alt") }); @@ -315,10 +309,25 @@ public class App : Application { try { - var ldd = SystemAware.GetProcessOutput("bash", $"-c \"ldd '{Path.Combine(ApplicationPath, "libcvextern.so")}' | grep not\""); - if (!string.IsNullOrWhiteSpace(ldd)) + var result = SystemAware.GetProcessOutput("bash", $"-c \"ldd '{Path.Combine(ApplicationPath, "libcvextern.so")}' | grep not\""); + if (!string.IsNullOrWhiteSpace(result)) { - message += $"Missing dependencies:\n{ldd}\n"; + message += $"Missing dependencies:\n{result}\n"; + } + } + catch (Exception e) + { + Debug.WriteLine(e); + } + } + else if (OperatingSystem.IsMacOS()) + { + try + { + var result = SystemAware.GetProcessOutput("otool", $"-L '{Path.Combine(ApplicationPath, "libcvextern.dylib")}'"); + if (!string.IsNullOrWhiteSpace(result)) + { + message += $"Dependencies:\n{result}\n"; } } catch (Exception e) diff --git a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml index 35553f0..45edb74 100644 --- a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml @@ -96,7 +96,7 @@ - + + + + + + + + + - - - - + + diff --git a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs index 4bbc62f..d6bf287 100644 --- a/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs +++ b/UVtools.WPF/Controls/Tools/ToolPCBExposureControl.axaml.cs @@ -25,6 +25,7 @@ namespace UVtools.WPF.Controls.Tools private Bitmap _previewImage; private OperationPCBExposure.PCBExposureFile _selectedFile; + private bool _cropPreview = true; public Bitmap PreviewImage { @@ -42,6 +43,16 @@ namespace UVtools.WPF.Controls.Tools } } + public bool CropPreview + { + get => _cropPreview; + set + { + if(!RaiseAndSetIfChanged(ref _cropPreview, value)) return; + UpdatePreview(); + } + } + public ToolPCBExposureControl() { BaseOperation = new OperationPCBExposure(SlicerFile); @@ -55,7 +66,7 @@ namespace UVtools.WPF.Controls.Tools Operation.AddFiles(args.Data.GetFileNames()?.ToArray() ?? Array.Empty()); }); - _timer = new Timer(20) + _timer = new Timer(50) { AutoReset = false }; @@ -115,10 +126,18 @@ namespace UVtools.WPF.Controls.Tools if (!OperationPCBExposure.ValidExtensions.Any(extension => _selectedFile.IsExtension(extension)) || !_selectedFile.Exists) return; var file = (OperationPCBExposure.PCBExposureFile)_selectedFile.Clone(); file.InvertPolarity = false; - using var mat = Operation.GetMat(file); - using var matCropped = mat.CropByBounds(20); _previewImage?.Dispose(); - PreviewImage = matCropped.ToBitmap(); + using var mat = Operation.GetMat(file); + + if (_cropPreview) + { + using var matCropped = mat.CropByBounds(20); + PreviewImage = matCropped.ToBitmap(); + } + else + { + PreviewImage = mat.ToBitmap(); + } } catch (Exception e) { diff --git a/UVtools.WPF/MainWindow.PixelEditor.cs b/UVtools.WPF/MainWindow.PixelEditor.cs index cb15bf4..c46e6e1 100644 --- a/UVtools.WPF/MainWindow.PixelEditor.cs +++ b/UVtools.WPF/MainWindow.PixelEditor.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Drawing; using System.Linq; using System.Threading.Tasks; @@ -22,7 +21,6 @@ using Emgu.CV.CvEnum; using Emgu.CV.Structure; using MessageBox.Avalonia.Enums; using SkiaSharp; -using UVtools.Core; using UVtools.Core.Extensions; using UVtools.Core.Layers; using UVtools.Core.PixelEditor; diff --git a/UVtools.WPF/MainWindow.axaml b/UVtools.WPF/MainWindow.axaml index 42528e1..2c43184 100644 --- a/UVtools.WPF/MainWindow.axaml +++ b/UVtools.WPF/MainWindow.axaml @@ -216,9 +216,8 @@ + Command="{Binding MenuHelpReportIssueClicked}" + i:MenuItem.Icon="fa-solid fa-bug"/> - + - + @@ -892,6 +890,7 @@ Width="Auto" /> + Items="{Binding DrawingPixelDrawing.BrushShape, Converter={StaticResource EnumToCollectionConverter}, Mode=OneTime}" + SelectedItem="{Binding DrawingPixelDrawing.BrushShape, Converter={StaticResource FromValueDescriptionToEnumConverter}}"/> + response = await remotePrinter.RequestUploadFile.SendRequest(remotePrinter, Progress, SlicerFile.Filename, SlicerFile.FileFullPath); + if (!response.IsSuccessStatusCode) { - while (isCopying) - { - Progress.ProcessedItems = (uint)(stream.Position / 1000000); - Thread.Sleep(200); - } - }); + await this.MessageBoxError(response.ToString(), "Send to printer"); + } } - catch (Exception) + catch (OperationCanceledException) { } + catch (Exception ex) { - // ignored + await this.MessageBoxError(ex.Message, "Send to printer"); } - - response = await remotePrinter.RequestUploadFile.SendRequest(remotePrinter, Progress, SlicerFile.Filename, httpContent); - isCopying = false; - if (!response.IsSuccessStatusCode) - { - await this.MessageBoxError(response.ToString(), "Send to printer"); - } - } - catch (OperationCanceledException) { } - catch (Exception ex) - { - await this.MessageBoxError(ex.Message, "Send to printer"); } - if ( - response is not null && response.IsSuccessStatusCode && - startPrint) + if (startPrint && (!remotePrinter.RequestUploadFile.IsValid || (response is not null && response.IsSuccessStatusCode))) { - response.Dispose(); + response?.Dispose(); Progress.Title = "Waiting 2 seconds..."; await Task.Delay(2000); try @@ -578,6 +570,7 @@ public partial class MainWindow : WindowEx { await this.MessageBoxError(response.ToString(), "Unable to send the print command"); } + response.Dispose(); /*else { await this.MessageBoxInfo(response.ToString(), "Print send command report"); @@ -592,6 +585,7 @@ public partial class MainWindow : WindowEx } else if (menuItem.Tag is MappedProcess process) { + ShowProgressWindow($"Sending: {SlicerFile.Filename} to {path}"); Progress.ItemName = "Waiting for completion"; try { @@ -606,51 +600,8 @@ public partial class MainWindow : WindowEx } else { - /*var copyResult = await Task.Factory.StartNew(() => - { - try - { - var fileDest = Path.Combine(path, SlicerFile.Filename); - //File.Copy(SlicerFile.FileFullPath, $"{Path.Combine(path, SlicerFile.Filename)}", true); - var buffer = new byte[1024 * 1024]; // 1MB buffer - - using var source = File.OpenRead(SlicerFile.FileFullPath); - using var dest = new FileStream(fileDest, FileMode.Create, FileAccess.Write); - //long totalBytes = 0; - //int currentBlockSize; - - Progress.Reset("Megabyte(s)", (uint)(source.Length / 1000000)); - var copyProgress = new Progress(copiedBytes => Progress.ProcessedItems = (uint)(copiedBytes / 1000000)); - source.CopyToAsync(dest, 0, copyProgress, Progress.Token).ConfigureAwait(false); - - /*while ((currentBlockSize = source.Read(buffer)) > 0) - { - totalBytes += currentBlockSize; - - dest.Write(buffer, 0, currentBlockSize); - - if (Progress.Token.IsCancellationRequested) - { - // Delete dest file here - dest.Dispose(); - File.Delete(fileDest); - return false; - } - - Progress.ProcessedItems = (uint)(totalBytes / 1000000); - }*/ - - /* return true; - } - catch (OperationCanceledException) { } - catch (Exception exception) - { - Dispatcher.UIThread.InvokeAsync(async () => - await this.MessageBoxError(exception.ToString(), "Unable to copy the file")); - } - - return false; - });*/ + ShowProgressWindow($"Sending: {SlicerFile.Filename} to {path}"); + Progress.ItemName = "Sending"; bool copyResult = false; var fileDest = Path.Combine(path, SlicerFile.Filename); @@ -659,8 +610,8 @@ public partial class MainWindow : WindowEx await using var source = File.OpenRead(SlicerFile.FileFullPath); await using var dest = new FileStream(fileDest, FileMode.Create, FileAccess.Write); - Progress.Reset("Megabyte(s)", (uint)(source.Length / 1000000)); - var copyProgress = new Progress(copiedBytes => Progress.ProcessedItems = (uint)(copiedBytes / 1000000)); + Progress.Reset("Megabyte(s)", (uint)(source.Length / 1048576)); + var copyProgress = new Progress(copiedBytes => Progress.ProcessedItems = (uint)(copiedBytes / 1048576)); await source.CopyToAsync(dest, copyProgress, Progress.Token); copyResult = true; @@ -688,6 +639,7 @@ public partial class MainWindow : WindowEx $"File '{SlicerFile.Filename}' has copied successfully into {removableDrive.Name}\n" + $"Do you want to eject the {removableDrive.Name} drive now?", "Copied ok, eject the drive?") == ButtonResult.Yes) { + Progress.ResetAll($"Ejecting {removableDrive.Name}"); var ejectResult = await Task.Factory.StartNew(() => { @@ -1147,7 +1099,23 @@ public partial class MainWindow : WindowEx SystemAware.StartProcess(UserSettings.SettingsFolder); } - private async void MenuHelpMaterialManagerClicked() + public void MenuHelpReportIssueClicked() + { + var system = string.Empty; + + try + { + system = AboutWindow.GetEssentialInformationStatic(); + } + catch + { + // ignored + } + + SystemAware.OpenBrowser($"https://github.com/sn4k3/UVtools/issues/new?template=bug_report_form.yml&title=%5BBug%5D+&system={HttpUtility.UrlEncode(system)}"); + } + + public async void MenuHelpMaterialManagerClicked() { await new MaterialManagerWindow().ShowDialog(this); } diff --git a/UVtools.WPF/Program.cs b/UVtools.WPF/Program.cs index d6dee91..6840668 100644 --- a/UVtools.WPF/Program.cs +++ b/UVtools.WPF/Program.cs @@ -1,11 +1,13 @@ using System; using System.Diagnostics; using System.Globalization; +using System.Text; using System.Threading.Tasks; using Avalonia; using Projektanker.Icons.Avalonia; using Projektanker.Icons.Avalonia.FontAwesome; using Projektanker.Icons.Avalonia.MaterialDesign; +using UVtools.Core; using UVtools.Core.SystemOS; namespace UVtools.WPF; @@ -40,9 +42,8 @@ public static class Program [STAThread] public static void Main(string[] args) { - CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture; - + CultureInfo.DefaultThreadCurrentUICulture = + CultureInfo.DefaultThreadCurrentCulture = CoreSettings.OptimalCultureInfo; ProgramStartupTime = Stopwatch.StartNew(); Args = args; try @@ -66,7 +67,8 @@ public static class Program IsCrashReport = true; break; } - } + } + /*using var mat = CvInvoke.Imread(@"D:\layer0.png", ImreadModes.Grayscale); var contours = mat.FindContours(out var hierarchy, RetrType.Tree, ChainApproxMethod.ChainApproxTc89Kcos); diff --git a/UVtools.WPF/Structures/AppVersionChecker.cs b/UVtools.WPF/Structures/AppVersionChecker.cs index a7f4afa..1fd13dd 100644 --- a/UVtools.WPF/Structures/AppVersionChecker.cs +++ b/UVtools.WPF/Structures/AppVersionChecker.cs @@ -274,6 +274,10 @@ public class AppVersionChecker : BindableBase await stream.WriteLineAsync("else"); await stream.WriteLineAsync($" cp -fR '{extractDirectoryPath}/'* '{macOSAppPath}'"); await stream.WriteLineAsync("fi"); + + await stream.WriteLineAsync(); + await stream.WriteLineAsync("echo '- Force codesign to allow the app to run directly'"); + await stream.WriteLineAsync($"codesign --force --deep --sign - '{macOSAppPath}'"); } else // Linux generic and macOS generic { @@ -305,10 +309,11 @@ public class AppVersionChecker : BindableBase } else // Can move { - await stream.WriteLineAsync("echo '- Attempt to rename directory to the new version name'"); await stream.WriteLineAsync($"if [ -d '{applicationPath}' ]; then"); + await stream.WriteLineAsync("echo '- Could not rename directory to the new version name, a directory with same name already exists'"); await stream.WriteLineAsync($" cd '{App.ApplicationPath}'"); await stream.WriteLineAsync("else"); + await stream.WriteLineAsync($"echo '- Attempt to rename directory {new DirectoryInfo(App.ApplicationPath).Name} to {new DirectoryInfo(applicationPath)}'"); await stream.WriteLineAsync($" mv -f '{App.ApplicationPath}' '{applicationPath}'"); await stream.WriteLineAsync($" cd '{applicationPath}'"); await stream.WriteLineAsync("fi"); @@ -318,11 +323,11 @@ public class AppVersionChecker : BindableBase if (App.SlicerFile is not null && App.SlicerFile.FileFullPath != Path.Combine(App.ApplicationPath, About.DemoFile)) { // Reload last file - await stream.WriteLineAsync($"nohup bash 'UVtools.sh' '{App.SlicerFile.FileFullPath}' &> /dev/null &"); + await stream.WriteLineAsync($"nohup ./UVtools.sh '{App.SlicerFile.FileFullPath}' &> /dev/null &"); } else { - await stream.WriteLineAsync("nohup bash 'UVtools.sh' &> /dev/null &"); + await stream.WriteLineAsync("nohup ./UVtools.sh &> /dev/null &"); } await stream.WriteLineAsync("disown"); await stream.WriteLineAsync(); @@ -334,10 +339,12 @@ public class AppVersionChecker : BindableBase //await stream.WriteLine("exit"); } + //SystemAware.StartProcess("chmod", $"775 \"{upgradeScriptFilePath}\"", true); + if (Program.IsDebug) { Console.WriteLine("In debug mode, will not auto-upgrade, please run:"); - Console.WriteLine($"bash \"{upgradeScriptFilePath}\""); + Console.WriteLine($"bash '{upgradeScriptFilePath}'"); } else { diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 7a4a941..a669366 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ LICENSE https://github.com/sn4k3/UVtools Git - 3.8.3 + 3.9.0 AnyCPU;x64 UVtools.png README.md @@ -44,9 +44,9 @@ - - - + + + diff --git a/UVtools.WPF/UVtools.sh b/UVtools.WPF/UVtools.sh index bd30eee..b023bd0 100644 --- a/UVtools.WPF/UVtools.sh +++ b/UVtools.WPF/UVtools.sh @@ -1,21 +1,22 @@ #!/bin/bash path="$(dirname "$0")" arch="$(uname -m)" # x86_64 or arm64 -args="$@" + +testcmd() { command -v "$1" &> /dev/null; } run_app_normal(){ - "$path/UVtools" $args + exec "$path/UVtools" $@ } run_app_dotnet(){ - if [ -z "$(command -v dotnet)" ]; then + if ! testcmd dotnet; then echo "$(uname) $arch requires dotnet in order to run." echo 'Please use the auto installer script to install all the dependencies.' exit -1 fi if [ -f "$path/UVtools.dll" ]; then - dotnet "$path/UVtools.dll" $args + exec dotnet "$path/UVtools.dll" $@ else echo "Error: UVtools.dll not found." exit -1 @@ -23,10 +24,8 @@ run_app_dotnet(){ } if [ "${OSTYPE:0:6}" == "darwin" ]; then - if [ "$arch" == "arm64" ]; then - run_app_dotnet - elif [ -d "$path/../../../UVtools.app" ]; then - open -n "$path/../../../UVtools.app" --args $args + if [ -d "$path/../../../UVtools.app" ]; then + open -n "$path/../../../UVtools.app" --args $@ else run_app_normal fi diff --git a/UVtools.WPF/UserSettings.cs b/UVtools.WPF/UserSettings.cs index 1d5da7f..00e638e 100644 --- a/UVtools.WPF/UserSettings.cs +++ b/UVtools.WPF/UserSettings.cs @@ -1760,6 +1760,28 @@ public sealed class UserSettings : BindableBase RequestPrintStatus = new (RemotePrinterRequest.RequestType.PrintStatus, RemotePrinterRequest.RequestMethod.GET, "job/list"), RequestPrinterInfo = new (RemotePrinterRequest.RequestType.PrinterInfo, RemotePrinterRequest.RequestMethod.GET, "setting/printerInfo"), }, + new RemotePrinter("0.0.0.0", 6000, "AnyCubic") + { + // https://github.com/rudetrooper/Octoprint-Chituboard/issues/4#issuecomment-961264287 + // https://github.com/adamoutler/anycubic-python + // https://github.com/adamoutler/Pi-Zero-W-Smart-USB-Flash-Drive/tree/main/src/home/pi/usb_share/scripts + CompatibleExtensions = "pws;pw0;pwx;dlp;dl2p;pwmo;pwma;pwms;pwmx;pmx2;pwmb;pwsq;pm3;pm3m;pm3r;pwc", + RequestUploadFile = new (RemotePrinterRequest.RequestType.UploadFile, RemotePrinterRequest.RequestMethod.TCP), + RequestPrintFile = new (RemotePrinterRequest.RequestType.PrintFile, RemotePrinterRequest.RequestMethod.TCP, @"<$getfile>{0}\/(\d+\.[\da-zA-Z]+),>goprint,{#1}$>"), + RequestDeleteFile = new (RemotePrinterRequest.RequestType.DeleteFile, RemotePrinterRequest.RequestMethod.TCP, @"<$getfile>{0}\/(\d+\.[\da-zA-Z]+),>delfile,{#1}$>"), + RequestPausePrint = new (RemotePrinterRequest.RequestType.PausePrint, RemotePrinterRequest.RequestMethod.TCP, "gopause"), + RequestResumePrint = new (RemotePrinterRequest.RequestType.ResumePrint, RemotePrinterRequest.RequestMethod.TCP, "goresume"), + RequestStopPrint = new (RemotePrinterRequest.RequestType.StopPrint, RemotePrinterRequest.RequestMethod.TCP, "gostop"), + RequestGetFiles = new (RemotePrinterRequest.RequestType.GetFiles, RemotePrinterRequest.RequestMethod.TCP, "getfile"), + RequestPrintStatus = new (RemotePrinterRequest.RequestType.PrintStatus, RemotePrinterRequest.RequestMethod.TCP, "getstatus"), + RequestPrinterInfo = new (RemotePrinterRequest.RequestType.PrinterInfo, RemotePrinterRequest.RequestMethod.TCP, "sysinfo"), + // getmode + // getwifi - displays the current wifi network name. + // gethistory - gets the history and print settings of previous prints. + // delhistory - deletes printing history. + // getPreview1 - returns a list of dimensions used for the print. + // getPreview2 - returns a binary preview image of the print. + }, /*new RemotePrinter("0.0.0.0", 40454, "Creality Halot") { CompatibleExtensions = "cxdlp", diff --git a/UVtools.WPF/Windows/AboutWindow.axaml b/UVtools.WPF/Windows/AboutWindow.axaml index 1e93377..8b8f6a4 100644 --- a/UVtools.WPF/Windows/AboutWindow.axaml +++ b/UVtools.WPF/Windows/AboutWindow.axaml @@ -135,7 +135,8 @@ diff --git a/UVtools.WPF/Windows/AboutWindow.axaml.cs b/UVtools.WPF/Windows/AboutWindow.axaml.cs index d5cfa41..283ac6c 100644 --- a/UVtools.WPF/Windows/AboutWindow.axaml.cs +++ b/UVtools.WPF/Windows/AboutWindow.axaml.cs @@ -1,11 +1,11 @@ using System; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; using Avalonia; using Avalonia.Markup.Xaml; using Emgu.CV; using UVtools.Core; +using UVtools.Core.Extensions; using UVtools.Core.SystemOS; using UVtools.WPF.Controls; @@ -36,10 +36,11 @@ public class AboutWindow : WindowEx { var sb = new StringBuilder(); var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var assembliesLengthPad = assemblies.Length.DigitCount(); for (var i = 0; i < assemblies.Length; i++) { var assembly = assemblies[i].GetName(); - sb.AppendLine($"{(i + 1).ToString().PadLeft(assemblies.Length.ToString().Length, '0')}: {assembly.Name}, Version={assembly.Version}"); + sb.AppendLine($"{(i + 1).ToString().PadLeft(assembliesLengthPad, '0')}: {assembly.Name}, Version={assembly.Version}"); } return sb.ToString(); @@ -60,11 +61,6 @@ public class AboutWindow : WindowEx try { return typeof(Mat).Assembly.GetName().Version!.ToString(3); - /*var match = Regex.Match(CvInvoke.BuildInformation, @"(?:Version control:\s*)(\S*)"); - if (!match.Success) return "Not found!"; - var index = match.Groups[1].Value.LastIndexOf('-'); - if (index < 0) return match.Groups[1].Value; - return match.Groups[1].Value[..index];*/ } catch { @@ -109,13 +105,13 @@ public class AboutWindow : WindowEx // ReSharper disable once ConditionIsAlwaysTrueOrFalse var onScreen = Screens.ScreenFromVisual(App.MainWindow is not null ? App.MainWindow : this); var screen = Screens.All[i]; - result.AppendLine($"{i+1}: {screen.Bounds.Width} x {screen.Bounds.Height} @ {screen.PixelDensity * 100}%" + - (screen.Primary ? " (Primary)" : string.Empty) + - (onScreen == screen ? " (On this)" : string.Empty) + result.AppendLine($"{i+1}: {screen.Bounds.Width} x {screen.Bounds.Height} @ {Math.Round(screen.PixelDensity * 100, 2)}%" + + (screen.Primary ? " (Primary)" : null) + + (onScreen == screen ? " (On this)" : null) ); result.AppendLine($" WA: {screen.WorkingArea.Width} x {screen.WorkingArea.Height} UA: {Math.Round(screen.WorkingArea.Width / screen.PixelDensity)} x {Math.Round(screen.WorkingArea.Height / screen.PixelDensity)}"); } - return result.ToString().TrimEnd(); + return result.ToString(); } } @@ -131,8 +127,6 @@ public class AboutWindow : WindowEx AvaloniaXamlLoader.Load(this); } - public void OpenLicense() => SystemAware.OpenBrowser(About.LicenseUrl); - public static string GetEssentialInformationStatic() { var message = new StringBuilder(); diff --git a/UVtools.WPF/Windows/SettingsWindow.axaml b/UVtools.WPF/Windows/SettingsWindow.axaml index 51129dd..cf92aa2 100644 --- a/UVtools.WPF/Windows/SettingsWindow.axaml +++ b/UVtools.WPF/Windows/SettingsWindow.axaml @@ -2115,7 +2115,7 @@ Width="Auto" /> + Width="130"> [path/to/file2] [path/to/file3] [...] + # Opens and loads specific file(s) with UVtools + --cmd-help + # Display UVtoolsCmd help message + --cmd, -c [option(s)] + # Redirect a command to UVtoolsCmd + --appimage-extract + # Unpack this AppImage into a local sub-directory [currently named 'squashfs-root'] + --appimage-help + # Show available AppImage options +" +} + +if [ "$1" == "--help" -o "$1" == "-h" ]; then + help + exit $? +fi + +if [ "$1" == "--cmd-help" ]; then + exec 'UVtoolsCmd' --help + exit $? +fi + +if [ "$1" == "--cmd" -o "$1" == "-c" ]; then + if [ "$#" -lt 2 ]; then + echo 'UVtoolsCmd requires at least one parameter' + exec 'UVtoolsCmd' --help + exit $? + fi + + shift + exec 'UVtoolsCmd' $@ + exit $? +fi + +exec 'UVtools' $@ \ No newline at end of file diff --git a/build/platforms/osx-arm64/arm64_version_string.inc b/build/platforms/osx-arm64/arm64_version_string.inc new file mode 100644 index 0000000..81da704 --- /dev/null +++ b/build/platforms/osx-arm64/arm64_version_string.inc @@ -0,0 +1,87 @@ +"\n" +"General configuration for OpenCV 4.6.0 =====================================\n" +" Version control: 3.4.2-7879-g1ca4fc084b\n" +"\n" +" Platform:\n" +" Timestamp: 2022-11-29T05:23:11Z\n" +" Host: Darwin 22.1.0 arm64\n" +" CMake: 3.25.0\n" +" CMake generator: Unix Makefiles\n" +" CMake build tool: /usr/bin/make\n" +" Configuration: Release\n" +"\n" +" CPU/HW features:\n" +" Baseline: NEON FP16\n" +"\n" +" C/C++:\n" +" Built as dynamic libs?: NO\n" +" C++ standard: 11\n" +" C++ Compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ (ver 14.0.0.14000029)\n" +" C++ flags (Release): -fsigned-char -W -Wall -Wreturn-type -Wnon-virtual-dtor -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wuninitialized -Winconsistent-missing-override -Wno-delete-non-virtual-dtor -Wno-unnamed-type-template-args -Wno-comment -Wno-deprecated-enum-enum-conversion -Wno-deprecated-anon-enum-enum-conversion -fdiagnostics-show-option -Qunused-arguments -Wno-semicolon-before-method-body -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -O3 -DNDEBUG -DNDEBUG\n" +" C++ flags (Debug): -fsigned-char -W -Wall -Wreturn-type -Wnon-virtual-dtor -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wuninitialized -Winconsistent-missing-override -Wno-delete-non-virtual-dtor -Wno-unnamed-type-template-args -Wno-comment -Wno-deprecated-enum-enum-conversion -Wno-deprecated-anon-enum-enum-conversion -fdiagnostics-show-option -Qunused-arguments -Wno-semicolon-before-method-body -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -g -O0 -DDEBUG -D_DEBUG\n" +" C Compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc\n" +" C flags (Release): -fsigned-char -W -Wall -Wreturn-type -Wnon-virtual-dtor -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wuninitialized -Winconsistent-missing-override -Wno-delete-non-virtual-dtor -Wno-unnamed-type-template-args -Wno-comment -Wno-deprecated-enum-enum-conversion -Wno-deprecated-anon-enum-enum-conversion -fdiagnostics-show-option -Qunused-arguments -Wno-semicolon-before-method-body -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -O3 -DNDEBUG -DNDEBUG\n" +" C flags (Debug): -fsigned-char -W -Wall -Wreturn-type -Wnon-virtual-dtor -Waddress -Wsequence-point -Wformat -Wformat-security -Wmissing-declarations -Wmissing-prototypes -Wstrict-prototypes -Wundef -Winit-self -Wpointer-arith -Wshadow -Wsign-promo -Wuninitialized -Winconsistent-missing-override -Wno-delete-non-virtual-dtor -Wno-unnamed-type-template-args -Wno-comment -Wno-deprecated-enum-enum-conversion -Wno-deprecated-anon-enum-enum-conversion -fdiagnostics-show-option -Qunused-arguments -Wno-semicolon-before-method-body -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -g -O0 -DDEBUG -D_DEBUG\n" +" Linker flags (Release): -Wl,-dead_strip \n" +" Linker flags (Debug): -Wl,-dead_strip \n" +" ccache: NO\n" +" Precompiled headers: NO\n" +" Extra dependencies: -framework OpenCL /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk/System/Library/Frameworks/Accelerate.framework -lm -ldl -framework Cocoa -framework AppKit\n" +" 3rdparty dependencies: ittnotify libjpeg-turbo libwebp libpng libtiff libopenjp2 IlmImf zlib tegra_hal\n" +"\n" +" OpenCV modules:\n" +" To be built: core highgui imgcodecs imgproc videoio\n" +" Disabled: dnn features2d flann gapi ml photo video world\n" +" Disabled by dependency: calib3d objdetect stitching\n" +" Unavailable: java python2 python3 ts\n" +" Applications: -\n" +" Documentation: NO\n" +" Non-free algorithms: NO\n" +"\n" +" GUI: COCOA\n" +" Cocoa: YES\n" +" VTK support: NO\n" +"\n" +" Media I/O: \n" +" ZLib: build (ver 1.2.12)\n" +" JPEG: build-libjpeg-turbo (ver 2.1.2-62)\n" +" WEBP: build (ver encoder: 0x020f)\n" +" PNG: build (ver 1.6.37)\n" +" TIFF: build (ver 42 - 4.2.0)\n" +" JPEG 2000: build (ver 2.4.0)\n" +" OpenEXR: build (ver 2.3.0)\n" +" HDR: YES\n" +" SUNRASTER: YES\n" +" PXM: YES\n" +" PFM: YES\n" +"\n" +" Video I/O:\n" +" DC1394: NO\n" +" FFMPEG: NO\n" +" avcodec: NO\n" +" avformat: NO\n" +" avutil: NO\n" +" swscale: NO\n" +" avresample: NO\n" +" GStreamer: NO\n" +" AVFoundation: YES\n" +"\n" +" Parallel framework: GCD\n" +"\n" +" Trace: YES (with Intel ITT)\n" +"\n" +" Other third-party libraries:\n" +" Lapack: YES (/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.0.sdk/System/Library/Frameworks/Accelerate.framework -lm -ldl)\n" +" Eigen: YES (ver 3.4.0)\n" +" Custom HAL: YES (carotene (ver 0.0.1))\n" +" Protobuf: build (3.19.1)\n" +"\n" +" OpenCL: YES (no extra features)\n" +" Include path: NO\n" +" Link libraries: -framework OpenCL\n" +"\n" +" Python (for build): /opt/local/bin/python2.7\n" +"\n" +" Install to: /Users/palmer/emgucv/platforms/ubuntu/22.04/build/install\n" +"-----------------------------------------------------------------\n" +"\n" diff --git a/build/platforms/osx-arm64/libcvextern.zip b/build/platforms/osx-arm64/libcvextern.zip index 9744155..5ed07c0 100644 Binary files a/build/platforms/osx-arm64/libcvextern.zip and b/build/platforms/osx-arm64/libcvextern.zip differ diff --git a/documentation/UVtools.Core.xml b/documentation/UVtools.Core.xml index 9576418..85bbf50 100644 --- a/documentation/UVtools.Core.xml +++ b/documentation/UVtools.Core.xml @@ -933,6 +933,28 @@ + + + Draw a square around a center point + + + + + + + + + + + Draw a rectangle around a center point + + + + + + + + Draw a polygon given number of sides and length @@ -4378,6 +4400,16 @@ https://www.ucamco.com/files/downloads/file_en/456/gerber-layer-format-specification-revision-2022-02_en.pdf?ac97011bf6bce9aaf0b1aac43d84b05f + + + Gets or sets the X offset for drawings in millimeters + + + + + Gets or sets the Y offset for drawings in millimeters + + Gets the current polarity as . will affect the return value