From cccadec44cfdddb8de6f169e37432825139c5a3c Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Tue, 25 Jan 2022 11:51:37 +0000 Subject: [PATCH] [FileExplorer] StlThumbnailProvider (#15568) * Adds StlThumbnailProvider * Spell checker fixes * Adds missing changes * Attempts to fix alpha background issue * Adds missing dependency references * Upgrades .NET Core 3.1 to .NET 5 * Updates Helix Toolkit to fix .net5 compatibility * Return null bitmap If STL model is empty --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/actions/spell-check/excludes.txt | 1 + .github/actions/spell-check/expect.txt | 3 + .pipelines/ESRPSigning_core.json | 4 + .../ci/templates/build-powertoys-steps.yml | 1 + PowerToys.sln | 18 +++ installer/PowerToysSetup/Product.wxs | 7 + src/common/utils/modulesRegistry.h | 17 +- .../StlThumbnailProvider.cs | 145 ++++++++++++++++++ .../StlThumbnailProvider.csproj | 65 ++++++++ .../HelperFiles/sample.stl | Bin 0 -> 6884 bytes .../StlThumbnailProviderTests.cs | 111 ++++++++++++++ .../UnitTests-StlThumbnailProvider.csproj | 78 ++++++++++ src/modules/previewpane/powerpreview/CLSID.h | 3 + .../previewpane/powerpreview/Resources.resx | 3 + .../previewpane/powerpreview/powerpreview.cpp | 4 + .../PowerPreviewProperties.cs | 17 ++ .../ViewModels/PowerPreviewViewModel.cs | 20 +++ .../ViewModelTests/PowerPreview.cs | 19 +++ .../Settings.UI/Strings/en-us/Resources.resw | 4 + .../Settings.UI/Views/PowerPreviewPage.xaml | 7 + .../BugReportTool/RegistryUtils.cpp | 4 +- 22 files changed, 530 insertions(+), 2 deletions(-) create mode 100644 src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.cs create mode 100644 src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.csproj create mode 100644 src/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl create mode 100644 src/modules/previewpane/UnitTests-StlThumbnailProvider/StlThumbnailProviderTests.cs create mode 100644 src/modules/previewpane/UnitTests-StlThumbnailProvider/UnitTests-StlThumbnailProvider.csproj diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3aa3eb1008..093eb881b0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -48,6 +48,7 @@ body: - PowerRename - PowerToys Run - Shortcut Guide + - STL Thumbnail - SVG Preview - SVG Thumbnail - Settings diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt index e87774eab0..82952268ee 100644 --- a/.github/actions/spell-check/excludes.txt +++ b/.github/actions/spell-check/excludes.txt @@ -26,6 +26,7 @@ ignore$ \.pdf$ \.PNG$ \.png$ +\.stl$ \.woff$ \.zip$ ^doc/devdocs/akaLinks\.md$ diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 3156e45743..dd6126fd24 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1103,6 +1103,7 @@ LPVOID LPW lpwndpl LPWSTR +LReader LRESULT lshift lstrcmp @@ -1397,6 +1398,7 @@ outsettings OVERLAPPEDWINDOW overlaywindow Overridable +Oversampling OWNDC PACL PAINTSTRUCT @@ -1861,6 +1863,7 @@ STEPIT stgm STGMEDIUM sticpl +stl stoi stol stoll diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 618e437b9c..1af2b33365 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -57,6 +57,8 @@ "modules\\FileExplorerPreview\\PowerToys.PdfThumbnailProvider.comhost.dll", "modules\\FileExplorerPreview\\PowerToys.powerpreview.dll", "modules\\FileExplorerPreview\\PowerToys.PreviewHandlerCommon.dll", + "modules\\FileExplorerPreview\\PowerToys.StlThumbnailProvider.dll", + "modules\\FileExplorerPreview\\PowerToys.StlThumbnailProvider.comhost.dll", "modules\\FileExplorerPreview\\PowerToys.SvgPreviewHandler.dll", "modules\\FileExplorerPreview\\PowerToys.SvgPreviewHandler.comhost.dll", "modules\\FileExplorerPreview\\PowerToys.SvgThumbnailProvider.dll", @@ -165,6 +167,8 @@ "NLog.dll", "HtmlAgilityPack.dll", "Markdig.Signed.dll", + "HelixToolkit.dll", + "HelixToolkit.Core.Wpf.dll", "Mages.Core.dll", "JetBrains.Annotations.dll", "ICSharpCode.SharpZipLib.dll", diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml index d016b552aa..c829384bd7 100644 --- a/.pipelines/ci/templates/build-powertoys-steps.yml +++ b/.pipelines/ci/templates/build-powertoys-steps.yml @@ -103,6 +103,7 @@ steps: testAssemblyVer2: | **\UnitTests-GcodeThumbnailProvider.dll **\UnitTests-SvgThumbnailProvider.dll + **\UnitTests-StlThumbnailProvider.dll **\UnitTests-PdfThumbnailProvider.dll **\Settings.UI.UnitTests.dll **\UnitTests-MarkdownPreviewHandler.dll diff --git a/PowerToys.sln b/PowerToys.sln index 2e80669738..2b19df568a 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -390,6 +390,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plu EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MousePointerCrosshair", "src\modules\MouseUtils\MousePointerCrosshair\MousePointerCrosshair.vcxproj", "{EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StlThumbnailProvider", "src\modules\previewpane\StlThumbnailProvider\StlThumbnailProvider.csproj", "{F7C8C0F1-5431-4347-89D0-8E5354F93CF2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-StlThumbnailProvider", "src\modules\previewpane\UnitTests-StlThumbnailProvider\UnitTests-StlThumbnailProvider.csproj", "{F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -1053,6 +1057,18 @@ Global {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|x64.ActiveCfg = Release|x64 {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|x64.Build.0 = Release|x64 {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E}.Release|x86.ActiveCfg = Release|x64 + {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Debug|x64.ActiveCfg = Debug|x64 + {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Debug|x64.Build.0 = Debug|x64 + {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Debug|x86.ActiveCfg = Debug|x64 + {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Release|x64.ActiveCfg = Release|x64 + {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Release|x64.Build.0 = Release|x64 + {F7C8C0F1-5431-4347-89D0-8E5354F93CF2}.Release|x86.ActiveCfg = Release|x64 + {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Debug|x64.ActiveCfg = Debug|x64 + {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Debug|x64.Build.0 = Debug|x64 + {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Debug|x86.ActiveCfg = Debug|x64 + {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Release|x64.ActiveCfg = Release|x64 + {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Release|x64.Build.0 = Release|x64 + {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC}.Release|x86.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1181,6 +1197,8 @@ Global {48A0A19E-A0BE-4256-ACF8-CC3B80291AF9} = {60CD2D4F-C3B9-4897-9821-FCA5098B41CE} {9F94B303-5E21-4364-9362-64426F8DB932} = {4AFC9975-2456-4C70-94A4-84073C1CED93} {EAE14C0E-7A6B-45DA-9080-A7D8C077BA6E} = {322566EF-20DC-43A6-B9F8-616AF942579A} + {F7C8C0F1-5431-4347-89D0-8E5354F93CF2} = {2F305555-C296-497E-AC20-5FA1B237996A} + {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC} = {2F305555-C296-497E-AC20-5FA1B237996A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 138fa8b3be..2fd6321248 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -877,6 +877,13 @@ + + + + + + + diff --git a/src/common/utils/modulesRegistry.h b/src/common/utils/modulesRegistry.h index 158fb0e5d7..85ec539a76 100644 --- a/src/common/utils/modulesRegistry.h +++ b/src/common/utils/modulesRegistry.h @@ -106,6 +106,20 @@ inline registry::ChangeSet getGcodeThumbnailHandlerChangeSet(const std::wstring L".gcode"); } +inline registry::ChangeSet getStlThumbnailHandlerChangeSet(const std::wstring installationDir, const bool perUser) +{ + using namespace registry::shellex; + return generatePreviewHandler(PreviewHandlerType::thumbnail, + perUser, + L"{8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF}", + get_std_product_version(), + (fs::path{ installationDir } / LR"d(modules\FileExplorerPreview\PowerToys.StlThumbnailProvider.comhost.dll)d").wstring(), + registry::DOTNET_COMPONENT_CATEGORY_CLSID, + L"Microsoft.PowerToys.ThumbnailHandler.Stl.StlThumbnailProvider", + L"Stl Thumbnail Provider", + L".stl"); +} + inline std::vector getAllModulesChangeSets(const std::wstring installationDir) { constexpr bool PER_USER = true; @@ -115,5 +129,6 @@ inline std::vector getAllModulesChangeSets(const std::wstri getGcodePreviewHandlerChangeSet(installationDir, PER_USER), getSvgThumbnailHandlerChangeSet(installationDir, PER_USER), getPdfThumbnailHandlerChangeSet(installationDir, PER_USER), - getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER) }; + getGcodeThumbnailHandlerChangeSet(installationDir, PER_USER), + getStlThumbnailHandlerChangeSet(installationDir, PER_USER) }; } \ No newline at end of file diff --git a/src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.cs b/src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.cs new file mode 100644 index 0000000000..ca3d7dcffb --- /dev/null +++ b/src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.cs @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Media3D; +using Common.ComInterlop; +using Common.Utilities; +using HelixToolkit.Wpf; +using Bitmap = System.Drawing.Bitmap; + +namespace Microsoft.PowerToys.ThumbnailHandler.Stl +{ + /// + /// Stl Thumbnail Provider. + /// + [Guid("8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF")] + [ClassInterface(ClassInterfaceType.None)] + [ComVisible(true)] + public class StlThumbnailProvider : IInitializeWithStream, IThumbnailProvider + { + /// + /// Gets the stream object to access file. + /// + public IStream Stream { get; private set; } + + /// + /// The maximum dimension (width or height) thumbnail we will generate. + /// + private const uint MaxThumbnailSize = 10000; + + /// + /// Loads the Stl model into a Viewport3D and renders a bitmap of it. + /// + /// The Stream instance for the Stl content. + /// The maximum thumbnail size, in pixels. + /// A thumbnail rendered from the Stl model. + public static Bitmap GetThumbnail(Stream stream, uint cx) + { + if (cx > MaxThumbnailSize || stream == null || stream.Length == 0) + { + return null; + } + + Bitmap thumbnail = null; + + var stlReader = new StLReader + { + DefaultMaterial = new DiffuseMaterial(new SolidColorBrush(Color.FromRgb(255, 201, 36))), + }; + + var model = stlReader.Read(stream); + + if (model.Bounds == Rect3D.Empty) + { + return null; + } + + var viewport = new System.Windows.Controls.Viewport3D(); + + viewport.Measure(new Size(cx, cx)); + viewport.Arrange(new Rect(0, 0, cx, cx)); + + var modelVisual = new ModelVisual3D() + { + Transform = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, 1), 180)), + }; + viewport.Children.Add(modelVisual); + viewport.Children.Add(new DefaultLights()); + + var perspectiveCamera = new PerspectiveCamera + { + Position = new Point3D(1, 2, 1), + LookDirection = new Vector3D(-1, -2, -1), + UpDirection = new Vector3D(0, 0, 1), + FieldOfView = 20, + NearPlaneDistance = 0.1, + FarPlaneDistance = double.PositiveInfinity, + }; + viewport.Camera = perspectiveCamera; + + modelVisual.Content = model; + + perspectiveCamera.ZoomExtents(viewport); + + var bitmapExporter = new BitmapExporter + { + Background = new SolidColorBrush(Colors.Transparent), + OversamplingMultiplier = 1, + }; + + var bitmapStream = new MemoryStream(); + + bitmapExporter.Export(viewport, bitmapStream); + + bitmapStream.Position = 0; + + thumbnail = new Bitmap(bitmapStream); + + return thumbnail; + } + + /// + public void Initialize(IStream pstream, uint grfMode) + { + // Ignore the grfMode always use read mode to access the file. + this.Stream = pstream; + } + + /// + public void GetThumbnail(uint cx, out IntPtr phbmp, out WTS_ALPHATYPE pdwAlpha) + { + phbmp = IntPtr.Zero; + pdwAlpha = WTS_ALPHATYPE.WTSAT_UNKNOWN; + + if (cx == 0 || cx > MaxThumbnailSize) + { + return; + } + + using (var stream = new ReadonlyStream(this.Stream as IStream)) + { + using (var memStream = new MemoryStream()) + { + stream.CopyTo(memStream); + + memStream.Position = 0; + + using (Bitmap thumbnail = GetThumbnail(memStream, cx)) + { + if (thumbnail != null && thumbnail.Size.Width > 0 && thumbnail.Size.Height > 0) + { + phbmp = thumbnail.GetHbitmap(System.Drawing.Color.Transparent); + pdwAlpha = WTS_ALPHATYPE.WTSAT_ARGB; + } + } + } + } + } + } +} diff --git a/src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.csproj b/src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.csproj new file mode 100644 index 0000000000..0f82a9d0de --- /dev/null +++ b/src/modules/previewpane/StlThumbnailProvider/StlThumbnailProvider.csproj @@ -0,0 +1,65 @@ + + + x64 + true + {F7C8C0F1-5431-4347-89D0-8E5354F93CF2} + Microsoft.PowerToys.ThumbnailHandler.Stl + PowerToys.StlThumbnailProvider + PowerToys.StlThumbnailProvider + PowerToys StlPreviewHandler + Microsoft Corporation + Copyright (C) 2020 Microsoft Corporation + PowerToys + net5.0-windows + true + true + Microsoft Corporation + PowerToys + en-US + PowerToys StlPreviewHandler + Copyright (C) 2020 Microsoft Corporation + $(SolutionDir)$(Platform)\$(Configuration)\modules\FileExplorerPreview\ + true + false + false + true + true + + + + + + + + + all + + + all + + + all + + + all + + + all + + + all + + + + + + + + + + + + StyleCop.json + + + \ No newline at end of file diff --git a/src/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl b/src/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl new file mode 100644 index 0000000000000000000000000000000000000000..1cfed5fe5994b7d1c114b3faa7f61e105b8ef030 GIT binary patch literal 6884 zcma)>&x;*J5XVPhV}yVMq6j817cZXnErJq%WCl-S_M{?$pg~1)NJ3U65CqYK%A%kb zeTXX$5-%F?=s_^=AsD;}A|AY%KOhL6Tms@$*YvAT^>oi>VBdCnzMrb<>gwLto#%E= zojv{5ndX^iPd>kM`pwsNn(cFEo6T9XeQKxK+T5Bov)S>7wvKN;Qr*(?;Yii2s!D$3 z3GJ?`7X43b>l=Q?H)|E?zAgR!X~bUtv*=#__PzGdyW7jbhxD(;#1DU5Xb)d`wS_9s z(*Nh%mxlh5j~l-`>-l{70d$s&4zF7pj`?I5K!e~!59aXn&9vLEOx?|0KPh7vabwE@k%u?z@N3Kzk z(1@kqKTVgYRa3Lr?T=dh zEjDe=E%w--8@W^leDouiClEt~+7my0EzzG@idy|mTN0i8TOx`5()LeIC5qVZ)$2FC z?a&@5`iTS%WfuixnN)U0Gp{PWUZk4yWjzq{_=Z#LT4?->%J z(4R&3^X~i0Jh8F-B)pON@G7Id52{Fi@#}7g_JeyL7>y;`s;V<@+_CmY+WY8Z?QLj& zNNAt-dm>S->GSvB-6|!kz&{q1k0kaNs$?a#lw^AFmlA%a;+RsJQT_Mi$F0PReK7d* zmPlMW^m!m;21)PV^>x4Yo7J_a)A^u^bgwRqBd?MuN`%jQR8^I?#BtpG?x#@USB@U; zIGf%Q302j7elE9Kef3JJPiIs6sjn)kNUQI9CBDmq&Uqc?MpuEhJwX-y=?bpH3ibrw zkkA>dGc(9|sUq!tXcjdmBxuPKRFT#h%w?B^)~n`+NS6t!NV68o zK3Gd-Vyqhf74<%p(7&%-!L;Owv5Iuu!O8G1Cuwcx2&zbzcOR;_8|?A_yWEw0R8>{un|_T`QC&QL zc$J+K>f$-7#u=x^xBD7rDjyfUd!ls4@ln|cf|gPrRM~k@{f+ls$(5a0yuZ>nea6m{ zP0qaI8!`-vO+!KRc5z*c4m!|p1K3b zSNK#^tRPw*tnO5|D86JwQDxb`1Z?ewRWm?Nkfuaz7@6)T+U#*bsH zQoE(7?A~zR*{#tWH&gTEJGI@`jS0PtObOa0ZMTL~gxzvYMMV|;*^O7~gM{5J&T(^_ z5_YFI??~U857PAK=Z|)I?`QpsWIpn$sd1PsW|X&)>B#f9j?6+Dl@aP=d{3S8L6zQ% z`v`rmSW8yNi_ZFUQAHo7VuWc_R-?2#pYTDuDXsUgsSnmG3F}YCkymL{c^_7z3%Ab8 zKJ3lh-Q%03bR1UUy!rREpF!G6X}zIOeQ@QFu>Lexd6h-Yv-bhH}MvUu0i{s GhW`Q9)hMt4 literal 0 HcmV?d00001 diff --git a/src/modules/previewpane/UnitTests-StlThumbnailProvider/StlThumbnailProviderTests.cs b/src/modules/previewpane/UnitTests-StlThumbnailProvider/StlThumbnailProviderTests.cs new file mode 100644 index 0000000000..1557943494 --- /dev/null +++ b/src/modules/previewpane/UnitTests-StlThumbnailProvider/StlThumbnailProviderTests.cs @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Common.ComInterlop; +using Microsoft.PowerToys.STATestExtension; +using Microsoft.PowerToys.ThumbnailHandler.Stl; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace StlThumbnailProviderUnitTests +{ + [STATestClass] + public class StlThumbnailProviderTests + { + [TestMethod] + public void GetThumbnailValidStreamStl() + { + // Act + var file = File.ReadAllBytes("HelperFiles/sample.stl"); + + StlThumbnailProvider provider = new StlThumbnailProvider(); + + provider.Initialize(GetMockStream(file), 0); + + provider.GetThumbnail(256, out IntPtr bitmap, out WTS_ALPHATYPE alphaType); + + Assert.IsTrue(bitmap != IntPtr.Zero); + Assert.IsTrue(alphaType == WTS_ALPHATYPE.WTSAT_ARGB); + } + + [TestMethod] + public void GetThumbnailInValidSizeStl() + { + // Act + var file = File.ReadAllBytes("HelperFiles/sample.stl"); + + StlThumbnailProvider provider = new StlThumbnailProvider(); + + provider.Initialize(GetMockStream(file), 0); + + provider.GetThumbnail(0, out IntPtr bitmap, out WTS_ALPHATYPE alphaType); + + Assert.IsTrue(bitmap == IntPtr.Zero); + Assert.IsTrue(alphaType == WTS_ALPHATYPE.WTSAT_UNKNOWN); + } + + [TestMethod] + public void GetThumbnailToBigStl() + { + // Act + var file = File.ReadAllBytes("HelperFiles/sample.stl"); + + StlThumbnailProvider provider = new StlThumbnailProvider(); + + provider.Initialize(GetMockStream(file), 0); + + provider.GetThumbnail(10001, out IntPtr bitmap, out WTS_ALPHATYPE alphaType); + + Assert.IsTrue(bitmap == IntPtr.Zero); + Assert.IsTrue(alphaType == WTS_ALPHATYPE.WTSAT_UNKNOWN); + } + + [TestMethod] + public void CheckNoStlEmptyStreamShouldReturnNullBitmap() + { + using (var stream = new MemoryStream()) + { + Bitmap thumbnail = StlThumbnailProvider.GetThumbnail(stream, 256); + Assert.IsTrue(thumbnail == null); + } + } + + [TestMethod] + public void CheckNoStlNullStreamShouldReturnNullBitmap() + { + Bitmap thumbnail = StlThumbnailProvider.GetThumbnail(null, 256); + Assert.IsTrue(thumbnail == null); + } + + private static IStream GetMockStream(byte[] sourceArray) + { + var streamMock = new Mock(); + int bytesRead = 0; + + streamMock + .Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((buffer, countToRead, bytesReadPtr) => + { + int actualCountToRead = Math.Min(sourceArray.Length - bytesRead, countToRead); + if (actualCountToRead > 0) + { + Array.Copy(sourceArray, bytesRead, buffer, 0, actualCountToRead); + Marshal.WriteInt32(bytesReadPtr, actualCountToRead); + bytesRead += actualCountToRead; + } + else + { + Marshal.WriteInt32(bytesReadPtr, 0); + } + }); + + return streamMock.Object; + } + } +} diff --git a/src/modules/previewpane/UnitTests-StlThumbnailProvider/UnitTests-StlThumbnailProvider.csproj b/src/modules/previewpane/UnitTests-StlThumbnailProvider/UnitTests-StlThumbnailProvider.csproj new file mode 100644 index 0000000000..d4b77ddfbd --- /dev/null +++ b/src/modules/previewpane/UnitTests-StlThumbnailProvider/UnitTests-StlThumbnailProvider.csproj @@ -0,0 +1,78 @@ + + + x64 + UnitTests-StlThumbnailProvider + PowerToys UnitTests-StlThumbnailProvider + Microsoft Corporation + Copyright (C) 2020 Microsoft Corporation + PowerToys + UnitTests-StlThumbnailProvider + Microsoft Corporation + PowerToys + en-US + PowerToys UnitTests-StlThumbnailProvider + Copyright (C) 2021 Microsoft Corporation + + + + {F1F6B6B6-9F18-4A17-8B5C-97DF552C53DC} + StlThumbnailProviderUnitTests + net5.0-windows10.0.18362.0 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + + + + + + + all + + + all + + + all + + + all + + + all + + + + + + all + + + + + + + + + + + + + + + StyleCop.json + + + + + Always + + + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/CLSID.h b/src/modules/previewpane/powerpreview/CLSID.h index d546fa4ae1..931c6469d4 100644 --- a/src/modules/previewpane/powerpreview/CLSID.h +++ b/src/modules/previewpane/powerpreview/CLSID.h @@ -39,6 +39,9 @@ const CLSID CLSID_GcodePreviewHandler = { 0xec52dea8, 0x7c9f, 0x4130, { 0xa7, 0x // BFEE99B4-B74D-4348-BCA5-E757029647FF const GUID CLSID_GcodeThumbnailProvider = { 0xbfee99b4, 0xb74d, 0x4348, { 0xbc, 0xa5, 0xe7, 0x57, 0x02, 0x96, 0x47, 0xff } }; + +// 8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF +const GUID CLSID_StlThumbnailProvider = { 0x8bc8afc2, 0x4e7c, 0x4695, { 0x81, 0x8e, 0x8c, 0x1f, 0xfd, 0xce, 0xa2, 0xaf } }; // Pairs of NativeClsid vs ManagedClsid used for preview handlers. const std::vector> NativeToManagedClsid({ diff --git a/src/modules/previewpane/powerpreview/Resources.resx b/src/modules/previewpane/powerpreview/Resources.resx index 333f8dcd1d..1efc7aa9af 100644 --- a/src/modules/previewpane/powerpreview/Resources.resx +++ b/src/modules/previewpane/powerpreview/Resources.resx @@ -186,4 +186,7 @@ G-code Previewer + + Stl Thumbnail Provider + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/powerpreview.cpp b/src/modules/previewpane/powerpreview/powerpreview.cpp index a0bbd6f43c..3c1a0ea981 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.cpp +++ b/src/modules/previewpane/powerpreview/powerpreview.cpp @@ -54,6 +54,10 @@ PowerPreviewModule::PowerPreviewModule() : .settingDescription = GET_RESOURCE_STRING(IDS_GCODE_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), .registryChanges = getGcodeThumbnailHandlerChangeSet(installationDir, installPerUser) }); + m_fileExplorerModules.push_back({ .settingName = L"stl-thumbnail-toggle-setting", + .settingDescription = GET_RESOURCE_STRING(IDS_STL_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), + .registryChanges = getStlThumbnailHandlerChangeSet(installationDir, installPerUser) }); + try { PowerToysSettings::PowerToyValues settings = diff --git a/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs b/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs index 0d35c229fd..0fffa808eb 100644 --- a/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs +++ b/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs @@ -131,6 +131,23 @@ namespace Microsoft.PowerToys.Settings.UI.Library } } + private bool enableStlThumbnail = true; + + [JsonPropertyName("stl-thumbnail-toggle-setting")] + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool EnableStlThumbnail + { + get => enableStlThumbnail; + set + { + if (value != enableStlThumbnail) + { + LogTelemetryEvent(value); + enableStlThumbnail = value; + } + } + } + public PowerPreviewProperties() { } diff --git a/src/settings-ui/Settings.UI.Library/ViewModels/PowerPreviewViewModel.cs b/src/settings-ui/Settings.UI.Library/ViewModels/PowerPreviewViewModel.cs index 238705368a..6b0dfd1437 100644 --- a/src/settings-ui/Settings.UI.Library/ViewModels/PowerPreviewViewModel.cs +++ b/src/settings-ui/Settings.UI.Library/ViewModels/PowerPreviewViewModel.cs @@ -53,6 +53,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels _gcodeRenderIsEnabled = Settings.Properties.EnableGcodePreview; _pdfThumbnailIsEnabled = Settings.Properties.EnablePdfThumbnail; _gcodeThumbnailIsEnabled = Settings.Properties.EnableGcodeThumbnail; + _stlThumbnailIsEnabled = Settings.Properties.EnableStlThumbnail; } private bool _svgRenderIsEnabled; @@ -62,6 +63,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private bool _svgThumbnailIsEnabled; private bool _pdfThumbnailIsEnabled; private bool _gcodeThumbnailIsEnabled; + private bool _stlThumbnailIsEnabled; public bool SVGRenderIsEnabled { @@ -189,6 +191,24 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public bool STLThumbnailIsEnabled + { + get + { + return _stlThumbnailIsEnabled; + } + + set + { + if (value != _stlThumbnailIsEnabled) + { + _stlThumbnailIsEnabled = value; + Settings.Properties.EnableStlThumbnail = value; + RaisePropertyChanged(); + } + } + } + public string GetSettingsSubPath() { return _settingsConfigFileFolder + "\\" + ModuleName; diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs index 03d916492f..cc53a37011 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs @@ -63,6 +63,7 @@ namespace ViewModelTests Assert.AreEqual(originalSettings.Properties.EnableSvgThumbnail, viewModel.SVGThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnablePdfThumbnail, viewModel.PDFThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableGcodeThumbnail, viewModel.GCODEThumbnailIsEnabled); + Assert.AreEqual(originalSettings.Properties.EnableStlThumbnail, viewModel.STLThumbnailIsEnabled); // Verify that the stub file was used var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings) @@ -141,6 +142,24 @@ namespace ViewModelTests viewModel.GCODEThumbnailIsEnabled = true; } + [TestMethod] + public void STLThumbnailIsEnabledShouldPrevHandlerWhenSuccessful() + { + // Assert + Func sendMockIPCConfigMSG = msg => + { + SndModuleSettings snd = JsonSerializer.Deserialize>(msg); + Assert.IsTrue(snd.PowertoysSetting.FileExplorerPreviewSettings.Properties.EnableStlThumbnail); + return 0; + }; + + // arrange + PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), sendMockIPCConfigMSG, PowerPreviewSettings.ModuleName); + + // act + viewModel.STLThumbnailIsEnabled = true; + } + [TestMethod] public void MDRenderIsEnabledShouldPrevHandlerWhenSuccessful() { diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 8295ecc966..4208e8601a 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -632,6 +632,10 @@ Enable SVG (.svg) thumbnails Do you want this feature on / off + + Enable STL (.stl) thumbnails + Do you want this feature on / off + Enable PDF (.pdf) thumbnails Do you want this feature on / off diff --git a/src/settings-ui/Settings.UI/Views/PowerPreviewPage.xaml b/src/settings-ui/Settings.UI/Views/PowerPreviewPage.xaml index 488d41449b..681492a926 100644 --- a/src/settings-ui/Settings.UI/Views/PowerPreviewPage.xaml +++ b/src/settings-ui/Settings.UI/Views/PowerPreviewPage.xaml @@ -77,6 +77,13 @@ x:Uid="ToggleSwitch"/> + + + + + + diff --git a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp index e725d3e152..6025336e1e 100644 --- a/tools/BugReportTool/BugReportTool/RegistryUtils.cpp +++ b/tools/BugReportTool/BugReportTool/RegistryUtils.cpp @@ -19,6 +19,7 @@ namespace { HKEY_CLASSES_ROOT, L"CLSID\\{ec52dea8-7c9f-4130-a77b-1737d0418507}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{BCC13D15-9720-4CC4-8371-EA74A274741E}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{BFEE99B4-B74D-4348-BCA5-E757029647FF}" }, + { HKEY_CLASSES_ROOT, L"CLSID\\{8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF}" }, { HKEY_CLASSES_ROOT, L"CLSID\\{51B4D7E5-7568-4234-B4BB-47FB3C016A69}\\InprocServer32" }, { HKEY_CLASSES_ROOT, L"CLSID\\{0440049F-D1DC-4E46-B27B-98393D79486B}" }, { HKEY_CLASSES_ROOT, L"AllFileSystemObjects\\ShellEx\\ContextMenuHandlers\\PowerRenameExt" }, @@ -29,7 +30,8 @@ namespace { HKEY_CLASSES_ROOT, L".pdf\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" }, { HKEY_CLASSES_ROOT, L".pdf\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" }, { HKEY_CLASSES_ROOT, L".gcode\\shellex\\{8895b1c6-b41f-4c1c-a562-0d564250836f}" }, - { HKEY_CLASSES_ROOT, L".gcode\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" } + { HKEY_CLASSES_ROOT, L".gcode\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" }, + { HKEY_CLASSES_ROOT, L".stl\\shellex\\{E357FCCD-A995-4576-B01F-234630154E96}" } }; vector> registryValues = {