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:
2025-11-27 20:29:12 -05:00
parent 8367d9f400
commit 2d956fd3f7
189 changed files with 374 additions and 621 deletions

186
OpenNest.Core/Align.cs Normal file
View 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);
}
}
}
}

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

View 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
View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,14 @@

namespace OpenNest.CNC
{
public enum CodeType
{
CircularMove,
Comment,
LinearMove,
RapidMove,
SetFeedrate,
SetKerf,
SubProgramCall
}
}

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

View 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);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace OpenNest.CNC
{
public interface ICode
{
CodeType Type { get; }
ICode Clone();
}
}

39
OpenNest.Core/CNC/Kerf.cs Normal file
View 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;
}
}
}

View File

@@ -0,0 +1,10 @@

namespace OpenNest.CNC
{
public enum KerfType
{
None,
Left,
Right
}
}

View File

@@ -0,0 +1,12 @@

namespace OpenNest.CNC
{
public enum LayerType
{
Display,
Scribe,
Cut,
Leadin,
Leadout
}
}

View 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));
}
}
}

View File

@@ -0,0 +1,9 @@

namespace OpenNest.CNC
{
public enum Mode
{
Absolute,
Incremental
}
}

View 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);
}
}

View 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);
}
}
}

View 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));
}
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Generic;
namespace OpenNest.Collections
{
public class DrawingCollection : HashSet<Drawing>
{
}
}

View 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);
}
}
}

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

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

View 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());
}
}
}
}

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

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

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

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

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

View File

@@ -0,0 +1,12 @@

namespace OpenNest.Geometry
{
public enum EntityType
{
Arc,
Circle,
Line,
Shape,
Polygon
}
}

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

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

View 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);
}
}
}

View 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
View 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);
}
}
}

View 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();
}
}

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

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

View File

@@ -0,0 +1,9 @@

namespace OpenNest
{
public enum OffsetSide
{
Left,
Right
}
}

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

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

View File

@@ -0,0 +1,13 @@

namespace OpenNest
{
public enum RelativePosition
{
Intersecting,
Left,
Right,
Top,
Bottom,
None
}
}

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

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

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

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

View 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
View 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
View 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);
}
}
}