First commit.

This commit is contained in:
aj
2016-05-16 22:09:19 -04:00
commit f2595d7cba
189 changed files with 26944 additions and 0 deletions

22
.gitattributes vendored Normal file
View File

@@ -0,0 +1,22 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

200
.gitignore vendored Normal file
View File

@@ -0,0 +1,200 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
# NSIS installer directory
Installer/*
!*.nsi
# Visual Studo 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding addin-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
*.[Cc]ache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt

BIN
External/Ionic.Zip.dll vendored Normal file

Binary file not shown.

BIN
External/netDxf.dll vendored Normal file

Binary file not shown.

138
Installer/script.nsi Normal file
View File

@@ -0,0 +1,138 @@
!include "MUI2.nsh"
Name "OpenNest"
OutFile "opennest-setup.exe"
InstallDir "$PROGRAMFILES\OpenNest"
RequestExecutionLevel admin
!define DOT_MAJOR "4"
!define DOT_MINOR "0"
!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "../license.txt"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_LANGUAGE "English"
UninstPage uninstConfirm
UninstPage instfiles
Section "OpenNest (required)"
Call IsDotNETInstalled
SectionIn RO
#!/usr/bin/env
; Set output path to the installation directory.
SetOutPath $INSTDIR
File "OpenNest.exe"
File "OpenNest.Core.dll"
File "OpenNest.Engine.dll"
File "Ionic.Zip.dll"
File "netDxf.dll"
; Write the installation path into the registry
WriteRegStr HKLM SOFTWARE\OpenNest "Install_Dir" "$INSTDIR"
; Write the uninstall keys for Windows
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenNest" "DisplayName" "OpenNest"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenNest" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenNest" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\OpenNest" "NoRepair" 1
WriteUninstaller "uninstall.exe"
SectionEnd
Section "Start Menu Shortcuts"
CreateDirectory "$SMPROGRAMS\OpenNest"
CreateShortCut "$SMPROGRAMS\OpenNest\Uninstall.lnk" "$INSTDIR\uninstall.exe" "" "$INSTDIR\uninstall.exe" 0
CreateShortCut "$SMPROGRAMS\OpenNest\OpenNest.lnk" "$INSTDIR\OpenNest.exe" "" "$INSTDIR\OpenNest.exe" 0
SectionEnd
Section "Uninstall"
Delete "$INSTDIR\uninstall.exe"
Delete "$INSTDIR\OpenNest.exe"
Delete "$INSTDIR\OpenNest.Core.dll"
Delete "$INSTDIR\OpenNest.Engine.dll"
Delete "$INSTDIR\Ionic.Zip.dll"
Delete "$INSTDIR\netDxf.dll"
Delete "$SMPROGRAMS\OpenNest\Uninstall.lnk"
Delete "$SMPROGRAMS\OpenNest\OpenNest.lnk"
Delete "$SMPROGRAMS\OpenNest"
SectionEnd
; Usage
; Define in your script two constants:
; DOT_MAJOR "(Major framework version)"
; DOT_MINOR "{Minor framework version)"
;
; Call IsDotNetInstalled
; This function will abort the installation if the required version
; or higher version of the .NET Framework is not installed. Place it in
; either your .onInit function or your first install section before
; other code.
Function IsDotNetInstalled
StrCpy $0 "0"
StrCpy $1 "SOFTWARE\Microsoft\.NETFramework" ;registry entry to look in.
StrCpy $2 0
StartEnum:
;Enumerate the versions installed.
EnumRegKey $3 HKLM "$1\policy" $2
;If we don't find any versions installed, it's not here.
StrCmp $3 "" noDotNet notEmpty
;We found something.
notEmpty:
;Find out if the RegKey starts with 'v'.
;If it doesn't, goto the next key.
StrCpy $4 $3 1 0
StrCmp $4 "v" +1 goNext
StrCpy $4 $3 1 1
;It starts with 'v'. Now check to see how the installed major version
;relates to our required major version.
;If it's equal check the minor version, if it's greater,
;we found a good RegKey.
IntCmp $4 ${DOT_MAJOR} +1 goNext yesDotNetReg
;Check the minor version. If it's equal or greater to our requested
;version then we're good.
StrCpy $4 $3 1 3
IntCmp $4 ${DOT_MINOR} yesDotNetReg goNext yesDotNetReg
goNext:
;Go to the next RegKey.
IntOp $2 $2 + 1
goto StartEnum
yesDotNetReg:
;Now that we've found a good RegKey, let's make sure it's actually
;installed by getting the install path and checking to see if the
;mscorlib.dll exists.
EnumRegValue $2 HKLM "$1\policy\$3" 0
;$2 should equal whatever comes after the major and minor versions
;(ie, v1.1.4322)
StrCmp $2 "" noDotNet
ReadRegStr $4 HKLM $1 "InstallRoot"
;Hopefully the install root isn't empty.
StrCmp $4 "" noDotNet
;build the actuall directory path to mscorlib.dll.
StrCpy $4 "$4$3.$2\mscorlib.dll"
IfFileExists $4 yesDotNet noDotNet
noDotNet:
;Nope, something went wrong along the way. Looks like the
;proper .NET Framework isn't installed.
MessageBox MB_OK "You must have v${DOT_MAJOR}.${DOT_MINOR} or greater of the .NET Framework installed. Aborting!"
Abort
yesDotNet:
;Everything checks out. Go on with the rest of the installation.
FunctionEnd

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

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

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,177 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace OpenNest.Collections
{
public class PartCollection : IList<Part>, ICollection<Part>, IEnumerable<Part>
{
private readonly List<Part> parts;
public event EventHandler<PartAddedEventArgs> PartAdded;
public event EventHandler<PartRemovedEventArgs> PartRemoved;
public event EventHandler<PartChangedEventArgs> PartChanged;
public PartCollection()
{
parts = new List<Part>();
}
public void Add(Part item)
{
var index = parts.Count;
parts.Add(item);
if (PartAdded != null)
PartAdded.Invoke(this, new PartAddedEventArgs(item, index));
}
public void AddRange(IEnumerable<Part> collection)
{
var index = parts.Count;
parts.AddRange(collection);
if (PartAdded != null)
{
foreach (var part in collection)
PartAdded.Invoke(this, new PartAddedEventArgs(part, index++));
}
}
public void Insert(int index, Part item)
{
parts.Insert(index, item);
if (PartAdded != null)
PartAdded.Invoke(this, new PartAddedEventArgs(item, index));
}
public bool Remove(Part item)
{
var success = parts.Remove(item);
if (PartRemoved != null)
PartRemoved.Invoke(this, new PartRemovedEventArgs(item, success));
return success;
}
public void RemoveAt(int index)
{
if (PartRemoved != null)
{
var part = parts[index];
parts.RemoveAt(index);
PartRemoved.Invoke(this, new PartRemovedEventArgs(part, true));
}
else
{
parts.RemoveAt(index);
}
}
public void Clear()
{
for (int i = parts.Count - 1; i >= 0; --i)
RemoveAt(i);
}
public int IndexOf(Part item)
{
return parts.IndexOf(item);
}
public Part this[int index]
{
get
{
return parts[index];
}
set
{
var old = parts[index];
parts[index] = value;
if (PartChanged != null)
{
var eventArgs = new PartChangedEventArgs(old, value, index);
PartChanged.Invoke(this, eventArgs);
}
}
}
public bool Contains(Part item)
{
return parts.Contains(item);
}
public void CopyTo(Part[] array, int arrayIndex)
{
parts.CopyTo(array, arrayIndex);
}
public int Count
{
get { return parts.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public object ForEach { get; set; }
public IEnumerator<Part> GetEnumerator()
{
return parts.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return parts.GetEnumerator();
}
}
public class PartAddedEventArgs : EventArgs
{
public readonly Part Part;
public readonly int Index;
public PartAddedEventArgs(Part part, int index)
{
Part = part;
Index = index;
}
}
public class PartRemovedEventArgs : EventArgs
{
public readonly Part Part;
public readonly bool Succeeded;
public PartRemovedEventArgs(Part part, bool succeeded)
{
Part = part;
Succeeded = succeeded;
}
}
public class PartChangedEventArgs : EventArgs
{
public readonly Part OldPart;
public readonly Part NewPart;
public readonly int Index;
public PartChangedEventArgs(Part oldPart, Part newPart, int index)
{
OldPart = oldPart;
NewPart = newPart;
Index = index;
}
}
}

View File

@@ -0,0 +1,187 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.Collections
{
public class PlateCollection : IList<Plate>, ICollection<Plate>, IEnumerable<Plate>
{
private List<Plate> plates;
public event EventHandler<PlateAddedEventArgs> PlateAdded;
public event EventHandler<PlateRemovedEventArgs> PlateRemoved;
public event EventHandler<PlateChangedEventArgs> PlateChanged;
public PlateCollection()
{
plates = new List<Plate>();
}
public void Add(Plate item)
{
var index = plates.Count;
plates.Add(item);
if (PlateAdded != null)
PlateAdded.Invoke(this, new PlateAddedEventArgs(item, index));
}
public void AddRange(IEnumerable<Plate> collection)
{
var index = plates.Count;
plates.AddRange(collection);
if (PlateAdded != null)
{
foreach (var plate in collection)
PlateAdded.Invoke(this, new PlateAddedEventArgs(plate, index++));
}
}
public void Insert(int index, Plate item)
{
plates.Insert(index, item);
if (PlateAdded != null)
PlateAdded.Invoke(this, new PlateAddedEventArgs(item, index));
}
public bool Remove(Plate item)
{
var success = plates.Remove(item);
if (PlateRemoved != null)
PlateRemoved.Invoke(this, new PlateRemovedEventArgs(item, success));
return success;
}
public void RemoveAt(int index)
{
var plate = plates[index];
plates.RemoveAt(index);
if (PlateRemoved != null)
PlateRemoved.Invoke(this, new PlateRemovedEventArgs(plate, true));
}
public void Clear()
{
for (int i = plates.Count - 1; i >= 0; --i)
RemoveAt(i);
}
public int IndexOf(Plate item)
{
return plates.IndexOf(item);
}
public Plate this[int index]
{
get
{
return plates[index];
}
set
{
var old = plates[index];
plates[index] = value;
if (PlateChanged != null)
{
var eventArgs = new PlateChangedEventArgs(old, value, index);
PlateChanged.Invoke(this, eventArgs);
}
}
}
public bool Contains(Plate item)
{
return plates.Contains(item);
}
public void CopyTo(Plate[] array, int arrayIndex)
{
plates.CopyTo(array, arrayIndex);
}
public int Count
{
get { return plates.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public IEnumerator<Plate> GetEnumerator()
{
return plates.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return plates.GetEnumerator();
}
public void RemoveEmptyPlates()
{
if (Count < 2)
return;
for (int i = Count - 1; i >= 0; --i)
{
if (this[i].Parts.Count == 0)
RemoveAt(i);
}
}
public int TotalCount
{
get { return plates.Sum(plate => plate.Quantity); }
}
}
public class PlateAddedEventArgs : EventArgs
{
public readonly Plate Plate;
public readonly int Index;
public PlateAddedEventArgs(Plate plate, int index)
{
Plate = plate;
Index = index;
}
}
public class PlateChangedEventArgs : EventArgs
{
public readonly Plate OldPlate;
public readonly Plate NewPlate;
public readonly int Index;
public PlateChangedEventArgs(Plate oldPlate, Plate newPlate, int index)
{
OldPlate = oldPlate;
NewPlate = newPlate;
Index = index;
}
}
public class PlateRemovedEventArgs : EventArgs
{
public readonly Plate Plate;
public readonly bool Succeeded;
public PlateRemovedEventArgs(Plate plate, bool succeeded)
{
Plate = plate;
Succeeded = succeeded;
}
}
}

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

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

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

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

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

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

View File

@@ -0,0 +1,139 @@
using System;
using OpenNest.Collections;
namespace OpenNest
{
public class Nest
{
public PlateCollection Plates;
public DrawingCollection Drawings;
public Nest()
: this(string.Empty)
{
}
public Nest(string name)
{
Name = name;
Plates = new PlateCollection();
Plates.PlateRemoved += Plates_PlateRemoved;
Drawings = new DrawingCollection();
PlateDefaults = new PlateSettings();
Customer = string.Empty;
Notes = string.Empty;
}
private static void Plates_PlateRemoved(object sender, PlateRemovedEventArgs e)
{
e.Plate.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,109 @@
<?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.0</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>
</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>
</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\PartCollection.cs" />
<Compile Include="Plate.cs" />
<Compile Include="Collections\PlateCollection.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>

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

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<PartAddedEventArgs> PartAdded
{
add { Parts.PartAdded += value; }
remove { Parts.PartAdded -= value; }
}
public event EventHandler<PartRemovedEventArgs> PartRemoved
{
add { Parts.PartRemoved += value; }
remove { Parts.PartRemoved -= value; }
}
public event EventHandler<PartChangedEventArgs> PartChanged
{
add { Parts.PartChanged += value; }
remove { Parts.PartChanged -= 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 PartCollection();
Parts.PartAdded += Parts_PartAdded;
Parts.PartRemoved += Parts_PartRemoved;
Quadrant = 1;
}
private void Parts_PartAdded(object sender, PartAddedEventArgs e)
{
e.Part.BaseDrawing.Quantity.Nested += Quantity;
}
private void Parts_PartRemoved(object sender, PartRemovedEventArgs e)
{
e.Part.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 PartCollection 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")]

View File

@@ -0,0 +1,14 @@
namespace OpenNest
{
public struct Quantity
{
public int Nested { get; internal set; }
public int Required { get; set; }
public int Remaining
{
get { return Required - Nested; }
}
}
}

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

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

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

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

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

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

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

View File

@@ -0,0 +1,80 @@
using System;
namespace OpenNest
{
internal static class BestCombination
{
public static bool FindFrom2(double length1, double length2, double overallLength, out int count1, out int count2)
{
overallLength += Tolerance.Epsilon;
if (length1 > overallLength)
{
if (length2 > overallLength)
{
count1 = 0;
count2 = 0;
return false;
}
count1 = 0;
count2 = (int)Math.Floor(overallLength / length2);
return true;
}
if (length2 > overallLength)
{
count1 = (int)Math.Floor(overallLength / length1);
count2 = 0;
return true;
}
var maxCountLength1 = (int)Math.Floor(overallLength / length1);
count1 = maxCountLength1;
count2 = 0;
var remnant = overallLength - maxCountLength1 * length1;
if (remnant.IsEqualTo(0))
return true;
for (int countLength1 = 0; countLength1 <= maxCountLength1; ++countLength1)
{
var remnant1 = overallLength - countLength1 * length1;
if (remnant1 >= length2)
{
var countLength2 = (int)Math.Floor(remnant1 / length2);
var remnant2 = remnant1 - length2 * countLength2;
if (!(remnant2 < remnant))
continue;
count1 = countLength1;
count2 = countLength2;
if (remnant2.IsEqualTo(0))
break;
remnant = remnant2;
}
else
{
if (!(remnant1 < remnant))
continue;
count1 = countLength1;
count2 = 0;
if (remnant1.IsEqualTo(0))
break;
remnant = remnant1;
}
}
return true;
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.CirclePacking
{
internal class Bin : Box
{
public Bin()
{
Items = new List<Item>();
}
public List<Item> Items { get; set; }
public double Density()
{
return Items.Sum(i => i.Area()) / Area();
}
public object Clone()
{
return new Bin
{
Location = this.Location,
Size = this.Size,
Items = new List<Item>(Items)
};
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
namespace OpenNest.CirclePacking
{
internal class FillEndEven : FillEngine
{
public FillEndEven(Bin bin)
: base(bin)
{
}
public override void Fill(Item item)
{
var max = new Vector(
Bin.Right - item.BoundingBox.Right + Tolerance.Epsilon,
Bin.Top - item.BoundingBox.Top + Tolerance.Epsilon);
var rows = Math.Floor((Bin.Height + Tolerance.Epsilon) / (item.Diameter));
var diameter = item.Diameter;
var remaining = Bin.Height - diameter * rows;
var radius = diameter * 0.5;
if (remaining < radius)
{
var yodd = Bin.Y + remaining;
var yoffset = diameter;
var xoffset = Trigonometry.Base(remaining, diameter);
int column = 0;
for (var x = Bin.X; x <= max.X; x += xoffset)
{
var y = column.IsOdd() ? yodd : Bin.Y;
for (; y <= max.Y; y += yoffset)
{
Bin.Items.Add(new Item
{
Center = new Vector(x, y)
});
}
column++;
}
}
else
{
var yoffset = (Bin.Height - diameter) / (2 * rows - 1);
var xoffset = Trigonometry.Base(yoffset, diameter);
var yodd = Bin.Y + yoffset;
yoffset *= 2.0;
int column = 0;
for (var x = Bin.X; x <= max.X; x += xoffset)
{
var y = column.IsOdd() ? yodd : Bin.Y;
for (; y <= max.Y; y += yoffset)
{
Bin.Items.Add(new Item
{
Center = new Vector(x, y)
});
}
column++;
}
}
}
public override void Fill(Item item, int maxCount)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
namespace OpenNest.CirclePacking
{
internal class FillEndOdd : FillEngine
{
public FillEndOdd(Bin bin)
: base(bin)
{
}
public override void Fill(Item item)
{
var bin1 = FillHorizontal(item);
var bin2 = FillVertical(item);
if (bin1.Items.Count > bin2.Items.Count)
Bin.Items.AddRange(bin1.Items);
else
Bin.Items.AddRange(bin2.Items);
}
public override void Fill(Item item, int maxCount)
{
throw new NotImplementedException();
}
private Bin FillHorizontal(Item item)
{
var bin = Bin.Clone() as Bin;
var max = new Vector(
bin.Right - item.BoundingBox.Right + Tolerance.Epsilon,
bin.Top - item.BoundingBox.Top + Tolerance.Epsilon);
var count = Math.Floor((bin.Width + Tolerance.Epsilon) / item.Diameter);
if (count == 0)
return bin;
var xoffset = (bin.Width - item.Diameter) / (count - 1);
var yoffset = Trigonometry.Height(xoffset * 0.5, item.Diameter);
int row = 0;
for (var y = bin.Y; y <= max.Y; y += yoffset)
{
var x = row.IsOdd() ? bin.X + xoffset * 0.5 : bin.X;
for (; x <= max.X; x += xoffset)
{
var addedItem = item.Clone() as Item;
addedItem.Center = new Vector(x, y);
bin.Items.Add(addedItem);
}
row++;
}
return bin;
}
private Bin FillVertical(Item item)
{
var bin = Bin.Clone() as Bin;
var max = new Vector(
Bin.Right - item.BoundingBox.Right + Tolerance.Epsilon,
Bin.Top - item.BoundingBox.Top + Tolerance.Epsilon);
var count = Math.Floor((bin.Height + Tolerance.Epsilon) / item.Diameter);
if (count == 0)
return bin;
var yoffset = (bin.Height - item.Diameter) / (count - 1);
var xoffset = Trigonometry.Base(yoffset * 0.5, item.Diameter);
int column = 0;
for (var x = bin.X; x <= max.X; x += xoffset)
{
var y = column.IsOdd() ? bin.Y + yoffset * 0.5 : bin.Y;
for (; y <= max.Y; y += yoffset)
{
var addedItem = item.Clone() as Item;
addedItem.Center = new Vector(x, y);
bin.Items.Add(addedItem);
}
column++;
}
return bin;
}
}
}

View File

@@ -0,0 +1,17 @@

namespace OpenNest.CirclePacking
{
internal abstract class FillEngine
{
public FillEngine(Bin bin)
{
Bin = bin;
}
public Bin Bin { get; set; }
public abstract void Fill(Item item);
public abstract void Fill(Item item, int maxCount);
}
}

View File

@@ -0,0 +1,19 @@
using OpenNest.Geometry;
namespace OpenNest.CirclePacking
{
internal class Item : Circle
{
public int Id { get; set; }
public object Clone()
{
return new Item
{
Radius = this.Radius,
Center = this.Center,
Id = this.Id
};
}
}
}

View File

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

namespace OpenNest
{
public enum NestDirection
{
Vertical,
Horizontal
}
}

View File

@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using OpenNest.RectanglePacking;
namespace OpenNest
{
public class NestEngine
{
public NestEngine(Plate plate)
{
Plate = plate;
}
public Plate Plate { get; set; }
public NestDirection NestDirection { get; set; }
public bool Fill(NestItem item)
{
var workArea = Plate.WorkArea();
return FillArea(workArea, item);
}
public bool Fill(NestItem item, int maxCount)
{
var workArea = Plate.WorkArea();
return FillArea(workArea, item, maxCount);
}
public bool FillArea(Box box, NestItem item)
{
var binItem = ConvertToRectangleItem(item);
var bin = new Bin
{
Location = box.Location,
Size = box.Size
};
bin.Width += Plate.PartSpacing;
bin.Height += Plate.PartSpacing;
var engine = new FillBestFit(bin);
engine.Fill(binItem);
var nestItems = new List<NestItem>();
nestItems.Add(item);
var parts = ConvertToParts(bin, nestItems);
Plate.Parts.AddRange(parts);
return parts.Count > 0;
}
public bool FillArea(Box box, NestItem item, int maxCount)
{
var binItem = ConvertToRectangleItem(item);
var bin = new Bin
{
Location = box.Location,
Size = box.Size
};
bin.Width += Plate.PartSpacing;
bin.Height += Plate.PartSpacing;
var engine = new FillBestFit(bin);
engine.Fill(binItem, maxCount);
var nestItems = new List<NestItem>();
nestItems.Add(item);
var parts = ConvertToParts(bin, nestItems);
Plate.Parts.AddRange(parts);
return parts.Count > 0;
}
public bool Pack(List<NestItem> items)
{
var workArea = Plate.WorkArea();
return PackArea(workArea, items);
}
public bool PackArea(Box box, List<NestItem> items)
{
var binItems = ConvertToRectangleItems(items);
var bin = new Bin
{
Location = box.Location,
Size = box.Size
};
bin.Width += Plate.PartSpacing;
bin.Height += Plate.PartSpacing;
var engine = new PackBottomLeft(bin);
engine.Pack(binItems);
var parts = ConvertToParts(bin, items);
Plate.Parts.AddRange(parts);
return parts.Count > 0;
}
private List<Part> ConvertToParts(Bin bin, List<NestItem> items)
{
var parts = new List<Part>();
foreach (var item in bin.Items)
{
var nestItem = items[item.Id];
var part = ConvertToPart(item, nestItem.Drawing);
parts.Add(part);
}
return parts;
}
private Part ConvertToPart(Item item, Drawing dwg)
{
var part = new Part(dwg);
if (item.IsRotated)
part.Rotate(Angle.HalfPI);
var boundingBox = part.Program.BoundingBox();
var offset = item.Location - boundingBox.Location;
part.Offset(offset);
return part;
}
private List<Item> ConvertToRectangleItems(List<NestItem> items)
{
var binItems = new List<Item>();
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
var binItem = ConvertToRectangleItem(item, i);
int maxQty = (int)Math.Floor(Plate.Area() / binItem.Area());
int qty = item.Quantity < maxQty
? item.Quantity
: maxQty;
for (int j = 0; j < qty; j++)
binItems.Add(binItem.Clone() as Item);
}
return binItems;
}
private Item ConvertToRectangleItem(NestItem item, int id = 0)
{
var box = item.Drawing.Program.BoundingBox();
box.Width += Plate.PartSpacing;
box.Height += Plate.PartSpacing;
return new Item
{
Id = id,
Location = box.Location,
Size = box.Size
};
}
}
}

View File

@@ -0,0 +1,35 @@
namespace OpenNest
{
public class NestItem
{
/// <summary>
/// The drawing to be nested.
/// </summary>
public Drawing Drawing { get; set; }
/// <summary>
/// Priority of the part determines nesting order. Highest priority will be nested first.
/// </summary>
public int Priority { get; set; }
/// <summary>
/// The number of parts to be nested.
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// The rotation step in radians.
/// </summary>
public double StepAngle { get; set; }
/// <summary>
/// The rotation start angle in radians.
/// </summary>
public double RotationStart { get; set; }
/// <summary>
/// The rotation end angle in radians.
/// </summary>
public double RotationEnd { get; set; }
}
}

View File

@@ -0,0 +1,70 @@
<?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>{0083B9CC-54AD-4085-A30D-56BC6834B71A}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>OpenNest</RootNamespace>
<AssemblyName>OpenNest.Engine</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</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>
</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>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="BestCombination.cs" />
<Compile Include="CirclePacking\Bin.cs" />
<Compile Include="CirclePacking\FillEndEven.cs" />
<Compile Include="CirclePacking\FillEndOdd.cs" />
<Compile Include="CirclePacking\FillEngine.cs" />
<Compile Include="CirclePacking\Item.cs" />
<Compile Include="NestDirection.cs" />
<Compile Include="NestEngine.cs" />
<Compile Include="NestItem.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RectanglePacking\Bin.cs" />
<Compile Include="RectanglePacking\FillBestFit.cs" />
<Compile Include="RectanglePacking\FillEngine.cs" />
<Compile Include="RectanglePacking\Item.cs" />
<Compile Include="RectanglePacking\PackBottomLeft.cs" />
<Compile Include="RectanglePacking\PackEngine.cs" />
<Compile Include="RectanglePacking\FillNoRotation.cs" />
<Compile Include="RectanglePacking\FillSameRotation.cs" />
<Compile Include="RectanglePacking\PackFirstFitDecreasing.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj">
<Project>{5a5fde8d-f8db-440e-866c-c4807e1686cf}</Project>
<Name>OpenNest.Core</Name>
</ProjectReference>
</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>

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.Engine")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OpenNest.Engine")]
[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("50013ca9-b047-41f5-b519-72e523902b53")]
// 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.1.0.0")]
[assembly: AssemblyFileVersion("0.1.0.0")]

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.RectanglePacking
{
internal class Bin : Box
{
public Bin()
{
Items = new List<Item>();
}
public List<Item> Items { get; set; }
public double Density()
{
return Items.Sum(i => i.Area()) / Area();
}
public object Clone()
{
return new Bin
{
Location = this.Location,
Size = this.Size,
Items = new List<Item>(Items)
};
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
namespace OpenNest.RectanglePacking
{
internal class FillBestFit : FillEngine
{
public FillBestFit(Bin bin)
: base(bin)
{
}
public override void Fill(Item item)
{
var bin1 = BestFitHorizontal(item);
var bin2 = BestFitVertical(item);
if (bin1.Items.Count == bin2.Items.Count)
{
var usedArea1 = bin1.Items.GetBoundingBox().Area();
var usedArea2 = bin2.Items.GetBoundingBox().Area();
if (usedArea2 < usedArea1)
Bin.Items.AddRange(bin2.Items);
else
Bin.Items.AddRange(bin1.Items);
}
else if (bin1.Items.Count > bin2.Items.Count)
Bin.Items.AddRange(bin1.Items);
else
Bin.Items.AddRange(bin2.Items);
}
public override void Fill(Item item, int maxCount)
{
throw new NotImplementedException();
}
private Bin BestFitHorizontal(Item item)
{
var bin = Bin.Clone() as Bin;
int normalColumns = 0;
int rotateColumns = 0;
if (!BestCombination.FindFrom2(item.Width, item.Height, bin.Width, out normalColumns, out rotateColumns))
return bin;
var normalRows = (int)Math.Floor((bin.Height + Tolerance.Epsilon) / item.Height);
var rotateRows = (int)Math.Floor((bin.Height + Tolerance.Epsilon) / item.Width);
item.Location = bin.Location;
bin.Items.AddRange(VPattern(item, normalRows, normalColumns, int.MaxValue));
item.Location.X += item.Width * normalColumns;
item.Rotate();
bin.Items.AddRange(VPattern(item, rotateRows, rotateColumns, int.MaxValue));
return bin;
}
private Bin BestFitVertical(Item item)
{
var bin = Bin.Clone() as Bin;
int normalRows = 0;
int rotateRows = 0;
if (!BestCombination.FindFrom2(item.Height, item.Width, Bin.Height, out normalRows, out rotateRows))
return bin;
var normalColumns = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width);
var rotateColumns = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Height);
item.Location = bin.Location;
bin.Items.AddRange(VPattern(item, normalRows, normalColumns, int.MaxValue));
item.Location.Y += item.Height * normalRows;
item.Rotate();
bin.Items.AddRange(VPattern(item, rotateRows, rotateColumns, int.MaxValue));
return bin;
}
}
}

View File

@@ -0,0 +1,82 @@
using System.Collections.Generic;
namespace OpenNest.RectanglePacking
{
internal abstract class FillEngine
{
public FillEngine(Bin bin)
{
Bin = bin;
}
public Bin Bin { get; set; }
public abstract void Fill(Item item);
public abstract void Fill(Item item, int maxCount);
/// <summary>
/// Vertical pattern.
/// </summary>
/// <param name="item"></param>
/// <param name="rows"></param>
/// <param name="columns"></param>
/// <param name="maxCount"></param>
protected List<Item> VPattern(Item item, int rows, int columns, int maxCount)
{
var items = new List<Item>();
for (int i = 0; i < columns; i++)
{
var x = item.Width * i + item.X;
for (int j = 0; j < rows; j++)
{
var y = item.Height * j + item.Y;
var addedItem = item.Clone() as Item;
addedItem.Location = new Vector(x, y);
items.Add(addedItem);
if (items.Count == maxCount)
return items;
}
}
return items;
}
/// <summary>
/// Horizontal pattern.
/// </summary>
/// <param name="item"></param>
/// <param name="rows"></param>
/// <param name="columns"></param>
/// <param name="maxCount"></param>
protected List<Item> HPattern(Item item, int rows, int columns, int maxCount)
{
var items = new List<Item>();
for (int i = 0; i < rows; i++)
{
var y = item.Height * i + item.Y;
for (int j = 0; j < rows; j++)
{
var x = item.Width * j + item.X;
var addedItem = item.Clone() as Item;
addedItem.Location = new Vector(x, y);
items.Add(addedItem);
if (items.Count == maxCount)
return items;
}
}
return items;
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
namespace OpenNest.RectanglePacking
{
internal class FillNoRotation : FillEngine
{
public FillNoRotation(Bin bin)
: base(bin)
{
}
public NestDirection NestDirection { get; set; }
public override void Fill(Item item)
{
var ycount = (int)Math.Floor((Bin.Height + Tolerance.Epsilon) / item.Height);
var xcount = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width);
var count = ycount * xcount;
for (int i = 0; i < xcount; i++)
{
var x = item.Width * i + Bin.X;
for (int j = 0; j < ycount; j++)
{
var y = item.Height * j + Bin.Y;
var addedItem = item.Clone() as Item;
addedItem.Location = new Vector(x, y);
Bin.Items.Add(addedItem);
}
}
}
public override void Fill(Item item, int maxCount)
{
var ycount = (int)Math.Floor((Bin.Height + Tolerance.Epsilon) / item.Height);
var xcount = (int)Math.Floor((Bin.Width + Tolerance.Epsilon) / item.Width);
var count = ycount * xcount;
if (count <= maxCount)
{
Fill(item);
return;
}
var columns = 0;
var rows = 0;
if (NestDirection == NestDirection.Vertical)
{
columns = (int)Math.Ceiling((double)maxCount / ycount);
rows = (int)Math.Ceiling((double)maxCount / columns);
}
else
{
rows = (int)Math.Ceiling((double)maxCount / xcount);
columns = (int)Math.Ceiling((double)maxCount / rows);
}
if (item.Width > item.Height)
VPattern(item, rows, columns, maxCount);
else
HPattern(item, rows, columns, maxCount);
}
}
}

View File

@@ -0,0 +1,65 @@

namespace OpenNest.RectanglePacking
{
internal class FillSameRotation : FillEngine
{
public FillSameRotation(Bin bin)
: base(bin)
{
}
public override void Fill(Item item)
{
var bin1 = Bin.Clone() as Bin;
var bin2 = Bin.Clone() as Bin;
var engine = new FillNoRotation(bin1);
engine.Fill(item);
item.Rotate();
engine.Bin = bin2;
engine.Fill(item);
var density1 = bin1.Density();
var density2 = bin2.Density();
if (density1.IsEqualTo(density2))
{
var bounds1 = bin1.Items.GetBoundingBox();
var bounds2 = bin2.Items.GetBoundingBox();
if (bounds2.Right < bounds1.Right)
Bin.Items.AddRange(bin2.Items);
else
Bin.Items.AddRange(bin1.Items);
}
else if (density1 > density2)
Bin.Items.AddRange(bin1.Items);
else
Bin.Items.AddRange(bin2.Items);
}
public override void Fill(Item item, int maxCount)
{
var bin1 = Bin.Clone() as Bin;
var bin2 = Bin.Clone() as Bin;
var engine = new FillNoRotation(bin1);
engine.Fill(item, maxCount);
item.Rotate();
engine.Bin = bin2;
engine.Fill(item, maxCount);
var bounds1 = bin1.Items.GetBoundingBox();
var bounds2 = bin2.Items.GetBoundingBox();
if (bounds2.Right < bounds1.Right)
Bin.Items.AddRange(bin2.Items);
else
Bin.Items.AddRange(bin1.Items);
}
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
namespace OpenNest.RectanglePacking
{
internal class Item : Box
{
public int Id { get; set; }
public bool IsRotated { get; private set; }
public void Rotate()
{
Generic.Swap(ref Size.Width, ref Size.Height);
IsRotated = !IsRotated;
}
public object Clone()
{
return new Item
{
IsRotated = this.IsRotated,
Location = this.Location,
Size = this.Size,
Id = this.Id
};
}
}
internal static class ItemListExtensions
{
public static Box GetBoundingBox(this IList<Item> items)
{
if (items.Count == 0)
return Box.Empty;
double minX = items[0].X;
double minY = items[0].Y;
double maxX = items[0].X + items[0].Width;
double maxY = items[0].Y + items[0].Height;
foreach (var box in items)
{
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);
}
}
}

View File

@@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.RectanglePacking
{
internal class PackBottomLeft : PackEngine
{
private List<Vector> points;
public PackBottomLeft(Bin bin)
: base(bin)
{
points = new List<Vector>();
}
public override void Pack(List<Item> items)
{
items = items.OrderBy(i => -i.Area()).ToList();
points.Add(Bin.Location);
var skip = new List<int>();
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
if (skip.Contains(item.Id))
continue;
var pt = FindPointVertical(item);
if (pt == null)
{
skip.Add(item.Id);
continue;
}
item.Location = pt.Value;
points.Remove(pt.Value);
points.Add(new Vector(item.Left, item.Top));
points.Add(new Vector(item.Right, item.Bottom));
Bin.Items.Add(item);
}
points.Clear();
}
private Vector? FindPointVertical(Item item)
{
var pt = new Vector(double.MaxValue, double.MaxValue);
for (int i = 0; i < points.Count; i++)
{
var point = points[i];
item.Location = point;
if (!IsValid(item))
continue;
if (point.X < pt.X)
pt = point;
else if (point.X.IsEqualTo(pt.X) && point.Y < pt.Y)
pt = point;
}
if (pt.X != double.MaxValue && pt.Y != double.MaxValue)
return pt;
return null;
}
private Vector? FindPointHorizontal(Item item)
{
var pt = new Vector(double.MaxValue, double.MaxValue);
for (int i = 0; i < points.Count; i++)
{
var point = points[i];
item.Location = point;
if (!IsValid(item))
continue;
if (point.Y < pt.Y)
pt = point;
else if (point.Y.IsEqualTo(pt.Y) && point.X < pt.X)
pt = point;
}
if (pt.X != double.MaxValue && pt.Y != double.MaxValue)
return pt;
return null;
}
private bool IsValid(Item item)
{
if (!Bin.Contains(item))
return false;
foreach (var it in Bin.Items)
{
if (item.Intersects(it))
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace OpenNest.RectanglePacking
{
internal abstract class PackEngine
{
public PackEngine(Bin bin)
{
Bin = bin;
}
public Bin Bin { get; set; }
public abstract void Pack(List<Item> items);
}
}

View File

@@ -0,0 +1,121 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.RectanglePacking
{
internal class FirstFitDecreasing : PackEngine
{
private readonly List<Level> levels;
public FirstFitDecreasing(Bin bin)
: base(bin)
{
levels = new List<Level>();
}
public override void Pack(List<Item> items)
{
items = items.OrderBy(i => -i.Height).ToList();
foreach (var item in items)
{
if (item.Height > Bin.Height)
continue;
var level = FindLevel(item);
if (level == null)
continue;
level.AddItem(item);
}
}
private Level FindLevel(Item item)
{
foreach (var level in levels)
{
if (level.Height < item.Height)
continue;
if (level.RemainingWidth < item.Width)
continue;
return level;
}
return CreateNewLevel(item);
}
private Level CreateNewLevel(Item item)
{
var y = Bin.Y;
var lastLevel = levels.LastOrDefault();
if (lastLevel != null)
y = lastLevel.Y + lastLevel.Height;
var remaining = Bin.Top - y;
if (remaining < item.Height)
return null;
var level = new Level(Bin);
level.Y = y;
level.Height = item.Height;
levels.Add(level);
return level;
}
private class Level
{
public Level(Bin parent)
{
Parent = parent;
NextItemLocation = parent.Location;
}
public Bin Parent { get; set; }
private Vector NextItemLocation;
public double X
{
get { return Parent.X; }
}
public double Y
{
get { return NextItemLocation.Y; }
set { NextItemLocation.Y = value; }
}
public double Width
{
get { return Parent.Width; }
}
public double Height { get; set; }
public double Top
{
get { return Y + Height; }
}
public double RemainingWidth
{
get { return X + Width - NextItemLocation.X; }
}
public void AddItem(Item item)
{
item.Location = NextItemLocation;
Parent.Items.Add(item);
NextItemLocation = new Vector(NextItemLocation.X + item.Width, NextItemLocation.Y);
}
}
}
}

34
Source/OpenNest.sln Normal file
View File

@@ -0,0 +1,34 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest", "OpenNest\OpenNest.csproj", "{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest.Core", "OpenNest.Core\OpenNest.Core.csproj", "{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenNest.Engine", "OpenNest.Engine\OpenNest.Engine.csproj", "{0083B9CC-54AD-4085-A30D-56BC6834B71A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1F1E40E0-5C53-474F-A258-69C9C3FAC15A}.Release|Any CPU.Build.0 = Release|Any CPU
{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A5FDE8D-F8DB-440E-866C-C4807E1686CF}.Release|Any CPU.Build.0 = Release|Any CPU
{0083B9CC-54AD-4085-A30D-56BC6834B71A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0083B9CC-54AD-4085-A30D-56BC6834B71A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0083B9CC-54AD-4085-A30D-56BC6834B71A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0083B9CC-54AD-4085-A30D-56BC6834B71A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,20 @@
using OpenNest.Controls;
namespace OpenNest.Actions
{
public abstract class Action
{
protected PlateView plateView;
protected Action(PlateView plateView)
{
this.plateView = plateView;
}
public abstract void DisconnectEvents();
public abstract void CancelAction();
public abstract bool IsBusy();
}
}

View File

@@ -0,0 +1,136 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using OpenNest.Controls;
namespace OpenNest.Actions
{
[DisplayName("Add Parts")]
public class ActionAddPart : Action
{
private LayoutPart part;
private double lastScale;
public ActionAddPart(PlateView plateView)
: this(plateView, null)
{
}
public ActionAddPart(PlateView plateView, Drawing drawing)
: base(plateView)
{
plateView.KeyDown += plateView_KeyDown;
plateView.MouseMove += plateView_MouseMove;
plateView.MouseDown += plateView_MouseDown;
plateView.Paint += plateView_Paint;
part = LayoutPart.Create(new Part(drawing), plateView);
part.IsSelected = true;
lastScale = double.NaN;
plateView.SelectedParts.Clear();
plateView.SelectedParts.Add(part);
}
private void plateView_MouseDown(object sender, MouseEventArgs e)
{
switch (e.Button)
{
case MouseButtons.Left:
Apply();
break;
}
}
private void plateView_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.F1:
case Keys.Enter:
Apply();
break;
case Keys.F:
if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
Fill();
break;
}
}
private void plateView_Paint(object sender, PaintEventArgs e)
{
if (plateView.ViewScale != lastScale)
{
part.Update(plateView);
part.Draw(e.Graphics);
}
else
{
if (part.IsDirty)
part.Update(plateView);
part.Draw(e.Graphics);
}
lastScale = plateView.ViewScale;
}
private void plateView_MouseMove(object sender, MouseEventArgs e)
{
var offset = plateView.CurrentPoint - part.BoundingBox.Location;
part.Offset(offset);
plateView.Invalidate();
}
public override void DisconnectEvents()
{
plateView.KeyDown -= plateView_KeyDown;
plateView.MouseMove -= plateView_MouseMove;
plateView.MouseDown -= plateView_MouseDown;
plateView.Paint -= plateView_Paint;
plateView.SelectedParts.Clear();
plateView.Invalidate();
}
public override void CancelAction()
{
}
public override bool IsBusy()
{
return false;
}
private void Fill()
{
var boxes = new List<Box>();
foreach (var part in plateView.Plate.Parts)
boxes.Add(part.BoundingBox.Offset(plateView.Plate.PartSpacing));
var bounds = plateView.Plate.WorkArea();
var vbox = Helper.GetLargestBoxVertically(plateView.CurrentPoint, bounds, boxes);
var hbox = Helper.GetLargestBoxHorizontally(plateView.CurrentPoint, bounds, boxes);
var box = vbox.Area() > hbox.Area() ? vbox : hbox;
var engine = new NestEngine(plateView.Plate);
engine.FillArea(box, new NestItem { Drawing = this.part.BasePart.BaseDrawing });
}
private void Apply()
{
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
{
plateView.PushSelected(PushDirection.Left);
plateView.PushSelected(PushDirection.Down);
}
plateView.Plate.Parts.Add(part.BasePart.Clone() as Part);
}
}
}

View File

@@ -0,0 +1,120 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using OpenNest.Controls;
namespace OpenNest.Actions
{
[DisplayName("Clone Parts")]
public class ActionClone : Action
{
private readonly List<LayoutPart> parts;
private double lastScale;
public ActionClone(PlateView plateView, List<Part> partsToClone)
: base(plateView)
{
plateView.KeyDown += plateView_KeyDown;
plateView.MouseMove += plateView_MouseMove;
plateView.MouseDown += plateView_MouseDown;
plateView.Paint += plateView_Paint;
parts = new List<LayoutPart>();
lastScale = double.NaN;
for (int i = 0; i < partsToClone.Count; i++)
{
var part = LayoutPart.Create(partsToClone[i].Clone() as Part, plateView);
part.IsSelected = true;
parts.Add(part);
}
plateView.SelectedParts.Clear();
plateView.SelectedParts.AddRange(parts);
}
private void plateView_MouseDown(object sender, MouseEventArgs e)
{
switch (e.Button)
{
case MouseButtons.Left:
Apply();
break;
}
}
private void plateView_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.F1:
case Keys.Enter:
Apply();
break;
}
}
private void plateView_Paint(object sender, PaintEventArgs e)
{
if (plateView.ViewScale != lastScale)
{
parts.ForEach(p =>
{
p.Update(plateView);
p.Draw(e.Graphics);
});
}
else
{
parts.ForEach(p =>
{
if (p.IsDirty)
p.Update(plateView);
p.Draw(e.Graphics);
});
}
lastScale = plateView.ViewScale;
}
private void plateView_MouseMove(object sender, MouseEventArgs e)
{
var offset = plateView.CurrentPoint - parts.GetBoundingBox().Location;
parts.ForEach(p => p.Offset(offset));
plateView.Invalidate();
}
public override void DisconnectEvents()
{
plateView.KeyDown -= plateView_KeyDown;
plateView.MouseMove -= plateView_MouseMove;
plateView.MouseDown -= plateView_MouseDown;
plateView.Paint -= plateView_Paint;
plateView.SelectedParts.Clear();
plateView.Invalidate();
}
public override void CancelAction()
{
}
public override bool IsBusy()
{
return false;
}
public void Apply()
{
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
{
plateView.PushSelected(PushDirection.Left);
plateView.PushSelected(PushDirection.Down);
}
parts.ForEach(p => plateView.Plate.Parts.Add(p.BasePart.Clone() as Part));
}
}
}

View File

@@ -0,0 +1,43 @@
using System.ComponentModel;
using System.Windows.Forms;
using OpenNest.Controls;
namespace OpenNest.Actions
{
[DisplayName("Fill Area")]
public class ActionFillArea : ActionSelectArea
{
private Drawing drawing;
public ActionFillArea(PlateView plateView, Drawing drawing)
: base(plateView)
{
plateView.PreviewKeyDown += plateView_PreviewKeyDown;
this.drawing = drawing;
}
private void plateView_PreviewKeyDown(object sender, System.Windows.Forms.PreviewKeyDownEventArgs e)
{
if (e.KeyCode == Keys.Enter)
FillArea();
}
private void FillArea()
{
var engine = new NestEngine(plateView.Plate);
engine.FillArea(SelectedArea, new NestItem
{
Drawing = drawing
});
plateView.Invalidate();
Update();
}
public override void DisconnectEvents()
{
plateView.PreviewKeyDown -= plateView_PreviewKeyDown;
base.DisconnectEvents();
}
}
}

View File

@@ -0,0 +1,252 @@
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using OpenNest.Controls;
namespace OpenNest.Actions
{
[DisplayName("Select")]
public class ActionSelect : Action
{
private readonly Pen borderPenContain;
private readonly Pen borderPenIntersect;
private readonly Brush fillBrushContain;
private readonly Brush fillBrushIntersect;
private Pen borderPen;
private Brush fillBrush;
public Vector Point1;
public Vector Point2;
private Status status;
public ActionSelect(PlateView plateView)
: base(plateView)
{
borderPenIntersect = new Pen(Color.FromArgb(50, 255, 50));
fillBrushIntersect = new SolidBrush(Color.FromArgb(50, 50, 255, 50));
borderPenContain = new Pen(Color.FromArgb(99, 162, 228));
fillBrushContain = new SolidBrush(Color.FromArgb(90, 150, 200, 255));
UpdateBrushAndPen();
status = Status.SetFirstPoint;
plateView.MouseDown += plateView_MouseDown;
plateView.MouseUp += plateView_MouseUp;
plateView.MouseMove += plateView_MouseMove;
plateView.Paint += plateView_Paint;
}
public override void DisconnectEvents()
{
plateView.MouseDown -= plateView_MouseDown;
plateView.MouseUp -= plateView_MouseUp;
plateView.MouseMove -= plateView_MouseMove;
plateView.Paint -= plateView_Paint;
}
public override void CancelAction()
{
status = Status.SetFirstPoint;
plateView.DeselectAll();
plateView.Invalidate();
}
public override bool IsBusy()
{
return status != Status.SetFirstPoint || plateView.SelectedParts.Count > 0;
}
private void plateView_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
if (status == Status.SetSecondPoint)
{
Point2 = plateView.PointControlToWorld(e.Location);
UpdateBrushAndPen();
}
else
{
if (plateView.SelectedParts.Count == 0)
return;
var offset = plateView.CurrentPoint - plateView.LastPoint;
foreach (var part in plateView.SelectedParts)
part.Offset(offset.X, offset.Y);
}
plateView.Invalidate();
}
private void plateView_MouseDown(object sender, MouseEventArgs e)
{
if (!plateView.AllowSelect)
return;
if (e.Button == MouseButtons.Left)
{
if (plateView.SelectedParts.Count > 0)
{
var part = plateView.GetPartAtControlPoint(e.Location);
if (part == null)
CancelAction();
}
if (SelectPartAtCurrentPoint())
return;
if (status == Status.SetFirstPoint)
{
Point1 = plateView.PointControlToWorld(e.Location);
Point2 = Point1;
status = Status.SetSecondPoint;
}
}
else if (e.Button == MouseButtons.Right)
{
CancelAction();
}
}
private void plateView_MouseUp(object sender, MouseEventArgs e)
{
if (!plateView.AllowSelect)
return;
if (e.Button != MouseButtons.Left)
return;
if (status == Status.SetSecondPoint)
{
Point2 = plateView.PointControlToWorld(e.Location);
SelectPartsInWindow();
plateView.Invalidate();
status = Status.SetFirstPoint;
}
}
private void plateView_Paint(object sender, PaintEventArgs e)
{
if (status != Status.SetSecondPoint)
return;
var rect = GetRectangle();
e.Graphics.FillRectangle(fillBrush, rect);
e.Graphics.DrawRectangle(borderPen,
rect.X,
rect.Y,
rect.Width,
rect.Height);
}
private bool SelectPartAtCurrentPoint()
{
var part = plateView.GetPartAtPoint(plateView.CurrentPoint);
if (part == null)
{
plateView.DeselectAll();
status = Status.SetFirstPoint;
return false;
}
if (Control.ModifierKeys != Keys.Control && plateView.SelectedParts.Contains(part) == false)
plateView.DeselectAll();
if (plateView.SelectedParts.Contains(part) == false)
{
plateView.SelectedParts.Add(part);
part.IsSelected = true;
}
plateView.Invalidate();
return true;
}
private void SelectPartsInWindow()
{
var parts = plateView.GetPartsFromWindow(GetRectangle(), SelectionType);
foreach (var part in parts)
{
if (plateView.SelectedParts.Contains(part))
continue;
plateView.SelectedParts.Add(part);
part.IsSelected = true;
}
}
private void UpdateBrushAndPen()
{
if (SelectionType == SelectionType.Intersect)
{
borderPen = borderPenIntersect;
fillBrush = fillBrushIntersect;
}
else
{
borderPen = borderPenContain;
fillBrush = fillBrushContain;
}
}
private RectangleF GetRectangle()
{
var rect = new RectangleF();
var pt1 = plateView.PointWorldToGraph(Point1);
var pt2 = plateView.PointWorldToGraph(Point2);
if (pt1.X < pt2.X)
{
rect.X = pt1.X;
rect.Width = pt2.X - pt1.X;
}
else
{
rect.X = pt2.X;
rect.Width = pt1.X - pt2.X;
}
if (pt1.Y < pt2.Y)
{
rect.Y = pt1.Y;
rect.Height = pt2.Y - pt1.Y;
}
else
{
rect.Y = pt2.Y;
rect.Height = pt1.Y - pt2.Y;
}
return rect;
}
public SelectionType SelectionType
{
get
{
return Point1.X < Point2.X
? SelectionType.Contains
: SelectionType.Intersect;
}
}
public enum Status
{
SetFirstPoint,
SetSecondPoint
}
}
}

View File

@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using OpenNest.Controls;
namespace OpenNest.Actions
{
[DisplayName("Select Area")]
public class ActionSelectArea : Action
{
public Box SelectedArea { get; private set; }
private Box Bounds { get; set; }
private List<Box> boxes { get; set; }
private bool dynamicSelect;
private bool altSelect;
private readonly Pen pen;
private readonly Brush brush;
private readonly Font font;
private readonly StringFormat stringFormat;
public ActionSelectArea(PlateView plateView)
: base(plateView)
{
boxes = new List<Box>();
pen = new Pen(Color.FromArgb(50, 255, 50));
brush = new SolidBrush(Color.FromArgb(50, 50, 255, 50));
font = new Font(SystemFonts.DefaultFont.FontFamily, 14);
stringFormat = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
SelectedArea = Box.Empty;
dynamicSelect = true;
Update();
if (dynamicSelect)
plateView.MouseMove += plateView_MouseMove;
else
plateView.MouseClick += plateView_MouseClick;
plateView.KeyUp += plateView_KeyUp;
plateView.Paint += plateView_Paint;
}
private void plateView_KeyUp(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Space:
altSelect = !altSelect;
UpdateSelectedArea();
break;
}
}
public bool DynamicSelect
{
get { return dynamicSelect; }
set
{
if (value == dynamicSelect)
return;
dynamicSelect = value;
if (dynamicSelect)
{
plateView.MouseMove += plateView_MouseMove;
plateView.MouseClick -= plateView_MouseClick;
}
else
{
plateView.MouseMove -= plateView_MouseMove;
plateView.MouseClick += plateView_MouseClick;
}
}
}
private void plateView_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
if (SelectedArea == Box.Empty)
return;
var location = plateView.PointWorldToGraph(SelectedArea.Location);
var size = new SizeF(
plateView.LengthWorldToGui(SelectedArea.Width),
plateView.LengthWorldToGui(SelectedArea.Height));
var rect = new System.Drawing.RectangleF(location.X, location.Y - size.Height, size.Width, size.Height);
e.Graphics.DrawRectangle(pen,
rect.X,
rect.Y,
rect.Width,
rect.Height);
e.Graphics.FillRectangle(brush, rect);
e.Graphics.DrawString(
SelectedArea.Size.ToString(2),
font,
Brushes.Green,
rect,
stringFormat);
}
private void plateView_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
UpdateSelectedArea();
}
private void plateView_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (e.Button != System.Windows.Forms.MouseButtons.Left)
return;
UpdateSelectedArea();
plateView.Invalidate();
}
public override void DisconnectEvents()
{
plateView.KeyUp -= plateView_KeyUp;
plateView.MouseMove -= plateView_MouseMove;
plateView.MouseClick -= plateView_MouseClick;
plateView.Paint -= plateView_Paint;
}
public override void CancelAction()
{
plateView.Invalidate();
}
public override bool IsBusy()
{
return false;
}
private void UpdateSelectedArea()
{
SelectedArea = altSelect
? Helper.GetLargestBoxHorizontally(plateView.CurrentPoint, Bounds, boxes)
: Helper.GetLargestBoxVertically(plateView.CurrentPoint, Bounds, boxes);
plateView.Invalidate();
}
public void Update()
{
foreach (var part in plateView.Plate.Parts)
boxes.Add(part.BoundingBox.Offset(plateView.Plate.PartSpacing));
Bounds = plateView.Plate.WorkArea();
}
}
}

View File

@@ -0,0 +1,155 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using OpenNest.Controls;
using OpenNest.Forms;
using OpenNest.Geometry;
namespace OpenNest.Actions
{
[DisplayName("Set Sequence")]
public class ActionSetSequence : Action
{
private readonly SequenceForm SequenceForm;
private readonly List<Pair> ShapePartPairs;
private readonly Pen pen;
private Shape ClosestShape;
private Part ClosestPart;
private int sequenceNumber = 1;
public int SequenceNumber
{
get { return sequenceNumber; }
set
{
if (value <= SequenceForm.numericUpDown1.Maximum && value >= SequenceForm.numericUpDown1.Minimum)
{
sequenceNumber = value;
SequenceForm.numericUpDown1.Value = sequenceNumber;
}
}
}
public ActionSetSequence(PlateView plateView)
: base(plateView)
{
SequenceForm = new Forms.SequenceForm();
SequenceForm.numericUpDown1.DataBindings.Add("Value", this, "SequenceNumber", false, DataSourceUpdateMode.OnPropertyChanged);
SequenceForm.numericUpDown1.Maximum = plateView.Plate.Parts.Count;
SequenceForm.Owner = Application.OpenForms[0];
SequenceForm.Show();
plateView.Invalidate();
pen = new Pen(Color.Blue, 2.0f);
ShapePartPairs = new List<Pair>();
foreach (var part in plateView.Plate.Parts)
{
var entities = ConvertProgram.ToGeometry(part.Program).Where(e => e.Layer == SpecialLayers.Cut).ToList();
entities.ForEach(entity => entity.Offset(part.Location));
var shapes = Helper.GetShapes(entities);
var shape = new Shape();
shape.Entities.AddRange(shapes);
ShapePartPairs.Add(new Pair() { Part = part, Shape = shape });
}
plateView.MouseMove += plateView_MouseMove;
plateView.MouseClick += plateView_MouseClick;
plateView.Paint += plateView_Paint;
SequenceNumber = 1;
}
private void plateView_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
CancelAction();
if (e.Button != MouseButtons.Left)
return;
if (SequenceNumber > plateView.Plate.Parts.Count)
SequenceNumber = plateView.Plate.Parts.Count;
if (ClosestPart == null)
return;
plateView.Plate.Parts.Remove(ClosestPart);
plateView.Plate.Parts.Insert(SequenceNumber - 1, ClosestPart);
SequenceNumber++;
plateView.Invalidate();
}
private void plateView_MouseMove(object sender, MouseEventArgs e)
{
var pair = GetClosestShape();
ClosestShape = pair.HasValue ? pair.Value.Shape : null;
ClosestPart = pair.HasValue ? pair.Value.Part : null;
plateView.Invalidate();
}
private void plateView_Paint(object sender, PaintEventArgs e)
{
if (ClosestShape == null) return;
var path = ClosestShape.GetGraphicsPath();
path.Transform(plateView.Matrix);
e.Graphics.DrawPath(pen, path);
path.Dispose();
}
private Pair? GetClosestShape()
{
Pair? closestShape = null;
double distance = double.MaxValue;
for (int i = 0; i < ShapePartPairs.Count; i++)
{
var shape = ShapePartPairs[i];
var closestPt = shape.Shape.ClosestPointTo(plateView.CurrentPoint);
var distance2 = closestPt.DistanceTo(plateView.CurrentPoint);
if (distance2 < distance)
{
closestShape = shape;
distance = distance2;
}
}
return closestShape;
}
public override void DisconnectEvents()
{
plateView.Paint -= plateView_Paint;
plateView.MouseMove -= plateView_MouseMove;
plateView.MouseClick -= plateView_MouseClick;
plateView.Invalidate();
SequenceForm.Close();
}
public override void CancelAction()
{
SequenceNumber = 1;
SequenceForm.numericUpDown1.Value = SequenceNumber + 1;
}
public override bool IsBusy()
{
return false;
}
private struct Pair
{
public Part Part;
public Shape Shape;
}
}
}

View File

@@ -0,0 +1,205 @@
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using OpenNest.Controls;
namespace OpenNest.Actions
{
[DisplayName("Zoom Window")]
public class ActionZoomWindow : Action
{
public Vector Point1;
public Vector Point2;
private readonly Pen borderPen;
private readonly Brush fillBrush;
private Status status;
public ActionZoomWindow(PlateView plateView)
: base(plateView)
{
borderPen = new Pen(Color.FromArgb(99, 162, 228));
fillBrush = new SolidBrush(Color.FromArgb(90, 150, 200, 255));
status = Status.SetFirstPoint;
plateView.Cursor = Cursors.Arrow;
plateView.MouseDown += plateView_MouseDown;
plateView.MouseUp += plateView_MouseUp;
plateView.MouseMove += plateView_MouseMove;
plateView.Paint += plateView_Paint;
}
public override void DisconnectEvents()
{
plateView.Cursor = Cursors.Cross;
plateView.MouseDown -= plateView_MouseDown;
plateView.MouseUp -= plateView_MouseUp;
plateView.MouseMove -= plateView_MouseMove;
plateView.Paint -= plateView_Paint;
}
public override void CancelAction()
{
Point1 = Vector.Invalid;
Point2 = Vector.Invalid;
plateView.Invalidate();
status = Status.SetFirstPoint;
}
public override bool IsBusy()
{
return status != Status.SetFirstPoint;
}
private void plateView_MouseMove(object sender, MouseEventArgs e)
{
if (status == Status.SetSecondPoint || e.Button == MouseButtons.Left)
{
Point2 = plateView.PointControlToWorld(e.Location);
plateView.Invalidate();
}
}
private void plateView_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
CancelAction();
return;
}
if (e.Button != MouseButtons.Left)
return;
if (e.Button == MouseButtons.Left && status == Status.SetFirstPoint)
{
Point1 = plateView.PointControlToWorld(e.Location);
Point2 = Point1;
plateView.Invalidate();
}
}
private void plateView_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left)
return;
Point2 = plateView.PointControlToWorld(e.Location);
if (status == Status.SetFirstPoint)
{
var distance1 = Point1.DistanceTo(Point2);
var distance2 = plateView.LengthWorldToGui(distance1);
if (distance2 > 40)
{
ZoomWindow();
CancelAction();
}
else
{
status = Status.SetSecondPoint;
}
}
else if (status == Status.SetSecondPoint)
{
ZoomWindow();
CancelAction();
}
}
private void plateView_Paint(object sender, PaintEventArgs e)
{
if (!Point1.IsValid() || !Point2.IsValid())
return;
var rect = GetRectangle();
e.Graphics.FillRectangle(fillBrush, rect);
e.Graphics.DrawRectangle(borderPen,
rect.X,
rect.Y,
rect.Width,
rect.Height);
var centerX = rect.X + rect.Width * 0.5f;
var centerY = rect.Y + rect.Height * 0.5f;
const float halfWidth = 10;
e.Graphics.DrawLine(borderPen, centerX, centerY - halfWidth, centerX, centerY + halfWidth);
e.Graphics.DrawLine(borderPen, centerX - halfWidth, centerY, centerX + halfWidth, centerY);
}
private void ZoomWindow()
{
double x, y, w, h;
if (Point1.X < Point2.X)
{
x = Point1.X;
w = Point2.X - Point1.X;
}
else
{
x = Point2.X;
w = Point1.X - Point2.X;
}
if (Point1.Y < Point2.Y)
{
y = Point1.Y;
h = Point2.Y - Point1.Y;
}
else
{
y = Point2.Y;
h = Point1.Y - Point2.Y;
}
plateView.ZoomToArea(x, y, w, h);
plateView.Refresh();
}
private RectangleF GetRectangle()
{
var rect = new RectangleF();
var pt1 = plateView.PointWorldToGraph(Point1);
var pt2 = plateView.PointWorldToGraph(Point2);
if (pt1.X < pt2.X)
{
rect.X = pt1.X;
rect.Width = pt2.X - pt1.X;
}
else
{
rect.X = pt2.X;
rect.Width = pt1.X - pt2.X;
}
if (pt1.Y < pt2.Y)
{
rect.Y = pt1.Y;
rect.Height = pt2.Y - pt1.Y;
}
else
{
rect.Y = pt2.Y;
rect.Height = pt1.Y - pt2.Y;
}
return rect;
}
public enum Status
{
SetFirstPoint,
SetSecondPoint
}
}
}

View File

@@ -0,0 +1,140 @@
using System.Drawing;
using System.Drawing.Drawing2D;
namespace OpenNest
{
public sealed class ColorScheme
{
private Color layoutOutlineColor;
private Color layoutFillColor;
private Color boundingBoxColor;
private Color rapidColor;
private Color originColor;
private Color edgeSpacingColor;
public static readonly ColorScheme Default = new ColorScheme
{
BackgroundColor = Color.DarkGray,
LayoutOutlineColor = Color.Gray,
LayoutFillColor = Color.WhiteSmoke,
BoundingBoxColor = Color.FromArgb(128, 128, 255),
RapidColor = Color.DodgerBlue,
OriginColor = Color.Gray,
EdgeSpacingColor = Color.FromArgb(180, 180, 180),
};
#region Pens/Brushes
public Pen LayoutOutlinePen { get; private set; }
public Brush LayoutFillBrush { get; private set; }
public Pen BoundingBoxPen { get; private set; }
public Pen RapidPen { get; private set; }
public Pen OriginPen { get; private set; }
public Pen EdgeSpacingPen { get; private set; }
#endregion Pens/Brushes
#region Colors
public Color BackgroundColor { get; set; }
public Color LayoutOutlineColor
{
get { return layoutOutlineColor; }
set
{
layoutOutlineColor = value;
if (LayoutOutlinePen != null)
LayoutOutlinePen.Dispose();
LayoutOutlinePen = new Pen(value);
}
}
public Color LayoutFillColor
{
get { return layoutFillColor; }
set
{
layoutFillColor = value;
if (LayoutFillBrush != null)
LayoutFillBrush.Dispose();
LayoutFillBrush = new SolidBrush(value);
}
}
public Color BoundingBoxColor
{
get { return boundingBoxColor; }
set
{
boundingBoxColor = value;
if (BoundingBoxPen != null)
BoundingBoxPen.Dispose();
BoundingBoxPen = new Pen(value);
}
}
public Color RapidColor
{
get { return rapidColor; }
set
{
rapidColor = value;
if (RapidPen != null)
RapidPen.Dispose();
RapidPen = new Pen(value)
{
DashPattern = new float[] { 10, 10 },
DashCap = DashCap.Flat
};
}
}
public Color OriginColor
{
get { return originColor; }
set
{
originColor = value;
if (OriginPen != null)
OriginPen.Dispose();
OriginPen = new Pen(value);
}
}
public Color EdgeSpacingColor
{
get { return edgeSpacingColor; }
set
{
edgeSpacingColor = value;
if (EdgeSpacingPen != null)
EdgeSpacingPen.Dispose();
EdgeSpacingPen = new Pen(value)
{
DashPattern = new float[] { 3, 3 },
DashCap = DashCap.Flat
};
}
}
#endregion Colors
}
}

View File

@@ -0,0 +1,36 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace OpenNest.Controls
{
public class BottomPanel : Panel
{
private readonly Pen lightPen;
private readonly Pen darkPen;
public BottomPanel()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.Height = 50;
this.Dock = DockStyle.Bottom;
lightPen = new Pen(ProfessionalColors.SeparatorLight);
darkPen = new Pen(ProfessionalColors.SeparatorDark);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.DrawLine(darkPen, 0, 0, Width, 0);
e.Graphics.DrawLine(lightPen, 0, 1, Width, 1);
}
}
}

View File

@@ -0,0 +1,247 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace OpenNest.Controls
{
public abstract class DrawControl : Control
{
protected PointF origin;
protected Point lastPoint;
protected System.Drawing.Size lastSize;
public Vector LastPoint { get; protected set; }
public Vector CurrentPoint { get; protected set; }
public float ViewScale { get; protected set; }
public float ViewScaleMin { get; protected set; }
public float ViewScaleMax { get; protected set; }
internal Matrix Matrix { get; set; }
protected const int BorderWidth = 50;
protected const float ZoomInFactor = 1.15f;
protected const float ZoomOutFactor = 1.0f / ZoomInFactor;
protected DrawControl()
{
ViewScale = 1.0f;
ViewScaleMin = 0.3f;
ViewScaleMax = 3000;
origin = new PointF(100, 100);
}
/// <summary>
/// Returns a point with coordinates relative to the control from the graph.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Point PointGraphToControl(float x, float y)
{
return new Point((int)(x + origin.X), (int)(y + origin.Y));
}
/// <summary>
/// Returns a point with coordinates relative to the control from the graph.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
public Point PointGraphToControl(PointF pt)
{
return PointGraphToControl(pt.X, pt.Y);
}
/// <summary>
/// Returns a point with coordinates relative to the control from the world.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Point PointWorldToControl(double x, double y)
{
return PointGraphToControl(PointWorldToGraph(x, y));
}
/// <summary>
/// Returns a point with coordinates relative to the control from the world.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
public Point PointWorldToControl(Vector pt)
{
return PointGraphToControl(PointWorldToGraph(pt.X, pt.Y));
}
/// <summary>
/// Returns a point with coordinates relative to the graph from the control.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public PointF PointControlToGraph(int x, int y)
{
return new PointF(x - origin.X, y - origin.Y);
}
/// <summary>
/// Returns a point with coordinates relative to the graph from the control.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
public PointF PointControlToGraph(Point pt)
{
return PointControlToGraph(pt.X, pt.Y);
}
/// <summary>
/// Returns a point with coordinates relative to the graph from the world.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public PointF PointWorldToGraph(double x, double y)
{
return new PointF(ViewScale * (float)x, -ViewScale * (float)y);
}
/// <summary>
/// Returns a point with coordinates relative to the graph from the world.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
public PointF PointWorldToGraph(Vector pt)
{
return PointWorldToGraph(pt.X, pt.Y);
}
/// <summary>
/// Returns a point with coordinates relative to the world from the control.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Vector PointControlToWorld(int x, int y)
{
return PointGraphToWorld(PointControlToGraph(x, y));
}
/// <summary>
/// Returns a point with coordinates relative to the world from the control.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
public Vector PointControlToWorld(Point pt)
{
return PointGraphToWorld(PointControlToGraph(pt.X, pt.Y));
}
/// <summary>
/// Returns a point with coordinates relative to the world from the graph.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Vector PointGraphToWorld(float x, float y)
{
return new Vector(x / ViewScale, y / -ViewScale);
}
/// <summary>
/// Returns a point with coordinates relative to the world from the graph.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
public Vector PointGraphToWorld(PointF pt)
{
return PointGraphToWorld(pt.X, pt.Y);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
origin.X += (Size.Width - lastSize.Width) * 0.5f;
origin.Y += (Size.Height - lastSize.Height) * 0.5f;
lastSize = Size;
}
public float LengthWorldToGui(double length)
{
return ViewScale * (float)length;
}
public double LengthGuiToWorld(float length)
{
return length / ViewScale;
}
public virtual void ZoomToControlPoint(Point pt, float zoomFactor, bool redraw = true)
{
var pt2 = PointControlToWorld(pt);
ZoomToPoint(pt2, zoomFactor, redraw);
}
public virtual void ZoomToPoint(Vector pt, float zoomFactor, bool redraw = true)
{
var newScale = ViewScale * zoomFactor;
if (newScale < ViewScaleMin)
zoomFactor = ViewScaleMin / ViewScale;
else if (newScale > ViewScaleMax)
zoomFactor = ViewScaleMax / ViewScale;
origin.X -= (float)(pt.X * zoomFactor - pt.X) * ViewScale;
origin.Y += (float)(pt.Y * zoomFactor - pt.Y) * ViewScale;
ViewScale *= zoomFactor;
UpdateMatrix();
if (redraw) Invalidate();
}
public virtual void ZoomToArea(Box box, bool redraw = true)
{
ZoomToArea(box.X, box.Y, box.Width, box.Height, redraw);
}
public virtual void ZoomToArea(double x, double y, double width, double height, bool redraw = true)
{
if (width <= 0 || height <= 0)
return;
var a = (Height - BorderWidth) / height;
var b = (Width - BorderWidth) / width;
ViewScale = (float)(a < b ? a : b);
if (ViewScale > ViewScaleMax)
ViewScale = ViewScaleMax;
else if (ViewScale < ViewScaleMin)
ViewScale = ViewScaleMin;
var px = LengthWorldToGui(x);
var py = LengthWorldToGui(y);
var pw = LengthWorldToGui(width);
var ph = LengthWorldToGui(height);
origin.X = (Width - pw) * 0.5f - px;
origin.Y = (Height + ph) * 0.5f + py;
UpdateMatrix();
if (redraw) Invalidate();
}
protected virtual void UpdateMatrix()
{
if (Matrix != null)
Matrix.Dispose();
Matrix = new Matrix();
Matrix.Scale(ViewScale, -ViewScale);
}
}
}

Some files were not shown because too many files have changed in this diff Show More