Moved the primitives contain into MatterControlLib

moved part sheet creator into MatterControlLib
moved some other experimental parts to MatterControlLib
This commit is contained in:
Lars Brubaker 2019-04-02 17:13:07 -07:00
parent f982a60ce6
commit ef17f38bb7
20 changed files with 3547 additions and 2 deletions

View file

@ -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<GeneratorItem>()
{
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);
}
}
}
}

View file

@ -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<ChairFootObject3D> 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<RingObject3D>().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;
}
}
}
}

View file

@ -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<ImageObject3D>().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<ImageCoinObject3D> 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<List<IntPoint>>();
// 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;
}
}
}

View file

@ -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<ElbowObject3D> 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);
}
}
}

View file

@ -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<IObject3D>() { fin1, fin2 });
}
override abstract public Task Rebuild();
}
}

View file

@ -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<GeneratorItem>()
{
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);
}
}
}
}

View file

@ -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<TeeObject3D> 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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<ILibraryObject3D>())
{
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"
};
}
}

View file

@ -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<IObject3D>().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<Type> { 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"
};
}
}

View file

@ -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;
}
/// <summary>
/// Measure the difference between two angles.
/// </summary>
/// <param name="startAngle"></param>
/// <param name="endAngle"></param>
/// <returns>The angle from a to b. If A = 2 and B = 0 return 2.
/// If A = 0 and B = 2 return -2.</returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="selectedItem"></param>
/// <returns></returns>
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);
}
}
}
}
}

View file

@ -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<InteractionVolume> Create(IInteractionVolumeContext context)
{
// X, Y, Z RotateCornerControls
return new[]
{
new RotateCornerControl(context, 0),
new RotateCornerControl(context, 1),
new RotateCornerControl(context, 2)
};
}
}
}

View file

@ -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<Vector2> lines = new List<Vector2>();
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;
}
}
}
}
}

View file

@ -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<InteractionVolume> Create(IInteractionVolumeContext context)
{
return new List<InteractionVolume>
{
new ScaleTopControl(context),
new ScaleCornerControl(context, 0),
new ScaleCornerControl(context, 1),
new ScaleCornerControl(context, 2),
new ScaleCornerControl(context, 3)
};
}
}
}

View file

@ -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<Vector2> lines = new List<Vector2>();
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);
}
}
}
}
}

View file

@ -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<ILibraryAssetStream>();
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"
};
}
}

View file

@ -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<ILibraryAssetStream> itemSource;
private List<PartImage> partImagesToPrint = new List<PartImage>();
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<ILibraryAssetStream> 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<Task>)(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<PartImage> partsOnLine = new List<PartImage>();
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;
}
}
}

View file

@ -90,6 +90,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="PDFsharp" Version="1.50.5147" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="Zeroconf" Version="3.0.30" />
</ItemGroup>

@ -1 +1 @@
Subproject commit 2bca5eccc42ebd622b391f882c7b933c6d0a3bfc
Subproject commit 412e81b6b44e306cabc254e362fe5e5ddac6d5b6

@ -1 +1 @@
Subproject commit 6715273dabb841c79edeb2324fac099b054331a5
Subproject commit 41b03f877385c1978292ef3a7f2d215715363e94