From e78e59e8494d0f825fcb8d67f525e119b416589e Mon Sep 17 00:00:00 2001 From: Lars Brubaker Date: Sun, 19 Nov 2023 16:14:51 -0800 Subject: [PATCH] Making new fit to bounds --- .../ApplicationView/SceneOperations.cs | 4 +- .../Obsolete/FitToBoundsObject3D.cs | 2 +- .../Operations/FitToBoundsObject3D_2.cs | 2 +- .../Operations/FitToBoundsObject3D_3.cs | 8 +- .../Operations/FitToBoundsObject3D_4.cs | 360 ++++++++++++++++++ StaticData/Translations/Master.txt | 21 + .../MatterControl/InteractiveSceneTests.cs | 10 +- 7 files changed, 397 insertions(+), 10 deletions(-) create mode 100644 MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_4.cs diff --git a/MatterControlLib/ApplicationView/SceneOperations.cs b/MatterControlLib/ApplicationView/SceneOperations.cs index b9c9080ac..24e7b6938 100644 --- a/MatterControlLib/ApplicationView/SceneOperations.cs +++ b/MatterControlLib/ApplicationView/SceneOperations.cs @@ -965,14 +965,14 @@ namespace MatterHackers.MatterControl { return new SceneOperation("Fit to Bounds") { - ResultType = typeof(FitToBoundsObject3D_3), + ResultType = typeof(FitToBoundsObject3D_4), TitleGetter = () => "Fit to Bounds".Localize(), Action = async (sceneContext) => { var scene = sceneContext.Scene; var selectedItem = scene.SelectedItem; scene.SelectedItem = null; - var fit = await FitToBoundsObject3D_3.Create(selectedItem.Clone()); + var fit = await FitToBoundsObject3D_4.Create(selectedItem.Clone()); fit.MakeNameNonColliding(); scene.UndoBuffer.AddAndDo(new ReplaceCommand(new[] { selectedItem }, new[] { fit })); scene.SelectedItem = fit; diff --git a/MatterControlLib/DesignTools/Obsolete/FitToBoundsObject3D.cs b/MatterControlLib/DesignTools/Obsolete/FitToBoundsObject3D.cs index fe2275d38..ece2782c7 100644 --- a/MatterControlLib/DesignTools/Obsolete/FitToBoundsObject3D.cs +++ b/MatterControlLib/DesignTools/Obsolete/FitToBoundsObject3D.cs @@ -63,7 +63,7 @@ namespace MatterHackers.MatterControl.DesignTools.Operations X_Y_Z } - [Obsolete("Not used anymore. Replaced with FitToBoundsObject3D_3", true)] + [Obsolete("Not used anymore. Replaced with FitToBoundsObject3D_4", true)] public class FitToBoundsObject3D : Object3D, IEditorDraw, IPropertyGridModifier { [Description("Set the shape the part will be fit into.")] diff --git a/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_2.cs b/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_2.cs index a7d82ca28..b1af295f9 100644 --- a/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_2.cs +++ b/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_2.cs @@ -48,7 +48,7 @@ using System.Threading.Tasks; namespace MatterHackers.MatterControl.DesignTools.Operations { - [Obsolete("Not used anymore. Replaced with FitToBoundsObject3D_3", true)] + [Obsolete("Not used anymore. Replaced with FitToBoundsObject3D_4", true)] public class FitToBoundsObject3D_2 : TransformWrapperObject3D, IEditorDraw { private Vector3 boundsSize; diff --git a/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_3.cs b/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_3.cs index 6432b417f..3f094c5ee 100644 --- a/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_3.cs +++ b/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_3.cs @@ -27,6 +27,11 @@ of the authors and should not be interpreted as representing official policies, either expressed or implied, of the FreeBSD Project. */ +/*********************************************************************/ +/**************************** OBSOLETE! ******************************/ +/************************ USE NEWER VERSION **************************/ +/*********************************************************************/ + using MatterHackers.Agg; using MatterHackers.Agg.UI; using MatterHackers.DataConverters3D; @@ -42,7 +47,8 @@ using System.Threading.Tasks; namespace MatterHackers.MatterControl.DesignTools.Operations { - public class FitToBoundsObject3D_3 : TransformWrapperObject3D, IEditorDraw, IPropertyGridModifier + [Obsolete("Not used anymore. Replaced with FitToBoundsObject3D_4", true)] + public class FitToBoundsObject3D_3 : TransformWrapperObject3D, IEditorDraw, IPropertyGridModifier { private InvalidateType additonalInvalidate; diff --git a/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_4.cs b/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_4.cs new file mode 100644 index 000000000..407bf5b9d --- /dev/null +++ b/MatterControlLib/DesignTools/Operations/FitToBoundsObject3D_4.cs @@ -0,0 +1,360 @@ +/* +Copyright (c) 2023, 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; +using MatterHackers.Agg.UI; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.PolygonMesh; +using MatterHackers.RenderOpenGl; +using MatterHackers.VectorMath; +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; + +namespace MatterHackers.MatterControl.DesignTools.Operations +{ + public class FitToBoundsObject3D_4 : TransformWrapperObject3D, IEditorDraw + { + private InvalidateType additonalInvalidate; + + public FitToBoundsObject3D_4() + { + Name = "Fit to Bounds".Localize(); + } + + public enum StretchOption + { + [Description("Do not change this side")] + None, + [Description("Shrink if required, but do not grow")] + Inside, + [Description("Grow to fill bounds")] + Expand + } + + private IObject3D FitBounds => Children.Last(); + + [EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)] + [Description("Ensure that the part maintains its proportions.")] + public LockProportions LockProportion { get; set; } = LockProportions.X_Y_Z; + + [MaxDecimalPlaces(3)] + public DoubleOrExpression Width { get; set; } = 0; + + [MaxDecimalPlaces(3)] + public DoubleOrExpression Depth { get; set; } = 0; + + [MaxDecimalPlaces(3)] + public DoubleOrExpression Height { get; set; } = 0; + + [SectionStart("X Axis"), DisplayName("Align")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_left.png", "align_center_x.png", "align_right.png", "align_origin.png" }, InvertIcons = true)] + public Align XAlign { get; set; } = Align.None; + + [DisplayName("Stretch")] + [EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)] + public StretchOption XStretchOption { get; set; } = StretchOption.Expand; + + [SectionStart("Y Axis"), DisplayName("Align")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_bottom.png", "align_center_y.png", "align_top.png", "align_origin.png" }, InvertIcons = true)] + public Align YAlign { get; set; } = Align.None; + + [DisplayName("Stretch")] + [EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)] + public StretchOption YStretchOption { get; set; } = StretchOption.Expand; + + [SectionStart("Z Axis"), DisplayName("Align")] + [EnumDisplay(IconPaths = new string[] { "424.png", "align_bottom.png", "align_center_y.png", "align_top.png", "align_origin.png" }, InvertIcons = true)] + public Align ZAlign { get; set; } = Align.None; + + [DisplayName("Stretch")] + [EnumDisplay(Mode = EnumDisplayAttribute.PresentationMode.Buttons)] + public StretchOption ZStretchOption { get; set; } = StretchOption.Expand; + + public static async Task Create(IObject3D itemToFit) + { + var fitToBounds = new FitToBoundsObject3D_4(); + using (fitToBounds.RebuildLock()) + { + var startingAabb = itemToFit.GetAxisAlignedBoundingBox(); + itemToFit.Translate(-startingAabb.Center); + + // add the fit item + var scaleItem = new Object3D(); + fitToBounds.Children.Add(scaleItem); + scaleItem.Children.Add(itemToFit); + + // create an object that just represents the bounds in the scene + var fitBounds = new Object3D() + { + Visible = false, + Color = new Color(Color.Red, 100), + Mesh = PlatonicSolids.CreateCube() + }; + // add the item that holds the bounds + fitToBounds.Children.Add(fitBounds); + + fitToBounds.Width = startingAabb.XSize; + fitToBounds.Depth = startingAabb.YSize; + fitToBounds.Height = startingAabb.ZSize; + await fitToBounds.Rebuild(); + + var finalAabb = fitToBounds.GetAxisAlignedBoundingBox(); + fitToBounds.Translate(startingAabb.Center - finalAabb.Center); + } + + return fitToBounds; + } + + AxisAlignedBoundingBox CalcBounds() + { + var aabb = UntransformedChildren.GetAxisAlignedBoundingBox(); + var center = aabb.Center; + + var constraint = new Vector3(Width.Value(this), Depth.Value(this), Height.Value(this)); + var aligns = new Align[] { XAlign, YAlign, ZAlign }; + + var minXyz = Vector3.Zero; + var maxXyz = Vector3.Zero; + for (int i = 0; i < 3; i++) + { + switch (aligns[i]) + { + case Align.Center: + case Align.None: + minXyz[i] = center[i] - constraint[i] / 2; + maxXyz[i] = center[i] + constraint[i] / 2; + break; + + case Align.Min: + minXyz[i] = aabb.MinXYZ[i]; + maxXyz[i] = minXyz[i] + constraint[i]; + break; + + case Align.Max: + maxXyz[i] = aabb.MaxXYZ[i]; + minXyz[i] = maxXyz[i] - constraint[i]; + break; + } + } + + return new AxisAlignedBoundingBox(minXyz, maxXyz); + } + + public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e) + { + layer.World.RenderAabb(this.CalcBounds(), this.WorldMatrix(), Color.Red, 1, 1); + } + + public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer) + { + return WorldViewExtensions.GetWorldspaceAabbOfRenderAabb(this.CalcBounds(), this.WorldMatrix(), 1, 1); + } + + public override AxisAlignedBoundingBox GetAxisAlignedBoundingBox(Matrix4X4 matrix) + { + if (Children.Count == 2) + { + AxisAlignedBoundingBox bounds; + using (FitBounds.RebuildLock()) + { + FitBounds.Visible = true; + bounds = base.GetAxisAlignedBoundingBox(matrix); + FitBounds.Visible = false; + } + + return bounds; + } + + return base.GetAxisAlignedBoundingBox(matrix); + } + + public override async void OnInvalidate(InvalidateArgs invalidateArgs) + { + additonalInvalidate = invalidateArgs.InvalidateType; + + if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children) + || invalidateArgs.InvalidateType.HasFlag(InvalidateType.Matrix) + || invalidateArgs.InvalidateType.HasFlag(InvalidateType.Mesh)) + && invalidateArgs.Source != this + && !RebuildLocked) + { + await Rebuild(); + } + else if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) && invalidateArgs.Source == this)) + { + await Rebuild(); + } + else if (Expressions.NeedRebuild(this, invalidateArgs)) + { + await Rebuild(); + } + else if (invalidateArgs.InvalidateType.HasFlag(InvalidateType.Properties) + || invalidateArgs.InvalidateType.HasFlag(InvalidateType.Matrix) + || invalidateArgs.InvalidateType.HasFlag(InvalidateType.Mesh) + || invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children)) + { + base.OnInvalidate(invalidateArgs); + } + + base.OnInvalidate(invalidateArgs); + + additonalInvalidate = InvalidateType.None; + } + + public override Task Rebuild() + { + this.DebugDepth("Rebuild"); + using (RebuildLock()) + { + using (new CenterAndHeightMaintainer(this)) + { + AdjustChildSize(null, null); + UpdateBoundsItem(); + } + } + + this.CancelAllParentBuilding(); + Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Matrix | additonalInvalidate)); + return Task.CompletedTask; + } + + private void AdjustChildSize(object sender, EventArgs e) + { + if (Children.Count > 0) + { + var aabb = UntransformedChildren.GetAxisAlignedBoundingBox(); + ItemWithTransform.Matrix = Matrix4X4.Identity; + var constraint = new Vector3(Width.Value(this), Depth.Value(this), Height.Value(this)); + + var scale = GetScale(constraint, aabb.Size); + + switch (LockProportion) + { + case LockProportions.None: + break; + + case LockProportions.X_Y: + var minXy = Math.Min(scale.X, scale.Y); + scale.X = minXy; + scale.Y = minXy; + break; + + case LockProportions.X_Y_Z: + var minXyz = Math.Min(Math.Min(scale.X, scale.Y), scale.Z); + scale.X = minXyz; + scale.Y = minXyz; + scale.Z = minXyz; + break; + } + + if (aabb.XSize > 0 && aabb.YSize > 0 && aabb.ZSize > 0) + { + ItemWithTransform.Matrix = Object3DExtensions.ApplyAtPosition(ItemWithTransform.Matrix, aabb.Center, Matrix4X4.CreateScale(scale)); + } + } + } + + private Vector3 GetScale(Vector3 constraint, Vector3 size) + { + var scale = Vector3.One; + + StretchOption[] stretchOptions = { XStretchOption, YStretchOption, ZStretchOption }; + + for (var i = 0; i < 3; i++) + { + switch (stretchOptions[i]) + { + case StretchOption.None: + scale[i] = 1; + break; + + case StretchOption.Inside: + scale[i] = Math.Min(constraint[i] / size[i], 1); + break; + + case StretchOption.Expand: + scale[i] = constraint[i] / size[i]; + break; + } + } + + return scale; + } + + private void UpdateBoundsItem() + { + if (Children.Count == 2) + { + var transformAabb = ItemWithTransform.GetAxisAlignedBoundingBox(); + var fitAabb = FitBounds.GetAxisAlignedBoundingBox(); + var fitSize = fitAabb.Size; + var boundsSize = new Vector3(Width.Value(this), Depth.Value(this), Height.Value(this)); + if (boundsSize.X != 0 && boundsSize.Y != 0 && boundsSize.Z != 0 + && (fitSize != boundsSize + || fitAabb.Center != transformAabb.Center)) + { + FitBounds.Matrix *= Matrix4X4.CreateScale( + boundsSize.X / fitSize.X, + boundsSize.Y / fitSize.Y, + boundsSize.Z / fitSize.Z); + + Vector3 offset = Vector3.Zero; + Align[] align = { XAlign, YAlign, ZAlign }; + + for (int i = 0; i < 3; i++) + { + switch (align[i]) + { + case Align.None: + break; + + case Align.Min: + offset[i] = transformAabb.MinXYZ[i] - fitAabb.MinXYZ[i]; + break; + + case Align.Center: + offset[i] = transformAabb.Center[i] - fitAabb.Center[i]; + break; + + case Align.Max: + offset[i] = transformAabb.MaxXYZ[i] - fitAabb.MaxXYZ[i]; + break; + } + } + + FitBounds.Matrix *= Matrix4X4.CreateTranslation(offset); + } + } + } + } +} \ No newline at end of file diff --git a/StaticData/Translations/Master.txt b/StaticData/Translations/Master.txt index 649ae3665..63189aef1 100644 --- a/StaticData/Translations/Master.txt +++ b/StaticData/Translations/Master.txt @@ -5236,6 +5236,9 @@ Translated:Stop trying to connect to the printer. English:Store Translated:Store +English:Stretch +Translated:Stretch + English:Stretch X Translated:Stretch X @@ -6703,6 +6706,9 @@ Translated:Write Filter English:Writing G-Code Translated:Writing G-Code +English:X Align +Translated:X Align + English:X and Y Distance Translated:X and Y Distance @@ -6715,6 +6721,9 @@ Translated:X Offset English:X Options Translated:X Options +English:X Stretch Option +Translated:X Stretch Option + English:X Y Nozzle Offsets Have Been Calibrated Translated:X Y Nozzle Offsets Have Been Calibrated @@ -6724,6 +6733,9 @@ Translated:XY Calibration English:XY Homing Position Translated:XY Homing Position +English:Y Align +Translated:Y Align + English:Y Intercept Translated:Y Intercept @@ -6733,6 +6745,9 @@ Translated:Y Offset English:Y Options Translated:Y Options +English:Y Stretch Option +Translated:Y Stretch Option + English:Yes Translated:Yes @@ -6841,6 +6856,9 @@ Translated:Your Start G-Code should not contain a G30 if you are planning on usi English:Your Start G-Code should not contain a G30 if you are planning on using Print Recovery. Change your start G-Code or turn off Print Recovery. Translated:Your Start G-Code should not contain a G30 if you are planning on using Print Recovery. Change your start G-Code or turn off Print Recovery. +English:Z Align +Translated:Z Align + English:Z Calibration Translated:Z Calibration @@ -6862,6 +6880,9 @@ Translated:Z Offset is too large. English:Z Options Translated:Z Options +English:Z Stretch Option +Translated:Z Stretch Option + English:ZIP File Translated:ZIP File diff --git a/Tests/MatterControl.Tests/MatterControl/InteractiveSceneTests.cs b/Tests/MatterControl.Tests/MatterControl/InteractiveSceneTests.cs index 6be4c5538..2f7282ab7 100644 --- a/Tests/MatterControl.Tests/MatterControl/InteractiveSceneTests.cs +++ b/Tests/MatterControl.Tests/MatterControl/InteractiveSceneTests.cs @@ -411,7 +411,7 @@ namespace MatterControl.Tests.MatterControl root.Children.Add(cube); Assert.IsTrue(root.GetAxisAlignedBoundingBox().Equals(new AxisAlignedBoundingBox(new Vector3(-10, -10, -10), new Vector3(10, 10, 10)), .001)); root.Children.Remove(cube); - var fit = await FitToBoundsObject3D_3.Create(cube); + var fit = await FitToBoundsObject3D_4.Create(cube); fit.Width = 50; fit.Depth = 20; @@ -426,7 +426,7 @@ namespace MatterControl.Tests.MatterControl { var root = new Object3D(); var cube = await CubeObject3D.Create(20, 20, 20); - var fit = await FitToBoundsObject3D_3.Create(cube); + var fit = await FitToBoundsObject3D_4.Create(cube); fit.Width = 50; fit.Depth = 20; @@ -471,7 +471,7 @@ namespace MatterControl.Tests.MatterControl { var root = new Object3D(); var cube = await CubeObject3D.Create(20, 20, 20); - var fit = await FitToBoundsObject3D_3.Create(cube); + var fit = await FitToBoundsObject3D_4.Create(cube); fit.Width = 50; fit.Depth = 20; @@ -606,7 +606,7 @@ namespace MatterControl.Tests.MatterControl var root = new Object3D(); var cube = await CubeObject3D.Create(20, 20, 20); - var fit = await FitToBoundsObject3D_3.Create(cube); + var fit = await FitToBoundsObject3D_4.Create(cube); fit.Width = 10; fit.Depth = 10; @@ -795,7 +795,7 @@ namespace MatterControl.Tests.MatterControl var root = new Object3D(); var cube = await CubeObject3D.Create(20, 20, 20); - var fit = await FitToBoundsObject3D_3.Create(cube); + var fit = await FitToBoundsObject3D_4.Create(cube); fit.Width = 50; fit.Depth = 20;