diff options
55 files changed, 2675 insertions, 2335 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e6515..ec76614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,24 @@ # Changelog -## 25/11/2020 - v2.0.0 +## 29/12/2020 - v2.1.0 + +* (Add) Tool - Redraw model/supports: Redraw the model or supports with a set brightness. + This requires an extra sliced file from same object but without any supports and raft, straight to the build plate. +* (Add) Tool - Raft Relief: + * Allow ignore a number of layer(s) to start only after that number, detault is 0 + * Allow set a pixel brightness for the operation, detault is 0 + * New "dimming" type, works like relief but instead of drill raft it set to a brightness level +* (Add) Arch-x64 package (#104) +* (Fix) A OS dependent startup crash when there's no primary screen set (#115) +* (Fix) Tool - Re height: Able to cancel the job +* (Fix) Unable to save "Calibration - Tolerance" profiles +* (Change) Core: Move all operations code from LayerManager and Layer to it own Operation* class within a Execute method (Abstraction) +* (Change) sh UVtools.sh to run independent UVtools instance first, if not found it will fallback to dotnet UVtools.dll +* (Change) Compile and zip project with WSL to keep the +x (execute) attribute for linux and unix systems +* (Change) MacOS builds are now packed as an application bundle (Auto-updater disabled for now) +* (Remove) Universal package from builds/releases + +## 25/12/2020 - v2.0.0 This release bump the major version due the introduction of .NET 5.0, the discontinuation old UVtools GUI project and the new calibration wizards. * (Upgrade) From .NET Core 3.1 to .NET 5.0 @@ -11,7 +29,6 @@ This release bump the major version due the introduction of .NET 5.0, the discon * **Take into account the screen scale factor to limit the dialogs windows maximum size**: Due wrong information UVtools can clamp the windows maximum size when you have plenty more avaliable or when use in a secondary monitor. If is the case disable this option * **Horizontal limiting margin:** Limits windows and dialogs maximum width to the screen resolution less this margin * **Vertical limiting margin:** Limits windows and dialogs maximum height to the screen resolution less this margin -* (Add) Setting - General: Take into account the screen scale factor to limit the dialogs windows maximum size. Due wrong information UVtools can cap the windows maximum size when you have plenty more avaliable or when use in a secondary monitor. If is the case disable this option * (Add) Ctrl + Shift + Z to undo and edit the last operation (If contain a valid operation) * (Add) Allow to deselect the current selected profile * (Add) Allow to set a default profile to load in when open a tool @@ -20,7 +20,7 @@ ### Jason S. McMullan (ezrec) -* For ideas and permission to use his project (uv3dp) +* For ideas, file formats and permission to use his project (uv3dp) * https://github.com/ezrec/uv3dp # Supporters / Contributors @@ -40,4 +40,5 @@ * Nahin Mulla * Jorge diego Robles Ayerbe * Timothy Gray -* David Gordon
\ No newline at end of file +* David Gordon +* Claus Pfeilschifter
\ No newline at end of file diff --git a/CreateRelease.WPF.ps1 b/CreateRelease.WPF.ps1 index 56da287..9ceec3e 100644 --- a/CreateRelease.WPF.ps1 +++ b/CreateRelease.WPF.ps1 @@ -29,7 +29,7 @@ class FixedEncoder : System.Text.UTF8Encoding { #################################### ### Configuration ### #################################### -$enableMSI = $false +$enableMSI = $true # Profilling $stopWatch = New-Object -TypeName System.Diagnostics.Stopwatch $deployStopWatch = New-Object -TypeName System.Diagnostics.Stopwatch @@ -44,7 +44,11 @@ $project = "UVtools.WPF" $buildWith = "Release" $netFolder = "net5.0" $releaseFolder = "$PSScriptRoot\$project\bin\$buildWith\$netFolder" -$publishFolder = "$PSScriptRoot\publish" +$objFolder = "$PSScriptRoot\$project\obj\$buildWith\$netFolder" +$publishFolder = "publish" +$platformsFolder = "UVtools.Platforms" + +$macIcns = "UVtools.CAD/UVtools.icns" #$version = (Get-Command "$releaseFolder\UVtools.dll").FileVersionInfo.ProductVersion $projectXml = [Xml] (Get-Content "$PSScriptRoot\$project\$project.csproj") @@ -80,14 +84,22 @@ $runtimes = "win-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" "exclude" = @("libcvextern.so", "libcvextern.dylib", "UVtools.sh") + "include" = @() } "linux-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" "exclude" = @("x86", "x64", "libcvextern.dylib") + "include" = @("libcvextern.so") + } + "arch-x64" = @{ + "extraCmd" = "-p:PublishReadyToRun=true" + "exclude" = @("x86", "x64", "libcvextern.dylib", "libcvextern.so") + "include" = @("libcvextern.so") } "rhel-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" "exclude" = @("x86", "x64", "libcvextern.dylib") + "include" = @("libcvextern.so") } #"unix-x64" = @{ # "extraCmd" = "-p:PublishReadyToRun=true" @@ -96,6 +108,7 @@ $runtimes = "osx-x64" = @{ "extraCmd" = "-p:PublishReadyToRun=true" "exclude" = @("x86", "x64", "libcvextern.so") + "include" = @("libcvextern.dylib") } } @@ -104,31 +117,65 @@ foreach ($obj in $runtimes.GetEnumerator()) { $deployStopWatch.Restart() $runtime = $obj.Name; # runtime name $extraCmd = $obj.extraCmd; # extra cmd to run with dotnet - $targetZip = "$publishFolder\${software}_${runtime}_v$version.zip" # Target zip filename + $targetZip = "$publishFolder/${software}_${runtime}_v$version.zip" # Target zip filename # Deploy Write-Output "################################ Building: $runtime" - dotnet publish $project -o "$publishFolder\$runtime" -c $buildWith -r $runtime $extraCmd + wsl dotnet publish $project -o "$publishFolder/$runtime" -c $buildWith -r $runtime $extraCmd # Cleanup - Remove-Item "$releaseFolder\$runtime" -Recurse -ErrorAction Ignore + Remove-Item "$releaseFolder\$runtime" -Recurse -ErrorAction Ignore + Remove-Item "$objFolder\$runtime" -Recurse -ErrorAction Ignore + Write-Output "$releaseFolder\$runtime" + foreach ($excludeObj in $obj.Value.exclude) { Write-Output "Excluding: $excludeObj" Remove-Item "$publishFolder\$runtime\$excludeObj" -Recurse -ErrorAction Ignore } - # Zip + foreach ($includeObj in $obj.Value.include) { + Write-Output "Including: $includeObj" + Copy-Item "$platformsFolder\$runtime\$includeObj" -Destination "$publishFolder\$runtime" -Recurse -ErrorAction Ignore + } + Write-Output "Compressing $runtime to: $targetZip" - [System.IO.Compression.ZipFile]::CreateFromDirectory("$publishFolder\$runtime", $targetZip, [System.IO.Compression.CompressionLevel]::Optimal, $false, [FixedEncoder]::new()) - $deployStopWatch.Stop() + Write-Output $targetZip "$publishFolder/$runtime" + + if($runtime.Equals('osx-x64')){ + $macAppFolder = "${software}.app" + $macPublishFolder = "$publishFolder/${macAppFolder}" + + wsl mkdir "$macPublishFolder" + wsl mkdir "$macPublishFolder/Contents" + wsl mkdir "$macPublishFolder/Contents/MacOS" + wsl mkdir "$macPublishFolder/Contents/Resources" + + $outputInfoplist = "$macPublishFolder/Contents/Info.plist" + wsl cp "$platformsFolder/$runtime/Info.plist" "$outputInfoplist" + ((Get-Content -Path "$outputInfoplist") -replace '#VERSION',"$version") | Set-Content -Path "$outputInfoplist" + wsl cp "$macIcns" "$macPublishFolder/Contents/Resources/$software.icns" + wsl cp -a "$publishFolder/$runtime/." "$macPublishFolder/Contents/MacOS" + + wsl cd "$publishFolder/" `&`& pwd `&`& zip -r "../$targetZip" "$macAppFolder/*" + } + else { + wsl cd "$publishFolder/$runtime" `&`& pwd `&`& zip -r "../../$targetZip" "." + } + # Zip + #Write-Output "Compressing $runtime to: $targetZip" + #Write-Output $targetZip "$publishFolder/$runtime" + #[System.IO.Compression.ZipFile]::CreateFromDirectory("$publishFolder\$runtime", $targetZip, [System.IO.Compression.CompressionLevel]::Optimal, $false, [FixedEncoder]::new()) + #wsl cd "$publishFolder/$runtime" `&`& pwd `&`& chmod +x -f "./$software" `|`| : `&`& zip -r "../../$targetZip" "." + $deployStopWatch.Stop() Write-Output "Took: $($deployStopWatch.Elapsed) ################################ " } # Universal package +<# $deployStopWatch.Restart() $runtime = "universal-x86-x64" $targetZip = "$publishFolder\${software}_${runtime}_v$version.zip" @@ -143,7 +190,7 @@ Write-Output "Took: $($deployStopWatch.Elapsed) ################################ " $stopWatch.Stop() - +#> # MSI Installer for Windows if($enableMSI) @@ -181,7 +181,7 @@ sudo dpkg -i packages-microsoft-prod.deb rm packages-microsoft-prod.deb sudo apt-get update sudo apt-get install -y apt-transport-https -dotnet-runtime-3.1 +dotnet-runtime-5.0 !--> ```bash @@ -211,7 +211,7 @@ To run UVtools open it folder on a terminal and call one of: 1. 4GB RAM or higher <!--- -* Donwload and install: https://dotnet.microsoft.com/download/dotnet-core/thank-you/sdk-3.1.402-macos-x64-installer +* Donwload and install: https://dotnet.microsoft.com/download/dotnet/thank-you/sdk-5.0.101-macos-x64-installer brew install libjpeg libpng libgeotiff libdc1394 ffmpeg openexr tbb --> diff --git a/UVtools.CAD/UVtools.icns b/UVtools.CAD/UVtools.icns Binary files differnew file mode 100644 index 0000000..7ae55fa --- /dev/null +++ b/UVtools.CAD/UVtools.icns diff --git a/UVtools.Core/Extensions/EmguExtensions.cs b/UVtools.Core/Extensions/EmguExtensions.cs index d9c1bc0..a48a5c7 100644 --- a/UVtools.Core/Extensions/EmguExtensions.cs +++ b/UVtools.Core/Extensions/EmguExtensions.cs @@ -248,5 +248,12 @@ namespace UVtools.Core.Extensions return mat; } + public static Mat InitMat(Size size, MCvScalar scalar, int channels = 1, DepthType depthType = DepthType.Cv8U) + { + var mat = new Mat(size, depthType, channels); + mat.SetTo(scalar); + return mat; + } + } } diff --git a/UVtools.Core/FileFormats/FileFormat.cs b/UVtools.Core/FileFormats/FileFormat.cs index 3bd20c5..21eef2f 100644 --- a/UVtools.Core/FileFormats/FileFormat.cs +++ b/UVtools.Core/FileFormats/FileFormat.cs @@ -565,7 +565,7 @@ namespace UVtools.Core.FileFormats if (SuppressRebuildProperties) return; if (e.PropertyName == nameof(LayerCount)) { - if (this[LayerCount - 1] is null) return; // Not initialized + if (LayerCount == 0 || this[LayerCount - 1] is null) return; // Not initialized LayerManager.RebuildLayersProperties(); RebuildGCode(); PrintTime = PrintTimeComputed; diff --git a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs index 445d6b4..0fbaee3 100644 --- a/UVtools.Core/FileFormats/PhotonWorkshopFile.cs +++ b/UVtools.Core/FileFormats/PhotonWorkshopFile.cs @@ -772,12 +772,12 @@ namespace UVtools.Core.FileFormats public override FileExtension[] FileExtensions { get; } = { - new FileExtension("pwmx", "Photon Mono X (PWMX)"), - new FileExtension("pwms", "Photon Mono SE (PWMS)"), - new FileExtension("pwmo", "Photon Mono (PWMO)"), - new FileExtension("pwx", "Photon X (PWX)"), - new FileExtension("pws", "Photon / Photon S (PWS)"), - new FileExtension("pw0", "Photon Zero (PW0)"), + new("pwmx", "Photon Mono X (PWMX)"), + new("pwms", "Photon Mono SE (PWMS)"), + new("pwmo", "Photon Mono (PWMO)"), + new("pwx", "Photon X (PWX)"), + new("pws", "Photon / Photon S (PWS)"), + new("pw0", "Photon Zero (PW0)"), }; diff --git a/UVtools.Core/Layer/Layer.cs b/UVtools.Core/Layer/Layer.cs index 3aa1fc1..875ca1b 100644 --- a/UVtools.Core/Layer/Layer.cs +++ b/UVtools.Core/Layer/Layer.cs @@ -210,14 +210,10 @@ namespace UVtools.Core } set { - using (var vector = new VectorOfByte()) - { - CvInvoke.Imencode(".png", value, vector); - CompressedBytes = vector.ToArray(); - - GetBoundingRectangle(value, true); - } - + using var vector = new VectorOfByte(); + CvInvoke.Imencode(".png", value, vector); + CompressedBytes = vector.ToArray(); + GetBoundingRectangle(value, true); RaisePropertyChanged(); } } @@ -633,646 +629,9 @@ namespace UVtools.Core return result; } - public void Move(OperationMove operation) - { - using (var mat = LayerMat) - { - if (operation.ImageWidth == 0) operation.ImageWidth = (uint)mat.Width; - if (operation.ImageHeight == 0) operation.ImageHeight = (uint)mat.Height; - - /*layer.Transform(1.0, 1.0, move.MarginLeft - move.MarginRight, move.MarginTop-move.MarginBottom); - LayerMat = layer;*/ - /*using (var layerRoi = new Mat(layer, operation.SrcRoi)) - using (var dstLayer = layer.CloneBlank()) - using (var dstRoi = new Mat(dstLayer, operation.DstRoi)) - { - layerRoi.CopyTo(dstRoi); - LayerMat = dstLayer; - }*/ - - using (var srcRoi = new Mat(mat, operation.ROI)) - using (var dstRoi = new Mat(mat, operation.DstRoi)) - { - if (operation.IsCutMove) - { - using (var targetRoi = srcRoi.Clone()) - { - srcRoi.SetTo(new MCvScalar(0)); - targetRoi.CopyTo(dstRoi); - } - } - else - { - srcRoi.CopyTo(dstRoi); - } - - LayerMat = mat; - } - } - } - - - public void Resize(double xScale, double yScale, OperationResize operation) - { - using (var mat = LayerMat) - { - Mat target = operation.GetRoiOrDefault(mat); - target.TransformFromCenter(xScale, yScale); - LayerMat = mat; - } - } - - public void Flip(OperationFlip operation) - { - using (var mat = LayerMat) - { - Mat target = operation.GetRoiOrDefault(mat); - - if (operation.MakeCopy) - { - using (Mat dst = new Mat()) - { - - CvInvoke.Flip(target, dst, operation.FlipTypeOpenCV); - CvInvoke.Add(target, dst, target); - } - } - else - { - CvInvoke.Flip(target, target, operation.FlipTypeOpenCV); - } - - LayerMat = mat; - } - } - - public void Rotate(OperationRotate operation) - { - using (var mat = LayerMat) - { - Mat target = operation.GetRoiOrDefault(mat); - target.Rotate((double) operation.AngleDegrees); - LayerMat = mat; - } - } - - public void Solidify(OperationSolidify operation) - { - using (Mat mat = LayerMat) - { - using (Mat filteredMat = new Mat()) - { - Mat target = operation.GetRoiOrDefault(mat); - - - CvInvoke.Threshold(target, filteredMat, 127, 255, ThresholdType.Binary); // Clean AA - - using (VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint()) - { - using (Mat hierarchy = new Mat()) - { - CvInvoke.FindContours(filteredMat, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple); - var arr = hierarchy.GetData(); - for (int i = 0; i < contours.Size; i++) - { - if ((int)arr.GetValue(0, i, 2) != -1 || (int)arr.GetValue(0, i, 3) == -1) continue; - if (operation.MinimumArea >= 1) - { - var rectangle = CvInvoke.BoundingRectangle(contours[i]); - if (operation.AreaCheckType == OperationSolidify.AreaCheckTypes.More) - { - if (rectangle.GetArea() < operation.MinimumArea) continue; - } - else - { - if (rectangle.GetArea() > operation.MinimumArea) continue; - } - - } - - CvInvoke.DrawContours(target, contours, i, new MCvScalar(255), -1); - } - } - } - } - - LayerMat = mat; - } - } - - public void Mask(OperationMask operation) - { - using (var mat = LayerMat) - { - Mat target = operation.GetRoiOrDefault(mat); - if(operation.Mask.Size != target.Size) return; - CvInvoke.BitwiseAnd(target, operation.Mask, target); - LayerMat = mat; - } - } - - /*public void MutatePixelDimming(Matrix<byte> evenPattern = null, Matrix<byte> oddPattern = null, ushort borderSize = 5) - { - var anchor = new Point(-1, -1); - var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - if (ReferenceEquals(evenPattern, null)) - { - evenPattern = new Matrix<byte>(2, 2) - { - [0, 0] = 127, - [0, 1] = 255, - [1, 0] = 255, - [1, 1] = 127, - }; - - if (ReferenceEquals(oddPattern, null)) - { - oddPattern = new Matrix<byte>(2, 2) - { - [0, 0] = 255, - [0, 1] = 127, - [1, 0] = 127, - [1, 1] = 255, - }; - } - } - - using (Mat dst = LayerMat) - { - using (Mat erode = new Mat()) - { - using (Mat diff = new Mat()) - { - using (Mat mask = dst.CloneBlank()) - { - CvInvoke.Erode(dst, erode, kernel, anchor, borderSize, BorderType.Reflect101, default); - CvInvoke.Subtract(dst, erode, diff); - - if (Index % 2 == 0) - { - CvInvoke.Repeat(evenPattern, dst.Rows / evenPattern.Rows + 1, dst.Cols / evenPattern.Cols + 1, mask); - } - else - { - CvInvoke.Repeat(oddPattern, dst.Rows / oddPattern.Rows + 1, dst.Cols / oddPattern.Cols + 1, mask); - } - - using (var maskReshape = new Mat(mask, new Rectangle(0, 0, dst.Width, dst.Height))) - { - CvInvoke.BitwiseAnd(erode, maskReshape, dst); - } - - CvInvoke.Add(dst, diff, dst); - LayerMat = dst; - } - } - } - } - }*/ - - public void PixelDimming(OperationPixelDimming operation, Mat patternMask, Mat alternatePatternMask = null) - { - var anchor = new Point(-1, -1); - var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - - if (ReferenceEquals(alternatePatternMask, null)) - { - alternatePatternMask = patternMask; - } - - int wallThickness = LayerManager.MutateGetIterationChamfer( - Index, - operation.LayerIndexStart, - operation.LayerIndexEnd, - (int)operation.WallThicknessStart, - (int)operation.WallThicknessEnd, - operation.Chamfer - ); - - - using (Mat dst = LayerMat) - using (Mat erode = new Mat()) - using (Mat diff = new Mat()) - { - Mat target = operation.GetRoiOrDefault(dst); - - - CvInvoke.Erode(target, erode, kernel, anchor, wallThickness, BorderType.Reflect101, default); - CvInvoke.Subtract(target, erode, diff); - - - if (operation.WallsOnly) - { - CvInvoke.BitwiseAnd(diff, operation.IsNormalPattern(Index) ? patternMask : alternatePatternMask, target); - CvInvoke.Add(erode, target, target); - } - else - { - CvInvoke.BitwiseAnd(erode, operation.IsNormalPattern(Index) ? patternMask : alternatePatternMask, target); - CvInvoke.Add(target, diff, target); - } - - LayerMat = dst; - } - } - - public void Infill(OperationInfill operation) - { - var anchor = new Point(-1, -1); - var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - - uint layerIndex = Index - operation.LayerIndexStart; - var infillColor = new MCvScalar(operation.InfillBrightness); - - Mat patternMask = null; - using (Mat dst = LayerMat) - using (Mat erode = new Mat()) - using (Mat diff = new Mat()) - { - Mat target = operation.GetRoiOrDefault(dst); - - /*if (operation.InfillType == OperationInfill.InfillAlgorithm.Rhombus) - { - const double rotationAngle = 55; - patternMask = target.CloneBlank(); - int offsetLayerPos = (int)(layerIndex % operation.InfillSpacing); - var pivot = new Point( operation.InfillThickness / 2, operation.InfillThickness / 2); - - Point[] points1 = { - new Point(operation.InfillThickness / 4, operation.InfillThickness / 2), // Left - new Point(operation.InfillThickness / 2, 0), // Top - new Point((int) (operation.InfillThickness / 1.25), operation.InfillThickness / 2), // Right - new Point(operation.InfillThickness / 2, operation.InfillThickness) // Bottom - }; - var vec1 = new VectorOfPoint(points1); - - - Point[] points2 = { - points1[0].Rotate(rotationAngle, pivot), - points1[1].Rotate(rotationAngle, pivot), - points1[2].Rotate(rotationAngle, pivot), - points1[3].Rotate(rotationAngle, pivot), - }; - var vec2 = new VectorOfPoint(points2); - - Point[] points3 = { - points1[0].Rotate(-rotationAngle, pivot), - points1[1].Rotate(-rotationAngle, pivot), - points1[2].Rotate(-rotationAngle, pivot), - points1[3].Rotate(-rotationAngle, pivot), - }; - var vec3 = new VectorOfPoint(points3); - - - /*int halfPos = (operation.InfillThickness + offsetPos) / 2; - - Point[] points = { - new Point(0+offsetPos, halfPos), // Left - new Point(halfPos, 0+offsetPos), // Top - new Point(operation.InfillThickness+offsetPos, halfPos), // Right - new Point(halfPos, operation.InfillThickness+offsetPos) // Bottom - }; */ - /* for (int y = 0; y < patternMask.Height; y += operation.InfillSpacing) - { - for (int x = 0; x < patternMask.Width; x+=operation.InfillSpacing) - { - CvInvoke.FillPoly(patternMask, vec1, infillColor, LineType.EightConnected, default, new Point(x, y+offsetLayerPos)); - CvInvoke.FillPoly(patternMask, vec2, infillColor, LineType.EightConnected, default, new Point(x- offsetLayerPos, y+ offsetLayerPos)); - CvInvoke.FillPoly(patternMask, vec3, infillColor, LineType.EightConnected, default, new Point(x+ offsetLayerPos, y+ offsetLayerPos)); - } - } - - patternMask.Save("D:\\mask.png"); - - - } - else*/ if (operation.InfillType == OperationInfill.InfillAlgorithm.Cubic || - operation.InfillType == OperationInfill.InfillAlgorithm.CubicCenterLink || - operation.InfillType == OperationInfill.InfillAlgorithm.CubicDynamicLink || - operation.InfillType == OperationInfill.InfillAlgorithm.CubicInterlinked) - { - using (var infillPattern = EmguExtensions.InitMat(new Size(operation.InfillSpacing, operation.InfillSpacing))) - using (Mat matPattern = dst.CloneBlank()) - { - bool firstPattern = true; - uint accumulator = 0; - uint step = 0; - bool dynamicCenter = false; - while (accumulator < layerIndex) - { - dynamicCenter = !dynamicCenter; - firstPattern = true; - accumulator += operation.InfillSpacing; - - if (accumulator >= layerIndex) break; - firstPattern = false; - accumulator += operation.InfillThickness; - } - - if (firstPattern) - { - int thickness = operation.InfillThickness / 2; - // Top Left - CvInvoke.Rectangle(infillPattern, - new Rectangle(0, 0, thickness, thickness), - infillColor, -1); - - // Top Right - CvInvoke.Rectangle(infillPattern, - new Rectangle(infillPattern.Width - thickness, 0, thickness, thickness), - infillColor, -1); - - // Bottom Left - CvInvoke.Rectangle(infillPattern, - new Rectangle(0, infillPattern.Height - thickness, thickness, thickness), - infillColor, -1); - - // Bottom Right - CvInvoke.Rectangle(infillPattern, - new Rectangle(infillPattern.Width - thickness, infillPattern.Height - thickness, - thickness, thickness), - infillColor, -1); - - // Center cross - int margin = (int)(operation.InfillSpacing - accumulator + layerIndex) - thickness; - int marginInv = (int)(accumulator - layerIndex) - thickness; - - if (operation.InfillType == OperationInfill.InfillAlgorithm.CubicCenterLink || - (operation.InfillType == OperationInfill.InfillAlgorithm.CubicDynamicLink && - dynamicCenter) || - operation.InfillType == OperationInfill.InfillAlgorithm.CubicInterlinked) - { - - CvInvoke.Rectangle(infillPattern, - new Rectangle(margin, margin, operation.InfillThickness, operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(marginInv, marginInv, operation.InfillThickness, - operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(margin, marginInv, operation.InfillThickness, - operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(marginInv, margin, operation.InfillThickness, - operation.InfillThickness), - infillColor, -1); - } - - - if (operation.InfillType == OperationInfill.InfillAlgorithm.CubicInterlinked || - (operation.InfillType == OperationInfill.InfillAlgorithm.CubicDynamicLink && !dynamicCenter)) - { - CvInvoke.Rectangle(infillPattern, - new Rectangle(margin, -thickness, operation.InfillThickness, - operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(marginInv, -thickness, operation.InfillThickness, - operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(-thickness, margin, operation.InfillThickness, - operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(-thickness, marginInv, operation.InfillThickness, - operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(operation.InfillSpacing - thickness, margin, - operation.InfillThickness, operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(operation.InfillSpacing - thickness, marginInv, - operation.InfillThickness, operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(margin, operation.InfillSpacing - thickness, - operation.InfillThickness, operation.InfillThickness), - infillColor, -1); - - CvInvoke.Rectangle(infillPattern, - new Rectangle(marginInv, operation.InfillSpacing - thickness, - operation.InfillThickness, operation.InfillThickness), - infillColor, -1); - } - - - } - else - { - CvInvoke.Rectangle(infillPattern, - new Rectangle(0, 0, operation.InfillSpacing, operation.InfillSpacing), - infillColor, operation.InfillThickness); - } - - - { - CvInvoke.Repeat(infillPattern, target.Rows / infillPattern.Rows + 1, - target.Cols / infillPattern.Cols + 1, matPattern); - patternMask = new Mat(matPattern, new Rectangle(0, 0, target.Width, target.Height)); - } - } - } - - - CvInvoke.Erode(target, erode, kernel, anchor, operation.WallThickness, BorderType.Reflect101, - default); - CvInvoke.Subtract(target, erode, diff); - - - CvInvoke.BitwiseAnd(erode, patternMask, target); - CvInvoke.Add(target, diff, target); - patternMask?.Dispose(); - - LayerMat = dst; - } - } - - public void Morph(OperationMorph operation, int iterations = 1, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (iterations == 0) - iterations = (int) operation.IterationsStart; - - using (Mat dst = LayerMat) - { - Mat target = operation.GetRoiOrDefault(dst); - CvInvoke.MorphologyEx(target, target, operation.MorphOperation, operation.Kernel.Matrix, operation.Kernel.Anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateErode(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.Erode(dst, dst, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateDilate(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.Dilate(dst, dst, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateOpen(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.MorphologyEx(dst, dst, MorphOp.Open, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateClose(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.MorphologyEx(dst, dst, MorphOp.Close, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void MutateGradient(int iterations = 1, IInputArray kernel = null, Point anchor = default, BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - if (anchor.IsEmpty) anchor = new Point(-1, -1); - if (ReferenceEquals(kernel, null)) - { - kernel = CvInvoke.GetStructuringElement(ElementShape.Cross, new Size(3, 3), anchor); - } - using (Mat dst = LayerMat) - { - CvInvoke.MorphologyEx(dst, dst, MorphOp.Gradient, kernel, anchor, iterations, borderType, borderValue); - LayerMat = dst; - } - } - - public void ThresholdPixels(OperationThreshold operation) - { - using (Mat dst = LayerMat) - { - Mat target = operation.GetRoiOrDefault(dst); - CvInvoke.Threshold(target, target, operation.Threshold, operation.Maximum, operation.Type); - LayerMat = dst; - } - } - - public void Blur(OperationBlur operation) - { - Size size = new Size((int) operation.Size, (int) operation.Size); - Point anchor = operation.Kernel.Anchor; - if (anchor.IsEmpty) anchor = new Point(-1, -1); - //if (size.IsEmpty) size = new Size(3, 3); - //if (anchor.IsEmpty) anchor = new Point(-1, -1); - using (Mat dst = LayerMat) - { - Mat target = operation.GetRoiOrDefault(dst); - switch (operation.BlurOperation) - { - case OperationBlur.BlurAlgorithm.Blur: - CvInvoke.Blur(target, target, size, operation.Kernel.Anchor); - break; - case OperationBlur.BlurAlgorithm.Pyramid: - CvInvoke.PyrDown(target, target); - CvInvoke.PyrUp(target, target); - break; - case OperationBlur.BlurAlgorithm.MedianBlur: - CvInvoke.MedianBlur(target, target, (int) operation.Size); - break; - case OperationBlur.BlurAlgorithm.GaussianBlur: - CvInvoke.GaussianBlur(target, target, size, 0); - break; - case OperationBlur.BlurAlgorithm.Filter2D: - CvInvoke.Filter2D(target, target, operation.Kernel.Matrix, anchor); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - LayerMat = dst; - } - } - - public void ChangeResolution(OperationChangeResolution operation) - { - using (var mat = LayerMat) - { - //mat.Transform(1, 1, newResolutionX-mat.Width+roi.Width, newResolutionY-mat.Height - roi.Height/2, new Size((int) newResolutionX, (int) newResolutionY)); - using (var matRoi = new Mat(mat, operation.VolumeBonds)) - { - using (var matDst = new Mat(new Size((int)operation.NewResolutionX, (int)operation.NewResolutionY), mat.Depth, - mat.NumberOfChannels)) - { - using (var matDstRoi = new Mat(matDst, - new Rectangle((int) (operation.NewResolutionX / 2 - operation.VolumeBonds.Width / 2), - (int)operation.NewResolutionY / 2 - operation.VolumeBonds.Height / 2, - operation.VolumeBonds.Width, operation.VolumeBonds.Height))) - { - matRoi.CopyTo(matDstRoi); - LayerMat = matDst; - } - } - } - } - } - - public void Pattern(OperationPattern operation) - { - using (var layer = LayerMat) - using (var layerRoi = new Mat(layer, operation.ROI)) - using (var dstLayer = layer.CloneBlank()) - { - for (ushort col = 0; col < operation.Cols; col++) - for (ushort row = 0; row < operation.Rows; row++) - { - using (var dstRoi = new Mat(dstLayer, operation.GetRoi(col, row))) - { - layerRoi.CopyTo(dstRoi); - } - } - - LayerMat = dstLayer; - } - } public Layer Clone() { - return new Layer(_index, CompressedBytes.ToArray(), ParentLayerManager) + return new(_index, CompressedBytes.ToArray(), ParentLayerManager) { PositionZ = _positionZ, ExposureTime = _exposureTime, diff --git a/UVtools.Core/Layer/LayerManager.cs b/UVtools.Core/Layer/LayerManager.cs index 4ef3a93..f354ea2 100644 --- a/UVtools.Core/Layer/LayerManager.cs +++ b/UVtools.Core/Layer/LayerManager.cs @@ -28,7 +28,7 @@ namespace UVtools.Core public class LayerManager : IEnumerable<Layer> { #region Properties - public FileFormat SlicerFile { get; private set; } + public FileFormat SlicerFile { get; } private Layer[] _layers; @@ -38,9 +38,11 @@ namespace UVtools.Core public Layer[] Layers { get => _layers; - private set + protected internal set { _layers = value; + BoundingRectangle = Rectangle.Empty; + SlicerFile.LayerCount = Count; if (value is null) return; SlicerFile.PrintTime = SlicerFile.PrintTimeComputed; } @@ -59,7 +61,7 @@ namespace UVtools.Core /// </summary> public uint Count => (uint) Layers.Length; - public byte LayerDigits => (byte) Layers.Length.ToString().Length; + public byte LayerDigits => (byte)Count.ToString().Length; /// <summary> /// Gets if any layer got modified, otherwise false @@ -85,7 +87,7 @@ namespace UVtools.Core public LayerManager(uint layerCount, FileFormat slicerFile) { SlicerFile = slicerFile; - Layers = new Layer[layerCount]; + _layers = new Layer[layerCount]; } #endregion @@ -201,7 +203,7 @@ namespace UVtools.Core public Rectangle GetBoundingRectangle(OperationProgress progress = null) { - if (!_boundingRectangle.IsEmpty) return _boundingRectangle; + if (!_boundingRectangle.IsEmpty || Count == 0 || this[0] is null) return _boundingRectangle; _boundingRectangle = this[0].BoundingRectangle; if (_boundingRectangle.IsEmpty) // Safe checking { @@ -281,230 +283,6 @@ namespace UVtools.Core return Layers[index]; } - public void Move(OperationMove operation, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - - if (operation.ROI == Rectangle.Empty) operation.ROI = GetBoundingRectangle(progress); - - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - - this[layerIndex].Move(operation); - - lock (progress.Mutex) - { - progress++; - } - }); - - _boundingRectangle = Rectangle.Empty; - - progress.Token.ThrowIfCancellationRequested(); - - - } - - /// <summary> - /// Resizes layer images in x and y factor, starting at 1 = 100% - /// </summary> - public void Resize(OperationResize operation, OperationProgress progress = null) - { - if (operation.X == 1m && operation.Y == 1m) return; - - if(ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - - decimal xSteps = Math.Abs(operation.X - 1) / (operation.LayerIndexEnd - operation.LayerIndexStart); - decimal ySteps = Math.Abs(operation.Y - 1) / (operation.LayerIndexEnd - operation.LayerIndexStart); - - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - var newX = operation.X; - var newY = operation.Y; - if (operation.IsFade) - { - if (newX != 1m) - { - - //maxIteration = Math.Max(iterationsStart, iterationsEnd); - - newX = newX < 1m - ? newX + (layerIndex - operation.LayerIndexStart) * xSteps - : newX - (layerIndex - operation.LayerIndexStart) * xSteps; - - // constrain - //iterations = Math.Min(Math.Max(1, iterations), maxIteration); - } - - if (newY != 1m) - { - - //maxIteration = Math.Max(iterationsStart, iterationsEnd); - - newY = (newY < 1m - ? newY + (layerIndex - operation.LayerIndexStart) * ySteps - : newY - (layerIndex - operation.LayerIndexStart) * ySteps); - - // constrain - //iterations = Math.Min(Math.Max(1, iterations), maxIteration); - } - } - - lock (progress.Mutex) - { - progress++; - } - - if (newX == 1.0m && newY == 1.0m) return; - - this[layerIndex].Resize((double) (newX / 100m), (double) (newY / 100m), operation); - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void Flip(OperationFlip operation, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].Flip(operation); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void Rotate(OperationRotate operation, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].Rotate(operation); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void Solidify(OperationSolidify operation, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].Solidify(operation); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void Mask(OperationMask operation, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].Mask(operation); - lock (progress.Mutex) - { - progress++; - } - }); - - progress.Token.ThrowIfCancellationRequested(); - } - - public void PixelDimming(OperationPixelDimming operation, OperationProgress progress = null) - { - if (progress is null) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - - if (operation.Pattern is null) - { - operation.Pattern = new Matrix<byte>(2, 2) - { - [0, 0] = 127, [0, 1] = 255, - [1, 0] = 255, [1, 1] = 127, - }; - - if (operation.AlternatePattern is null) - { - operation.AlternatePattern = new Matrix<byte>(2, 2) - { - [0, 0] = 255, [0, 1] = 127, - [1, 0] = 127, [1, 1] = 255, - }; - } - } - - if (operation.AlternatePattern is null) - { - operation.AlternatePattern = operation.Pattern; - } - - using (Mat mat = this[0].LayerMat) - using (Mat matPattern = mat.CloneBlank()) - using (Mat matAlternatePattern = mat.CloneBlank()) - { - Mat target = operation.GetRoiOrDefault(mat); - - CvInvoke.Repeat(operation.Pattern, target.Rows / operation.Pattern.Rows + 1, - target.Cols / operation.Pattern.Cols + 1, matPattern); - CvInvoke.Repeat(operation.AlternatePattern, target.Rows / operation.AlternatePattern.Rows + 1, - target.Cols / operation.AlternatePattern.Cols + 1, matAlternatePattern); - - using (var patternMask = new Mat(matPattern, new Rectangle(0, 0, target.Width, target.Height))) - using (var alternatePatternMask = new Mat(matAlternatePattern, new Rectangle(0, 0, target.Width, target.Height))) - { - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].PixelDimming(operation, patternMask, alternatePatternMask); - lock (progress.Mutex) - { - progress++; - } - }); - } - } - - progress.Token.ThrowIfCancellationRequested(); - } - - public void Infill(OperationInfill operation, OperationProgress progress) - { - if (progress is null) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].Infill(operation); - lock (progress.Mutex) - { - progress++; - } - }); - } - public static void MutateGetVarsIterationChamfer(uint startLayerIndex, uint endLayerIndex, int iterationsStart, int iterationsEnd, ref bool isFade, out float iterationSteps, out int maxIteration) { iterationSteps = 0; @@ -535,379 +313,6 @@ namespace UVtools.Core return MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, layerIndex); } - public void Morph(OperationMorph operation, BorderType borderType = BorderType.Default, MCvScalar borderValue = default, OperationProgress progress = null) - { - if (progress is null) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - - var isFade = operation.Chamfer; - MutateGetVarsIterationChamfer( - operation.LayerIndexStart, - operation.LayerIndexEnd, - (int) operation.IterationsStart, - (int) operation.IterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, - //new ParallelOptions {MaxDegreeOfParallelism = 1}, - layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, (int) operation.IterationsStart, (int) operation.IterationsEnd, iterationSteps, maxIteration, operation.LayerIndexStart, (uint)layerIndex); - //Debug.WriteLine(iterations); - this[layerIndex].Morph(operation, iterations, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - /*public void MutateErode(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Eroding", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint) layerIndex); - this[layerIndex].MutateErode(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateDilate(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Dilating", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); - this[layerIndex].MutateDilate(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateOpen(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Removing Noise", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); - this[layerIndex].MutateOpen(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateClose(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Gap Closing", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); - this[layerIndex].MutateClose(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - public void MutateGradient(uint startLayerIndex, uint endLayerIndex, int iterationsStart = 1, int iterationsEnd = 1, bool isFade = false, OperationProgress progress = null, - IInputArray kernel = null, Point anchor = default, - BorderType borderType = BorderType.Default, MCvScalar borderValue = default) - { - MutateGetVarsIterationFade( - startLayerIndex, - endLayerIndex, - iterationsStart, - iterationsEnd, - ref isFade, - out var iterationSteps, - out var maxIteration - ); - - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset("Gradient", endLayerIndex - startLayerIndex+1); - - Parallel.For(startLayerIndex, endLayerIndex + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - int iterations = MutateGetIterationVar(isFade, iterationsStart, iterationsEnd, iterationSteps, maxIteration, startLayerIndex, (uint)layerIndex); - this[layerIndex].MutateGradient(iterations, kernel, anchor, borderType, borderValue); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - }*/ - - public void RaftRelief(OperationRaftRelief operation, OperationProgress progress) - { - const uint minLength = 5; - if (progress is null) progress = new OperationProgress(); - //progress.Reset(operation.ProgressAction); - - Mat supportsMat = null; - var anchor = new Point(-1, -1); - var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); - - - uint firstSupportLayerIndex = 0; - for (; firstSupportLayerIndex < Count; firstSupportLayerIndex++) - { - progress.Reset("Tracing raft", Count, firstSupportLayerIndex); - if (progress.Token.IsCancellationRequested) return; - supportsMat = operation.GetRoiOrDefault(this[firstSupportLayerIndex].LayerMat); - var circles = CvInvoke.HoughCircles(supportsMat, HoughModes.Gradient, 1, 20, 100, 30, 5, 200); - if (circles.Length >= minLength) break; - - supportsMat.Dispose(); - supportsMat = null; - } - - if (supportsMat is null) return; - Mat patternMat = null; - - if (operation.DilateIterations > 0) - { - CvInvoke.Dilate(supportsMat, supportsMat, - CvInvoke.GetStructuringElement(ElementShape.Ellipse, new Size(3, 3), new Point(-1, -1)), - new Point(-1, -1), operation.DilateIterations, BorderType.Reflect101, new MCvScalar()); - } - - switch (operation.ReliefType) - { - case OperationRaftRelief.RaftReliefTypes.Relief: - patternMat = EmguExtensions.InitMat(supportsMat.Size); - int shapeSize = operation.HoleDiameter + operation.HoleSpacing; - using (var shape = EmguExtensions.InitMat(new Size(shapeSize, shapeSize))) - { - - int center = operation.HoleDiameter / 2; - //int centerTwo = operation.HoleDiameter + operation.HoleSpacing + operation.HoleDiameter / 2; - int radius = center; - CvInvoke.Circle(shape, new Point(shapeSize / 2, shapeSize / 2), radius, EmguExtensions.WhiteByte, -1); - CvInvoke.Circle(shape, new Point(0, 0), radius / 2, EmguExtensions.WhiteByte, -1); - CvInvoke.Circle(shape, new Point(0, shapeSize), radius / 2, EmguExtensions.WhiteByte, -1); - CvInvoke.Circle(shape, new Point(shapeSize, 0), radius / 2, EmguExtensions.WhiteByte, -1); - CvInvoke.Circle(shape, new Point(shapeSize, shapeSize), radius / 2, EmguExtensions.WhiteByte, -1); - - CvInvoke.Repeat(shape, supportsMat.Height / shape.Height + 1, supportsMat.Width / shape.Width + 1, patternMat); - - patternMat = new Mat(patternMat, new Rectangle(0, 0, supportsMat.Width, supportsMat.Height)); - } - - break; - } - - progress.Reset(operation.ProgressAction, firstSupportLayerIndex); - Parallel.For(0, firstSupportLayerIndex, layerIndex => - { - using (Mat dst = this[layerIndex].LayerMat) - { - var target = operation.GetRoiOrDefault(dst); - - switch (operation.ReliefType) - { - case OperationRaftRelief.RaftReliefTypes.Relief: - using (Mat mask = new Mat()) - { - /*CvInvoke.Subtract(target, supportsMat, mask); - CvInvoke.Erode(mask, mask, kernel, anchor, operation.WallMargin, BorderType.Reflect101, new MCvScalar()); - CvInvoke.Subtract(target, patternMat, target, mask);*/ - - CvInvoke.Erode(target, mask, kernel, anchor, operation.WallMargin, BorderType.Reflect101, default); - CvInvoke.Subtract(mask, supportsMat, mask); - CvInvoke.Subtract(target, patternMat, target, mask); - } - - break; - case OperationRaftRelief.RaftReliefTypes.Decimate: - supportsMat.CopyTo(target); - break; - } - - - this[layerIndex].LayerMat = dst; - } - - lock (progress.Mutex) - { - progress++; - } - }); - - - supportsMat.Dispose(); - patternMat?.Dispose(); - } - - public void Arithmetic(OperationArithmetic operation, OperationProgress progress = null) - { - if (!operation.IsValid) return; - if (progress is null) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, (uint)operation.Operations.Count); - - using (Mat result = this[operation.Operations[0].LayerIndex].LayerMat) - { - Mat resultRoi = operation.GetRoiOrDefault(result); - for (int i = 1; i < operation.Operations.Count; i++) - { - using (var image = this[operation.Operations[i].LayerIndex].LayerMat) - { - Mat imageRoi = operation.GetRoiOrDefault(image); - switch (operation.Operations[i - 1].Operator) - { - case OperationArithmetic.ArithmeticOperators.Add: - CvInvoke.Add(resultRoi, imageRoi, resultRoi); - break; - case OperationArithmetic.ArithmeticOperators.Subtract: - CvInvoke.Subtract(resultRoi, imageRoi, resultRoi); - break; - case OperationArithmetic.ArithmeticOperators.Multiply: - CvInvoke.Multiply(resultRoi, imageRoi, resultRoi); - break; - case OperationArithmetic.ArithmeticOperators.Divide: - CvInvoke.Divide(resultRoi, imageRoi, resultRoi); - break; - case OperationArithmetic.ArithmeticOperators.BitwiseAnd: - CvInvoke.BitwiseAnd(resultRoi, imageRoi, resultRoi); - break; - case OperationArithmetic.ArithmeticOperators.BitwiseOr: - CvInvoke.BitwiseOr(resultRoi, imageRoi, resultRoi); - break; - case OperationArithmetic.ArithmeticOperators.BitwiseXor: - CvInvoke.BitwiseXor(resultRoi, imageRoi, resultRoi); - break; - } - } - } - - Parallel.ForEach(operation.SetLayers, layerIndex => - { - if (operation.Operations.Count == 1 && operation.HaveROI) - { - var mat = this[layerIndex].LayerMat; - var matRoi = operation.GetRoiOrDefault(mat); - resultRoi.CopyTo(matRoi); - this[layerIndex].LayerMat = mat; - return; - } - this[layerIndex].LayerMat = result; - }); - } - } - - public void ThresholdPixels(OperationThreshold operation, OperationProgress progress) - { - if (progress is null) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].ThresholdPixels(operation); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - - - public void Blur(OperationBlur operation, OperationProgress progress) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - this[layerIndex].Blur(operation); - lock (progress.Mutex) - { - progress++; - } - }); - progress.Token.ThrowIfCancellationRequested(); - } - public List<LayerIssue> GetAllIssues( IslandDetectionConfiguration islandConfig = null, OverhangDetectionConfiguration overhangConfig = null, @@ -977,8 +382,8 @@ namespace UVtools.Core (!ReferenceEquals(overhangConfig.WhiteListLayers, null) && !overhangConfig.WhiteListLayers.Contains(layer.Index)) && (!ReferenceEquals(islandConfig.WhiteListLayers, null) && !islandConfig.WhiteListLayers.Contains(layer.Index)) ) - ) ) + ) { lock (progress.Mutex) { @@ -1187,7 +592,7 @@ namespace UVtools.Core // Check for overhangs if (overhangConfig.Enabled && !overhangConfig.IndependentFromIslands && island is null - || !ReferenceEquals(island, null) && islandConfig.EnhancedDetection && pixelsSupportingIsland >= 10 + || !ReferenceEquals(island, null) && islandConfig.EnhancedDetection && pixelsSupportingIsland >= 10 ) { points.Clear(); @@ -1278,7 +683,7 @@ namespace UVtools.Core { AddIssue(new LayerIssue( layer, LayerIssue.IssueType.Overhang, vecPoints.ToArray(), layer.BoundingRectangle - )); + )); } } } @@ -1559,495 +964,10 @@ namespace UVtools.Core return result.OrderBy(issue => issue.Type).ThenBy(issue => issue.LayerIndex).ThenBy(issue => issue.PixelsCount).ToList(); } - public void RepairLayers(OperationRepairLayers operation, OperationProgress progress = null) - { - if(ReferenceEquals(progress, null)) progress = new OperationProgress(); - - - var issues = operation.Issues; - - // Remove islands - if (!ReferenceEquals(issues, null) - && !ReferenceEquals(operation.IslandDetectionConfig, null) - && operation.RepairIslands - && operation.RemoveIslandsBelowEqualPixelCount > 0 - && operation.RemoveIslandsRecursiveIterations != 1) - { - progress.Reset("Removed recursive islands", 0); - ushort limit = operation.RemoveIslandsRecursiveIterations == 0 - ? ushort.MaxValue - : operation.RemoveIslandsRecursiveIterations; - - var recursiveIssues = issues; - ConcurrentBag<uint> islandsToRecompute = null; - - var islandConfig = operation.IslandDetectionConfig; - var overhangConfig = new OverhangDetectionConfiguration(false); - var touchingBoundsConfig = new TouchingBoundDetectionConfiguration(false); - var resinTrapsConfig = new ResinTrapDetectionConfiguration(false); - var emptyLayersConfig = false; - - islandConfig.Enabled = true; - islandConfig.RequiredAreaToProcessCheck = (byte) Math.Ceiling(operation.RemoveIslandsBelowEqualPixelCount/2m); - - for (uint i = 0; i < limit; i++) - { - if (i > 0) - { - /*var whiteList = islandsToRecompute.GroupBy(u => u) - .Select(grp => grp.First()) - .ToList();*/ - islandConfig.WhiteListLayers = islandsToRecompute.ToList(); - recursiveIssues = GetAllIssues(islandConfig, overhangConfig, resinTrapsConfig, touchingBoundsConfig, emptyLayersConfig); - //Debug.WriteLine(i); - } - - var issuesGroup = - recursiveIssues - .Where(issue => issue.Type == LayerIssue.IssueType.Island && - issue.Pixels.Length <= operation.RemoveIslandsBelowEqualPixelCount) - .GroupBy(issue => issue.LayerIndex); - - if (!issuesGroup.Any()) break; // Nothing to process - islandsToRecompute = new ConcurrentBag<uint>(); - Parallel.ForEach(issuesGroup, group => - { - if (progress.Token.IsCancellationRequested) return; - Layer layer = this[group.Key]; - Mat image = layer.LayerMat; - Span<byte> bytes = image.GetPixelSpan<byte>(); - foreach (var issue in group) - { - foreach (var issuePixel in issue.Pixels) - { - bytes[image.GetPixelPos(issuePixel)] = 0; - } - - lock (progress.Mutex) - { - progress++; - } - } - - var nextLayerIndex = group.Key + 1; - if(nextLayerIndex < Count) - islandsToRecompute.Add(nextLayerIndex); - - layer.LayerMat = image; - }); - - if (islandsToRecompute.IsEmpty) break; // No more leftovers - } - } - - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - if (operation.RepairIslands || operation.RepairResinTraps) - { - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - Layer layer = this[layerIndex]; - Mat image = null; - - void initImage() - { - if(image is null) - image = layer.LayerMat; - } - - if (!ReferenceEquals(issues, null)) - { - if (operation.RepairIslands && operation.RemoveIslandsBelowEqualPixelCount > 0 && operation.RemoveIslandsRecursiveIterations == 1) - { - Span<byte> bytes = null; - foreach (var issue in issues) - { - if ( - issue.LayerIndex != layerIndex || - issue.Type != LayerIssue.IssueType.Island || - issue.Pixels.Length > operation.RemoveIslandsBelowEqualPixelCount) continue; - - initImage(); - if(bytes == null) - bytes = image.GetPixelSpan<byte>(); - - foreach (var issuePixel in issue.Pixels) - { - bytes[image.GetPixelPos(issuePixel)] = 0; - } - } - /*if (issues.TryGetValue((uint)layerIndex, out var issueList)) - { - var bytes = image.GetPixelSpan<byte>(); - foreach (var issue in issueList.Where(issue => - issue.Type == LayerIssue.IssueType.Island && issue.Pixels.Length <= removeIslandsBelowEqualPixels)) - { - foreach (var issuePixel in issue.Pixels) - { - bytes[image.GetPixelPos(issuePixel)] = 0; - } - } - }*/ - } - - if (operation.RepairResinTraps) - { - foreach (var issue in issues.Where(issue => issue.LayerIndex == layerIndex && issue.Type == LayerIssue.IssueType.ResinTrap)) - { - initImage(); - using (var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels))) - { - CvInvoke.DrawContours(image, - vec, - -1, - new MCvScalar(255), - -1); - } - } - } - } - - if (operation.RepairIslands && (operation.GapClosingIterations > 0 || operation.NoiseRemovalIterations > 0)) - { - initImage(); - using (Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), - new Point(-1, -1))) - { - if (operation.GapClosingIterations > 0) - { - CvInvoke.MorphologyEx(image, image, MorphOp.Close, kernel, new Point(-1, -1), - (int)operation.GapClosingIterations, BorderType.Default, new MCvScalar()); - } - - if (operation.NoiseRemovalIterations > 0) - { - CvInvoke.MorphologyEx(image, image, MorphOp.Open, kernel, new Point(-1, -1), - (int)operation.NoiseRemovalIterations, BorderType.Default, new MCvScalar()); - } - } - } - - if (!ReferenceEquals(image, null)) - { - layer.LayerMat = image; - image.Dispose(); - } - - lock (progress.Mutex) - { - progress++; - } - }); - } - - if (operation.RemoveEmptyLayers) - { - List<uint> removeLayers = new List<uint>(); - for (uint layerIndex = operation.LayerIndexStart; layerIndex <= operation.LayerIndexEnd; layerIndex++) - { - if (this[layerIndex].NonZeroPixelCount == 0) - { - removeLayers.Add(layerIndex); - } - } - - if (removeLayers.Count > 0) - { - RemoveLayers(removeLayers); - } - } - - progress.Token.ThrowIfCancellationRequested(); - } - - public void Import(OperationLayerImport operation, OperationProgress progress = null) - { - if (progress is null) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, (uint)operation.Count); - - var oldLayers = Layers; - uint newLayerCount = operation.CalculateTotalLayers((uint) Layers.Length); - uint startIndex = operation.LayerIndexStart; - Layers = new Layer[newLayerCount]; - - // Keep same layers up to InsertAfterLayerIndex - for (uint i = 0; i <= operation.InsertAfterLayerIndex; i++) - { - Layers[i] = oldLayers[i]; - } - - // Keep all old layers if not discarding them - if (operation.ReplaceSubsequentLayers) - { - if (!operation.DiscardRemainingLayers) - { - for (uint i = operation.InsertAfterLayerIndex + 1; i < oldLayers.Length; i++) - { - Layers[i] = oldLayers[i]; - } - } - } - else // Push remaining layers to the end of imported layers - { - uint oldLayerIndex = operation.InsertAfterLayerIndex; - for (uint i = operation.LayerIndexEnd + 1; i < newLayerCount; i++) - { - oldLayerIndex++; - Layers[i] = oldLayers[oldLayerIndex]; - } - } - - - Parallel.For(0, operation.Count, - //new ParallelOptions{MaxDegreeOfParallelism = 1}, - i => - { - var mat = CvInvoke.Imread(operation.Files[i].TagString, ImreadModes.Grayscale); - uint layerIndex = (uint) (startIndex + i); - if (operation.MergeImages) - { - if (!(this[layerIndex] is null)) - { - using (var oldMat = this[layerIndex].LayerMat) - { - CvInvoke.Add(oldMat, mat, mat); - } - } - } - this[layerIndex] = new Layer(layerIndex, mat, this); - - lock (progress.Mutex) - { - progress++; - } - }); - - SlicerFile.LayerCount = Count; - BoundingRectangle = Rectangle.Empty; - SlicerFile.RequireFullEncode = true; - - RebuildLayersProperties(); - - progress.Token.ThrowIfCancellationRequested(); - } - - public void CloneLayer(OperationLayerClone operation, OperationProgress progress = null) - { - var oldLayers = Layers; - - uint totalClones = (operation.LayerIndexEnd - operation.LayerIndexStart + 1) * operation.Clones; - uint newLayerCount = Count + totalClones; - Layers = new Layer[newLayerCount]; - - progress.Reset(operation.ProgressAction, totalClones); - - uint newLayerIndex = 0; - for (uint layerIndex = 0; layerIndex < oldLayers.Length; layerIndex++) - { - Layers[newLayerIndex] = oldLayers[layerIndex]; - if (layerIndex >= operation.LayerIndexStart && layerIndex <= operation.LayerIndexEnd) - { - for (uint i = 0; i < operation.Clones; i++) - { - newLayerIndex++; - Layers[newLayerIndex] = oldLayers[layerIndex].Clone(); - Layers[newLayerIndex].IsModified = true; - - progress++; - } - } - - newLayerIndex++; - } - - SlicerFile.LayerCount = Count; - BoundingRectangle = Rectangle.Empty; - SlicerFile.RequireFullEncode = true; - - RebuildLayersProperties(); - - progress.Token.ThrowIfCancellationRequested(); - } - - public void RemoveLayer(uint layerIndex) => RemoveLayers(new OperationLayerRemove - { - LayerIndexStart = layerIndex, - LayerIndexEnd = layerIndex, - }); - - public void RemoveLayers(OperationLayerRemove operation, OperationProgress progress = null) - { - if(progress is null) - progress = new OperationProgress(false); - var layersRemove = new List<uint>(); - for (uint layerIndex = operation.LayerIndexStart; layerIndex <= operation.LayerIndexEnd; layerIndex++) - { - layersRemove.Add(layerIndex); - } - - RemoveLayers(layersRemove, progress); - } - - public void RemoveLayers(List<uint> layersRemove, OperationProgress progress = null) - { - if (layersRemove.Count == 0) return; - - if (progress is null) - progress = new OperationProgress(false); - - progress.Reset("removed layers", (uint) layersRemove.Count); - - var oldLayers = Layers; - float layerHeight = SlicerFile.LayerHeight; - - Layers = new Layer[Count - layersRemove.Count]; - - // Re-set - uint newLayerIndex = 0; - for (uint layerIndex = 0; layerIndex < oldLayers.Length; layerIndex++) - { - if (layersRemove.Contains(layerIndex)) continue; - Layers[newLayerIndex] = oldLayers[layerIndex]; - Layers[newLayerIndex].Index = newLayerIndex; - - // Re-Z - float posZ = layerHeight; - if (newLayerIndex > 0) - { - if (oldLayers[layerIndex - 1].PositionZ == oldLayers[layerIndex].PositionZ) - { - posZ = Layers[newLayerIndex - 1].PositionZ; - } - else - { - posZ = (float)Math.Round(Layers[newLayerIndex - 1].PositionZ + layerHeight, 2); - } - } - - Layers[newLayerIndex].PositionZ = posZ; - Layers[newLayerIndex].IsModified = true; - - newLayerIndex++; - progress++; - } - - SlicerFile.LayerCount = Count; - BoundingRectangle = Rectangle.Empty; - SlicerFile.RequireFullEncode = true; - } - - public void ReHeight(OperationLayerReHeight operation, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.Item.LayerCount); - - var oldLayers = Layers; - - Layers = new Layer[operation.Item.LayerCount]; - - uint newLayerIndex = 0; - for (uint layerIndex = 0; layerIndex < oldLayers.Length; layerIndex++) - { - var oldLayer = oldLayers[layerIndex]; - if (operation.Item.IsDivision) - { - for (byte i = 0; i < operation.Item.Modifier; i++) - { - var newLayer = oldLayer.Clone(); - newLayer.Index = newLayerIndex; - newLayer.PositionZ = (float) (operation.Item.LayerHeight * (newLayerIndex + 1)); - Layers[newLayerIndex] = newLayer; - newLayerIndex++; - progress++; - } - } - else - { - using (var mat = oldLayers[layerIndex++].LayerMat) - { - for (byte i = 1; i < operation.Item.Modifier; i++) - { - using (var nextMat = oldLayers[layerIndex++].LayerMat) - { - CvInvoke.Add(mat, nextMat, mat); - } - } - - var newLayer = oldLayer.Clone(); - newLayer.Index = newLayerIndex; - newLayer.PositionZ = (float) (operation.Item.LayerHeight * (newLayerIndex + 1)); - newLayer.LayerMat = mat; - Layers[newLayerIndex] = newLayer; - newLayerIndex++; - layerIndex--; - progress++; - } - } - } - - - SlicerFile.LayerHeight = (float)operation.Item.LayerHeight; - SlicerFile.LayerCount = Count; - BoundingRectangle = Rectangle.Empty; - SlicerFile.RequireFullEncode = true; - } - - public void ChangeResolution(OperationChangeResolution operation, OperationProgress progress) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, Count); - - Parallel.For(0, Count, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - - this[layerIndex].ChangeResolution(operation); - - lock (progress.Mutex) - { - progress++; - } - }); - - progress.Token.ThrowIfCancellationRequested(); - - SlicerFile.ResolutionX = operation.NewResolutionX; - SlicerFile.ResolutionY = operation.NewResolutionY; - } - - public void Pattern(OperationPattern operation, OperationProgress progress = null) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerRangeCount); - - Parallel.For(operation.LayerIndexStart, operation.LayerIndexEnd + 1, layerIndex => - { - if (progress.Token.IsCancellationRequested) return; - - this[layerIndex].Pattern(operation); - - lock (progress.Mutex) - { - progress++; - } - }); - - _boundingRectangle = Rectangle.Empty; - - progress.Token.ThrowIfCancellationRequested(); - - if (operation.Anchor == Enumerations.Anchor.None) return; - var operationMove = new OperationMove(BoundingRectangle, operation.ImageWidth, operation.ImageHeight, operation.Anchor) - { - LayerIndexStart = operation.LayerIndexStart, LayerIndexEnd = operation.LayerIndexEnd - }; - Move(operationMove, progress); - - } public void DrawModifications(IList<PixelOperation> drawings, OperationProgress progress = null) { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); + progress ??= new OperationProgress(); progress.Reset("Drawings", (uint) drawings.Count); ConcurrentDictionary<uint, Mat> modifiedLayers = new ConcurrentDictionary<uint, Mat>(); @@ -2261,219 +1181,5 @@ namespace UVtools.Core #endregion - - - public void CalibrateElephantFoot(OperationCalibrateElephantFoot operation, OperationProgress progress) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, 3); - SlicerFile.SuppressRebuildProperties = true; - - Layers = new Layer[operation.BottomLayers + operation.NormalLayers]; - - SlicerFile.LayerHeight = (float) operation.LayerHeight; - SlicerFile.BottomExposureTime = (float) operation.BottomExposure; - SlicerFile.ExposureTime = (float) operation.NormalExposure; - SlicerFile.BottomLayerCount = operation.BottomLayers; - - var layers = operation.GetLayers(); - progress++; - - - var bottomLayer = new Layer(0, layers[0], this) - { - IsModified = true - }; - var layer = new Layer(0, layers[1], this) - { - IsModified = true - }; - bottomLayer.Move(new OperationMove(bottomLayer.BoundingRectangle, layers[0].Size)); - layer.Move(new OperationMove(layer.BoundingRectangle, layers[0].Size)); - progress++; - - for (uint layerIndex = 0; - layerIndex < operation.BottomLayers+operation.NormalLayers; - layerIndex++) - { - Layers[layerIndex] = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomLayer.Clone(), layer.Clone()); - } - - foreach (var mat in layers) - { - mat.Dispose(); - } - - SlicerFile.LayerCount = Count; - RebuildLayersProperties(); - - BoundingRectangle = Rectangle.Empty; - - if(SlicerFile.ThumbnailsCount > 0) - SlicerFile.SetThumbnails(operation.GetThumbnail()); - - progress++; - - SlicerFile.SuppressRebuildProperties = false; - } - - public void CalibrateXYZAccuracy(OperationCalibrateXYZAccuracy operation, OperationProgress progress) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerCount); - - SlicerFile.SuppressRebuildProperties = true; - - Layers = new Layer[operation.LayerCount]; - - SlicerFile.LayerHeight = (float)operation.LayerHeight; - SlicerFile.BottomExposureTime = (float)operation.BottomExposure; - SlicerFile.ExposureTime = (float)operation.NormalExposure; - SlicerFile.BottomLayerCount = operation.BottomLayers; - - var layers = operation.GetLayers(); - - var bottomLayer = new Layer(0, layers[0], this) - { - IsModified = true - }; - var layer = new Layer(0, layers[1], this) - { - IsModified = true - }; - - for (uint layerIndex = 0; layerIndex < operation.LayerCount; layerIndex++) - { - Layers[layerIndex] = SlicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomLayer.Clone(), layer.Clone()); - progress++; - } - - foreach (var mat in layers) - { - mat.Dispose(); - } - - SlicerFile.LayerCount = Count; - RebuildLayersProperties(); - - BoundingRectangle = Rectangle.Empty; - - if (SlicerFile.ThumbnailsCount > 0) - SlicerFile.SetThumbnails(operation.GetThumbnail()); - - SlicerFile.SuppressRebuildProperties = false; - } - - public void CalibrateGrayscale(OperationCalibrateGrayscale operation, OperationProgress progress) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerCount); - SlicerFile.SuppressRebuildProperties = true; - - Layers = new Layer[operation.LayerCount]; - - SlicerFile.LayerHeight = (float)operation.LayerHeight; - SlicerFile.BottomExposureTime = (float)operation.BottomExposure; - SlicerFile.ExposureTime = (float)operation.NormalExposure; - SlicerFile.BottomLayerCount = operation.BottomLayers; - - var layers = operation.GetLayers(); - progress++; - - - var bottomLayer = new Layer(0, layers[0], this) - { - IsModified = true - }; - var interfaceLayer = operation.InterfaceLayers > 0 && layers[1] is not null ? new Layer(0, layers[1], this) - { - IsModified = true - } : null; - var layer = new Layer(0, layers[2], this) - { - IsModified = true - }; - - uint layerIndex = 0; - for (uint i = 0; i < operation.BottomLayers; i++) - { - Layers[layerIndex] = bottomLayer.Clone(); - progress++; - layerIndex++; - } - - for (uint i = 0; i < operation.InterfaceLayers; i++) - { - Layers[layerIndex] = interfaceLayer.Clone(); - progress++; - layerIndex++; - } - - - for (uint i = 0; i < operation.NormalLayers; i++) - { - Layers[layerIndex] = layer.Clone(); - progress++; - layerIndex++; - } - - foreach (var mat in layers) - { - mat?.Dispose(); - } - - SlicerFile.LayerCount = Count; - RebuildLayersProperties(); - - BoundingRectangle = Rectangle.Empty; - - if (SlicerFile.ThumbnailsCount > 0) - SlicerFile.SetThumbnails(operation.GetThumbnail()); - - progress++; - - SlicerFile.SuppressRebuildProperties = false; - } - - public void CalibrateTolerance(OperationCalibrateTolerance operation, OperationProgress progress) - { - if (ReferenceEquals(progress, null)) progress = new OperationProgress(); - progress.Reset(operation.ProgressAction, operation.LayerCount); - SlicerFile.SuppressRebuildProperties = true; - - Layers = new Layer[operation.LayerCount]; - - SlicerFile.LayerHeight = (float)operation.LayerHeight; - SlicerFile.BottomExposureTime = (float)operation.BottomExposure; - SlicerFile.ExposureTime = (float)operation.NormalExposure; - SlicerFile.BottomLayerCount = operation.BottomLayers; - - var layers = operation.GetLayers(); - - Parallel.For(0, operation.LayerCount, layerIndex => - { - Layers[layerIndex] = new Layer((uint) layerIndex, layers[layerIndex], this); - layers[layerIndex].Dispose(); - lock (progress) - { - progress++; - } - }); - - SlicerFile.LayerCount = Count; - RebuildLayersProperties(); - - BoundingRectangle = Rectangle.Empty; - Move(new OperationMove(BoundingRectangle, SlicerFile.Resolution) - { - IsCutMove = true, - LayerIndexEnd = Count-1 - }, progress); - - if (SlicerFile.ThumbnailsCount > 0) - SlicerFile.SetThumbnails(operation.GetThumbnail()); - - SlicerFile.SuppressRebuildProperties = false; - } } } diff --git a/UVtools.Core/Operations/Operation.cs b/UVtools.Core/Operations/Operation.cs index 2786c0d..fabeb31 100644 --- a/UVtools.Core/Operations/Operation.cs +++ b/UVtools.Core/Operations/Operation.cs @@ -8,8 +8,10 @@ using System; using System.Drawing; +using System.Reflection.Metadata.Ecma335; using System.Xml.Serialization; using Emgu.CV; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -183,6 +185,16 @@ namespace UVtools.Core.Operations } } + public virtual bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + throw new NotImplementedException(); + } + + public virtual bool Execute(Mat mat, params object[] arguments) + { + throw new NotImplementedException(); + } + public virtual void Dispose() { } } } diff --git a/UVtools.Core/Operations/OperationArithmetic.cs b/UVtools.Core/Operations/OperationArithmetic.cs index 00f9f61..fc6fe69 100644 --- a/UVtools.Core/Operations/OperationArithmetic.cs +++ b/UVtools.Core/Operations/OperationArithmetic.cs @@ -9,8 +9,10 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; using Emgu.CV; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -18,8 +20,11 @@ namespace UVtools.Core.Operations [Serializable] public class OperationArithmetic : Operation { + #region Members private string _sentence; + #endregion + #region Enums public enum ArithmeticOperators : byte { None, @@ -31,7 +36,9 @@ namespace UVtools.Core.Operations BitwiseOr, BitwiseXor } + #endregion + #region SubClasses public sealed class ArithmeticOperation { public uint LayerIndex { get; } @@ -43,7 +50,9 @@ namespace UVtools.Core.Operations Operator = arithmeticOperator; } } + #endregion + #region Overrides public override string Title => "Arithmetic"; public override string Description => "Perform arithmetic operations over the layers pixels.\n\n" + @@ -81,19 +90,30 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + public override string ToString() + { + var result = $"{_sentence}" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion + + #region Properties public string Sentence { get => _sentence; set => RaiseAndSetIfChanged(ref _sentence, value); } - [XmlIgnore] - public List<ArithmeticOperation> Operations { get; } = new List<ArithmeticOperation>(); + public List<ArithmeticOperation> Operations { get; } = new(); [XmlIgnore] public List<uint> SetLayers { get; } = new List<uint>(); public bool IsValid => SetLayers.Count > 0 & Operations.Count > 0; + #endregion + + #region Methods public bool Parse() { @@ -185,13 +205,63 @@ namespace UVtools.Core.Operations return true; } - public override string ToString() + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) { - var result = $"{_sentence}" + LayerRangeString; - if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; - return result; + if (!IsValid) return false; + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, (uint)Operations.Count); + + using Mat result = slicerFile[Operations[0].LayerIndex].LayerMat; + Mat resultRoi = GetRoiOrDefault(result); + for (int i = 1; i < Operations.Count; i++) + { + using var image = slicerFile[Operations[i].LayerIndex].LayerMat; + Mat imageRoi = GetRoiOrDefault(image); + switch (Operations[i - 1].Operator) + { + case ArithmeticOperators.Add: + CvInvoke.Add(resultRoi, imageRoi, resultRoi); + break; + case ArithmeticOperators.Subtract: + CvInvoke.Subtract(resultRoi, imageRoi, resultRoi); + break; + case ArithmeticOperators.Multiply: + CvInvoke.Multiply(resultRoi, imageRoi, resultRoi); + break; + case ArithmeticOperators.Divide: + CvInvoke.Divide(resultRoi, imageRoi, resultRoi); + break; + case ArithmeticOperators.BitwiseAnd: + CvInvoke.BitwiseAnd(resultRoi, imageRoi, resultRoi); + break; + case ArithmeticOperators.BitwiseOr: + CvInvoke.BitwiseOr(resultRoi, imageRoi, resultRoi); + break; + case ArithmeticOperators.BitwiseXor: + CvInvoke.BitwiseXor(resultRoi, imageRoi, resultRoi); + break; + } + } + + Parallel.ForEach(SetLayers, layerIndex => + { + if (Operations.Count == 1 && HaveROI) + { + var mat = slicerFile[layerIndex].LayerMat; + var matRoi = GetRoiOrDefault(mat); + resultRoi.CopyTo(matRoi); + slicerFile[layerIndex].LayerMat = mat; + return; + } + slicerFile[layerIndex].LayerMat = result; + }); + + return true; } + #endregion + + #region Equality protected bool Equals(OperationArithmetic other) { return _sentence == other._sentence; @@ -209,5 +279,6 @@ namespace UVtools.Core.Operations { return (_sentence != null ? _sentence.GetHashCode() : 0); } + #endregion } } diff --git a/UVtools.Core/Operations/OperationBlur.cs b/UVtools.Core/Operations/OperationBlur.cs index f8c1e58..a5237e6 100644 --- a/UVtools.Core/Operations/OperationBlur.cs +++ b/UVtools.Core/Operations/OperationBlur.cs @@ -7,8 +7,12 @@ */ using System; +using System.Drawing; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; +using Emgu.CV; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -120,6 +124,66 @@ namespace UVtools.Core.Operations #endregion + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using (var mat = slicerFile[layerIndex].LayerMat) + { + Execute(mat); + slicerFile[layerIndex].LayerMat = mat; + } + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + Size size = new Size((int)Size, (int)Size); + Point anchor = Kernel.Anchor; + if (anchor.IsEmpty) anchor = new Point(-1, -1); + //if (size.IsEmpty) size = new Size(3, 3); + //if (anchor.IsEmpty) anchor = new Point(-1, -1); + Mat target = GetRoiOrDefault(mat); + switch (BlurOperation) + { + case BlurAlgorithm.Blur: + CvInvoke.Blur(target, target, size, Kernel.Anchor); + break; + case BlurAlgorithm.Pyramid: + CvInvoke.PyrDown(target, target); + CvInvoke.PyrUp(target, target); + break; + case BlurAlgorithm.MedianBlur: + CvInvoke.MedianBlur(target, target, (int)Size); + break; + case BlurAlgorithm.GaussianBlur: + CvInvoke.GaussianBlur(target, target, size, 0); + break; + case BlurAlgorithm.Filter2D: + CvInvoke.Filter2D(target, target, Kernel.Matrix, anchor); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return true; + } + + + #endregion + #region Equality private bool Equals(OperationBlur other) { diff --git a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs index 3c477a6..49bcd2a 100644 --- a/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs +++ b/UVtools.Core/Operations/OperationCalibrateElephantFoot.cs @@ -16,6 +16,7 @@ using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.Util; using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -523,8 +524,7 @@ namespace UVtools.Core.Operations new Rectangle(new Point(currentX, currentY), shape.Size))) using (var erode = new Mat()) { - CvInvoke.Erode(shape, erode, ErodeKernel.Matrix, ErodeKernel.Anchor, iteration, BorderType.Reflect101, - default); + CvInvoke.Erode(shape, erode, ErodeKernel.Matrix, ErodeKernel.Anchor, iteration, BorderType.Reflect101, default); erode.CopyTo(roi); addText(roi, count, $"E: {iteration}i"); } @@ -610,6 +610,65 @@ namespace UVtools.Core.Operations return thumbnail; } + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, 3); + slicerFile.SuppressRebuildProperties = true; + + var newLayers = new Layer[LayerCount]; + + slicerFile.LayerHeight = (float)LayerHeight; + slicerFile.BottomExposureTime = (float)BottomExposure; + slicerFile.ExposureTime = (float)NormalExposure; + slicerFile.BottomLayerCount = BottomLayers; + + var layers = GetLayers(); + progress++; + + + var bottomLayer = new Layer(0, layers[0], slicerFile.LayerManager) + { + IsModified = true + }; + var layer = new Layer(0, layers[1], slicerFile.LayerManager) + { + IsModified = true + }; + var moveOp = new OperationMove(bottomLayer.BoundingRectangle, layers[0].Size); + moveOp.Execute(layers[0]); + moveOp.Execute(layers[1]); + + bottomLayer.LayerMat = layers[0]; + layer.LayerMat = layers[1]; + + progress++; + + for (uint layerIndex = 0; + layerIndex < LayerCount; + layerIndex++) + { + newLayers[layerIndex] = slicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomLayer.Clone(), layer.Clone()); + } + + foreach (var mat in layers) + { + mat.Dispose(); + } + + + if (slicerFile.ThumbnailsCount > 0) + slicerFile.SetThumbnails(GetThumbnail()); + + progress++; + + slicerFile.LayerManager.Layers = newLayers; + slicerFile.SuppressRebuildProperties = false; + slicerFile.LayerManager.RebuildLayersProperties(); + + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs index 4e9a015..5ddddf7 100644 --- a/UVtools.Core/Operations/OperationCalibrateGrayscale.cs +++ b/UVtools.Core/Operations/OperationCalibrateGrayscale.cs @@ -15,6 +15,7 @@ using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.CV.Util; using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -437,6 +438,78 @@ namespace UVtools.Core.Operations return thumbnail; } + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerCount); + slicerFile.SuppressRebuildProperties = true; + + var newLayers = new Layer[LayerCount]; + + slicerFile.LayerHeight = (float)LayerHeight; + slicerFile.BottomExposureTime = (float)BottomExposure; + slicerFile.ExposureTime = (float)NormalExposure; + slicerFile.BottomLayerCount = BottomLayers; + + var layers = GetLayers(); + progress++; + + + var bottomLayer = new Layer(0, layers[0], slicerFile.LayerManager) + { + IsModified = true + }; + var interfaceLayer = InterfaceLayers > 0 && layers[1] is not null ? new Layer(0, layers[1], slicerFile.LayerManager) + { + IsModified = true + } : null; + var layer = new Layer(0, layers[2], slicerFile.LayerManager) + { + IsModified = true + }; + + uint layerIndex = 0; + for (uint i = 0; i < BottomLayers; i++) + { + newLayers[layerIndex] = bottomLayer.Clone(); + progress++; + layerIndex++; + } + + for (uint i = 0; i < InterfaceLayers; i++) + { + newLayers[layerIndex] = interfaceLayer.Clone(); + progress++; + layerIndex++; + } + + + for (uint i = 0; i < NormalLayers; i++) + { + newLayers[layerIndex] = layer.Clone(); + progress++; + layerIndex++; + } + + foreach (var mat in layers) + { + mat?.Dispose(); + } + + + if (slicerFile.ThumbnailsCount > 0) + slicerFile.SetThumbnails(GetThumbnail()); + + progress++; + + + slicerFile.LayerManager.Layers = newLayers; + slicerFile.SuppressRebuildProperties = false; + slicerFile.LayerManager.RebuildLayersProperties(); + + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationCalibrateTolerance.cs b/UVtools.Core/Operations/OperationCalibrateTolerance.cs index 82b3dfe..a7fc0ff 100644 --- a/UVtools.Core/Operations/OperationCalibrateTolerance.cs +++ b/UVtools.Core/Operations/OperationCalibrateTolerance.cs @@ -16,6 +16,7 @@ using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -474,8 +475,6 @@ namespace UVtools.Core.Operations #endregion #region Methods - - public Mat[] GetLayers() { var layers = new Mat[LayerCount]; @@ -695,6 +694,49 @@ namespace UVtools.Core.Operations return thumbnail; } + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerCount); + slicerFile.SuppressRebuildProperties = true; + + var newLayers = new Layer[LayerCount]; + + slicerFile.LayerHeight = (float)LayerHeight; + slicerFile.BottomExposureTime = (float)BottomExposure; + slicerFile.ExposureTime = (float)NormalExposure; + slicerFile.BottomLayerCount = BottomLayers; + + var layers = GetLayers(); + + Parallel.For(0, LayerCount, layerIndex => + { + newLayers[layerIndex] = new Layer((uint)layerIndex, layers[layerIndex], slicerFile.LayerManager); + layers[layerIndex].Dispose(); + lock (progress) + { + progress++; + } + }); + + slicerFile.LayerManager.Layers = newLayers; + slicerFile.LayerManager.RebuildLayersProperties(); + + var moveOp = new OperationMove(slicerFile.LayerManager.BoundingRectangle, slicerFile.Resolution) + { + IsCutMove = true, + LayerIndexEnd = slicerFile.LayerCount - 1 + }; + moveOp.Execute(slicerFile, progress); + + if (slicerFile.ThumbnailsCount > 0) + slicerFile.SetThumbnails(GetThumbnail()); + + slicerFile.SuppressRebuildProperties = false; + + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs index df0fe59..7ac8790 100644 --- a/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs +++ b/UVtools.Core/Operations/OperationCalibrateXYZAccuracy.cs @@ -14,6 +14,7 @@ using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -703,6 +704,51 @@ namespace UVtools.Core.Operations return thumbnail; } + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerCount); + + slicerFile.SuppressRebuildProperties = true; + + var newLayers = new Layer[LayerCount]; + + slicerFile.LayerHeight = (float)LayerHeight; + slicerFile.BottomExposureTime = (float)BottomExposure; + slicerFile.ExposureTime = (float)NormalExposure; + slicerFile.BottomLayerCount = BottomLayers; + + var layers = GetLayers(); + + var bottomLayer = new Layer(0, layers[0], slicerFile.LayerManager) + { + IsModified = true + }; + var layer = new Layer(0, layers[1], slicerFile.LayerManager) + { + IsModified = true + }; + + for (uint layerIndex = 0; layerIndex < LayerCount; layerIndex++) + { + newLayers[layerIndex] = slicerFile.GetInitialLayerValueOrNormal(layerIndex, bottomLayer.Clone(), layer.Clone()); + progress++; + } + + foreach (var mat in layers) + { + mat.Dispose(); + } + + if (slicerFile.ThumbnailsCount > 0) + slicerFile.SetThumbnails(GetThumbnail()); + + slicerFile.LayerManager.Layers = newLayers; + slicerFile.LayerManager.RebuildLayersProperties(); + slicerFile.SuppressRebuildProperties = false; + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationChangeResolution.cs b/UVtools.Core/Operations/OperationChangeResolution.cs index da9160c..fe4d3cf 100644 --- a/UVtools.Core/Operations/OperationChangeResolution.cs +++ b/UVtools.Core/Operations/OperationChangeResolution.cs @@ -9,6 +9,9 @@ using System; using System.Drawing; using System.Text; +using System.Threading.Tasks; +using Emgu.CV; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -16,8 +19,10 @@ namespace UVtools.Core.Operations [Serializable] public sealed class OperationChangeResolution : Operation { + #region Members private uint _newResolutionX; private uint _newResolutionY; + #endregion #region Subclasses public class Resolution @@ -84,6 +89,13 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + public override string ToString() + { + var result = $"{_newResolutionX} x {_newResolutionY}"; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion #region Properties @@ -146,13 +158,55 @@ namespace UVtools.Core.Operations public static Resolution[] Presets => GetResolutions(); - public override string ToString() + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) { - var result = $"{_newResolutionX} x {_newResolutionY}"; - if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; - return result; + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, slicerFile.LayerCount); + + Parallel.For(0, slicerFile.LayerCount, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + + using var mat = slicerFile[layerIndex].LayerMat; + using var matRoi = new Mat(mat, VolumeBonds); + using var matDst = new Mat(new Size((int)NewResolutionX, (int)NewResolutionY), mat.Depth, mat.NumberOfChannels); + using var matDstRoi = new Mat(matDst, + new Rectangle((int)(NewResolutionX / 2 - VolumeBonds.Width / 2), + (int)NewResolutionY / 2 - VolumeBonds.Height / 2, + VolumeBonds.Width, VolumeBonds.Height)); + matRoi.CopyTo(matDstRoi); + //Execute(mat); + slicerFile[layerIndex].LayerMat = matDst; + + lock (progress.Mutex) + { + progress++; + } + }); + + progress.Token.ThrowIfCancellationRequested(); + + slicerFile.ResolutionX = NewResolutionX; + slicerFile.ResolutionY = NewResolutionY; + + return true; } + /*public override bool Execute(Mat mat, params object[] arguments) + { + //mat.Transform(1, 1, newResolutionX-mat.Width+roi.Width, newResolutionY-mat.Height - roi.Height/2, new Size((int) newResolutionX, (int) newResolutionY)); + using var matRoi = new Mat(mat, VolumeBonds); + using var matDst = new Mat(new Size((int)NewResolutionX, (int)NewResolutionY), mat.Depth, mat.NumberOfChannels); + using var matDstRoi = new Mat(matDst, + new Rectangle((int)(NewResolutionX / 2 - VolumeBonds.Width / 2), + (int)NewResolutionY / 2 - VolumeBonds.Height / 2, + VolumeBonds.Width, VolumeBonds.Height)); + matRoi.CopyTo(matDstRoi); + + return true; + }*/ + #endregion #region Equality diff --git a/UVtools.Core/Operations/OperationEditParameters.cs b/UVtools.Core/Operations/OperationEditParameters.cs index 7f774a6..b31e017 100644 --- a/UVtools.Core/Operations/OperationEditParameters.cs +++ b/UVtools.Core/Operations/OperationEditParameters.cs @@ -17,8 +17,11 @@ namespace UVtools.Core.Operations [Serializable] public class OperationEditParameters : Operation { + #region Members private bool _perLayerOverride; + #endregion + #region Overrides public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; public override bool CanROI => false; @@ -75,6 +78,9 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + #endregion + + #region Propertiers public FileFormat.PrintParameterModifier[] Modifiers { get; set; } @@ -86,6 +92,7 @@ namespace UVtools.Core.Operations get => _perLayerOverride; set => RaiseAndSetIfChanged(ref _perLayerOverride, value); } + #endregion public OperationEditParameters() { @@ -95,5 +102,15 @@ namespace UVtools.Core.Operations { Modifiers = modifiers; } + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + slicerFile.EditPrintParameters(this); + return true; + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationFlip.cs b/UVtools.Core/Operations/OperationFlip.cs index 7a63814..5381d1b 100644 --- a/UVtools.Core/Operations/OperationFlip.cs +++ b/UVtools.Core/Operations/OperationFlip.cs @@ -7,16 +7,22 @@ */ using System; +using System.Threading.Tasks; +using Emgu.CV; using Emgu.CV.CvEnum; +using UVtools.Core.FileFormats; namespace UVtools.Core.Operations { [Serializable] public class OperationFlip : Operation { + #region Members private bool _makeCopy; private Enumerations.FlipDirection _flipDirection = Enumerations.FlipDirection.Horizontally; + #endregion + #region Overrides public override string Title => "Flip"; public override string Description => "Flip the layers of the model vertically and/or horizontally.\n" + @@ -34,6 +40,16 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Flipped layers"; + public override string ToString() + { + var result = $"[{_flipDirection}] [Blend: {_makeCopy}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion + + #region Properties + public Enumerations.FlipDirection FlipDirection { get => _flipDirection; @@ -69,13 +85,7 @@ namespace UVtools.Core.Operations return flipType; } } - - public override string ToString() - { - var result = $"[{_flipDirection}] [Blend: {_makeCopy}]" + LayerRangeString; - if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; - return result; - } + #endregion #region Equality @@ -101,5 +111,45 @@ namespace UVtools.Core.Operations } #endregion + + #region Methods + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using var mat = slicerFile[layerIndex].LayerMat; + Execute(mat); + slicerFile[layerIndex].LayerMat = mat; + + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + var target = GetRoiOrDefault(mat); + + if (MakeCopy) + { + using Mat dst = new Mat(); + CvInvoke.Flip(target, dst, FlipTypeOpenCV); + CvInvoke.Add(target, dst, target); + } + else + { + CvInvoke.Flip(target, target, FlipTypeOpenCV); + } + + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationInfill.cs b/UVtools.Core/Operations/OperationInfill.cs index e305338..b05891e 100644 --- a/UVtools.Core/Operations/OperationInfill.cs +++ b/UVtools.Core/Operations/OperationInfill.cs @@ -7,17 +7,26 @@ */ using System; +using System.Drawing; +using System.Threading.Tasks; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; namespace UVtools.Core.Operations { [Serializable] public sealed class OperationInfill : Operation { + #region Members private InfillAlgorithm _infillType = InfillAlgorithm.CubicDynamicLink; private ushort _wallThickness = 64; private ushort _infillThickness = 45; private ushort _infillSpacing = 160; private ushort _infillBrightness = 255; + #endregion #region Overrides @@ -89,7 +98,200 @@ namespace UVtools.Core.Operations #endregion #region Equality - + + #endregion + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + + using var mat = slicerFile[layerIndex].LayerMat; + Execute(mat, layerIndex); + slicerFile[layerIndex].LayerMat = mat; + + lock (progress.Mutex) + { + progress++; + } + }); + + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + if (arguments is null || arguments.Length < 1) return false; + var anchor = new Point(-1, -1); + var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + uint index = Convert.ToUInt32(arguments[0]); + uint layerIndex = index - LayerIndexStart; + var infillColor = new MCvScalar(InfillBrightness); + + Mat patternMask = null; + using Mat erode = new Mat(); + using Mat diff = new Mat(); + Mat target = GetRoiOrDefault(mat); + + + if (InfillType == InfillAlgorithm.Cubic || + InfillType == InfillAlgorithm.CubicCenterLink || + InfillType == InfillAlgorithm.CubicDynamicLink || + InfillType == InfillAlgorithm.CubicInterlinked) + { + using var infillPattern = EmguExtensions.InitMat(new Size(InfillSpacing, InfillSpacing)); + using Mat matPattern = mat.CloneBlank(); + bool firstPattern = true; + uint accumulator = 0; + uint step = 0; + bool dynamicCenter = false; + while (accumulator < layerIndex) + { + dynamicCenter = !dynamicCenter; + firstPattern = true; + accumulator += InfillSpacing; + + if (accumulator >= layerIndex) break; + firstPattern = false; + accumulator += InfillThickness; + } + + if (firstPattern) + { + int thickness = InfillThickness / 2; + // Top Left + CvInvoke.Rectangle(infillPattern, + new Rectangle(0, 0, thickness, thickness), + infillColor, -1); + + // Top Right + CvInvoke.Rectangle(infillPattern, + new Rectangle(infillPattern.Width - thickness, 0, thickness, thickness), + infillColor, -1); + + // Bottom Left + CvInvoke.Rectangle(infillPattern, + new Rectangle(0, infillPattern.Height - thickness, thickness, thickness), + infillColor, -1); + + // Bottom Right + CvInvoke.Rectangle(infillPattern, + new Rectangle(infillPattern.Width - thickness, infillPattern.Height - thickness, + thickness, thickness), + infillColor, -1); + + // Center cross + int margin = (int) (InfillSpacing - accumulator + layerIndex) - thickness; + int marginInv = (int) (accumulator - layerIndex) - thickness; + + if (InfillType == InfillAlgorithm.CubicCenterLink || + (InfillType == InfillAlgorithm.CubicDynamicLink && + dynamicCenter) || + InfillType == InfillAlgorithm.CubicInterlinked) + { + + CvInvoke.Rectangle(infillPattern, + new Rectangle(margin, margin, InfillThickness, InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(marginInv, marginInv, InfillThickness, + InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(margin, marginInv, InfillThickness, + InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(marginInv, margin, InfillThickness, + InfillThickness), + infillColor, -1); + } + + + if (InfillType == InfillAlgorithm.CubicInterlinked || + (InfillType == InfillAlgorithm.CubicDynamicLink && + !dynamicCenter)) + { + CvInvoke.Rectangle(infillPattern, + new Rectangle(margin, -thickness, InfillThickness, + InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(marginInv, -thickness, InfillThickness, + InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(-thickness, margin, InfillThickness, + InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(-thickness, marginInv, InfillThickness, + InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(InfillSpacing - thickness, margin, + InfillThickness, InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(InfillSpacing - thickness, marginInv, + InfillThickness, InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(margin, InfillSpacing - thickness, + InfillThickness, InfillThickness), + infillColor, -1); + + CvInvoke.Rectangle(infillPattern, + new Rectangle(marginInv, InfillSpacing - thickness, + InfillThickness, InfillThickness), + infillColor, -1); + } + + + } + else + { + CvInvoke.Rectangle(infillPattern, + new Rectangle(0, 0, InfillSpacing, InfillSpacing), + infillColor, InfillThickness); + } + + + { + CvInvoke.Repeat(infillPattern, target.Rows / infillPattern.Rows + 1, + target.Cols / infillPattern.Cols + 1, matPattern); + patternMask = new Mat(matPattern, new Rectangle(0, 0, target.Width, target.Height)); + } + } + + + CvInvoke.Erode(target, erode, kernel, anchor, WallThickness, BorderType.Reflect101, + default); + CvInvoke.Subtract(target, erode, diff); + + + CvInvoke.BitwiseAnd(erode, patternMask, target); + CvInvoke.Add(target, diff, target); + patternMask?.Dispose(); + + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationLayerClone.cs b/UVtools.Core/Operations/OperationLayerClone.cs index eb77b6c..211cb7d 100644 --- a/UVtools.Core/Operations/OperationLayerClone.cs +++ b/UVtools.Core/Operations/OperationLayerClone.cs @@ -7,7 +7,9 @@ */ using System; +using System.Drawing; using System.Text; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -15,7 +17,9 @@ namespace UVtools.Core.Operations [Serializable] public sealed class OperationLayerClone : Operation { + #region Members private uint _clones = 1; + #endregion #region Overrides @@ -50,6 +54,13 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + public override string ToString() + { + var result = $"[Clones: {Clones}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion #region Properties @@ -65,11 +76,45 @@ namespace UVtools.Core.Operations #endregion - public override string ToString() + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) { - var result = $"[Clones: {Clones}]" + LayerRangeString; - if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; - return result; + var oldLayers = slicerFile.LayerManager.Layers; + + uint totalClones = (LayerIndexEnd - LayerIndexStart + 1) * Clones; + uint newLayerCount = (uint) (oldLayers.Length + totalClones); + var layers = new Layer[newLayerCount]; + + progress.Reset(ProgressAction, totalClones); + + uint newLayerIndex = 0; + for (uint layerIndex = 0; layerIndex < oldLayers.Length; layerIndex++) + { + layers[newLayerIndex] = oldLayers[layerIndex]; + if (layerIndex >= LayerIndexStart && layerIndex <= LayerIndexEnd) + { + for (uint i = 0; i < Clones; i++) + { + newLayerIndex++; + layers[newLayerIndex] = oldLayers[layerIndex].Clone(); + layers[newLayerIndex].IsModified = true; + + progress++; + } + } + + newLayerIndex++; + } + + slicerFile.LayerManager.Layers = layers; + slicerFile.RequireFullEncode = true; + + progress.Token.ThrowIfCancellationRequested(); + + return true; } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationLayerImport.cs b/UVtools.Core/Operations/OperationLayerImport.cs index 4a22279..17c4dba 100644 --- a/UVtools.Core/Operations/OperationLayerImport.cs +++ b/UVtools.Core/Operations/OperationLayerImport.cs @@ -7,7 +7,6 @@ */ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.IO; @@ -16,6 +15,7 @@ using System.Text; using System.Threading.Tasks; using Emgu.CV; using Emgu.CV.CvEnum; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -185,6 +185,74 @@ namespace UVtools.Core.Operations return result; } + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, (uint)Count); + + var oldLayers = slicerFile.LayerManager.Layers; + uint newLayerCount = CalculateTotalLayers((uint)oldLayers.Length); + uint startIndex = LayerIndexStart; + var layers = new Layer[newLayerCount]; + + // Keep same layers up to InsertAfterLayerIndex + for (uint i = 0; i <= InsertAfterLayerIndex; i++) + { + layers[i] = oldLayers[i]; + } + + // Keep all old layers if not discarding them + if (ReplaceSubsequentLayers) + { + if (!DiscardRemainingLayers) + { + for (uint i = InsertAfterLayerIndex + 1; i < oldLayers.Length; i++) + { + layers[i] = oldLayers[i]; + } + } + } + else // Push remaining layers to the end of imported layers + { + uint oldLayerIndex = InsertAfterLayerIndex; + for (uint i = LayerIndexEnd + 1; i < newLayerCount; i++) + { + oldLayerIndex++; + layers[i] = oldLayers[oldLayerIndex]; + } + } + + + Parallel.For(0, Count, + //new ParallelOptions{MaxDegreeOfParallelism = 1}, + i => + { + var mat = CvInvoke.Imread(Files[i].TagString, ImreadModes.Grayscale); + uint layerIndex = (uint)(startIndex + i); + if (MergeImages) + { + if (layers[layerIndex] is not null) + { + using var oldMat = layers[layerIndex].LayerMat; + CvInvoke.Add(oldMat, mat, mat); + } + } + layers[layerIndex] = new Layer(layerIndex, mat, slicerFile.LayerManager); + + lock (progress.Mutex) + { + progress++; + } + }); + + slicerFile.LayerManager.Layers = layers; + slicerFile.RequireFullEncode = true; + + progress.Token.ThrowIfCancellationRequested(); + + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationLayerReHeight.cs b/UVtools.Core/Operations/OperationLayerReHeight.cs index aa9dea2..fb04a34 100644 --- a/UVtools.Core/Operations/OperationLayerReHeight.cs +++ b/UVtools.Core/Operations/OperationLayerReHeight.cs @@ -8,9 +8,10 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Text; +using Emgu.CV; using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -18,7 +19,9 @@ namespace UVtools.Core.Operations [Serializable] public sealed class OperationLayerReHeight : Operation { + #region Members private OperationLayerReHeightItem _item; + #endregion #region Overrides @@ -38,8 +41,6 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Height adjusted layers"; - public override bool CanCancel => false; - public override bool CanHaveProfiles => false; public override StringTag Validate(params object[] parameters) @@ -55,8 +56,15 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + public override string ToString() + { + var result = $"[Layer Count: {Item.LayerCount}] [Layer Height: {Item.LayerHeight}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } #endregion + #region Properties public OperationLayerReHeightItem Item { get => _item; @@ -89,8 +97,9 @@ namespace UVtools.Core.Operations return list.ToArray(); } + #endregion - + #region Subclasses public class OperationLayerReHeightItem { public bool IsMultiply { get; } @@ -112,12 +121,62 @@ namespace UVtools.Core.Operations return (IsMultiply ? 'x' : '÷') + $" {Modifier} → {LayerCount} layers at {LayerHeight}mm"; } } + #endregion - public override string ToString() + #region Methods + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) { - var result = $"[Layer Count: {Item.LayerCount}] [Layer Height: {Item.LayerHeight}]" + LayerRangeString; - if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; - return result; + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, Item.LayerCount); + + var oldLayers = slicerFile.LayerManager.Layers; + + var layers = new Layer[Item.LayerCount]; + + uint newLayerIndex = 0; + for (uint layerIndex = 0; layerIndex < oldLayers.Length; layerIndex++) + { + progress.Token.ThrowIfCancellationRequested(); + + var oldLayer = oldLayers[layerIndex]; + if (Item.IsDivision) + { + for (byte i = 0; i < Item.Modifier; i++) + { + var newLayer = oldLayer.Clone(); + newLayer.Index = newLayerIndex; + newLayer.PositionZ = (float)(Item.LayerHeight * (newLayerIndex + 1)); + layers[newLayerIndex] = newLayer; + newLayerIndex++; + progress++; + } + } + else + { + using var mat = oldLayers[layerIndex++].LayerMat; + for (byte i = 1; i < Item.Modifier; i++) + { + using var nextMat = oldLayers[layerIndex++].LayerMat; + CvInvoke.Add(mat, nextMat, mat); + } + + var newLayer = oldLayer.Clone(); + newLayer.Index = newLayerIndex; + newLayer.PositionZ = (float)(Item.LayerHeight * (newLayerIndex + 1)); + newLayer.LayerMat = mat; + layers[newLayerIndex] = newLayer; + newLayerIndex++; + layerIndex--; + progress++; + } + } + + slicerFile.LayerManager.Layers = layers; + slicerFile.LayerHeight = (float)Item.LayerHeight; + slicerFile.RequireFullEncode = true; + + return true; } + #endregion } } diff --git a/UVtools.Core/Operations/OperationLayerRemove.cs b/UVtools.Core/Operations/OperationLayerRemove.cs index 25dd621..45816dd 100644 --- a/UVtools.Core/Operations/OperationLayerRemove.cs +++ b/UVtools.Core/Operations/OperationLayerRemove.cs @@ -7,7 +7,10 @@ */ using System; +using System.Collections.Generic; using System.Text; +using Emgu.CV; +using UVtools.Core.FileFormats; namespace UVtools.Core.Operations { @@ -43,5 +46,68 @@ namespace UVtools.Core.Operations #endregion + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(false); + var layersRemove = new List<uint>(); + for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++) + { + layersRemove.Add(layerIndex); + } + + return RemoveLayers(slicerFile, layersRemove, progress); + } + + public static bool RemoveLayers(FileFormat slicerFile, List<uint> layersRemove, OperationProgress progress = null) + { + if (layersRemove.Count == 0) return false; + + progress ??= new OperationProgress(false); + + progress.Reset("removed layers", (uint)layersRemove.Count); + + var oldLayers = slicerFile.LayerManager.Layers; + float layerHeight = slicerFile.LayerHeight; + + var layers = new Layer[oldLayers.Length - layersRemove.Count]; + + // Re-set + uint newLayerIndex = 0; + for (uint layerIndex = 0; layerIndex < oldLayers.Length; layerIndex++) + { + if (layersRemove.Contains(layerIndex)) continue; + layers[newLayerIndex] = oldLayers[layerIndex]; + layers[newLayerIndex].Index = newLayerIndex; + + // Re-Z + float posZ = layerHeight; + if (newLayerIndex > 0) + { + if (oldLayers[layerIndex - 1].PositionZ == oldLayers[layerIndex].PositionZ) + { + posZ = layers[newLayerIndex - 1].PositionZ; + } + else + { + posZ = (float)Math.Round(layers[newLayerIndex - 1].PositionZ + layerHeight, 2); + } + } + + layers[newLayerIndex].PositionZ = posZ; + layers[newLayerIndex].IsModified = true; + + newLayerIndex++; + progress++; + } + + slicerFile.LayerManager.Layers = layers; + slicerFile.RequireFullEncode = true; + + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationMask.cs b/UVtools.Core/Operations/OperationMask.cs index c702371..972157f 100644 --- a/UVtools.Core/Operations/OperationMask.cs +++ b/UVtools.Core/Operations/OperationMask.cs @@ -8,9 +8,11 @@ using System; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; using Emgu.CV; using Emgu.CV.CvEnum; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -18,6 +20,7 @@ namespace UVtools.Core.Operations [Serializable] public class OperationMask : Operation { + #region Overrides public override string Title => "Mask"; public override string Description => "Mask the intensity of the LCD output using a greyscale input image.\n\n" + @@ -45,16 +48,52 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + #endregion + #region Properties [XmlIgnore] public Mat Mask { get; set; } public bool HaveMask => !(Mask is null); + #endregion + + #region Methods public void InvertMask() { if (!HaveMask) return; CvInvoke.BitwiseNot(Mask, Mask); } + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using var mat = slicerFile[layerIndex].LayerMat; + Execute(mat); + slicerFile[layerIndex].LayerMat = mat; + lock (progress.Mutex) + { + progress++; + } + }); + + progress.Token.ThrowIfCancellationRequested(); + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + var target = GetRoiOrDefault(mat); + if (Mask.Size != target.Size) return false; + CvInvoke.BitwiseAnd(target, Mask, target); + return true; + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationMorph.cs b/UVtools.Core/Operations/OperationMorph.cs index ab603d5..bdf5e8a 100644 --- a/UVtools.Core/Operations/OperationMorph.cs +++ b/UVtools.Core/Operations/OperationMorph.cs @@ -7,8 +7,11 @@ */ using System; +using System.Threading.Tasks; using System.Xml.Serialization; +using Emgu.CV; using Emgu.CV.CvEnum; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -16,11 +19,13 @@ namespace UVtools.Core.Operations [Serializable] public sealed class OperationMorph : Operation { + #region Members private MorphOp _morphOperation = MorphOp.Erode; private uint _iterationsStart = 1; private uint _iterationsEnd = 1; private bool _chamfer; - + #endregion + #region Overrides public override string Title => "Morph"; @@ -123,5 +128,57 @@ namespace UVtools.Core.Operations } #endregion + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + + var isFade = Chamfer; + LayerManager.MutateGetVarsIterationChamfer( + LayerIndexStart, + LayerIndexEnd, + (int)IterationsStart, + (int)IterationsEnd, + ref isFade, + out var iterationSteps, + out var maxIteration + ); + + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, + //new ParallelOptions {MaxDegreeOfParallelism = 1}, + layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + int iterations = LayerManager.MutateGetIterationVar(isFade, (int)IterationsStart, (int)IterationsEnd, iterationSteps, maxIteration, LayerIndexStart, (uint)layerIndex); + + using var mat = slicerFile[layerIndex].LayerMat; + Execute(mat, iterations); + slicerFile[layerIndex].LayerMat = mat; + + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + int iterations = (int) _iterationsStart; + if (arguments is not null && arguments.Length >= 1) + { + iterations = (int) arguments[0]; + } + var target = GetRoiOrDefault(mat); + CvInvoke.MorphologyEx(target, target, MorphOperation, Kernel.Matrix, Kernel.Anchor, iterations, BorderType.Reflect101, default); + return true; + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationMove.cs b/UVtools.Core/Operations/OperationMove.cs index d3141e7..09f046d 100644 --- a/UVtools.Core/Operations/OperationMove.cs +++ b/UVtools.Core/Operations/OperationMove.cs @@ -6,9 +6,12 @@ * of this license document, but changing it is not allowed. */ using System; -using System.Diagnostics; using System.Drawing; using System.Text; +using System.Threading.Tasks; +using Emgu.CV; +using Emgu.CV.Structure; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -16,6 +19,7 @@ namespace UVtools.Core.Operations [Serializable] public class OperationMove : Operation { + #region Overrides public override string Title => "Move"; public override string Description => "Change or copy the position of the model on the build plate.\n" + @@ -45,6 +49,15 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + public override string ToString() + { + var result = $"[{ROI} -> {DstRoi}] [Cut: {IsCutMove}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion + + #region Members private Rectangle _dstRoi = Rectangle.Empty; private uint _imageWidth; private uint _imageHeight; @@ -55,6 +68,9 @@ namespace UVtools.Core.Operations private int _marginBottom; private bool _isCutMove = true; private bool _isWithinBoundary; + #endregion + + #region Properties public Rectangle DstRoi { @@ -66,59 +82,6 @@ namespace UVtools.Core.Operations } } - public void CalculateDstRoi() - { - _dstRoi.Size = ROI.Size; - - switch (Anchor) - { - case Enumerations.Anchor.TopLeft: - _dstRoi.Location = new Point(0, 0); - break; - case Enumerations.Anchor.TopCenter: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - ROI.Width / 2), 0); - break; - case Enumerations.Anchor.TopRight: - _dstRoi.Location = new Point((int)(ImageWidth - ROI.Width), 0); - break; - case Enumerations.Anchor.MiddleLeft: - _dstRoi.Location = new Point(0, (int)(ImageHeight / 2 - ROI.Height / 2)); - break; - case Enumerations.Anchor.MiddleCenter: - //case Anchor.None: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - ROI.Width / 2), (int)(ImageHeight / 2 - ROI.Height / 2)); - break; - case Enumerations.Anchor.MiddleRight: - _dstRoi.Location = new Point((int)(ImageWidth - ROI.Width), (int)(ImageHeight / 2 - ROI.Height / 2)); - break; - case Enumerations.Anchor.BottomLeft: - _dstRoi.Location = new Point(0, (int)(ImageHeight - ROI.Height)); - break; - case Enumerations.Anchor.BottomCenter: - _dstRoi.Location = new Point((int)(ImageWidth / 2 - ROI.Width / 2), (int)(ImageHeight - ROI.Height)); - break; - case Enumerations.Anchor.BottomRight: - _dstRoi.Location = new Point((int)(ImageWidth - ROI.Width), (int)(ImageHeight - ROI.Height)); - break; - default: - throw new ArgumentOutOfRangeException(); - } - - _dstRoi.X += MarginLeft; - _dstRoi.X -= MarginRight; - _dstRoi.Y += MarginTop; - _dstRoi.Y -= MarginBottom; - - IsWithinBoundary = !(DstRoi.IsEmpty || DstRoi.X < 0 || DstRoi.Y < 0 || - DstRoi.Width == 0 || DstRoi.Right > ImageWidth || - DstRoi.Height == 0 || DstRoi.Bottom > ImageHeight); - - RaisePropertyChanged(nameof(DstRoi)); - RaisePropertyChanged(nameof(LocationXStr)); - RaisePropertyChanged(nameof(LocationYStr)); - } - - public uint ImageWidth { get => _imageWidth; @@ -205,6 +168,61 @@ namespace UVtools.Core.Operations public string IsWithinBoundaryStr => "Model within boundary: " + (_isWithinBoundary ? "Yes" : "No"); + #endregion + + #region Methods + public void CalculateDstRoi() + { + _dstRoi.Size = ROI.Size; + + switch (Anchor) + { + case Enumerations.Anchor.TopLeft: + _dstRoi.Location = new Point(0, 0); + break; + case Enumerations.Anchor.TopCenter: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - ROI.Width / 2), 0); + break; + case Enumerations.Anchor.TopRight: + _dstRoi.Location = new Point((int)(ImageWidth - ROI.Width), 0); + break; + case Enumerations.Anchor.MiddleLeft: + _dstRoi.Location = new Point(0, (int)(ImageHeight / 2 - ROI.Height / 2)); + break; + case Enumerations.Anchor.MiddleCenter: + //case Anchor.None: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - ROI.Width / 2), (int)(ImageHeight / 2 - ROI.Height / 2)); + break; + case Enumerations.Anchor.MiddleRight: + _dstRoi.Location = new Point((int)(ImageWidth - ROI.Width), (int)(ImageHeight / 2 - ROI.Height / 2)); + break; + case Enumerations.Anchor.BottomLeft: + _dstRoi.Location = new Point(0, (int)(ImageHeight - ROI.Height)); + break; + case Enumerations.Anchor.BottomCenter: + _dstRoi.Location = new Point((int)(ImageWidth / 2 - ROI.Width / 2), (int)(ImageHeight - ROI.Height)); + break; + case Enumerations.Anchor.BottomRight: + _dstRoi.Location = new Point((int)(ImageWidth - ROI.Width), (int)(ImageHeight - ROI.Height)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + _dstRoi.X += MarginLeft; + _dstRoi.X -= MarginRight; + _dstRoi.Y += MarginTop; + _dstRoi.Y -= MarginBottom; + + IsWithinBoundary = !(DstRoi.IsEmpty || DstRoi.X < 0 || DstRoi.Y < 0 || + DstRoi.Width == 0 || DstRoi.Right > ImageWidth || + DstRoi.Height == 0 || DstRoi.Bottom > ImageHeight); + + RaisePropertyChanged(nameof(DstRoi)); + RaisePropertyChanged(nameof(LocationXStr)); + RaisePropertyChanged(nameof(LocationYStr)); + } + public OperationMove() { } @@ -220,8 +238,8 @@ namespace UVtools.Core.Operations public OperationMove(Rectangle srcRoi, Size resolution, Enumerations.Anchor anchor = Enumerations.Anchor.MiddleCenter) { ROI = srcRoi; - ImageWidth = (uint) resolution.Width; - ImageHeight = (uint) resolution.Height; + ImageWidth = (uint)resolution.Width; + ImageHeight = (uint)resolution.Height; Anchor = anchor; } @@ -239,18 +257,64 @@ namespace UVtools.Core.Operations } - public bool ValidateBounds() { CalculateDstRoi(); return IsWithinBoundary; } - public override string ToString() + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) { - var result = $"[{ROI} -> {DstRoi}] [Cut: {IsCutMove}]" + LayerRangeString; - if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; - return result; + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + + if (ROI == Rectangle.Empty) ROI = slicerFile.LayerManager.GetBoundingRectangle(progress); + + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + + using (var mat = slicerFile[layerIndex].LayerMat) + { + Execute(mat); + slicerFile[layerIndex].LayerMat = mat; + } + + lock (progress.Mutex) + { + progress++; + } + }); + + slicerFile.LayerManager.BoundingRectangle = Rectangle.Empty; + + progress.Token.ThrowIfCancellationRequested(); + + return true; } + + public override bool Execute(Mat mat, params object[] arguments) + { + + if (ImageWidth == 0) ImageWidth = (uint) mat.Width; + if (ImageHeight == 0) ImageHeight = (uint) mat.Height; + + using var srcRoi = new Mat(mat, ROI); + using var dstRoi = new Mat(mat, DstRoi); + if (IsCutMove) + { + using var targetRoi = srcRoi.Clone(); + srcRoi.SetTo(new MCvScalar(0)); + targetRoi.CopyTo(dstRoi); + } + else + { + srcRoi.CopyTo(dstRoi); + } + + return true; + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationPattern.cs b/UVtools.Core/Operations/OperationPattern.cs index 592aa18..97d75f7 100644 --- a/UVtools.Core/Operations/OperationPattern.cs +++ b/UVtools.Core/Operations/OperationPattern.cs @@ -9,6 +9,10 @@ using System; using System.Drawing; using System.Text; +using System.Threading.Tasks; +using Emgu.CV; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -16,6 +20,7 @@ namespace UVtools.Core.Operations [Serializable] public class OperationPattern : Operation { + #region Members private Enumerations.Anchor _anchor = Enumerations.Anchor.None; private ushort _colSpacing; private ushort _rowSpacing; @@ -26,6 +31,7 @@ namespace UVtools.Core.Operations private ushort _maxCols; private ushort _maxRows; private bool _isWithinBoundary = true; + #endregion #region Overrides @@ -62,6 +68,7 @@ namespace UVtools.Core.Operations #endregion + #region Properties public Enumerations.Anchor Anchor { get => _anchor; @@ -176,7 +183,8 @@ namespace UVtools.Core.Operations public string InfoModelWithinBoundaryStr => "Model within boundary: " + (_isWithinBoundary ? "Yes" : "No"); - public Size GetPatternVolume => new Size(Cols * ROI.Width + (Cols - 1) * ColSpacing, Rows * ROI.Height + (Rows - 1) * RowSpacing); + public Size GetPatternVolume => new(Cols * ROI.Width + (Cols - 1) * ColSpacing, Rows * ROI.Height + (Rows - 1) * RowSpacing); + #endregion public OperationPattern() { @@ -191,6 +199,7 @@ namespace UVtools.Core.Operations Fill(); } + #region Methods public void SetAnchor(byte value) { Anchor = (Enumerations.Anchor)value; @@ -262,5 +271,64 @@ namespace UVtools.Core.Operations if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; return result; } + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + + using var mat = slicerFile[layerIndex].LayerMat; + using var layerRoi = new Mat(mat, ROI); + using var dstLayer = mat.CloneBlank(); + for (ushort col = 0; col < Cols; col++) + for (ushort row = 0; row < Rows; row++) + { + var roi = GetRoi(col, row); + using var dstRoi = new Mat(dstLayer, roi); + layerRoi.CopyTo(dstRoi); + } + //Execute(mat); + slicerFile[layerIndex].LayerMat = dstLayer; + + lock (progress.Mutex) + { + progress++; + } + }); + + slicerFile.LayerManager.BoundingRectangle = Rectangle.Empty; + + progress.Token.ThrowIfCancellationRequested(); + + if (Anchor == Enumerations.Anchor.None) return true; + var operationMove = new OperationMove(slicerFile.LayerManager.BoundingRectangle, ImageWidth, ImageHeight, Anchor) + { + LayerIndexStart = LayerIndexStart, + LayerIndexEnd = LayerIndexEnd + }; + operationMove.Execute(slicerFile, progress); + + return true; + } + + /*public override bool Execute(Mat mat, params object[] arguments) + { + using var layerRoi = new Mat(mat, ROI); + using var dstLayer = mat.CloneBlank(); + for (ushort col = 0; col < Cols; col++) + for (ushort row = 0; row < Rows; row++) + { + var dstRoi = new Mat(dstLayer, GetRoi(col, row)); + layerRoi.CopyTo(dstRoi); + } + + return true; + }*/ + + #endregion } } diff --git a/UVtools.Core/Operations/OperationPixelDimming.cs b/UVtools.Core/Operations/OperationPixelDimming.cs index e14a3ba..25c16e8 100644 --- a/UVtools.Core/Operations/OperationPixelDimming.cs +++ b/UVtools.Core/Operations/OperationPixelDimming.cs @@ -7,10 +7,14 @@ */ using System; +using System.Drawing; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; using Emgu.CV; +using Emgu.CV.CvEnum; using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -30,6 +34,8 @@ namespace UVtools.Core.Operations } } #endregion + + #region Members private uint _wallThicknessStart = 5; private uint _wallThicknessEnd = 5; private bool _wallsOnly; @@ -42,6 +48,7 @@ namespace UVtools.Core.Operations private byte _brightness = 127; private ushort _infillGenThickness = 10; private ushort _infillGenSpacing = 20; + #endregion #region Overrides public override string Title => "Pixel dimming"; @@ -526,6 +533,103 @@ namespace UVtools.Core.Operations return; } } + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + + if (Pattern is null) + { + Pattern = new Matrix<byte>(2, 2) + { + [0, 0] = 127, + [0, 1] = 255, + [1, 0] = 255, + [1, 1] = 127, + }; + + AlternatePattern ??= new Matrix<byte>(2, 2) + { + [0, 0] = 255, + [0, 1] = 127, + [1, 0] = 127, + [1, 1] = 255, + }; + } + + AlternatePattern ??= Pattern; + + using var blankMat = EmguExtensions.InitMat(slicerFile.Resolution); + using var matPattern = blankMat.CloneBlank(); + using var matAlternatePattern = blankMat.CloneBlank(); + var target = GetRoiOrDefault(blankMat); + + CvInvoke.Repeat(Pattern, target.Rows / Pattern.Rows + 1, target.Cols / Pattern.Cols + 1, matPattern); + CvInvoke.Repeat(AlternatePattern, target.Rows / AlternatePattern.Rows + 1, target.Cols / AlternatePattern.Cols + 1, matAlternatePattern); + + using var patternMask = new Mat(matPattern, new Rectangle(0, 0, target.Width, target.Height)); + using var alternatePatternMask = new Mat(matAlternatePattern, new Rectangle(0, 0, target.Width, target.Height)); + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using var mat = slicerFile[layerIndex].LayerMat; + Execute(mat, layerIndex, patternMask, alternatePatternMask); + slicerFile[layerIndex].LayerMat = mat; + + lock (progress.Mutex) + { + progress++; + } + }); + + progress.Token.ThrowIfCancellationRequested(); + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + if (arguments is null || arguments.Length < 2) return false; + var anchor = new Point(-1, -1); + var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + + uint layerIndex = Convert.ToUInt32(arguments[0]); + Mat patternMask = (Mat)arguments[1]; + Mat alternatePatternMask = arguments.Length >= 3 && arguments[2] is not null ? (Mat)arguments[2] : patternMask; + + int wallThickness = LayerManager.MutateGetIterationChamfer( + layerIndex, + LayerIndexStart, + LayerIndexEnd, + (int)WallThicknessStart, + (int)WallThicknessEnd, + Chamfer + ); + + + using Mat erode = new Mat(); + using Mat diff = new Mat(); + Mat target = GetRoiOrDefault(mat); + + + CvInvoke.Erode(target, erode, kernel, anchor, wallThickness, BorderType.Reflect101, default); + CvInvoke.Subtract(target, erode, diff); + + + if (WallsOnly) + { + CvInvoke.BitwiseAnd(diff, IsNormalPattern(layerIndex) ? patternMask : alternatePatternMask, target); + CvInvoke.Add(erode, target, target); + } + else + { + CvInvoke.BitwiseAnd(erode, IsNormalPattern(layerIndex) ? patternMask : alternatePatternMask, target); + CvInvoke.Add(target, diff, target); + } + + return true; + } + #endregion } } diff --git a/UVtools.Core/Operations/OperationRaftRelief.cs b/UVtools.Core/Operations/OperationRaftRelief.cs index 4c5a90a..c76b55a 100644 --- a/UVtools.Core/Operations/OperationRaftRelief.cs +++ b/UVtools.Core/Operations/OperationRaftRelief.cs @@ -7,17 +7,39 @@ */ using System; +using System.Drawing; +using System.Threading.Tasks; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; namespace UVtools.Core.Operations { [Serializable] public class OperationRaftRelief : Operation { + #region Enums + public enum RaftReliefTypes : byte + { + Relief, + Dimming, + Decimate + } + #endregion + + #region Members private RaftReliefTypes _reliefType = RaftReliefTypes.Relief; + private byte _ignoreFirstLayers; + private byte _brightness; private byte _dilateIterations = 10; private byte _wallMargin = 20; private byte _holeDiameter = 50; private byte _holeSpacing = 20; + #endregion + + #region Overrides public override string Title => "Raft relief"; public override string Description => "Relief raft by adding holes in between to reduce FEP suction, save resin and easier to remove the prints."; @@ -32,33 +54,52 @@ namespace UVtools.Core.Operations public override Enumerations.LayerRangeSelection StartLayerRangeSelection => Enumerations.LayerRangeSelection.None; - - - public OperationRaftRelief() - { - } - + public override string ToString() { - var result = $"[{_reliefType}] [Dilate: {_dilateIterations}] [Wall margin: {_wallMargin}] [Hole diameter: {_holeDiameter}] [Hole spacing: {_holeSpacing}]"; + var result = $"[{_reliefType}] [Ignore: {_ignoreFirstLayers}] [B: {_brightness}] [Dilate: {_dilateIterations}] [Wall margin: {_wallMargin}] [Hole diameter: {_holeDiameter}] [Hole spacing: {_holeSpacing}]"; if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; return result; } + #endregion - public enum RaftReliefTypes : byte - { - Relief, - Decimate - } - + #region Properties public static Array RaftReliefItems => Enum.GetValues(typeof(RaftReliefTypes)); public RaftReliefTypes ReliefType { get => _reliefType; - set => RaiseAndSetIfChanged(ref _reliefType, value); + set + { + if(!RaiseAndSetIfChanged(ref _reliefType, value)) return; + RaisePropertyChanged(nameof(IsRelief)); + RaisePropertyChanged(nameof(IsDimming)); + RaisePropertyChanged(nameof(IsDecimate)); + } + } + + public bool IsRelief => _reliefType == RaftReliefTypes.Relief; + public bool IsDimming => _reliefType == RaftReliefTypes.Dimming; + public bool IsDecimate => _reliefType == RaftReliefTypes.Decimate; + + public byte IgnoreFirstLayers + { + get => _ignoreFirstLayers; + set => RaiseAndSetIfChanged(ref _ignoreFirstLayers, value); + } + + public byte Brightness + { + get => _brightness; + set + { + if (!RaiseAndSetIfChanged(ref _brightness, value)) return; + RaisePropertyChanged(nameof(BrightnessPercent)); + } } + public decimal BrightnessPercent => Math.Round(_brightness * 100 / 255M, 2); + public byte DilateIterations { get => _dilateIterations; @@ -82,12 +123,13 @@ namespace UVtools.Core.Operations get => _holeSpacing; set => RaiseAndSetIfChanged(ref _holeSpacing, value); } + #endregion #region Equality protected bool Equals(OperationRaftRelief other) { - return _reliefType == other._reliefType && _dilateIterations == other._dilateIterations && _holeDiameter == other._holeDiameter && _holeSpacing == other._holeSpacing && _wallMargin == other._wallMargin; + return _reliefType == other._reliefType && _ignoreFirstLayers == other._ignoreFirstLayers && _brightness == other._brightness && _dilateIterations == other._dilateIterations && _wallMargin == other._wallMargin && _holeDiameter == other._holeDiameter && _holeSpacing == other._holeSpacing; } public override bool Equals(object obj) @@ -100,15 +142,120 @@ namespace UVtools.Core.Operations public override int GetHashCode() { - unchecked + return HashCode.Combine((int) _reliefType, _ignoreFirstLayers, _brightness, _dilateIterations, _wallMargin, _holeDiameter, _holeSpacing); + } + + #endregion + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + const uint minLength = 5; + progress ??= new OperationProgress(); + //progress.Reset(operation.ProgressAction); + + Mat supportsMat = null; + var anchor = new Point(-1, -1); + var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), anchor); + + + uint firstSupportLayerIndex = 0; + for (; firstSupportLayerIndex < slicerFile.LayerCount; firstSupportLayerIndex++) { - var hashCode = (int) _reliefType; - hashCode = (hashCode * 397) ^ _dilateIterations.GetHashCode(); - hashCode = (hashCode * 397) ^ _holeDiameter.GetHashCode(); - hashCode = (hashCode * 397) ^ _holeSpacing.GetHashCode(); - hashCode = (hashCode * 397) ^ _wallMargin.GetHashCode(); - return hashCode; + progress.Reset("Tracing raft", slicerFile.LayerCount, firstSupportLayerIndex); + if (progress.Token.IsCancellationRequested) return false; + supportsMat = GetRoiOrDefault(slicerFile[firstSupportLayerIndex].LayerMat); + var circles = CvInvoke.HoughCircles(supportsMat, HoughModes.Gradient, 1, 20, 100, 30, 5, 200); + if (circles.Length >= minLength) break; + + supportsMat.Dispose(); + supportsMat = null; + } + + if (supportsMat is null) return false; + Mat patternMat = null; + + if (DilateIterations > 0) + { + CvInvoke.Dilate(supportsMat, supportsMat, + CvInvoke.GetStructuringElement(ElementShape.Ellipse, new Size(3, 3), new Point(-1, -1)), + new Point(-1, -1), DilateIterations, BorderType.Reflect101, new MCvScalar()); } + + var color = new MCvScalar(255 - Brightness); + + switch (ReliefType) + { + case OperationRaftRelief.RaftReliefTypes.Relief: + patternMat = EmguExtensions.InitMat(supportsMat.Size); + int shapeSize = HoleDiameter + HoleSpacing; + using (var shape = EmguExtensions.InitMat(new Size(shapeSize, shapeSize))) + { + + int center = HoleDiameter / 2; + //int centerTwo = operation.HoleDiameter + operation.HoleSpacing + operation.HoleDiameter / 2; + int radius = center; + CvInvoke.Circle(shape, new Point(shapeSize / 2, shapeSize / 2), radius, color, -1); + CvInvoke.Circle(shape, new Point(0, 0), radius / 2, color, -1); + CvInvoke.Circle(shape, new Point(0, shapeSize), radius / 2, color, -1); + CvInvoke.Circle(shape, new Point(shapeSize, 0), radius / 2, color, -1); + CvInvoke.Circle(shape, new Point(shapeSize, shapeSize), radius / 2, color, -1); + + CvInvoke.Repeat(shape, supportsMat.Height / shape.Height + 1, supportsMat.Width / shape.Width + 1, patternMat); + + patternMat = new Mat(patternMat, new Rectangle(0, 0, supportsMat.Width, supportsMat.Height)); + } + + break; + case OperationRaftRelief.RaftReliefTypes.Dimming: + patternMat = EmguExtensions.InitMat(supportsMat.Size, color); + break; + } + + progress.Reset(ProgressAction, firstSupportLayerIndex); + Parallel.For(IgnoreFirstLayers, firstSupportLayerIndex, layerIndex => + { + using (Mat dst = slicerFile[layerIndex].LayerMat) + { + var target = GetRoiOrDefault(dst); + + switch (ReliefType) + { + case RaftReliefTypes.Relief: + case RaftReliefTypes.Dimming: + using (Mat mask = new Mat()) + { + /*CvInvoke.Subtract(target, supportsMat, mask); + CvInvoke.Erode(mask, mask, kernel, anchor, operation.WallMargin, BorderType.Reflect101, new MCvScalar()); + CvInvoke.Subtract(target, patternMat, target, mask);*/ + + CvInvoke.Erode(target, mask, kernel, anchor, WallMargin, BorderType.Reflect101, default); + CvInvoke.Subtract(mask, supportsMat, mask); + CvInvoke.Subtract(target, patternMat, target, mask); + } + + break; + case RaftReliefTypes.Decimate: + supportsMat.CopyTo(target); + break; + } + + + slicerFile[layerIndex].LayerMat = dst; + } + + lock (progress.Mutex) + { + progress++; + } + }); + + + supportsMat.Dispose(); + patternMat?.Dispose(); + + return true; } #endregion diff --git a/UVtools.Core/Operations/OperationRedrawModel.cs b/UVtools.Core/Operations/OperationRedrawModel.cs new file mode 100644 index 0000000..7f156b1 --- /dev/null +++ b/UVtools.Core/Operations/OperationRedrawModel.cs @@ -0,0 +1,249 @@ +/* + * GNU AFFERO GENERAL PUBLIC LICENSE + * Version 3, 19 November 2007 + * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + * Everyone is permitted to copy and distribute verbatim copies + * of this license document, but changing it is not allowed. + */ + +using System; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Emgu.CV.Util; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; +using UVtools.Core.Objects; + +namespace UVtools.Core.Operations +{ + [Serializable] + public class OperationRedrawModel : Operation + { + #region Members + + private string _filePath; + private byte _brightness = 220; + private bool _contactPointsOnly = true; + private RedrawTypes _redrawType = RedrawTypes.Supports; + private bool _ignoreContactLessPixels = true; + + #endregion + + #region Overrides + + public override Enumerations.LayerRangeSelection StartLayerRangeSelection { get; } = Enumerations.LayerRangeSelection.None; + + public override string Title => "Redraw model/supports"; + + public override string Description => + "Redraw the model or supports with a set brightness. This requires an extra sliced file from same object but without any supports and raft, straight to the build plate.\n" + + "Note: Run this tool prior to any made modification. You must find the optimal exposure/brightness combo, or supports can fail."; + + public override string ConfirmationText => "redraw the "+ (_redrawType == RedrawTypes.Supports ? "supports" : "model") + + $" with an brightness of {_brightness}?"; + + public override string ProgressTitle => "Redrawing " + (_redrawType == RedrawTypes.Supports ? "supports" : "model"); + + public override string ProgressAction => "Redraw layers"; + + public override StringTag Validate(params object[] parameters) + { + var sb = new StringBuilder(); + + if (IsFileValid() is null) + { + sb.AppendLine("The selected file is not valid."); + } + + return new StringTag(sb.ToString()); + } + + + public override string ToString() + { + var result = $"[{_redrawType}] [B: {_brightness}] [CS: {_contactPointsOnly}] [ICLP: {_ignoreContactLessPixels}]"; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + + + #endregion + + #region Enums + public enum RedrawTypes : byte + { + Supports, + Model, + } + #endregion + + #region Properties + + [XmlIgnore] + public string FilePath + { + get => _filePath; + set => RaiseAndSetIfChanged(ref _filePath, value); + } + + public RedrawTypes RedrawType + { + get => _redrawType; + set => RaiseAndSetIfChanged(ref _redrawType, value); + } + + public static Array RedrawTypesItems => Enum.GetValues(typeof(RedrawTypes)); + + public byte Brightness + { + get => _brightness; + set + { + if (!RaiseAndSetIfChanged(ref _brightness, value)) return; + RaisePropertyChanged(nameof(BrightnessPercent)); + } + } + + public decimal BrightnessPercent => Math.Round(_brightness * 100 / 255M, 2); + + public bool ContactPointsOnly + { + get => _contactPointsOnly; + set => RaiseAndSetIfChanged(ref _contactPointsOnly, value); + } + + public bool IgnoreContactLessPixels + { + get => _ignoreContactLessPixels; + set => RaiseAndSetIfChanged(ref _ignoreContactLessPixels, value); + } + + #endregion + + #region Equality + + protected bool Equals(OperationRedrawModel other) + { + return _brightness == other._brightness && _contactPointsOnly == other._contactPointsOnly && _redrawType == other._redrawType && _ignoreContactLessPixels == other._ignoreContactLessPixels; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((OperationRedrawModel) obj); + } + + public override int GetHashCode() + { + return HashCode.Combine(_brightness, _contactPointsOnly, (int) _redrawType, _ignoreContactLessPixels); + } + + #endregion + + #region Methods + + public FileFormat IsFileValid(bool returnNewInstance = false) => + FileFormat.FindByExtension(_filePath, true, returnNewInstance); + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + + var otherFile = IsFileValid(true); + otherFile.Decode(_filePath, progress); + + progress.Reset(ProgressAction, otherFile.LayerCount); + + int startLayerIndex = (int)(slicerFile.LayerCount - otherFile.LayerCount); + if (startLayerIndex < 0) return false; + Parallel.For(0, otherFile.LayerCount, layerIndex => + { + var fullMatLayerIndex = startLayerIndex + layerIndex; + using var fullMat = slicerFile[fullMatLayerIndex].LayerMat; + using var bodyMat = otherFile[layerIndex].LayerMat; + using var fullMatRoi = GetRoiOrDefault(fullMat); + using var bodyMatRoi = GetRoiOrDefault(bodyMat); + using var patternMat = EmguExtensions.InitMat(fullMatRoi.Size, new MCvScalar(255 - _brightness)); + using var supportsMat = new Mat(); + + bool modified = false; + if (_redrawType == RedrawTypes.Supports && _contactPointsOnly) + { + if (layerIndex + 1 >= otherFile.LayerCount) return; + CvInvoke.Subtract(fullMatRoi, bodyMatRoi, supportsMat); // Supports + using var contours = new VectorOfVectorOfPoint(); + using var hierarchyMat = new Mat(); + CvInvoke.FindContours(supportsMat, contours, hierarchyMat, RetrType.List, ChainApproxMethod.ChainApproxSimple); + if (contours.Size <= 0) return; + using var nextLayerMat = otherFile[layerIndex + 1].LayerMat; + using var nextLayerMatRoi = GetRoiOrDefault(nextLayerMat); + var fullSpan = fullMatRoi.GetPixelSpan<byte>(); + var supportsSpan = supportsMat.GetPixelSpan<byte>(); + var nextSpan = nextLayerMatRoi.GetPixelSpan<byte>(); + for (int i = 0; i < contours.Size; i++) + { + var foundContour = false; + var rectangle = CvInvoke.BoundingRectangle(contours[i]); + for (int y = rectangle.Y; y < rectangle.Bottom && !foundContour; y++) + for (int x = rectangle.X; x < rectangle.Right; x++) + { + var pos = supportsMat.GetPixelPos(x, y); + if (_ignoreContactLessPixels) + { + if (supportsSpan[pos] <= 10) continue; + if (nextSpan[pos] <= 0) continue; + modified = true; + fullSpan[pos] = _brightness; + } + else + { + if (supportsSpan[pos] <= 100) continue; + if (nextSpan[pos] <= 150) continue; + CvInvoke.DrawContours(fullMatRoi, contours, i, new MCvScalar(_brightness), -1, LineType.AntiAlias); + modified = true; + foundContour = true; + break; + } + + } + } + } + else + { + switch (_redrawType) + { + case RedrawTypes.Supports: + CvInvoke.Subtract(fullMatRoi, bodyMatRoi, supportsMat); // Supports + break; + case RedrawTypes.Model: + CvInvoke.BitwiseAnd(fullMatRoi, bodyMatRoi, supportsMat); // Model + break; + } + + CvInvoke.Subtract(fullMatRoi, patternMat, fullMatRoi, supportsMat); + modified = true; + } + + if (modified) + { + slicerFile[fullMatLayerIndex].LayerMat = fullMat; + } + + lock (progress.Mutex) + { + progress++; + } + }); + + return true; + } + + #endregion + } +} diff --git a/UVtools.Core/Operations/OperationRepairLayers.cs b/UVtools.Core/Operations/OperationRepairLayers.cs index 2622432..e46d3c2 100644 --- a/UVtools.Core/Operations/OperationRepairLayers.cs +++ b/UVtools.Core/Operations/OperationRepairLayers.cs @@ -7,9 +7,19 @@ */ using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Drawing; +using System.Linq; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Emgu.CV.Util; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -17,13 +27,17 @@ namespace UVtools.Core.Operations [Serializable] public class OperationRepairLayers : Operation { + #region Members private bool _repairIslands = true; private bool _repairResinTraps = true; private bool _removeEmptyLayers = true; private byte _removeIslandsBelowEqualPixelCount = 5; private ushort _removeIslandsRecursiveIterations = 4; private uint _gapClosingIterations = 1; - private uint _noiseRemovalIterations = 0; + private uint _noiseRemovalIterations; + #endregion + + #region Overrides public override bool CanROI => false; public override string Title => "Repair layers and issues"; public override string Description => null; @@ -48,7 +62,9 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + #endregion + #region Properties public bool RepairIslands { get => _repairIslands; @@ -96,5 +112,207 @@ namespace UVtools.Core.Operations [XmlIgnore] public List<LayerIssue> Issues { get; set; } + #endregion + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + + + + // Remove islands + if (!ReferenceEquals(Issues, null) + && !ReferenceEquals(IslandDetectionConfig, null) + && RepairIslands + && RemoveIslandsBelowEqualPixelCount > 0 + && RemoveIslandsRecursiveIterations != 1) + { + progress.Reset("Removed recursive islands", 0); + ushort limit = RemoveIslandsRecursiveIterations == 0 + ? ushort.MaxValue + : RemoveIslandsRecursiveIterations; + + var recursiveIssues = Issues; + ConcurrentBag<uint> islandsToRecompute = null; + + var islandConfig = IslandDetectionConfig; + var overhangConfig = new OverhangDetectionConfiguration(false); + var touchingBoundsConfig = new TouchingBoundDetectionConfiguration(false); + var resinTrapsConfig = new ResinTrapDetectionConfiguration(false); + var emptyLayersConfig = false; + + islandConfig.Enabled = true; + islandConfig.RequiredAreaToProcessCheck = (byte)Math.Ceiling(RemoveIslandsBelowEqualPixelCount / 2m); + + for (uint i = 0; i < limit; i++) + { + if (i > 0) + { + /*var whiteList = islandsToRecompute.GroupBy(u => u) + .Select(grp => grp.First()) + .ToList();*/ + islandConfig.WhiteListLayers = islandsToRecompute.ToList(); + recursiveIssues = slicerFile.LayerManager.GetAllIssues(islandConfig, overhangConfig, resinTrapsConfig, touchingBoundsConfig, emptyLayersConfig); + //Debug.WriteLine(i); + } + + var issuesGroup = + recursiveIssues + .Where(issue => issue.Type == LayerIssue.IssueType.Island && + issue.Pixels.Length <= RemoveIslandsBelowEqualPixelCount) + .GroupBy(issue => issue.LayerIndex); + + if (!issuesGroup.Any()) break; // Nothing to process + islandsToRecompute = new ConcurrentBag<uint>(); + Parallel.ForEach(issuesGroup, group => + { + if (progress.Token.IsCancellationRequested) return; + Layer layer = slicerFile[group.Key]; + Mat image = layer.LayerMat; + Span<byte> bytes = image.GetPixelSpan<byte>(); + foreach (var issue in group) + { + foreach (var issuePixel in issue.Pixels) + { + bytes[image.GetPixelPos(issuePixel)] = 0; + } + + lock (progress.Mutex) + { + progress++; + } + } + + var nextLayerIndex = group.Key + 1; + if (nextLayerIndex < slicerFile.LayerCount) + islandsToRecompute.Add(nextLayerIndex); + + layer.LayerMat = image; + }); + + if (islandsToRecompute.IsEmpty) break; // No more leftovers + } + } + + progress.Reset(ProgressAction, LayerRangeCount); + if (RepairIslands || RepairResinTraps) + { + Parallel.For(LayerIndexStart, LayerIndexEnd, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + Layer layer = slicerFile[layerIndex]; + Mat image = null; + + void initImage() + { + image ??= layer.LayerMat; + } + + if (Issues is not null) + { + if (RepairIslands && RemoveIslandsBelowEqualPixelCount > 0 && RemoveIslandsRecursiveIterations == 1) + { + Span<byte> bytes = null; + foreach (var issue in Issues) + { + if ( + issue.LayerIndex != layerIndex || + issue.Type != LayerIssue.IssueType.Island || + issue.Pixels.Length > RemoveIslandsBelowEqualPixelCount) continue; + + initImage(); + if (bytes == null) + bytes = image.GetPixelSpan<byte>(); + + foreach (var issuePixel in issue.Pixels) + { + bytes[image.GetPixelPos(issuePixel)] = 0; + } + } + /*if (issues.TryGetValue((uint)layerIndex, out var issueList)) + { + var bytes = image.GetPixelSpan<byte>(); + foreach (var issue in issueList.Where(issue => + issue.Type == LayerIssue.IssueType.Island && issue.Pixels.Length <= removeIslandsBelowEqualPixels)) + { + foreach (var issuePixel in issue.Pixels) + { + bytes[image.GetPixelPos(issuePixel)] = 0; + } + } + }*/ + } + + if (RepairResinTraps) + { + foreach (var issue in Issues.Where(issue => issue.LayerIndex == layerIndex && issue.Type == LayerIssue.IssueType.ResinTrap)) + { + initImage(); + using var vec = new VectorOfVectorOfPoint(new VectorOfPoint(issue.Pixels)); + CvInvoke.DrawContours(image, + vec, + -1, + EmguExtensions.WhiteByte, + -1); + } + } + } + + if (RepairIslands && (GapClosingIterations > 0 || NoiseRemovalIterations > 0)) + { + initImage(); + using Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), + new Point(-1, -1)); + if (GapClosingIterations > 0) + { + CvInvoke.MorphologyEx(image, image, MorphOp.Close, kernel, new Point(-1, -1), + (int)GapClosingIterations, BorderType.Default, default); + } + + if (NoiseRemovalIterations > 0) + { + CvInvoke.MorphologyEx(image, image, MorphOp.Open, kernel, new Point(-1, -1), + (int)NoiseRemovalIterations, BorderType.Default, default); + } + } + + if (!ReferenceEquals(image, null)) + { + layer.LayerMat = image; + image.Dispose(); + } + + lock (progress.Mutex) + { + progress++; + } + }); + } + + if (RemoveEmptyLayers) + { + List<uint> removeLayers = new List<uint>(); + for (uint layerIndex = LayerIndexStart; layerIndex <= LayerIndexEnd; layerIndex++) + { + if (slicerFile[layerIndex].NonZeroPixelCount == 0) + { + removeLayers.Add(layerIndex); + } + } + + if (removeLayers.Count > 0) + { + OperationLayerRemove.RemoveLayers(slicerFile, removeLayers, progress); + } + } + + progress.Token.ThrowIfCancellationRequested(); + + return true; + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationResize.cs b/UVtools.Core/Operations/OperationResize.cs index fa20389..0f909e5 100644 --- a/UVtools.Core/Operations/OperationResize.cs +++ b/UVtools.Core/Operations/OperationResize.cs @@ -8,6 +8,10 @@ using System; using System.Text; +using System.Threading.Tasks; +using Emgu.CV; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; using UVtools.Core.Objects; namespace UVtools.Core.Operations @@ -15,10 +19,14 @@ namespace UVtools.Core.Operations [Serializable] public class OperationResize : Operation { + #region Members private decimal _x = 100; private decimal _y = 100; private bool _constrainXy; private bool _isFade; + #endregion + + #region Overrides public override string Title => "Resize"; public override string Description => "Resize the model by a percentage in the X/Y plane.\n\n" + @@ -47,8 +55,9 @@ namespace UVtools.Core.Operations return new StringTag(sb.ToString()); } + #endregion - + #region Properties public decimal X { get => _x; @@ -57,13 +66,21 @@ namespace UVtools.Core.Operations RaiseAndSetIfChanged(ref _x, value); if (_constrainXy) Y = value; + RaisePropertyChanged(nameof(XScale)); } } + public decimal XScale => _x / 100m; + public decimal YScale => _y / 100m; + public decimal Y { get => _y; - set => RaiseAndSetIfChanged(ref _y, value); + set + { + if(!RaiseAndSetIfChanged(ref _y, value)) return; + RaisePropertyChanged(nameof(YScale)); + } } public bool ConstrainXY @@ -84,7 +101,7 @@ namespace UVtools.Core.Operations get => _isFade; set => RaiseAndSetIfChanged(ref _isFade, value); } - + #endregion public OperationResize() { @@ -125,5 +142,82 @@ namespace UVtools.Core.Operations } #endregion + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + if (X == 1m && Y == 1m) return false; + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + + decimal xSteps = Math.Abs(X - 1) / (LayerIndexEnd - LayerIndexStart); + decimal ySteps = Math.Abs(Y - 1) / (LayerIndexEnd - LayerIndexStart); + + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + var newX = X; + var newY = Y; + if (IsFade) + { + if (newX != 1m) + { + + //maxIteration = Math.Max(iterationsStart, iterationsEnd); + + newX = newX < 1m + ? newX + (layerIndex - LayerIndexStart) * xSteps + : newX - (layerIndex - LayerIndexStart) * xSteps; + + // constrain + //iterations = Math.Min(Math.Max(1, iterations), maxIteration); + } + + if (newY != 1m) + { + + //maxIteration = Math.Max(iterationsStart, iterationsEnd); + + newY = (newY < 1m + ? newY + (layerIndex - LayerIndexStart) * ySteps + : newY - (layerIndex - LayerIndexStart) * ySteps); + + // constrain + //iterations = Math.Min(Math.Max(1, iterations), maxIteration); + } + } + + lock (progress.Mutex) + { + progress++; + } + + if (newX == 1.0m && newY == 1.0m) return; + + using var mat = slicerFile[layerIndex].LayerMat; + Execute(mat, newX / 100m, newY / 100m); + slicerFile[layerIndex].LayerMat = mat; + }); + progress.Token.ThrowIfCancellationRequested(); + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + var xScale = XScale; + var yScale = YScale; + if (arguments is not null && arguments.Length >= 2) + { + xScale = (decimal) arguments[0]; + yScale = (decimal) arguments[1]; + } + + var target = GetRoiOrDefault(mat); + target.TransformFromCenter((double) xScale, (double) yScale); + return true; + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationRotate.cs b/UVtools.Core/Operations/OperationRotate.cs index 4d32762..f9a864e 100644 --- a/UVtools.Core/Operations/OperationRotate.cs +++ b/UVtools.Core/Operations/OperationRotate.cs @@ -7,13 +7,21 @@ */ using System; +using System.Threading.Tasks; +using Emgu.CV; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; namespace UVtools.Core.Operations { [Serializable] public class OperationRotate : Operation { + #region Members private decimal _angleDegrees = 90; + #endregion + + #region Overrides public override string Title => "Rotate"; public override string Description => "Rotate the layers of the model.\n"; @@ -25,19 +33,22 @@ namespace UVtools.Core.Operations $"Rotating layers {LayerIndexStart} through {LayerIndexEnd} {(AngleDegrees < 0 ? "counter-clockwise" : "clockwise")} by {Math.Abs(AngleDegrees)}°"; public override string ProgressAction => "Rotated layers"; - - public decimal AngleDegrees - { - get => _angleDegrees; - set => RaiseAndSetIfChanged(ref _angleDegrees, value); - } - + public override string ToString() { var result = $"[Angle: {Math.Abs(_angleDegrees)}º {(AngleDegrees < 0 ? "CCW" : "CW")}]" + LayerRangeString; if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; return result; } + #endregion + + #region Properties + public decimal AngleDegrees + { + get => _angleDegrees; + set => RaiseAndSetIfChanged(ref _angleDegrees, value); + } + #endregion #region Equality @@ -60,5 +71,36 @@ namespace UVtools.Core.Operations } #endregion + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using var mat = slicerFile[layerIndex].LayerMat; + Execute(mat); + slicerFile[layerIndex].LayerMat = mat; + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + Mat target = GetRoiOrDefault(mat); + target.Rotate((double)AngleDegrees); + return true; + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationSolidify.cs b/UVtools.Core/Operations/OperationSolidify.cs index 4dc4088..ca6577f 100644 --- a/UVtools.Core/Operations/OperationSolidify.cs +++ b/UVtools.Core/Operations/OperationSolidify.cs @@ -7,6 +7,13 @@ */ using System; +using System.Threading.Tasks; +using Emgu.CV; +using Emgu.CV.CvEnum; +using Emgu.CV.Structure; +using Emgu.CV.Util; +using UVtools.Core.Extensions; +using UVtools.Core.FileFormats; namespace UVtools.Core.Operations { @@ -62,5 +69,61 @@ namespace UVtools.Core.Operations } #endregion + + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) + { + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using var mat = slicerFile[layerIndex].LayerMat; + Execute(mat); + slicerFile[layerIndex].LayerMat = mat; + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + return true; + } + + public override bool Execute(Mat mat, params object[] arguments) + { + using Mat filteredMat = new Mat(); + using VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint(); + using Mat hierarchy = new Mat(); + Mat target = GetRoiOrDefault(mat); + + CvInvoke.Threshold(target, filteredMat, 127, 255, ThresholdType.Binary); // Clean AA + CvInvoke.FindContours(filteredMat, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple); + var arr = hierarchy.GetData(); + for (int i = 0; i < contours.Size; i++) + { + if ((int)arr.GetValue(0, i, 2) != -1 || (int)arr.GetValue(0, i, 3) == -1) continue; + if (MinimumArea >= 1) + { + var rectangle = CvInvoke.BoundingRectangle(contours[i]); + if (AreaCheckType == AreaCheckTypes.More) + { + if (rectangle.GetArea() < MinimumArea) continue; + } + else + { + if (rectangle.GetArea() > MinimumArea) continue; + } + + } + + CvInvoke.DrawContours(target, contours, i, new MCvScalar(255), -1); + } + + return true; + } + + #endregion } } diff --git a/UVtools.Core/Operations/OperationThreshold.cs b/UVtools.Core/Operations/OperationThreshold.cs index 143f173..f3776a4 100644 --- a/UVtools.Core/Operations/OperationThreshold.cs +++ b/UVtools.Core/Operations/OperationThreshold.cs @@ -7,17 +7,23 @@ */ using System; +using System.Threading.Tasks; +using Emgu.CV; using Emgu.CV.CvEnum; +using UVtools.Core.FileFormats; namespace UVtools.Core.Operations { [Serializable] public class OperationThreshold : Operation { + #region Members private byte _threshold = 127; private byte _maximum = 255; private ThresholdType _type = ThresholdType.Binary; + #endregion + #region Overrides public override string Title => "Threshold pixels"; public override string Description => "Manipulate pixel values based on a threshold.\n\n" + @@ -33,6 +39,15 @@ namespace UVtools.Core.Operations public override string ProgressAction => "Thresholded layers"; + public override string ToString() + { + var result = $"[{_type} = {_threshold} / {_maximum}]" + LayerRangeString; + if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; + return result; + } + #endregion + + #region Properties public byte Threshold { get => _threshold; @@ -52,14 +67,42 @@ namespace UVtools.Core.Operations } public static Array ThresholdTypes => Enum.GetValues(typeof(ThresholdType)); + #endregion - public override string ToString() + #region Methods + + public override bool Execute(FileFormat slicerFile, OperationProgress progress = null) { - var result = $"[{_type} = {_threshold} / {_maximum}]" + LayerRangeString; - if (!string.IsNullOrEmpty(ProfileName)) result = $"{ProfileName}: {result}"; - return result; + progress ??= new OperationProgress(); + progress.Reset(ProgressAction, LayerRangeCount); + Parallel.For(LayerIndexStart, LayerIndexEnd + 1, layerIndex => + { + if (progress.Token.IsCancellationRequested) return; + using (var mat = slicerFile[layerIndex].LayerMat) + { + Execute(mat); + slicerFile[layerIndex].LayerMat = mat; + } + + lock (progress.Mutex) + { + progress++; + } + }); + progress.Token.ThrowIfCancellationRequested(); + + return true; } + public override bool Execute(Mat mat, params object[] arguments) + { + Mat target = GetRoiOrDefault(mat); + CvInvoke.Threshold(target, target, Threshold, Maximum, Type); + return true; + } + + #endregion + #region Equality protected bool Equals(OperationThreshold other) diff --git a/UVtools.Core/UVtools.Core.csproj b/UVtools.Core/UVtools.Core.csproj index 72c2fb8..73ec8e0 100644 --- a/UVtools.Core/UVtools.Core.csproj +++ b/UVtools.Core/UVtools.Core.csproj @@ -10,7 +10,7 @@ <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <PackageProjectUrl>https://github.com/sn4k3/UVtools</PackageProjectUrl> <Description>MSLA/DLP, file analysis, repair, conversion and manipulation</Description> - <Version>2.0.0</Version> + <Version>2.1.0</Version> <Copyright>Copyright © 2020 PTRTECH</Copyright> <PackageIcon>UVtools.png</PackageIcon> <Platforms>AnyCPU;x64</Platforms> diff --git a/UVtools.Platforms/arch-x64/libcvextern.so b/UVtools.Platforms/arch-x64/libcvextern.so Binary files differnew file mode 100644 index 0000000..b8b0375 --- /dev/null +++ b/UVtools.Platforms/arch-x64/libcvextern.so diff --git a/UVtools.WPF/libcvextern.so b/UVtools.Platforms/linux-x64/libcvextern.so Binary files differindex a75ef49..a75ef49 100644 --- a/UVtools.WPF/libcvextern.so +++ b/UVtools.Platforms/linux-x64/libcvextern.so diff --git a/UVtools.Platforms/osx-x64/Info.plist b/UVtools.Platforms/osx-x64/Info.plist new file mode 100644 index 0000000..bd45bf0 --- /dev/null +++ b/UVtools.Platforms/osx-x64/Info.plist @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> + <dict> + <key>CFBundleIconFile</key> + <string>UVtools.icns</string> + <key>CFBundleIdentifier</key> + <string>com.UVtools</string> + <key>CFBundleName</key> + <string>UVtools</string> + <key>CFBundleVersion</key> + <string>#VERSION</string> + <key>LSMinimumSystemVersion</key> + <string>10.12</string> + <key>CFBundleExecutable</key> + <string>UVtools</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>#VERSION</string> + <key>NSHighResolutionCapable</key> + <true /> + </dict> +</plist> diff --git a/UVtools.WPF/libcvextern.dylib b/UVtools.Platforms/osx-x64/libcvextern.dylib Binary files differindex 1af1f8c..1af1f8c 100644 --- a/UVtools.WPF/libcvextern.dylib +++ b/UVtools.Platforms/osx-x64/libcvextern.dylib diff --git a/UVtools.Platforms/rhel-x64/libcvextern.so b/UVtools.Platforms/rhel-x64/libcvextern.so Binary files differnew file mode 100644 index 0000000..a75ef49 --- /dev/null +++ b/UVtools.Platforms/rhel-x64/libcvextern.so diff --git a/UVtools.WPF/App.axaml.cs b/UVtools.WPF/App.axaml.cs index fe21cb8..4d14ccc 100644 --- a/UVtools.WPF/App.axaml.cs +++ b/UVtools.WPF/App.axaml.cs @@ -38,7 +38,7 @@ namespace UVtools.WPF public static AppVersionChecker VersionChecker { get; } = new AppVersionChecker(); - public static Size MaxWindowSize; + public static Size MaxWindowSize = Size.Empty; public override void Initialize() { @@ -84,7 +84,6 @@ namespace UVtools.WPF $"{e}", "UVtools can not run"); return; } - desktop.MainWindow = MainWindow; desktop.Exit += (sender, e) @@ -189,17 +188,17 @@ namespace UVtools.WPF public static string GetPrusaSlicerDirectory() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OperatingSystem.IsWindows()) { return $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}{Path.DirectorySeparatorChar}PrusaSlicer"; } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + if (OperatingSystem.IsLinux()) { return $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.PrusaSlicer"; } - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + if (OperatingSystem.IsMacOS()) { return string.Format("{0}{1}Library{1}Application Support{1}PrusaSlicer", Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), Path.DirectorySeparatorChar); diff --git a/UVtools.WPF/Assets/Icons/code-branch-16x16.png b/UVtools.WPF/Assets/Icons/code-branch-16x16.png Binary files differnew file mode 100644 index 0000000..e718637 --- /dev/null +++ b/UVtools.WPF/Assets/Icons/code-branch-16x16.png diff --git a/UVtools.WPF/Assets/Icons/sun-16x16.png b/UVtools.WPF/Assets/Icons/sun-16x16.png Binary files differnew file mode 100644 index 0000000..997ab7e --- /dev/null +++ b/UVtools.WPF/Assets/Icons/sun-16x16.png diff --git a/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml index fb3a6f4..02b0ad7 100644 --- a/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml +++ b/UVtools.WPF/Controls/Tools/ToolRaftReliefControl.axaml @@ -8,90 +8,123 @@ <Grid ColumnDefinitions="Auto,10,Auto,5,Auto" - RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto" + RowDefinitions="Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto,10,Auto" > <TextBlock Text="Relief type:" VerticalAlignment="Center"/> - <ComboBox - Name="ReliefType" + <ComboBox Grid.Row="0" Grid.Column="2" Width="100" SelectedItem="{Binding Operation.ReliefType}" Items="{Binding Operation.RaftReliefItems}"/> + <TextBlock Grid.Row="2" - IsVisible="{Binding !#ReliefType.SelectedIndex}" + Grid.Column="0" + Text="Ignore first:" VerticalAlignment="Center"/> + + <NumericUpDown Grid.Row="2" Grid.Column="2" + Width="100" + Minimum="0" + Maximum="255" + Value="{Binding Operation.IgnoreFirstLayers}"/> + <TextBlock + Grid.Row="2" Grid.Column="4" + Text="layer(s)" VerticalAlignment="Center"/> + + <TextBlock + Grid.Row="4" + Grid.Column="0" + IsVisible="{Binding !Operation.IsDecimate}" + Text="Pixel brightness:" VerticalAlignment="Center"/> + + <NumericUpDown Grid.Row="4" Grid.Column="2" + Width="100" + Minimum="0" + Maximum="255" + IsVisible="{Binding !Operation.IsDecimate}" + Value="{Binding Operation.Brightness}"/> + <TextBlock + Grid.Row="4" Grid.Column="4" + IsVisible="{Binding !Operation.IsDecimate}" + Text="{Binding Operation.BrightnessPercent, StringFormat=\{0:0.00\}%}" VerticalAlignment="Center"/> + + + + <TextBlock + Grid.Row="6" + IsVisible="{Binding !Operation.IsDecimate}" Text="Supports margin:" VerticalAlignment="Center"/> <TextBlock - Grid.Row="2" - IsVisible="{Binding #ReliefType.SelectedIndex}" + Grid.Row="6" + IsVisible="{Binding Operation.IsDecimate}" ToolTip.Tip="Raft will be replaced by the present supports and then dilated by this value to thicken the supports and increase the adhesion. 
Use large numbers with tiny supports for best adhesion." Text="Dilate supports by:" VerticalAlignment="Center"/> - <NumericUpDown Grid.Row="2" Grid.Column="2" + <NumericUpDown Grid.Row="6" Grid.Column="2" Width="100" Minimum="0" Maximum="255" Value="{Binding Operation.DilateIterations}"/> <TextBlock - Grid.Row="2" Grid.Column="4" + Grid.Row="6" Grid.Column="4" Text="px" VerticalAlignment="Center"/> <TextBlock - Grid.Row="4" + Grid.Row="8" Text="Wall margin:" VerticalAlignment="Center" - IsVisible="{Binding !#ReliefType.SelectedIndex}" + IsVisible="{Binding !Operation.IsDecimate}" /> - <NumericUpDown Grid.Row="4" Grid.Column="2" + <NumericUpDown Grid.Row="8" Grid.Column="2" Width="100" Minimum="1" Maximum="255" Value="{Binding Operation.WallMargin}" - IsVisible="{Binding !#ReliefType.SelectedIndex}"/> + IsVisible="{Binding !Operation.IsDecimate}"/> <TextBlock - Grid.Row="4" Grid.Column="4" + Grid.Row="8" Grid.Column="4" Text="px" VerticalAlignment="Center" - IsVisible="{Binding !#ReliefType.SelectedIndex}"/> + IsVisible="{Binding !Operation.IsDecimate}"/> <TextBlock - Grid.Row="6" + Grid.Row="10" Text="Hole diameter:" VerticalAlignment="Center" - IsVisible="{Binding !#ReliefType.SelectedIndex}" + IsVisible="{Binding Operation.IsRelief}" /> - <NumericUpDown Grid.Row="6" Grid.Column="2" + <NumericUpDown Grid.Row="10" Grid.Column="2" Width="100" Minimum="10" Maximum="255" Value="{Binding Operation.HoleDiameter}" - IsVisible="{Binding !#ReliefType.SelectedIndex}"/> + IsVisible="{Binding Operation.IsRelief}"/> <TextBlock - Grid.Row="6" Grid.Column="4" + Grid.Row="10" Grid.Column="4" Text="px" VerticalAlignment="Center" - IsVisible="{Binding !#ReliefType.SelectedIndex}"/> + IsVisible="{Binding Operation.IsRelief}"/> <TextBlock - Grid.Row="8" + Grid.Row="12" Text="Hole spacing:" VerticalAlignment="Center" - IsVisible="{Binding !#ReliefType.SelectedIndex}" + IsVisible="{Binding Operation.IsRelief}" /> - <NumericUpDown Grid.Row="8" Grid.Column="2" + <NumericUpDown Grid.Row="12" Grid.Column="2" Width="100" Minimum="10" Maximum="255" Value="{Binding Operation.HoleSpacing}" - IsVisible="{Binding !#ReliefType.SelectedIndex}"/> + IsVisible="{Binding Operation.IsRelief}"/> <TextBlock - Grid.Row="8" Grid.Column="4" + Grid.Row="12" Grid.Column="4" Text="px" VerticalAlignment="Center" - IsVisible="{Binding !#ReliefType.SelectedIndex}"/> - + IsVisible="{Binding Operation.IsRelief}"/> + </Grid> </UserControl> diff --git a/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml new file mode 100644 index 0000000..cce0f0e --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml @@ -0,0 +1,66 @@ +<UserControl xmlns="https://github.com/avaloniaui" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + x:Class="UVtools.WPF.Controls.Tools.ToolRedrawModelControl"> + <StackPanel Spacing="10"> + <Grid RowDefinitions="Auto,10,Auto,10,Auto,10,Auto" + ColumnDefinitions="Auto,10,100,5,300,5,Auto"> + <TextBlock Grid.Row="0" Grid.Column="0" + ToolTip.Tip="Select the sliced file without supports and raft. (Model B)" + VerticalAlignment="Center" + Text="Body file:"/> + <TextBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" + ToolTip.Tip="Select the sliced file without supports and raft. (Model B)" + IsReadOnly="True" + Watermark="Select the sliced file without supports and raft. (Model B)" + Text="{Binding Operation.FilePath}"/> + <Button Grid.Row="0" Grid.Column="6" + ToolTip.Tip="Select the sliced file without supports and raft. (Model B)" + Command="{Binding ImportFile}"> + <Image Source="/Assets/Icons/file-import-16x16.png"/> + </Button> + + <TextBlock Grid.Row="2" Grid.Column="0" + VerticalAlignment="Center" + Text="Redraw:"/> + <ComboBox Grid.Row="2" Grid.Column="2" + SelectedItem="{Binding Operation.RedrawType}" + Items="{Binding Operation.RedrawTypesItems}"/> + + <TextBlock Grid.Row="4" Grid.Column="0" + VerticalAlignment="Center" + Text="Brightness:"/> + <NumericUpDown Grid.Row="4" Grid.Column="2" + ClipValueToMinMax="True" + Minimum="0" + Maximum="255" + Increment="1" + Value="{Binding Operation.Brightness}"/> + <TextBlock Grid.Row="4" Grid.Column="4" + VerticalAlignment="Center" + Text="{Binding Operation.BrightnessPercent, StringFormat=\{0:0.00\}%}"/> + + <StackPanel Grid.Row="2" Grid.Column="4" Grid.ColumnSpan="3" + Orientation="Horizontal" Spacing="10"> + <CheckBox + VerticalAlignment="Center" + IsVisible="{Binding !Operation.RedrawType}" + IsChecked="{Binding Operation.ContactPointsOnly}" + ToolTip.Tip="If enabled, it will only redraw the supports with physical contact points within the model. (Slower)" + Content="Only the contact supports"/> + + <CheckBox + VerticalAlignment="Center" + IsVisible="{Binding Operation.ContactPointsOnly}" + IsChecked="{Binding Operation.IgnoreContactLessPixels}" + ToolTip.Tip="If enabled, all supports pixels with no physical contact with the model will be ignored and maintain the same original brightness." + Content="Ignore contact-less pixels"/> + </StackPanel> + + </Grid> + + + </StackPanel> +</UserControl> diff --git a/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml.cs b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml.cs new file mode 100644 index 0000000..83934ff --- /dev/null +++ b/UVtools.WPF/Controls/Tools/ToolRedrawModelControl.axaml.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using UVtools.Core.FileFormats; +using UVtools.Core.Operations; + +namespace UVtools.WPF.Controls.Tools +{ + public class ToolRedrawModelControl : ToolControl + { + public OperationRedrawModel Operation => BaseOperation as OperationRedrawModel; + public ToolRedrawModelControl() + { + InitializeComponent(); + BaseOperation = new OperationRedrawModel(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + public async void ImportFile() + { + var filters = Helpers.ToAvaloniaFileFilter(FileFormat.AllFileFiltersAvalonia); + var orderedFilters = new List<FileDialogFilter> { filters[UserSettings.Instance.General.DefaultOpenFileExtensionIndex] }; + for (int i = 0; i < filters.Count; i++) + { + if (i == UserSettings.Instance.General.DefaultOpenFileExtensionIndex) continue; + orderedFilters.Add(filters[i]); + } + + var dialog = new OpenFileDialog + { + AllowMultiple = false, + Filters = orderedFilters, + Directory = UserSettings.Instance.General.DefaultDirectoryOpenFile + }; + var files = await dialog.ShowAsync(ParentWindow); + if (files is null || files.Length <= 0) return; + if (FileFormat.FindByExtension(files[0], true) is null) return; + Operation.FilePath = files[0]; + } + } +} diff --git a/UVtools.WPF/MainWindow.Issues.cs b/UVtools.WPF/MainWindow.Issues.cs index 53cef90..e1b2f76 100644 --- a/UVtools.WPF/MainWindow.Issues.cs +++ b/UVtools.WPF/MainWindow.Issues.cs @@ -166,7 +166,7 @@ namespace UVtools.WPF if (layersRemove.Count > 0) { - SlicerFile.LayerManager.RemoveLayers(layersRemove); + OperationLayerRemove.RemoveLayers(SlicerFile, layersRemove); result = true; } diff --git a/UVtools.WPF/MainWindow.axaml.cs b/UVtools.WPF/MainWindow.axaml.cs index 51c2286..78de0d8 100644 --- a/UVtools.WPF/MainWindow.axaml.cs +++ b/UVtools.WPF/MainWindow.axaml.cs @@ -22,6 +22,7 @@ using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Threading.Tasks; using UVtools.Core; using UVtools.Core.Extensions; @@ -131,6 +132,14 @@ namespace UVtools.WPF }, new MenuItem { + Tag = new OperationRedrawModel(), + Icon = new Avalonia.Controls.Image + { + Source = new Bitmap(App.GetAsset("/Assets/Icons/sun-16x16.png")) + } + }, + new MenuItem + { Tag = new OperationThreshold(), Icon = new Avalonia.Controls.Image { @@ -401,8 +410,6 @@ namespace UVtools.WPF //this.AttachDevTools(); #endif - UpdateMaxWindowsSize(); - App.ThemeSelector?.EnableThemes(this); InitInformation(); InitIssues(); @@ -422,7 +429,7 @@ namespace UVtools.WPF { foreach (var menuTool in menuItem) { - if (!(menuTool.Tag is Operation operation)) continue; + if (menuTool.Tag is not Operation operation) continue; menuTool.Header = operation.Title; menuTool.Click += async (sender, args) => await ShowRunOperation(operation.GetType()); } @@ -439,20 +446,23 @@ namespace UVtools.WPF } };*/ //PropertyChanged += OnPropertyChanged; - var clientSizeObs = this.GetObservable(ClientSizeProperty); - clientSizeObs.Subscribe(size => UpdateLayerTrackerHighlightIssues()); - var windowStateObs = this.GetObservable(WindowStateProperty); - windowStateObs.Subscribe(size => UpdateLayerTrackerHighlightIssues()); - + UpdateTitle(); + UpdateMaxWindowsSize(); - if (Settings.General.StartMaximized + if (Settings.General.StartMaximized || ClientSize.Width > App.MaxWindowSize.Width || ClientSize.Height > App.MaxWindowSize.Height) { WindowState = WindowState.Maximized; } + var clientSizeObs = this.GetObservable(ClientSizeProperty); + clientSizeObs.Subscribe(size => UpdateLayerTrackerHighlightIssues()); + var windowStateObs = this.GetObservable(WindowStateProperty); + windowStateObs.Subscribe(size => UpdateLayerTrackerHighlightIssues()); + + DataContext = this; AddHandler(DragDrop.DropEvent, (sender, e) => @@ -463,14 +473,22 @@ namespace UVtools.WPF public void UpdateMaxWindowsSize() { - App.MaxWindowSize = new System.Drawing.Size(Settings.General.WindowsTakeIntoAccountScreenScaling ? (int)(Screens.Primary.WorkingArea.Width / Screens.Primary.PixelDensity) : Screens.Primary.WorkingArea.Width, - Settings.General.WindowsTakeIntoAccountScreenScaling ? (int)(Screens.Primary.WorkingArea.Height / Screens.Primary.PixelDensity) : Screens.Primary.WorkingArea.Height); + /*Console.WriteLine($"Settings is null?: {Settings is null}"); + Console.WriteLine($"Settings.General is null?: {Settings.General is null}"); + Console.WriteLine($"Screens is null?: {Screens is null}"); + Console.WriteLine($"ScreenCount: {Screens.ScreenCount}"); + Console.WriteLine($"Screens.Primary is null?: {Screens.Primary is null}");*/ + var screen = Screens.Primary ?? Screens.All[0]; + App.MaxWindowSize = new System.Drawing.Size( + Settings.General.WindowsTakeIntoAccountScreenScaling ? (int)(screen.WorkingArea.Width / screen.PixelDensity) : screen.WorkingArea.Width, + Settings.General.WindowsTakeIntoAccountScreenScaling ? (int)(screen.WorkingArea.Height / screen.PixelDensity) : screen.WorkingArea.Height); } protected override void OnOpened(EventArgs e) { base.OnOpened(e); - Program.ProgramStartupTime.Stop(); + + AddLog($"{About.Software} start", Program.ProgramStartupTime.Elapsed.TotalSeconds); if (Settings.General.CheckForUpdatesOnStartup) @@ -489,6 +507,7 @@ namespace UVtools.WPF UpdateTitle(); return true; }, TimeSpan.FromSeconds(1)); + Program.ProgramStartupTime.Stop(); } private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) @@ -804,6 +823,12 @@ namespace UVtools.WPF "Changelog:\n" + $"{VersionChecker.Changelog}", $"Update UVtools to v{VersionChecker.Version}?", ButtonEnum.YesNoCancel); + + if (result == ButtonResult.No || OperatingSystem.IsMacOS()) + { + App.OpenBrowser(VersionChecker.UrlLatestRelease); + return; + } if (result == ButtonResult.Yes) { IsGUIEnabled = false; @@ -832,11 +857,7 @@ namespace UVtools.WPF return; } - if (result == ButtonResult.No) - { - App.OpenBrowser(VersionChecker.UrlLatestRelease); - return; - } + } #endregion @@ -936,7 +957,7 @@ namespace UVtools.WPF ClipboardManager.Instance.Init(SlicerFile); - if (!(SlicerFile.ConvertToFormats is null)) + if (SlicerFile.ConvertToFormats is not null) { List<MenuItem> menuItems = new List<MenuItem>(); foreach (var fileFormatType in SlicerFile.ConvertToFormats) @@ -1290,18 +1311,13 @@ namespace UVtools.WPF switch (baseOperation) { case OperationEditParameters operation: - /*foreach (var modifier in operation.Modifiers.Where(modifier => modifier.HasChanged)) - { - SlicerFile.SetValueFromPrintParameterModifier(modifier, modifier.NewValue); - }*/ - SlicerFile.EditPrintParameters(operation); + operation.Execute(SlicerFile); RefreshProperties(); RefreshCurrentLayerData(); ResetDataContext(); CanSave = true; - - return false; + return true; case OperationRepairLayers operation: if (Issues is null) { @@ -1319,7 +1335,7 @@ namespace UVtools.WPF } operation.Issues = Issues.ToList(); - + operation.IslandDetectionConfig = GetIslandDetectionConfiguration(); break; } @@ -1331,110 +1347,13 @@ namespace UVtools.WPF ShowProgressWindow(baseOperation.ProgressTitle); backup = SlicerFile.LayerManager.Clone(); - /*var backup = new Layer[baseOperation.LayerRangeCount]; - uint i = 0; - for (uint layerIndex = baseOperation.LayerIndexStart; layerIndex <= baseOperation.LayerIndexEnd; layerIndex++) - { - backup[i++] = SlicerFile[layerIndex].Clone(); - }*/ - try { - switch (baseOperation) - { - // Tools - case OperationRepairLayers operation: - operation.IslandDetectionConfig = GetIslandDetectionConfiguration(); - SlicerFile.LayerManager.RepairLayers(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationMove operation: - SlicerFile.LayerManager.Move(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationResize operation: - SlicerFile.LayerManager.Resize(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationFlip operation: - SlicerFile.LayerManager.Flip(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationRotate operation: - SlicerFile.LayerManager.Rotate(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationSolidify operation: - SlicerFile.LayerManager.Solidify(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationMorph operation: - SlicerFile.LayerManager.Morph(operation, BorderType.Default, new MCvScalar(), ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationRaftRelief operation: - SlicerFile.LayerManager.RaftRelief(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationThreshold operation: - SlicerFile.LayerManager.ThresholdPixels(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationArithmetic operation: - SlicerFile.LayerManager.Arithmetic(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationMask operation: - SlicerFile.LayerManager.Mask(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationPixelDimming operation: - SlicerFile.LayerManager.PixelDimming(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationInfill operation: - SlicerFile.LayerManager.Infill(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationBlur operation: - SlicerFile.LayerManager.Blur(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - - case OperationChangeResolution operation: - SlicerFile.LayerManager.ChangeResolution(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationLayerReHeight operation: - SlicerFile.LayerManager.ReHeight(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationPattern operation: - SlicerFile.LayerManager.Pattern(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - // Actions - case OperationLayerImport operation: - SlicerFile.LayerManager.Import(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationLayerClone operation: - SlicerFile.LayerManager.CloneLayer(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationLayerRemove operation: - SlicerFile.LayerManager.RemoveLayers(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - - // Calibrators - case OperationCalibrateElephantFoot operation: - SlicerFile.LayerManager.CalibrateElephantFoot(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationCalibrateXYZAccuracy operation: - SlicerFile.LayerManager.CalibrateXYZAccuracy(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationCalibrateTolerance operation: - SlicerFile.LayerManager.CalibrateTolerance(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - case OperationCalibrateGrayscale operation: - SlicerFile.LayerManager.CalibrateGrayscale(operation, ProgressWindow.RestartProgress(operation.CanCancel)); - break; - - default: - throw new NotImplementedException(); - } - - return true; + return baseOperation.Execute(SlicerFile, ProgressWindow.RestartProgress(baseOperation.CanCancel)); } catch (OperationCanceledException) { SlicerFile.LayerManager = backup; - /*i = 0; - for (uint layerIndex = baseOperation.LayerIndexStart; layerIndex <= baseOperation.LayerIndexEnd; layerIndex++) - { - SlicerFile[layerIndex] = backup[i++]; - }*/ } catch (Exception ex) { @@ -1467,7 +1386,7 @@ namespace UVtools.WPF } } - return true; + return result; } #endregion diff --git a/UVtools.WPF/Structures/AppVersionChecker.cs b/UVtools.WPF/Structures/AppVersionChecker.cs index 79e6903..eaa6128 100644 --- a/UVtools.WPF/Structures/AppVersionChecker.cs +++ b/UVtools.WPF/Structures/AppVersionChecker.cs @@ -159,85 +159,82 @@ namespace UVtools.WPF.Structures progress.ItemName = "Megabytes"; try { - using (var client = new WebClient()) - { - var path = Path.GetTempPath(); - DownloadedFile = Path.Combine(path, Filename); - Debug.WriteLine($"Downloading to: {DownloadedFile}"); + using var client = new WebClient(); + var path = Path.GetTempPath(); + DownloadedFile = Path.Combine(path, Filename); + Debug.WriteLine($"Downloading to: {DownloadedFile}"); - client.DownloadProgressChanged += (sender, e) => + client.DownloadProgressChanged += (sender, e) => + { + progress.Reset("Megabytes", (uint)e.TotalBytesToReceive / 1048576, (uint)e.BytesReceived / 1048576); + }; + client.DownloadFileCompleted += (sender, e) => + { + progress.Reset("Extracting"); + if (OperatingSystem.IsWindows()) { - progress.Reset("Megabytes", (uint)e.TotalBytesToReceive / 1048576, (uint)e.BytesReceived / 1048576); - }; - client.DownloadFileCompleted += (sender, e) => + App.StartProcess(DownloadedFile); + Environment.Exit(0); + } + else { - progress.Reset("Extracting"); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - App.StartProcess(DownloadedFile); - Environment.Exit(0); - } - else + string upgradeFolder = "UPDATED_VERSION"; + var targetDir = Path.Combine(App.ApplicationPath, upgradeFolder); + using (var stream = File.Open(DownloadedFile, FileMode.Open)) { - string upgradeFolder = "UPDATED_VERSION"; - var targetDir = Path.Combine(App.ApplicationPath, upgradeFolder); - using (var stream = File.Open(DownloadedFile, FileMode.Open)) - { - using (ZipArchive zip = new ZipArchive(stream, ZipArchiveMode.Read)) - { - zip.ExtractToDirectory(targetDir, true); - } - } - - File.Delete(DownloadedFile); - - string upgradeFileName = $"{About.Software}_upgrade.sh"; - var upgradeFile = Path.Combine(App.ApplicationPath, upgradeFileName); - using (var stream = File.CreateText(upgradeFile)) + using (ZipArchive zip = new ZipArchive(stream, ZipArchiveMode.Read)) { - stream.WriteLine("#!/bin/bash"); - stream.WriteLine($"echo {About.Software} v{App.Version} updater script"); - stream.WriteLine($"cd '{App.ApplicationPath}'"); - stream.WriteLine($"killall {About.Software}"); - stream.WriteLine("sleep 0.5"); - stream.WriteLine($"cp -fR {upgradeFolder}/* ."); - stream.WriteLine($"rm -fr {upgradeFolder}"); - stream.WriteLine("sleep 0.5"); - //stream.WriteLine($"[ -f {About.Software} ] && {App.AppExecutableQuoted} & || dotnet {About.Software}.dll &"); - stream.WriteLine($"if [ -f '{About.Software}' ]; then"); - stream.WriteLine($" ./{About.Software} &"); - stream.WriteLine("else"); - stream.WriteLine($" dotnet {About.Software}.dll &"); - stream.WriteLine("fi"); - stream.WriteLine($"rm -f {upgradeFileName}"); - //stream.WriteLine("exit"); - stream.Close(); + zip.ExtractToDirectory(targetDir, true); } - - App.StartProcess("bash", $"\"{upgradeFile}\""); - - //App.NewInstance(App.MainWindow.SlicerFile?.FileFullPath); - Environment.Exit(0); } - lock (e.UserState) + File.Delete(DownloadedFile); + + string upgradeFileName = $"{About.Software}_upgrade.sh"; + var upgradeFile = Path.Combine(App.ApplicationPath, upgradeFileName); + using (var stream = File.CreateText(upgradeFile)) { - //releases blocked thread - Monitor.Pulse(e.UserState); + stream.WriteLine("#!/bin/bash"); + stream.WriteLine($"echo {About.Software} v{App.Version} updater script"); + stream.WriteLine($"cd '{App.ApplicationPath}'"); + stream.WriteLine($"killall {About.Software}"); + stream.WriteLine("sleep 0.5"); + stream.WriteLine($"cp -fR {upgradeFolder}/* ."); + stream.WriteLine($"rm -fr {upgradeFolder}"); + stream.WriteLine("sleep 0.5"); + //stream.WriteLine($"[ -f {About.Software} ] && {App.AppExecutableQuoted} & || dotnet {About.Software}.dll &"); + stream.WriteLine($"if [ -f '{About.Software}' ]; then"); + stream.WriteLine($" ./{About.Software} &"); + stream.WriteLine("else"); + stream.WriteLine($" dotnet {About.Software}.dll &"); + stream.WriteLine("fi"); + stream.WriteLine($"rm -f {upgradeFileName}"); + //stream.WriteLine("exit"); + stream.Close(); } - }; + App.StartProcess("bash", $"\"{upgradeFile}\""); + + //App.NewInstance(App.MainWindow.SlicerFile?.FileFullPath); + Environment.Exit(0); + } - var syncObject = new object(); - - lock (syncObject) + lock (e.UserState) { - client.DownloadFileAsync(new Uri(DownloadLink), DownloadedFile, syncObject); - //This would block the thread until download completes - Monitor.Wait(syncObject); + //releases blocked thread + Monitor.Pulse(e.UserState); } + }; + + var syncObject = new object(); + + lock (syncObject) + { + client.DownloadFileAsync(new Uri(DownloadLink), DownloadedFile, syncObject); + //This would block the thread until download completes + Monitor.Wait(syncObject); } } catch (Exception e) diff --git a/UVtools.WPF/Structures/OperationProfiles.cs b/UVtools.WPF/Structures/OperationProfiles.cs index 57e5c72..257b213 100644 --- a/UVtools.WPF/Structures/OperationProfiles.cs +++ b/UVtools.WPF/Structures/OperationProfiles.cs @@ -32,6 +32,7 @@ namespace UVtools.WPF.Structures //[XmlElement(typeof(OperationMask))] [XmlElement(typeof(OperationMorph))] [XmlElement(typeof(OperationRaftRelief))] + [XmlElement(typeof(OperationRedrawModel))] //[XmlElement(typeof(OperationMove))] //[XmlElement(typeof(OperationPattern))] [XmlElement(typeof(OperationPixelDimming))] @@ -42,6 +43,7 @@ namespace UVtools.WPF.Structures [XmlElement(typeof(OperationThreshold))] [XmlElement(typeof(OperationCalibrateElephantFoot))] [XmlElement(typeof(OperationCalibrateXYZAccuracy))] + [XmlElement(typeof(OperationCalibrateTolerance))] [XmlElement(typeof(OperationCalibrateGrayscale))] public List<Operation> Operations { get; internal set; } = new List<Operation>(); diff --git a/UVtools.WPF/UVtools.WPF.csproj b/UVtools.WPF/UVtools.WPF.csproj index 15aaf26..3c153db 100644 --- a/UVtools.WPF/UVtools.WPF.csproj +++ b/UVtools.WPF/UVtools.WPF.csproj @@ -12,7 +12,7 @@ <PackageLicenseFile>LICENSE</PackageLicenseFile> <RepositoryUrl>https://github.com/sn4k3/UVtools</RepositoryUrl> <RepositoryType>Git</RepositoryType> - <Version>2.0.0</Version> + <Version>2.1.0</Version> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> @@ -48,9 +48,6 @@ <None Remove="Assets\Icons\UVtools.ico" /> </ItemGroup> <ItemGroup> - <None Update="libcvextern.dylib"> - <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> - </None> <None Update="libcvextern.so"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> @@ -88,6 +85,9 @@ <Compile Update="Controls\Tools\ToolLayerRemoveControl.axaml.cs"> <DependentUpon>ToolLayerRemoveControl.axaml</DependentUpon> </Compile> + <Compile Update="Controls\Tools\ToolRedrawModelControl.axaml.cs"> + <DependentUpon>ToolRedrawModelControl.axaml</DependentUpon> + </Compile> <Compile Update="Controls\Tools\ToolResizeControl.axaml.cs"> <DependentUpon>ToolResizeControl.axaml</DependentUpon> </Compile> diff --git a/UVtools.WPF/UVtools.sh b/UVtools.WPF/UVtools.sh index 4bac434..e4516b4 100644 --- a/UVtools.WPF/UVtools.sh +++ b/UVtools.WPF/UVtools.sh @@ -1,3 +1,3 @@ #!/bin/bash cd "$(dirname "$0")" -dotnet UVtools.dll
\ No newline at end of file +[ -f UVtools ] && ./UVtools || dotnet UVtools.dll |