diff --git a/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/Braille/BrailleContainer.cs b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/Braille/BrailleContainer.cs new file mode 100644 index 000000000..c3b8686f1 --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/Braille/BrailleContainer.cs @@ -0,0 +1,67 @@ +/* +Copyright (c) 2017, 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.Threading.Tasks; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.DesignTools; +using MatterHackers.MatterControl.Plugins.BrailleBuilder; + +namespace MatterHackers.MatterControl.Library +{ + public class BrailleContainer : LibraryContainer + { + public BrailleContainer() + { + Name = "Braille".Localize(); + } + + public override void Load() + { + var libraryItems = new List() + { + new GeneratorItem( + () => "Braille".Localize(), + async () => await BrailleObject3D.Create()), + new GeneratorItem( + () => "Braille Card".Localize(), + async () => await BrailleCardObject3D.Create()), + }; + + string title = "Primitive Shapes".Localize(); + + foreach (var item in libraryItems) + { + item.Category = title; + Items.Add(item); + } + } + } +} diff --git a/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/ChairFootObject3D.cs b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/ChairFootObject3D.cs new file mode 100644 index 000000000..5b21113bb --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/ChairFootObject3D.cs @@ -0,0 +1,188 @@ +/* +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 MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.VectorMath; +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; + +namespace MatterHackers.MatterControl.DesignTools +{ + [UnlockLinkAttribute("mattercontrol-image-converter-add-on")] + public class ChairFootObject3D : Object3D + { + public enum OutputMode { Final_Part, Fit_Test }; + + //private static string permissionKey = "ag1zfm1oLWRmcy10ZXN0chgLEgtEaWdpdGFsSXRlbRiAgICA3pCBCgw"; + + public ChairFootObject3D() + { + //PermissionCheckRequest.CheckPermission(permissionKey); + } + + //[Icons(new string[] { "424.png", "align_left.png", "align_center_x.png", "align_right.png" })] + public OutputMode Output { get; set; } = OutputMode.Final_Part; + + [DisplayName("Angle")] + public double AngleDegrees { get; set; } = 3; + + [DisplayName("Height")] + public double HeightFromFloorToBottomOfLeg { get; set; } = 10; + + [DisplayName("Inner Size")] + public double InnerSize { get; set; } = 20; + + [DisplayName("Reach")] + public double InsideReach { get; set; } = 10; + + [DisplayName("Outer Size")] + public double OuterSize { get; set; } = 22; + + public double BaseBevel { get; set; } = 2; + + //public override bool Persistable { get => PermissionCheckRequest.UserAuthorized(permissionKey); } + + public override bool CanFlatten => Persistable; + + public override bool CanEdit => Persistable; + + public static async Task Create() + { + var chairFoot = new ChairFootObject3D(); + await chairFoot.Rebuild(); + return chairFoot; + } + + private object locker = new object(); + private bool inRebuild; + + public override PolygonMesh.Mesh Mesh + { + get + { + lock (locker) + { + // Check a known condition for have persisted meshes or need to rebuild + if (!inRebuild + && Children.Count > 0 + && !this.Descendants().Where((d) => d.Mesh != null).Any()) + { + Rebuild(); + } + return base.Mesh; + } + } + + set => base.Mesh = value; + } + + public async override Task Rebuild() + { + using (RebuildLock()) + { + inRebuild = true; + if (AngleDegrees > 45) + { + AngleDegrees = 45; + } + using (new CenterAndHeightMantainer(this)) + { + // This would be better expressed as the desired offset height (height from ground to bottom of chair leg). + double angleRadians = MathHelper.DegreesToRadians(AngleDegrees); + + var insideReach = InsideReach; + var heightFromFloorToBottomOfLeg = HeightFromFloorToBottomOfLeg; + if (Output == OutputMode.Fit_Test) + { + insideReach = 4; + angleRadians = 0; + heightFromFloorToBottomOfLeg = 4; + } + + double extraHeightForRotation = Math.Sinh(angleRadians) * OuterSize; // get the distance to clip off the extra bottom + double unclippedFootHeight = heightFromFloorToBottomOfLeg + extraHeightForRotation; + + var baseBevelClamped = Math.Max(0, Math.Min(OuterSize / 2, BaseBevel)); + RoundedRect footBase = new RoundedRect(-OuterSize / 2, -OuterSize / 2, OuterSize / 2, OuterSize / 2, baseBevelClamped) + { + ResolutionScale = 1000 + }; + IObject3D chairFoot = new Object3D() + { + Mesh = VertexSourceToMesh.Extrude(footBase, unclippedFootHeight) + }; + + IObject3D ring = new RingObject3D(InnerSize - 2, InnerSize - 6, insideReach, 60); + ring.Translate(0, 0, -insideReach / 2 - .02); + + VertexStorage finShape = new VertexStorage(); + finShape.MoveTo(0, 0); + finShape.LineTo(3, 0); + finShape.LineTo(3, ring.ZSize()); + finShape.LineTo(0, ring.ZSize() - 3); + IObject3D fins = new Object3D() + { + Mesh = VertexSourceToMesh.Extrude(finShape, 1) + }; + fins.Rotate(Vector3.Zero, Vector3.UnitX, -MathHelper.Tau / 4); + fins.Rotate(Vector3.Zero, Vector3.UnitZ, -MathHelper.Tau / 2); + fins = (new TranslateObject3D(fins, 1.48, 1, -ring.ZSize() - .02)).Plus(new TranslateObject3D(fins, 1.48, -1, -ring.ZSize() - .02)); + fins = new TranslateObject3D(fins, InnerSize / 2 - .1); + + ring = ring.Plus(new RotateObject3D(fins, 0, 0, MathHelper.DegreesToRadians(45))); + ring = ring.Plus(new RotateObject3D(fins, 0, 0, MathHelper.DegreesToRadians(45 + 90))); + ring = ring.Plus(new RotateObject3D(fins, 0, 0, MathHelper.DegreesToRadians(45 + 180))); + ring = ring.Plus(new RotateObject3D(fins, 0, 0, MathHelper.DegreesToRadians(45 - 90))); + + chairFoot = chairFoot.Plus(new AlignObject3D(ring, FaceAlign.Bottom, chairFoot, FaceAlign.Top, 0, 0, -.1)); + + chairFoot = new RotateObject3D(chairFoot, 0, angleRadians, 0); + if (unclippedFootHeight != heightFromFloorToBottomOfLeg) + { + IObject3D clipBox = new AlignObject3D(await CubeObject3D.Create(OuterSize * 2, OuterSize * 2, unclippedFootHeight), FaceAlign.Top, chairFoot, FaceAlign.Bottom, 0, 0, extraHeightForRotation); + chairFoot = chairFoot.Minus(clipBox); + chairFoot = new TranslateObject3D(chairFoot, 0, 0, clipBox.GetAxisAlignedBoundingBox().MaxXYZ.Z); + } + + this.Children.Modify(list => + { + list.Clear(); + list.Add(chairFoot); + }); + } + inRebuild = false; + } + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/ImageCoinObject3D.cs b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/ImageCoinObject3D.cs new file mode 100644 index 000000000..1e287a77b --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/ImageCoinObject3D.cs @@ -0,0 +1,294 @@ +/* +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 ClipperLib; +using MatterHackers.Agg; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.Transform; +using MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters2D; +using MatterHackers.DataConverters3D; +using MatterHackers.DataConverters3D.UndoCommands; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.VectorMath; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class ImageCoinObject3D : Object3D, IVisualLeafNode + { + private readonly double innerDiameter = 35; + private readonly double outerDiameter = 40; + private object locker = new object(); + + public ImageCoinObject3D() + { + } + + [JsonIgnore] + public ImageObject3D ImageObject { get { return this.Children.OfType().FirstOrDefault(); } set { } } + + [Description("Create a hook so that the coin can be hung from a chain.")] + public bool CreateHook { get; set; } = true; + + [Description("Subtract the image from a disk so that the negative space will be printed.")] + public bool NegativeSpace { get; set; } = false; + + [Description("Change the scale of the image within the coin.")] + public double ScalePercent { get; set; } = 90; + + [Description("Normally the image is expanded to the edge. This will try to center the weight of the image visually.")] + public bool AlternateCentering { get; set; } = false; + + [Description("Change the width of the image lines.")] + public double Inflate { get; set; } + + public static async Task Create() + { + var imageCoin = new ImageCoinObject3D(); + await imageCoin.Rebuild(); + return imageCoin; + } + + public override async void OnInvalidate(InvalidateArgs invalidateType) + { + if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children) + || invalidateType.InvalidateType.HasFlag(InvalidateType.Matrix) + || invalidateType.InvalidateType.HasFlag(InvalidateType.Mesh)) + && invalidateType.Source != this + && !RebuildLocked) + { + await Rebuild(); + } + else if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties) + && invalidateType.Source == this) + { + await Rebuild(); + } + else + { + base.OnInvalidate(invalidateType); + } + } + + public override async Task Rebuild() + { + this.DebugDepth("Rebuild"); + + using (RebuildLock()) + { + var currentAssetPath = ImageObject == null ? AggContext.StaticData.ToAssetPath(Path.Combine("Images", "mh-logo.png")) : ImageObject.AssetPath; + + this.Children.Modify((list) => + { + list.Clear(); + }); + + var imageObject = new ImageObject3D() + { + AssetPath = currentAssetPath, + }; + + await imageObject.Rebuild(); + + this.Children.Add(imageObject); + + IObject3D logoBase = new CylinderObject3D(outerDiameter, 3, 60); + IObject3D logoRing = new AlignObject3D(new RingObject3D(outerDiameter, innerDiameter, 2, 60), FaceAlign.Bottom, logoBase, FaceAlign.Top); + + IObject3D coinBlank = logoBase.Plus(logoRing); + if (CreateHook) + { + var cube = await CubeObject3D.Create(4, 2, 4); + IObject3D connect = logoBase.Plus(new AlignObject3D(cube, FaceAlign.Front | FaceAlign.Bottom, logoBase, FaceAlign.Back | FaceAlign.Bottom, 0, -.5)); + IObject3D hook = logoBase.Plus(new AlignObject3D(new RingObject3D(10, 7, 5, 30), FaceAlign.Front | FaceAlign.Bottom, connect, FaceAlign.Back | FaceAlign.Bottom, 0, -.5)); + + coinBlank = coinBlank.Plus(connect); + coinBlank = coinBlank.Plus(hook); + } + + var imageToPath = new ImageToPathObject3D(); + + imageToPath.Children.Add(imageObject); + + await imageToPath.Rebuild(); + + var inputShape = imageToPath.VertexSource; + + if (Inflate != 0) + { + var bounds = inputShape.GetBounds(); + var scale = Math.Max(bounds.Width, bounds.Height) / (17 * 4); + inputShape = inputShape.Offset(Inflate * scale); + } + + if (AlternateCentering) + { + inputShape = new VertexSourceApplyTransform(inputShape, GetCenteringTransformVisualCenter(inputShape, innerDiameter / 2)); + } + else + { + inputShape = new VertexSourceApplyTransform(inputShape, GetCenteringTransformExpandedToRadius(inputShape, innerDiameter / 2)); + } + + if (ScalePercent != 100 + && ScalePercent != 0) + { + inputShape = new VertexSourceApplyTransform(inputShape, Affine.NewScaling(ScalePercent / 100.0)); + } + + if (NegativeSpace) + { + var disk = new Ellipse(0, 0, innerDiameter / 2 + .2, innerDiameter / 2 + .2) + { + ResolutionScale = 1000 + }; + inputShape = disk.Minus(inputShape); + } + + imageToPath.VertexSource = inputShape; + + var pathExtrusion = new LinearExtrudeObject3D(); + pathExtrusion.Children.Add(imageToPath); + await pathExtrusion.Rebuild(); + + IObject3D extrusionObject = imageObject; + + var loadingScale = 32 / extrusionObject.XSize(); + extrusionObject = new ScaleObject3D(extrusionObject, loadingScale, loadingScale, 1 / extrusionObject.ZSize()); + extrusionObject = PlaceOnBase(logoBase, extrusionObject); + + this.Children.Add(coinBlank); + this.Children.Add(extrusionObject); + } + + Invalidate(InvalidateType.Mesh); + } + + private static Affine GetCenteringTransformExpandedToRadius(IVertexSource vertexSource, double radius) + { + var circle = SmallestEnclosingCircle.MakeCircle(vertexSource.Vertices().Select((v) => new Vector2(v.position.X, v.position.Y))); + + // move the circle center to the origin + var centering = Affine.NewTranslation(-circle.Center); + // scale to the fit size in x y + double scale = radius / circle.Radius; + var scalling = Affine.NewScaling(scale); + + return centering * scalling; + } + + private static Affine GetCenteringTransformVisualCenter(IVertexSource vertexSource, double goalRadius) + { + var outsidePolygons = new List>(); + // remove all holes from the polygons so we only center the major outlines + var polygons = vertexSource.CreatePolygons(); + foreach(var polygon in polygons) + { + if(polygon.GetWindingDirection() == 1) + { + outsidePolygons.Add(polygon); + } + } + + IVertexSource outsideSource = outsidePolygons.CreateVertexStorage(); + + Vector2 center = outsideSource.GetWeightedCenter(); + + outsideSource = new VertexSourceApplyTransform(outsideSource, Affine.NewTranslation(-center)); + + double radius = MaxXyDistFromCenter(outsideSource); + + double scale = goalRadius / radius; + var scalling = Affine.NewScaling(scale); + + var centering = Affine.NewTranslation(-center); + + return centering * scalling; + } + + private static double MaxXyDistFromCenter(IObject3D imageMesh) + { + double maxDistSqrd = 0.000001; + var center = imageMesh.GetAxisAlignedBoundingBox().Center; + var itemWithMesh = imageMesh.VisibleMeshes().First(); + var matrix = itemWithMesh.WorldMatrix(imageMesh); + foreach (var vertex in itemWithMesh.Mesh.Vertices) + { + throw new NotImplementedException(); + //var position = vertex.Position; + //var distSqrd = (new Vector2(position.X, position.Y) - new Vector2(center.X, center.Y)).LengthSquared; + //if (distSqrd > maxDistSqrd) + //{ + // maxDistSqrd = distSqrd; + //} + } + + return Math.Sqrt(maxDistSqrd); + } + + private static double MaxXyDistFromCenter(IVertexSource vertexSource) + { + double maxDistSqrd = 0.000001; + var center = vertexSource.GetBounds().Center; + foreach (var vertex in vertexSource.Vertices()) + { + var position = vertex.position; + var distSqrd = (new Vector2(position.X, position.Y) - new Vector2(center.X, center.Y)).LengthSquared; + if (distSqrd > maxDistSqrd) + { + maxDistSqrd = distSqrd; + } + } + + return Math.Sqrt(maxDistSqrd); + } + + private static IObject3D PlaceOnBase(IObject3D logoBase, IObject3D imageObject) + { + if (imageObject != null) + { + // put it at the right height + imageObject = new AlignObject3D(imageObject, FaceAlign.Bottom, logoBase, FaceAlign.Top); + // move it to the base center + imageObject = new TranslateObject3D(imageObject, -new Vector3(logoBase.GetCenter().X, logoBase.GetCenter().Y, 0)); + } + return imageObject; + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/ElbowObject3D.cs b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/ElbowObject3D.cs new file mode 100644 index 000000000..0694315ef --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/ElbowObject3D.cs @@ -0,0 +1,141 @@ +/* +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.ComponentModel; +using System.Threading.Tasks; +using MatterHackers.Agg; +using MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.VectorMath; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class ElbowObject3D : PipeWorksBaseObject3D + { + public ElbowObject3D() + { + } + + public static async Task Create() + { + var item = new ElbowObject3D(); + await item.Rebuild(); + + return item; + } + + [DisplayName("Inner Radius")] + public double InnerDiameter { get; set; } + + [DisplayName("Outer Radius")] + public double OuterDiameter { get; set; } + + public double Angle { get; set; } = 90; + + public double BottomReach { get; set; } + + public double FrontReach { get; set; } + + public override async Task Rebuild() + { + using (RebuildLock()) + { + using (new CenterAndHeightMantainer(this)) + { + // validate the some of the values and store in user data if changed + InnerDiameter = ValidateValue(InnerDiameter, "PipeWorksInnerDiameter", 15); + OuterDiameter = ValidateValue(OuterDiameter, "PipeWorksOuterDiameter", 20); + BottomReach = ValidateValue(BottomReach, "PipeWorksBottomReach", 30); + FrontReach = ValidateValue(FrontReach, "PipeWorksFrontReach", 25); + + IObject3D bottomReach = new RotateObject3D(CreateReach(BottomReach, InnerDiameter), -MathHelper.Tau / 4); + IObject3D frontReach = null; + IObject3D elbowConnect = null; + if (Angle < 90) + { + frontReach = bottomReach.Clone(); + + var translate = new Vector3(-OuterDiameter / 2, 0, 0); + bottomReach.Translate(translate); + + frontReach = new RotateObject3D(frontReach, 0, 0, -MathHelper.DegreesToRadians(Angle)); + translate = Vector3Ex.Transform(-translate, Matrix4X4.CreateRotationZ(MathHelper.DegreesToRadians(Angle))); + frontReach.Translate(translate); + + var torus = new TorusObject3D(); + + using (torus.RebuildLock()) + { + torus.Advanced = true; + torus.InnerDiameter = 0; + OuterDiameter = OuterDiameter * 2; + torus.RingSides = Sides; + torus.Sides = Sides; + torus.StartingAngle = Angle; + torus.EndingAngle = 180; + } + + torus.Invalidate(new InvalidateArgs(torus, InvalidateType.Properties)); + elbowConnect = torus; + } + else if (Angle < 270) + { + bottomReach.Translate(0, -OuterDiameter / 2, 0); + IObject3D reachConnect = await CylinderObject3D.Create(OuterDiameter, OuterDiameter, Sides, Alignment.Y); + reachConnect = new AlignObject3D(reachConnect, FaceAlign.Front, bottomReach, FaceAlign.Back); + reachConnect = new SetCenterObject3D(reachConnect, bottomReach.GetCenter(), true, false, true); + bottomReach = bottomReach.Plus(reachConnect); + + frontReach = bottomReach.Clone(); + frontReach = new RotateObject3D(frontReach, 0, 0, -MathHelper.DegreesToRadians(Angle)); + + elbowConnect = new SphereObject3D(OuterDiameter, Sides); + } + + // output multiple meshes for pipe connector + this.Children.Modify(list => + { + list.Clear(); + list.Add(elbowConnect); + list.Add(bottomReach); + list.Add(frontReach); + }); + + this.Color = Color.Transparent; + this.Mesh = null; + } + } + + Invalidate(InvalidateType.Children); + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/PipeWorksBaseObject3D.cs b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/PipeWorksBaseObject3D.cs new file mode 100644 index 000000000..3f59aab73 --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/PipeWorksBaseObject3D.cs @@ -0,0 +1,101 @@ +/* +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.Threading.Tasks; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.VectorMath; + +namespace MatterHackers.MatterControl.DesignTools +{ + public abstract class PipeWorksBaseObject3D : Object3D, IVisualLeafNode + { + protected int Sides => 50; + + protected double ValidateValue(double currentValue, string keyName, double defaultValue) + { + double databaseValue = UserSettings.Instance.Fields.GetDouble(keyName, defaultValue); + if (currentValue == 0) + { + currentValue = databaseValue; + } + if (currentValue != databaseValue) + { + UserSettings.Instance.Fields.SetDouble(keyName, currentValue); + } + + return currentValue; + } + + public override void OnInvalidate(InvalidateArgs invalidateType) + { + if (invalidateType.InvalidateType.HasFlag(InvalidateType.Properties) + && invalidateType.Source == this) + { + Rebuild(); + } + else + { + base.OnInvalidate(invalidateType); + } + } + + protected IObject3D CreateReach(double reach, double innerDiameter) + { + var finWidth = 4.0; + var finLength = innerDiameter; + + var pattern = new VertexStorage(); + pattern.MoveTo(0, 0); + pattern.LineTo(finLength/2, 0); + pattern.LineTo(finLength/2, reach - finLength / 8); + pattern.LineTo(finLength/2 - finLength / 8, reach); + pattern.LineTo(-finLength/2 + finLength / 8, reach); + pattern.LineTo(-finLength/2, reach - finLength / 8); + pattern.LineTo(-finLength/2, 0); + + var fin1 = new Object3D() + { + Mesh = VertexSourceToMesh.Extrude(pattern, finWidth) + }; + fin1 = new TranslateObject3D(fin1, 0, 0, -finWidth / 2); + //fin1.ChamferEdge(Face.Top | Face.Back, finLength / 8); + //fin1.ChamferEdge(Face.Top | Face.Front, finLength / 8); + fin1 = new RotateObject3D(fin1, -MathHelper.Tau / 4); + var fin2 = new SetCenterObject3D(new RotateObject3D(fin1, 0, 0, MathHelper.Tau / 4), fin1.GetCenter()); + + return new Object3D().SetChildren(new List() { fin1, fin2 }); + } + + override abstract public Task Rebuild(); + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/PipeWorksContainer.cs b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/PipeWorksContainer.cs new file mode 100644 index 000000000..7a0ee3d8e --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/PipeWorksContainer.cs @@ -0,0 +1,67 @@ +/* +Copyright (c) 2017, 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.Threading.Tasks; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.DesignTools; +using MatterHackers.MatterControl.Plugins.BrailleBuilder; + +namespace MatterHackers.MatterControl.Library +{ + public class PipeWorksContainer : LibraryContainer + { + public PipeWorksContainer() + { + Name = "Pipe Works".Localize(); + } + + public override void Load() + { + var libraryItems = new List() + { + new GeneratorItem( + () => "Tee".Localize(), + async () => await TeeObject3D.Create()), + new GeneratorItem( + () => "Elbow".Localize(), + async () => await ElbowObject3D.Create()), + }; + + string title = "Primitive Shapes".Localize(); + + foreach (var item in libraryItems) + { + item.Category = title; + Items.Add(item); + } + } + } +} diff --git a/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/TeeObject3D.cs b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/TeeObject3D.cs new file mode 100644 index 000000000..162b23877 --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/PipeWorks/TeeObject3D.cs @@ -0,0 +1,113 @@ +/* +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.ComponentModel; +using System.Threading.Tasks; +using MatterHackers.Agg; +using MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.VectorMath; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class TeeObject3D : PipeWorksBaseObject3D + { + public TeeObject3D() + { + } + + public static async Task Create() + { + var item = new TeeObject3D(); + await item.Rebuild(); + + return item; + } + + [DisplayName("Inner Radius")] + public double InnerDiameter { get; set; } + + [DisplayName("Outer Radius")] + public double OuterDiameter { get; set; } + + public double BottomReach { get; set; } + + public double FrontReach { get; set; } + + public double TopReach { get; set; } + + public async override Task Rebuild() + { + using (RebuildLock()) + { + using (new CenterAndHeightMantainer(this)) + { + // validate the some of the values and store in user data if changed + InnerDiameter = ValidateValue(InnerDiameter, "PipeWorksInnerDiameter", 15); + OuterDiameter = ValidateValue(OuterDiameter, "PipeWorksOuterDiameter", 20); + BottomReach = ValidateValue(BottomReach, "PipeWorksBottomReach", 30); + FrontReach = ValidateValue(FrontReach, "PipeWorksFrontReach", 25); + TopReach = ValidateValue(TopReach, "PipeWorksTopReach", 30); + + IObject3D topBottomConnect = await CylinderObject3D.Create(OuterDiameter, OuterDiameter, Sides, Alignment.Y); + IObject3D frontConnect = await CylinderObject3D.Create(OuterDiameter, OuterDiameter, Sides, Alignment.X); + frontConnect = new AlignObject3D(frontConnect, FaceAlign.Right, topBottomConnect, FaceAlign.Right); + + IObject3D bottomReach = new RotateObject3D(CreateReach(BottomReach, InnerDiameter), -MathHelper.Tau / 4); + bottomReach = new AlignObject3D(bottomReach, FaceAlign.Back, topBottomConnect, FaceAlign.Front, 0, .02); + + IObject3D topReach = new RotateObject3D(CreateReach(TopReach, InnerDiameter), MathHelper.Tau / 4); + topReach = new AlignObject3D(topReach, FaceAlign.Front, topBottomConnect, FaceAlign.Back, 0, -.02); + + IObject3D frontReach = new RotateObject3D(CreateReach(FrontReach, InnerDiameter), 0, -MathHelper.Tau / 4); + frontReach = new AlignObject3D(frontReach, FaceAlign.Left, topBottomConnect, FaceAlign.Right, -.02); + + // output multiple meshes for pipe connector + this.Children.Modify(list => + { + list.Clear(); + list.Add(topBottomConnect); + list.Add(frontConnect); + list.Add(bottomReach); + list.Add(topReach); + list.Add(frontReach); + }); + + this.Color = Color.Transparent; + this.Mesh = null; + } + } + + Invalidate(InvalidateType.Children); + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/RibbonWithNameObject3D.cs b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/RibbonWithNameObject3D.cs new file mode 100644 index 000000000..52163ea1c --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/DesignApps/Parts/RibbonWithNameObject3D.cs @@ -0,0 +1,91 @@ +/* +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.ComponentModel; +using System.Threading; +using System.Threading.Tasks; +using MatterHackers.Agg.Font; +using MatterHackers.Agg.UI; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.VectorMath; + +namespace MatterHackers.MatterControl.DesignTools +{ + public class RibbonWithNameObject3D : Object3D + { + public RibbonWithNameObject3D() + { + Rebuild(); + } + + [DisplayName("Name")] + public string NameToWrite { get; set; } = "MatterHackers"; + + public NamedTypeFace Font { get; set; } = new NamedTypeFace(); + + public override Task Rebuild() + { + IObject3D cancerRibbonStl = Object3D.Load("Cancer_Ribbon.stl", CancellationToken.None); + + cancerRibbonStl = new RotateObject3D(cancerRibbonStl, MathHelper.DegreesToRadians(90)); + + var letterPrinter = new TypeFacePrinter(NameToWrite.ToUpper(), new StyledTypeFace(ApplicationController.GetTypeFace(Font), 12)); + + IObject3D nameMesh = new Object3D() + { + Mesh = VertexSourceToMesh.Extrude(letterPrinter, 5) + }; + + AxisAlignedBoundingBox textBounds = nameMesh.GetAxisAlignedBoundingBox(); + var textArea = new Vector2(25, 6); + + double scale = Math.Min(textArea.X / textBounds.XSize, textArea.Y / textBounds.YSize); + nameMesh = new ScaleObject3D(nameMesh, scale, scale, 2 / textBounds.ZSize); + nameMesh = new AlignObject3D(nameMesh, FaceAlign.Bottom | FaceAlign.Front, cancerRibbonStl, FaceAlign.Top | FaceAlign.Front, 0, 0, -1); + nameMesh = new SetCenterObject3D(nameMesh, cancerRibbonStl.GetCenter(), true, false, false); + + nameMesh = new RotateObject3D(nameMesh, 0, 0, MathHelper.DegreesToRadians(50)); + nameMesh = new TranslateObject3D(nameMesh, -37, -14, -1); + + // output two meshes for card holder and text + this.Children.Modify(list => + { + list.Clear(); + list.Add(cancerRibbonStl); + list.Add(nameMesh); + }); + + this.Mesh = null; + this.Invalidate(InvalidateType.Children); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/EditorToolsPlugin.cs b/MatterControlLib/DesignTools/EditorTools/EditorToolsPlugin.cs new file mode 100644 index 000000000..1cd040f2a --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/EditorToolsPlugin.cs @@ -0,0 +1,67 @@ +/* +Copyright (c) 2019, 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.Linq; +using MatterHackers.MatterControl; +using MatterHackers.MatterControl.Extensibility; +using MatterHackers.MatterControl.Library; + +namespace MatterHackers.Plugins.EditorTools +{ + public class EditorToolsPlugin : IApplicationPlugin + { + public void Initialize() + { + var applicationController = ApplicationController.Instance; + + var primitives = new PrimitivesContainer(); + primitives.Load(); + + foreach (var item in primitives.Items.OfType()) + { + applicationController.Library.RegisterCreator(item); + } + + applicationController.Extensions.Register(new ScaleCornersPlugin()); + applicationController.Extensions.Register(new RotateCornerPlugins()); + + applicationController.Extensions.Register(new OpenSCADBuilder()); + //applicationController.Extensions.Register(new PrimitivesEditor()); + } + + public PluginInfo MetaData { get; } = new PluginInfo() + { + Name = "Editor Tools", + UUID = "1A3C7BE4-EEC2-43BA-A7B0-035C3DB51875", + About = "Editor Tools", + Developer = "MatterHackers, Inc.", + Url = "https://www.matterhackers.com" + }; + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs b/MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs new file mode 100644 index 000000000..df6572654 --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs @@ -0,0 +1,108 @@ +/* +Copyright (c) 2018, 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.Linq; +using System.Threading.Tasks; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.UI; +using MatterHackers.DataConverters3D; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.DesignTools; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.MatterControl.Extensibility; +using MatterHackers.MatterControl.PartPreviewWindow.View3D; +using MatterHackers.VectorMath; + +namespace MatterHackers.MatterControl.Plugins.Lithophane +{ + public class LithophanePlugin : IApplicationPlugin + { + public static void WrapWith(IObject3D originalItem, IObject3D wrapper, InteractiveScene scene) + { + using (originalItem.RebuildLock()) + { + originalItem.Parent.Children.Modify(list => + { + list.Remove(originalItem); + + wrapper.Matrix = originalItem.Matrix; + + originalItem.Matrix = Matrix4X4.Identity; + wrapper.Children.Add(originalItem); + + list.Add(wrapper); + }); + + if (scene != null) + { + var topParent = wrapper.Parents().LastOrDefault((i) => i.Parent != null); + UiThread.RunOnIdle(() => + { + scene.SelectedItem = topParent != null ? topParent : wrapper; + }); + } + } + } + + public void Initialize() + { + ApplicationController.Instance.Graph.RegisterOperation( + new Library.NodeOperation() + { + OperationID = "Lithophane".Localize(), + Title = "Lithophane".Localize(), + MappedTypes = new List { typeof(ImageObject3D) }, + ResultType = typeof(LithophaneObject3D), + Operation = (sceneItem, scene) => + { + if (sceneItem is IObject3D imageObject) + { + WrapWith(sceneItem, new LithophaneObject3D(), scene); + } + + return Task.CompletedTask; + }, + IsEnabled = (sceneItem) => true, + IsVisible = (sceneItem) => true, + IconCollector = (theme) => AggContext.StaticData.LoadIcon("lithophane.png", 16, 16, theme.InvertIcons) + }); + } + + public PluginInfo MetaData { get; } = new PluginInfo() + { + Name = "Lithophane Creator", + UUID = "B07B4EB0-CAFD-4721-A04A-FD9C3E001D2B", + About = "A Lithophane Creator.", + Developer = "MatterHackers, Inc.", + Url = "https://www.matterhackers.com" + }; + } +} diff --git a/MatterControlLib/DesignTools/EditorTools/RotateControls/RotateCornerControl.cs b/MatterControlLib/DesignTools/EditorTools/RotateControls/RotateCornerControl.cs new file mode 100644 index 000000000..0600b6aec --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/RotateControls/RotateCornerControl.cs @@ -0,0 +1,895 @@ +/* +Copyright (c) 2014, Lars Brubaker +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; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.Transform; +using MatterHackers.Agg.UI; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.MeshVisualizer; +using MatterHackers.PolygonMesh; +using MatterHackers.RayTracer; +using MatterHackers.RenderOpenGl; +using MatterHackers.VectorMath; + +namespace MatterHackers.Plugins.EditorTools +{ + public class RotateCornerControl : InteractionVolume + { + public IObject3D SelectedItemOnMouseDown; + private static VertexStorage arrows = new VertexStorage("M267.96599,177.26875L276.43374,168.80101C276.43374,170.2123 276.43374,171.62359 276.43374,173.03488C280.02731,173.01874 282.82991,174.13254 286.53647,171.29154C290.08503,168.16609 288.97661,164.24968 289.13534,160.33327L284.90147,160.33327L293.36921,151.86553L301.83695,160.33327L297.60308,160.33327C297.60308,167.38972 298.67653,171.4841 293.23666,177.24919C286.80975,182.82626 283.014,181.02643 276.43374,181.50262L276.43374,185.73649L267.96599,177.26875L267.96599,177.26875z"); + private static ImageBuffer rotationImageWhite; + + private readonly double arrowsOffset = 15; + private readonly double ringWidth = 20; + + private InlineEditControl angleTextControl; + + private double lastSnappedRotation = 0; + private Mouse3DInfo mouseDownInfo = null; + private Mouse3DInfo mouseMoveInfo = null; + private int numSnapPoints = 8; + private Mesh rotationHandle; + private double rotationTransformScale = 1; + private Vector3 selectCubeSize = new Vector3(30, 30, .1) * GuiWidget.DeviceScale; + + public RotateCornerControl(IInteractionVolumeContext context, int axisIndex) + : base(context) + { + angleTextControl = new InlineEditControl() + { + ForceHide = ForceHideAngle, + GetDisplayString = (value) => "{0:0.0#}°".FormatWith(value) + }; + + angleTextControl.VisibleChanged += (s, e) => + { + mouseDownInfo = null; + Invalidate(); + }; + + InteractionContext.GuiSurface.AddChild(angleTextControl); + angleTextControl.Visible = false; + + angleTextControl.EditComplete += (s, e) => + { + var selectedItem = RootSelection; + + if (selectedItem != null + && mouseDownInfo != null) + { + if (mouseMoveInfo != null) + { + SnappedRotationAngle = MathHelper.DegreesToRadians(angleTextControl.Value); + + mouseMoveInfo.AngleOfHit = mouseDownInfo.AngleOfHit + SnappedRotationAngle; + + rotatingCW = DeltaAngle(0, SnappedRotationAngle) < 0; + + // undo the last rotation + RotateAroundAxis(selectedItem, -lastSnappedRotation); + + // rotate it + RotateAroundAxis(selectedItem, SnappedRotationAngle); + + Invalidate(); + + lastSnappedRotation = SnappedRotationAngle; + } + + InteractionContext.Scene.AddTransformSnapshot(mouseDownInfo.SelectedObjectTransform); + } + }; + + this.RotationAxis = axisIndex; + + DrawOnTop = true; + + rotationHandle = PlatonicSolids.CreateCube(selectCubeSize); + + RectangleDouble bounds = arrows.GetBounds(); + if (rotationImageWhite == null) + { + rotationImageWhite = new ImageBuffer(64, 64, 32, new BlenderBGRA()); + } + VertexSourceApplyTransform arrows2 = new VertexSourceApplyTransform(arrows, Affine.NewTranslation(-bounds.Center) + * Affine.NewScaling(rotationImageWhite.Width / bounds.Width, rotationImageWhite.Height / bounds.Height) + * Affine.NewTranslation(rotationImageWhite.Width / 2, rotationImageWhite.Height / 2)); + + Graphics2D imageGraphics = rotationImageWhite.NewGraphics2D(); + imageGraphics.Clear(new Color(Color.White, 0)); + imageGraphics.Render(new FlattenCurves(arrows2), Color.White); + + ImageBuffer clearImage = new ImageBuffer(2, 2, 32, new BlenderBGRA()); + + for(int i=0; i< rotationHandle.Faces.Count; i++) + { + if (i == 0 || i == 1) + { + rotationHandle.PlaceTextureOnFace(i, rotationImageWhite); + } + else + { + rotationHandle.PlaceTextureOnFace(i, clearImage); + } + } + + CollisionVolume = rotationHandle.CreateTraceData(); + + InteractionContext.GuiSurface.AfterDraw += InteractionLayer_AfterDraw; + } + + public int RotationAxis { get; private set; } + + public Vector3 RotationPlanNormal + { + get + { + var rotationPlanNormal = Vector3.Zero; + rotationPlanNormal[RotationAxis] = 1; + return rotationPlanNormal; + } + } + + private bool rotatingCW { get; set; } = true; + + private double SnappedRotationAngle { get; set; } + + public override void DrawGlContent(DrawGlContentEventArgs e) + { + IObject3D selectedItem = RootSelection; + // We only draw rotation controls if something is selected + if (selectedItem != null) + { + // make sure the image is initialized + RenderOpenGl.ImageGlPlugin.GetImageGlPlugin(rotationImageWhite, true); + + // We only draw the rotation arrows when the user has not selected any interaction volumes (they are not actively scaling or rotating anything). + if (InteractionContext.SelectedInteractionVolume == null) + { + var color = MouseOver ? Color.Red : Color.Black; + GLHelper.Render(rotationHandle, new Color(color, 254), TotalTransform, RenderTypes.Shaded); + } + + // If the user is over the control or has clicked on it + if (mouseMoveInfo != null || mouseDownInfo != null + || MouseOver) + { + DrawRotationCompass(selectedItem, e); + } + } + + base.DrawGlContent(e); + } + + public Vector3 GetCornerPosition(IObject3D objectBeingRotated) + { + int cornerIndex; + return GetCornerPosition(objectBeingRotated, out cornerIndex); + } + + public Vector3 GetCornerPosition(IObject3D objectBeingRotated, out int cornerIndexOut) + { + cornerIndexOut = 0; + AxisAlignedBoundingBox currentSelectedBounds = objectBeingRotated.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector3 bestZCornerPosition = Vector3.Zero; + int xCornerIndex = 0; + Vector3 bestXCornerPosition = Vector3.Zero; + int yCornerIndex = 1; + Vector3 bestYCornerPosition = Vector3.Zero; + int zCornerIndex = 2; + + double bestCornerZ = double.PositiveInfinity; + // get the closest z on the bottom in view space + for (int cornerIndex = 0; cornerIndex < 4; cornerIndex++) + { + Vector3 cornerPosition = currentSelectedBounds.GetBottomCorner(cornerIndex); + Vector3 cornerScreenSpace = InteractionContext.World.GetScreenSpace(cornerPosition); + if (cornerScreenSpace.Z < bestCornerZ) + { + zCornerIndex = cornerIndex; + bestCornerZ = cornerScreenSpace.Z; + bestZCornerPosition = cornerPosition; + + bestZCornerPosition = SetBottomControlHeight(currentSelectedBounds, bestZCornerPosition); + + Vector3 testCornerPosition = currentSelectedBounds.GetBottomCorner((cornerIndex + 1) % 4); + if (testCornerPosition.Y == cornerPosition.Y) + { + xCornerIndex = (cornerIndex + 1) % 4; + yCornerIndex = (cornerIndex + 3) % 4; + } + else + { + xCornerIndex = (cornerIndex + 3) % 4; + yCornerIndex = (cornerIndex + 1) % 4; + } + + bestXCornerPosition = currentSelectedBounds.GetBottomCorner(xCornerIndex); + bestXCornerPosition.Z = currentSelectedBounds.MaxXYZ.Z; + bestYCornerPosition = currentSelectedBounds.GetBottomCorner(yCornerIndex); + bestYCornerPosition.Z = currentSelectedBounds.MaxXYZ.Z; + } + } + + switch (RotationAxis) + { + case 0: + cornerIndexOut = xCornerIndex; + return bestXCornerPosition; + + case 1: + cornerIndexOut = yCornerIndex; + return bestYCornerPosition; + + case 2: + cornerIndexOut = zCornerIndex; + return bestZCornerPosition; + } + + return bestZCornerPosition; + } + + public override void OnMouseDown(MouseEvent3DArgs mouseEvent3D) + { + InteractionContext.Scene.DrawSelection = false; + + IObject3D selectedItem = RootSelection; + + if (mouseEvent3D.info != null && selectedItem != null) + { + SelectedItemOnMouseDown = selectedItem; + + angleTextControl.Visible = true; + + var selectedObject = SelectedItemOnMouseDown; + AxisAlignedBoundingBox currentSelectedBounds = selectedObject.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + var selectedObjectRotationCenter = currentSelectedBounds.Center; + Vector3 cornerForAxis = GetCornerPosition(selectedObject); + // move the rotation center to the correct side of the bounding box + selectedObjectRotationCenter[RotationAxis] = cornerForAxis[RotationAxis]; + + mouseDownInfo = new Mouse3DInfo( + mouseEvent3D.info.HitPosition, + selectedObject.Matrix, + selectedObjectRotationCenter, + GetControlCenter(selectedObject), + RotationAxis + ); + + // Get move data updated + lastSnappedRotation = 0; + SnappedRotationAngle = 0; + rotatingCW = true; + mouseMoveInfo = mouseDownInfo; + InteractionContext.Scene.ShowSelectionShadow = false; + + mouseMoveInfo = new Mouse3DInfo( + mouseEvent3D.info.HitPosition, + selectedObject.Matrix, + selectedObjectRotationCenter, + GetControlCenter(selectedObject), + RotationAxis + ); + } + + base.OnMouseDown(mouseEvent3D); + } + + public override void OnMouseMove(MouseEvent3DArgs mouseEvent3D) + { + IObject3D selectedItem = RootSelection; + if (selectedItem != null) + { + var controlCenter = GetControlCenter(selectedItem); + if(mouseDownInfo != null) + { + controlCenter = mouseDownInfo.ControlCenter; + } + var hitPlane = new PlaneShape(RotationPlanNormal, Vector3Ex.Dot(RotationPlanNormal, controlCenter), null); + IntersectInfo hitOnRotationPlane = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay); + if (hitOnRotationPlane != null) + { + AxisAlignedBoundingBox currentSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + var selectedObjectRotationCenter = currentSelectedBounds.Center; + if(mouseDownInfo != null) + { + selectedObjectRotationCenter = mouseDownInfo.SelectedObjectRotationCenter; + } + + Vector3 cornerForAxis = GetCornerPosition(selectedItem); + // move the rotation center to the correct side of the bounding box + selectedObjectRotationCenter[RotationAxis] = cornerForAxis[RotationAxis]; + + mouseMoveInfo = new Mouse3DInfo( + hitOnRotationPlane.HitPosition, + selectedItem.Matrix, + selectedObjectRotationCenter, + controlCenter, + RotationAxis + ); + + if (MouseDownOnControl + && mouseDownInfo != null) + { + double rawDeltaRotationAngle = mouseMoveInfo.AngleOfHit - mouseDownInfo.AngleOfHit; + double snapRadians = MathHelper.DegreesToRadians(1); + + // snap this position to the grid + if (rawDeltaRotationAngle > 0) + { + SnappedRotationAngle = ((int)((rawDeltaRotationAngle / snapRadians) + .5)) * snapRadians; + } + else + { + SnappedRotationAngle = ((int)((rawDeltaRotationAngle / snapRadians) - .5)) * snapRadians; + } + + int snappingIndex = GetSnapIndex(selectedItem, numSnapPoints); + if (snappingIndex != -1) + { + SnappedRotationAngle = snappingIndex * MathHelper.Tau / numSnapPoints; + } + else if (InteractionContext.GuiSurface.ModifierKeys == Keys.Shift) + { + snapRadians = MathHelper.DegreesToRadians(45); + + if (rawDeltaRotationAngle > 0) + { + SnappedRotationAngle = ((int)((rawDeltaRotationAngle / snapRadians) + .5)) * snapRadians; + } + else + { + SnappedRotationAngle = ((int)((rawDeltaRotationAngle / snapRadians) - .5)) * snapRadians; + } + } + + if (SnappedRotationAngle < 0) + { + SnappedRotationAngle += MathHelper.Tau; + } + + // check if this move crosses zero degrees + if (lastSnappedRotation == 0 && SnappedRotationAngle != 0) + { + rotatingCW = DeltaAngle(0, SnappedRotationAngle) < 0; + } + else if ((DeltaAngle(0, SnappedRotationAngle) < 0 + && DeltaAngle(0, lastSnappedRotation) > 0 + && Math.Abs(DeltaAngle(0, lastSnappedRotation)) < 1) + || (DeltaAngle(0, SnappedRotationAngle) > 0 + && DeltaAngle(0, lastSnappedRotation) < 0 + && Math.Abs(DeltaAngle(0, lastSnappedRotation)) < 1)) + { + // let's figure out which way we are going + rotatingCW = (DeltaAngle(0, SnappedRotationAngle) < 0 && DeltaAngle(0, lastSnappedRotation) > 0); + } + + // undo the last rotation + RotateAroundAxis(selectedItem, -lastSnappedRotation); + + // rotate it + RotateAroundAxis(selectedItem, SnappedRotationAngle); + + lastSnappedRotation = SnappedRotationAngle; + + Invalidate(); + } + } + } + + base.OnMouseMove(mouseEvent3D); + } + + public override void OnMouseUp(MouseEvent3DArgs mouseEvent3D) + { + InteractionContext.Scene.DrawSelection = true; + // if we rotated it + if (mouseDownInfo != null) + { + // put in the start transform so we can go back to it if we have to + InteractionContext.Scene.AddTransformSnapshot(mouseDownInfo.SelectedObjectTransform); + } + + if (mouseDownInfo != null) + { + InteractionContext.Scene.ShowSelectionShadow = true; + } + + base.OnMouseUp(mouseEvent3D); + } + + public override void CancelOpperation() + { + IObject3D selectedItem = RootSelection; + if (selectedItem != null + && MouseDownOnControl + && mouseDownInfo != null) + { + selectedItem.Matrix = mouseDownInfo.SelectedObjectTransform; + MouseDownOnControl = false; + MouseOver = false; + mouseDownInfo = null; + mouseMoveInfo = null; + + InteractionContext.Scene.DrawSelection = true; + InteractionContext.Scene.ShowSelectionShadow = true; + } + + base.CancelOpperation(); + } + + public override void SetPosition(IObject3D selectedItem) + { + Vector3 boxCenter = GetControlCenter(selectedItem); + double distBetweenPixelsWorldSpace = InteractionContext.World.GetWorldUnitsPerScreenPixelAtPosition(boxCenter); + + int cornerIndexOut; + GetCornerPosition(selectedItem, out cornerIndexOut); + + Matrix4X4 centerMatrix = Matrix4X4.Identity; + switch (RotationAxis) + { + case 0: + if (cornerIndexOut == 1 || cornerIndexOut == 3) + { + centerMatrix *= Matrix4X4.CreateRotationX(MathHelper.DegreesToRadians(90)); + } + else + { + centerMatrix *= Matrix4X4.CreateRotationY(MathHelper.DegreesToRadians(-90)); + } + centerMatrix *= Matrix4X4.CreateRotationZ(MathHelper.DegreesToRadians(90) * cornerIndexOut); + break; + + case 1: + if (cornerIndexOut == 1 || cornerIndexOut == 3) + { + centerMatrix *= Matrix4X4.CreateRotationY(MathHelper.DegreesToRadians(-90)); + } + else + { + centerMatrix *= Matrix4X4.CreateRotationX(MathHelper.DegreesToRadians(90)); + } + centerMatrix *= Matrix4X4.CreateRotationZ(MathHelper.DegreesToRadians(90) * cornerIndexOut); + break; + + case 2: + centerMatrix *= Matrix4X4.CreateRotationZ(MathHelper.DegreesToRadians(90) * cornerIndexOut); + break; + } + centerMatrix *= Matrix4X4.CreateScale(distBetweenPixelsWorldSpace) * Matrix4X4.CreateTranslation(boxCenter); + TotalTransform = centerMatrix; + } + + /// + /// Measure the difference between two angles. + /// + /// + /// + /// The angle from a to b. If A = 2 and B = 0 return 2. + /// If A = 0 and B = 2 return -2. + private static double DeltaAngle(double startAngle, double endAngle) + { + return Math.Atan2(Math.Sin(startAngle - endAngle), Math.Cos(startAngle - endAngle)); + } + + private void DrawRotationCompass(IObject3D selectedItem, DrawGlContentEventArgs drawEventArgs) + { + if (InteractionContext.Scene.SelectedItem == null) + { + return; + } + + double alphaValue = 1; + if (!drawEventArgs.ZBuffered) + { + alphaValue = .3; + } + + AxisAlignedBoundingBox currentSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + if(currentSelectedBounds.XSize > 100000) + { + // something is wrong the part is too big (probably in invalid selection) + return; + } + Vector3 controlCenter = GetControlCenter(selectedItem); + Vector3 rotationCenter = GetRotationCenter(selectedItem, currentSelectedBounds); + if (mouseDownInfo != null) + { + rotationCenter = mouseDownInfo.SelectedObjectRotationCenter; + controlCenter = mouseDownInfo.ControlCenter; + } + + if (mouseMoveInfo != null) + { + double distBetweenPixelsWorldSpace = InteractionContext.World.GetWorldUnitsPerScreenPixelAtPosition(rotationCenter); + + double radius; + Matrix4X4 rotationCenterTransform = GetRotationTransform(selectedItem, out radius); + + double innerRadius = radius + ringWidth / 2; + double outerRadius = innerRadius + ringWidth; + double snappingMarkRadius = outerRadius + 20; + + double startBlue = 0; + double endBlue = MathHelper.Tau; + + double mouseAngle = 0; + if (mouseMoveInfo != null) + { + mouseAngle = mouseMoveInfo.AngleOfHit; + } + if (mouseDownInfo != null) + { + mouseAngle = mouseDownInfo.AngleOfHit; + } + + var graphics2DOpenGL = new Graphics2DOpenGL(); + + if (mouseDownInfo != null || MouseOver) + { + IVertexSource blueRing = new JoinPaths(new Arc(0, 0, outerRadius, outerRadius, startBlue, endBlue, Arc.Direction.CounterClockWise), + new Arc(0, 0, innerRadius, innerRadius, startBlue, endBlue, Arc.Direction.ClockWise)); + graphics2DOpenGL.RenderTransformedPath(rotationCenterTransform, blueRing, new Color(Color.Blue, (int)(50 * alphaValue)), drawEventArgs.ZBuffered); + // tick 60 marks + DrawTickMarks(drawEventArgs, alphaValue, rotationCenterTransform, innerRadius, outerRadius, 60); + } + + if (mouseDownInfo != null) + { + double startRed = mouseDownInfo.AngleOfHit; + double endRed = SnappedRotationAngle + mouseDownInfo.AngleOfHit; + + if (!rotatingCW) + { + var temp = startRed; + startRed = endRed; + endRed = temp; + } + + IVertexSource redAngle = new JoinPaths(new Arc(0, 0, 0, 0, startRed, endRed, Arc.Direction.CounterClockWise), + new Arc(0, 0, innerRadius, innerRadius, startRed, endRed, Arc.Direction.ClockWise)); + graphics2DOpenGL.RenderTransformedPath(rotationCenterTransform, redAngle, new Color(Color.Red, (int)(70 * alphaValue)), drawEventArgs.ZBuffered); + + // draw a line to the mouse on the rotation circle + if (mouseMoveInfo != null && MouseDownOnControl) + { + Vector3 unitPosition = new Vector3(Math.Cos(mouseMoveInfo.AngleOfHit), Math.Sin(mouseMoveInfo.AngleOfHit), 0); + Vector3 startPosition = Vector3Ex.Transform(unitPosition * innerRadius, rotationCenterTransform); + var center = Vector3Ex.Transform(Vector3.Zero, rotationCenterTransform); + if ((mouseMoveInfo.HitPosition - center).Length > rotationTransformScale * innerRadius) + { + InteractionContext.World.Render3DLine(startPosition, mouseMoveInfo.HitPosition, Color.Red, drawEventArgs.ZBuffered); + } + + DrawSnappingMarks(drawEventArgs, mouseAngle, alphaValue, rotationCenterTransform, snappingMarkRadius, 5, numSnapPoints, GetSnapIndex(selectedItem, numSnapPoints)); + } + } + } + } + + private void DrawSnappingMarks(DrawGlContentEventArgs drawEventArgs, double mouseAngle, double alphaValue, Matrix4X4 rotationCenterTransform, double distanceFromCenter, double dotRadius, int numSnapPoints, int markToSnapTo) + { + var graphics2DOpenGL = new Graphics2DOpenGL(); + + double snappingRadians = MathHelper.Tau / numSnapPoints; + var clippingFrustum = GLHelper.GetClippingFrustum(InteractionContext.World); + for (int i = 0; i < numSnapPoints; i++) + { + double startAngle = i * snappingRadians + mouseAngle; + + VertexStorage snapShape = new VertexStorage(); + snapShape.MoveTo(-10, 0); + snapShape.LineTo(5, 7); + snapShape.LineTo(5, -7); + snapShape.ClosePolygon(); + + var transformed = new VertexSourceApplyTransform(snapShape, Affine.NewTranslation(distanceFromCenter, 0) * Affine.NewRotation(startAngle)); + // new Ellipse(startPosition.x, startPosition.y, dotRadius, dotRadius); + + var color = Color.Black; + if (i == markToSnapTo) + { + color = Color.Red; + } + + graphics2DOpenGL.RenderTransformedPath(rotationCenterTransform, transformed, new Color(color, (int)(254 * alphaValue)), drawEventArgs.ZBuffered); + } + } + + private void DrawTickMarks(DrawGlContentEventArgs drawEventArgs, double alphaValue, Matrix4X4 rotationCenterTransform, double innerRadius, double outerRadius, int numTicks) + { + double snappingRadians = MathHelper.Tau / numTicks; + var clippingFrustum = GLHelper.GetClippingFrustum(InteractionContext.World); + for (int i = 0; i < numTicks; i++) + { + double startAngle = i * snappingRadians; + + Vector3 unitPosition = new Vector3(Math.Cos(startAngle), Math.Sin(startAngle), 0); + Vector3 startPosition = Vector3Ex.Transform(unitPosition * innerRadius, rotationCenterTransform); + Vector3 endPosition = Vector3Ex.Transform(unitPosition * outerRadius, rotationCenterTransform); + + InteractionContext.World.Render3DLine(clippingFrustum, startPosition, endPosition, new Color(Color.Black, (int)(254 * alphaValue)), drawEventArgs.ZBuffered); + } + } + + private bool ForceHideAngle() + { + return (InteractionContext.HoveredInteractionVolume != this + && InteractionContext.HoveredInteractionVolume != null) + || RootSelection != SelectedItemOnMouseDown; + } + + /// + /// This gets the world position of the rotation control handle. It is + /// complicated by the fact that the control is pushed away from the corner + /// that it is part of by a number of pixels in screen space. + /// + /// + /// + private Vector3 GetControlCenter(IObject3D selectedItem) + { + Vector3 boxCenter = GetCornerPosition(selectedItem); + double distBetweenPixelsWorldSpace = InteractionContext.World.GetWorldUnitsPerScreenPixelAtPosition(boxCenter); + // figure out which way the corner is relative to the bounds + Vector3 otherSideDelta = GetDeltaToOtherSideXy(selectedItem); + + double xSign = otherSideDelta.X > 0 ? 1 : -1; + double ySign = otherSideDelta.Y > 0 ? 1 : -1; + + Vector3 delta = new Vector3(xSign * (selectCubeSize.X / 2 + arrowsOffset) * distBetweenPixelsWorldSpace, + ySign * (selectCubeSize.Y / 2 + arrowsOffset) * distBetweenPixelsWorldSpace, + -selectCubeSize.Z / 2 * distBetweenPixelsWorldSpace); + delta[RotationAxis] = 0; + boxCenter -= delta; + + return boxCenter; + } + + private Vector3 GetDeltaToOtherSideXy(IObject3D selectedItem) + { + AxisAlignedBoundingBox currentSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + int cornerIndex; + Vector3 cornerPosition = GetCornerPosition(selectedItem, out cornerIndex); + Vector3 cornerPositionCcw = currentSelectedBounds.GetBottomCorner((cornerIndex + 1) % 4); + Vector3 cornerPositionCw = currentSelectedBounds.GetBottomCorner((cornerIndex + 3) % 4); + + double xDirection = cornerPositionCcw.X - cornerPosition.X; + if (xDirection == 0) xDirection = cornerPositionCw.X - cornerPosition.X; + double yDirection = cornerPositionCcw.Y - cornerPosition.Y; + if (yDirection == 0) yDirection = cornerPositionCw.Y - cornerPosition.Y; + + return new Vector3(xDirection, yDirection, cornerPosition.Z); + } + + private Vector3 GetRotationCenter(IObject3D selectedItem, AxisAlignedBoundingBox currentSelectedBounds) + { + var rotationCenter = currentSelectedBounds.Center; + Vector3 cornerForAxis = GetCornerPosition(selectedItem); + // move the rotation center to the plane of the control + rotationCenter[RotationAxis] = cornerForAxis[RotationAxis]; + + if (mouseDownInfo != null) + { + rotationCenter = mouseDownInfo.SelectedObjectRotationCenter; + } + + return rotationCenter; + } + + private Matrix4X4 GetRotationTransform(IObject3D selectedItem, out double radius) + { + Matrix4X4 rotationCenterTransform; + + AxisAlignedBoundingBox currentSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector3 controlCenter = GetControlCenter(selectedItem); + Vector3 rotationCenter = GetRotationCenter(selectedItem, currentSelectedBounds); + if (mouseDownInfo != null) + { + rotationCenter = mouseDownInfo.SelectedObjectRotationCenter; + controlCenter = mouseDownInfo.ControlCenter; + } + + double distBetweenPixelsWorldSpace = InteractionContext.World.GetWorldUnitsPerScreenPixelAtPosition(rotationCenter); + double lengthFromCenterToControl = (rotationCenter - controlCenter).Length; + + radius = lengthFromCenterToControl * (1 / distBetweenPixelsWorldSpace); + + rotationTransformScale = distBetweenPixelsWorldSpace; + rotationCenterTransform = Matrix4X4.CreateScale(distBetweenPixelsWorldSpace) * Matrix4X4.CreateTranslation(rotationCenter); + + var center = Vector3Ex.Transform(Vector3.Zero, rotationCenterTransform); + + switch (RotationAxis) + { + case 0: + { + rotationCenterTransform = + Matrix4X4.CreateTranslation(-center) + * Matrix4X4.CreateRotation(new Vector3(0, -MathHelper.Tau / 4, 0)) + * Matrix4X4.CreateRotation(new Vector3(-MathHelper.Tau / 4, 0, 0)) + * rotationCenterTransform; + + var center2 = Vector3Ex.Transform(Vector3.Zero, rotationCenterTransform); + rotationCenterTransform *= Matrix4X4.CreateTranslation(center - center2); + } + break; + + case 1: + { + rotationCenterTransform = + Matrix4X4.CreateTranslation(-center) + * Matrix4X4.CreateRotation(new Vector3(MathHelper.Tau / 4, 0, 0)) + * Matrix4X4.CreateRotation(new Vector3(0, MathHelper.Tau / 4, 0)) + * rotationCenterTransform; + + var center2 = Vector3Ex.Transform(Vector3.Zero, rotationCenterTransform); + rotationCenterTransform *= Matrix4X4.CreateTranslation(center - center2); + } + break; + + case 2: + break; + } + + return rotationCenterTransform; + } + + private int GetSnapIndex(IObject3D selectedItem, int numSnapPoints) + { + // If we have the control grabbed + if (mouseMoveInfo != null + && mouseDownInfo != null) + { + double angleAroundPoint = MathHelper.DegreesToRadians(5); + // check if we are within the snap control area + double snappingRadians = MathHelper.Tau / numSnapPoints; + var clippingFrustum = GLHelper.GetClippingFrustum(InteractionContext.World); + for (int i = 0; i < numSnapPoints; i++) + { + double markAngle = i * snappingRadians; + + double differenceAngle = DeltaAngle(markAngle, SnappedRotationAngle); + + if (Math.Abs(differenceAngle) < angleAroundPoint) + { + if (mouseMoveInfo != null) + { + double radius; + Matrix4X4 rotationCenterTransform = GetRotationTransform(selectedItem, out radius); + + double innerRadius = radius + ringWidth / 2; + double outerRadius = innerRadius + ringWidth; + double snappingMarkRadius = outerRadius + 20; + + var center = Vector3Ex.Transform(Vector3.Zero, rotationCenterTransform); + if (Math.Abs((mouseMoveInfo.HitPosition - center).Length - rotationTransformScale * snappingMarkRadius) < 20 * rotationTransformScale) + { + return i; + } + } + } + } + } + + return -1; + } + + private bool IsTriangle(BvhIterator x) + { + return true; + } + + private void InteractionLayer_AfterDraw(object sender, DrawEventArgs drawEvent) + { + IObject3D selectedItem = RootSelection; + if (selectedItem != null + && mouseDownInfo != null) + { + double radius; + Matrix4X4 rotationCenterTransform = GetRotationTransform(selectedItem, out radius); + + Vector3 unitPosition = new Vector3(Math.Cos(mouseDownInfo.AngleOfHit), Math.Sin(mouseDownInfo.AngleOfHit), 0); + Vector3 anglePosition = Vector3Ex.Transform(unitPosition * (radius + 100), rotationCenterTransform); + + Vector2 angleDisplayPosition = InteractionContext.World.GetScreenPosition(anglePosition); + + var displayAngle = MathHelper.RadiansToDegrees(SnappedRotationAngle); + if (!rotatingCW && SnappedRotationAngle > 0) + { + displayAngle -= 360; + } + + angleTextControl.Value = displayAngle; + angleTextControl.OriginRelativeParent = angleDisplayPosition - angleTextControl.LocalBounds.Center; + } + } + + private void RotateAroundAxis(IObject3D selectedItem, double rotationAngle) + { + Vector3 rotationVector = Vector3.Zero; + rotationVector[RotationAxis] = -rotationAngle; + Matrix4X4 rotationMatrix = Matrix4X4.CreateRotation(rotationVector); + + selectedItem.Matrix = selectedItem.Matrix.ApplyAtPosition(mouseDownInfo.SelectedObjectRotationCenter, rotationMatrix); + } + + internal class Mouse3DInfo + { + internal Mouse3DInfo(Vector3 downPosition, Matrix4X4 selectedObjectTransform, Vector3 selectedObjectRotationCenter, Vector3 controlCenter, int rotationAxis) + { + HitPosition = downPosition; + SelectedObjectTransform = selectedObjectTransform; + SelectedObjectRotationCenter = selectedObjectRotationCenter; + ControlCenter = controlCenter; + DeltaObjectToHit = HitPosition - SelectedObjectRotationCenter; + + AngleOfHit = GetAngleForAxis(DeltaObjectToHit, rotationAxis); + } + + internal double AngleOfHit { get; set; } + + internal Vector3 ControlCenter { get; private set; } + + internal Vector3 DeltaObjectToHit { get; private set; } + + internal Vector3 HitPosition { get; set; } + + internal Vector3 SelectedObjectRotationCenter { get; private set; } + + internal Matrix4X4 SelectedObjectTransform { get; private set; } = Matrix4X4.Identity; + + static internal double GetAngleForAxis(Vector3 deltaVector, int axisIndex) + { + switch (axisIndex) + { + case 0: + return Math.Atan2(deltaVector.Z, deltaVector.Y); + + case 1: + return Math.Atan2(deltaVector.X, deltaVector.Z); + + default: + return Math.Atan2(deltaVector.Y, deltaVector.X); + } + } + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/RotateControls/RotateInteractionPlugins.cs b/MatterControlLib/DesignTools/EditorTools/RotateControls/RotateInteractionPlugins.cs new file mode 100644 index 000000000..1ea7d9568 --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/RotateControls/RotateInteractionPlugins.cs @@ -0,0 +1,48 @@ +/* +Copyright (c) 2017, 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 MatterHackers.MeshVisualizer; + +namespace MatterHackers.Plugins.EditorTools +{ + public class RotateCornerPlugins : IInteractionVolumeProvider + { + public IEnumerable Create(IInteractionVolumeContext context) + { + // X, Y, Z RotateCornerControls + return new[] + { + new RotateCornerControl(context, 0), + new RotateCornerControl(context, 1), + new RotateCornerControl(context, 2) + }; + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleCornerControl.cs b/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleCornerControl.cs new file mode 100644 index 000000000..a268ab7d7 --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleCornerControl.cs @@ -0,0 +1,535 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 MatterHackers.Agg; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.UI; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.MeshVisualizer; +using MatterHackers.PolygonMesh; +using MatterHackers.RayTracer; +using MatterHackers.RenderOpenGl; +using MatterHackers.VectorMath; + +namespace MatterHackers.Plugins.EditorTools +{ + public class ScaleCornerControl : InteractionVolume + { + public IObject3D ActiveSelectedItem; + protected PlaneShape hitPlane; + protected Vector3 initialHitPosition; + protected Mesh minXminYMesh; + protected AxisAlignedBoundingBox mouseDownSelectedBounds; + protected Matrix4X4 transformOnMouseDown = Matrix4X4.Identity; + protected Matrix4X4 transformAppliedByThis = Matrix4X4.Identity; + private double distToStart = 10; + private double lineLength = 35; + private List lines = new List(); + private Vector3 originalPointToMove; + private int quadrantIndex; + private double selectCubeSize = 7 * GuiWidget.DeviceScale; + private InlineEditControl xValueDisplayInfo; + private InlineEditControl yValueDisplayInfo; + private bool HadClickOnControl; + + public ScaleCornerControl(IInteractionVolumeContext context, int cornerIndex) + : base(context) + { + xValueDisplayInfo = new InlineEditControl() + { + ForceHide = ForceHideScale, + GetDisplayString = (value) => "{0:0.0}mm".FormatWith(value), + }; + + xValueDisplayInfo.EditComplete += EditComplete; + + xValueDisplayInfo.VisibleChanged += (s, e) => + { + if (!xValueDisplayInfo.Visible) + { + HadClickOnControl = false; + } + }; + + yValueDisplayInfo = new InlineEditControl() + { + ForceHide = ForceHideScale, + GetDisplayString = (value) => "{0:0.0}mm".FormatWith(value) + }; + + yValueDisplayInfo.EditComplete += EditComplete; + + yValueDisplayInfo.VisibleChanged += (s, e) => + { + if (!yValueDisplayInfo.Visible) + { + HadClickOnControl = false; + } + }; + + InteractionContext.GuiSurface.AddChild(xValueDisplayInfo); + InteractionContext.GuiSurface.AddChild(yValueDisplayInfo); + + this.quadrantIndex = cornerIndex; + + DrawOnTop = true; + + minXminYMesh = PlatonicSolids.CreateCube(selectCubeSize, selectCubeSize, selectCubeSize); + + CollisionVolume = minXminYMesh.CreateTraceData(); + + InteractionContext.GuiSurface.AfterDraw += InteractionLayer_AfterDraw; + } + + void EditComplete(object s, EventArgs e) + { + var selectedItem = ActiveSelectedItem; + Matrix4X4 startingTransform = selectedItem.Matrix; + + AxisAlignedBoundingBox originalSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector3 cornerPosition = GetCornerPosition(selectedItem, quadrantIndex); + Vector3 cornerPositionCcw = GetCornerPosition(selectedItem, (quadrantIndex + 1) % 4); + Vector3 lockedCorner = GetCornerPosition(selectedItem, (quadrantIndex + 2) % 4); + Vector3 cornerPositionCw = GetCornerPosition(selectedItem, (quadrantIndex + 3) % 4); + + Vector3 otherSideDelta = GetDeltaToOtherSideXy(selectedItem, quadrantIndex); + + Vector3 newSize = Vector3.Zero; + newSize.X = xValueDisplayInfo.Value; + newSize.Y = yValueDisplayInfo.Value; + + Vector3 scaleAmount = GetScalingConsideringShiftKey(originalSelectedBounds, mouseDownSelectedBounds, newSize, InteractionContext.GuiSurface.ModifierKeys); + + // scale it + Matrix4X4 scale = Matrix4X4.CreateScale(scaleAmount); + + selectedItem.Matrix = selectedItem.ApplyAtBoundsCenter(scale); + + // and keep the locked edge in place + Vector3 newLockedCorner = GetCornerPosition(selectedItem, (quadrantIndex + 2) % 4); + + AxisAlignedBoundingBox postScaleBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + newLockedCorner.Z = 0; + lockedCorner.Z = originalSelectedBounds.MinXYZ.Z - postScaleBounds.MinXYZ.Z; + + selectedItem.Matrix *= Matrix4X4.CreateTranslation(lockedCorner - newLockedCorner); + + Invalidate(); + + InteractionContext.Scene.AddTransformSnapshot(startingTransform); + + transformAppliedByThis = selectedItem.Matrix; + } + + public override void DrawGlContent(DrawGlContentEventArgs e) + { + bool shouldDrawScaleControls = true; + if (InteractionContext.SelectedInteractionVolume != null + && InteractionContext.SelectedInteractionVolume as ScaleCornerControl == null) + { + shouldDrawScaleControls = false; + } + + var selectedItem = RootSelection; + + if (selectedItem != null + && InteractionContext.Scene.ShowSelectionShadow) + { + // Ensures that functions in this scope run against the original instance reference rather than the + // current value, thus avoiding null reference errors that would occur otherwise + + if (shouldDrawScaleControls) + { + // don't draw if any other control is dragging + if (MouseOver) + { + GLHelper.Render(minXminYMesh, Color.Red, TotalTransform, RenderTypes.Shaded); + } + else + { + GLHelper.Render(minXminYMesh, Color.Black, TotalTransform, RenderTypes.Shaded); + } + } + + if (e != null) + { + Vector3 startPosition = GetCornerPosition(selectedItem, quadrantIndex); + + Vector3 endPosition = GetCornerPosition(selectedItem, (quadrantIndex + 1) % 4); + + Frustum clippingFrustum = GLHelper.GetClippingFrustum(InteractionContext.World); + + if (clippingFrustum.ClipLine(ref startPosition, ref endPosition)) + { + if (e.ZBuffered) + { + InteractionContext.World.Render3DLine(clippingFrustum, startPosition, endPosition, Color.Black); + } + else + { + // render on top of everything very lightly + InteractionContext.World.Render3DLine(clippingFrustum, startPosition, endPosition, new Color(Color.Black, 20), false); + } + } + + //Vector3 startScreenSpace = InteractionContext.World.GetScreenSpace(startPosition); + //e.graphics2D.Circle(startScreenSpace.x, startScreenSpace.y, 5, Color.Red); + + //Vector2 startScreenPosition = InteractionContext.World.GetScreenPosition(startPosition); + //e.graphics2D.Circle(startScreenPosition.x, startScreenPosition.y, 5, Color.Red); + } + } + + base.DrawGlContent(e); + } + + public Vector3 GetCornerPosition(IObject3D item, int quadrantIndex) + { + AxisAlignedBoundingBox originalSelectedBounds = item.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + Vector3 cornerPosition = originalSelectedBounds.GetBottomCorner(quadrantIndex); + + return SetBottomControlHeight(originalSelectedBounds, cornerPosition); + } + + public override void OnMouseDown(MouseEvent3DArgs mouseEvent3D) + { + var selectedItem = RootSelection; + ActiveSelectedItem = selectedItem; + + if (mouseEvent3D.MouseEvent2D.Button == MouseButtons.Left + && mouseEvent3D.info != null + && selectedItem != null) + { + HadClickOnControl = true; + + yValueDisplayInfo.Visible = true; + xValueDisplayInfo.Visible = true; + + hitPlane = new PlaneShape(Vector3.UnitZ, mouseEvent3D.info.HitPosition.Z, null); + originalPointToMove = GetCornerPosition(selectedItem, quadrantIndex); + + initialHitPosition = mouseEvent3D.info.HitPosition; + transformOnMouseDown = transformAppliedByThis = selectedItem.Matrix; + mouseDownSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + } + + base.OnMouseDown(mouseEvent3D); + } + + public override void OnMouseMove(MouseEvent3DArgs mouseEvent3D) + { + var selectedItem = RootSelection; + ActiveSelectedItem = selectedItem; + + if (MouseOver) + { + xValueDisplayInfo.Visible = true; + yValueDisplayInfo.Visible = true; + } + else if (!HadClickOnControl + || (selectedItem != null && selectedItem.Matrix != transformAppliedByThis)) + { + xValueDisplayInfo.Visible = false; + yValueDisplayInfo.Visible = false; + } + + if (MouseDownOnControl) + { + IntersectInfo info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay); + + if (info != null + && selectedItem != null) + { + AxisAlignedBoundingBox originalSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector3 delta = info.HitPosition - initialHitPosition; + + Vector3 newPosition = originalPointToMove + delta; + + if (InteractionContext.SnapGridDistance > 0) + { + // snap this position to the grid + double snapGridDistance = InteractionContext.SnapGridDistance; + + // snap this position to the grid + newPosition.X = ((int)((newPosition.X / snapGridDistance) + .5)) * snapGridDistance; + newPosition.Y = ((int)((newPosition.Y / snapGridDistance) + .5)) * snapGridDistance; + } + + Vector3 cornerPosition = GetCornerPosition(selectedItem, quadrantIndex); + Vector3 cornerPositionCcw = GetCornerPosition(selectedItem, (quadrantIndex + 1) % 4); + Vector3 lockedCorner = GetCornerPosition(selectedItem, (quadrantIndex + 2) % 4); + Vector3 cornerPositionCw = GetCornerPosition(selectedItem, (quadrantIndex + 3) % 4); + + Vector3 otherSideDelta = GetDeltaToOtherSideXy(selectedItem, quadrantIndex); + + Vector3 newSize = Vector3.Zero; + newSize.X = lockedCorner.X - newPosition.X; + if (quadrantIndex == 0 || quadrantIndex == 3) + { + newSize.X *= -1; + } + newSize.Y = lockedCorner.Y - newPosition.Y; + if (quadrantIndex == 0 || quadrantIndex == 1) + { + newSize.Y *= -1; + } + + Vector3 scaleAmount = GetScalingConsideringShiftKey(originalSelectedBounds, mouseDownSelectedBounds, newSize, InteractionContext.GuiSurface.ModifierKeys); + + // scale it + Matrix4X4 scale = Matrix4X4.CreateScale(scaleAmount); + + selectedItem.Matrix = selectedItem.ApplyAtBoundsCenter(scale); + + // and keep the locked edge in place + Vector3 newLockedCorner = GetCornerPosition(selectedItem, (quadrantIndex + 2) % 4); + + AxisAlignedBoundingBox postScaleBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + newLockedCorner.Z = 0; + lockedCorner.Z = mouseDownSelectedBounds.MinXYZ.Z - postScaleBounds.MinXYZ.Z; + + selectedItem.Matrix *= Matrix4X4.CreateTranslation(lockedCorner - newLockedCorner); + + Invalidate(); + } + } + + if (selectedItem != null) + { + transformAppliedByThis = selectedItem.Matrix; + } + + base.OnMouseMove(mouseEvent3D); + } + + public override void OnMouseUp(MouseEvent3DArgs mouseEvent3D) + { + if (HadClickOnControl) + { + InteractionContext.Scene.AddTransformSnapshot(transformOnMouseDown); + } + base.OnMouseUp(mouseEvent3D); + } + + public override void CancelOpperation() + { + IObject3D selectedItem = RootSelection; + if (selectedItem != null + && MouseDownOnControl) + { + selectedItem.Matrix = transformOnMouseDown; + MouseDownOnControl = false; + MouseOver = false; + + InteractionContext.Scene.DrawSelection = true; + InteractionContext.Scene.ShowSelectionShadow = true; + } + + base.CancelOpperation(); + } + + public override void SetPosition(IObject3D selectedItem) + { + AxisAlignedBoundingBox selectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector3 cornerPosition = GetCornerPosition(selectedItem, quadrantIndex); + double distBetweenPixelsWorldSpace = InteractionContext.World.GetWorldUnitsPerScreenPixelAtPosition(cornerPosition); + + Vector3 cornerPositionCcw = GetCornerPosition(selectedItem, (quadrantIndex + 1) % 4); + Vector3 cornerPositionCw = GetCornerPosition(selectedItem, (quadrantIndex + 3) % 4); + + // figure out which way the corner is relative to the bounds + Vector3 otherSideDelta = GetDeltaToOtherSideXy(selectedItem, quadrantIndex); + + double xSign = otherSideDelta.X > 0 ? 1 : -1; + double ySign = otherSideDelta.Y > 0 ? 1 : -1; + + Vector3 boxCenter = cornerPosition; + boxCenter.X -= xSign * selectCubeSize / 2 * distBetweenPixelsWorldSpace; + boxCenter.Y -= ySign * selectCubeSize / 2 * distBetweenPixelsWorldSpace; + boxCenter.Z += selectCubeSize / 2 * distBetweenPixelsWorldSpace; + + Matrix4X4 centerMatrix = Matrix4X4.CreateTranslation(boxCenter); + centerMatrix = Matrix4X4.CreateScale(distBetweenPixelsWorldSpace) * centerMatrix; + TotalTransform = centerMatrix; + + Vector3 xOtherSide = new Vector3(cornerPosition.X + otherSideDelta.X, cornerPosition.Y, cornerPosition.Z); + Vector3 yOtherSide = new Vector3(cornerPosition.X, cornerPosition.Y + otherSideDelta.Y, cornerPosition.Z); + + lines.Clear(); + // left lines + lines.Add(InteractionContext.World.GetScreenPosition(cornerPosition - new Vector3(xSign * distToStart * distBetweenPixelsWorldSpace, 0, 0))); + lines.Add(InteractionContext.World.GetScreenPosition(cornerPosition - new Vector3(xSign * (distToStart + lineLength) * distBetweenPixelsWorldSpace, 0, 0))); + + lines.Add(InteractionContext.World.GetScreenPosition(yOtherSide - new Vector3(xSign * distToStart * distBetweenPixelsWorldSpace, 0, 0))); + lines.Add(InteractionContext.World.GetScreenPosition(yOtherSide - new Vector3(xSign * (distToStart + lineLength) * distBetweenPixelsWorldSpace, 0, 0))); + + // bottom lines + lines.Add(InteractionContext.World.GetScreenPosition(cornerPosition - new Vector3(0, ySign * distToStart * distBetweenPixelsWorldSpace, 0))); + lines.Add(InteractionContext.World.GetScreenPosition(cornerPosition - new Vector3(0, ySign * (distToStart + lineLength) * distBetweenPixelsWorldSpace, 0))); + + lines.Add(InteractionContext.World.GetScreenPosition(xOtherSide - new Vector3(0, ySign * distToStart * distBetweenPixelsWorldSpace, 0))); + lines.Add(InteractionContext.World.GetScreenPosition(xOtherSide - new Vector3(0, ySign * (distToStart + lineLength) * distBetweenPixelsWorldSpace, 0))); + } + + public static Vector3 GetScalingConsideringShiftKey(AxisAlignedBoundingBox originalSelectedBounds, + AxisAlignedBoundingBox mouseDownSelectedBounds, + Vector3 newSize, + Keys modifierKeys) + { + var minimumSize = .1; + Vector3 scaleAmount = Vector3.One; + + if(originalSelectedBounds.XSize <= 0 + || originalSelectedBounds.YSize <= 0 + || originalSelectedBounds.ZSize <= 0) + { + // don't scale if any dimension will go to 0 + return scaleAmount; + } + + if (modifierKeys == Keys.Shift) + { + newSize.X = newSize.X <= minimumSize ? minimumSize : newSize.X; + newSize.Y = newSize.Y <= minimumSize ? minimumSize : newSize.Y; + newSize.Z = newSize.Z <= minimumSize ? minimumSize : newSize.Z; + + scaleAmount.X = mouseDownSelectedBounds.XSize / originalSelectedBounds.XSize; + scaleAmount.Y = mouseDownSelectedBounds.YSize / originalSelectedBounds.YSize; + scaleAmount.Z = mouseDownSelectedBounds.ZSize / originalSelectedBounds.ZSize; + + double scaleFromOriginal = Math.Max(newSize.X / mouseDownSelectedBounds.XSize, newSize.Y / mouseDownSelectedBounds.YSize); + scaleFromOriginal = Math.Max(scaleFromOriginal, newSize.Z / mouseDownSelectedBounds.ZSize); + scaleAmount *= scaleFromOriginal; + } + else + { + if (newSize.X > 0) + { + newSize.X = newSize.X <= minimumSize ? minimumSize : newSize.X; + scaleAmount.X = newSize.X / originalSelectedBounds.XSize; + } + if (newSize.Y > 0) + { + newSize.Y = newSize.Y <= minimumSize ? minimumSize : newSize.Y; + scaleAmount.Y = newSize.Y / originalSelectedBounds.YSize; + } + if (newSize.Z > 0) + { + newSize.Z = newSize.Z <= minimumSize ? minimumSize : newSize.Z; + scaleAmount.Z = newSize.Z / originalSelectedBounds.ZSize; + } + } + + return scaleAmount; + } + + private bool ForceHideScale() + { + var selectedItem = RootSelection; + // if the selection changes + if (selectedItem != ActiveSelectedItem) + { + return true; + } + + // if another control gets a hover + if (InteractionContext.HoveredInteractionVolume != this + && InteractionContext.HoveredInteractionVolume != null) + { + return true; + } + + // if we clicked on the control + if (HadClickOnControl) + { + return false; + } + + return false; + } + private Vector3 GetDeltaToOtherSideXy(IObject3D selectedItem, int quadrantIndex) + { + Vector3 cornerPosition = GetCornerPosition(selectedItem, quadrantIndex); + Vector3 cornerPositionCcw = GetCornerPosition(selectedItem, (quadrantIndex + 1) % 4); + Vector3 cornerPositionCw = GetCornerPosition(selectedItem, (quadrantIndex + 3) % 4); + + double xDirection = cornerPositionCcw.X - cornerPosition.X; + if (xDirection == 0) xDirection = cornerPositionCw.X - cornerPosition.X; + double yDirection = cornerPositionCcw.Y - cornerPosition.Y; + if (yDirection == 0) yDirection = cornerPositionCw.Y - cornerPosition.Y; + + return new Vector3(xDirection, yDirection, cornerPosition.Z); + } + + private void InteractionLayer_AfterDraw(object sender, DrawEventArgs drawEvent) + { + var selectedItem = RootSelection; + + if (selectedItem != null) + { + if (MouseOver || MouseDownOnControl) + { + + for (int i = 0; i < lines.Count; i += 2) + { + // draw the line that is on the ground + drawEvent.Graphics2D.Line(lines[i], lines[i + 1], Color.Black); + } + + for (int i = 0; i < lines.Count; i += 4) + { + DrawMeasureLine(drawEvent.Graphics2D, (lines[i] + lines[i + 1]) / 2, (lines[i + 2] + lines[i + 3]) / 2, Color.Black, LineArrows.Both); + } + + int j = 4; + + AxisAlignedBoundingBox selectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector2 widthDisplayCenter = (((lines[j] + lines[j + 1]) / 2) + ((lines[j + 2] + lines[j + 3]) / 2)) / 2; + xValueDisplayInfo.Value = selectedBounds.XSize; + xValueDisplayInfo.OriginRelativeParent = widthDisplayCenter - xValueDisplayInfo.LocalBounds.Center; + + j = 0; + Vector2 heightDisplayCenter = (((lines[j] + lines[j + 1]) / 2) + ((lines[j + 2] + lines[j + 3]) / 2)) / 2; + yValueDisplayInfo.Value = selectedBounds.YSize; + yValueDisplayInfo.OriginRelativeParent = heightDisplayCenter - yValueDisplayInfo.LocalBounds.Center; + } + } + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleInteractionPlugins.cs b/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleInteractionPlugins.cs new file mode 100644 index 000000000..fc39f645a --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleInteractionPlugins.cs @@ -0,0 +1,49 @@ +/* +Copyright (c) 2017, 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 MatterHackers.MeshVisualizer; + +namespace MatterHackers.Plugins.EditorTools +{ + public class ScaleCornersPlugin : IInteractionVolumeProvider + { + public IEnumerable Create(IInteractionVolumeContext context) + { + return new List + { + new ScaleTopControl(context), + new ScaleCornerControl(context, 0), + new ScaleCornerControl(context, 1), + new ScaleCornerControl(context, 2), + new ScaleCornerControl(context, 3) + }; + } + } +} \ No newline at end of file diff --git a/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleTopControl.cs b/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleTopControl.cs new file mode 100644 index 000000000..bff13c17a --- /dev/null +++ b/MatterControlLib/DesignTools/EditorTools/ScaleControls/ScaleTopControl.cs @@ -0,0 +1,371 @@ +/* +Copyright (c) 2014, Lars Brubaker +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 MatterHackers.Agg; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.UI; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.DesignTools.Operations; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.MeshVisualizer; +using MatterHackers.PolygonMesh; +using MatterHackers.RayTracer; +using MatterHackers.RenderOpenGl; +using MatterHackers.VectorMath; + +namespace MatterHackers.Plugins.EditorTools +{ + public class ScaleTopControl : InteractionVolume + { + public IObject3D ActiveSelectedItem; + protected PlaneShape hitPlane; + protected Vector3 initialHitPosition; + protected Mesh topScaleMesh; + protected AxisAlignedBoundingBox mouseDownSelectedBounds; + protected Matrix4X4 transformOnMouseDown = Matrix4X4.Identity; + private double distToStart = 5; + private double lineLength = 55; + private List lines = new List(); + private Vector3 originalPointToMove; + private double selectCubeSize = 7 * GuiWidget.DeviceScale; + private InlineEditControl zValueDisplayInfo; + private bool HadClickOnControl; + + public ScaleTopControl(IInteractionVolumeContext context) + : base(context) + { + zValueDisplayInfo = new InlineEditControl() + { + ForceHide = () => + { + // if the selection changes + if (RootSelection != ActiveSelectedItem) + { + return true; + } + + // if another control gets a hover + if (InteractionContext.HoveredInteractionVolume != this + && InteractionContext.HoveredInteractionVolume != null) + { + return true; + } + + + // if we clicked on the control + if (HadClickOnControl) + { + return false; + } + + return false; + } +, + GetDisplayString = (value) => "{0:0.0}mm".FormatWith(value) + }; + + zValueDisplayInfo.VisibleChanged += (s, e) => + { + if (!zValueDisplayInfo.Visible) + { + HadClickOnControl = false; + } + }; + + zValueDisplayInfo.EditComplete += (s, e) => + { + var selectedItem = ActiveSelectedItem; + + Matrix4X4 startingTransform = selectedItem.Matrix; + var originalSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + Vector3 topPosition = GetTopPosition(selectedItem); + Vector3 lockedBottom = new Vector3(topPosition.X, topPosition.Y, originalSelectedBounds.MinXYZ.Z); + + Vector3 newSize = Vector3.Zero; + newSize.Z = zValueDisplayInfo.Value; + Vector3 scaleAmount = ScaleCornerControl.GetScalingConsideringShiftKey(originalSelectedBounds, mouseDownSelectedBounds, newSize, InteractionContext.GuiSurface.ModifierKeys); + + Matrix4X4 scale = Matrix4X4.CreateScale(scaleAmount); + + selectedItem.Matrix = selectedItem.ApplyAtBoundsCenter(scale); + + // and keep the locked edge in place + AxisAlignedBoundingBox scaledSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + Vector3 newLockedBottom = new Vector3(topPosition.X, topPosition.Y, scaledSelectedBounds.MinXYZ.Z); + + selectedItem.Matrix *= Matrix4X4.CreateTranslation(lockedBottom - newLockedBottom); + + Invalidate(); + + InteractionContext.Scene.AddTransformSnapshot(startingTransform); + }; + + InteractionContext.GuiSurface.AddChild(zValueDisplayInfo); + + DrawOnTop = true; + + topScaleMesh = PlatonicSolids.CreateCube(selectCubeSize, selectCubeSize, selectCubeSize); + + CollisionVolume = topScaleMesh.CreateTraceData(); + + InteractionContext.GuiSurface.AfterDraw += InteractionLayer_AfterDraw; + } + + public override void DrawGlContent(DrawGlContentEventArgs e) + { + bool shouldDrawScaleControls = true; + var selectedItem = RootSelection; + + if (InteractionContext.SelectedInteractionVolume != null + && InteractionContext.SelectedInteractionVolume as ScaleTopControl == null) + { + shouldDrawScaleControls = false; + } + + if (selectedItem != null) + { + if (shouldDrawScaleControls) + { + // don't draw if any other control is dragging + if (MouseOver) + { + GLHelper.Render(topScaleMesh, Color.Red, TotalTransform, RenderTypes.Shaded); + } + else + { + GLHelper.Render(topScaleMesh, Color.Black, TotalTransform, RenderTypes.Shaded); + } + } + + if (e != null) + { + // evaluate the position of the up line to draw + Vector3 topPosition = GetTopPosition(selectedItem); + + var bottomPosition = topPosition; + var originalSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + bottomPosition.Z = originalSelectedBounds.MinXYZ.Z; + + // render with z-buffer full black + double distBetweenPixelsWorldSpace = InteractionContext.World.GetWorldUnitsPerScreenPixelAtPosition(topPosition); + Vector3 delta = topPosition - bottomPosition; + Vector3 centerPosition = (topPosition + bottomPosition) / 2; + Matrix4X4 rotateTransform = Matrix4X4.CreateRotation(new Quaternion(delta, Vector3.UnitX)); + Matrix4X4 scaleTransform = Matrix4X4.CreateScale((topPosition - bottomPosition).Length, distBetweenPixelsWorldSpace, distBetweenPixelsWorldSpace); + Matrix4X4 lineTransform = scaleTransform * rotateTransform * Matrix4X4.CreateTranslation(centerPosition); + + Frustum clippingFrustum = GLHelper.GetClippingFrustum(InteractionContext.World); + + if (e.ZBuffered) + { + InteractionContext.World.Render3DLine(clippingFrustum, bottomPosition, topPosition, Color.Black); + } + else + { + // render on top of everything very lightly + InteractionContext.World.Render3DLine(clippingFrustum, bottomPosition, topPosition, new Color(Color.Black, 20), false); + } + } + } + + base.DrawGlContent(e); + } + + public Vector3 GetTopPosition(IObject3D selectedItem) + { + AxisAlignedBoundingBox originalSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + return new Vector3(originalSelectedBounds.Center.X, originalSelectedBounds.Center.Y, originalSelectedBounds.MaxXYZ.Z); + } + + public override void OnMouseDown(MouseEvent3DArgs mouseEvent3D) + { + if (mouseEvent3D.info != null && InteractionContext.Scene.SelectedItem != null) + { + HadClickOnControl = true; + ActiveSelectedItem = RootSelection; + + zValueDisplayInfo.Visible = true; + + var selectedItem = ActiveSelectedItem; + + double distanceToHit = Vector3Ex.Dot(mouseEvent3D.info.HitPosition, mouseEvent3D.MouseRay.directionNormal); + hitPlane = new PlaneShape(mouseEvent3D.MouseRay.directionNormal, distanceToHit, null); + originalPointToMove = GetTopPosition(selectedItem); + + initialHitPosition = mouseEvent3D.info.HitPosition; + transformOnMouseDown = selectedItem.Matrix; + mouseDownSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + } + + base.OnMouseDown(mouseEvent3D); + } + + public override void OnMouseMove(MouseEvent3DArgs mouseEvent3D) + { + var selectedItem = RootSelection; + + ActiveSelectedItem = selectedItem; + if (MouseOver) + { + zValueDisplayInfo.Visible = true; + } + else if (!HadClickOnControl) + { + zValueDisplayInfo.Visible = false; + } + + if (MouseDownOnControl) + { + IntersectInfo info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay); + + if (info != null + && selectedItem != null) + { + AxisAlignedBoundingBox originalSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector3 delta = info.HitPosition - initialHitPosition; + + Vector3 newPosition = originalPointToMove + delta; + + if (InteractionContext.SnapGridDistance > 0) + { + // snap this position to the grid + double snapGridDistance = InteractionContext.SnapGridDistance; + + // snap this position to the grid + newPosition.Z = ((int)((newPosition.Z / snapGridDistance) + .5)) * snapGridDistance; + } + + Vector3 topPosition = GetTopPosition(selectedItem); + Vector3 lockedBottom = new Vector3(topPosition.X, topPosition.Y, originalSelectedBounds.MinXYZ.Z); + + Vector3 newSize = Vector3.Zero; + newSize.Z = newPosition.Z - lockedBottom.Z; + + // scale it + Vector3 scaleAmount = ScaleCornerControl.GetScalingConsideringShiftKey(originalSelectedBounds, mouseDownSelectedBounds, newSize, InteractionContext.GuiSurface.ModifierKeys); + + Matrix4X4 scale = Matrix4X4.CreateScale(scaleAmount); + + selectedItem.Matrix = selectedItem.ApplyAtBoundsCenter(scale); + + // and keep the locked edge in place + AxisAlignedBoundingBox scaledSelectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + Vector3 newLockedBottom = new Vector3(topPosition.X, topPosition.Y, scaledSelectedBounds.MinXYZ.Z); + + selectedItem.Matrix *= Matrix4X4.CreateTranslation(lockedBottom - newLockedBottom); + + Invalidate(); + } + } + + base.OnMouseMove(mouseEvent3D); + } + + public override void OnMouseUp(MouseEvent3DArgs mouseEvent3D) + { + InteractionContext.Scene.AddTransformSnapshot(transformOnMouseDown); + base.OnMouseUp(mouseEvent3D); + } + + public override void CancelOpperation() + { + IObject3D selectedItem = RootSelection; + if (selectedItem != null + && MouseDownOnControl) + { + selectedItem.Matrix = transformOnMouseDown; + MouseDownOnControl = false; + MouseOver = false; + + InteractionContext.Scene.DrawSelection = true; + InteractionContext.Scene.ShowSelectionShadow = true; + } + + base.CancelOpperation(); + } + + public override void SetPosition(IObject3D selectedItem) + { + AxisAlignedBoundingBox selectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector3 topPosition = GetTopPosition(selectedItem); + Vector3 bottomPosition = new Vector3(topPosition.X, topPosition.Y, selectedBounds.MinXYZ.Z); + double distBetweenPixelsWorldSpace = InteractionContext.World.GetWorldUnitsPerScreenPixelAtPosition(topPosition); + + Vector3 boxCenter = topPosition; + boxCenter.Z += selectCubeSize / 2 * distBetweenPixelsWorldSpace; + + Matrix4X4 centerMatrix = Matrix4X4.CreateTranslation(boxCenter); + centerMatrix = Matrix4X4.CreateScale(distBetweenPixelsWorldSpace) * centerMatrix; + TotalTransform = centerMatrix; + + lines.Clear(); + // left lines + lines.Add(InteractionContext.World.GetScreenPosition(topPosition + new Vector3(distToStart * distBetweenPixelsWorldSpace, 0, 0))); + lines.Add(new Vector2(lines[0].X + lineLength, lines[0].Y)); + + lines.Add(InteractionContext.World.GetScreenPosition(bottomPosition + new Vector3(distToStart * distBetweenPixelsWorldSpace, 0, 0))); + lines.Add(new Vector2(lines[2].X + lineLength, lines[2].Y)); + } + + private void InteractionLayer_AfterDraw(object sender, DrawEventArgs drawEvent) + { + var selectedItem = RootSelection; + + if (selectedItem != null) + { + if (zValueDisplayInfo.Visible) + { + for (int i = 0; i < lines.Count; i += 2) + { + // draw the measure line + drawEvent.Graphics2D.Line(lines[i], lines[i + 1], Color.Black); + } + + for (int i = 0; i < lines.Count; i += 4) + { + DrawMeasureLine(drawEvent.Graphics2D, (lines[i] + lines[i + 1]) / 2, (lines[i + 2] + lines[i + 3]) / 2, Color.Black, LineArrows.Both); + } + + int j = 0; + AxisAlignedBoundingBox selectedBounds = selectedItem.GetAxisAlignedBoundingBox(Matrix4X4.Identity); + + Vector2 heightDisplayCenter = (((lines[j] + lines[j + 1]) / 2) + ((lines[j + 2] + lines[j + 3]) / 2)) / 2; + zValueDisplayInfo.Value = selectedBounds.ZSize; + zValueDisplayInfo.OriginRelativeParent = heightDisplayCenter + new Vector2(10, -zValueDisplayInfo.LocalBounds.Center.Y); + } + } + } + } +} \ No newline at end of file diff --git a/MatterControlLib/Library/PartSheets/PartSheetPlugin.cs b/MatterControlLib/Library/PartSheets/PartSheetPlugin.cs new file mode 100644 index 000000000..a5bcd44d2 --- /dev/null +++ b/MatterControlLib/Library/PartSheets/PartSheetPlugin.cs @@ -0,0 +1,82 @@ +/* +Copyright (c) 2019, Kevin Pope, John Lewin +All rights reserved. +*/ + +using System.Linq; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.UI; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.Extensibility; +using MatterHackers.MatterControl.Library; +using MatterHackers.MatterControl.PrintQueue; + +namespace MatterHackers.MatterControl.Plugins +{ + public class PartSheetPlugin : IApplicationPlugin + { + public void Initialize() + { + // PDF export is limited to Windows + if (AggContext.OperatingSystem != OSType.Windows) + { + return; + } + + ApplicationController.Instance.Library.MenuExtensions.Add( + new LibraryAction(ActionScope.ListItem) + { + Title = "Create Part Sheet".Localize(), + Action = (selectedLibraryItems, listView) => + { + UiThread.RunOnIdle(() => + { + var printItems = selectedLibraryItems.OfType(); + if (printItems.Any()) + { + AggContext.FileDialogs.SaveFileDialog( + new SaveFileDialogParams("Save Parts Sheet|*.pdf") + { + ActionButtonLabel = "Save Parts Sheet".Localize(), + Title = ApplicationController.Instance.ProductName + " - " + "Save".Localize() + }, + (saveParams) => + { + if (!string.IsNullOrEmpty(saveParams.FileName)) + { + var feedbackWindow = new SavePartsSheetFeedbackWindow( + printItems.Count(), + printItems.FirstOrDefault()?.Name, + AppContext.Theme.BackgroundColor); + + var currentPartsInQueue = new PartsSheet(printItems, saveParams.FileName); + currentPartsInQueue.UpdateRemainingItems += feedbackWindow.StartingNextPart; + currentPartsInQueue.DoneSaving += feedbackWindow.DoneSaving; + + feedbackWindow.ShowAsSystemWindow(); + + currentPartsInQueue.SaveSheets().ConfigureAwait(false); + } + }); + } + }); + }, + IsEnabled = (selectedListItems, listView) => + { + // Multiselect - disallow containers + return listView.SelectedItems.Any() + && listView.SelectedItems.All(i => !(i.Model is ILibraryContainerLink)); + } + }); + } + + public PluginInfo MetaData { get; } = new PluginInfo() + { + Name = "Part Sheets", + UUID = "580D8EF3-885C-4DD3-903A-4DB136AFD84B", + About = "A part sheet plugin", + Developer = "MatterHackers, Inc.", + Url = "https://www.matterhackers.com" + }; + } +} diff --git a/MatterControlLib/Library/PartSheets/PartsSheetCreator.cs b/MatterControlLib/Library/PartSheets/PartsSheetCreator.cs new file mode 100644 index 000000000..608b69726 --- /dev/null +++ b/MatterControlLib/Library/PartSheets/PartsSheetCreator.cs @@ -0,0 +1,327 @@ +/* +Copyright (c) 2017, 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.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MatterHackers.Agg; +using MatterHackers.Agg.Font; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.VertexSource; +using MatterHackers.DataConverters3D; +using MatterHackers.MatterControl.DataStorage; +using MatterHackers.MatterControl.Library; +using MatterHackers.VectorMath; +using PdfSharp.Drawing; +using PdfSharp.Pdf; + +namespace MatterHackers.MatterControl.Plugins +{ + public class PartsSheet + { + internal class PartImage + { + internal double xOffset = 0; + internal bool wasDrawn = false; + internal ImageBuffer image; + + public PartImage(ImageBuffer imageOfPart) + { + this.image = imageOfPart; + } + } + + private string pathAndFileToSaveTo; + + public event EventHandler DoneSaving; + + public event EventHandler UpdateRemainingItems; + + public class FileNameAndPresentationName + { + public string fileName; + public string presentationName; + + public FileNameAndPresentationName(string fileName, string presentationName) + { + this.fileName = fileName; + this.presentationName = presentationName; + } + } + + private IEnumerable itemSource; + private List partImagesToPrint = new List(); + private const double inchesPerMm = 0.0393701; + + private static bool currentlySaving = false; + + public Vector2 SheetSizeMM { get; set; } + + public Vector2 SheetSizeInches + { + get { return SheetSizeMM * inchesPerMm; } + set { SheetSizeMM = value / inchesPerMm; } + } + + public double PixelPerMM => inchesPerMm * SheetDpi; + + public BorderDouble PageMarginMM { get; } = new BorderDouble(10, 5); + + public BorderDouble PageMarginPixels => PageMarginMM * PixelPerMM; + + public double PartMarginMM { get; } = 2; + + public double PartMarginPixels => PartMarginMM * PixelPerMM; + + public double PartPaddingMM { get; } = 2; + + public double PartPaddingPixels => PartPaddingMM * PixelPerMM; + + public int SheetDpi { get; set; } + + public PartsSheet(IEnumerable itemSource, string pathAndFileToSaveTo) + { + this.pathAndFileToSaveTo = pathAndFileToSaveTo; + SheetDpi = 300; + SheetSizeInches = new Vector2(8.5, 11); + + this.itemSource = itemSource; + } + + private void OnDoneSaving() + { + DoneSaving?.Invoke(this, new StringEventArgs(Path.GetFileName("Saving to PDF"))); + } + + public async Task SaveSheets() + { + await Task.Run((Func)(async () => + { + currentlySaving = true; + // first create images for all the parts + foreach (var item in itemSource) + { + var object3D = await item.CreateContent(); + + var loadedMeshGroups = object3D.VisibleMeshes().ToList(); + if (loadedMeshGroups?.Count > 0) + { + AxisAlignedBoundingBox aabb = loadedMeshGroups[0].Mesh.GetAxisAlignedBoundingBox(loadedMeshGroups[0].WorldMatrix()); + + for (int i = 1; i < loadedMeshGroups.Count; i++) + { + aabb = AxisAlignedBoundingBox.Union(aabb, loadedMeshGroups[i].Mesh.GetAxisAlignedBoundingBox(loadedMeshGroups[i].WorldMatrix())); + } + + RectangleDouble bounds2D = new RectangleDouble(aabb.MinXYZ.X, aabb.MinXYZ.Y, aabb.MaxXYZ.X, aabb.MaxXYZ.Y); + double widthInMM = bounds2D.Width + PartMarginMM * 2; + double textSpaceMM = 5; + double heightMM = textSpaceMM + bounds2D.Height + PartMarginMM * 2; + + TypeFacePrinter typeFacePrinter = new TypeFacePrinter(item.Name, 28, Vector2.Zero, Justification.Center, Baseline.BoundsCenter); + double sizeOfNameX = typeFacePrinter.GetSize().X + PartMarginPixels * 2; + Vector2 sizeOfRender = new Vector2(widthInMM * PixelPerMM, heightMM * PixelPerMM); + + ImageBuffer imageOfPart = new ImageBuffer((int)(Math.Max(sizeOfNameX, sizeOfRender.X)), (int)(sizeOfRender.Y)); + typeFacePrinter.Origin = new Vector2(imageOfPart.Width / 2, (textSpaceMM / 2) * PixelPerMM); + + Graphics2D partGraphics2D = imageOfPart.NewGraphics2D(); + + RectangleDouble rectBounds = new RectangleDouble(0, 0, imageOfPart.Width, imageOfPart.Height); + double strokeWidth = .5 * PixelPerMM; + rectBounds.Inflate(-strokeWidth / 2); + RoundedRect rect = new RoundedRect(rectBounds, PartMarginMM * PixelPerMM); + partGraphics2D.Render(rect, Color.LightGray); + Stroke rectOutline = new Stroke(rect, strokeWidth); + partGraphics2D.Render(rectOutline, Color.DarkGray); + + foreach (var meshGroup in loadedMeshGroups) + { + PolygonMesh.Rendering.OrthographicZProjection.DrawTo(partGraphics2D, meshGroup.Mesh, meshGroup.WorldMatrix(), new Vector2(-bounds2D.Left + PartMarginMM, -bounds2D.Bottom + textSpaceMM + PartMarginMM), PixelPerMM, Color.Black); + } + partGraphics2D.Render(typeFacePrinter, Color.Black); + + partImagesToPrint.Add(new PartImage(imageOfPart)); + } + + UpdateRemainingItems?.Invoke(this, new StringEventArgs(item.Name)); + } + + partImagesToPrint.Sort(BiggestToLittlestImages); + + PdfDocument document = new PdfDocument(); + document.Info.Title = "MatterHackers Parts Sheet"; + document.Info.Author = "MatterHackers Inc."; + document.Info.Subject = "This is a list of the parts that are in a queue from MatterControl."; + document.Info.Keywords = "MatterControl, STL, 3D Printing"; + + int nextPartToPrintIndex = 0; + int plateNumber = 1; + bool done = false; + + while (!done && nextPartToPrintIndex < partImagesToPrint.Count) + { + PdfPage pdfPage = document.AddPage(); + CreateOnePage(plateNumber++, ref nextPartToPrintIndex, pdfPage); + } + + try + { + // save the final document + document.Save(pathAndFileToSaveTo); + + // Now try and open the document. This will launch whatever PDF viewer is on the system and ask it + // to show the file (at least on Windows). + Process.Start(pathAndFileToSaveTo); + } + catch (Exception) + { + } + + OnDoneSaving(); + currentlySaving = false; + })); + } + + private static int BiggestToLittlestImages(PartImage one, PartImage two) + { + return two.image.Height.CompareTo(one.image.Height); + } + + private void CreateOnePage(int plateNumber, ref int nextPartToPrintIndex, PdfPage pdfPage) + { + ImageBuffer plateInventoryImage = new ImageBuffer((int)(300 * 8.5), 300 * 11); + Graphics2D plateGraphics = plateInventoryImage.NewGraphics2D(); + double currentlyPrintingHeightPixels = PrintTopOfPage(plateInventoryImage, plateGraphics); + + Vector2 offset = new Vector2(PageMarginPixels.Left, currentlyPrintingHeightPixels); + double tallestHeight = 0; + List partsOnLine = new List(); + while (nextPartToPrintIndex < partImagesToPrint.Count) + { + ImageBuffer image = partImagesToPrint[nextPartToPrintIndex].image; + tallestHeight = Math.Max(tallestHeight, image.Height); + + if (partsOnLine.Count > 0 && offset.X + image.Width > plateInventoryImage.Width - PageMarginPixels.Right) + { + if (partsOnLine.Count == 1) + { + plateGraphics.Render(partsOnLine[0].image, plateInventoryImage.Width / 2 - partsOnLine[0].image.Width / 2, offset.Y - tallestHeight); + } + else + { + foreach (PartImage partToDraw in partsOnLine) + { + plateGraphics.Render(partToDraw.image, partToDraw.xOffset, offset.Y - tallestHeight); + } + } + + offset.X = PageMarginPixels.Left; + offset.Y -= (tallestHeight + PartPaddingPixels * 2); + tallestHeight = 0; + partsOnLine.Clear(); + if (offset.Y - image.Height < PageMarginPixels.Bottom) + { + break; + } + } + else + { + partImagesToPrint[nextPartToPrintIndex].xOffset = offset.X; + partsOnLine.Add(partImagesToPrint[nextPartToPrintIndex]); + //plateGraphics.Render(image, offset.x, offset.y - image.Height); + offset.X += image.Width + PartPaddingPixels * 2; + nextPartToPrintIndex++; + } + } + + // print the last line of parts + foreach (PartImage partToDraw in partsOnLine) + { + plateGraphics.Render(partToDraw.image, partToDraw.xOffset, offset.Y - tallestHeight); + } + + TypeFacePrinter printer = new TypeFacePrinter(string.Format("{0}", Path.GetFileNameWithoutExtension(pathAndFileToSaveTo)), 32, justification: Justification.Center); + printer.Origin = new Vector2(plateGraphics.DestImage.Width / 2, 110); + plateGraphics.Render(printer, Color.Black); + + printer = new TypeFacePrinter(string.Format("Page {0}", plateNumber), 28, justification: Justification.Center); + printer.Origin = new Vector2(plateGraphics.DestImage.Width / 2, 60); + plateGraphics.Render(printer, Color.Black); + + string folderToSavePrintsTo = Path.Combine(ApplicationDataStorage.Instance.ApplicationTempDataPath, "plateImages"); + string jpegFileName = Path.Combine(folderToSavePrintsTo, plateNumber.ToString() + ".jpeg"); + + Directory.CreateDirectory(folderToSavePrintsTo); + + AggContext.ImageIO.SaveImageData(jpegFileName, plateInventoryImage); + + XGraphics gfx = XGraphics.FromPdfPage(pdfPage); + XImage jpegImage = XImage.FromFile(jpegFileName); + //double width = jpegImage.PixelWidth * 72 / jpegImage.HorizontalResolution; + //double height = jpegImage.PixelHeight * 72 / jpegImage. .HorizontalResolution; + + gfx.DrawImage(jpegImage, 0, 0, pdfPage.Width, pdfPage.Height); + } + + private double PrintTopOfPage(ImageBuffer plateInventoryImage, Graphics2D plateGraphics) + { + plateGraphics.Clear(Color.White); + + double currentlyPrintingHeightPixels = plateInventoryImage.Height - PageMarginMM.Top * PixelPerMM; + + string logoPathAndFile = Path.Combine("Images", "PartSheetLogo.png"); + if (AggContext.StaticData.FileExists(logoPathAndFile)) + { + ImageBuffer logoImage = AggContext.StaticData.LoadImage(logoPathAndFile); + currentlyPrintingHeightPixels -= logoImage.Height; + plateGraphics.Render(logoImage, (plateInventoryImage.Width - logoImage.Width) / 2, currentlyPrintingHeightPixels); + } + + currentlyPrintingHeightPixels -= PartPaddingPixels; + + double underlineHeightMM = 1; + + var lineBounds = new RectangleDouble(0, 0, plateInventoryImage.Width - PageMarginPixels.Left * 2, underlineHeightMM * PixelPerMM); + lineBounds.Offset(PageMarginPixels.Left, currentlyPrintingHeightPixels - lineBounds.Height); + plateGraphics.FillRectangle(lineBounds, Color.Black); + + return currentlyPrintingHeightPixels - (lineBounds.Height + PartPaddingPixels); + } + + public static bool IsSaving() + { + return currentlySaving; + } + } +} \ No newline at end of file diff --git a/MatterControlLib/MatterControlLib.csproj b/MatterControlLib/MatterControlLib.csproj index 5b5c01ac3..6fcebc6d8 100644 --- a/MatterControlLib/MatterControlLib.csproj +++ b/MatterControlLib/MatterControlLib.csproj @@ -90,6 +90,7 @@ + diff --git a/Submodules/MatterSlice b/Submodules/MatterSlice index 2bca5eccc..412e81b6b 160000 --- a/Submodules/MatterSlice +++ b/Submodules/MatterSlice @@ -1 +1 @@ -Subproject commit 2bca5eccc42ebd622b391f882c7b933c6d0a3bfc +Subproject commit 412e81b6b44e306cabc254e362fe5e5ddac6d5b6 diff --git a/Submodules/agg-sharp b/Submodules/agg-sharp index 6715273da..41b03f877 160000 --- a/Submodules/agg-sharp +++ b/Submodules/agg-sharp @@ -1 +1 @@ -Subproject commit 6715273dabb841c79edeb2324fac099b054331a5 +Subproject commit 41b03f877385c1978292ef3a7f2d215715363e94