From dca3b4edf239f8e555cf17dd1bc2ea2fc8af4138 Mon Sep 17 00:00:00 2001 From: LarsBrubaker Date: Fri, 25 Nov 2022 10:58:41 -0800 Subject: [PATCH] Adding QRCode objec Moving classes to new files Adding sheet data calculation tests --- .../ApplicationView/SceneOperations.cs | 4 +- .../EditorTools/LithophanePlugin.cs | 6 +- .../DesignTools/LithophaneObject3D.cs | 2 +- .../Primitives/IEditorWidgetModifier.cs | 39 ++ .../DesignTools/Primitives/IImageProvider.cs | 38 ++ .../DesignTools/Primitives/ImageObject3D.cs | 11 +- .../DesignTools/Primitives/QrCodeObject3D.cs | 475 ++++++++++++++++++ .../MatterControl/PrimitivesContainer.cs | 4 + MatterControlLib/MatterControlLib.csproj | 2 + StaticData/Translations/Master.txt | 3 + Submodules/MatterSlice | 2 +- Submodules/agg-sharp | 2 +- .../DesignTools/SheetTests.cs | 28 ++ 13 files changed, 598 insertions(+), 18 deletions(-) create mode 100644 MatterControlLib/DesignTools/Primitives/IEditorWidgetModifier.cs create mode 100644 MatterControlLib/DesignTools/Primitives/IImageProvider.cs create mode 100644 MatterControlLib/DesignTools/Primitives/QrCodeObject3D.cs diff --git a/MatterControlLib/ApplicationView/SceneOperations.cs b/MatterControlLib/ApplicationView/SceneOperations.cs index 721492179..c7812b78a 100644 --- a/MatterControlLib/ApplicationView/SceneOperations.cs +++ b/MatterControlLib/ApplicationView/SceneOperations.cs @@ -353,7 +353,7 @@ namespace MatterHackers.MatterControl { var scene = sceneContext.Scene; var sceneItem = scene.SelectedItem; - if (sceneItem is IObject3D imageObject) + if (sceneItem is IImageProvider imageObject) { // TODO: make it look like this (and get rid of all the other stuff) // scene.Replace(sceneItem, new ImageToPathObject3D_2(sceneItem.Clone())); @@ -373,7 +373,7 @@ namespace MatterHackers.MatterControl }, Icon = (theme) => StaticData.Instance.LoadIcon("image_to_path.png", 16, 16).SetToColor(theme.TextColor).SetPreMultiply(), HelpTextGetter = () => "An image must be selected".Localize().Stars(), - IsEnabled = (sceneContext) => sceneContext.Scene.SelectedItem != null && sceneContext.Scene.SelectedItem is ImageObject3D, + IsEnabled = (sceneContext) => sceneContext.Scene.SelectedItem != null && sceneContext.Scene.SelectedItem is IImageProvider, }; } diff --git a/MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs b/MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs index 52afd8793..36f15bd20 100644 --- a/MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs +++ b/MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs @@ -80,14 +80,14 @@ namespace MatterHackers.MatterControl.Plugins.Lithophane { var scene = sceneContext.Scene; var sceneItem = scene.SelectedItem; - if (sceneItem is IObject3D imageObject) + if (sceneItem is IImageProvider imageObject) { WrapWith(sceneItem, new LithophaneObject3D(), scene); } }, - IsEnabled = (sceneContext) => sceneContext?.Scene?.SelectedItem is ImageObject3D, + IsEnabled = (sceneContext) => sceneContext?.Scene?.SelectedItem is IImageProvider, HelpTextGetter = () => "An image must be selected".Localize().Stars(), - ShowInModifyMenu = (sceneContext) => sceneContext?.Scene?.SelectedItem is ImageObject3D, + ShowInModifyMenu = (sceneContext) => sceneContext?.Scene?.SelectedItem is IImageProvider, Icon = (theme) => StaticData.Instance.LoadIcon("lithophane.png", 16, 16).SetToColor(theme.TextColor) }, "Image"); diff --git a/MatterControlLib/DesignTools/LithophaneObject3D.cs b/MatterControlLib/DesignTools/LithophaneObject3D.cs index 31d6b1571..15638a6f7 100644 --- a/MatterControlLib/DesignTools/LithophaneObject3D.cs +++ b/MatterControlLib/DesignTools/LithophaneObject3D.cs @@ -53,7 +53,7 @@ namespace MatterHackers.MatterControl.Plugins.Lithophane } [JsonIgnore] - public ImageObject3D ImageChild => this.Children.OfType().FirstOrDefault(); + public IImageProvider ImageChild => this.Children.OfType().FirstOrDefault(); [DisplayName("Pixels Per mm"), Range(0.5, 3, ErrorMessage = "Value for {0} must be between {1} and {2}.")] public double PixelsPerMM { get; set; } = 1.5; diff --git a/MatterControlLib/DesignTools/Primitives/IEditorWidgetModifier.cs b/MatterControlLib/DesignTools/Primitives/IEditorWidgetModifier.cs new file mode 100644 index 000000000..88a99fb99 --- /dev/null +++ b/MatterControlLib/DesignTools/Primitives/IEditorWidgetModifier.cs @@ -0,0 +1,39 @@ +/* +Copyright (c) 2022 Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System; +using MatterHackers.Agg.UI; + +namespace MatterHackers.MatterControl.DesignTools +{ + public interface IEditorWidgetModifier + { + void ModifyEditorWidget(GuiWidget widget, ThemeConfig theme, Action requestWidgetUpdate); + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Primitives/IImageProvider.cs b/MatterControlLib/DesignTools/Primitives/IImageProvider.cs new file mode 100644 index 000000000..48ee1e33c --- /dev/null +++ b/MatterControlLib/DesignTools/Primitives/IImageProvider.cs @@ -0,0 +1,38 @@ +/* +Copyright (c) 2022 Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using MatterHackers.Agg.Image; + +namespace MatterHackers.MatterControl.DesignTools +{ + public interface IImageProvider + { + ImageBuffer Image { get; } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Primitives/ImageObject3D.cs b/MatterControlLib/DesignTools/Primitives/ImageObject3D.cs index 9665357ba..b96db19cb 100644 --- a/MatterControlLib/DesignTools/Primitives/ImageObject3D.cs +++ b/MatterControlLib/DesignTools/Primitives/ImageObject3D.cs @@ -46,17 +46,8 @@ using Newtonsoft.Json; namespace MatterHackers.MatterControl.DesignTools { - public interface IEditorWidgetModifier - { - void ModifyEditorWidget(GuiWidget widget, ThemeConfig theme, Action requestWidgetUpdate); - } - public interface IImageProvider - { - ImageBuffer Image { get; } - } - - [HideMeterialAndColor] + [HideMeterialAndColor] public class ImageObject3D : AssetObject3D, IImageProvider, IObject3DControlsProvider, IEditorWidgetModifier { private const double DefaultSizeMm = 60; diff --git a/MatterControlLib/DesignTools/Primitives/QrCodeObject3D.cs b/MatterControlLib/DesignTools/Primitives/QrCodeObject3D.cs new file mode 100644 index 000000000..b6842a54e --- /dev/null +++ b/MatterControlLib/DesignTools/Primitives/QrCodeObject3D.cs @@ -0,0 +1,475 @@ +/* +Copyright (c) 2022 Lars Brubaker, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System; +using System.ComponentModel; +using System.IO; +using System.Threading.Tasks; +using MatterHackers.Agg; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.ImageProcessing; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.UI; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.DataStorage; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.PolygonMesh; +using Newtonsoft.Json; +using QRCoder; + +namespace MatterHackers.MatterControl.DesignTools +{ + [HideMeterialAndColor] + public class QrCodeObject3D : Object3D, IImageProvider, IObject3DControlsProvider + { + private const double DefaultSizeMm = 60; + + private ImageBuffer _image; + + private bool _invert; + + public QrCodeObject3D() + { + Name = "QR Code".Localize(); + } + + public static async Task Create() + { + var item = new QrCodeObject3D(); + await item.Rebuild(); + return item; + } + + public override bool CanApply => false; + + public StringOrExpression Text { get; set; } = "https://www.matterhackers.com"; + + [DisplayName("")] + [JsonIgnore] + [ImageDisplay(Margin = new int[] { 9, 3, 9, 3 }, MaxXSize = 400, Stretch = true)] + public ImageBuffer Image + { + get + { + if (_image == null) + { + RebuildImage(); + + // send the invalidate on image change + this.CancelAllParentBuilding(); + Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Image)); + Invalidate(InvalidateType.DisplayValues); + } + + return _image; + } + + set + { + } + } + + private void RebuildImage() + { + // set a temp image so we don't have any problems with threading + var image = this.BuildImage(); + + if (image != null) + { + if (this.Invert) + { + image = InvertLightness.DoInvertLightness(image); + } + } + else // bad load + { + image = new ImageBuffer(200, 100); + var graphics2D = image.NewGraphics2D(); + graphics2D.Clear(Color.White); + graphics2D.DrawString("Image Missing".Localize(), image.Width / 2, image.Height / 2, 20, Agg.Font.Justification.Center, Agg.Font.Baseline.BoundsCenter); + } + + // we don't want to invalidate on the mesh change + using (RebuildLock()) + { + base.Mesh = this.InitMesh(image) ?? PlatonicSolids.CreateCube(100, 100, 0.2); + } + + if (_image == null) + { + _image = image; + } + else + { + _image.CopyFrom(image); + } + } + + public bool Invert + { + get => _invert; + set + { + if (_invert != value) + { + _invert = value; + RebuildImage(); + Invalidate(InvalidateType.Image); + } + } + } + + public override async void OnInvalidate(InvalidateArgs invalidateArgs) + { + if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this)) + { + RebuildImage(); + await Rebuild(); + } + else if (SheetObject3D.NeedsRebuild(this, invalidateArgs)) + { + RebuildImage(); + await Rebuild(); + } + else + { + base.OnInvalidate(invalidateArgs); + } + } + + public static string GetFileOrAsset(string file) + { + if (!File.Exists(file)) + { + var path = Path.Combine(ApplicationDataStorage.Instance.LibraryAssetsPath, file); + if (File.Exists(path)) + { + return path; + } + + // can't find a real file + return null; + } + + return file; + } + + + public static bool FilesAreEqual(string first, string second) + { + if (string.IsNullOrEmpty(first) + || string.IsNullOrEmpty(second)) + { + return false; + } + + var diskFirst = GetFileOrAsset(first); + var diskSecond = GetFileOrAsset(second); + if (File.Exists(diskFirst) && File.Exists(diskSecond)) + { + return FilesAreEqual(new FileInfo(diskFirst), new FileInfo(diskSecond)); + } + + return false; + } + + public static bool FilesAreEqual(FileInfo first, FileInfo second) + { + if (first.Length != second.Length) + { + return false; + } + + if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + int readSize = 1 << 16; + int numReads = (int)Math.Ceiling((double)first.Length / readSize); + + using (var firstFs = first.OpenRead()) + { + using (var secondFs = second.OpenRead()) + { + byte[] one = new byte[readSize]; + byte[] two = new byte[readSize]; + + for (int i = 0; i < numReads; i++) + { + firstFs.Read(one, 0, readSize); + secondFs.Read(two, 0, readSize); + + for (int j = 0; j < readSize; j++) + { + if (one[j] != two[j]) + { + return false; + } + } + } + } + } + + return true; + } + public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer) + { + object3DControlsLayer.AddControls(ControlTypes.Standard2D); + } + + private ImageBuffer BuildImage() + { + QRCodeGenerator qrGenerator = new QRCodeGenerator(); + QRCodeData qrCodeData = qrGenerator.CreateQrCode(Text.Value(this), QRCodeGenerator.ECCLevel.Q); + QRCode qrCode = new QRCode(qrCodeData); + System.Drawing.Bitmap qrCodeImage = qrCode.GetGraphic(16); + + var destImage = new ImageBuffer(); + ConvertBitmapToImage(destImage, qrCodeImage); + return destImage; + } + + public bool ConvertBitmapToImage(ImageBuffer destImage, System.Drawing.Bitmap bitmap) + { + if (bitmap != null) + { + switch (bitmap.PixelFormat) + { + case System.Drawing.Imaging.PixelFormat.Format32bppArgb: + { + destImage.Allocate(bitmap.Width, bitmap.Height, bitmap.Width * 4, 32); + if (destImage.GetRecieveBlender() == null) + { + destImage.SetRecieveBlender(new BlenderBGRA()); + } + + System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits( + new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat); + int sourceIndex = 0; + int destIndex = 0; + unsafe + { + byte[] destBuffer = destImage.GetBuffer(out int offset); + byte* pSourceBuffer = (byte*)bitmapData.Scan0; + for (int y = 0; y < destImage.Height; y++) + { + destIndex = destImage.GetBufferOffsetXY(0, destImage.Height - 1 - y); + for (int x = 0; x < destImage.Width; x++) + { +#if true + destBuffer[destIndex++] = pSourceBuffer[sourceIndex++]; + destBuffer[destIndex++] = pSourceBuffer[sourceIndex++]; + destBuffer[destIndex++] = pSourceBuffer[sourceIndex++]; + destBuffer[destIndex++] = pSourceBuffer[sourceIndex++]; +#else + Color notPreMultiplied = new Color(pSourceBuffer[sourceIndex + 0], pSourceBuffer[sourceIndex + 1], pSourceBuffer[sourceIndex + 2], pSourceBuffer[sourceIndex + 3]); + sourceIndex += 4; + Color preMultiplied = notPreMultiplied.ToColorF().premultiply().ToColor(); + destBuffer[destIndex++] = preMultiplied.blue; + destBuffer[destIndex++] = preMultiplied.green; + destBuffer[destIndex++] = preMultiplied.red; + destBuffer[destIndex++] = preMultiplied.alpha; +#endif + } + } + } + + bitmap.UnlockBits(bitmapData); + + return true; + } + + case System.Drawing.Imaging.PixelFormat.Format24bppRgb: + { + destImage.Allocate(bitmap.Width, bitmap.Height, bitmap.Width * 4, 32); + if (destImage.GetRecieveBlender() == null) + { + destImage.SetRecieveBlender(new BlenderBGRA()); + } + + System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat); + int sourceIndex = 0; + int destIndex = 0; + unsafe + { + byte[] destBuffer = destImage.GetBuffer(out int offset); + byte* pSourceBuffer = (byte*)bitmapData.Scan0; + for (int y = 0; y < destImage.Height; y++) + { + sourceIndex = y * bitmapData.Stride; + destIndex = destImage.GetBufferOffsetXY(0, destImage.Height - 1 - y); + for (int x = 0; x < destImage.Width; x++) + { + destBuffer[destIndex++] = pSourceBuffer[sourceIndex++]; + destBuffer[destIndex++] = pSourceBuffer[sourceIndex++]; + destBuffer[destIndex++] = pSourceBuffer[sourceIndex++]; + destBuffer[destIndex++] = 255; + } + } + } + + bitmap.UnlockBits(bitmapData); + return true; + } + + default: + // let this code fall through and return false + break; + } + } + + return false; + } + + public override Mesh Mesh + { + get + { + if (base.Mesh == null || base.Mesh.FaceTextures.Count <= 0) + { + using (this.RebuildLock()) + { + // TODO: Revise fallback mesh + base.Mesh = this.InitMesh(this.Image) ?? PlatonicSolids.CreateCube(100, 100, 0.2); + } + } + + return base.Mesh; + } + } + + public double ScaleMmPerPixels { get; private set; } + + public override Task Rebuild() + { + InitMesh(this.Image); + + UiThread.RunOnIdle(() => Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Image))); + + return base.Rebuild(); + } + + private Mesh InitMesh(ImageBuffer imageBuffer) + { + if (imageBuffer != null) + { + ScaleMmPerPixels = Math.Min(DefaultSizeMm / imageBuffer.Width, DefaultSizeMm / imageBuffer.Height); + + // Create texture mesh + double width = ScaleMmPerPixels * imageBuffer.Width; + double height = ScaleMmPerPixels * imageBuffer.Height; + + Mesh textureMesh = PlatonicSolids.CreateCube(width, height, 0.2); + textureMesh.PlaceTextureOnFaces(0, imageBuffer); + + return textureMesh; + } + + return null; + } + + public static void ModifyImageObjectEditorWidget(ImageObject3D imageObject, GuiWidget widget, ThemeConfig theme, Action requestWidgetUpdate) + { + widget.Click += (s, e) => + { + if (e.Button == MouseButtons.Left) + { + ShowOpenDialog(imageObject); + } + + if (e.Button == MouseButtons.Right) + { + var popupMenu = new PopupMenu(theme); + + var openMenu = popupMenu.CreateMenuItem("Open".Localize()); + openMenu.Click += (s2, e2) => + { + popupMenu.Close(); + ShowOpenDialog(imageObject); + }; + + popupMenu.CreateSeparator(); + + var copyMenu = popupMenu.CreateMenuItem("Copy".Localize()); + copyMenu.Click += (s2, e2) => + { + Clipboard.Instance.SetImage(imageObject.Image); + }; + + var pasteMenu = popupMenu.CreateMenuItem("Paste".Localize()); + pasteMenu.Click += (s2, e2) => + { + var activeImage = Clipboard.Instance.GetImage(); + + // Persist + string filePath = ApplicationDataStorage.Instance.GetNewLibraryFilePath(".png"); + ImageIO.SaveImageData( + filePath, + activeImage); + + imageObject.AssetPath = filePath; + imageObject.Mesh = null; + + requestWidgetUpdate(); + + imageObject.Invalidate(InvalidateType.Image); + }; + + pasteMenu.Enabled = Clipboard.Instance.ContainsImage; + + popupMenu.ShowMenu(widget, e); + } + }; + } + + public static void ShowOpenDialog(IAssetObject assetObject) + { + UiThread.RunOnIdle(() => + { + // we do this using to make sure that the stream is closed before we try and insert the Picture + AggContext.FileDialogs.OpenFileDialog( + new OpenFileDialogParams( + "Select an image file|*.jpg;*.png;*.bmp;*.gif;*.pdf", + multiSelect: false, + title: "Add Image".Localize()), + (openParams) => + { + if (!File.Exists(openParams.FileName)) + { + return; + } + + assetObject.AssetPath = openParams.FileName; + }); + }); + } + } +} \ No newline at end of file diff --git a/MatterControlLib/Library/Providers/MatterControl/PrimitivesContainer.cs b/MatterControlLib/Library/Providers/MatterControl/PrimitivesContainer.cs index cadedc09e..63789d6f7 100644 --- a/MatterControlLib/Library/Providers/MatterControl/PrimitivesContainer.cs +++ b/MatterControlLib/Library/Providers/MatterControl/PrimitivesContainer.cs @@ -120,6 +120,10 @@ namespace MatterHackers.MatterControl.Library "Dual Contouring".Localize(), async () => await DualContouringObject3D.Create()) { DateCreated = new DateTime(index++) }, + new GeneratorItem( + "QR Code".Localize(), + async () => await QrCodeObject3D.Create()) + { DateCreated = new DateTime(index++) }, #endif new GeneratorItem( "Image Converter".Localize(), diff --git a/MatterControlLib/MatterControlLib.csproj b/MatterControlLib/MatterControlLib.csproj index b586235d1..880bf10c1 100644 --- a/MatterControlLib/MatterControlLib.csproj +++ b/MatterControlLib/MatterControlLib.csproj @@ -4,6 +4,7 @@ net6.0-windows MatterHackers Inc. 2.20.12 + true @@ -104,6 +105,7 @@ + diff --git a/StaticData/Translations/Master.txt b/StaticData/Translations/Master.txt index 70cb79c48..53088e113 100644 --- a/StaticData/Translations/Master.txt +++ b/StaticData/Translations/Master.txt @@ -3916,6 +3916,9 @@ Translated:px English:Pyramid Translated:Pyramid +English:QR Code +Translated:QR Code + English:Quality Translated:Quality diff --git a/Submodules/MatterSlice b/Submodules/MatterSlice index 7e79fdb5d..fe32884d5 160000 --- a/Submodules/MatterSlice +++ b/Submodules/MatterSlice @@ -1 +1 @@ -Subproject commit 7e79fdb5d6d0ff90eb5d24f84716c62e5fb048df +Subproject commit fe32884d5958d2666f3912615f90234271b2471f diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 0d0d08f1c..5d6079f19 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 0d0d08f1cb4c56b977354b246fd002deb98ba8f7 +Subproject commit 5d6079f193aadd944321af96244700691d84e0f9 diff --git a/Tests/MatterControl.AutomationTests/DesignTools/SheetTests.cs b/Tests/MatterControl.AutomationTests/DesignTools/SheetTests.cs index 383313202..c57abae88 100644 --- a/Tests/MatterControl.AutomationTests/DesignTools/SheetTests.cs +++ b/Tests/MatterControl.AutomationTests/DesignTools/SheetTests.cs @@ -16,6 +16,34 @@ using TestInvoker; namespace MatterHackers.MatterControl.Tests.Automation { + [TestFixture] + public class SheetDataTests + { + [Test] + public void Calculations() + { + var sheetData = new SheetData(4, 4); + + sheetData[0, 0].Expression = "=4*2"; + + Assert.AreEqual("8", sheetData.EvaluateExpression("A1")); + Assert.AreEqual("8", sheetData.EvaluateExpression("a1")); + + sheetData["a2"].Expression = "=max(4, 5)"; + sheetData.Recalculate(); + Assert.AreEqual("5", sheetData.EvaluateExpression("a2")); + + sheetData["a3"].Expression = "=a1+a2"; + sheetData.Recalculate(); + Assert.AreEqual("13", sheetData.EvaluateExpression("a3")); + + sheetData["a4"].Expression = "=((4+5)/3+7)/5"; + sheetData.Recalculate(); + Assert.AreEqual("2", sheetData.EvaluateExpression("a4")); + + } + } + [TestFixture, Category("MatterControl.UI.Automation"), Parallelizable(ParallelScope.Children)] public class PrimitiveAndSheetsTests {