diff --git a/MatterControlLib/ApplicationView/SceneOperations.cs b/MatterControlLib/ApplicationView/SceneOperations.cs index 14b0ce5cb..a785e3480 100644 --- a/MatterControlLib/ApplicationView/SceneOperations.cs +++ b/MatterControlLib/ApplicationView/SceneOperations.cs @@ -763,7 +763,8 @@ namespace MatterHackers.MatterControl { CurveOperation(), PinchOperation(), - TwistOperation(), + RadialPinchOperation(), + TwistOperation(), PlaneCutOperation(), #if DEBUG FindSliceOperation(), @@ -1451,7 +1452,25 @@ namespace MatterHackers.MatterControl }; } - private static SceneOperation UngroupOperation() + private static SceneOperation RadialPinchOperation() + { + return new SceneOperation("Radial Pinch") + { + ResultType = typeof(RadialPinchObject3D), + TitleGetter = () => "Radial Pinch".Localize(), + Action = (sceneContext) => + { + var radialPinch = new RadialPinchObject3D(); + radialPinch.WrapSelectedItemAndSelect(sceneContext.Scene); + }, + Icon = (theme) => StaticData.Instance.LoadIcon("radial-pinch.png", 16, 16).GrayToColor(theme.TextColor), + HelpTextGetter = () => "At least 1 part must be selected".Localize().Stars(), + IsEnabled = (sceneContext) => IsMeshObject(sceneContext.Scene.SelectedItem), + }; + } + + + private static SceneOperation UngroupOperation() { return new SceneOperation("Ungroup") { diff --git a/MatterControlLib/DesignTools/Operations/IRadiusProvider.cs b/MatterControlLib/DesignTools/Operations/IRadiusProvider.cs new file mode 100644 index 000000000..d4f1d3022 --- /dev/null +++ b/MatterControlLib/DesignTools/Operations/IRadiusProvider.cs @@ -0,0 +1,36 @@ +/* +Copyright (c) 2018, 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. +*/ + +namespace MatterHackers.MatterControl.DesignTools +{ + public interface IRadiusProvider + { + double Radius { get; } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Operations/ObjectCircleExtensions.cs b/MatterControlLib/DesignTools/Operations/ObjectCircleExtensions.cs new file mode 100644 index 000000000..ae8c32ff7 --- /dev/null +++ b/MatterControlLib/DesignTools/Operations/ObjectCircleExtensions.cs @@ -0,0 +1,62 @@ +/* +Copyright (c) 2018, 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.Collections.Generic; +using System.Linq; +using MatterHackers.DataConverters3D; +using MatterHackers.PolygonMesh; +using MatterHackers.VectorMath; + +namespace MatterHackers.MatterControl.DesignTools +{ + public static class ObjectCircleExtensions + { + public static Circle GetSmallestEnclosingCircleAlongZ(this IObject3D object3D) + { + var visibleMeshes = object3D.VisibleMeshes().Select(vm => (source: vm, convexHull: vm.Mesh.GetConvexHull(false))).ToList(); + + IEnumerable GetVertices() + { + foreach (var visibleMesh in visibleMeshes) + { + var matrix = visibleMesh.source.WorldMatrix(object3D); + foreach (var positon in visibleMesh.convexHull.Vertices) + { + var transformed = positon.Transform(matrix); + yield return new Vector2(transformed.X, transformed.Y); + } + } + } + + var circle = SmallestEnclosingCircle.MakeCircle(GetVertices()); + + return circle; + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Operations/RadialPinchObject3D.cs b/MatterControlLib/DesignTools/Operations/RadialPinchObject3D.cs new file mode 100644 index 000000000..88d6b927d --- /dev/null +++ b/MatterControlLib/DesignTools/Operations/RadialPinchObject3D.cs @@ -0,0 +1,309 @@ +/* +Copyright (c) 2018, 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.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MatterHackers.Agg; +using MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.PolygonMesh; +using MatterHackers.RenderOpenGl; +using MatterHackers.RenderOpenGl.OpenGl; +using MatterHackers.VectorMath; +using static MatterHackers.MatterControl.DesignTools.LinearHorizontalOffset2D; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class LinearHorizontalOffset2D + { + public class ControlPoint + { + public double WidthRatio { get; set; } = 1.0; + + public double TopControlRatio { get; set; } + + public double BottomControlRatio { get; set; } + } + + public List ControlPoints { get; set; } = new List() + { + new ControlPoint() { WidthRatio = 1.0, TopControlRatio = .5, BottomControlRatio = 0 }, + new ControlPoint() { WidthRatio = 1.5, TopControlRatio = .5, BottomControlRatio = .5 }, + new ControlPoint() { WidthRatio = 1.0, TopControlRatio = 0, BottomControlRatio = .5 }, + }; + + public VertexStorage GetOffsetPath(double radius, double totalHeight) + { + double segmentHeight = totalHeight / (ControlPoints.Count - 1); + var vertexStorage = new VertexStorage(); + for (int i = 0; i < ControlPoints.Count; i++) + { + if (i == 0) + { + vertexStorage.MoveTo(radius * ControlPoints[0].WidthRatio, 0); + } + else + { + var curPoint = ControlPoints[i]; + var x = radius * curPoint.WidthRatio; + var y = i * segmentHeight; + vertexStorage.curve4(x, y - curPoint.BottomControlRatio * segmentHeight, x, y - curPoint.TopControlRatio * segmentHeight, x, y); + } + } + + return vertexStorage; + } + } + + public class RadialPinchObject3D : OperationSourceContainerObject3D, IPropertyGridModifier, IEditorDraw + { + public RadialPinchObject3D() + { + Name = "Radial Pinch".Localize(); + } + + public LinearHorizontalOffset2D LinearHorizontalOffset { get; set; } = new LinearHorizontalOffset2D(); + + [Description("Specifies the number of vertical cuts required to ensure the part can be pinched well.")] + [Slider(0, 50, snapDistance: 1)] + public IntOrExpression PinchSlices { get; set; } = 5; + + [Description("Enable advanced features.")] + public bool Advanced { get; set; } = false; + + [Description("Allows for the repositioning of the rotation origin")] + public Vector2 RotationOffset { get; set; } + + [Description("The percentage up from the bottom to end the pinch")] + [Slider(0, 100, Easing.EaseType.Quadratic, snapDistance: 1)] + public DoubleOrExpression EndHeightPercent { get; set; } = 100; + + [Description("The percentage up from the bottom to start the pinch")] + [Slider(0, 100, Easing.EaseType.Quadratic, snapDistance: 1)] + public DoubleOrExpression StartHeightPercent { get; set; } = 0; + + public IRadiusProvider RadiusProvider + { + get + { + if (this.SourceContainer.Children.Count == 1 + && this.SourceContainer.Children.First() is IRadiusProvider radiusProvider) + { + return radiusProvider; + } + + return null; + } + } + + public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e) + { + var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox(); + var rotationCenter = SourceContainer.GetSmallestEnclosingCircleAlongZ().Center + RotationOffset; + + var center = new Vector3(rotationCenter.X, rotationCenter.Y, sourceAabb.Center.Z); + + // render the top and bottom rings + layer.World.RenderCylinderOutline(this.WorldMatrix(), center, 1, sourceAabb.ZSize, 15, Color.Red, Color.Red, 5); + + // turn the lighting back on + GL.Enable(EnableCap.Lighting); + } + + public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer) + { + var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox(); + var rotationCenter = SourceContainer.GetSmallestEnclosingCircleAlongZ().Center + RotationOffset; + var center = new Vector3(rotationCenter.X, rotationCenter.Y, sourceAabb.Center.Z); + return AxisAlignedBoundingBox.CenteredBox(new Vector3(1, 1, sourceAabb.ZSize), center).NewTransformed(this.WorldMatrix()); + } + + public override Task Rebuild() + { + this.DebugDepth("Rebuild"); + + bool valuesChanged = false; + + var aabb = this.GetAxisAlignedBoundingBox(); + + var pinchSlices = PinchSlices.ClampIfNotCalculated(this, 0, 300, ref valuesChanged); + var endHeightPercent = EndHeightPercent.ClampIfNotCalculated(this, 0, 100, ref valuesChanged); + endHeightPercent = EndHeightPercent.ClampIfNotCalculated(this, 1, 100, ref valuesChanged); + var startHeightPercent = StartHeightPercent.ClampIfNotCalculated(this, 0, endHeightPercent - 1, ref valuesChanged); + startHeightPercent = Math.Min(endHeightPercent - 1, startHeightPercent); + + var rebuildLocks = this.RebuilLockAll(); + + return ApplicationController.Instance.Tasks.Execute( + "Pinch".Localize(), + null, + (reporter, cancellationToken) => + { + var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox(); + + var bottom = sourceAabb.MinXYZ.Z; + var top = sourceAabb.MaxXYZ.Z; + var size = sourceAabb.ZSize; + if (Advanced) + { + top -= sourceAabb.ZSize * endHeightPercent / 100.0; + bottom += sourceAabb.ZSize * startHeightPercent / 100.0; + size = top - bottom; + } + + double numberOfCuts = pinchSlices; + + double cutSize = size / numberOfCuts; + var cuts = new List(); + for (int i = 0; i < numberOfCuts + 1; i++) + { + var ratio = i / numberOfCuts; + cuts.Add(bottom - cutSize + (size * ratio)); + } + + // get the rotation from the center of the circumscribed circle of the convex hull + var enclosingCircle = SourceContainer.GetSmallestEnclosingCircleAlongZ(); + var rotationCenter = enclosingCircle.Center + RotationOffset; + + var horizontalOffest = LinearHorizontalOffset.GetOffsetPath(enclosingCircle.Radius + RotationOffset.Length, size); + + var pinchedChildren = new List(); + + foreach (var sourceItem in SourceContainer.VisibleMeshes()) + { + var originalMesh = sourceItem.Mesh; + var status = "Copy Mesh".Localize(); + reporter?.Invoke(0, status); + var transformedMesh = originalMesh.Copy(CancellationToken.None); + var itemMatrix = sourceItem.WorldMatrix(SourceContainer); + + // transform into this space + transformedMesh.Transform(itemMatrix); + + status = "Split Mesh".Localize(); + reporter?.Invoke(0, status); + + // split the mesh along the z axis + transformedMesh.SplitOnPlanes(Vector3.UnitZ, cuts, cutSize / 8); + + for (int i = 0; i < transformedMesh.Vertices.Count; i++) + { + var position = transformedMesh.Vertices[i]; + + var ratio = 1.0; + + if (position.Z >= bottom + && position.Z <= top) + { + ratio = (position.Z - bottom) / size; + } + + var positionXy = new Vector2(position) - rotationCenter; + if (true) + { + positionXy *= Easing.Quadratic.InOut(ratio); + } + else + { + //positionXy *= horizontalOffest.GetXAtY(positionXy.Y - bottom); + } + positionXy += rotationCenter; + transformedMesh.Vertices[i] = new Vector3Float(positionXy.X, positionXy.Y, position.Z); + } + + // transform back into item local space + transformedMesh.Transform(itemMatrix.Inverted); + + //transformedMesh.MergeVertices(.1); + transformedMesh.CalculateNormals(); + + var pinchedChild = new Object3D() + { + Mesh = transformedMesh + }; + pinchedChild.CopyWorldProperties(sourceItem, SourceContainer, Object3DPropertyFlags.All, false); + pinchedChild.Visible = true; + + pinchedChildren.Add(pinchedChild); + } + + RemoveAllButSource(); + this.SourceContainer.Visible = false; + + this.Children.Modify((list) => + { + list.AddRange(pinchedChildren); + }); + + ApplyHoles(reporter, cancellationToken.Token); + + UiThread.RunOnIdle(() => + { + rebuildLocks.Dispose(); + this.CancelAllParentBuilding(); + Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children)); + Invalidate(InvalidateType.DisplayValues); + }); + + return Task.CompletedTask; + }); + } + + private Dictionary changeSet = new Dictionary(); + + public void UpdateControls(PublicPropertyChange change) + { + changeSet.Clear(); + + changeSet.Add(nameof(RotationOffset), Advanced); + changeSet.Add(nameof(StartHeightPercent), Advanced); + changeSet.Add(nameof(EndHeightPercent), Advanced); + + // first turn on all the settings we want to see + foreach (var kvp in changeSet.Where(c => c.Value)) + { + change.SetRowVisible(kvp.Key, () => kvp.Value); + } + + // then turn off all the settings we want to hide + foreach (var kvp in changeSet.Where(c => !c.Value)) + { + change.SetRowVisible(kvp.Key, () => kvp.Value); + } + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/Operations/TwistObject3D.cs b/MatterControlLib/DesignTools/Operations/TwistObject3D.cs index 17c667970..fab13ed56 100644 --- a/MatterControlLib/DesignTools/Operations/TwistObject3D.cs +++ b/MatterControlLib/DesignTools/Operations/TwistObject3D.cs @@ -48,7 +48,7 @@ using Newtonsoft.Json; namespace MatterHackers.MatterControl.DesignTools { - public class TwistObject3D : OperationSourceContainerObject3D, IPropertyGridModifier, IEditorDraw + public class TwistObject3D : OperationSourceContainerObject3D, IPropertyGridModifier, IEditorDraw { public TwistObject3D() { @@ -374,34 +374,4 @@ namespace MatterHackers.MatterControl.DesignTools } } } - - public interface IRadiusProvider - { - double Radius { get; } - } - - public static class ObjectCircleExtensions - { - public static Circle GetSmallestEnclosingCircleAlongZ(this IObject3D object3D) - { - var visibleMeshes = object3D.VisibleMeshes().Select(vm => (source: vm, convexHull: vm.Mesh.GetConvexHull(false))).ToList(); - - IEnumerable GetVertices() - { - foreach (var visibleMesh in visibleMeshes) - { - var matrix = visibleMesh.source.WorldMatrix(object3D); - foreach (var positon in visibleMesh.convexHull.Vertices) - { - var transformed = positon.Transform(matrix); - yield return new Vector2(transformed.X, transformed.Y); - } - } - } - - var circle = SmallestEnclosingCircle.MakeCircle(GetVertices()); - - return circle; - } - } } \ No newline at end of file diff --git a/StaticData/Icons/radial-pinch.png b/StaticData/Icons/radial-pinch.png new file mode 100644 index 000000000..cbb7d3114 Binary files /dev/null and b/StaticData/Icons/radial-pinch.png differ diff --git a/StaticData/Translations/Master.txt b/StaticData/Translations/Master.txt index 6186fc39e..b24da83ee 100644 --- a/StaticData/Translations/Master.txt +++ b/StaticData/Translations/Master.txt @@ -3712,6 +3712,9 @@ Translated:Pin English:Pinch Translated:Pinch +English:Pinch Slices +Translated:Pinch Slices + English:Pipe Works Translated:Pipe Works @@ -4123,6 +4126,9 @@ Translated:Queue English:Radial Array Translated:Radial Array +English:Radial Pinch +Translated:Radial Pinch + English:Radius Translated:Radius