Restructure project layout to flatten directory structure
Move all projects from Source/ to repository root for simpler navigation. - Remove External/ dependency DLLs (will use NuGet packages) - Remove Installer/ NSIS script - Replace PartCollection/PlateCollection with ObservableList - Add packages.config for NuGet dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
186
OpenNest.Core/Align.cs
Normal file
186
OpenNest.Core/Align.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Align
|
||||
{
|
||||
public static void Vertically(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(fixedEntity.BoundingBox.Center.X - movableEntity.BoundingBox.Center.X, 0);
|
||||
}
|
||||
|
||||
public static void Vertically(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Vertically(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Vertically(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(fixedPart.BoundingBox.Center.X - movablePart.BoundingBox.Center.X, 0);
|
||||
}
|
||||
|
||||
public static void Vertically(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Vertically(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Horizontally(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(0, fixedEntity.BoundingBox.Center.Y - movableEntity.BoundingBox.Center.Y);
|
||||
}
|
||||
|
||||
public static void Horizontally(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Horizontally(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Horizontally(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(0, fixedPart.BoundingBox.Center.Y - movablePart.BoundingBox.Center.Y);
|
||||
}
|
||||
|
||||
public static void Horizontally(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Horizontally(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Left(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(fixedEntity.BoundingBox.Left - movableEntity.BoundingBox.Left, 0);
|
||||
}
|
||||
|
||||
public static void Left(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Left(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Left(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(fixedPart.BoundingBox.Left - movablePart.BoundingBox.Left, 0);
|
||||
}
|
||||
|
||||
public static void Left(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Left(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Right(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(fixedEntity.BoundingBox.Right - movableEntity.BoundingBox.Right, 0);
|
||||
}
|
||||
|
||||
public static void Right(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Right(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Right(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(fixedPart.BoundingBox.Right - movablePart.BoundingBox.Right, 0);
|
||||
}
|
||||
|
||||
public static void Right(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Right(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Top(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(0, fixedEntity.BoundingBox.Top - movableEntity.BoundingBox.Top);
|
||||
}
|
||||
|
||||
public static void Top(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Top(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Top(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(0, fixedPart.BoundingBox.Top - movablePart.BoundingBox.Top);
|
||||
}
|
||||
|
||||
public static void Top(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Top(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Bottom(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(0, fixedEntity.BoundingBox.Bottom - movableEntity.BoundingBox.Bottom);
|
||||
}
|
||||
|
||||
public static void Bottom(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Bottom(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Bottom(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(0, fixedPart.BoundingBox.Bottom - movablePart.BoundingBox.Bottom);
|
||||
}
|
||||
|
||||
public static void Bottom(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Bottom(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void EvenlyDistributeHorizontally(List<Part> parts)
|
||||
{
|
||||
if (parts.Count < 3)
|
||||
return;
|
||||
|
||||
var list = new List<Part>(parts);
|
||||
list.Sort((p1, p2) => p1.BoundingBox.Center.X.CompareTo(p2.BoundingBox.Center.X));
|
||||
|
||||
var lastIndex = list.Count - 1;
|
||||
|
||||
var first = list[0];
|
||||
var last = list[lastIndex];
|
||||
|
||||
var start = first.BoundingBox.Center.X;
|
||||
var end = last.BoundingBox.Center.X;
|
||||
var diff = end - start;
|
||||
|
||||
var spacing = diff / lastIndex;
|
||||
|
||||
for (int i = 1; i < lastIndex; ++i)
|
||||
{
|
||||
var part = list[i];
|
||||
var newX = start + i * spacing;
|
||||
var curX = part.BoundingBox.Center.X;
|
||||
|
||||
part.Offset(newX - curX, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EvenlyDistributeVertically(List<Part> parts)
|
||||
{
|
||||
if (parts.Count < 3)
|
||||
return;
|
||||
|
||||
var list = new List<Part>(parts);
|
||||
list.Sort((p1, p2) => p1.BoundingBox.Center.Y.CompareTo(p2.BoundingBox.Center.Y));
|
||||
|
||||
var lastIndex = list.Count - 1;
|
||||
|
||||
var first = list[0];
|
||||
var last = list[lastIndex];
|
||||
|
||||
var start = first.BoundingBox.Center.Y;
|
||||
var end = last.BoundingBox.Center.Y;
|
||||
var diff = end - start;
|
||||
|
||||
var spacing = diff / lastIndex;
|
||||
|
||||
for (int i = 1; i < lastIndex; ++i)
|
||||
{
|
||||
var part = list[i];
|
||||
var newX = start + i * spacing;
|
||||
var curX = part.BoundingBox.Center.Y;
|
||||
|
||||
part.Offset(0, newX - curX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
OpenNest.Core/AlignType.cs
Normal file
15
OpenNest.Core/AlignType.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum AlignType
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
Horizontally,
|
||||
Vertically,
|
||||
EvenlySpaceHorizontally,
|
||||
EvenlySpaceVertically
|
||||
}
|
||||
}
|
||||
125
OpenNest.Core/Angle.cs
Normal file
125
OpenNest.Core/Angle.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Angle
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of radians equal to 1 degree.
|
||||
/// </summary>
|
||||
public const double RadPerDeg = Math.PI / 180.0;
|
||||
|
||||
/// <summary>
|
||||
/// Number of degrees equal to 1 radian.
|
||||
/// </summary>
|
||||
public const double DegPerRad = 180.0 / Math.PI;
|
||||
|
||||
/// <summary>
|
||||
/// Half of PI.
|
||||
/// </summary>
|
||||
public const double HalfPI = Math.PI * 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// 2 x PI
|
||||
/// </summary>
|
||||
public const double TwoPI = Math.PI * 2.0;
|
||||
|
||||
/// <summary>
|
||||
/// Converts radians to degrees.
|
||||
/// </summary>
|
||||
/// <param name="radians"></param>
|
||||
/// <returns></returns>
|
||||
public static double ToDegrees(double radians)
|
||||
{
|
||||
return DegPerRad * radians;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts degrees to radians.
|
||||
/// </summary>
|
||||
/// <param name="degrees"></param>
|
||||
/// <returns></returns>
|
||||
public static double ToRadians(double degrees)
|
||||
{
|
||||
return RadPerDeg * degrees;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes an angle.
|
||||
/// The normalized angle will be in the range from 0 to TwoPI,
|
||||
/// where TwoPI itself is not included.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <returns></returns>
|
||||
public static double NormalizeRad(double angle)
|
||||
{
|
||||
double r = angle % Angle.TwoPI;
|
||||
return r < 0 ? Angle.TwoPI + r : r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes an angle.
|
||||
/// The normalized angle will be in the range from 0 to 360,
|
||||
/// where 360 itself is not included.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <returns></returns>
|
||||
public static double NormalizeDeg(double angle)
|
||||
{
|
||||
double r = angle % 360.0;
|
||||
return r < 0 ? 360.0 + r : r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given angle is between the two specified angles (a1 & a2).
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="a1"></param>
|
||||
/// <param name="a2"></param>
|
||||
/// <param name="reversed"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBetweenRad(double angle, double a1, double a2, bool reversed = false)
|
||||
{
|
||||
if (reversed)
|
||||
Generic.Swap(ref a1, ref a2);
|
||||
|
||||
var diff = Angle.NormalizeRad(a2 - a1);
|
||||
|
||||
// full circle
|
||||
if (a2.IsEqualTo(a1))
|
||||
return true;
|
||||
|
||||
a1 = Angle.NormalizeRad(angle - a1);
|
||||
a2 = Angle.NormalizeRad(a2 - angle);
|
||||
|
||||
return diff >= a1 - Tolerance.Epsilon ||
|
||||
diff >= a2 - Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given angle is between the two specified angles (a1 & a2).
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="a1"></param>
|
||||
/// <param name="a2"></param>
|
||||
/// <param name="reversed"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBetweenDeg(double angle, double a1, double a2, bool reversed = false)
|
||||
{
|
||||
if (reversed)
|
||||
Generic.Swap(ref a1, ref a2);
|
||||
|
||||
var diff = Angle.NormalizeRad(a2 - a1);
|
||||
|
||||
// full circle
|
||||
if (a2.IsEqualTo(a1))
|
||||
return true;
|
||||
|
||||
a1 = Angle.NormalizeRad(angle - a1);
|
||||
a2 = Angle.NormalizeRad(a2 - angle);
|
||||
|
||||
return diff >= a1 - Tolerance.Epsilon ||
|
||||
diff >= a2 - Tolerance.Epsilon;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
OpenNest.Core/BoundingBox.cs
Normal file
77
OpenNest.Core/BoundingBox.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class BoundingBox
|
||||
{
|
||||
public static Box GetBoundingBox(this IList<Box> boxes)
|
||||
{
|
||||
if (boxes.Count == 0)
|
||||
return Box.Empty;
|
||||
|
||||
double minX = boxes[0].X;
|
||||
double minY = boxes[0].Y;
|
||||
double maxX = boxes[0].X + boxes[0].Width;
|
||||
double maxY = boxes[0].Y + boxes[0].Height;
|
||||
|
||||
foreach (var box in boxes)
|
||||
{
|
||||
if (box.Left < minX) minX = box.Left;
|
||||
if (box.Right > maxX) maxX = box.Right;
|
||||
if (box.Bottom < minY) minY = box.Bottom;
|
||||
if (box.Top > maxY) maxY = box.Top;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public static Box GetBoundingBox(this IList<Vector> pts)
|
||||
{
|
||||
if (pts.Count == 0)
|
||||
return Box.Empty;
|
||||
|
||||
var first = pts[0];
|
||||
var minX = first.X;
|
||||
var maxX = first.X;
|
||||
var minY = first.Y;
|
||||
var maxY = first.Y;
|
||||
|
||||
for (int i = 1; i < pts.Count; ++i)
|
||||
{
|
||||
var vertex = pts[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public static Box GetBoundingBox(this IEnumerable<IBoundable> items)
|
||||
{
|
||||
var first = items.FirstOrDefault();
|
||||
|
||||
if (first == null)
|
||||
return Box.Empty;
|
||||
|
||||
double left = first.Left;
|
||||
double bottom = first.Bottom;
|
||||
double right = first.Right;
|
||||
double top = first.Top;
|
||||
|
||||
foreach (var box in items)
|
||||
{
|
||||
if (box.Left < left) left = box.Left;
|
||||
if (box.Right > right) right = box.Right;
|
||||
if (box.Bottom < bottom) bottom = box.Bottom;
|
||||
if (box.Top > top) top = box.Top;
|
||||
}
|
||||
|
||||
return new Box(left, bottom, right - left, top - bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
206
OpenNest.Core/Box.cs
Normal file
206
OpenNest.Core/Box.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Box
|
||||
{
|
||||
public static readonly Box Empty = new Box();
|
||||
|
||||
public Box()
|
||||
: this(0, 0, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public Box(double x, double y, double w, double h)
|
||||
{
|
||||
Location = new Vector(x, y);
|
||||
Width = w;
|
||||
Height = h;
|
||||
}
|
||||
|
||||
public Vector Location;
|
||||
|
||||
public Vector Center
|
||||
{
|
||||
get { return new Vector(X + Width * 0.5, Y + Height * 0.5); }
|
||||
}
|
||||
|
||||
public Size Size;
|
||||
|
||||
public double X
|
||||
{
|
||||
get { return Location.X; }
|
||||
set { Location.X = value; }
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get { return Location.Y; }
|
||||
set { Location.Y = value; }
|
||||
}
|
||||
|
||||
public double Width
|
||||
{
|
||||
get { return Size.Width; }
|
||||
set { Size.Width = value; }
|
||||
}
|
||||
|
||||
public double Height
|
||||
{
|
||||
get { return Size.Height; }
|
||||
set { Size.Height = value; }
|
||||
}
|
||||
|
||||
public void MoveTo(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public void MoveTo(Vector pt)
|
||||
{
|
||||
X = pt.X;
|
||||
Y = pt.Y;
|
||||
}
|
||||
|
||||
public void Offset(double x, double y)
|
||||
{
|
||||
X += x;
|
||||
Y += y;
|
||||
}
|
||||
|
||||
public void Offset(Vector voffset)
|
||||
{
|
||||
Location += voffset;
|
||||
}
|
||||
|
||||
public double Left
|
||||
{
|
||||
get { return X; }
|
||||
}
|
||||
|
||||
public double Right
|
||||
{
|
||||
get { return X + Width; }
|
||||
}
|
||||
|
||||
public double Top
|
||||
{
|
||||
get { return Y + Height; }
|
||||
}
|
||||
|
||||
public double Bottom
|
||||
{
|
||||
get { return Y; }
|
||||
}
|
||||
|
||||
public double Area()
|
||||
{
|
||||
return Width * Height;
|
||||
}
|
||||
|
||||
public double Perimeter()
|
||||
{
|
||||
return Width * 2 + Height * 2;
|
||||
}
|
||||
|
||||
public bool Intersects(Box box)
|
||||
{
|
||||
if (Left >= box.Right) return false;
|
||||
if (Right <= box.Left) return false;
|
||||
if (Top <= box.Bottom) return false;
|
||||
if (Bottom >= box.Top) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Intersects(Box box, out Box intersectingBox)
|
||||
{
|
||||
if (!Intersects(box))
|
||||
{
|
||||
intersectingBox = Box.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
var left = Left < box.Left ? box.Left : Left;
|
||||
var right = Right < box.Right ? Right : box.Right;
|
||||
|
||||
var bottom = Bottom < box.Bottom ? box.Bottom : Bottom;
|
||||
var top = Top < box.Top ? Top : box.Top;
|
||||
|
||||
intersectingBox = new Box(left, bottom, right - left, top - bottom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Box box)
|
||||
{
|
||||
if (box.Top > Top) return false;
|
||||
if (box.Left < Left) return false;
|
||||
if (box.Right > Right) return false;
|
||||
if (box.Bottom < Bottom) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Vector pt)
|
||||
{
|
||||
return pt.X >= Left - Tolerance.Epsilon && pt.X <= Right + Tolerance.Epsilon
|
||||
&& pt.Y >= Bottom - Tolerance.Epsilon && pt.Y <= Top + Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
public bool IsHorizontalTo(Box box)
|
||||
{
|
||||
return Bottom > box.Top || Top < box.Bottom;
|
||||
}
|
||||
|
||||
public bool IsHorizontalTo(Box box, out RelativePosition pos)
|
||||
{
|
||||
if (Bottom >= box.Top || Top <= box.Bottom)
|
||||
{
|
||||
pos = RelativePosition.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Left >= box.Right)
|
||||
pos = RelativePosition.Right;
|
||||
else if (Right <= box.Left)
|
||||
pos = RelativePosition.Left;
|
||||
else
|
||||
pos = RelativePosition.Intersecting;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsVerticalTo(Box box)
|
||||
{
|
||||
return Left > box.Right || Right < box.Left;
|
||||
}
|
||||
|
||||
public bool IsVerticalTo(Box box, out RelativePosition pos)
|
||||
{
|
||||
if (Left >= box.Right || Right <= box.Left)
|
||||
{
|
||||
pos = RelativePosition.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Bottom >= box.Top)
|
||||
pos = RelativePosition.Top;
|
||||
else if (Top <= box.Bottom)
|
||||
pos = RelativePosition.Bottom;
|
||||
else
|
||||
pos = RelativePosition.Intersecting;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Box Offset(double d)
|
||||
{
|
||||
return new Box(X - d, Y - d, Width + d * 2, Height + d * 2);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[Box: X={0}, Y={1}, Width={2}, Height={3}]", X, Y, Width, Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
OpenNest.Core/BoxSplitter.cs
Normal file
57
OpenNest.Core/BoxSplitter.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class BoxSplitter
|
||||
{
|
||||
public static Box SplitTop(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = small.Top;
|
||||
var w = large.Width;
|
||||
var h = large.Top - y;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitLeft(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = large.Bottom;
|
||||
var w = small.Left - x;
|
||||
var h = large.Height;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitBottom(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = large.Bottom;
|
||||
var w = large.Width;
|
||||
var h = small.Top - y;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitRight(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = small.Right;
|
||||
var y = large.Bottom;
|
||||
var w = large.Right - x;
|
||||
var h = large.Height;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
OpenNest.Core/CNC/CircularMove.cs
Normal file
89
OpenNest.Core/CNC/CircularMove.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class CircularMove : Motion
|
||||
{
|
||||
public CircularMove()
|
||||
{
|
||||
}
|
||||
|
||||
public CircularMove(double x, double y, double i, double j, RotationType rotation = RotationType.CCW)
|
||||
: this(new Vector(x, y), new Vector(i, j), rotation)
|
||||
{
|
||||
}
|
||||
|
||||
public CircularMove(Vector endPoint, Vector centerPoint, RotationType rotation = RotationType.CCW)
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
CenterPoint = centerPoint;
|
||||
Rotation = rotation;
|
||||
Layer = LayerType.Cut;
|
||||
}
|
||||
|
||||
public LayerType Layer { get; set; }
|
||||
|
||||
public RotationType Rotation { get; set; }
|
||||
|
||||
public Vector CenterPoint { get; set; }
|
||||
|
||||
public double Radius
|
||||
{
|
||||
get { return CenterPoint.DistanceTo(EndPoint); }
|
||||
}
|
||||
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
base.Rotate(angle);
|
||||
CenterPoint = CenterPoint.Rotate(angle);
|
||||
}
|
||||
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
base.Rotate(angle, origin);
|
||||
CenterPoint = CenterPoint.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
base.Offset(x, y);
|
||||
CenterPoint = new Vector(CenterPoint.X + x, CenterPoint.Y + y);
|
||||
}
|
||||
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
base.Offset(voffset);
|
||||
CenterPoint += voffset;
|
||||
}
|
||||
|
||||
public override CodeType Type
|
||||
{
|
||||
get { return CodeType.CircularMove; }
|
||||
}
|
||||
|
||||
public override ICode Clone()
|
||||
{
|
||||
return new CircularMove(EndPoint, CenterPoint, Rotation)
|
||||
{
|
||||
Layer = Layer
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(DefaultDecimalPlaces);
|
||||
}
|
||||
|
||||
public override string ToString(int decimalPlaces)
|
||||
{
|
||||
var dp = "N" + decimalPlaces;
|
||||
var x = EndPoint.X.ToString(dp);
|
||||
var y = EndPoint.Y.ToString(dp);
|
||||
var i = CenterPoint.X.ToString(dp);
|
||||
var j = CenterPoint.Y.ToString(dp);
|
||||
|
||||
return Rotation == RotationType.CW ?
|
||||
string.Format("G02 X{0} Y{1} I{2} J{3}", x, y, i, j) :
|
||||
string.Format("G03 X{0} Y{1} I{2} J{3}", x, y, i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
OpenNest.Core/CNC/CodeType.cs
Normal file
14
OpenNest.Core/CNC/CodeType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum CodeType
|
||||
{
|
||||
CircularMove,
|
||||
Comment,
|
||||
LinearMove,
|
||||
RapidMove,
|
||||
SetFeedrate,
|
||||
SetKerf,
|
||||
SubProgramCall
|
||||
}
|
||||
}
|
||||
31
OpenNest.Core/CNC/Comment.cs
Normal file
31
OpenNest.Core/CNC/Comment.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class Comment : ICode
|
||||
{
|
||||
public Comment()
|
||||
{
|
||||
}
|
||||
|
||||
public Comment(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public CodeType Type
|
||||
{
|
||||
get { return CodeType.Comment; }
|
||||
}
|
||||
|
||||
public ICode Clone()
|
||||
{
|
||||
return new Comment(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ':' + Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
OpenNest.Core/CNC/Feedrate.cs
Normal file
35
OpenNest.Core/CNC/Feedrate.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class Feedrate : ICode
|
||||
{
|
||||
public const int UseDefault = -1;
|
||||
|
||||
public const int UseMax = -2;
|
||||
|
||||
public Feedrate()
|
||||
{
|
||||
}
|
||||
|
||||
public Feedrate(double value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public double Value { get; set; }
|
||||
|
||||
public CodeType Type
|
||||
{
|
||||
get { return CodeType.SetFeedrate; }
|
||||
}
|
||||
|
||||
public ICode Clone()
|
||||
{
|
||||
return new Feedrate(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("F{0}", Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
OpenNest.Core/CNC/ICode.cs
Normal file
9
OpenNest.Core/CNC/ICode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public interface ICode
|
||||
{
|
||||
CodeType Type { get; }
|
||||
|
||||
ICode Clone();
|
||||
}
|
||||
}
|
||||
39
OpenNest.Core/CNC/Kerf.cs
Normal file
39
OpenNest.Core/CNC/Kerf.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class Kerf : ICode
|
||||
{
|
||||
public Kerf(KerfType kerf = KerfType.Left)
|
||||
{
|
||||
Value = kerf;
|
||||
}
|
||||
|
||||
public KerfType Value { get; set; }
|
||||
|
||||
public CodeType Type
|
||||
{
|
||||
get { return CodeType.SetKerf; }
|
||||
}
|
||||
|
||||
public ICode Clone()
|
||||
{
|
||||
return new Kerf(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (Value)
|
||||
{
|
||||
case KerfType.Left:
|
||||
return "G41";
|
||||
|
||||
case KerfType.Right:
|
||||
return "G42";
|
||||
|
||||
case KerfType.None:
|
||||
return "G40";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
OpenNest.Core/CNC/KerfType.cs
Normal file
10
OpenNest.Core/CNC/KerfType.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum KerfType
|
||||
{
|
||||
None,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
12
OpenNest.Core/CNC/LayerType.cs
Normal file
12
OpenNest.Core/CNC/LayerType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum LayerType
|
||||
{
|
||||
Display,
|
||||
Scribe,
|
||||
Cut,
|
||||
Leadin,
|
||||
Leadout
|
||||
}
|
||||
}
|
||||
47
OpenNest.Core/CNC/LinearMove.cs
Normal file
47
OpenNest.Core/CNC/LinearMove.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class LinearMove : Motion
|
||||
{
|
||||
public LinearMove()
|
||||
: this(new Vector())
|
||||
{
|
||||
}
|
||||
|
||||
public LinearMove(double x, double y)
|
||||
: this(new Vector(x, y))
|
||||
{
|
||||
}
|
||||
|
||||
public LinearMove(Vector endPoint)
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
Layer = LayerType.Cut;
|
||||
}
|
||||
|
||||
public LayerType Layer { get; set; }
|
||||
|
||||
public override CodeType Type
|
||||
{
|
||||
get { return CodeType.LinearMove; }
|
||||
}
|
||||
|
||||
public override ICode Clone()
|
||||
{
|
||||
return new LinearMove(EndPoint)
|
||||
{
|
||||
Layer = Layer
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(DefaultDecimalPlaces);
|
||||
}
|
||||
|
||||
public override string ToString(int decimalPlaces)
|
||||
{
|
||||
var dp = "N" + decimalPlaces;
|
||||
return string.Format("G01 X{0} Y{1}", EndPoint.X.ToString(dp), EndPoint.Y.ToString(dp));
|
||||
}
|
||||
}
|
||||
}
|
||||
9
OpenNest.Core/CNC/Mode.cs
Normal file
9
OpenNest.Core/CNC/Mode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum Mode
|
||||
{
|
||||
Absolute,
|
||||
Incremental
|
||||
}
|
||||
}
|
||||
45
OpenNest.Core/CNC/Motion.cs
Normal file
45
OpenNest.Core/CNC/Motion.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public abstract class Motion : ICode
|
||||
{
|
||||
protected const int DefaultDecimalPlaces = 4;
|
||||
|
||||
public Vector EndPoint { get; set; }
|
||||
|
||||
public bool UseExactStop { get; set; }
|
||||
|
||||
public int Feedrate { get; set; }
|
||||
|
||||
protected Motion()
|
||||
{
|
||||
Feedrate = CNC.Feedrate.UseDefault;
|
||||
}
|
||||
|
||||
public virtual void Rotate(double angle)
|
||||
{
|
||||
EndPoint = EndPoint.Rotate(angle);
|
||||
}
|
||||
|
||||
public virtual void Rotate(double angle, Vector origin)
|
||||
{
|
||||
EndPoint = EndPoint.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
public virtual void Offset(double x, double y)
|
||||
{
|
||||
EndPoint = new Vector(EndPoint.X + x, EndPoint.Y + y);
|
||||
}
|
||||
|
||||
public virtual void Offset(Vector voffset)
|
||||
{
|
||||
EndPoint += voffset;
|
||||
}
|
||||
|
||||
public abstract CodeType Type { get; }
|
||||
|
||||
public abstract ICode Clone();
|
||||
|
||||
public abstract string ToString(int decimalPlaces);
|
||||
}
|
||||
}
|
||||
477
OpenNest.Core/CNC/Program.cs
Normal file
477
OpenNest.Core/CNC/Program.cs
Normal file
@@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public List<ICode> Codes;
|
||||
|
||||
private Mode mode;
|
||||
|
||||
public Program(Mode mode = Mode.Absolute)
|
||||
{
|
||||
Codes = new List<ICode>();
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public Mode Mode
|
||||
{
|
||||
get { return mode; }
|
||||
set
|
||||
{
|
||||
if (value == Mode.Absolute)
|
||||
SetModeAbs();
|
||||
else
|
||||
SetModeInc();
|
||||
}
|
||||
}
|
||||
|
||||
public double Rotation { get; protected set; }
|
||||
|
||||
private void SetModeInc()
|
||||
{
|
||||
if (mode == Mode.Incremental)
|
||||
return;
|
||||
|
||||
ConvertMode.ToIncremental(this);
|
||||
|
||||
mode = Mode.Incremental;
|
||||
}
|
||||
|
||||
private void SetModeAbs()
|
||||
{
|
||||
if (mode == Mode.Absolute)
|
||||
return;
|
||||
|
||||
ConvertMode.ToAbsolute(this);
|
||||
|
||||
mode = Mode.Absolute;
|
||||
}
|
||||
|
||||
public virtual void Rotate(double angle)
|
||||
{
|
||||
var mode = Mode;
|
||||
|
||||
SetModeAbs();
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
|
||||
if (subpgm.Program != null)
|
||||
subpgm.Program.Rotate(angle);
|
||||
}
|
||||
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var code2 = (Motion)code;
|
||||
|
||||
code2.Rotate(angle);
|
||||
}
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
SetModeInc();
|
||||
|
||||
Rotation = Angle.NormalizeRad(Rotation + angle);
|
||||
}
|
||||
|
||||
public virtual void Rotate(double angle, Vector origin)
|
||||
{
|
||||
var mode = Mode;
|
||||
|
||||
SetModeAbs();
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
|
||||
if (subpgm.Program != null)
|
||||
subpgm.Program.Rotate(angle);
|
||||
}
|
||||
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var code2 = (Motion)code;
|
||||
|
||||
code2.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
SetModeInc();
|
||||
|
||||
Rotation = Angle.NormalizeRad(Rotation + angle);
|
||||
}
|
||||
|
||||
public virtual void Offset(double x, double y)
|
||||
{
|
||||
var mode = Mode;
|
||||
|
||||
SetModeAbs();
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var code2 = (Motion)code;
|
||||
|
||||
code2.Offset(x, y);
|
||||
}
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
SetModeInc();
|
||||
}
|
||||
|
||||
public virtual void Offset(Vector voffset)
|
||||
{
|
||||
var mode = Mode;
|
||||
|
||||
SetModeAbs();
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var code2 = (Motion)code;
|
||||
|
||||
code2.Offset(voffset);
|
||||
}
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
SetModeInc();
|
||||
}
|
||||
|
||||
public void LineTo(double x, double y)
|
||||
{
|
||||
Codes.Add(new LinearMove(x, y));
|
||||
}
|
||||
|
||||
public void LineTo(Vector pt)
|
||||
{
|
||||
Codes.Add(new LinearMove(pt));
|
||||
}
|
||||
|
||||
public void MoveTo(double x, double y)
|
||||
{
|
||||
Codes.Add(new RapidMove(x, y));
|
||||
}
|
||||
|
||||
public void MoveTo(Vector pt)
|
||||
{
|
||||
Codes.Add(new RapidMove(pt));
|
||||
}
|
||||
|
||||
public void ArcTo(double x, double y, double i, double j, RotationType rotation)
|
||||
{
|
||||
Codes.Add(new CircularMove(x, y, i, j, rotation));
|
||||
}
|
||||
|
||||
public void ArcTo(Vector endpt, Vector center, RotationType rotation)
|
||||
{
|
||||
Codes.Add(new CircularMove(endpt, center, rotation));
|
||||
}
|
||||
|
||||
public void AddSubProgram(Program program)
|
||||
{
|
||||
Codes.Add(new SubProgramCall(program, program.Rotation));
|
||||
}
|
||||
|
||||
public ICode this[int index]
|
||||
{
|
||||
get { return Codes[index]; }
|
||||
set { Codes[index] = value; }
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return Codes.Count; }
|
||||
}
|
||||
|
||||
public void Merge(Program pgm)
|
||||
{
|
||||
// Set the program to be merged to the same Mode as the current.
|
||||
pgm.Mode = this.Mode;
|
||||
|
||||
if (Mode == Mode.Absolute)
|
||||
{
|
||||
bool isRapid = false;
|
||||
|
||||
// Check if the first motion code is a rapid move
|
||||
foreach (var code in pgm.Codes)
|
||||
{
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var motion = (Motion)code;
|
||||
|
||||
isRapid = motion.GetType() == typeof(RapidMove);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the first motion code is not a rapid, move to the origin.
|
||||
if (!isRapid)
|
||||
MoveTo(0, 0);
|
||||
|
||||
Codes.AddRange(pgm.Codes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Codes.AddRange(pgm.Codes);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector EndPoint()
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case Mode.Absolute:
|
||||
{
|
||||
for (int i = Codes.Count; i >= 0; --i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion == null) continue;
|
||||
|
||||
return motion.EndPoint;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode.Incremental:
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion == null) continue;
|
||||
|
||||
pos += motion.EndPoint;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector(0, 0);
|
||||
}
|
||||
|
||||
public Box BoundingBox()
|
||||
{
|
||||
var origin = new Vector(0, 0);
|
||||
return BoundingBox(ref origin);
|
||||
}
|
||||
|
||||
private Box BoundingBox(ref Vector pos)
|
||||
{
|
||||
double minX = 0.0;
|
||||
double minY = 0.0;
|
||||
double maxX = 0.0;
|
||||
double maxY = 0.0;
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
switch (code.Type)
|
||||
{
|
||||
case CodeType.LinearMove:
|
||||
{
|
||||
var line = (LinearMove)code;
|
||||
var pt = Mode == Mode.Absolute ?
|
||||
line.EndPoint :
|
||||
line.EndPoint + pos;
|
||||
|
||||
if (pt.X > maxX)
|
||||
maxX = pt.X;
|
||||
else if (pt.X < minX)
|
||||
minX = pt.X;
|
||||
|
||||
if (pt.Y > maxY)
|
||||
maxY = pt.Y;
|
||||
else if (pt.Y < minY)
|
||||
minY = pt.Y;
|
||||
|
||||
pos = pt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.RapidMove:
|
||||
{
|
||||
var line = (RapidMove)code;
|
||||
var pt = Mode == Mode.Absolute
|
||||
? line.EndPoint
|
||||
: line.EndPoint + pos;
|
||||
|
||||
if (pt.X > maxX)
|
||||
maxX = pt.X;
|
||||
else if (pt.X < minX)
|
||||
minX = pt.X;
|
||||
|
||||
if (pt.Y > maxY)
|
||||
maxY = pt.Y;
|
||||
else if (pt.Y < minY)
|
||||
minY = pt.Y;
|
||||
|
||||
pos = pt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.CircularMove:
|
||||
{
|
||||
var arc = (CircularMove)code;
|
||||
var radius = arc.CenterPoint.DistanceTo(arc.EndPoint);
|
||||
|
||||
Vector endpt;
|
||||
Vector centerpt;
|
||||
|
||||
if (Mode == Mode.Incremental)
|
||||
{
|
||||
endpt = arc.EndPoint + pos;
|
||||
centerpt = arc.CenterPoint + pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
endpt = arc.EndPoint;
|
||||
centerpt = arc.CenterPoint;
|
||||
}
|
||||
|
||||
double minX1;
|
||||
double minY1;
|
||||
double maxX1;
|
||||
double maxY1;
|
||||
|
||||
if (pos.X < endpt.X)
|
||||
{
|
||||
minX1 = pos.X;
|
||||
maxX1 = endpt.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
minX1 = endpt.X;
|
||||
maxX1 = pos.X;
|
||||
}
|
||||
|
||||
if (pos.Y < endpt.Y)
|
||||
{
|
||||
minY1 = pos.Y;
|
||||
maxY1 = endpt.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
minY1 = endpt.Y;
|
||||
maxY1 = pos.Y;
|
||||
}
|
||||
|
||||
var startAngle = pos.AngleFrom(centerpt);
|
||||
var endAngle = endpt.AngleFrom(centerpt);
|
||||
|
||||
// switch the angle to counter clockwise.
|
||||
if (arc.Rotation == RotationType.CW)
|
||||
Generic.Swap(ref startAngle, ref endAngle);
|
||||
|
||||
startAngle = Angle.NormalizeRad(startAngle);
|
||||
endAngle = Angle.NormalizeRad(endAngle);
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.HalfPI, startAngle, endAngle))
|
||||
maxY1 = centerpt.Y + radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Math.PI, startAngle, endAngle))
|
||||
minX1 = centerpt.X - radius;
|
||||
|
||||
const double oneHalfPI = Math.PI * 1.5;
|
||||
|
||||
if (Angle.IsBetweenRad(oneHalfPI, startAngle, endAngle))
|
||||
minY1 = centerpt.Y - radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.TwoPI, startAngle, endAngle))
|
||||
maxX1 = centerpt.X + radius;
|
||||
|
||||
if (maxX1 > maxX)
|
||||
maxX = maxX1;
|
||||
|
||||
if (minX1 < minX)
|
||||
minX = minX1;
|
||||
|
||||
if (maxY1 > maxY)
|
||||
maxY = maxY1;
|
||||
|
||||
if (minY1 < minY)
|
||||
minY = minY1;
|
||||
|
||||
pos = endpt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.SubProgramCall:
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
var box = subpgm.Program.BoundingBox(ref pos);
|
||||
|
||||
if (box.Left < minX)
|
||||
minX = box.Left;
|
||||
|
||||
if (box.Right > maxX)
|
||||
maxX = box.Right;
|
||||
|
||||
if (box.Bottom < minY)
|
||||
minY = box.Bottom;
|
||||
|
||||
if (box.Top > maxY)
|
||||
maxY = box.Top;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var pgm = new Program()
|
||||
{
|
||||
mode = this.mode,
|
||||
Rotation = this.Rotation
|
||||
};
|
||||
|
||||
var codes = new ICode[Length];
|
||||
|
||||
for (int i = 0; i < Length; ++i)
|
||||
codes[i] = this.Codes[i].Clone();
|
||||
|
||||
pgm.Codes.AddRange(codes);
|
||||
|
||||
return pgm;
|
||||
}
|
||||
|
||||
public List<Entity> ToGeometry()
|
||||
{
|
||||
return ConvertProgram.ToGeometry(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
OpenNest.Core/CNC/RapidMove.cs
Normal file
42
OpenNest.Core/CNC/RapidMove.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class RapidMove : Motion
|
||||
{
|
||||
public RapidMove()
|
||||
{
|
||||
Feedrate = CNC.Feedrate.UseMax;
|
||||
}
|
||||
|
||||
public RapidMove(Vector endPoint)
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
}
|
||||
|
||||
public RapidMove(double x, double y)
|
||||
{
|
||||
EndPoint = new Vector(x, y);
|
||||
}
|
||||
|
||||
public override CodeType Type
|
||||
{
|
||||
get { return CodeType.RapidMove; }
|
||||
}
|
||||
|
||||
public override ICode Clone()
|
||||
{
|
||||
return new RapidMove(EndPoint);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(DefaultDecimalPlaces);
|
||||
}
|
||||
|
||||
public override string ToString(int decimalPlaces)
|
||||
{
|
||||
var dp = "N" + decimalPlaces;
|
||||
return string.Format("G00 X{0} Y{1}", EndPoint.X.ToString(dp), EndPoint.Y.ToString(dp));
|
||||
}
|
||||
}
|
||||
}
|
||||
87
OpenNest.Core/CNC/SubProgramCall.cs
Normal file
87
OpenNest.Core/CNC/SubProgramCall.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class SubProgramCall : ICode
|
||||
{
|
||||
private double rotation;
|
||||
private Program program;
|
||||
|
||||
public SubProgramCall()
|
||||
{
|
||||
}
|
||||
|
||||
public SubProgramCall(Program program, double rotation)
|
||||
{
|
||||
this.program = program;
|
||||
this.Rotation = rotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The program ID.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the program.
|
||||
/// </summary>
|
||||
public Program Program
|
||||
{
|
||||
get { return program; }
|
||||
set
|
||||
{
|
||||
program = value;
|
||||
UpdateProgramRotation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rotation of the program in degrees.
|
||||
/// </summary>
|
||||
public double Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
set
|
||||
{
|
||||
rotation = value;
|
||||
UpdateProgramRotation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the program by the difference of the current
|
||||
/// rotation set in the sub program call and the program.
|
||||
/// </summary>
|
||||
private void UpdateProgramRotation()
|
||||
{
|
||||
if (program != null)
|
||||
{
|
||||
var diffAngle = Angle.ToRadians(rotation) - program.Rotation;
|
||||
|
||||
if (!diffAngle.IsEqualTo(0.0))
|
||||
program.Rotate(diffAngle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the code type.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public CodeType Type
|
||||
{
|
||||
get { return CodeType.SubProgramCall; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a shallow copy.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ICode Clone()
|
||||
{
|
||||
return new SubProgramCall(program, Rotation);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("G65 P{0} R{1}", Id, Rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
OpenNest.Core/Collections/DrawingCollection.cs
Normal file
8
OpenNest.Core/Collections/DrawingCollection.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Collections
|
||||
{
|
||||
public class DrawingCollection : HashSet<Drawing>
|
||||
{
|
||||
}
|
||||
}
|
||||
164
OpenNest.Core/Collections/ObservableList.cs
Normal file
164
OpenNest.Core/Collections/ObservableList.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Collections
|
||||
{
|
||||
public class ObservableList<T> : IList<T>, ICollection<T>, IEnumerable<T>
|
||||
{
|
||||
private readonly List<T> items;
|
||||
|
||||
public event EventHandler<ItemAddedEventArgs<T>> ItemAdded;
|
||||
public event EventHandler<ItemRemovedEventArgs<T>> ItemRemoved;
|
||||
public event EventHandler<ItemChangedEventArgs<T>> ItemChanged;
|
||||
|
||||
public ObservableList()
|
||||
{
|
||||
items = new List<T>();
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
var index = items.Count;
|
||||
items.Add(item);
|
||||
ItemAdded?.Invoke(this, new ItemAddedEventArgs<T>(item, index));
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> collection)
|
||||
{
|
||||
var index = items.Count;
|
||||
items.AddRange(collection);
|
||||
|
||||
if (ItemAdded != null)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
ItemAdded.Invoke(this, new ItemAddedEventArgs<T>(item, index++));
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
items.Insert(index, item);
|
||||
ItemAdded?.Invoke(this, new ItemAddedEventArgs<T>(item, index));
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
var success = items.Remove(item);
|
||||
ItemRemoved?.Invoke(this, new ItemRemovedEventArgs<T>(item, success));
|
||||
return success;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
var item = items[index];
|
||||
items.RemoveAt(index);
|
||||
ItemRemoved?.Invoke(this, new ItemRemovedEventArgs<T>(item, true));
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = items.Count - 1; i >= 0; --i)
|
||||
RemoveAt(i);
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
return items.IndexOf(item);
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => items[index];
|
||||
set
|
||||
{
|
||||
var old = items[index];
|
||||
items[index] = value;
|
||||
ItemChanged?.Invoke(this, new ItemChangedEventArgs<T>(old, value, index));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return items.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
items.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public int Count => items.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return items.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return items.GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemAddedEventArgs<T> : EventArgs
|
||||
{
|
||||
public T Item { get; }
|
||||
public int Index { get; }
|
||||
|
||||
public ItemAddedEventArgs(T item, int index)
|
||||
{
|
||||
Item = item;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemRemovedEventArgs<T> : EventArgs
|
||||
{
|
||||
public T Item { get; }
|
||||
public bool Succeeded { get; }
|
||||
|
||||
public ItemRemovedEventArgs(T item, bool succeeded)
|
||||
{
|
||||
Item = item;
|
||||
Succeeded = succeeded;
|
||||
}
|
||||
}
|
||||
|
||||
public class ItemChangedEventArgs<T> : EventArgs
|
||||
{
|
||||
public T OldItem { get; }
|
||||
public T NewItem { get; }
|
||||
public int Index { get; }
|
||||
|
||||
public ItemChangedEventArgs(T oldItem, T newItem, int index)
|
||||
{
|
||||
OldItem = oldItem;
|
||||
NewItem = newItem;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlateCollectionExtensions
|
||||
{
|
||||
public static void RemoveEmptyPlates(this ObservableList<Plate> plates)
|
||||
{
|
||||
if (plates.Count < 2)
|
||||
return;
|
||||
|
||||
for (int i = plates.Count - 1; i >= 0; --i)
|
||||
{
|
||||
if (plates[i].Parts.Count == 0)
|
||||
plates.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public static int TotalCount(this ObservableList<Plate> plates)
|
||||
{
|
||||
return plates.Sum(plate => plate.Quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
OpenNest.Core/ConvertGeometry.cs
Normal file
117
OpenNest.Core/ConvertGeometry.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class ConvertGeometry
|
||||
{
|
||||
public static Program ToProgram(IList<Entity> geometry)
|
||||
{
|
||||
var shapes = Helper.GetShapes(geometry);
|
||||
|
||||
if (shapes.Count == 0)
|
||||
return null;
|
||||
|
||||
var perimeter = shapes[0];
|
||||
var area = perimeter.BoundingBox.Area();
|
||||
var index = 0;
|
||||
|
||||
for (int i = 1; i < shapes.Count; ++i)
|
||||
{
|
||||
var program = shapes[i];
|
||||
var area2 = program.BoundingBox.Area();
|
||||
if (area2 > area)
|
||||
{
|
||||
perimeter = program;
|
||||
area = area2;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
shapes.RemoveAt(index);
|
||||
|
||||
var pgm = new Program();
|
||||
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
var subpgm = ToProgram(shape);
|
||||
pgm.Merge(subpgm);
|
||||
}
|
||||
|
||||
pgm.Merge(ToProgram(perimeter));
|
||||
pgm.Mode = Mode.Incremental;
|
||||
|
||||
return pgm;
|
||||
}
|
||||
|
||||
public static Program ToProgram(Shape shape)
|
||||
{
|
||||
var pgm = new Program();
|
||||
var lastpt = new Vector();
|
||||
|
||||
for (int i = 0; i < shape.Entities.Count; i++)
|
||||
lastpt = AddEntity(pgm, lastpt, shape.Entities[i]);
|
||||
|
||||
return pgm;
|
||||
}
|
||||
|
||||
private static Vector AddEntity(Program pgm, Vector lastpt, Entity geo)
|
||||
{
|
||||
switch (geo.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
lastpt = AddArc(pgm, lastpt, (Arc)geo);
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
lastpt = AddCircle(pgm, lastpt, (Circle)geo);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
lastpt = AddLine(pgm, lastpt, (Line)geo);
|
||||
break;
|
||||
}
|
||||
|
||||
return lastpt;
|
||||
}
|
||||
|
||||
private static Vector AddArc(Program pgm, Vector lastpt, Arc arc)
|
||||
{
|
||||
var startpt = arc.StartPoint();
|
||||
var endpt = arc.EndPoint();
|
||||
|
||||
if (startpt != lastpt)
|
||||
pgm.MoveTo(startpt);
|
||||
|
||||
lastpt = endpt;
|
||||
|
||||
pgm.ArcTo(endpt, arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW);
|
||||
return lastpt;
|
||||
}
|
||||
|
||||
private static Vector AddCircle(Program pgm, Vector lastpt, Circle circle)
|
||||
{
|
||||
var startpt = new Vector(circle.Center.X + circle.Radius, circle.Center.Y);
|
||||
|
||||
if (startpt != lastpt)
|
||||
pgm.MoveTo(startpt);
|
||||
|
||||
pgm.ArcTo(startpt, circle.Center, RotationType.CCW);
|
||||
|
||||
lastpt = startpt;
|
||||
return lastpt;
|
||||
}
|
||||
|
||||
private static Vector AddLine(Program pgm, Vector lastpt, Line line)
|
||||
{
|
||||
if (line.StartPoint != lastpt)
|
||||
pgm.MoveTo(line.StartPoint);
|
||||
|
||||
pgm.LineTo(line.EndPoint);
|
||||
|
||||
lastpt = line.EndPoint;
|
||||
return lastpt;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
OpenNest.Core/ConvertMode.cs
Normal file
52
OpenNest.Core/ConvertMode.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using OpenNest.CNC;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class ConvertMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the program to absolute coordinates.
|
||||
/// Does NOT check program mode before converting.
|
||||
/// </summary>
|
||||
/// <param name="pgm"></param>
|
||||
public static void ToAbsolute(Program pgm)
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (int i = 0; i < pgm.Codes.Count; ++i)
|
||||
{
|
||||
var code = pgm.Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion != null)
|
||||
{
|
||||
motion.Offset(pos);
|
||||
pos = motion.EndPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the program to intermental coordinates.
|
||||
/// Does NOT check program mode before converting.
|
||||
/// </summary>
|
||||
/// <param name="pgm"></param>
|
||||
public static void ToIncremental(Program pgm)
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (int i = 0; i < pgm.Codes.Count; ++i)
|
||||
{
|
||||
var code = pgm.Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion != null)
|
||||
{
|
||||
var pos2 = motion.EndPoint;
|
||||
motion.Offset(-pos.X, -pos.Y);
|
||||
pos = pos2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
137
OpenNest.Core/ConvertProgram.cs
Normal file
137
OpenNest.Core/ConvertProgram.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class ConvertProgram
|
||||
{
|
||||
public static List<Entity> ToGeometry(Program pgm)
|
||||
{
|
||||
var geometry = new List<Entity>();
|
||||
var curpos = new Vector();
|
||||
var mode = Mode.Absolute;
|
||||
|
||||
AddProgram(pgm, ref mode, ref curpos, ref geometry);
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
private static void AddProgram(Program program, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
{
|
||||
mode = program.Mode;
|
||||
|
||||
for (int i = 0; i < program.Length; ++i)
|
||||
{
|
||||
var code = program[i];
|
||||
|
||||
switch (code.Type)
|
||||
{
|
||||
case CodeType.CircularMove:
|
||||
AddCircularMove((CircularMove)code, ref mode, ref curpos, ref geometry);
|
||||
break;
|
||||
|
||||
case CodeType.LinearMove:
|
||||
AddLinearMove((LinearMove)code, ref mode, ref curpos, ref geometry);
|
||||
break;
|
||||
|
||||
case CodeType.RapidMove:
|
||||
AddRapidMove((RapidMove)code, ref mode, ref curpos, ref geometry);
|
||||
break;
|
||||
|
||||
case CodeType.SubProgramCall:
|
||||
var tmpmode = mode;
|
||||
var subpgm = (SubProgramCall)code;
|
||||
var geoProgram = new Shape();
|
||||
AddProgram(subpgm.Program, ref mode, ref curpos, ref geoProgram.Entities);
|
||||
geometry.Add(geoProgram);
|
||||
mode = tmpmode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddLinearMove(LinearMove linearMove, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
{
|
||||
var pt = linearMove.EndPoint;
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
pt += curpos;
|
||||
|
||||
var line = new Line(curpos, pt)
|
||||
{
|
||||
Layer = ConvertLayer(linearMove.Layer)
|
||||
};
|
||||
geometry.Add(line);
|
||||
curpos = pt;
|
||||
}
|
||||
|
||||
private static void AddRapidMove(RapidMove rapidMove, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
{
|
||||
var pt = rapidMove.EndPoint;
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
pt += curpos;
|
||||
|
||||
var line = new Line(curpos, pt)
|
||||
{
|
||||
Layer = SpecialLayers.Rapid
|
||||
};
|
||||
geometry.Add(line);
|
||||
curpos = pt;
|
||||
}
|
||||
|
||||
private static void AddCircularMove(CircularMove circularMove, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
{
|
||||
var center = circularMove.CenterPoint;
|
||||
var endpt = circularMove.EndPoint;
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
{
|
||||
endpt += curpos;
|
||||
center += curpos;
|
||||
}
|
||||
|
||||
var startAngle = center.AngleTo(curpos);
|
||||
var endAngle = center.AngleTo(endpt);
|
||||
|
||||
var dx = endpt.X - center.X;
|
||||
var dy = endpt.Y - center.Y;
|
||||
|
||||
var radius = Math.Sqrt(dx * dx + dy * dy);
|
||||
var layer = ConvertLayer(circularMove.Layer);
|
||||
|
||||
if (startAngle.IsEqualTo(endAngle))
|
||||
geometry.Add(new Circle(center, radius) { Layer = layer });
|
||||
else
|
||||
geometry.Add(new Arc(center, radius, startAngle, endAngle, circularMove.Rotation == RotationType.CW) { Layer = layer });
|
||||
|
||||
curpos = endpt;
|
||||
}
|
||||
|
||||
private static Layer ConvertLayer(LayerType layer)
|
||||
{
|
||||
switch (layer)
|
||||
{
|
||||
case LayerType.Cut:
|
||||
return SpecialLayers.Cut;
|
||||
|
||||
case LayerType.Display:
|
||||
return SpecialLayers.Display;
|
||||
|
||||
case LayerType.Leadin:
|
||||
return SpecialLayers.Leadin;
|
||||
|
||||
case LayerType.Leadout:
|
||||
return SpecialLayers.Leadout;
|
||||
|
||||
case LayerType.Scribe:
|
||||
return SpecialLayers.Scribe;
|
||||
|
||||
default:
|
||||
return new Layer(layer.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
OpenNest.Core/CutParameters.cs
Normal file
15
OpenNest.Core/CutParameters.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class CutParameters
|
||||
{
|
||||
public double Feedrate { get; set; }
|
||||
|
||||
public double RapidTravelRate { get; set; }
|
||||
|
||||
public TimeSpan PierceTime { get; set; }
|
||||
|
||||
public Units Units { get; set; }
|
||||
}
|
||||
}
|
||||
117
OpenNest.Core/Drawing.cs
Normal file
117
OpenNest.Core/Drawing.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Drawing
|
||||
{
|
||||
private Program program;
|
||||
|
||||
public Drawing()
|
||||
: this(string.Empty, new Program())
|
||||
{
|
||||
}
|
||||
|
||||
public Drawing(string name)
|
||||
: this(name, new Program())
|
||||
{
|
||||
}
|
||||
|
||||
public Drawing(string name, Program pgm)
|
||||
{
|
||||
Name = name;
|
||||
Material = new Material();
|
||||
Program = pgm;
|
||||
Constraints = new NestConstraints();
|
||||
Source = new SourceInfo();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Customer { get; set; }
|
||||
|
||||
public int Priority { get; set; }
|
||||
|
||||
public Quantity Quantity;
|
||||
|
||||
public Material Material { get; set; }
|
||||
|
||||
public Program Program
|
||||
{
|
||||
get { return program; }
|
||||
set
|
||||
{
|
||||
program = value;
|
||||
UpdateArea();
|
||||
}
|
||||
}
|
||||
|
||||
public Color Color { get; set; }
|
||||
|
||||
public NestConstraints Constraints { get; set; }
|
||||
|
||||
public SourceInfo Source { get; set; }
|
||||
|
||||
public double Area { get; protected set; }
|
||||
|
||||
public void UpdateArea()
|
||||
{
|
||||
var geometry = ConvertProgram.ToGeometry(Program).Where(entity => entity.Layer != SpecialLayers.Rapid);
|
||||
var shapes = Helper.GetShapes(geometry);
|
||||
|
||||
if (shapes.Count == 0)
|
||||
return;
|
||||
|
||||
var areas = new double[shapes.Count];
|
||||
|
||||
for (int i = 0; i < shapes.Count; i++)
|
||||
{
|
||||
var shape = shapes[i];
|
||||
areas[i] = shape.Area();
|
||||
}
|
||||
|
||||
int largestAreaIndex = 0;
|
||||
|
||||
for (int i = 1; i < areas.Length; i++)
|
||||
{
|
||||
var area = areas[i];
|
||||
|
||||
if (area > areas[largestAreaIndex])
|
||||
largestAreaIndex = i;
|
||||
}
|
||||
|
||||
var outerArea = areas[largestAreaIndex];
|
||||
|
||||
Area = outerArea - (areas.Sum() - outerArea);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Drawing == false)
|
||||
return false;
|
||||
|
||||
var dwg = (Drawing)obj;
|
||||
|
||||
return Name == dwg.Name;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class SourceInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the source file.
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset distances to the original location.
|
||||
/// </summary>
|
||||
public Vector Offset { get; set; }
|
||||
}
|
||||
}
|
||||
15
OpenNest.Core/EvenOdd.cs
Normal file
15
OpenNest.Core/EvenOdd.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class EvenOdd
|
||||
{
|
||||
public static bool IsEven(this int i)
|
||||
{
|
||||
return (i % 2) == 0;
|
||||
}
|
||||
|
||||
public static bool IsOdd(this int i)
|
||||
{
|
||||
return (i % 2) == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
OpenNest.Core/Generic.cs
Normal file
12
OpenNest.Core/Generic.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Generic
|
||||
{
|
||||
public static void Swap<T>(ref T a, ref T b)
|
||||
{
|
||||
T c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
538
OpenNest.Core/Geometry/Arc.cs
Normal file
538
OpenNest.Core/Geometry/Arc.cs
Normal file
@@ -0,0 +1,538 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Arc : Entity
|
||||
{
|
||||
private double radius;
|
||||
private double startAngle;
|
||||
private double endAngle;
|
||||
private Vector center;
|
||||
private bool reversed;
|
||||
|
||||
public Arc()
|
||||
{
|
||||
}
|
||||
|
||||
public Arc(double x, double y, double r, double a1, double a2, bool reversed = false)
|
||||
: this(new Vector(x, y), r, a1, a2, reversed)
|
||||
{
|
||||
}
|
||||
|
||||
public Arc(Vector center, double radius, double startAngle, double endAngle, bool reversed = false)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
this.startAngle = startAngle;
|
||||
this.endAngle = endAngle;
|
||||
this.reversed = reversed;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Center point.
|
||||
/// </summary>
|
||||
public Vector Center
|
||||
{
|
||||
get { return center; }
|
||||
set
|
||||
{
|
||||
var offset = value - center;
|
||||
boundingBox.Offset(offset);
|
||||
center = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arc radius.
|
||||
/// </summary>
|
||||
public double Radius
|
||||
{
|
||||
get { return radius; }
|
||||
set
|
||||
{
|
||||
radius = value;
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arc radius * 2. Value NOT stored.
|
||||
/// </summary>
|
||||
public double Diameter
|
||||
{
|
||||
get { return Radius * 2.0; }
|
||||
set { Radius = value / 2.0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start angle in radians.
|
||||
/// </summary>
|
||||
public double StartAngle
|
||||
{
|
||||
get { return startAngle; }
|
||||
set
|
||||
{
|
||||
startAngle = Angle.NormalizeRad(value);
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End angle in radians.
|
||||
/// </summary>
|
||||
public double EndAngle
|
||||
{
|
||||
get { return endAngle; }
|
||||
set
|
||||
{
|
||||
endAngle = Angle.NormalizeRad(value);
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Angle in radians between start and end angles.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double SweepAngle()
|
||||
{
|
||||
var startAngle = StartAngle;
|
||||
var endAngle = EndAngle;
|
||||
|
||||
if (IsReversed)
|
||||
Generic.Swap(ref startAngle, ref endAngle);
|
||||
|
||||
if (startAngle > endAngle)
|
||||
startAngle -= Angle.TwoPI;
|
||||
|
||||
return endAngle - startAngle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the arc direction is reversed (clockwise).
|
||||
/// </summary>
|
||||
public bool IsReversed
|
||||
{
|
||||
get { return reversed; }
|
||||
set
|
||||
{
|
||||
if (reversed != value)
|
||||
Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
public RotationType Rotation
|
||||
{
|
||||
get { return IsReversed ? RotationType.CW : RotationType.CCW; }
|
||||
set
|
||||
{
|
||||
IsReversed = (value == RotationType.CW);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start point of the arc.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Vector StartPoint()
|
||||
{
|
||||
return new Vector(
|
||||
Center.X + Radius * Math.Cos(StartAngle),
|
||||
Center.Y + Radius * Math.Sin(StartAngle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End point of the arc.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Vector EndPoint()
|
||||
{
|
||||
return new Vector(
|
||||
Center.X + Radius * Math.Cos(EndAngle),
|
||||
Center.Y + Radius * Math.Sin(EndAngle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc has the same center point and radius as this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsCoradialTo(Arc arc)
|
||||
{
|
||||
return center == arc.Center && Radius.IsEqualTo(arc.Radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc has the same radius as this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsConcentricTo(Arc arc)
|
||||
{
|
||||
return center == arc.center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle has the same radius as this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsConcentricTo(Circle circle)
|
||||
{
|
||||
return center == circle.Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the arc to a group of points.
|
||||
/// </summary>
|
||||
/// <param name="segments">Number of parts to divide the arc into.</param>
|
||||
/// <returns></returns>
|
||||
public List<Vector> ToPoints(int segments = 1000)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
var stepAngle = reversed
|
||||
? -SweepAngle() / segments
|
||||
: SweepAngle() / segments;
|
||||
|
||||
for (int i = 0; i <= segments; ++i)
|
||||
{
|
||||
var angle = stepAngle * i + StartAngle;
|
||||
|
||||
points.Add(new Vector(
|
||||
Math.Cos(angle) * Radius + Center.X,
|
||||
Math.Sin(angle) * Radius + Center.Y));
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear distance of the arc.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get { return Diameter * Math.PI * SweepAngle() / Angle.TwoPI; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
reversed = !reversed;
|
||||
Generic.Swap(ref startAngle, ref endAngle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the center point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The x-coordinate</param>
|
||||
/// <param name="y">The y-coordinate</param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
Center = new Vector(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the center point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt">The new center point location.</param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
Center = pt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the center point by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x">The x-axis offset distance.</param>
|
||||
/// <param name="y">The y-axis offset distance.</param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
Center = new Vector(Center.X + x, Center.Y + y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the center point by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
Center += voffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the arc from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
center *= factor;
|
||||
radius *= factor;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the arc from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
center = center.Scale(factor, origin);
|
||||
radius *= factor;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the arc from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
startAngle += angle;
|
||||
endAngle += angle;
|
||||
center = center.Rotate(angle);
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the arc from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
startAngle += angle;
|
||||
endAngle += angle;
|
||||
center = center.Rotate(angle, origin);
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
var startpt = StartPoint();
|
||||
var endpt = EndPoint();
|
||||
|
||||
double minX;
|
||||
double minY;
|
||||
double maxX;
|
||||
double maxY;
|
||||
|
||||
if (startpt.X < endpt.X)
|
||||
{
|
||||
minX = startpt.X;
|
||||
maxX = endpt.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
minX = endpt.X;
|
||||
maxX = startpt.X;
|
||||
}
|
||||
|
||||
if (startpt.Y < endpt.Y)
|
||||
{
|
||||
minY = startpt.Y;
|
||||
maxY = endpt.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
minY = endpt.Y;
|
||||
maxY = startpt.Y;
|
||||
}
|
||||
|
||||
var angle1 = StartAngle;
|
||||
var angle2 = EndAngle;
|
||||
|
||||
// switch the angle to counter clockwise.
|
||||
if (IsReversed)
|
||||
Generic.Swap(ref angle1, ref angle2);
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.HalfPI, angle1, angle2))
|
||||
maxY = Center.Y + Radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Math.PI, angle1, angle2))
|
||||
minX = Center.X - Radius;
|
||||
|
||||
const double oneHalfPI = Math.PI * 1.5;
|
||||
|
||||
if (Angle.IsBetweenRad(oneHalfPI, angle1, angle2))
|
||||
minY = Center.Y - Radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.TwoPI, angle1, angle2))
|
||||
maxX = Center.X + Radius;
|
||||
|
||||
boundingBox.X = minX;
|
||||
boundingBox.Y = minY;
|
||||
boundingBox.Width = maxX - minX;
|
||||
boundingBox.Height = maxY - minY;
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
if (side == OffsetSide.Left && reversed)
|
||||
{
|
||||
return new Arc(center, radius + distance, startAngle, endAngle, reversed);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (distance >= radius)
|
||||
return null;
|
||||
|
||||
return new Arc(center, radius - distance, startAngle, endAngle, reversed);
|
||||
}
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the arc to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
var angle = Center.AngleTo(pt);
|
||||
|
||||
if (Angle.IsBetweenRad(angle, StartAngle, EndAngle, IsReversed))
|
||||
{
|
||||
return new Vector(
|
||||
Math.Cos(angle) * Radius + Center.X,
|
||||
Math.Sin(angle) * Radius + Center.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sp = StartPoint();
|
||||
var ep = EndPoint();
|
||||
|
||||
return pt.DistanceTo(sp) <= pt.DistanceTo(ep) ? sp : ep;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, arc, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, arc, out pts); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, circle, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, circle, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, line, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, line, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Arc; }
|
||||
}
|
||||
}
|
||||
}
|
||||
415
OpenNest.Core/Geometry/Circle.cs
Normal file
415
OpenNest.Core/Geometry/Circle.cs
Normal file
@@ -0,0 +1,415 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Circle : Entity
|
||||
{
|
||||
private Vector center;
|
||||
private double radius;
|
||||
|
||||
public Circle()
|
||||
{
|
||||
}
|
||||
|
||||
public Circle(double x, double y, double radius)
|
||||
: this(new Vector(x, y), radius)
|
||||
{
|
||||
}
|
||||
|
||||
public Circle(Vector center, double radius)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
this.Rotation = RotationType.CCW;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a circle from two points.
|
||||
/// </summary>
|
||||
/// <param name="pt1"></param>
|
||||
/// <param name="pt2"></param>
|
||||
/// <returns></returns>
|
||||
public static Circle CreateFrom2Points(Vector pt1, Vector pt2)
|
||||
{
|
||||
var line = new Line(pt1, pt2);
|
||||
return new Circle(line.MidPoint, line.Length * 0.5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Center point of the circle.
|
||||
/// </summary>
|
||||
public Vector Center
|
||||
{
|
||||
get { return center; }
|
||||
set
|
||||
{
|
||||
var offset = value - center;
|
||||
boundingBox.Offset(offset);
|
||||
center = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Radius of the circle.
|
||||
/// </summary>
|
||||
public double Radius
|
||||
{
|
||||
get { return radius; }
|
||||
set
|
||||
{
|
||||
radius = value;
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Radius * 2. Value NOT stored.
|
||||
/// </summary>
|
||||
public double Diameter
|
||||
{
|
||||
get { return Radius * 2.0; }
|
||||
set { Radius = value / 2.0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation direction.
|
||||
/// </summary>
|
||||
public RotationType Rotation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Area of the circle.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Area()
|
||||
{
|
||||
return Math.PI * Radius * Radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear distance around the circle.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Circumference()
|
||||
{
|
||||
return Math.PI * Diameter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle has the same radius as this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsConcentricTo(Circle circle)
|
||||
{
|
||||
return center == circle.center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle has the same radius as this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsConcentricTo(Arc arc)
|
||||
{
|
||||
return center == arc.Center;
|
||||
}
|
||||
|
||||
public bool ContainsPoint(Vector pt)
|
||||
{
|
||||
return Center.DistanceTo(pt) <= Radius;
|
||||
}
|
||||
|
||||
public List<Vector> ToPoints(int segments = 1000)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
var stepAngle = Angle.TwoPI / segments;
|
||||
|
||||
for (int i = 0; i <= segments; ++i)
|
||||
{
|
||||
var angle = stepAngle * i;
|
||||
|
||||
points.Add(new Vector(
|
||||
Math.Cos(angle) * Radius + Center.X,
|
||||
Math.Sin(angle) * Radius + Center.Y));
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear distance around the circle.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get { return Circumference(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
if (Rotation == RotationType.CCW)
|
||||
Rotation = RotationType.CW;
|
||||
else
|
||||
Rotation = RotationType.CCW;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the center point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
Center = new Vector(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the center point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
Center = pt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the center point by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
Center = new Vector(Center.X + x, Center.Y + y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the center point by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
Center += voffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the circle from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
center *= factor;
|
||||
radius *= factor;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the circle from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
center = center.Scale(factor, origin);
|
||||
radius *= factor;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the circle from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
Center = Center.Rotate(angle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// /// Rotates the circle from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
Center = Center.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
boundingBox.X = Center.X - Radius;
|
||||
boundingBox.Y = Center.Y - Radius;
|
||||
boundingBox.Width = Diameter;
|
||||
boundingBox.Height = Diameter;
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
if (side == OffsetSide.Left && Rotation == RotationType.CCW)
|
||||
{
|
||||
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
||||
{
|
||||
Layer = Layer,
|
||||
Rotation = Rotation
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Circle(center, Radius + distance) { Layer = Layer };
|
||||
}
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
if (ContainsPoint(pt))
|
||||
{
|
||||
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
||||
{
|
||||
Layer = Layer,
|
||||
Rotation = Rotation
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Circle(center, Radius + distance) { Layer = Layer };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the circle to the specified point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
var angle = Center.AngleTo(pt);
|
||||
|
||||
return new Vector(
|
||||
Math.Cos(angle) * Radius + Center.X,
|
||||
Math.Sin(angle) * Radius + Center.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
var dist = Center.DistanceTo(circle.Center);
|
||||
return (dist < (Radius + circle.Radius) && dist > Math.Abs(Radius - circle.Radius));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, circle, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, line, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, line, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Circle; }
|
||||
}
|
||||
}
|
||||
}
|
||||
42
OpenNest.Core/Geometry/DefinedShape.cs
Normal file
42
OpenNest.Core/Geometry/DefinedShape.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class DefinedShape
|
||||
{
|
||||
public DefinedShape(Shape shape)
|
||||
{
|
||||
Update(shape.Entities);
|
||||
}
|
||||
|
||||
public DefinedShape(List<Entity> entities)
|
||||
{
|
||||
Update(entities);
|
||||
}
|
||||
|
||||
private void Update(List<Entity> entities)
|
||||
{
|
||||
var shapes = Helper.GetShapes(entities);
|
||||
|
||||
Perimeter = shapes[0];
|
||||
Cutouts = new List<Shape>();
|
||||
|
||||
for (int i = 1; i < shapes.Count; i++)
|
||||
{
|
||||
if (shapes[i].Left < Perimeter.Left)
|
||||
{
|
||||
Cutouts.Add(Perimeter);
|
||||
Perimeter = shapes[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
Cutouts.Add(shapes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Shape Perimeter { get; set; }
|
||||
|
||||
public List<Shape> Cutouts { get; set; }
|
||||
}
|
||||
}
|
||||
268
OpenNest.Core/Geometry/Entity.cs
Normal file
268
OpenNest.Core/Geometry/Entity.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public abstract class Entity : IBoundable
|
||||
{
|
||||
protected Box boundingBox;
|
||||
|
||||
protected Entity()
|
||||
{
|
||||
Layer = OpenNest.Geometry.Layer.Default;
|
||||
boundingBox = new Box();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smallest box that contains the entity.
|
||||
/// </summary>
|
||||
public Box BoundingBox
|
||||
{
|
||||
get { return boundingBox; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity layer type.
|
||||
/// </summary>
|
||||
public Layer Layer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// X-Coordinate of the left-most point.
|
||||
/// </summary>
|
||||
public virtual double Left
|
||||
{
|
||||
get { return boundingBox.Left; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// X-Coordinate of the right-most point.
|
||||
/// </summary>
|
||||
public virtual double Right
|
||||
{
|
||||
get { return boundingBox.Right; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Y-Coordinate of the highest point.
|
||||
/// </summary>
|
||||
public virtual double Top
|
||||
{
|
||||
get { return boundingBox.Top; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Y-Coordinate of the lowest point.
|
||||
/// </summary>
|
||||
public virtual double Bottom
|
||||
{
|
||||
get { return boundingBox.Bottom; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Length of the entity.
|
||||
/// </summary>
|
||||
public abstract double Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the entity.
|
||||
/// </summary>
|
||||
public abstract void Reverse();
|
||||
|
||||
/// <summary>
|
||||
/// Moves the entity location to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public abstract void MoveTo(double x, double y);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the entity location to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public abstract void MoveTo(Vector pt);
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the entity location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public abstract void Offset(double x, double y);
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the entity location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public abstract void Offset(Vector voffset);
|
||||
|
||||
/// <summary>
|
||||
/// Scales the entity from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public abstract void Scale(double factor);
|
||||
|
||||
/// <summary>
|
||||
/// Scales the entity from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public abstract void Scale(double factor, Vector origin);
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the entity from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public abstract void Rotate(double angle);
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the entity from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public abstract void Rotate(double angle, Vector origin);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public abstract void UpdateBounds();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new entity offset the given distance from this entity.
|
||||
/// </summary>
|
||||
/// <param name="distance"></param>
|
||||
/// <param name="side"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Entity OffsetEntity(double distance, OffsetSide side);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new entity offset the given distance from this entity. Offset side determined by point.
|
||||
/// </summary>
|
||||
/// <param name="distance"></param>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Entity OffsetEntity(double distance, Vector pt);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the entity to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Vector ClosestPointTo(Vector pt);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Arc arc);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts">List to store the points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Arc arc, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Circle circle);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Circle circle, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Line line);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Line line, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Polygon polygon);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Polygon polygon, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Shape shape);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Shape shape, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public abstract EntityType Type { get; }
|
||||
}
|
||||
|
||||
public static class EntityExtensions
|
||||
{
|
||||
public static double FindBestRotation(this List<Entity> entities, double stepAngle, double startAngle = 0, double endAngle = Angle.TwoPI)
|
||||
{
|
||||
startAngle = Angle.NormalizeRad(startAngle);
|
||||
|
||||
if (!endAngle.IsEqualTo(Angle.TwoPI))
|
||||
endAngle = Angle.NormalizeRad(endAngle);
|
||||
|
||||
if (stepAngle.IsEqualTo(0.0))
|
||||
return startAngle;
|
||||
|
||||
entities.ForEach(e => e.Rotate(startAngle));
|
||||
|
||||
var bestAngle = startAngle;
|
||||
var bestArea = entities.GetBoundingBox().Area();
|
||||
|
||||
var steps = startAngle < endAngle
|
||||
? (endAngle - startAngle) / stepAngle
|
||||
: (endAngle + Angle.TwoPI) - startAngle / stepAngle;
|
||||
|
||||
for (int i = 1; i <= steps; ++i)
|
||||
{
|
||||
entities.ForEach(e => e.Rotate(stepAngle));
|
||||
|
||||
var area = entities.GetBoundingBox().Area();
|
||||
|
||||
if (area < bestArea)
|
||||
{
|
||||
bestArea = area;
|
||||
bestAngle = startAngle + stepAngle * i;
|
||||
}
|
||||
}
|
||||
|
||||
return bestAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
OpenNest.Core/Geometry/EntityType.cs
Normal file
12
OpenNest.Core/Geometry/EntityType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public enum EntityType
|
||||
{
|
||||
Arc,
|
||||
Circle,
|
||||
Line,
|
||||
Shape,
|
||||
Polygon
|
||||
}
|
||||
}
|
||||
29
OpenNest.Core/Geometry/Layer.cs
Normal file
29
OpenNest.Core/Geometry/Layer.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Layer
|
||||
{
|
||||
public static readonly Layer Default = new Layer("0")
|
||||
{
|
||||
Color = Color.White,
|
||||
IsVisible = true
|
||||
};
|
||||
|
||||
public Layer(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
public Color Color { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
552
OpenNest.Core/Geometry/Line.cs
Normal file
552
OpenNest.Core/Geometry/Line.cs
Normal file
@@ -0,0 +1,552 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Line : Entity
|
||||
{
|
||||
private Vector pt1;
|
||||
private Vector pt2;
|
||||
|
||||
public Line()
|
||||
{
|
||||
}
|
||||
|
||||
public Line(double x1, double y1, double x2, double y2)
|
||||
: this(new Vector(x1, y1), new Vector(x2, y2))
|
||||
{
|
||||
}
|
||||
|
||||
public Line(Vector startPoint, Vector endPoint)
|
||||
{
|
||||
pt1 = startPoint;
|
||||
pt2 = endPoint;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start point of the line.
|
||||
/// </summary>
|
||||
public Vector StartPoint
|
||||
{
|
||||
get { return pt1; }
|
||||
set
|
||||
{
|
||||
pt1 = value;
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mid-point of the line.
|
||||
/// </summary>
|
||||
public Vector MidPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
var x = (pt1.X + pt2.X) * 0.5;
|
||||
var y = (pt1.Y + pt2.Y) * 0.5;
|
||||
return new Vector(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End point of the line.
|
||||
/// </summary>
|
||||
public Vector EndPoint
|
||||
{
|
||||
get { return pt2; }
|
||||
set
|
||||
{
|
||||
pt2 = value;
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point on the line that is perpendicular from the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public Vector PointPerpendicularFrom(Vector pt)
|
||||
{
|
||||
var diff1 = pt - StartPoint;
|
||||
var diff2 = EndPoint - StartPoint;
|
||||
var dotProduct = diff1.X * diff2.X + diff1.Y * diff2.Y;
|
||||
var lengthSquared = diff2.X * diff2.X + diff2.Y * diff2.Y;
|
||||
var param = dotProduct / lengthSquared;
|
||||
|
||||
if (param < 0)
|
||||
return StartPoint;
|
||||
else if (param > 1)
|
||||
return EndPoint;
|
||||
else
|
||||
{
|
||||
return new Vector(
|
||||
StartPoint.X + param * diff2.X,
|
||||
StartPoint.Y + param * diff2.Y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is parallel to this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsParallelTo(Line line)
|
||||
{
|
||||
bool line1Vertical = IsVertical();
|
||||
bool line2Vertical = line.IsVertical();
|
||||
|
||||
if (line1Vertical)
|
||||
return line2Vertical;
|
||||
else if (line2Vertical)
|
||||
return false;
|
||||
|
||||
return Slope().IsEqualTo(line.Slope());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is perpendicular to this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsPerpendicularTo(Line line)
|
||||
{
|
||||
bool line1Vertical = IsVertical();
|
||||
bool line2Vertical = line.IsVertical();
|
||||
|
||||
if (line1Vertical)
|
||||
return line.IsHorizontal();
|
||||
else if (line.IsVertical())
|
||||
return IsHorizontal();
|
||||
|
||||
return Slope().IsEqualTo(-1 / line.Slope());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pt">Point of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public bool Intersects(Line line, out Vector pt)
|
||||
{
|
||||
var a1 = EndPoint.Y - StartPoint.Y;
|
||||
var b1 = StartPoint.X - EndPoint.X;
|
||||
var c1 = a1 * StartPoint.X + b1 * StartPoint.Y;
|
||||
|
||||
var a2 = line.EndPoint.Y - line.StartPoint.Y;
|
||||
var b2 = line.StartPoint.X - line.EndPoint.X;
|
||||
var c2 = a2 * line.StartPoint.X + b2 * line.StartPoint.Y;
|
||||
|
||||
var d = a1 * b2 - a2 * b1;
|
||||
|
||||
if (d.IsEqualTo(0.0))
|
||||
{
|
||||
pt = new Vector();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = (b2 * c1 - b1 * c2) / d;
|
||||
var y = (a1 * c2 - a2 * c1) / d;
|
||||
|
||||
pt = new Vector(x, y);
|
||||
return boundingBox.Contains(pt) && line.boundingBox.Contains(pt);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is vertical.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsVertical()
|
||||
{
|
||||
return pt1.X.IsEqualTo(pt2.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is horizontal.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsHorizontal()
|
||||
{
|
||||
return pt1.Y.IsEqualTo(pt2.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is collinear to this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsCollinearTo(Line line)
|
||||
{
|
||||
if (IsVertical())
|
||||
{
|
||||
if (!line.IsVertical())
|
||||
return false;
|
||||
|
||||
return StartPoint.X.IsEqualTo(line.StartPoint.X);
|
||||
}
|
||||
else if (line.IsVertical())
|
||||
return false;
|
||||
|
||||
if (!YIntercept().IsEqualTo(line.YIntercept()))
|
||||
return false;
|
||||
|
||||
if (!Slope().IsEqualTo(line.Slope()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Angle of the line from start point to end point.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Angle()
|
||||
{
|
||||
return StartPoint.AngleTo(EndPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle between the two lines in radians.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleBetween(Line line)
|
||||
{
|
||||
var m1 = Slope();
|
||||
var m2 = line.Slope();
|
||||
return Math.Atan(Math.Abs((m2 - m1) / (1 + m2 * m1)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slope of the line.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Slope()
|
||||
{
|
||||
if (IsVertical())
|
||||
throw new DivideByZeroException();
|
||||
|
||||
return (EndPoint.Y - StartPoint.Y) / (EndPoint.X - StartPoint.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the y-axis intersection coordinate.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double YIntercept()
|
||||
{
|
||||
return StartPoint.Y - Slope() * StartPoint.X;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Length of the line from start point to end point.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get
|
||||
{
|
||||
var x = EndPoint.X - StartPoint.X;
|
||||
var y = EndPoint.Y - StartPoint.Y;
|
||||
return Math.Sqrt(x * x + y * y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reversed the line.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
Generic.Swap<Vector>(ref pt1, ref pt2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
var xoffset = pt1.X - x;
|
||||
var yoffset = pt1.Y - y;
|
||||
|
||||
pt2.X += xoffset;
|
||||
pt2.Y += yoffset;
|
||||
pt1.X = x;
|
||||
pt1.Y = y;
|
||||
boundingBox.Offset(xoffset, yoffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
var offset = pt1 - pt;
|
||||
|
||||
pt2 += offset;
|
||||
pt1 = pt;
|
||||
boundingBox.Offset(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the line location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
pt2.X += x;
|
||||
pt2.Y += y;
|
||||
pt1.X += x;
|
||||
pt1.Y += y;
|
||||
boundingBox.Offset(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the line location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
pt1 += voffset;
|
||||
pt2 += voffset;
|
||||
boundingBox.Offset(voffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the line from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
pt1 *= factor;
|
||||
pt2 *= factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the line from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
pt1 = (pt1 - origin) * factor + origin;
|
||||
pt2 = (pt2 - origin) * factor + origin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the line from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
StartPoint = StartPoint.Rotate(angle);
|
||||
EndPoint = EndPoint.Rotate(angle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the line from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
StartPoint = StartPoint.Rotate(angle, origin);
|
||||
EndPoint = EndPoint.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override sealed void UpdateBounds()
|
||||
{
|
||||
if (StartPoint.X < EndPoint.X)
|
||||
{
|
||||
boundingBox.X = StartPoint.X;
|
||||
boundingBox.Width = EndPoint.X - StartPoint.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
boundingBox.X = EndPoint.X;
|
||||
boundingBox.Width = StartPoint.X - EndPoint.X;
|
||||
}
|
||||
|
||||
if (StartPoint.Y < EndPoint.Y)
|
||||
{
|
||||
boundingBox.Y = StartPoint.Y;
|
||||
boundingBox.Height = EndPoint.Y - StartPoint.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
boundingBox.Y = EndPoint.Y;
|
||||
boundingBox.Height = StartPoint.Y - EndPoint.Y;
|
||||
}
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
var angle = OpenNest.Angle.NormalizeRad(Angle() + OpenNest.Angle.HalfPI);
|
||||
|
||||
var x = Math.Cos(angle) * distance;
|
||||
var y = Math.Sin(angle) * distance;
|
||||
|
||||
var pt = new Vector(x, y);
|
||||
|
||||
return side == OffsetSide.Left
|
||||
? new Line(StartPoint + pt, EndPoint + pt)
|
||||
: new Line(EndPoint + pt, StartPoint + pt);
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
var a = pt - StartPoint;
|
||||
var b = EndPoint - StartPoint;
|
||||
var c = a.DotProduct(b);
|
||||
var side = c < 0 ? OffsetSide.Left : OffsetSide.Right;
|
||||
|
||||
return OffsetEntity(distance, side);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the line to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
var perpendicularPt = PointPerpendicularFrom(pt);
|
||||
|
||||
if (BoundingBox.Contains(perpendicularPt))
|
||||
return perpendicularPt;
|
||||
else
|
||||
return pt.DistanceTo(StartPoint) <= pt.DistanceTo(EndPoint) ? StartPoint : EndPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
Vector pt;
|
||||
return Intersects(line, out pt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
Vector pt;
|
||||
var success = Helper.Intersects(this, line, out pt);
|
||||
pts = new List<Vector>(new[] { pt });
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Line; }
|
||||
}
|
||||
}
|
||||
}
|
||||
500
OpenNest.Core/Geometry/Polygon.cs
Normal file
500
OpenNest.Core/Geometry/Polygon.cs
Normal file
@@ -0,0 +1,500 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Polygon : Entity
|
||||
{
|
||||
public List<Vector> Vertices;
|
||||
|
||||
public Polygon()
|
||||
{
|
||||
Vertices = new List<Vector>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the polygon if it's not already.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
return;
|
||||
|
||||
var first = Vertices.First();
|
||||
var last = Vertices.Last();
|
||||
|
||||
if (first != last)
|
||||
Vertices.Add(first);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the polygon is closed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsClosed()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
return false;
|
||||
|
||||
return (Vertices.First() == Vertices.Last());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the polygon is self intersecting.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsComplex()
|
||||
{
|
||||
var lines = ToLines();
|
||||
|
||||
for (int i = 0; i < lines.Count; ++i)
|
||||
{
|
||||
var line1 = lines[i];
|
||||
|
||||
for (int j = i; j < lines.Count; ++j)
|
||||
{
|
||||
var line2 = lines[j];
|
||||
|
||||
if (line1.Intersects(line2))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Area of the polygon.
|
||||
/// </summary>
|
||||
/// <returns>Returns the area or 0 if the polygon is NOT closed.</returns>
|
||||
public double Area()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
return 0.0;
|
||||
|
||||
return Math.Abs(CalculateArea());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Distance around the polygon.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Perimeter()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
return 0.0;
|
||||
|
||||
double sum = 0.0;
|
||||
|
||||
var last = Vertices[0];
|
||||
|
||||
for (int i = 1; i < Vertices.Count; ++i)
|
||||
{
|
||||
var current = Vertices[i];
|
||||
sum += last.DistanceTo(current);
|
||||
last = current;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation direction of the polygon.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public RotationType RotationDirection()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
throw new Exception("Not enough points to determine direction. Must have at least 3 points.");
|
||||
|
||||
return CalculateArea() > 0 ? RotationType.CCW : RotationType.CW;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the polygon to a group of lines.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Line> ToLines()
|
||||
{
|
||||
var list = new List<Line>();
|
||||
|
||||
if (Vertices.Count < 2)
|
||||
return list;
|
||||
|
||||
var last = Vertices[0];
|
||||
|
||||
for (int i = 1; i < Vertices.Count; ++i)
|
||||
{
|
||||
var current = Vertices[i];
|
||||
list.Add(new Line(last, current));
|
||||
last = current;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the area of the polygon.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the area of the polygon.
|
||||
/// * Positive number = counter-clockwise rotation
|
||||
/// * Negative number = clockwise rotation
|
||||
/// </returns>
|
||||
private double CalculateArea()
|
||||
{
|
||||
double xsum = 0;
|
||||
double ysum = 0;
|
||||
|
||||
for (int i = 0; i < Vertices.Count - 1; ++i)
|
||||
{
|
||||
var current = Vertices[i];
|
||||
var next = Vertices[i + 1];
|
||||
|
||||
xsum += current.X * next.Y;
|
||||
ysum += current.Y * next.X;
|
||||
}
|
||||
|
||||
return (xsum - ysum) * 0.5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Distance around the polygon.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get { return Perimeter(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction of the polygon.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
Vertices.Reverse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
if (Vertices.Count == 0)
|
||||
return;
|
||||
|
||||
var first = Vertices[0];
|
||||
var offset = new Vector(x - first.X, y - first.Y);
|
||||
|
||||
Vertices.ForEach(vertex => vertex += offset);
|
||||
boundingBox.Offset(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
if (Vertices.Count == 0)
|
||||
return;
|
||||
|
||||
var first = Vertices[0];
|
||||
var offset = pt - first;
|
||||
|
||||
Vertices.ForEach(vertex => vertex += offset);
|
||||
boundingBox.Offset(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = Vertices[i].Offset(x, y);
|
||||
|
||||
boundingBox.Offset(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = Vertices[i].Offset(voffset);
|
||||
|
||||
boundingBox.Offset(voffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the polygon from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] *= factor;
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the polygon from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = (Vertices[i] - origin) * factor + origin;
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the polygon from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = Vertices[i].Rotate(angle);
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the polygon from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = Vertices[i].Rotate(angle, origin);
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
if (Vertices.Count == 0)
|
||||
return;
|
||||
|
||||
var first = Vertices[0];
|
||||
var minX = first.X;
|
||||
var maxX = first.X;
|
||||
var minY = first.Y;
|
||||
var maxY = first.Y;
|
||||
|
||||
for (int i = 1; i < Vertices.Count; ++i)
|
||||
{
|
||||
var vertex = Vertices[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
}
|
||||
|
||||
boundingBox.X = minX;
|
||||
boundingBox.Y = minY;
|
||||
boundingBox.Width = maxX - minX;
|
||||
boundingBox.Height = maxY - minY;
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the polygon to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
var lines = ToLines();
|
||||
|
||||
if (lines.Count == 0)
|
||||
return Vector.Invalid;
|
||||
|
||||
Vector closestPt = lines[0].ClosestPointTo(pt);
|
||||
double distance = closestPt.DistanceTo(pt);
|
||||
|
||||
for (int i = 1; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
var closestPt2 = line.ClosestPointTo(pt);
|
||||
var distance2 = closestPt2.DistanceTo(pt);
|
||||
|
||||
if (distance2 < distance)
|
||||
{
|
||||
closestPt = closestPt2;
|
||||
distance = distance2;
|
||||
}
|
||||
}
|
||||
|
||||
return closestPt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(line, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(line, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(shape, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(shape, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Polygon; }
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
for (int i = Vertices.Count - 1; i > 0; i--)
|
||||
{
|
||||
var vertex = Vertices[i];
|
||||
var nextVertex = Vertices[i - 1];
|
||||
|
||||
if (vertex == nextVertex)
|
||||
Vertices.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public double FindBestRotation(double stepAngle)
|
||||
{
|
||||
var entities = new List<Entity>(ToLines());
|
||||
return entities.FindBestRotation(stepAngle);
|
||||
}
|
||||
|
||||
public double FindBestRotation(double stepAngle, double startAngle, double endAngle)
|
||||
{
|
||||
var entities = new List<Entity>(ToLines());
|
||||
return entities.FindBestRotation(stepAngle, startAngle, endAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
569
OpenNest.Core/Geometry/Shape.cs
Normal file
569
OpenNest.Core/Geometry/Shape.cs
Normal file
@@ -0,0 +1,569 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Shape : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// Entities that make up the shape.
|
||||
/// </summary>
|
||||
public List<Entity> Entities;
|
||||
|
||||
public Shape()
|
||||
{
|
||||
Entities = new List<Entity>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the shape is closed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsClosed()
|
||||
{
|
||||
if (Entities.Count == 0)
|
||||
return false;
|
||||
|
||||
var first = Entities[0];
|
||||
Vector firstStartPoint;
|
||||
Vector firstEndPoint;
|
||||
|
||||
switch (first.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)first;
|
||||
firstStartPoint = arc.StartPoint();
|
||||
firstEndPoint = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
return Entities.Count == 1;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)first;
|
||||
firstStartPoint = line.StartPoint;
|
||||
firstEndPoint = line.EndPoint;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
return false;
|
||||
}
|
||||
|
||||
var endpt = firstEndPoint;
|
||||
|
||||
Entity geo = null;
|
||||
|
||||
for (int i = 1; i < Entities.Count; ++i)
|
||||
{
|
||||
geo = Entities[i];
|
||||
|
||||
switch (geo.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)geo;
|
||||
|
||||
if (arc.StartPoint() != endpt)
|
||||
return false;
|
||||
|
||||
endpt = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
return Entities.Count == 1;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)geo;
|
||||
|
||||
if (line.StartPoint != endpt)
|
||||
return false;
|
||||
|
||||
endpt = line.EndPoint;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (geo == null)
|
||||
return false;
|
||||
|
||||
var last = geo;
|
||||
Vector lastEndPoint;
|
||||
|
||||
switch (last.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)last;
|
||||
lastEndPoint = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)last;
|
||||
lastEndPoint = line.EndPoint;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
return false;
|
||||
}
|
||||
|
||||
return lastEndPoint == firstStartPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the area.
|
||||
/// </summary>
|
||||
/// <returns>Returns the area or 0 if the shape is NOT closed.</returns>
|
||||
public double Area()
|
||||
{
|
||||
// Check if the shape is closed so we can get the area.
|
||||
if (!IsClosed())
|
||||
return 0;
|
||||
|
||||
// If the shape is closed and only one entity in the geometry
|
||||
// then that entity would have to be a circle.
|
||||
if (Entities.Count == 1)
|
||||
{
|
||||
var circle = Entities[0] as Circle;
|
||||
return circle == null ? 0 : circle.Area();
|
||||
}
|
||||
|
||||
return ToPolygon().Area();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Joins all overlapping lines and arcs.
|
||||
/// </summary>
|
||||
public void Optimize()
|
||||
{
|
||||
var lines = new List<Line>();
|
||||
var arcs = new List<Arc>();
|
||||
|
||||
foreach (var geo in Entities)
|
||||
{
|
||||
switch (geo.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
arcs.Add((Arc)geo);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
lines.Add((Line)geo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Helper.Optimize(lines);
|
||||
Helper.Optimize(arcs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the shape to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <param name="entity">Entity that contains the point.</param>
|
||||
/// <returns></returns>
|
||||
public Vector ClosestPointTo(Vector pt, out Entity entity)
|
||||
{
|
||||
if (Entities.Count == 0)
|
||||
{
|
||||
entity = null;
|
||||
return Vector.Invalid;
|
||||
}
|
||||
|
||||
var first = Entities[0];
|
||||
|
||||
Vector closestPt = first.ClosestPointTo(pt);
|
||||
double distance = closestPt.DistanceTo(pt);
|
||||
|
||||
entity = first;
|
||||
|
||||
for (int i = 1; i < Entities.Count; i++)
|
||||
{
|
||||
var entity2 = Entities[i];
|
||||
var closestPt2 = entity2.ClosestPointTo(pt);
|
||||
var distance2 = closestPt2.DistanceTo(pt);
|
||||
|
||||
if (distance2 < distance)
|
||||
{
|
||||
closestPt = closestPt2;
|
||||
distance = distance2;
|
||||
entity = entity2;
|
||||
}
|
||||
}
|
||||
|
||||
return closestPt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the shape to a polygon.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Polygon ToPolygon(int arcSegments = 1000)
|
||||
{
|
||||
var polygon = new Polygon();
|
||||
|
||||
foreach (var entity in Entities)
|
||||
{
|
||||
switch (entity.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)entity;
|
||||
polygon.Vertices.AddRange(arc.ToPoints(arcSegments));
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)entity;
|
||||
polygon.Vertices.AddRange(new[]
|
||||
{
|
||||
line.StartPoint,
|
||||
line.EndPoint
|
||||
});
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
var circle = (Circle)entity;
|
||||
polygon.Vertices.AddRange(circle.ToPoints(arcSegments));
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
polygon.Close();
|
||||
polygon.Cleanup();
|
||||
|
||||
return polygon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction of the shape.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
Entities.ForEach(e => e.Reverse());
|
||||
Entities.Reverse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear distance of the shape.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get { return Entities.Sum(geo => geo.Length); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the shape location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
Entities.ForEach(e => e.Offset(x, y));
|
||||
boundingBox.Offset(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the shape location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
Entities.ForEach(e => e.Offset(voffset));
|
||||
boundingBox.Offset(voffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the shape from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
Entities.ForEach(e => e.Scale(factor));
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the shape from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
Entities.ForEach(e => e.Scale(factor, origin));
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the shape from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
Entities.ForEach(e => e.Rotate(angle));
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the shape from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
Entities.ForEach(e => e.Rotate(angle, origin));
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
boundingBox = Entities.Select(geo => geo.BoundingBox)
|
||||
.ToList()
|
||||
.GetBoundingBox();
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
var offsetShape = new Shape();
|
||||
var definedShape = new DefinedShape(this);
|
||||
|
||||
Entity lastEntity = null;
|
||||
Entity lastOffsetEntity = null;
|
||||
|
||||
foreach (var entity in definedShape.Perimeter.Entities)
|
||||
{
|
||||
var offsetEntity = entity.OffsetEntity(distance, side);
|
||||
|
||||
if (offsetEntity == null)
|
||||
continue;
|
||||
|
||||
switch (entity.Type)
|
||||
{
|
||||
case EntityType.Line:
|
||||
{
|
||||
var line = (Line)entity;
|
||||
var offsetLine = (Line)offsetEntity;
|
||||
|
||||
if (lastOffsetEntity != null && lastOffsetEntity.Type == EntityType.Line)
|
||||
{
|
||||
var lastLine = lastEntity as Line;
|
||||
var lastOffsetLine = lastOffsetEntity as Line;
|
||||
|
||||
if (lastLine == null || lastOffsetLine == null)
|
||||
continue;
|
||||
|
||||
Vector intersection;
|
||||
|
||||
if (Helper.Intersects(offsetLine, lastOffsetLine, out intersection))
|
||||
{
|
||||
offsetLine.StartPoint = intersection;
|
||||
lastOffsetLine.EndPoint = intersection;
|
||||
}
|
||||
else
|
||||
{
|
||||
var arc = new Arc(
|
||||
line.StartPoint,
|
||||
distance,
|
||||
line.StartPoint.AngleTo(lastOffsetLine.EndPoint),
|
||||
line.StartPoint.AngleTo(offsetLine.StartPoint),
|
||||
side == OffsetSide.Left
|
||||
);
|
||||
|
||||
offsetShape.Entities.Add(arc);
|
||||
}
|
||||
}
|
||||
|
||||
offsetShape.Entities.Add(offsetLine);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
offsetShape.Entities.Add(offsetEntity);
|
||||
break;
|
||||
}
|
||||
|
||||
lastOffsetEntity = offsetEntity;
|
||||
lastEntity = entity;
|
||||
}
|
||||
|
||||
foreach (var cutout in definedShape.Cutouts)
|
||||
offsetShape.Entities.AddRange(((Shape)cutout.OffsetEntity(distance, side)).Entities);
|
||||
|
||||
return offsetShape;
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the shape to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
Entity entity;
|
||||
return ClosestPointTo(pt, out entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(line, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(line, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Shape; }
|
||||
}
|
||||
|
||||
public double FindBestRotation(double stepAngle)
|
||||
{
|
||||
return Entities.FindBestRotation(stepAngle);
|
||||
}
|
||||
|
||||
public double FindBestRotation(double stepAngle, double startAngle, double endAngle)
|
||||
{
|
||||
return Entities.FindBestRotation(stepAngle, startAngle, endAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
990
OpenNest.Core/Helper.cs
Normal file
990
OpenNest.Core/Helper.cs
Normal file
@@ -0,0 +1,990 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Rounds a number down to the nearest factor.
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <param name="factor"></param>
|
||||
/// <returns></returns>
|
||||
public static double RoundDownToNearest(double num, double factor)
|
||||
{
|
||||
return factor.IsEqualTo(0) ? num : Math.Floor(num / factor) * factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds a number up to the nearest factor.
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <param name="factor"></param>
|
||||
/// <returns></returns>
|
||||
public static double RoundUpToNearest(double num, double factor)
|
||||
{
|
||||
return factor.IsEqualTo(0) ? num : Math.Ceiling(num / factor) * factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds a number to the nearest factor using midpoint rounding convention.
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <param name="factor"></param>
|
||||
/// <returns></returns>
|
||||
public static double RoundToNearest(double num, double factor)
|
||||
{
|
||||
return factor.IsEqualTo(0) ? num : Math.Round(num / factor) * factor;
|
||||
}
|
||||
|
||||
public static void Optimize(IList<Arc> arcs)
|
||||
{
|
||||
for (int i = 0; i < arcs.Count; ++i)
|
||||
{
|
||||
var arc = arcs[i];
|
||||
|
||||
var coradialArcs = arcs.GetCoradialArs(arc, i);
|
||||
int index = 0;
|
||||
|
||||
while (index < coradialArcs.Count)
|
||||
{
|
||||
Arc arc2 = coradialArcs[index];
|
||||
Arc joinArc;
|
||||
|
||||
if (!TryJoinArcs(arc, arc2, out joinArc))
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
coradialArcs.Remove(arc2);
|
||||
arcs.Remove(arc2);
|
||||
|
||||
arc = joinArc;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
arcs[i] = arc;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Optimize(IList<Line> lines)
|
||||
{
|
||||
for (int i = 0; i < lines.Count; ++i)
|
||||
{
|
||||
var line = lines[i];
|
||||
|
||||
var collinearLines = lines.GetCollinearLines(line, i);
|
||||
var index = 0;
|
||||
|
||||
while (index < collinearLines.Count)
|
||||
{
|
||||
Line line2 = collinearLines[index];
|
||||
Line joinLine;
|
||||
|
||||
if (!TryJoinLines(line, line2, out joinLine))
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
collinearLines.Remove(line2);
|
||||
lines.Remove(line2);
|
||||
|
||||
line = joinLine;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
lines[i] = line;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryJoinLines(Line line1, Line line2, out Line lineOut)
|
||||
{
|
||||
lineOut = null;
|
||||
|
||||
if (line1 == line2)
|
||||
return false;
|
||||
|
||||
if (!line1.IsCollinearTo(line2))
|
||||
return false;
|
||||
|
||||
bool onPoint = false;
|
||||
|
||||
if (line1.StartPoint == line2.StartPoint)
|
||||
onPoint = true;
|
||||
else if (line1.StartPoint == line2.EndPoint)
|
||||
onPoint = true;
|
||||
else if (line1.EndPoint == line2.StartPoint)
|
||||
onPoint = true;
|
||||
else if (line1.EndPoint == line2.EndPoint)
|
||||
onPoint = true;
|
||||
|
||||
var t1 = line1.StartPoint.Y > line1.EndPoint.Y ? line1.StartPoint.Y : line1.EndPoint.Y;
|
||||
var t2 = line2.StartPoint.Y > line2.EndPoint.Y ? line2.StartPoint.Y : line2.EndPoint.Y;
|
||||
var b1 = line1.StartPoint.Y < line1.EndPoint.Y ? line1.StartPoint.Y : line1.EndPoint.Y;
|
||||
var b2 = line2.StartPoint.Y < line2.EndPoint.Y ? line2.StartPoint.Y : line2.EndPoint.Y;
|
||||
var l1 = line1.StartPoint.X < line1.EndPoint.X ? line1.StartPoint.X : line1.EndPoint.X;
|
||||
var l2 = line2.StartPoint.X < line2.EndPoint.X ? line2.StartPoint.X : line2.EndPoint.X;
|
||||
var r1 = line1.StartPoint.X > line1.EndPoint.X ? line1.StartPoint.X : line1.EndPoint.X;
|
||||
var r2 = line2.StartPoint.X > line2.EndPoint.X ? line2.StartPoint.X : line2.EndPoint.X;
|
||||
|
||||
if (!onPoint)
|
||||
{
|
||||
if (t1 < b2 - Tolerance.Epsilon) return false;
|
||||
if (b1 > t2 + Tolerance.Epsilon) return false;
|
||||
if (l1 > r2 + Tolerance.Epsilon) return false;
|
||||
if (r1 < l2 - Tolerance.Epsilon) return false;
|
||||
}
|
||||
|
||||
var l = l1 < l2 ? l1 : l2;
|
||||
var r = r1 > r2 ? r1 : r2;
|
||||
var t = t1 > t2 ? t1 : t2;
|
||||
var b = b1 < b2 ? b1 : b2;
|
||||
|
||||
if (!line1.IsVertical() && line1.Slope() < 0)
|
||||
lineOut = new Line(new Vector(l, t), new Vector(r, b));
|
||||
else
|
||||
lineOut = new Line(new Vector(l, b), new Vector(r, t));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryJoinArcs(Arc arc1, Arc arc2, out Arc arcOut)
|
||||
{
|
||||
arcOut = null;
|
||||
|
||||
if (arc1 == arc2)
|
||||
return false;
|
||||
|
||||
if (arc1.Center != arc2.Center)
|
||||
return false;
|
||||
|
||||
if (!arc1.Radius.IsEqualTo(arc2.Radius))
|
||||
return false;
|
||||
|
||||
if (arc1.StartAngle > arc1.EndAngle)
|
||||
arc1.StartAngle -= Angle.TwoPI;
|
||||
|
||||
if (arc2.StartAngle > arc2.EndAngle)
|
||||
arc2.StartAngle -= Angle.TwoPI;
|
||||
|
||||
if (arc1.EndAngle < arc2.StartAngle || arc1.StartAngle > arc2.EndAngle)
|
||||
return false;
|
||||
|
||||
var startAngle = arc1.StartAngle < arc2.StartAngle ? arc1.StartAngle : arc2.StartAngle;
|
||||
var endAngle = arc1.EndAngle > arc2.EndAngle ? arc1.EndAngle : arc2.EndAngle;
|
||||
|
||||
if (startAngle < 0) startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0) endAngle += Angle.TwoPI;
|
||||
|
||||
arcOut = new Arc(arc1.Center, arc1.Radius, startAngle, endAngle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<Line> GetCollinearLines(this IList<Line> lines, Line line, int startIndex)
|
||||
{
|
||||
var collinearLines = new List<Line>();
|
||||
|
||||
Parallel.For(startIndex, lines.Count, index =>
|
||||
{
|
||||
var compareLine = lines[index];
|
||||
|
||||
if (Object.ReferenceEquals(line, compareLine))
|
||||
return;
|
||||
|
||||
if (!line.IsCollinearTo(compareLine))
|
||||
return;
|
||||
|
||||
lock (collinearLines)
|
||||
{
|
||||
collinearLines.Add(compareLine);
|
||||
}
|
||||
});
|
||||
|
||||
return collinearLines;
|
||||
}
|
||||
|
||||
private static List<Arc> GetCoradialArs(this IList<Arc> arcs, Arc arc, int startIndex)
|
||||
{
|
||||
var coradialArcs = new List<Arc>();
|
||||
|
||||
Parallel.For(startIndex, arcs.Count, index =>
|
||||
{
|
||||
var compareArc = arcs[index];
|
||||
|
||||
if (Object.ReferenceEquals(arc, compareArc))
|
||||
return;
|
||||
|
||||
if (!arc.IsCoradialTo(compareArc))
|
||||
return;
|
||||
|
||||
lock (coradialArcs)
|
||||
{
|
||||
coradialArcs.Add(compareArc);
|
||||
}
|
||||
});
|
||||
|
||||
return coradialArcs;
|
||||
}
|
||||
|
||||
public static List<Shape> GetShapes(IEnumerable<Entity> entities)
|
||||
{
|
||||
var lines = new List<Line>();
|
||||
var arcs = new List<Arc>();
|
||||
var circles = new List<Circle>();
|
||||
var shapes = new List<Shape>();
|
||||
|
||||
var entities2 = new Queue<Entity>(entities);
|
||||
|
||||
while (entities2.Count > 0)
|
||||
{
|
||||
var entity = entities2.Dequeue();
|
||||
|
||||
switch (entity.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
arcs.Add((Arc)entity);
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
circles.Add((Circle)entity);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
lines.Add((Line)entity);
|
||||
break;
|
||||
|
||||
case EntityType.Shape:
|
||||
var shape = (Shape)entity;
|
||||
shape.Entities.ForEach(e => entities2.Enqueue(e));
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var circle in circles)
|
||||
{
|
||||
var shape = new Shape();
|
||||
shape.Entities.Add(circle);
|
||||
shape.UpdateBounds();
|
||||
shapes.Add(shape);
|
||||
}
|
||||
|
||||
var entityList = new List<Entity>();
|
||||
|
||||
entityList.AddRange(lines);
|
||||
entityList.AddRange(arcs);
|
||||
|
||||
while (entityList.Count > 0)
|
||||
{
|
||||
var next = entityList[0];
|
||||
var shape = new Shape();
|
||||
shape.Entities.Add(next);
|
||||
|
||||
entityList.RemoveAt(0);
|
||||
|
||||
Vector startPoint = new Vector();
|
||||
Entity connected;
|
||||
|
||||
switch (next.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)next;
|
||||
startPoint = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)next;
|
||||
startPoint = line.EndPoint;
|
||||
break;
|
||||
}
|
||||
|
||||
while ((connected = GetConnected(startPoint, entityList)) != null)
|
||||
{
|
||||
shape.Entities.Add(connected);
|
||||
entityList.Remove(connected);
|
||||
|
||||
switch (connected.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)connected;
|
||||
startPoint = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)connected;
|
||||
startPoint = line.EndPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
shape.UpdateBounds();
|
||||
shapes.Add(shape);
|
||||
}
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
internal static Entity GetConnected(Vector pt, IEnumerable<Entity> geometry)
|
||||
{
|
||||
foreach (var geo in geometry)
|
||||
{
|
||||
switch (geo.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)geo;
|
||||
|
||||
if (arc.StartPoint() == pt)
|
||||
return arc;
|
||||
|
||||
if (arc.EndPoint() == pt)
|
||||
{
|
||||
arc.Reverse();
|
||||
return arc;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)geo;
|
||||
|
||||
if (line.StartPoint == pt)
|
||||
return line;
|
||||
|
||||
if (line.EndPoint == pt)
|
||||
{
|
||||
line.Reverse();
|
||||
return line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc1, Arc arc2, out List<Vector> pts)
|
||||
{
|
||||
var c1 = new Circle(arc1.Center, arc1.Radius);
|
||||
var c2 = new Circle(arc2.Center, arc2.Radius);
|
||||
|
||||
if (!Intersects(c1, c2, out pts))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt =>
|
||||
Angle.IsBetweenRad(arc1.Center.AngleTo(pt), arc1.StartAngle, arc1.EndAngle, arc1.IsReversed) &&
|
||||
Angle.IsBetweenRad(arc2.Center.AngleTo(pt), arc2.StartAngle, arc2.EndAngle, arc2.IsReversed))
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc, Circle circle, out List<Vector> pts)
|
||||
{
|
||||
var c1 = new Circle(arc.Center, arc.Radius);
|
||||
|
||||
if (!Intersects(c1, circle, out pts))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc, Line line, out List<Vector> pts)
|
||||
{
|
||||
var c1 = new Circle(arc.Center, arc.Radius);
|
||||
|
||||
if (!Intersects(c1, line, out pts))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc, Shape shape, out List<Vector> pts)
|
||||
{
|
||||
var pts2 = new List<Vector>();
|
||||
|
||||
foreach (var geo in shape.Entities)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
geo.Intersects(arc, out pts3);
|
||||
pts2.AddRange(pts3);
|
||||
}
|
||||
|
||||
pts = pts2.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc, Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
var pts2 = new List<Vector>();
|
||||
var lines = polygon.ToLines();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
Intersects(arc, line, out pts3);
|
||||
pts2.AddRange(pts3);
|
||||
}
|
||||
|
||||
pts = pts2.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Circle circle1, Circle circle2, out List<Vector> pts)
|
||||
{
|
||||
var distance = circle1.Center.DistanceTo(circle2.Center);
|
||||
|
||||
// check if circles are too far apart
|
||||
if (distance > circle1.Radius + circle2.Radius)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if one circle contains the other
|
||||
if (distance < Math.Abs(circle1.Radius - circle2.Radius))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
var d = circle2.Center - circle1.Center;
|
||||
var a = (circle1.Radius * circle1.Radius - circle2.Radius * circle2.Radius + distance * distance) / (2.0 * distance);
|
||||
var h = Math.Sqrt(circle1.Radius * circle1.Radius - a * a);
|
||||
|
||||
var pt = new Vector(
|
||||
circle1.Center.X + (a * d.X) / distance,
|
||||
circle1.Center.Y + (a * d.Y) / distance);
|
||||
|
||||
var i1 = new Vector(
|
||||
pt.X + (h * d.Y) / distance,
|
||||
pt.Y - (h * d.X) / distance);
|
||||
|
||||
var i2 = new Vector(
|
||||
pt.X - (h * d.Y) / distance,
|
||||
pt.Y + (h * d.X) / distance);
|
||||
|
||||
pts = i1 != i2 ? new List<Vector> { i1, i2 } : new List<Vector> { i1 };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Circle circle, Line line, out List<Vector> pts)
|
||||
{
|
||||
var d1 = line.EndPoint - line.StartPoint;
|
||||
var d2 = line.StartPoint - circle.Center;
|
||||
|
||||
var a = d1.X * d1.X + d1.Y * d1.Y;
|
||||
var b = (d1.X * d2.X + d1.Y * d2.Y) * 2;
|
||||
var c = (d2.X * d2.X + d2.Y * d2.Y) - circle.Radius * circle.Radius;
|
||||
|
||||
var det = b * b - 4 * a * c;
|
||||
|
||||
if ((a <= Tolerance.Epsilon) || (det < 0))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
double t;
|
||||
pts = new List<Vector>();
|
||||
|
||||
if (det.IsEqualTo(0))
|
||||
{
|
||||
t = -b / (2 * a);
|
||||
var pt1 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y);
|
||||
|
||||
if (line.BoundingBox.Contains(pt1))
|
||||
pts.Add(pt1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
t = (-b + Math.Sqrt(det)) / (2 * a);
|
||||
var pt2 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y);
|
||||
|
||||
if (line.BoundingBox.Contains(pt2))
|
||||
pts.Add(pt2);
|
||||
|
||||
t = (-b - Math.Sqrt(det)) / (2 * a);
|
||||
var pt3 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y);
|
||||
|
||||
if (line.BoundingBox.Contains(pt3))
|
||||
pts.Add(pt3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Circle circle, Shape shape, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
foreach (var geo in shape.Entities)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
geo.Intersects(circle, out pts3);
|
||||
pts.AddRange(pts3);
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Circle circle, Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
var lines = polygon.ToLines();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
Intersects(circle, line, out pts3);
|
||||
pts.AddRange(pts3);
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Line line1, Line line2, out Vector pt)
|
||||
{
|
||||
var a1 = line1.EndPoint.Y - line1.StartPoint.Y;
|
||||
var b1 = line1.StartPoint.X - line1.EndPoint.X;
|
||||
var c1 = a1 * line1.StartPoint.X + b1 * line1.StartPoint.Y;
|
||||
|
||||
var a2 = line2.EndPoint.Y - line2.StartPoint.Y;
|
||||
var b2 = line2.StartPoint.X - line2.EndPoint.X;
|
||||
var c2 = a2 * line2.StartPoint.X + b2 * line2.StartPoint.Y;
|
||||
|
||||
var d = a1 * b2 - a2 * b1;
|
||||
|
||||
if (d.IsEqualTo(0.0))
|
||||
{
|
||||
pt = Vector.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
var x = (b2 * c1 - b1 * c2) / d;
|
||||
var y = (a1 * c2 - a2 * c1) / d;
|
||||
|
||||
pt = new Vector(x, y);
|
||||
return line1.BoundingBox.Contains(pt) && line2.BoundingBox.Contains(pt);
|
||||
}
|
||||
|
||||
internal static bool Intersects(Line line, Shape shape, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
foreach (var geo in shape.Entities)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
geo.Intersects(line, out pts3);
|
||||
pts.AddRange(pts3);
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Line line, Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
var lines = polygon.ToLines();
|
||||
|
||||
foreach (var line2 in lines)
|
||||
{
|
||||
Vector pt;
|
||||
|
||||
if (Intersects(line, line2, out pt))
|
||||
pts.Add(pt);
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Shape shape1, Shape shape2, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
for (int i = 0; i < shape1.Entities.Count; i++)
|
||||
{
|
||||
var geo1 = shape1.Entities[i];
|
||||
|
||||
for (int j = 0; j < shape2.Entities.Count; j++)
|
||||
{
|
||||
List<Vector> pts2;
|
||||
bool success = false;
|
||||
|
||||
var geo2 = shape2.Entities[j];
|
||||
|
||||
switch (geo2.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
success = geo1.Intersects((Arc)geo2, out pts2);
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
success = geo1.Intersects((Circle)geo2, out pts2);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
success = geo1.Intersects((Line)geo2, out pts2);
|
||||
break;
|
||||
|
||||
case EntityType.Shape:
|
||||
success = geo1.Intersects((Shape)geo2, out pts2);
|
||||
break;
|
||||
|
||||
case EntityType.Polygon:
|
||||
success = geo1.Intersects((Polygon)geo2, out pts2);
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (success)
|
||||
pts.AddRange(pts2);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Shape shape, Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
var lines = polygon.ToLines();
|
||||
|
||||
for (int i = 0; i < shape.Entities.Count; i++)
|
||||
{
|
||||
var geo = shape.Entities[i];
|
||||
|
||||
for (int j = 0; j < lines.Count; j++)
|
||||
{
|
||||
var line = lines[j];
|
||||
|
||||
List<Vector> pts2;
|
||||
|
||||
if (geo.Intersects(line, out pts2))
|
||||
pts.AddRange(pts2);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Polygon polygon1, Polygon polygon2, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
var lines1 = polygon1.ToLines();
|
||||
var lines2 = polygon2.ToLines();
|
||||
|
||||
for (int i = 0; i < lines1.Count; i++)
|
||||
{
|
||||
var line1 = lines1[i];
|
||||
|
||||
for (int j = 0; j < lines2.Count; j++)
|
||||
{
|
||||
var line2 = lines2[j];
|
||||
Vector pt;
|
||||
|
||||
if (Intersects(line1, line2, out pt))
|
||||
pts.Add(pt);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
public static double ClosestDistanceLeft(Box box, List<Box> boxes)
|
||||
{
|
||||
var closestDistance = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
var compareBox = boxes[i];
|
||||
|
||||
RelativePosition pos;
|
||||
|
||||
if (!box.IsHorizontalTo(compareBox, out pos))
|
||||
continue;
|
||||
|
||||
if (pos != RelativePosition.Right)
|
||||
continue;
|
||||
|
||||
var distance = box.Left - compareBox.Right;
|
||||
|
||||
if (distance < closestDistance)
|
||||
closestDistance = distance;
|
||||
}
|
||||
|
||||
return closestDistance == double.MaxValue ? double.NaN : closestDistance;
|
||||
}
|
||||
|
||||
public static double ClosestDistanceRight(Box box, List<Box> boxes)
|
||||
{
|
||||
var closestDistance = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
var compareBox = boxes[i];
|
||||
|
||||
RelativePosition pos;
|
||||
|
||||
if (!box.IsHorizontalTo(compareBox, out pos))
|
||||
continue;
|
||||
|
||||
if (pos != RelativePosition.Left)
|
||||
continue;
|
||||
|
||||
var distance = compareBox.Left - box.Right;
|
||||
|
||||
if (distance < closestDistance)
|
||||
closestDistance = distance;
|
||||
}
|
||||
|
||||
return closestDistance == double.MaxValue ? double.NaN : closestDistance;
|
||||
}
|
||||
|
||||
public static double ClosestDistanceUp(Box box, List<Box> boxes)
|
||||
{
|
||||
var closestDistance = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
var compareBox = boxes[i];
|
||||
|
||||
RelativePosition pos;
|
||||
|
||||
if (!box.IsVerticalTo(compareBox, out pos))
|
||||
continue;
|
||||
|
||||
if (pos != RelativePosition.Bottom)
|
||||
continue;
|
||||
|
||||
var distance = compareBox.Bottom - box.Top;
|
||||
|
||||
if (distance < closestDistance)
|
||||
closestDistance = distance;
|
||||
}
|
||||
|
||||
return closestDistance == double.MaxValue ? double.NaN : closestDistance;
|
||||
}
|
||||
|
||||
public static double ClosestDistanceDown(Box box, List<Box> boxes)
|
||||
{
|
||||
var closestDistance = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
var compareBox = boxes[i];
|
||||
|
||||
RelativePosition pos;
|
||||
|
||||
if (!box.IsVerticalTo(compareBox, out pos))
|
||||
continue;
|
||||
|
||||
if (pos != RelativePosition.Top)
|
||||
continue;
|
||||
|
||||
var distance = box.Bottom - compareBox.Top;
|
||||
|
||||
if (distance < closestDistance)
|
||||
closestDistance = distance;
|
||||
}
|
||||
|
||||
return closestDistance == double.MaxValue ? double.NaN : closestDistance;
|
||||
}
|
||||
|
||||
public static Box GetLargestBoxVertically(Vector pt, Box bounds, IEnumerable<Box> boxes)
|
||||
{
|
||||
var verticalBoxes = boxes.Where(b => !(b.Left > pt.X || b.Right < pt.X)).ToList();
|
||||
|
||||
#region Find Top/Bottom Limits
|
||||
|
||||
var top = double.MaxValue;
|
||||
var btm = double.MinValue;
|
||||
|
||||
foreach (var box in verticalBoxes)
|
||||
{
|
||||
var boxBtm = box.Bottom;
|
||||
var boxTop = box.Top;
|
||||
|
||||
if (boxBtm > pt.Y && boxBtm < top)
|
||||
top = boxBtm;
|
||||
|
||||
else if (box.Top < pt.Y && boxTop > btm)
|
||||
btm = boxTop;
|
||||
}
|
||||
|
||||
if (top == double.MaxValue)
|
||||
{
|
||||
if (bounds.Top > pt.Y)
|
||||
top = bounds.Top;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
if (btm == double.MinValue)
|
||||
{
|
||||
if (bounds.Bottom < pt.Y)
|
||||
btm = bounds.Bottom;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
var horizontalBoxes = boxes.Where(b => !(b.Bottom >= top || b.Top <= btm)).ToList();
|
||||
|
||||
#region Find Left/Right Limits
|
||||
|
||||
var lft = double.MinValue;
|
||||
var rgt = double.MaxValue;
|
||||
|
||||
foreach (var box in horizontalBoxes)
|
||||
{
|
||||
var boxLft = box.Left;
|
||||
var boxRgt = box.Right;
|
||||
|
||||
if (boxLft > pt.X && boxLft < rgt)
|
||||
rgt = boxLft;
|
||||
|
||||
else if (boxRgt < pt.X && boxRgt > lft)
|
||||
lft = boxRgt;
|
||||
}
|
||||
|
||||
if (rgt == double.MaxValue)
|
||||
{
|
||||
if (bounds.Right > pt.X)
|
||||
rgt = bounds.Right;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
if (lft == double.MinValue)
|
||||
{
|
||||
if (bounds.Left < pt.X)
|
||||
lft = bounds.Left;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return new Box(lft, btm, rgt - lft, top - btm);
|
||||
}
|
||||
|
||||
public static Box GetLargestBoxHorizontally(Vector pt, Box bounds, IEnumerable<Box> boxes)
|
||||
{
|
||||
var horizontalBoxes = boxes.Where(b => !(b.Bottom > pt.Y || b.Top < pt.Y)).ToList();
|
||||
|
||||
#region Find Left/Right Limits
|
||||
|
||||
var lft = double.MinValue;
|
||||
var rgt = double.MaxValue;
|
||||
|
||||
foreach (var box in horizontalBoxes)
|
||||
{
|
||||
var boxLft = box.Left;
|
||||
var boxRgt = box.Right;
|
||||
|
||||
if (boxLft > pt.X && boxLft < rgt)
|
||||
rgt = boxLft;
|
||||
|
||||
else if (boxRgt < pt.X && boxRgt > lft)
|
||||
lft = boxRgt;
|
||||
}
|
||||
|
||||
if (rgt == double.MaxValue)
|
||||
{
|
||||
if (bounds.Right > pt.X)
|
||||
rgt = bounds.Right;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
if (lft == double.MinValue)
|
||||
{
|
||||
if (bounds.Left < pt.X)
|
||||
lft = bounds.Left;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
var verticalBoxes = boxes.Where(b => !(b.Left >= rgt || b.Right <= lft)).ToList();
|
||||
|
||||
#region Find Top/Bottom Limits
|
||||
|
||||
var top = double.MaxValue;
|
||||
var btm = double.MinValue;
|
||||
|
||||
foreach (var box in verticalBoxes)
|
||||
{
|
||||
var boxBtm = box.Bottom;
|
||||
var boxTop = box.Top;
|
||||
|
||||
if (boxBtm > pt.Y && boxBtm < top)
|
||||
top = boxBtm;
|
||||
|
||||
else if (box.Top < pt.Y && boxTop > btm)
|
||||
btm = boxTop;
|
||||
}
|
||||
|
||||
if (top == double.MaxValue)
|
||||
{
|
||||
if (bounds.Top > pt.Y)
|
||||
top = bounds.Top;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
if (btm == double.MinValue)
|
||||
{
|
||||
if (bounds.Bottom < pt.Y)
|
||||
btm = bounds.Bottom;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return new Box(lft, btm, rgt - lft, top - btm);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
OpenNest.Core/IBoundable.cs
Normal file
18
OpenNest.Core/IBoundable.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface IBoundable
|
||||
{
|
||||
Box BoundingBox { get; }
|
||||
|
||||
double Left { get; }
|
||||
|
||||
double Right { get; }
|
||||
|
||||
double Top { get; }
|
||||
|
||||
double Bottom { get; }
|
||||
|
||||
void UpdateBounds();
|
||||
}
|
||||
}
|
||||
17
OpenNest.Core/IPostProcessor.cs
Normal file
17
OpenNest.Core/IPostProcessor.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.IO;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface IPostProcessor
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
string Author { get; }
|
||||
|
||||
string Description { get; }
|
||||
|
||||
void Post(Nest nest, Stream outputStream);
|
||||
|
||||
void Post(Nest nest, string outputFile);
|
||||
}
|
||||
}
|
||||
33
OpenNest.Core/Material.cs
Normal file
33
OpenNest.Core/Material.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Material
|
||||
{
|
||||
public Material()
|
||||
{
|
||||
}
|
||||
|
||||
public Material(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public Material(string name, string grade)
|
||||
{
|
||||
Name = name;
|
||||
Grade = grade;
|
||||
}
|
||||
|
||||
public Material(string name, string grade, double density)
|
||||
{
|
||||
Name = name;
|
||||
Grade = grade;
|
||||
Density = density;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Grade { get; set; }
|
||||
|
||||
public double Density { get; set; }
|
||||
}
|
||||
}
|
||||
139
OpenNest.Core/Nest.cs
Normal file
139
OpenNest.Core/Nest.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using OpenNest.Collections;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Nest
|
||||
{
|
||||
public ObservableList<Plate> Plates;
|
||||
public DrawingCollection Drawings;
|
||||
|
||||
public Nest()
|
||||
: this(string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
public Nest(string name)
|
||||
{
|
||||
Name = name;
|
||||
Plates = new ObservableList<Plate>();
|
||||
Plates.ItemRemoved += Plates_PlateRemoved;
|
||||
Drawings = new DrawingCollection();
|
||||
PlateDefaults = new PlateSettings();
|
||||
Customer = string.Empty;
|
||||
Notes = string.Empty;
|
||||
}
|
||||
|
||||
private static void Plates_PlateRemoved(object sender, ItemRemovedEventArgs<Plate> e)
|
||||
{
|
||||
e.Item.Parts.Clear();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Customer { get; set; }
|
||||
|
||||
public string Notes { get; set; }
|
||||
|
||||
public Units Units { get; set; }
|
||||
|
||||
public DateTime DateCreated { get; set; }
|
||||
|
||||
public DateTime DateLastModified { get; set; }
|
||||
|
||||
public PlateSettings PlateDefaults { get; set; }
|
||||
|
||||
public Plate CreatePlate()
|
||||
{
|
||||
var plate = PlateDefaults.CreateNew();
|
||||
Plates.Add(plate);
|
||||
return plate;
|
||||
}
|
||||
|
||||
public void UpdateDrawingQuantities()
|
||||
{
|
||||
foreach (var drawing in Drawings)
|
||||
{
|
||||
drawing.Quantity.Nested = 0;
|
||||
}
|
||||
|
||||
foreach (var plate in Plates)
|
||||
{
|
||||
foreach (var part in plate.Parts)
|
||||
{
|
||||
part.BaseDrawing.Quantity.Nested += plate.Quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PlateSettings
|
||||
{
|
||||
private readonly Plate plate;
|
||||
|
||||
public PlateSettings()
|
||||
{
|
||||
plate = new Plate();
|
||||
}
|
||||
|
||||
public int Quadrant
|
||||
{
|
||||
get { return plate.Quadrant; }
|
||||
set { plate.Quadrant = value; }
|
||||
}
|
||||
|
||||
public double Thickness
|
||||
{
|
||||
get { return plate.Thickness; }
|
||||
set { plate.Thickness = value; }
|
||||
}
|
||||
|
||||
public Material Material
|
||||
{
|
||||
get { return plate.Material; }
|
||||
set { plate.Material = value; }
|
||||
}
|
||||
|
||||
public Size Size
|
||||
{
|
||||
get { return plate.Size; }
|
||||
set { plate.Size = value; }
|
||||
}
|
||||
|
||||
public Spacing EdgeSpacing
|
||||
{
|
||||
get { return plate.EdgeSpacing; }
|
||||
set { plate.EdgeSpacing = value; }
|
||||
}
|
||||
|
||||
public double PartSpacing
|
||||
{
|
||||
get { return plate.PartSpacing; }
|
||||
set { plate.PartSpacing = value; }
|
||||
}
|
||||
|
||||
public void SetFromExisting(Plate plate)
|
||||
{
|
||||
Thickness = plate.Thickness;
|
||||
Quadrant = plate.Quadrant;
|
||||
Material = plate.Material;
|
||||
Size = plate.Size;
|
||||
EdgeSpacing = plate.EdgeSpacing;
|
||||
PartSpacing = plate.PartSpacing;
|
||||
}
|
||||
|
||||
public Plate CreateNew()
|
||||
{
|
||||
return new Plate()
|
||||
{
|
||||
Thickness = Thickness,
|
||||
Size = Size,
|
||||
EdgeSpacing = EdgeSpacing,
|
||||
PartSpacing = PartSpacing,
|
||||
Material = Material,
|
||||
Quadrant = Quadrant,
|
||||
Quantity = 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
OpenNest.Core/NestConstraints.cs
Normal file
46
OpenNest.Core/NestConstraints.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class NestConstraints
|
||||
{
|
||||
/// <summary>
|
||||
/// The rotation step in radians.
|
||||
/// </summary>
|
||||
public double StepAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rotation start angle in radians.
|
||||
/// </summary>
|
||||
public double StartAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rotation end angle in radians.
|
||||
/// </summary>
|
||||
public double EndAngle { get; set; }
|
||||
|
||||
public bool Allow180Equivalent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the StartAngle and EndAngle to allow 360 degree rotation.
|
||||
/// </summary>
|
||||
public void AllowAnyRotation()
|
||||
{
|
||||
StartAngle = 0;
|
||||
EndAngle = Angle.TwoPI;
|
||||
}
|
||||
|
||||
public bool HasLimitedRotation()
|
||||
{
|
||||
var diff = EndAngle - StartAngle;
|
||||
|
||||
if (diff.IsEqualTo(Angle.TwoPI))
|
||||
return false;
|
||||
|
||||
if ((diff > Math.PI || diff.IsEqualTo(Math.PI)) && Allow180Equivalent)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
OpenNest.Core/OffsetSide.cs
Normal file
9
OpenNest.Core/OffsetSide.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum OffsetSide
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
110
OpenNest.Core/OpenNest.Core.csproj
Normal file
110
OpenNest.Core/OpenNest.Core.csproj
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>OpenNest</RootNamespace>
|
||||
<AssemblyName>OpenNest.Core</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Drawing" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Align.cs" />
|
||||
<Compile Include="AlignType.cs" />
|
||||
<Compile Include="Angle.cs" />
|
||||
<Compile Include="CNC\KerfType.cs" />
|
||||
<Compile Include="CNC\CodeType.cs" />
|
||||
<Compile Include="CNC\LayerType.cs" />
|
||||
<Compile Include="CNC\Mode.cs" />
|
||||
<Compile Include="CutParameters.cs" />
|
||||
<Compile Include="Drawing.cs" />
|
||||
<Compile Include="Collections\DrawingCollection.cs" />
|
||||
<Compile Include="EvenOdd.cs" />
|
||||
<Compile Include="ConvertGeometry.cs" />
|
||||
<Compile Include="CNC\CircularMove.cs" />
|
||||
<Compile Include="CNC\Comment.cs" />
|
||||
<Compile Include="CNC\ICode.cs" />
|
||||
<Compile Include="CNC\LinearMove.cs" />
|
||||
<Compile Include="CNC\Motion.cs" />
|
||||
<Compile Include="CNC\RapidMove.cs" />
|
||||
<Compile Include="CNC\Feedrate.cs" />
|
||||
<Compile Include="CNC\Kerf.cs" />
|
||||
<Compile Include="CNC\SubProgramCall.cs" />
|
||||
<Compile Include="Generic.cs" />
|
||||
<Compile Include="Geometry\Arc.cs" />
|
||||
<Compile Include="BoundingBox.cs" />
|
||||
<Compile Include="Box.cs" />
|
||||
<Compile Include="BoxSplitter.cs" />
|
||||
<Compile Include="Geometry\Circle.cs" />
|
||||
<Compile Include="Geometry\DefinedShape.cs" />
|
||||
<Compile Include="Geometry\Entity.cs" />
|
||||
<Compile Include="Geometry\EntityType.cs" />
|
||||
<Compile Include="Helper.cs" />
|
||||
<Compile Include="IBoundable.cs" />
|
||||
<Compile Include="Geometry\Layer.cs" />
|
||||
<Compile Include="Geometry\Line.cs" />
|
||||
<Compile Include="OffsetSide.cs" />
|
||||
<Compile Include="Geometry\Polygon.cs" />
|
||||
<Compile Include="RelativePosition.cs" />
|
||||
<Compile Include="RotationType.cs" />
|
||||
<Compile Include="Geometry\Shape.cs" />
|
||||
<Compile Include="Size.cs" />
|
||||
<Compile Include="Tolerance.cs" />
|
||||
<Compile Include="Units.cs" />
|
||||
<Compile Include="Vector.cs" />
|
||||
<Compile Include="IPostProcessor.cs" />
|
||||
<Compile Include="NestConstraints.cs" />
|
||||
<Compile Include="Sequence.cs" />
|
||||
<Compile Include="SpecialLayers.cs" />
|
||||
<Compile Include="Material.cs" />
|
||||
<Compile Include="ConvertMode.cs" />
|
||||
<Compile Include="Nest.cs" />
|
||||
<Compile Include="Part.cs" />
|
||||
<Compile Include="Collections\ObservableList.cs" />
|
||||
<Compile Include="Plate.cs" />
|
||||
<Compile Include="CNC\Program.cs" />
|
||||
<Compile Include="ConvertProgram.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Quantity.cs" />
|
||||
<Compile Include="Spacing.cs" />
|
||||
<Compile Include="Timing.cs" />
|
||||
<Compile Include="TimingInfo.cs" />
|
||||
<Compile Include="Trigonometry.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
190
OpenNest.Core/Part.cs
Normal file
190
OpenNest.Core/Part.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface IPart : IBoundable
|
||||
{
|
||||
Vector Location { get; set; }
|
||||
double Rotation { get; }
|
||||
void Rotate(double angle);
|
||||
void Rotate(double angle, Vector origin);
|
||||
void Offset(double x, double y);
|
||||
void Offset(Vector voffset);
|
||||
void Update();
|
||||
}
|
||||
|
||||
public class Part : IPart, IBoundable
|
||||
{
|
||||
private Vector location;
|
||||
|
||||
public readonly Drawing BaseDrawing;
|
||||
|
||||
public Part(Drawing baseDrawing)
|
||||
: this(baseDrawing, new Vector())
|
||||
{
|
||||
}
|
||||
|
||||
public Part(Drawing baseDrawing, Vector location)
|
||||
{
|
||||
BaseDrawing = baseDrawing;
|
||||
Program = baseDrawing.Program.Clone() as Program;
|
||||
this.location = location;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Location of the part.
|
||||
/// </summary>
|
||||
public Vector Location
|
||||
{
|
||||
get { return location; }
|
||||
set
|
||||
{
|
||||
BoundingBox.Offset(value - location);
|
||||
location = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Program Program { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation of the part in radians.
|
||||
/// </summary>
|
||||
public double Rotation
|
||||
{
|
||||
get { return Program.Rotation; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the part.
|
||||
/// </summary>
|
||||
/// <param name="angle">Angle of rotation in radians.</param>
|
||||
public void Rotate(double angle)
|
||||
{
|
||||
Program.Rotate(angle);
|
||||
location = Location.Rotate(angle);
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the part around the specified origin.
|
||||
/// </summary>
|
||||
/// <param name="angle">Angle of rotation in radians.</param>
|
||||
/// <param name="origin">The origin to rotate the part around.</param>
|
||||
public void Rotate(double angle, Vector origin)
|
||||
{
|
||||
Program.Rotate(angle);
|
||||
location = Location.Rotate(angle, origin);
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the part.
|
||||
/// </summary>
|
||||
/// <param name="x">The x-axis offset distance.</param>
|
||||
/// <param name="y">The y-axis offset distance.</param>
|
||||
public void Offset(double x, double y)
|
||||
{
|
||||
location = new Vector(location.X + x, location.Y + y);
|
||||
BoundingBox.Offset(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the part.
|
||||
/// </summary>
|
||||
/// <param name="voffset">The vector containing the x-axis & y-axis offset distances.</param>
|
||||
public void Offset(Vector voffset)
|
||||
{
|
||||
location += voffset;
|
||||
BoundingBox.Offset(voffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box of the part.
|
||||
/// </summary>
|
||||
public void UpdateBounds()
|
||||
{
|
||||
BoundingBox = Program.BoundingBox();
|
||||
BoundingBox.Offset(Location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the part from the drawing it was derived from.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
var rotation = Rotation;
|
||||
Program = BaseDrawing.Program.Clone() as Program;
|
||||
Program.Rotate(Program.Rotation - rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The smallest box that contains the part.
|
||||
/// </summary>
|
||||
public Box BoundingBox { get; protected set; }
|
||||
|
||||
public bool Intersects(Part part, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
var entities1 = ConvertProgram.ToGeometry(Program);
|
||||
var entities2 = ConvertProgram.ToGeometry(part.Program);
|
||||
|
||||
var shapes1 = Helper.GetShapes(entities1);
|
||||
var shapes2 = Helper.GetShapes(entities2);
|
||||
|
||||
shapes1.ForEach(shape => shape.Offset(Location));
|
||||
shapes2.ForEach(shape => shape.Offset(part.Location));
|
||||
|
||||
for (int i = 0; i < shapes1.Count; i++)
|
||||
{
|
||||
var shape1 = shapes1[i];
|
||||
|
||||
for (int j = 0; j < shapes2.Count; j++)
|
||||
{
|
||||
var shape2 = shapes2[j];
|
||||
List<Vector> pts2;
|
||||
|
||||
if (shape1.Intersects(shape2, out pts2))
|
||||
pts.AddRange(pts2);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
public double Left
|
||||
{
|
||||
get { return BoundingBox.Left; }
|
||||
}
|
||||
|
||||
public double Right
|
||||
{
|
||||
get { return BoundingBox.Right; }
|
||||
}
|
||||
|
||||
public double Top
|
||||
{
|
||||
get { return BoundingBox.Top; }
|
||||
}
|
||||
|
||||
public double Bottom
|
||||
{
|
||||
get { return BoundingBox.Bottom; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a deep copy of the part.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object Clone()
|
||||
{
|
||||
var part = new Part(BaseDrawing);
|
||||
part.Rotate(Rotation);
|
||||
part.Location = Location;
|
||||
|
||||
return part;
|
||||
}
|
||||
}
|
||||
}
|
||||
475
OpenNest.Core/Plate.cs
Normal file
475
OpenNest.Core/Plate.cs
Normal file
@@ -0,0 +1,475 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Collections;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Plate
|
||||
{
|
||||
private int quadrant;
|
||||
|
||||
public event EventHandler<ItemAddedEventArgs<Part>> PartAdded
|
||||
{
|
||||
add { Parts.ItemAdded += value; }
|
||||
remove { Parts.ItemAdded -= value; }
|
||||
}
|
||||
|
||||
public event EventHandler<ItemRemovedEventArgs<Part>> PartRemoved
|
||||
{
|
||||
add { Parts.ItemRemoved += value; }
|
||||
remove { Parts.ItemRemoved -= value; }
|
||||
}
|
||||
|
||||
public event EventHandler<ItemChangedEventArgs<Part>> PartChanged
|
||||
{
|
||||
add { Parts.ItemChanged += value; }
|
||||
remove { Parts.ItemChanged -= value; }
|
||||
}
|
||||
|
||||
public Plate()
|
||||
: this(60, 120)
|
||||
{
|
||||
}
|
||||
|
||||
public Plate(double width, double length)
|
||||
: this(new Size(length, width))
|
||||
{
|
||||
}
|
||||
|
||||
public Plate(Size size)
|
||||
{
|
||||
EdgeSpacing = new Spacing();
|
||||
Size = size;
|
||||
Material = new Material();
|
||||
Parts = new ObservableList<Part>();
|
||||
Parts.ItemAdded += Parts_PartAdded;
|
||||
Parts.ItemRemoved += Parts_PartRemoved;
|
||||
Quadrant = 1;
|
||||
}
|
||||
|
||||
private void Parts_PartAdded(object sender, ItemAddedEventArgs<Part> e)
|
||||
{
|
||||
e.Item.BaseDrawing.Quantity.Nested += Quantity;
|
||||
}
|
||||
|
||||
private void Parts_PartRemoved(object sender, ItemRemovedEventArgs<Part> e)
|
||||
{
|
||||
e.Item.BaseDrawing.Quantity.Nested -= Quantity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thickness of the plate.
|
||||
/// </summary>
|
||||
public double Thickness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The spacing between parts.
|
||||
/// </summary>
|
||||
public double PartSpacing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The spacing along the edges of the plate.
|
||||
/// </summary>
|
||||
public Spacing EdgeSpacing;
|
||||
|
||||
/// <summary>
|
||||
/// The size of the plate.
|
||||
/// </summary>
|
||||
public Size Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Material the plate is made out of.
|
||||
/// </summary>
|
||||
public Material Material { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parts that the plate contains.
|
||||
/// </summary>
|
||||
public ObservableList<Part> Parts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of times to cut the plate.
|
||||
/// </summary>
|
||||
public int Quantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The quadrant the plate is located in.
|
||||
/// 1 = TopRight
|
||||
/// 2 = TopLeft
|
||||
/// 3 = BottomLeft
|
||||
/// 4 = BottomRight
|
||||
/// </summary>
|
||||
public int Quadrant
|
||||
{
|
||||
get { return quadrant; }
|
||||
set { quadrant = value <= 4 && value > 0 ? value : 1; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the plate clockwise or counter-clockwise along with all parts.
|
||||
/// </summary>
|
||||
/// <param name="rotationDirection"></param>
|
||||
/// <param name="keepSameQuadrant"></param>
|
||||
public void Rotate90(RotationType rotationDirection, bool keepSameQuadrant = true)
|
||||
{
|
||||
const double oneAndHalfPI = Math.PI * 1.5;
|
||||
|
||||
Size = new Size(Size.Height, Size.Width);
|
||||
|
||||
if (rotationDirection == RotationType.CW)
|
||||
{
|
||||
Rotate(oneAndHalfPI);
|
||||
|
||||
if (keepSameQuadrant)
|
||||
{
|
||||
switch (Quadrant)
|
||||
{
|
||||
case 1:
|
||||
Offset(0, Size.Height);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Offset(-Size.Width, 0);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
Offset(0, -Size.Height);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
Offset(Size.Width, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Quadrant = Quadrant < 2 ? 4 : Quadrant - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Rotate(Angle.HalfPI);
|
||||
|
||||
if (keepSameQuadrant)
|
||||
{
|
||||
switch (Quadrant)
|
||||
{
|
||||
case 1:
|
||||
Offset(Size.Width, 0);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
Offset(0, Size.Height);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
Offset(-Size.Width, 0);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
Offset(0, -Size.Height);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Quadrant = Quadrant > 3 ? 1 : Quadrant + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the plate 180 degrees along with all parts.
|
||||
/// </summary>
|
||||
/// <param name="keepSameQuadrant"></param>
|
||||
public void Rotate180(bool keepSameQuadrant = true)
|
||||
{
|
||||
if (keepSameQuadrant)
|
||||
{
|
||||
Vector centerpt;
|
||||
|
||||
switch (Quadrant)
|
||||
{
|
||||
case 1:
|
||||
centerpt = new Vector(Size.Width * 0.5, Size.Height * 0.5);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
centerpt = new Vector(-Size.Width * 0.5, Size.Height * 0.5);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
centerpt = new Vector(-Size.Width * 0.5, -Size.Height * 0.5);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
centerpt = new Vector(Size.Width * 0.5, -Size.Height * 0.5);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Rotate(Math.PI, centerpt);
|
||||
}
|
||||
else
|
||||
{
|
||||
Rotate(Math.PI);
|
||||
Quadrant = (Quadrant + 2) % 4;
|
||||
|
||||
if (Quadrant == 0)
|
||||
Quadrant = 4;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the parts on the plate.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public void Rotate(double angle)
|
||||
{
|
||||
for (int i = 0; i < Parts.Count; ++i)
|
||||
{
|
||||
var part = Parts[i];
|
||||
part.Rotate(angle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the parts on the plate around the specified origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public void Rotate(double angle, Vector origin)
|
||||
{
|
||||
for (int i = 0; i < Parts.Count; ++i)
|
||||
{
|
||||
var part = Parts[i];
|
||||
part.Rotate(angle, origin);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the parts on the plate.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public void Offset(double x, double y)
|
||||
{
|
||||
for (int i = 0; i < Parts.Count; ++i)
|
||||
{
|
||||
var part = Parts[i];
|
||||
part.Offset(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the parts on the plate.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public void Offset(Vector voffset)
|
||||
{
|
||||
for (int i = 0; i < Parts.Count; ++i)
|
||||
{
|
||||
var part = Parts[i];
|
||||
part.Offset(voffset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The smallest box that contains the plate.
|
||||
/// </summary>
|
||||
/// <param name="includeParts"></param>
|
||||
/// <returns></returns>
|
||||
public Box BoundingBox(bool includeParts = true)
|
||||
{
|
||||
var plateBox = new Box();
|
||||
|
||||
switch (Quadrant)
|
||||
{
|
||||
case 1:
|
||||
plateBox.X = 0;
|
||||
plateBox.Y = 0;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
plateBox.X = (float)-Size.Width;
|
||||
plateBox.Y = 0;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
plateBox.X = (float)-Size.Width;
|
||||
plateBox.Y = (float)-Size.Height;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
plateBox.X = 0;
|
||||
plateBox.Y = (float)-Size.Height;
|
||||
break;
|
||||
|
||||
default:
|
||||
return new Box();
|
||||
}
|
||||
|
||||
plateBox.Width = Size.Width;
|
||||
plateBox.Height = Size.Height;
|
||||
|
||||
if (!includeParts)
|
||||
return plateBox;
|
||||
|
||||
var boundingBox = new Box();
|
||||
var partsBox = Parts.GetBoundingBox();
|
||||
|
||||
boundingBox.X = partsBox.Left < plateBox.Left
|
||||
? partsBox.Left
|
||||
: plateBox.Left;
|
||||
|
||||
boundingBox.Y = partsBox.Bottom < plateBox.Bottom
|
||||
? partsBox.Bottom
|
||||
: plateBox.Bottom;
|
||||
|
||||
boundingBox.Width = partsBox.Right > plateBox.Right
|
||||
? partsBox.Right - boundingBox.X
|
||||
: plateBox.Right - boundingBox.X;
|
||||
|
||||
boundingBox.Height = partsBox.Top > plateBox.Top
|
||||
? partsBox.Top - boundingBox.Y
|
||||
: plateBox.Top - boundingBox.Y;
|
||||
|
||||
return boundingBox;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The area within the edge spacing.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Box WorkArea()
|
||||
{
|
||||
var box = BoundingBox(false);
|
||||
|
||||
box.X += EdgeSpacing.Left;
|
||||
box.Y += EdgeSpacing.Bottom;
|
||||
box.Width -= EdgeSpacing.Left + EdgeSpacing.Right;
|
||||
box.Height -= EdgeSpacing.Top + EdgeSpacing.Bottom;
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatically sizes the plate to fit the parts.
|
||||
/// </summary>
|
||||
/// <param name="roundingFactor">The factor to round the actual size up to.</param>
|
||||
/// <example>
|
||||
/// AutoSize 9.7 x 10.1
|
||||
/// * roundingFactor=1.0 new Size=10 x 11
|
||||
/// * roundingFactor=0.5 new Size=10 x 10.5
|
||||
/// * roundingFactor=0.25 new Size=9.75 x 10.25
|
||||
/// * roundingFactor=0.0 new Size=9.7 x 10.1
|
||||
/// </example>
|
||||
public void AutoSize(double roundingFactor = 1.0)
|
||||
{
|
||||
if (Parts.Count == 0)
|
||||
return;
|
||||
|
||||
var bounds = Parts.GetBoundingBox();
|
||||
|
||||
double width;
|
||||
double height;
|
||||
|
||||
switch (Quadrant)
|
||||
{
|
||||
case 1:
|
||||
width = Math.Abs(bounds.Right) + EdgeSpacing.Right;
|
||||
height = Math.Abs(bounds.Top) + EdgeSpacing.Top;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
width = Math.Abs(bounds.Left) + EdgeSpacing.Left;
|
||||
height = Math.Abs(bounds.Top) + EdgeSpacing.Top;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
width = Math.Abs(bounds.Left) + EdgeSpacing.Left;
|
||||
height = Math.Abs(bounds.Bottom) + EdgeSpacing.Bottom;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
width = Math.Abs(bounds.Right) + EdgeSpacing.Right;
|
||||
height = Math.Abs(bounds.Bottom) + EdgeSpacing.Bottom;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Size = new Size(
|
||||
Helper.RoundUpToNearest(width, roundingFactor),
|
||||
Helper.RoundUpToNearest(height, roundingFactor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the area of the top surface of the plate.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Area()
|
||||
{
|
||||
return Size.Width * Size.Height;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the volume of the plate.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Volume()
|
||||
{
|
||||
return Area() * Thickness;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the weight of the plate.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Weight()
|
||||
{
|
||||
return Volume() * Material.Density;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Percentage of the material used.
|
||||
/// </summary>
|
||||
/// <returns>Returns a number between 0.0 and 1.0</returns>
|
||||
public double Utilization()
|
||||
{
|
||||
return Parts.Sum(part => part.BaseDrawing.Area) / Area();
|
||||
}
|
||||
|
||||
public bool HasOverlappingParts(out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
for (int i = 0; i < Parts.Count; i++)
|
||||
{
|
||||
var part1 = Parts[i];
|
||||
|
||||
for (int j = i + 1; j < Parts.Count; j++)
|
||||
{
|
||||
var part2 = Parts[j];
|
||||
|
||||
List<Vector> pts2;
|
||||
|
||||
if (part1.Intersects(part2, out pts2))
|
||||
pts.AddRange(pts2);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
OpenNest.Core/Properties/AssemblyInfo.cs
Normal file
35
OpenNest.Core/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenNest.Core")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("OpenNest.Core")]
|
||||
[assembly: AssemblyCopyright("Copyright © AJ Isaacs 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("13d3c141-e430-4f27-9098-60d6f93e2a7b")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.4.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.4.0.0")]
|
||||
18
OpenNest.Core/Quantity.cs
Normal file
18
OpenNest.Core/Quantity.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public struct Quantity
|
||||
{
|
||||
public int Nested { get; internal set; }
|
||||
|
||||
public int Required { get; set; }
|
||||
|
||||
public int Remaining
|
||||
{
|
||||
get
|
||||
{
|
||||
var x = Required - Nested;
|
||||
return x < 0 ? 0: x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
OpenNest.Core/RelativePosition.cs
Normal file
13
OpenNest.Core/RelativePosition.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum RelativePosition
|
||||
{
|
||||
Intersecting,
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
None
|
||||
}
|
||||
}
|
||||
16
OpenNest.Core/RotationType.cs
Normal file
16
OpenNest.Core/RotationType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum RotationType
|
||||
{
|
||||
/// <summary>
|
||||
/// Clockwise
|
||||
/// </summary>
|
||||
CW,
|
||||
|
||||
/// <summary>
|
||||
/// Counter-Clockwise
|
||||
/// </summary>
|
||||
CCW
|
||||
}
|
||||
}
|
||||
67
OpenNest.Core/Sequence.cs
Normal file
67
OpenNest.Core/Sequence.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface ISequencer
|
||||
{
|
||||
List<Part> SequenceParts(IList<Part> parts);
|
||||
List<Part> SequenceParts(IList<Part> parts, Vector origin);
|
||||
}
|
||||
|
||||
public class SequenceByNearest : ISequencer
|
||||
{
|
||||
public List<Part> SequenceParts(IList<Part> parts)
|
||||
{
|
||||
return SequenceParts(parts, Vector.Zero);
|
||||
}
|
||||
|
||||
public List<Part> SequenceParts(IList<Part> parts, Vector origin)
|
||||
{
|
||||
if (parts.Count == 0)
|
||||
return new List<Part>();
|
||||
|
||||
var dupList = new List<Part>(parts);
|
||||
var seqList = new List<Part>(parts.Count);
|
||||
|
||||
var lastPart = GetClosestPart(origin, dupList);
|
||||
seqList.Add(lastPart);
|
||||
dupList.Remove(lastPart);
|
||||
|
||||
for (int i = 0; i < parts.Count - 1 /*STOP BEFORE LAST PART*/; i++)
|
||||
{
|
||||
var nextPart = GetClosestPart(lastPart.Location, dupList);
|
||||
|
||||
if (nextPart == null)
|
||||
break;
|
||||
|
||||
seqList.Add(nextPart);
|
||||
dupList.Remove(nextPart);
|
||||
lastPart = nextPart;
|
||||
}
|
||||
|
||||
return seqList;
|
||||
}
|
||||
|
||||
private Part GetClosestPart(Vector pt, IList<Part> parts)
|
||||
{
|
||||
if (parts.Count == 0)
|
||||
return null;
|
||||
|
||||
var closestPart = parts[0];
|
||||
var closestDistance = parts[0].Location.DistanceTo(pt);
|
||||
|
||||
for (int i = 1; i < parts.Count; i++)
|
||||
{
|
||||
var distance = parts[i].Location.DistanceTo(pt);
|
||||
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestPart = parts[i];
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return closestPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
OpenNest.Core/Size.cs
Normal file
55
OpenNest.Core/Size.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public struct Size
|
||||
{
|
||||
public Size(double width, double height)
|
||||
{
|
||||
Height = height;
|
||||
Width = width;
|
||||
}
|
||||
|
||||
public double Height;
|
||||
|
||||
public double Width;
|
||||
|
||||
public static Size Parse(string size)
|
||||
{
|
||||
var a = size.ToUpper().Split('X');
|
||||
|
||||
if (a.Length > 2)
|
||||
throw new FormatException("Invalid size format.");
|
||||
|
||||
var height = double.Parse(a[0]);
|
||||
var width = double.Parse(a[1]);
|
||||
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
public static bool TryParse(string s, out Size size)
|
||||
{
|
||||
try
|
||||
{
|
||||
size = Parse(s);
|
||||
}
|
||||
catch
|
||||
{
|
||||
size = new Size(0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} x {1}", Height, Width);
|
||||
}
|
||||
|
||||
public string ToString(int decimalPlaces)
|
||||
{
|
||||
return string.Format("{0} x {1}", Math.Round(Height, decimalPlaces), Math.Round(Width, decimalPlaces));
|
||||
}
|
||||
}
|
||||
}
|
||||
27
OpenNest.Core/Spacing.cs
Normal file
27
OpenNest.Core/Spacing.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public struct Spacing
|
||||
{
|
||||
public Spacing(double topBottom, double leftRight)
|
||||
{
|
||||
Top = Bottom = topBottom;
|
||||
Left = Right = leftRight;
|
||||
}
|
||||
|
||||
public Spacing(double left, double bottom, double right, double top)
|
||||
{
|
||||
Left = left;
|
||||
Bottom = bottom;
|
||||
Right = right;
|
||||
Top = top;
|
||||
}
|
||||
|
||||
public double Left;
|
||||
|
||||
public double Bottom;
|
||||
|
||||
public double Right;
|
||||
|
||||
public double Top;
|
||||
}
|
||||
}
|
||||
21
OpenNest.Core/SpecialLayers.cs
Normal file
21
OpenNest.Core/SpecialLayers.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class SpecialLayers
|
||||
{
|
||||
public static readonly Layer Default = new Layer("0");
|
||||
|
||||
public static readonly Layer Cut = new Layer("CUT");
|
||||
|
||||
public static readonly Layer Rapid = new Layer("RAPID");
|
||||
|
||||
public static readonly Layer Display = new Layer("DISPLAY");
|
||||
|
||||
public static readonly Layer Leadin = new Layer("LEADIN");
|
||||
|
||||
public static readonly Layer Leadout = new Layer("LEADOUT");
|
||||
|
||||
public static readonly Layer Scribe = new Layer("SCRIBE");
|
||||
}
|
||||
}
|
||||
91
OpenNest.Core/Timing.cs
Normal file
91
OpenNest.Core/Timing.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Timing
|
||||
{
|
||||
public static TimingInfo GetTimingInfo(Program pgm)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(pgm);
|
||||
var shapes = Helper.GetShapes(entities.Where(entity => entity.Layer != SpecialLayers.Rapid));
|
||||
var info = new TimingInfo { PierceCount = shapes.Count };
|
||||
|
||||
var last = entities[0];
|
||||
ProcessEntity(info, last, null);
|
||||
|
||||
for (int i = 1; i < entities.Count; i++)
|
||||
{
|
||||
var entity = entities[i];
|
||||
ProcessEntity(info, entity, last);
|
||||
last = entity;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static TimingInfo GetTimingInfo(Plate plate)
|
||||
{
|
||||
var info = new TimingInfo();
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
foreach (var part in plate.Parts)
|
||||
{
|
||||
var endpt = part.Program.EndPoint() + part.Location;
|
||||
info += GetTimingInfo(part.Program);
|
||||
info.TravelDistance += pos.DistanceTo(endpt);
|
||||
pos = endpt;
|
||||
}
|
||||
|
||||
info *= plate.Quantity;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static TimingInfo GetTimingInfo(Nest nest)
|
||||
{
|
||||
var info = new TimingInfo();
|
||||
return nest.Plates.Aggregate(info, (current, plate) => current + GetTimingInfo(plate));
|
||||
}
|
||||
|
||||
private static void ProcessEntity(TimingInfo info, Entity entity, Entity lastEntity)
|
||||
{
|
||||
if (entity.Layer == SpecialLayers.Cut)
|
||||
{
|
||||
info.CutDistance += entity.Length;
|
||||
|
||||
if (entity.Type == EntityType.Line &&
|
||||
lastEntity != null &&
|
||||
lastEntity.Type == EntityType.Line &&
|
||||
lastEntity.Layer == SpecialLayers.Cut)
|
||||
info.IntersectionCount++;
|
||||
}
|
||||
else if (entity.Layer == SpecialLayers.Rapid)
|
||||
info.TravelDistance += entity.Length;
|
||||
}
|
||||
|
||||
public static TimeSpan CalculateTime(TimingInfo info, CutParameters cutParams)
|
||||
{
|
||||
var time = new TimeSpan();
|
||||
|
||||
switch (cutParams.Units)
|
||||
{
|
||||
case Units.Inches:
|
||||
time += TimeSpan.FromMinutes(info.CutDistance / cutParams.Feedrate);
|
||||
time += TimeSpan.FromMinutes(info.TravelDistance / cutParams.RapidTravelRate);
|
||||
break;
|
||||
|
||||
case Units.Millimeters:
|
||||
time += TimeSpan.FromSeconds(info.CutDistance / cutParams.Feedrate);
|
||||
time += TimeSpan.FromSeconds(info.TravelDistance / cutParams.RapidTravelRate);
|
||||
break;
|
||||
}
|
||||
|
||||
time += TimeSpan.FromTicks(info.PierceCount * cutParams.PierceTime.Ticks);
|
||||
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
OpenNest.Core/TimingInfo.cs
Normal file
57
OpenNest.Core/TimingInfo.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public class TimingInfo
|
||||
{
|
||||
public int PierceCount;
|
||||
|
||||
public int IntersectionCount;
|
||||
|
||||
public double TravelDistance;
|
||||
|
||||
public double CutDistance;
|
||||
|
||||
public static TimingInfo operator +(TimingInfo info1, TimingInfo info2)
|
||||
{
|
||||
return new TimingInfo
|
||||
{
|
||||
CutDistance = info1.CutDistance + info2.CutDistance,
|
||||
IntersectionCount = info1.IntersectionCount + info2.IntersectionCount,
|
||||
TravelDistance = info1.TravelDistance + info2.TravelDistance,
|
||||
PierceCount = info1.PierceCount + info2.PierceCount
|
||||
};
|
||||
}
|
||||
|
||||
public static TimingInfo operator -(TimingInfo info1, TimingInfo info2)
|
||||
{
|
||||
return new TimingInfo
|
||||
{
|
||||
CutDistance = info1.CutDistance - info2.CutDistance,
|
||||
IntersectionCount = info1.IntersectionCount - info2.IntersectionCount,
|
||||
TravelDistance = info1.TravelDistance - info2.TravelDistance,
|
||||
PierceCount = info1.PierceCount - info2.PierceCount
|
||||
};
|
||||
}
|
||||
|
||||
public static TimingInfo operator *(TimingInfo info1, TimingInfo info2)
|
||||
{
|
||||
return new TimingInfo
|
||||
{
|
||||
CutDistance = info1.CutDistance * info2.CutDistance,
|
||||
IntersectionCount = info1.IntersectionCount * info2.IntersectionCount,
|
||||
TravelDistance = info1.TravelDistance * info2.TravelDistance,
|
||||
PierceCount = info1.PierceCount * info2.PierceCount
|
||||
};
|
||||
}
|
||||
|
||||
public static TimingInfo operator *(TimingInfo info1, int factor)
|
||||
{
|
||||
return new TimingInfo
|
||||
{
|
||||
CutDistance = info1.CutDistance * factor,
|
||||
IntersectionCount = info1.IntersectionCount * factor,
|
||||
TravelDistance = info1.TravelDistance * factor,
|
||||
PierceCount = info1.PierceCount * factor
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
OpenNest.Core/Tolerance.cs
Normal file
14
OpenNest.Core/Tolerance.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Tolerance
|
||||
{
|
||||
public const double Epsilon = 0.00001;
|
||||
|
||||
public static bool IsEqualTo(this double a, double b, double tolerance = Epsilon)
|
||||
{
|
||||
return Math.Abs(b - a) <= tolerance;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
OpenNest.Core/Trigonometry.cs
Normal file
40
OpenNest.Core/Trigonometry.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Trigonometry
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="height">Height</param>
|
||||
/// <param name="hypotenuse">Hypotenuse</param>
|
||||
/// <returns></returns>
|
||||
public static double Base(double height, double hypotenuse)
|
||||
{
|
||||
return Math.Sqrt(hypotenuse * hypotenuse - height * height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bottom">Base</param>
|
||||
/// <param name="hypotenuse">Hypotenuse</param>
|
||||
/// <returns></returns>
|
||||
public static double Height(double bottom, double hypotenuse)
|
||||
{
|
||||
return Math.Sqrt(hypotenuse * hypotenuse - bottom * bottom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="height">Height</param>
|
||||
/// <param name="bottom">Base</param>
|
||||
/// <returns></returns>
|
||||
public static double Hypotenuse(double height, double bottom)
|
||||
{
|
||||
return Math.Sqrt(height * height + bottom * bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
OpenNest.Core/Units.cs
Normal file
82
OpenNest.Core/Units.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum Units
|
||||
{
|
||||
Inches,
|
||||
Millimeters
|
||||
}
|
||||
|
||||
public static class UnitsHelper
|
||||
{
|
||||
public static string GetShortString(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case Units.Inches:
|
||||
return "in";
|
||||
|
||||
case Units.Millimeters:
|
||||
return "mm";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLongString(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case Units.Inches:
|
||||
return "inches";
|
||||
|
||||
case Units.Millimeters:
|
||||
return "millimeters";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetShortTimeUnit(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case Units.Inches:
|
||||
return "min";
|
||||
|
||||
case Units.Millimeters:
|
||||
return "sec";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLongTimeUnit(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case Units.Inches:
|
||||
return "minute";
|
||||
|
||||
case Units.Millimeters:
|
||||
return "second";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetShortTimeUnitPair(Units units)
|
||||
{
|
||||
return GetShortString(units) + "/" + GetShortTimeUnit(units);
|
||||
}
|
||||
|
||||
public static string GetLongTimeUnitPair(Units units)
|
||||
{
|
||||
return GetLongString(units) + "/" + GetLongTimeUnit(units);
|
||||
}
|
||||
}
|
||||
}
|
||||
213
OpenNest.Core/Vector.cs
Normal file
213
OpenNest.Core/Vector.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public struct Vector
|
||||
{
|
||||
public static readonly Vector Invalid = new Vector(double.NaN, double.NaN);
|
||||
public static readonly Vector Zero = new Vector(0, 0);
|
||||
|
||||
public double X;
|
||||
public double Y;
|
||||
|
||||
public Vector(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public double DistanceTo(Vector pt)
|
||||
{
|
||||
var vx = pt.X - X;
|
||||
var vy = pt.Y - Y;
|
||||
|
||||
return Math.Sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
|
||||
public double DistanceTo(double x, double y)
|
||||
{
|
||||
var vx = x - X;
|
||||
var vy = y - Y;
|
||||
|
||||
return Math.Sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
|
||||
public double DotProduct(Vector pt)
|
||||
{
|
||||
return X * pt.X + Y * pt.Y;
|
||||
}
|
||||
|
||||
public double Angle()
|
||||
{
|
||||
return OpenNest.Angle.NormalizeRad(Math.Atan2(Y, X));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle to the given point when the origin is this point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleTo(Vector pt)
|
||||
{
|
||||
return (pt - this).Angle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle when the origin is set at the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleFrom(Vector pt)
|
||||
{
|
||||
return (this - pt).Angle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle between this point and the given point.
|
||||
/// Source: http://math.stackexchange.com/questions/878785/how-to-find-an-angle-in-range0-360-between-2-vectors
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleBetween(Vector pt)
|
||||
{
|
||||
var v1 = Normalize();
|
||||
var v2 = pt.Normalize();
|
||||
var dot = v1.X * v2.X + v1.Y + v2.Y;
|
||||
var det = v1.X * v2.X - v1.Y + v2.Y;
|
||||
|
||||
return Math.Atan2(det, dot);
|
||||
}
|
||||
|
||||
public static Vector operator +(Vector pt1, Vector pt2)
|
||||
{
|
||||
return new Vector(pt1.X + pt2.X, pt1.Y + pt2.Y);
|
||||
}
|
||||
|
||||
public static Vector operator -(Vector pt1, Vector pt2)
|
||||
{
|
||||
return new Vector(pt1.X - pt2.X, pt1.Y - pt2.Y);
|
||||
}
|
||||
|
||||
public static Vector operator -(Vector pt)
|
||||
{
|
||||
return new Vector(-pt.X, -pt.Y);
|
||||
}
|
||||
|
||||
public static Vector operator *(Vector pt, double factor)
|
||||
{
|
||||
return new Vector(pt.X * factor, pt.Y * factor);
|
||||
}
|
||||
|
||||
public static Vector operator *(double factor, Vector pt)
|
||||
{
|
||||
return new Vector(pt.X * factor, pt.Y * factor);
|
||||
}
|
||||
|
||||
public static Vector operator *(Vector pt, Vector factor)
|
||||
{
|
||||
return new Vector(pt.X * factor.X, pt.Y * factor.Y);
|
||||
}
|
||||
|
||||
public static Vector operator /(Vector pt, double divisor)
|
||||
{
|
||||
return new Vector(pt.X / divisor, pt.Y / divisor);
|
||||
}
|
||||
|
||||
public static bool operator ==(Vector pt1, Vector pt2)
|
||||
{
|
||||
return pt1.X.IsEqualTo(pt2.X) && pt1.Y.IsEqualTo(pt2.Y);
|
||||
}
|
||||
|
||||
public static bool operator !=(Vector pt1, Vector pt2)
|
||||
{
|
||||
return !(pt1 == pt2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the unit vector equivalent to this point.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Vector Normalize()
|
||||
{
|
||||
var d = DistanceTo(Vector.Zero);
|
||||
return new Vector(X / d, Y / d);
|
||||
}
|
||||
|
||||
public Vector Rotate(double angle)
|
||||
{
|
||||
var v = new Vector();
|
||||
|
||||
var cos = Math.Cos(angle);
|
||||
var sin = Math.Sin(angle);
|
||||
|
||||
v.X = X * cos - Y * sin;
|
||||
v.Y = X * sin + Y * cos;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public Vector Rotate(double angle, Vector origin)
|
||||
{
|
||||
var v = new Vector();
|
||||
var pt = this - origin;
|
||||
|
||||
var cos = Math.Cos(angle);
|
||||
var sin = Math.Sin(angle);
|
||||
|
||||
v.X = pt.X * cos - pt.Y * sin + origin.X;
|
||||
v.Y = pt.X * sin + pt.Y * cos + origin.Y;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public Vector Offset(double x, double y)
|
||||
{
|
||||
return new Vector(X + x, Y + y);
|
||||
}
|
||||
|
||||
public Vector Offset(Vector voffset)
|
||||
{
|
||||
return this + voffset;
|
||||
}
|
||||
|
||||
public Vector Scale(double factor)
|
||||
{
|
||||
return new Vector(X * factor, Y * factor);
|
||||
}
|
||||
|
||||
public Vector Scale(double factor, Vector origin)
|
||||
{
|
||||
return (this - origin) * factor + origin;
|
||||
}
|
||||
|
||||
public Vector Clone()
|
||||
{
|
||||
return new Vector(X, Y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is Vector))
|
||||
return false;
|
||||
|
||||
var pt = (Vector)obj;
|
||||
|
||||
return (X.IsEqualTo(pt.X)) && (Y.IsEqualTo(pt.Y));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[Vector: X:{0}, Y:{1}]", X, Y);
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return !double.IsNaN(X) && !double.IsNaN(Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user