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 0000000000..1cfed5fe59 Binary files /dev/null and b/src/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl differ 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 = {