Moved the primitives contain into MatterControlLib
moved part sheet creator into MatterControlLib moved some other experimental parts to MatterControlLib
This commit is contained in:
parent
f982a60ce6
commit
ef17f38bb7
20 changed files with 3547 additions and 2 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
};
|
||||
}
|
||||
}
|
||||
108
MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs
Normal file
108
MatterControlLib/DesignTools/EditorTools/LithophanePlugin.cs
Normal 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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
82
MatterControlLib/Library/PartSheets/PartSheetPlugin.cs
Normal file
82
MatterControlLib/Library/PartSheets/PartSheetPlugin.cs
Normal 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"
|
||||
};
|
||||
}
|
||||
}
|
||||
327
MatterControlLib/Library/PartSheets/PartsSheetCreator.cs
Normal file
327
MatterControlLib/Library/PartSheets/PartsSheetCreator.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue