First commit.
This commit is contained in:
22
.gitattributes
vendored
Normal file
22
.gitattributes
vendored
Normal 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
200
.gitignore
vendored
Normal 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
BIN
External/Ionic.Zip.dll
vendored
Normal file
Binary file not shown.
BIN
External/netDxf.dll
vendored
Normal file
BIN
External/netDxf.dll
vendored
Normal file
Binary file not shown.
138
Installer/script.nsi
Normal file
138
Installer/script.nsi
Normal 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
|
||||
186
Source/OpenNest.Core/Align.cs
Normal file
186
Source/OpenNest.Core/Align.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Align
|
||||
{
|
||||
public static void Vertically(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(fixedEntity.BoundingBox.Center.X - movableEntity.BoundingBox.Center.X, 0);
|
||||
}
|
||||
|
||||
public static void Vertically(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Vertically(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Vertically(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(fixedPart.BoundingBox.Center.X - movablePart.BoundingBox.Center.X, 0);
|
||||
}
|
||||
|
||||
public static void Vertically(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Vertically(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Horizontally(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(0, fixedEntity.BoundingBox.Center.Y - movableEntity.BoundingBox.Center.Y);
|
||||
}
|
||||
|
||||
public static void Horizontally(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Horizontally(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Horizontally(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(0, fixedPart.BoundingBox.Center.Y - movablePart.BoundingBox.Center.Y);
|
||||
}
|
||||
|
||||
public static void Horizontally(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Horizontally(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Left(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(fixedEntity.BoundingBox.Left - movableEntity.BoundingBox.Left, 0);
|
||||
}
|
||||
|
||||
public static void Left(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Left(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Left(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(fixedPart.BoundingBox.Left - movablePart.BoundingBox.Left, 0);
|
||||
}
|
||||
|
||||
public static void Left(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Left(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Right(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(fixedEntity.BoundingBox.Right - movableEntity.BoundingBox.Right, 0);
|
||||
}
|
||||
|
||||
public static void Right(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Right(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Right(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(fixedPart.BoundingBox.Right - movablePart.BoundingBox.Right, 0);
|
||||
}
|
||||
|
||||
public static void Right(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Right(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Top(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(0, fixedEntity.BoundingBox.Top - movableEntity.BoundingBox.Top);
|
||||
}
|
||||
|
||||
public static void Top(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Top(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Top(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(0, fixedPart.BoundingBox.Top - movablePart.BoundingBox.Top);
|
||||
}
|
||||
|
||||
public static void Top(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Top(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void Bottom(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(0, fixedEntity.BoundingBox.Bottom - movableEntity.BoundingBox.Bottom);
|
||||
}
|
||||
|
||||
public static void Bottom(Entity fixedEntity, List<Entity> entities)
|
||||
{
|
||||
entities.ForEach(entity => Bottom(fixedEntity, entity));
|
||||
}
|
||||
|
||||
public static void Bottom(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(0, fixedPart.BoundingBox.Bottom - movablePart.BoundingBox.Bottom);
|
||||
}
|
||||
|
||||
public static void Bottom(Part fixedPart, List<Part> parts)
|
||||
{
|
||||
parts.ForEach(part => Bottom(fixedPart, part));
|
||||
}
|
||||
|
||||
public static void EvenlyDistributeHorizontally(List<Part> parts)
|
||||
{
|
||||
if (parts.Count < 3)
|
||||
return;
|
||||
|
||||
var list = new List<Part>(parts);
|
||||
list.Sort((p1, p2) => p1.BoundingBox.Center.X.CompareTo(p2.BoundingBox.Center.X));
|
||||
|
||||
var lastIndex = list.Count - 1;
|
||||
|
||||
var first = list[0];
|
||||
var last = list[lastIndex];
|
||||
|
||||
var start = first.BoundingBox.Center.X;
|
||||
var end = last.BoundingBox.Center.X;
|
||||
var diff = end - start;
|
||||
|
||||
var spacing = diff / lastIndex;
|
||||
|
||||
for (int i = 1; i < lastIndex; ++i)
|
||||
{
|
||||
var part = list[i];
|
||||
var newX = start + i * spacing;
|
||||
var curX = part.BoundingBox.Center.X;
|
||||
|
||||
part.Offset(newX - curX, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EvenlyDistributeVertically(List<Part> parts)
|
||||
{
|
||||
if (parts.Count < 3)
|
||||
return;
|
||||
|
||||
var list = new List<Part>(parts);
|
||||
list.Sort((p1, p2) => p1.BoundingBox.Center.Y.CompareTo(p2.BoundingBox.Center.Y));
|
||||
|
||||
var lastIndex = list.Count - 1;
|
||||
|
||||
var first = list[0];
|
||||
var last = list[lastIndex];
|
||||
|
||||
var start = first.BoundingBox.Center.Y;
|
||||
var end = last.BoundingBox.Center.Y;
|
||||
var diff = end - start;
|
||||
|
||||
var spacing = diff / lastIndex;
|
||||
|
||||
for (int i = 1; i < lastIndex; ++i)
|
||||
{
|
||||
var part = list[i];
|
||||
var newX = start + i * spacing;
|
||||
var curX = part.BoundingBox.Center.Y;
|
||||
|
||||
part.Offset(0, newX - curX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Source/OpenNest.Core/AlignType.cs
Normal file
15
Source/OpenNest.Core/AlignType.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum AlignType
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
Horizontally,
|
||||
Vertically,
|
||||
EvenlySpaceHorizontally,
|
||||
EvenlySpaceVertically
|
||||
}
|
||||
}
|
||||
125
Source/OpenNest.Core/Angle.cs
Normal file
125
Source/OpenNest.Core/Angle.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Angle
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of radians equal to 1 degree.
|
||||
/// </summary>
|
||||
public const double RadPerDeg = Math.PI / 180.0;
|
||||
|
||||
/// <summary>
|
||||
/// Number of degrees equal to 1 radian.
|
||||
/// </summary>
|
||||
public const double DegPerRad = 180.0 / Math.PI;
|
||||
|
||||
/// <summary>
|
||||
/// Half of PI.
|
||||
/// </summary>
|
||||
public const double HalfPI = Math.PI * 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// 2 x PI
|
||||
/// </summary>
|
||||
public const double TwoPI = Math.PI * 2.0;
|
||||
|
||||
/// <summary>
|
||||
/// Converts radians to degrees.
|
||||
/// </summary>
|
||||
/// <param name="radians"></param>
|
||||
/// <returns></returns>
|
||||
public static double ToDegrees(double radians)
|
||||
{
|
||||
return DegPerRad * radians;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts degrees to radians.
|
||||
/// </summary>
|
||||
/// <param name="degrees"></param>
|
||||
/// <returns></returns>
|
||||
public static double ToRadians(double degrees)
|
||||
{
|
||||
return RadPerDeg * degrees;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes an angle.
|
||||
/// The normalized angle will be in the range from 0 to TwoPI,
|
||||
/// where TwoPI itself is not included.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <returns></returns>
|
||||
public static double NormalizeRad(double angle)
|
||||
{
|
||||
double r = angle % Angle.TwoPI;
|
||||
return r < 0 ? Angle.TwoPI + r : r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes an angle.
|
||||
/// The normalized angle will be in the range from 0 to 360,
|
||||
/// where 360 itself is not included.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <returns></returns>
|
||||
public static double NormalizeDeg(double angle)
|
||||
{
|
||||
double r = angle % 360.0;
|
||||
return r < 0 ? 360.0 + r : r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given angle is between the two specified angles (a1 & a2).
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="a1"></param>
|
||||
/// <param name="a2"></param>
|
||||
/// <param name="reversed"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBetweenRad(double angle, double a1, double a2, bool reversed = false)
|
||||
{
|
||||
if (reversed)
|
||||
Generic.Swap(ref a1, ref a2);
|
||||
|
||||
var diff = Angle.NormalizeRad(a2 - a1);
|
||||
|
||||
// full circle
|
||||
if (a2.IsEqualTo(a1))
|
||||
return true;
|
||||
|
||||
a1 = Angle.NormalizeRad(angle - a1);
|
||||
a2 = Angle.NormalizeRad(a2 - angle);
|
||||
|
||||
return diff >= a1 - Tolerance.Epsilon ||
|
||||
diff >= a2 - Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given angle is between the two specified angles (a1 & a2).
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="a1"></param>
|
||||
/// <param name="a2"></param>
|
||||
/// <param name="reversed"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsBetweenDeg(double angle, double a1, double a2, bool reversed = false)
|
||||
{
|
||||
if (reversed)
|
||||
Generic.Swap(ref a1, ref a2);
|
||||
|
||||
var diff = Angle.NormalizeRad(a2 - a1);
|
||||
|
||||
// full circle
|
||||
if (a2.IsEqualTo(a1))
|
||||
return true;
|
||||
|
||||
a1 = Angle.NormalizeRad(angle - a1);
|
||||
a2 = Angle.NormalizeRad(a2 - angle);
|
||||
|
||||
return diff >= a1 - Tolerance.Epsilon ||
|
||||
diff >= a2 - Tolerance.Epsilon;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
Source/OpenNest.Core/BoundingBox.cs
Normal file
77
Source/OpenNest.Core/BoundingBox.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class BoundingBox
|
||||
{
|
||||
public static Box GetBoundingBox(this IList<Box> boxes)
|
||||
{
|
||||
if (boxes.Count == 0)
|
||||
return Box.Empty;
|
||||
|
||||
double minX = boxes[0].X;
|
||||
double minY = boxes[0].Y;
|
||||
double maxX = boxes[0].X + boxes[0].Width;
|
||||
double maxY = boxes[0].Y + boxes[0].Height;
|
||||
|
||||
foreach (var box in boxes)
|
||||
{
|
||||
if (box.Left < minX) minX = box.Left;
|
||||
if (box.Right > maxX) maxX = box.Right;
|
||||
if (box.Bottom < minY) minY = box.Bottom;
|
||||
if (box.Top > maxY) maxY = box.Top;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public static Box GetBoundingBox(this IList<Vector> pts)
|
||||
{
|
||||
if (pts.Count == 0)
|
||||
return Box.Empty;
|
||||
|
||||
var first = pts[0];
|
||||
var minX = first.X;
|
||||
var maxX = first.X;
|
||||
var minY = first.Y;
|
||||
var maxY = first.Y;
|
||||
|
||||
for (int i = 1; i < pts.Count; ++i)
|
||||
{
|
||||
var vertex = pts[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public static Box GetBoundingBox(this IEnumerable<IBoundable> items)
|
||||
{
|
||||
var first = items.FirstOrDefault();
|
||||
|
||||
if (first == null)
|
||||
return Box.Empty;
|
||||
|
||||
double left = first.Left;
|
||||
double bottom = first.Bottom;
|
||||
double right = first.Right;
|
||||
double top = first.Top;
|
||||
|
||||
foreach (var box in items)
|
||||
{
|
||||
if (box.Left < left) left = box.Left;
|
||||
if (box.Right > right) right = box.Right;
|
||||
if (box.Bottom < bottom) bottom = box.Bottom;
|
||||
if (box.Top > top) top = box.Top;
|
||||
}
|
||||
|
||||
return new Box(left, bottom, right - left, top - bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
206
Source/OpenNest.Core/Box.cs
Normal file
206
Source/OpenNest.Core/Box.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Box
|
||||
{
|
||||
public static readonly Box Empty = new Box();
|
||||
|
||||
public Box()
|
||||
: this(0, 0, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public Box(double x, double y, double w, double h)
|
||||
{
|
||||
Location = new Vector(x, y);
|
||||
Width = w;
|
||||
Height = h;
|
||||
}
|
||||
|
||||
public Vector Location;
|
||||
|
||||
public Vector Center
|
||||
{
|
||||
get { return new Vector(X + Width * 0.5, Y + Height * 0.5); }
|
||||
}
|
||||
|
||||
public Size Size;
|
||||
|
||||
public double X
|
||||
{
|
||||
get { return Location.X; }
|
||||
set { Location.X = value; }
|
||||
}
|
||||
|
||||
public double Y
|
||||
{
|
||||
get { return Location.Y; }
|
||||
set { Location.Y = value; }
|
||||
}
|
||||
|
||||
public double Width
|
||||
{
|
||||
get { return Size.Width; }
|
||||
set { Size.Width = value; }
|
||||
}
|
||||
|
||||
public double Height
|
||||
{
|
||||
get { return Size.Height; }
|
||||
set { Size.Height = value; }
|
||||
}
|
||||
|
||||
public void MoveTo(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public void MoveTo(Vector pt)
|
||||
{
|
||||
X = pt.X;
|
||||
Y = pt.Y;
|
||||
}
|
||||
|
||||
public void Offset(double x, double y)
|
||||
{
|
||||
X += x;
|
||||
Y += y;
|
||||
}
|
||||
|
||||
public void Offset(Vector voffset)
|
||||
{
|
||||
Location += voffset;
|
||||
}
|
||||
|
||||
public double Left
|
||||
{
|
||||
get { return X; }
|
||||
}
|
||||
|
||||
public double Right
|
||||
{
|
||||
get { return X + Width; }
|
||||
}
|
||||
|
||||
public double Top
|
||||
{
|
||||
get { return Y + Height; }
|
||||
}
|
||||
|
||||
public double Bottom
|
||||
{
|
||||
get { return Y; }
|
||||
}
|
||||
|
||||
public double Area()
|
||||
{
|
||||
return Width * Height;
|
||||
}
|
||||
|
||||
public double Perimeter()
|
||||
{
|
||||
return Width * 2 + Height * 2;
|
||||
}
|
||||
|
||||
public bool Intersects(Box box)
|
||||
{
|
||||
if (Left >= box.Right) return false;
|
||||
if (Right <= box.Left) return false;
|
||||
if (Top <= box.Bottom) return false;
|
||||
if (Bottom >= box.Top) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Intersects(Box box, out Box intersectingBox)
|
||||
{
|
||||
if (!Intersects(box))
|
||||
{
|
||||
intersectingBox = Box.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
var left = Left < box.Left ? box.Left : Left;
|
||||
var right = Right < box.Right ? Right : box.Right;
|
||||
|
||||
var bottom = Bottom < box.Bottom ? box.Bottom : Bottom;
|
||||
var top = Top < box.Top ? Top : box.Top;
|
||||
|
||||
intersectingBox = new Box(left, bottom, right - left, top - bottom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Box box)
|
||||
{
|
||||
if (box.Top > Top) return false;
|
||||
if (box.Left < Left) return false;
|
||||
if (box.Right > Right) return false;
|
||||
if (box.Bottom < Bottom) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Vector pt)
|
||||
{
|
||||
return pt.X >= Left - Tolerance.Epsilon && pt.X <= Right + Tolerance.Epsilon
|
||||
&& pt.Y >= Bottom - Tolerance.Epsilon && pt.Y <= Top + Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
public bool IsHorizontalTo(Box box)
|
||||
{
|
||||
return Bottom > box.Top || Top < box.Bottom;
|
||||
}
|
||||
|
||||
public bool IsHorizontalTo(Box box, out RelativePosition pos)
|
||||
{
|
||||
if (Bottom >= box.Top || Top <= box.Bottom)
|
||||
{
|
||||
pos = RelativePosition.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Left >= box.Right)
|
||||
pos = RelativePosition.Right;
|
||||
else if (Right <= box.Left)
|
||||
pos = RelativePosition.Left;
|
||||
else
|
||||
pos = RelativePosition.Intersecting;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsVerticalTo(Box box)
|
||||
{
|
||||
return Left > box.Right || Right < box.Left;
|
||||
}
|
||||
|
||||
public bool IsVerticalTo(Box box, out RelativePosition pos)
|
||||
{
|
||||
if (Left >= box.Right || Right <= box.Left)
|
||||
{
|
||||
pos = RelativePosition.None;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Bottom >= box.Top)
|
||||
pos = RelativePosition.Top;
|
||||
else if (Top <= box.Bottom)
|
||||
pos = RelativePosition.Bottom;
|
||||
else
|
||||
pos = RelativePosition.Intersecting;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Box Offset(double d)
|
||||
{
|
||||
return new Box(X - d, Y - d, Width + d * 2, Height + d * 2);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[Box: X={0}, Y={1}, Width={2}, Height={3}]", X, Y, Width, Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Source/OpenNest.Core/BoxSplitter.cs
Normal file
57
Source/OpenNest.Core/BoxSplitter.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class BoxSplitter
|
||||
{
|
||||
public static Box SplitTop(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = small.Top;
|
||||
var w = large.Width;
|
||||
var h = large.Top - y;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitLeft(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = large.Bottom;
|
||||
var w = small.Left - x;
|
||||
var h = large.Height;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitBottom(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = large.Left;
|
||||
var y = large.Bottom;
|
||||
var w = large.Width;
|
||||
var h = small.Top - y;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
|
||||
public static Box SplitRight(Box large, Box small)
|
||||
{
|
||||
if (!large.Intersects(small))
|
||||
return Box.Empty;
|
||||
|
||||
var x = small.Right;
|
||||
var y = large.Bottom;
|
||||
var w = large.Right - x;
|
||||
var h = large.Height;
|
||||
|
||||
return new Box(x, y, w, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Source/OpenNest.Core/CNC/CircularMove.cs
Normal file
89
Source/OpenNest.Core/CNC/CircularMove.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class CircularMove : Motion
|
||||
{
|
||||
public CircularMove()
|
||||
{
|
||||
}
|
||||
|
||||
public CircularMove(double x, double y, double i, double j, RotationType rotation = RotationType.CCW)
|
||||
: this(new Vector(x, y), new Vector(i, j), rotation)
|
||||
{
|
||||
}
|
||||
|
||||
public CircularMove(Vector endPoint, Vector centerPoint, RotationType rotation = RotationType.CCW)
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
CenterPoint = centerPoint;
|
||||
Rotation = rotation;
|
||||
Layer = LayerType.Cut;
|
||||
}
|
||||
|
||||
public LayerType Layer { get; set; }
|
||||
|
||||
public RotationType Rotation { get; set; }
|
||||
|
||||
public Vector CenterPoint { get; set; }
|
||||
|
||||
public double Radius
|
||||
{
|
||||
get { return CenterPoint.DistanceTo(EndPoint); }
|
||||
}
|
||||
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
base.Rotate(angle);
|
||||
CenterPoint = CenterPoint.Rotate(angle);
|
||||
}
|
||||
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
base.Rotate(angle, origin);
|
||||
CenterPoint = CenterPoint.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
base.Offset(x, y);
|
||||
CenterPoint = new Vector(CenterPoint.X + x, CenterPoint.Y + y);
|
||||
}
|
||||
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
base.Offset(voffset);
|
||||
CenterPoint += voffset;
|
||||
}
|
||||
|
||||
public override CodeType Type
|
||||
{
|
||||
get { return CodeType.CircularMove; }
|
||||
}
|
||||
|
||||
public override ICode Clone()
|
||||
{
|
||||
return new CircularMove(EndPoint, CenterPoint, Rotation)
|
||||
{
|
||||
Layer = Layer
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(DefaultDecimalPlaces);
|
||||
}
|
||||
|
||||
public override string ToString(int decimalPlaces)
|
||||
{
|
||||
var dp = "N" + decimalPlaces;
|
||||
var x = EndPoint.X.ToString(dp);
|
||||
var y = EndPoint.Y.ToString(dp);
|
||||
var i = CenterPoint.X.ToString(dp);
|
||||
var j = CenterPoint.Y.ToString(dp);
|
||||
|
||||
return Rotation == RotationType.CW ?
|
||||
string.Format("G02 X{0} Y{1} I{2} J{3}", x, y, i, j) :
|
||||
string.Format("G03 X{0} Y{1} I{2} J{3}", x, y, i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Source/OpenNest.Core/CNC/CodeType.cs
Normal file
14
Source/OpenNest.Core/CNC/CodeType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum CodeType
|
||||
{
|
||||
CircularMove,
|
||||
Comment,
|
||||
LinearMove,
|
||||
RapidMove,
|
||||
SetFeedrate,
|
||||
SetKerf,
|
||||
SubProgramCall
|
||||
}
|
||||
}
|
||||
31
Source/OpenNest.Core/CNC/Comment.cs
Normal file
31
Source/OpenNest.Core/CNC/Comment.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class Comment : ICode
|
||||
{
|
||||
public Comment()
|
||||
{
|
||||
}
|
||||
|
||||
public Comment(string value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public CodeType Type
|
||||
{
|
||||
get { return CodeType.Comment; }
|
||||
}
|
||||
|
||||
public ICode Clone()
|
||||
{
|
||||
return new Comment(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ':' + Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Source/OpenNest.Core/CNC/Feedrate.cs
Normal file
35
Source/OpenNest.Core/CNC/Feedrate.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class Feedrate : ICode
|
||||
{
|
||||
public const int UseDefault = -1;
|
||||
|
||||
public const int UseMax = -2;
|
||||
|
||||
public Feedrate()
|
||||
{
|
||||
}
|
||||
|
||||
public Feedrate(double value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public double Value { get; set; }
|
||||
|
||||
public CodeType Type
|
||||
{
|
||||
get { return CodeType.SetFeedrate; }
|
||||
}
|
||||
|
||||
public ICode Clone()
|
||||
{
|
||||
return new Feedrate(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("F{0}", Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Source/OpenNest.Core/CNC/ICode.cs
Normal file
9
Source/OpenNest.Core/CNC/ICode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public interface ICode
|
||||
{
|
||||
CodeType Type { get; }
|
||||
|
||||
ICode Clone();
|
||||
}
|
||||
}
|
||||
39
Source/OpenNest.Core/CNC/Kerf.cs
Normal file
39
Source/OpenNest.Core/CNC/Kerf.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class Kerf : ICode
|
||||
{
|
||||
public Kerf(KerfType kerf = KerfType.Left)
|
||||
{
|
||||
Value = kerf;
|
||||
}
|
||||
|
||||
public KerfType Value { get; set; }
|
||||
|
||||
public CodeType Type
|
||||
{
|
||||
get { return CodeType.SetKerf; }
|
||||
}
|
||||
|
||||
public ICode Clone()
|
||||
{
|
||||
return new Kerf(Value);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
switch (Value)
|
||||
{
|
||||
case KerfType.Left:
|
||||
return "G41";
|
||||
|
||||
case KerfType.Right:
|
||||
return "G42";
|
||||
|
||||
case KerfType.None:
|
||||
return "G40";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Source/OpenNest.Core/CNC/KerfType.cs
Normal file
10
Source/OpenNest.Core/CNC/KerfType.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum KerfType
|
||||
{
|
||||
None,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
12
Source/OpenNest.Core/CNC/LayerType.cs
Normal file
12
Source/OpenNest.Core/CNC/LayerType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum LayerType
|
||||
{
|
||||
Display,
|
||||
Scribe,
|
||||
Cut,
|
||||
Leadin,
|
||||
Leadout
|
||||
}
|
||||
}
|
||||
47
Source/OpenNest.Core/CNC/LinearMove.cs
Normal file
47
Source/OpenNest.Core/CNC/LinearMove.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class LinearMove : Motion
|
||||
{
|
||||
public LinearMove()
|
||||
: this(new Vector())
|
||||
{
|
||||
}
|
||||
|
||||
public LinearMove(double x, double y)
|
||||
: this(new Vector(x, y))
|
||||
{
|
||||
}
|
||||
|
||||
public LinearMove(Vector endPoint)
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
Layer = LayerType.Cut;
|
||||
}
|
||||
|
||||
public LayerType Layer { get; set; }
|
||||
|
||||
public override CodeType Type
|
||||
{
|
||||
get { return CodeType.LinearMove; }
|
||||
}
|
||||
|
||||
public override ICode Clone()
|
||||
{
|
||||
return new LinearMove(EndPoint)
|
||||
{
|
||||
Layer = Layer
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(DefaultDecimalPlaces);
|
||||
}
|
||||
|
||||
public override string ToString(int decimalPlaces)
|
||||
{
|
||||
var dp = "N" + decimalPlaces;
|
||||
return string.Format("G01 X{0} Y{1}", EndPoint.X.ToString(dp), EndPoint.Y.ToString(dp));
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Source/OpenNest.Core/CNC/Mode.cs
Normal file
9
Source/OpenNest.Core/CNC/Mode.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum Mode
|
||||
{
|
||||
Absolute,
|
||||
Incremental
|
||||
}
|
||||
}
|
||||
45
Source/OpenNest.Core/CNC/Motion.cs
Normal file
45
Source/OpenNest.Core/CNC/Motion.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public abstract class Motion : ICode
|
||||
{
|
||||
protected const int DefaultDecimalPlaces = 4;
|
||||
|
||||
public Vector EndPoint { get; set; }
|
||||
|
||||
public bool UseExactStop { get; set; }
|
||||
|
||||
public int Feedrate { get; set; }
|
||||
|
||||
protected Motion()
|
||||
{
|
||||
Feedrate = CNC.Feedrate.UseDefault;
|
||||
}
|
||||
|
||||
public virtual void Rotate(double angle)
|
||||
{
|
||||
EndPoint = EndPoint.Rotate(angle);
|
||||
}
|
||||
|
||||
public virtual void Rotate(double angle, Vector origin)
|
||||
{
|
||||
EndPoint = EndPoint.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
public virtual void Offset(double x, double y)
|
||||
{
|
||||
EndPoint = new Vector(EndPoint.X + x, EndPoint.Y + y);
|
||||
}
|
||||
|
||||
public virtual void Offset(Vector voffset)
|
||||
{
|
||||
EndPoint += voffset;
|
||||
}
|
||||
|
||||
public abstract CodeType Type { get; }
|
||||
|
||||
public abstract ICode Clone();
|
||||
|
||||
public abstract string ToString(int decimalPlaces);
|
||||
}
|
||||
}
|
||||
477
Source/OpenNest.Core/CNC/Program.cs
Normal file
477
Source/OpenNest.Core/CNC/Program.cs
Normal file
@@ -0,0 +1,477 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public List<ICode> Codes;
|
||||
|
||||
private Mode mode;
|
||||
|
||||
public Program(Mode mode = Mode.Absolute)
|
||||
{
|
||||
Codes = new List<ICode>();
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public Mode Mode
|
||||
{
|
||||
get { return mode; }
|
||||
set
|
||||
{
|
||||
if (value == Mode.Absolute)
|
||||
SetModeAbs();
|
||||
else
|
||||
SetModeInc();
|
||||
}
|
||||
}
|
||||
|
||||
public double Rotation { get; protected set; }
|
||||
|
||||
private void SetModeInc()
|
||||
{
|
||||
if (mode == Mode.Incremental)
|
||||
return;
|
||||
|
||||
ConvertMode.ToIncremental(this);
|
||||
|
||||
mode = Mode.Incremental;
|
||||
}
|
||||
|
||||
private void SetModeAbs()
|
||||
{
|
||||
if (mode == Mode.Absolute)
|
||||
return;
|
||||
|
||||
ConvertMode.ToAbsolute(this);
|
||||
|
||||
mode = Mode.Absolute;
|
||||
}
|
||||
|
||||
public virtual void Rotate(double angle)
|
||||
{
|
||||
var mode = Mode;
|
||||
|
||||
SetModeAbs();
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
|
||||
if (subpgm.Program != null)
|
||||
subpgm.Program.Rotate(angle);
|
||||
}
|
||||
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var code2 = (Motion)code;
|
||||
|
||||
code2.Rotate(angle);
|
||||
}
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
SetModeInc();
|
||||
|
||||
Rotation = Angle.NormalizeRad(Rotation + angle);
|
||||
}
|
||||
|
||||
public virtual void Rotate(double angle, Vector origin)
|
||||
{
|
||||
var mode = Mode;
|
||||
|
||||
SetModeAbs();
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
if (code.Type == CodeType.SubProgramCall)
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
|
||||
if (subpgm.Program != null)
|
||||
subpgm.Program.Rotate(angle);
|
||||
}
|
||||
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var code2 = (Motion)code;
|
||||
|
||||
code2.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
SetModeInc();
|
||||
|
||||
Rotation = Angle.NormalizeRad(Rotation + angle);
|
||||
}
|
||||
|
||||
public virtual void Offset(double x, double y)
|
||||
{
|
||||
var mode = Mode;
|
||||
|
||||
SetModeAbs();
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var code2 = (Motion)code;
|
||||
|
||||
code2.Offset(x, y);
|
||||
}
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
SetModeInc();
|
||||
}
|
||||
|
||||
public virtual void Offset(Vector voffset)
|
||||
{
|
||||
var mode = Mode;
|
||||
|
||||
SetModeAbs();
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var code2 = (Motion)code;
|
||||
|
||||
code2.Offset(voffset);
|
||||
}
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
SetModeInc();
|
||||
}
|
||||
|
||||
public void LineTo(double x, double y)
|
||||
{
|
||||
Codes.Add(new LinearMove(x, y));
|
||||
}
|
||||
|
||||
public void LineTo(Vector pt)
|
||||
{
|
||||
Codes.Add(new LinearMove(pt));
|
||||
}
|
||||
|
||||
public void MoveTo(double x, double y)
|
||||
{
|
||||
Codes.Add(new RapidMove(x, y));
|
||||
}
|
||||
|
||||
public void MoveTo(Vector pt)
|
||||
{
|
||||
Codes.Add(new RapidMove(pt));
|
||||
}
|
||||
|
||||
public void ArcTo(double x, double y, double i, double j, RotationType rotation)
|
||||
{
|
||||
Codes.Add(new CircularMove(x, y, i, j, rotation));
|
||||
}
|
||||
|
||||
public void ArcTo(Vector endpt, Vector center, RotationType rotation)
|
||||
{
|
||||
Codes.Add(new CircularMove(endpt, center, rotation));
|
||||
}
|
||||
|
||||
public void AddSubProgram(Program program)
|
||||
{
|
||||
Codes.Add(new SubProgramCall(program, program.Rotation));
|
||||
}
|
||||
|
||||
public ICode this[int index]
|
||||
{
|
||||
get { return Codes[index]; }
|
||||
set { Codes[index] = value; }
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return Codes.Count; }
|
||||
}
|
||||
|
||||
public void Merge(Program pgm)
|
||||
{
|
||||
// Set the program to be merged to the same Mode as the current.
|
||||
pgm.Mode = this.Mode;
|
||||
|
||||
if (Mode == Mode.Absolute)
|
||||
{
|
||||
bool isRapid = false;
|
||||
|
||||
// Check if the first motion code is a rapid move
|
||||
foreach (var code in pgm.Codes)
|
||||
{
|
||||
if (code is Motion == false)
|
||||
continue;
|
||||
|
||||
var motion = (Motion)code;
|
||||
|
||||
isRapid = motion.GetType() == typeof(RapidMove);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the first motion code is not a rapid, move to the origin.
|
||||
if (!isRapid)
|
||||
MoveTo(0, 0);
|
||||
|
||||
Codes.AddRange(pgm.Codes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Codes.AddRange(pgm.Codes);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector EndPoint()
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case Mode.Absolute:
|
||||
{
|
||||
for (int i = Codes.Count; i >= 0; --i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion == null) continue;
|
||||
|
||||
return motion.EndPoint;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode.Incremental:
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion == null) continue;
|
||||
|
||||
pos += motion.EndPoint;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector(0, 0);
|
||||
}
|
||||
|
||||
public Box BoundingBox()
|
||||
{
|
||||
var origin = new Vector(0, 0);
|
||||
return BoundingBox(ref origin);
|
||||
}
|
||||
|
||||
private Box BoundingBox(ref Vector pos)
|
||||
{
|
||||
double minX = 0.0;
|
||||
double minY = 0.0;
|
||||
double maxX = 0.0;
|
||||
double maxY = 0.0;
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
|
||||
switch (code.Type)
|
||||
{
|
||||
case CodeType.LinearMove:
|
||||
{
|
||||
var line = (LinearMove)code;
|
||||
var pt = Mode == Mode.Absolute ?
|
||||
line.EndPoint :
|
||||
line.EndPoint + pos;
|
||||
|
||||
if (pt.X > maxX)
|
||||
maxX = pt.X;
|
||||
else if (pt.X < minX)
|
||||
minX = pt.X;
|
||||
|
||||
if (pt.Y > maxY)
|
||||
maxY = pt.Y;
|
||||
else if (pt.Y < minY)
|
||||
minY = pt.Y;
|
||||
|
||||
pos = pt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.RapidMove:
|
||||
{
|
||||
var line = (RapidMove)code;
|
||||
var pt = Mode == Mode.Absolute
|
||||
? line.EndPoint
|
||||
: line.EndPoint + pos;
|
||||
|
||||
if (pt.X > maxX)
|
||||
maxX = pt.X;
|
||||
else if (pt.X < minX)
|
||||
minX = pt.X;
|
||||
|
||||
if (pt.Y > maxY)
|
||||
maxY = pt.Y;
|
||||
else if (pt.Y < minY)
|
||||
minY = pt.Y;
|
||||
|
||||
pos = pt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.CircularMove:
|
||||
{
|
||||
var arc = (CircularMove)code;
|
||||
var radius = arc.CenterPoint.DistanceTo(arc.EndPoint);
|
||||
|
||||
Vector endpt;
|
||||
Vector centerpt;
|
||||
|
||||
if (Mode == Mode.Incremental)
|
||||
{
|
||||
endpt = arc.EndPoint + pos;
|
||||
centerpt = arc.CenterPoint + pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
endpt = arc.EndPoint;
|
||||
centerpt = arc.CenterPoint;
|
||||
}
|
||||
|
||||
double minX1;
|
||||
double minY1;
|
||||
double maxX1;
|
||||
double maxY1;
|
||||
|
||||
if (pos.X < endpt.X)
|
||||
{
|
||||
minX1 = pos.X;
|
||||
maxX1 = endpt.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
minX1 = endpt.X;
|
||||
maxX1 = pos.X;
|
||||
}
|
||||
|
||||
if (pos.Y < endpt.Y)
|
||||
{
|
||||
minY1 = pos.Y;
|
||||
maxY1 = endpt.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
minY1 = endpt.Y;
|
||||
maxY1 = pos.Y;
|
||||
}
|
||||
|
||||
var startAngle = pos.AngleFrom(centerpt);
|
||||
var endAngle = endpt.AngleFrom(centerpt);
|
||||
|
||||
// switch the angle to counter clockwise.
|
||||
if (arc.Rotation == RotationType.CW)
|
||||
Generic.Swap(ref startAngle, ref endAngle);
|
||||
|
||||
startAngle = Angle.NormalizeRad(startAngle);
|
||||
endAngle = Angle.NormalizeRad(endAngle);
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.HalfPI, startAngle, endAngle))
|
||||
maxY1 = centerpt.Y + radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Math.PI, startAngle, endAngle))
|
||||
minX1 = centerpt.X - radius;
|
||||
|
||||
const double oneHalfPI = Math.PI * 1.5;
|
||||
|
||||
if (Angle.IsBetweenRad(oneHalfPI, startAngle, endAngle))
|
||||
minY1 = centerpt.Y - radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.TwoPI, startAngle, endAngle))
|
||||
maxX1 = centerpt.X + radius;
|
||||
|
||||
if (maxX1 > maxX)
|
||||
maxX = maxX1;
|
||||
|
||||
if (minX1 < minX)
|
||||
minX = minX1;
|
||||
|
||||
if (maxY1 > maxY)
|
||||
maxY = maxY1;
|
||||
|
||||
if (minY1 < minY)
|
||||
minY = minY1;
|
||||
|
||||
pos = endpt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.SubProgramCall:
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
var box = subpgm.Program.BoundingBox(ref pos);
|
||||
|
||||
if (box.Left < minX)
|
||||
minX = box.Left;
|
||||
|
||||
if (box.Right > maxX)
|
||||
maxX = box.Right;
|
||||
|
||||
if (box.Bottom < minY)
|
||||
minY = box.Bottom;
|
||||
|
||||
if (box.Top > maxY)
|
||||
maxY = box.Top;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var pgm = new Program()
|
||||
{
|
||||
mode = this.mode,
|
||||
Rotation = this.Rotation
|
||||
};
|
||||
|
||||
var codes = new ICode[Length];
|
||||
|
||||
for (int i = 0; i < Length; ++i)
|
||||
codes[i] = this.Codes[i].Clone();
|
||||
|
||||
pgm.Codes.AddRange(codes);
|
||||
|
||||
return pgm;
|
||||
}
|
||||
|
||||
public List<Entity> ToGeometry()
|
||||
{
|
||||
return ConvertProgram.ToGeometry(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Source/OpenNest.Core/CNC/RapidMove.cs
Normal file
42
Source/OpenNest.Core/CNC/RapidMove.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class RapidMove : Motion
|
||||
{
|
||||
public RapidMove()
|
||||
{
|
||||
Feedrate = CNC.Feedrate.UseMax;
|
||||
}
|
||||
|
||||
public RapidMove(Vector endPoint)
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
}
|
||||
|
||||
public RapidMove(double x, double y)
|
||||
{
|
||||
EndPoint = new Vector(x, y);
|
||||
}
|
||||
|
||||
public override CodeType Type
|
||||
{
|
||||
get { return CodeType.RapidMove; }
|
||||
}
|
||||
|
||||
public override ICode Clone()
|
||||
{
|
||||
return new RapidMove(EndPoint);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(DefaultDecimalPlaces);
|
||||
}
|
||||
|
||||
public override string ToString(int decimalPlaces)
|
||||
{
|
||||
var dp = "N" + decimalPlaces;
|
||||
return string.Format("G00 X{0} Y{1}", EndPoint.X.ToString(dp), EndPoint.Y.ToString(dp));
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Source/OpenNest.Core/CNC/SubProgramCall.cs
Normal file
87
Source/OpenNest.Core/CNC/SubProgramCall.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public class SubProgramCall : ICode
|
||||
{
|
||||
private double rotation;
|
||||
private Program program;
|
||||
|
||||
public SubProgramCall()
|
||||
{
|
||||
}
|
||||
|
||||
public SubProgramCall(Program program, double rotation)
|
||||
{
|
||||
this.program = program;
|
||||
this.Rotation = rotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The program ID.
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the program.
|
||||
/// </summary>
|
||||
public Program Program
|
||||
{
|
||||
get { return program; }
|
||||
set
|
||||
{
|
||||
program = value;
|
||||
UpdateProgramRotation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rotation of the program in degrees.
|
||||
/// </summary>
|
||||
public double Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
set
|
||||
{
|
||||
rotation = value;
|
||||
UpdateProgramRotation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the program by the difference of the current
|
||||
/// rotation set in the sub program call and the program.
|
||||
/// </summary>
|
||||
private void UpdateProgramRotation()
|
||||
{
|
||||
if (program != null)
|
||||
{
|
||||
var diffAngle = Angle.ToRadians(rotation) - program.Rotation;
|
||||
|
||||
if (!diffAngle.IsEqualTo(0.0))
|
||||
program.Rotate(diffAngle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the code type.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public CodeType Type
|
||||
{
|
||||
get { return CodeType.SubProgramCall; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a shallow copy.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ICode Clone()
|
||||
{
|
||||
return new SubProgramCall(program, Rotation);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("G65 P{0} R{1}", Id, Rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Source/OpenNest.Core/Collections/DrawingCollection.cs
Normal file
8
Source/OpenNest.Core/Collections/DrawingCollection.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Collections
|
||||
{
|
||||
public class DrawingCollection : HashSet<Drawing>
|
||||
{
|
||||
}
|
||||
}
|
||||
177
Source/OpenNest.Core/Collections/PartCollection.cs
Normal file
177
Source/OpenNest.Core/Collections/PartCollection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
187
Source/OpenNest.Core/Collections/PlateCollection.cs
Normal file
187
Source/OpenNest.Core/Collections/PlateCollection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
Source/OpenNest.Core/ConvertGeometry.cs
Normal file
117
Source/OpenNest.Core/ConvertGeometry.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class ConvertGeometry
|
||||
{
|
||||
public static Program ToProgram(IList<Entity> geometry)
|
||||
{
|
||||
var shapes = Helper.GetShapes(geometry);
|
||||
|
||||
if (shapes.Count == 0)
|
||||
return null;
|
||||
|
||||
var perimeter = shapes[0];
|
||||
var area = perimeter.BoundingBox.Area();
|
||||
var index = 0;
|
||||
|
||||
for (int i = 1; i < shapes.Count; ++i)
|
||||
{
|
||||
var program = shapes[i];
|
||||
var area2 = program.BoundingBox.Area();
|
||||
if (area2 > area)
|
||||
{
|
||||
perimeter = program;
|
||||
area = area2;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
shapes.RemoveAt(index);
|
||||
|
||||
var pgm = new Program();
|
||||
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
var subpgm = ToProgram(shape);
|
||||
pgm.Merge(subpgm);
|
||||
}
|
||||
|
||||
pgm.Merge(ToProgram(perimeter));
|
||||
pgm.Mode = Mode.Incremental;
|
||||
|
||||
return pgm;
|
||||
}
|
||||
|
||||
public static Program ToProgram(Shape shape)
|
||||
{
|
||||
var pgm = new Program();
|
||||
var lastpt = new Vector();
|
||||
|
||||
for (int i = 0; i < shape.Entities.Count; i++)
|
||||
lastpt = AddEntity(pgm, lastpt, shape.Entities[i]);
|
||||
|
||||
return pgm;
|
||||
}
|
||||
|
||||
private static Vector AddEntity(Program pgm, Vector lastpt, Entity geo)
|
||||
{
|
||||
switch (geo.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
lastpt = AddArc(pgm, lastpt, (Arc)geo);
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
lastpt = AddCircle(pgm, lastpt, (Circle)geo);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
lastpt = AddLine(pgm, lastpt, (Line)geo);
|
||||
break;
|
||||
}
|
||||
|
||||
return lastpt;
|
||||
}
|
||||
|
||||
private static Vector AddArc(Program pgm, Vector lastpt, Arc arc)
|
||||
{
|
||||
var startpt = arc.StartPoint();
|
||||
var endpt = arc.EndPoint();
|
||||
|
||||
if (startpt != lastpt)
|
||||
pgm.MoveTo(startpt);
|
||||
|
||||
lastpt = endpt;
|
||||
|
||||
pgm.ArcTo(endpt, arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW);
|
||||
return lastpt;
|
||||
}
|
||||
|
||||
private static Vector AddCircle(Program pgm, Vector lastpt, Circle circle)
|
||||
{
|
||||
var startpt = new Vector(circle.Center.X + circle.Radius, circle.Center.Y);
|
||||
|
||||
if (startpt != lastpt)
|
||||
pgm.MoveTo(startpt);
|
||||
|
||||
pgm.ArcTo(startpt, circle.Center, RotationType.CCW);
|
||||
|
||||
lastpt = startpt;
|
||||
return lastpt;
|
||||
}
|
||||
|
||||
private static Vector AddLine(Program pgm, Vector lastpt, Line line)
|
||||
{
|
||||
if (line.StartPoint != lastpt)
|
||||
pgm.MoveTo(line.StartPoint);
|
||||
|
||||
pgm.LineTo(line.EndPoint);
|
||||
|
||||
lastpt = line.EndPoint;
|
||||
return lastpt;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Source/OpenNest.Core/ConvertMode.cs
Normal file
52
Source/OpenNest.Core/ConvertMode.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using OpenNest.CNC;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class ConvertMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the program to absolute coordinates.
|
||||
/// Does NOT check program mode before converting.
|
||||
/// </summary>
|
||||
/// <param name="pgm"></param>
|
||||
public static void ToAbsolute(Program pgm)
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (int i = 0; i < pgm.Codes.Count; ++i)
|
||||
{
|
||||
var code = pgm.Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion != null)
|
||||
{
|
||||
motion.Offset(pos);
|
||||
pos = motion.EndPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the program to intermental coordinates.
|
||||
/// Does NOT check program mode before converting.
|
||||
/// </summary>
|
||||
/// <param name="pgm"></param>
|
||||
public static void ToIncremental(Program pgm)
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (int i = 0; i < pgm.Codes.Count; ++i)
|
||||
{
|
||||
var code = pgm.Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion != null)
|
||||
{
|
||||
var pos2 = motion.EndPoint;
|
||||
motion.Offset(-pos.X, -pos.Y);
|
||||
pos = pos2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Source/OpenNest.Core/ConvertProgram.cs
Normal file
137
Source/OpenNest.Core/ConvertProgram.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class ConvertProgram
|
||||
{
|
||||
public static List<Entity> ToGeometry(Program pgm)
|
||||
{
|
||||
var geometry = new List<Entity>();
|
||||
var curpos = new Vector();
|
||||
var mode = Mode.Absolute;
|
||||
|
||||
AddProgram(pgm, ref mode, ref curpos, ref geometry);
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
private static void AddProgram(Program program, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
{
|
||||
mode = program.Mode;
|
||||
|
||||
for (int i = 0; i < program.Length; ++i)
|
||||
{
|
||||
var code = program[i];
|
||||
|
||||
switch (code.Type)
|
||||
{
|
||||
case CodeType.CircularMove:
|
||||
AddCircularMove((CircularMove)code, ref mode, ref curpos, ref geometry);
|
||||
break;
|
||||
|
||||
case CodeType.LinearMove:
|
||||
AddLinearMove((LinearMove)code, ref mode, ref curpos, ref geometry);
|
||||
break;
|
||||
|
||||
case CodeType.RapidMove:
|
||||
AddRapidMove((RapidMove)code, ref mode, ref curpos, ref geometry);
|
||||
break;
|
||||
|
||||
case CodeType.SubProgramCall:
|
||||
var tmpmode = mode;
|
||||
var subpgm = (SubProgramCall)code;
|
||||
var geoProgram = new Shape();
|
||||
AddProgram(subpgm.Program, ref mode, ref curpos, ref geoProgram.Entities);
|
||||
geometry.Add(geoProgram);
|
||||
mode = tmpmode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddLinearMove(LinearMove linearMove, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
{
|
||||
var pt = linearMove.EndPoint;
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
pt += curpos;
|
||||
|
||||
var line = new Line(curpos, pt)
|
||||
{
|
||||
Layer = ConvertLayer(linearMove.Layer)
|
||||
};
|
||||
geometry.Add(line);
|
||||
curpos = pt;
|
||||
}
|
||||
|
||||
private static void AddRapidMove(RapidMove rapidMove, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
{
|
||||
var pt = rapidMove.EndPoint;
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
pt += curpos;
|
||||
|
||||
var line = new Line(curpos, pt)
|
||||
{
|
||||
Layer = SpecialLayers.Rapid
|
||||
};
|
||||
geometry.Add(line);
|
||||
curpos = pt;
|
||||
}
|
||||
|
||||
private static void AddCircularMove(CircularMove circularMove, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
{
|
||||
var center = circularMove.CenterPoint;
|
||||
var endpt = circularMove.EndPoint;
|
||||
|
||||
if (mode == Mode.Incremental)
|
||||
{
|
||||
endpt += curpos;
|
||||
center += curpos;
|
||||
}
|
||||
|
||||
var startAngle = center.AngleTo(curpos);
|
||||
var endAngle = center.AngleTo(endpt);
|
||||
|
||||
var dx = endpt.X - center.X;
|
||||
var dy = endpt.Y - center.Y;
|
||||
|
||||
var radius = Math.Sqrt(dx * dx + dy * dy);
|
||||
var layer = ConvertLayer(circularMove.Layer);
|
||||
|
||||
if (startAngle.IsEqualTo(endAngle))
|
||||
geometry.Add(new Circle(center, radius) { Layer = layer });
|
||||
else
|
||||
geometry.Add(new Arc(center, radius, startAngle, endAngle, circularMove.Rotation == RotationType.CW) { Layer = layer });
|
||||
|
||||
curpos = endpt;
|
||||
}
|
||||
|
||||
private static Layer ConvertLayer(LayerType layer)
|
||||
{
|
||||
switch (layer)
|
||||
{
|
||||
case LayerType.Cut:
|
||||
return SpecialLayers.Cut;
|
||||
|
||||
case LayerType.Display:
|
||||
return SpecialLayers.Display;
|
||||
|
||||
case LayerType.Leadin:
|
||||
return SpecialLayers.Leadin;
|
||||
|
||||
case LayerType.Leadout:
|
||||
return SpecialLayers.Leadout;
|
||||
|
||||
case LayerType.Scribe:
|
||||
return SpecialLayers.Scribe;
|
||||
|
||||
default:
|
||||
return new Layer(layer.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Source/OpenNest.Core/CutParameters.cs
Normal file
15
Source/OpenNest.Core/CutParameters.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class CutParameters
|
||||
{
|
||||
public double Feedrate { get; set; }
|
||||
|
||||
public double RapidTravelRate { get; set; }
|
||||
|
||||
public TimeSpan PierceTime { get; set; }
|
||||
|
||||
public Units Units { get; set; }
|
||||
}
|
||||
}
|
||||
117
Source/OpenNest.Core/Drawing.cs
Normal file
117
Source/OpenNest.Core/Drawing.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Drawing
|
||||
{
|
||||
private Program program;
|
||||
|
||||
public Drawing()
|
||||
: this(string.Empty, new Program())
|
||||
{
|
||||
}
|
||||
|
||||
public Drawing(string name)
|
||||
: this(name, new Program())
|
||||
{
|
||||
}
|
||||
|
||||
public Drawing(string name, Program pgm)
|
||||
{
|
||||
Name = name;
|
||||
Material = new Material();
|
||||
Program = pgm;
|
||||
Constraints = new NestConstraints();
|
||||
Source = new SourceInfo();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Customer { get; set; }
|
||||
|
||||
public int Priority { get; set; }
|
||||
|
||||
public Quantity Quantity;
|
||||
|
||||
public Material Material { get; set; }
|
||||
|
||||
public Program Program
|
||||
{
|
||||
get { return program; }
|
||||
set
|
||||
{
|
||||
program = value;
|
||||
UpdateArea();
|
||||
}
|
||||
}
|
||||
|
||||
public Color Color { get; set; }
|
||||
|
||||
public NestConstraints Constraints { get; set; }
|
||||
|
||||
public SourceInfo Source { get; set; }
|
||||
|
||||
public double Area { get; protected set; }
|
||||
|
||||
public void UpdateArea()
|
||||
{
|
||||
var geometry = ConvertProgram.ToGeometry(Program).Where(entity => entity.Layer != SpecialLayers.Rapid);
|
||||
var shapes = Helper.GetShapes(geometry);
|
||||
|
||||
if (shapes.Count == 0)
|
||||
return;
|
||||
|
||||
var areas = new double[shapes.Count];
|
||||
|
||||
for (int i = 0; i < shapes.Count; i++)
|
||||
{
|
||||
var shape = shapes[i];
|
||||
areas[i] = shape.Area();
|
||||
}
|
||||
|
||||
int largestAreaIndex = 0;
|
||||
|
||||
for (int i = 1; i < areas.Length; i++)
|
||||
{
|
||||
var area = areas[i];
|
||||
|
||||
if (area > areas[largestAreaIndex])
|
||||
largestAreaIndex = i;
|
||||
}
|
||||
|
||||
var outerArea = areas[largestAreaIndex];
|
||||
|
||||
Area = outerArea - (areas.Sum() - outerArea);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is Drawing == false)
|
||||
return false;
|
||||
|
||||
var dwg = (Drawing)obj;
|
||||
|
||||
return Name == dwg.Name;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public class SourceInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the source file.
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset distances to the original location.
|
||||
/// </summary>
|
||||
public Vector Offset { get; set; }
|
||||
}
|
||||
}
|
||||
15
Source/OpenNest.Core/EvenOdd.cs
Normal file
15
Source/OpenNest.Core/EvenOdd.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class EvenOdd
|
||||
{
|
||||
public static bool IsEven(this int i)
|
||||
{
|
||||
return (i % 2) == 0;
|
||||
}
|
||||
|
||||
public static bool IsOdd(this int i)
|
||||
{
|
||||
return (i % 2) == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Source/OpenNest.Core/Generic.cs
Normal file
12
Source/OpenNest.Core/Generic.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Generic
|
||||
{
|
||||
public static void Swap<T>(ref T a, ref T b)
|
||||
{
|
||||
T c = a;
|
||||
a = b;
|
||||
b = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
538
Source/OpenNest.Core/Geometry/Arc.cs
Normal file
538
Source/OpenNest.Core/Geometry/Arc.cs
Normal file
@@ -0,0 +1,538 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Arc : Entity
|
||||
{
|
||||
private double radius;
|
||||
private double startAngle;
|
||||
private double endAngle;
|
||||
private Vector center;
|
||||
private bool reversed;
|
||||
|
||||
public Arc()
|
||||
{
|
||||
}
|
||||
|
||||
public Arc(double x, double y, double r, double a1, double a2, bool reversed = false)
|
||||
: this(new Vector(x, y), r, a1, a2, reversed)
|
||||
{
|
||||
}
|
||||
|
||||
public Arc(Vector center, double radius, double startAngle, double endAngle, bool reversed = false)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
this.startAngle = startAngle;
|
||||
this.endAngle = endAngle;
|
||||
this.reversed = reversed;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Center point.
|
||||
/// </summary>
|
||||
public Vector Center
|
||||
{
|
||||
get { return center; }
|
||||
set
|
||||
{
|
||||
var offset = value - center;
|
||||
boundingBox.Offset(offset);
|
||||
center = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arc radius.
|
||||
/// </summary>
|
||||
public double Radius
|
||||
{
|
||||
get { return radius; }
|
||||
set
|
||||
{
|
||||
radius = value;
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Arc radius * 2. Value NOT stored.
|
||||
/// </summary>
|
||||
public double Diameter
|
||||
{
|
||||
get { return Radius * 2.0; }
|
||||
set { Radius = value / 2.0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start angle in radians.
|
||||
/// </summary>
|
||||
public double StartAngle
|
||||
{
|
||||
get { return startAngle; }
|
||||
set
|
||||
{
|
||||
startAngle = Angle.NormalizeRad(value);
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End angle in radians.
|
||||
/// </summary>
|
||||
public double EndAngle
|
||||
{
|
||||
get { return endAngle; }
|
||||
set
|
||||
{
|
||||
endAngle = Angle.NormalizeRad(value);
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Angle in radians between start and end angles.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double SweepAngle()
|
||||
{
|
||||
var startAngle = StartAngle;
|
||||
var endAngle = EndAngle;
|
||||
|
||||
if (IsReversed)
|
||||
Generic.Swap(ref startAngle, ref endAngle);
|
||||
|
||||
if (startAngle > endAngle)
|
||||
startAngle -= Angle.TwoPI;
|
||||
|
||||
return endAngle - startAngle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the arc direction is reversed (clockwise).
|
||||
/// </summary>
|
||||
public bool IsReversed
|
||||
{
|
||||
get { return reversed; }
|
||||
set
|
||||
{
|
||||
if (reversed != value)
|
||||
Reverse();
|
||||
}
|
||||
}
|
||||
|
||||
public RotationType Rotation
|
||||
{
|
||||
get { return IsReversed ? RotationType.CW : RotationType.CCW; }
|
||||
set
|
||||
{
|
||||
IsReversed = (value == RotationType.CW);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start point of the arc.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Vector StartPoint()
|
||||
{
|
||||
return new Vector(
|
||||
Center.X + Radius * Math.Cos(StartAngle),
|
||||
Center.Y + Radius * Math.Sin(StartAngle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End point of the arc.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Vector EndPoint()
|
||||
{
|
||||
return new Vector(
|
||||
Center.X + Radius * Math.Cos(EndAngle),
|
||||
Center.Y + Radius * Math.Sin(EndAngle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc has the same center point and radius as this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsCoradialTo(Arc arc)
|
||||
{
|
||||
return center == arc.Center && Radius.IsEqualTo(arc.Radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc has the same radius as this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsConcentricTo(Arc arc)
|
||||
{
|
||||
return center == arc.center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle has the same radius as this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsConcentricTo(Circle circle)
|
||||
{
|
||||
return center == circle.Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the arc to a group of points.
|
||||
/// </summary>
|
||||
/// <param name="segments">Number of parts to divide the arc into.</param>
|
||||
/// <returns></returns>
|
||||
public List<Vector> ToPoints(int segments = 1000)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
var stepAngle = reversed
|
||||
? -SweepAngle() / segments
|
||||
: SweepAngle() / segments;
|
||||
|
||||
for (int i = 0; i <= segments; ++i)
|
||||
{
|
||||
var angle = stepAngle * i + StartAngle;
|
||||
|
||||
points.Add(new Vector(
|
||||
Math.Cos(angle) * Radius + Center.X,
|
||||
Math.Sin(angle) * Radius + Center.Y));
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear distance of the arc.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get { return Diameter * Math.PI * SweepAngle() / Angle.TwoPI; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
reversed = !reversed;
|
||||
Generic.Swap(ref startAngle, ref endAngle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the center point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The x-coordinate</param>
|
||||
/// <param name="y">The y-coordinate</param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
Center = new Vector(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the center point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt">The new center point location.</param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
Center = pt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the center point by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x">The x-axis offset distance.</param>
|
||||
/// <param name="y">The y-axis offset distance.</param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
Center = new Vector(Center.X + x, Center.Y + y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the center point by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
Center += voffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the arc from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
center *= factor;
|
||||
radius *= factor;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the arc from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
center = center.Scale(factor, origin);
|
||||
radius *= factor;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the arc from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
startAngle += angle;
|
||||
endAngle += angle;
|
||||
center = center.Rotate(angle);
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the arc from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
startAngle += angle;
|
||||
endAngle += angle;
|
||||
center = center.Rotate(angle, origin);
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
var startpt = StartPoint();
|
||||
var endpt = EndPoint();
|
||||
|
||||
double minX;
|
||||
double minY;
|
||||
double maxX;
|
||||
double maxY;
|
||||
|
||||
if (startpt.X < endpt.X)
|
||||
{
|
||||
minX = startpt.X;
|
||||
maxX = endpt.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
minX = endpt.X;
|
||||
maxX = startpt.X;
|
||||
}
|
||||
|
||||
if (startpt.Y < endpt.Y)
|
||||
{
|
||||
minY = startpt.Y;
|
||||
maxY = endpt.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
minY = endpt.Y;
|
||||
maxY = startpt.Y;
|
||||
}
|
||||
|
||||
var angle1 = StartAngle;
|
||||
var angle2 = EndAngle;
|
||||
|
||||
// switch the angle to counter clockwise.
|
||||
if (IsReversed)
|
||||
Generic.Swap(ref angle1, ref angle2);
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.HalfPI, angle1, angle2))
|
||||
maxY = Center.Y + Radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Math.PI, angle1, angle2))
|
||||
minX = Center.X - Radius;
|
||||
|
||||
const double oneHalfPI = Math.PI * 1.5;
|
||||
|
||||
if (Angle.IsBetweenRad(oneHalfPI, angle1, angle2))
|
||||
minY = Center.Y - Radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.TwoPI, angle1, angle2))
|
||||
maxX = Center.X + Radius;
|
||||
|
||||
boundingBox.X = minX;
|
||||
boundingBox.Y = minY;
|
||||
boundingBox.Width = maxX - minX;
|
||||
boundingBox.Height = maxY - minY;
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
if (side == OffsetSide.Left && reversed)
|
||||
{
|
||||
return new Arc(center, radius + distance, startAngle, endAngle, reversed);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (distance >= radius)
|
||||
return null;
|
||||
|
||||
return new Arc(center, radius - distance, startAngle, endAngle, reversed);
|
||||
}
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the arc to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
var angle = Center.AngleTo(pt);
|
||||
|
||||
if (Angle.IsBetweenRad(angle, StartAngle, EndAngle, IsReversed))
|
||||
{
|
||||
return new Vector(
|
||||
Math.Cos(angle) * Radius + Center.X,
|
||||
Math.Sin(angle) * Radius + Center.Y);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sp = StartPoint();
|
||||
var ep = EndPoint();
|
||||
|
||||
return pt.DistanceTo(sp) <= pt.DistanceTo(ep) ? sp : ep;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, arc, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, arc, out pts); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, circle, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, circle, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, line, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, line, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Arc; }
|
||||
}
|
||||
}
|
||||
}
|
||||
415
Source/OpenNest.Core/Geometry/Circle.cs
Normal file
415
Source/OpenNest.Core/Geometry/Circle.cs
Normal file
@@ -0,0 +1,415 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Circle : Entity
|
||||
{
|
||||
private Vector center;
|
||||
private double radius;
|
||||
|
||||
public Circle()
|
||||
{
|
||||
}
|
||||
|
||||
public Circle(double x, double y, double radius)
|
||||
: this(new Vector(x, y), radius)
|
||||
{
|
||||
}
|
||||
|
||||
public Circle(Vector center, double radius)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
this.Rotation = RotationType.CCW;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a circle from two points.
|
||||
/// </summary>
|
||||
/// <param name="pt1"></param>
|
||||
/// <param name="pt2"></param>
|
||||
/// <returns></returns>
|
||||
public static Circle CreateFrom2Points(Vector pt1, Vector pt2)
|
||||
{
|
||||
var line = new Line(pt1, pt2);
|
||||
return new Circle(line.MidPoint, line.Length * 0.5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Center point of the circle.
|
||||
/// </summary>
|
||||
public Vector Center
|
||||
{
|
||||
get { return center; }
|
||||
set
|
||||
{
|
||||
var offset = value - center;
|
||||
boundingBox.Offset(offset);
|
||||
center = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Radius of the circle.
|
||||
/// </summary>
|
||||
public double Radius
|
||||
{
|
||||
get { return radius; }
|
||||
set
|
||||
{
|
||||
radius = value;
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Radius * 2. Value NOT stored.
|
||||
/// </summary>
|
||||
public double Diameter
|
||||
{
|
||||
get { return Radius * 2.0; }
|
||||
set { Radius = value / 2.0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotation direction.
|
||||
/// </summary>
|
||||
public RotationType Rotation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Area of the circle.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Area()
|
||||
{
|
||||
return Math.PI * Radius * Radius;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear distance around the circle.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Circumference()
|
||||
{
|
||||
return Math.PI * Diameter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle has the same radius as this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsConcentricTo(Circle circle)
|
||||
{
|
||||
return center == circle.center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle has the same radius as this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsConcentricTo(Arc arc)
|
||||
{
|
||||
return center == arc.Center;
|
||||
}
|
||||
|
||||
public bool ContainsPoint(Vector pt)
|
||||
{
|
||||
return Center.DistanceTo(pt) <= Radius;
|
||||
}
|
||||
|
||||
public List<Vector> ToPoints(int segments = 1000)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
var stepAngle = Angle.TwoPI / segments;
|
||||
|
||||
for (int i = 0; i <= segments; ++i)
|
||||
{
|
||||
var angle = stepAngle * i;
|
||||
|
||||
points.Add(new Vector(
|
||||
Math.Cos(angle) * Radius + Center.X,
|
||||
Math.Sin(angle) * Radius + Center.Y));
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear distance around the circle.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get { return Circumference(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
if (Rotation == RotationType.CCW)
|
||||
Rotation = RotationType.CW;
|
||||
else
|
||||
Rotation = RotationType.CCW;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the center point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
Center = new Vector(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the center point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
Center = pt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the center point by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
Center = new Vector(Center.X + x, Center.Y + y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the center point by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
Center += voffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the circle from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
center *= factor;
|
||||
radius *= factor;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the circle from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
center = center.Scale(factor, origin);
|
||||
radius *= factor;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the circle from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
Center = Center.Rotate(angle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// /// Rotates the circle from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
Center = Center.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
boundingBox.X = Center.X - Radius;
|
||||
boundingBox.Y = Center.Y - Radius;
|
||||
boundingBox.Width = Diameter;
|
||||
boundingBox.Height = Diameter;
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
if (side == OffsetSide.Left && Rotation == RotationType.CCW)
|
||||
{
|
||||
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
||||
{
|
||||
Layer = Layer,
|
||||
Rotation = Rotation
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Circle(center, Radius + distance) { Layer = Layer };
|
||||
}
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
if (ContainsPoint(pt))
|
||||
{
|
||||
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
||||
{
|
||||
Layer = Layer,
|
||||
Rotation = Rotation
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Circle(center, Radius + distance) { Layer = Layer };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the circle to the specified point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
var angle = Center.AngleTo(pt);
|
||||
|
||||
return new Vector(
|
||||
Math.Cos(angle) * Radius + Center.X,
|
||||
Math.Sin(angle) * Radius + Center.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
var dist = Center.DistanceTo(circle.Center);
|
||||
return (dist < (Radius + circle.Radius) && dist > Math.Abs(Radius - circle.Radius));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, circle, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, line, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, line, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts">Points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Circle; }
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Source/OpenNest.Core/Geometry/DefinedShape.cs
Normal file
42
Source/OpenNest.Core/Geometry/DefinedShape.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class DefinedShape
|
||||
{
|
||||
public DefinedShape(Shape shape)
|
||||
{
|
||||
Update(shape.Entities);
|
||||
}
|
||||
|
||||
public DefinedShape(List<Entity> entities)
|
||||
{
|
||||
Update(entities);
|
||||
}
|
||||
|
||||
private void Update(List<Entity> entities)
|
||||
{
|
||||
var shapes = Helper.GetShapes(entities);
|
||||
|
||||
Perimeter = shapes[0];
|
||||
Cutouts = new List<Shape>();
|
||||
|
||||
for (int i = 1; i < shapes.Count; i++)
|
||||
{
|
||||
if (shapes[i].Left < Perimeter.Left)
|
||||
{
|
||||
Cutouts.Add(Perimeter);
|
||||
Perimeter = shapes[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
Cutouts.Add(shapes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Shape Perimeter { get; set; }
|
||||
|
||||
public List<Shape> Cutouts { get; set; }
|
||||
}
|
||||
}
|
||||
268
Source/OpenNest.Core/Geometry/Entity.cs
Normal file
268
Source/OpenNest.Core/Geometry/Entity.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public abstract class Entity : IBoundable
|
||||
{
|
||||
protected Box boundingBox;
|
||||
|
||||
protected Entity()
|
||||
{
|
||||
Layer = OpenNest.Geometry.Layer.Default;
|
||||
boundingBox = new Box();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smallest box that contains the entity.
|
||||
/// </summary>
|
||||
public Box BoundingBox
|
||||
{
|
||||
get { return boundingBox; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entity layer type.
|
||||
/// </summary>
|
||||
public Layer Layer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// X-Coordinate of the left-most point.
|
||||
/// </summary>
|
||||
public virtual double Left
|
||||
{
|
||||
get { return boundingBox.Left; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// X-Coordinate of the right-most point.
|
||||
/// </summary>
|
||||
public virtual double Right
|
||||
{
|
||||
get { return boundingBox.Right; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Y-Coordinate of the highest point.
|
||||
/// </summary>
|
||||
public virtual double Top
|
||||
{
|
||||
get { return boundingBox.Top; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Y-Coordinate of the lowest point.
|
||||
/// </summary>
|
||||
public virtual double Bottom
|
||||
{
|
||||
get { return boundingBox.Bottom; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Length of the entity.
|
||||
/// </summary>
|
||||
public abstract double Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the entity.
|
||||
/// </summary>
|
||||
public abstract void Reverse();
|
||||
|
||||
/// <summary>
|
||||
/// Moves the entity location to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public abstract void MoveTo(double x, double y);
|
||||
|
||||
/// <summary>
|
||||
/// Moves the entity location to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public abstract void MoveTo(Vector pt);
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the entity location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public abstract void Offset(double x, double y);
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the entity location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public abstract void Offset(Vector voffset);
|
||||
|
||||
/// <summary>
|
||||
/// Scales the entity from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public abstract void Scale(double factor);
|
||||
|
||||
/// <summary>
|
||||
/// Scales the entity from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public abstract void Scale(double factor, Vector origin);
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the entity from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public abstract void Rotate(double angle);
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the entity from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public abstract void Rotate(double angle, Vector origin);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public abstract void UpdateBounds();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new entity offset the given distance from this entity.
|
||||
/// </summary>
|
||||
/// <param name="distance"></param>
|
||||
/// <param name="side"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Entity OffsetEntity(double distance, OffsetSide side);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a new entity offset the given distance from this entity. Offset side determined by point.
|
||||
/// </summary>
|
||||
/// <param name="distance"></param>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Entity OffsetEntity(double distance, Vector pt);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the entity to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public abstract Vector ClosestPointTo(Vector pt);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Arc arc);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts">List to store the points of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Arc arc, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Circle circle);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Circle circle, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Line line);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Line line, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Polygon polygon);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Polygon polygon, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Shape shape);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool Intersects(Shape shape, out List<Vector> pts);
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public abstract EntityType Type { get; }
|
||||
}
|
||||
|
||||
public static class EntityExtensions
|
||||
{
|
||||
public static double FindBestRotation(this List<Entity> entities, double stepAngle, double startAngle = 0, double endAngle = Angle.TwoPI)
|
||||
{
|
||||
startAngle = Angle.NormalizeRad(startAngle);
|
||||
|
||||
if (!endAngle.IsEqualTo(Angle.TwoPI))
|
||||
endAngle = Angle.NormalizeRad(endAngle);
|
||||
|
||||
if (stepAngle.IsEqualTo(0.0))
|
||||
return startAngle;
|
||||
|
||||
entities.ForEach(e => e.Rotate(startAngle));
|
||||
|
||||
var bestAngle = startAngle;
|
||||
var bestArea = entities.GetBoundingBox().Area();
|
||||
|
||||
var steps = startAngle < endAngle
|
||||
? (endAngle - startAngle) / stepAngle
|
||||
: (endAngle + Angle.TwoPI) - startAngle / stepAngle;
|
||||
|
||||
for (int i = 1; i <= steps; ++i)
|
||||
{
|
||||
entities.ForEach(e => e.Rotate(stepAngle));
|
||||
|
||||
var area = entities.GetBoundingBox().Area();
|
||||
|
||||
if (area < bestArea)
|
||||
{
|
||||
bestArea = area;
|
||||
bestAngle = startAngle + stepAngle * i;
|
||||
}
|
||||
}
|
||||
|
||||
return bestAngle;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Source/OpenNest.Core/Geometry/EntityType.cs
Normal file
12
Source/OpenNest.Core/Geometry/EntityType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public enum EntityType
|
||||
{
|
||||
Arc,
|
||||
Circle,
|
||||
Line,
|
||||
Shape,
|
||||
Polygon
|
||||
}
|
||||
}
|
||||
29
Source/OpenNest.Core/Geometry/Layer.cs
Normal file
29
Source/OpenNest.Core/Geometry/Layer.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Layer
|
||||
{
|
||||
public static readonly Layer Default = new Layer("0")
|
||||
{
|
||||
Color = Color.White,
|
||||
IsVisible = true
|
||||
};
|
||||
|
||||
public Layer(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public bool IsVisible { get; set; }
|
||||
|
||||
public Color Color { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
552
Source/OpenNest.Core/Geometry/Line.cs
Normal file
552
Source/OpenNest.Core/Geometry/Line.cs
Normal file
@@ -0,0 +1,552 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Line : Entity
|
||||
{
|
||||
private Vector pt1;
|
||||
private Vector pt2;
|
||||
|
||||
public Line()
|
||||
{
|
||||
}
|
||||
|
||||
public Line(double x1, double y1, double x2, double y2)
|
||||
: this(new Vector(x1, y1), new Vector(x2, y2))
|
||||
{
|
||||
}
|
||||
|
||||
public Line(Vector startPoint, Vector endPoint)
|
||||
{
|
||||
pt1 = startPoint;
|
||||
pt2 = endPoint;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start point of the line.
|
||||
/// </summary>
|
||||
public Vector StartPoint
|
||||
{
|
||||
get { return pt1; }
|
||||
set
|
||||
{
|
||||
pt1 = value;
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mid-point of the line.
|
||||
/// </summary>
|
||||
public Vector MidPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
var x = (pt1.X + pt2.X) * 0.5;
|
||||
var y = (pt1.Y + pt2.Y) * 0.5;
|
||||
return new Vector(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End point of the line.
|
||||
/// </summary>
|
||||
public Vector EndPoint
|
||||
{
|
||||
get { return pt2; }
|
||||
set
|
||||
{
|
||||
pt2 = value;
|
||||
UpdateBounds();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the point on the line that is perpendicular from the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public Vector PointPerpendicularFrom(Vector pt)
|
||||
{
|
||||
var diff1 = pt - StartPoint;
|
||||
var diff2 = EndPoint - StartPoint;
|
||||
var dotProduct = diff1.X * diff2.X + diff1.Y * diff2.Y;
|
||||
var lengthSquared = diff2.X * diff2.X + diff2.Y * diff2.Y;
|
||||
var param = dotProduct / lengthSquared;
|
||||
|
||||
if (param < 0)
|
||||
return StartPoint;
|
||||
else if (param > 1)
|
||||
return EndPoint;
|
||||
else
|
||||
{
|
||||
return new Vector(
|
||||
StartPoint.X + param * diff2.X,
|
||||
StartPoint.Y + param * diff2.Y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is parallel to this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsParallelTo(Line line)
|
||||
{
|
||||
bool line1Vertical = IsVertical();
|
||||
bool line2Vertical = line.IsVertical();
|
||||
|
||||
if (line1Vertical)
|
||||
return line2Vertical;
|
||||
else if (line2Vertical)
|
||||
return false;
|
||||
|
||||
return Slope().IsEqualTo(line.Slope());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is perpendicular to this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsPerpendicularTo(Line line)
|
||||
{
|
||||
bool line1Vertical = IsVertical();
|
||||
bool line2Vertical = line.IsVertical();
|
||||
|
||||
if (line1Vertical)
|
||||
return line.IsHorizontal();
|
||||
else if (line.IsVertical())
|
||||
return IsHorizontal();
|
||||
|
||||
return Slope().IsEqualTo(-1 / line.Slope());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pt">Point of intersection.</param>
|
||||
/// <returns></returns>
|
||||
public bool Intersects(Line line, out Vector pt)
|
||||
{
|
||||
var a1 = EndPoint.Y - StartPoint.Y;
|
||||
var b1 = StartPoint.X - EndPoint.X;
|
||||
var c1 = a1 * StartPoint.X + b1 * StartPoint.Y;
|
||||
|
||||
var a2 = line.EndPoint.Y - line.StartPoint.Y;
|
||||
var b2 = line.StartPoint.X - line.EndPoint.X;
|
||||
var c2 = a2 * line.StartPoint.X + b2 * line.StartPoint.Y;
|
||||
|
||||
var d = a1 * b2 - a2 * b1;
|
||||
|
||||
if (d.IsEqualTo(0.0))
|
||||
{
|
||||
pt = new Vector();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = (b2 * c1 - b1 * c2) / d;
|
||||
var y = (a1 * c2 - a2 * c1) / d;
|
||||
|
||||
pt = new Vector(x, y);
|
||||
return boundingBox.Contains(pt) && line.boundingBox.Contains(pt);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is vertical.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsVertical()
|
||||
{
|
||||
return pt1.X.IsEqualTo(pt2.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this is horizontal.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsHorizontal()
|
||||
{
|
||||
return pt1.Y.IsEqualTo(pt2.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is collinear to this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsCollinearTo(Line line)
|
||||
{
|
||||
if (IsVertical())
|
||||
{
|
||||
if (!line.IsVertical())
|
||||
return false;
|
||||
|
||||
return StartPoint.X.IsEqualTo(line.StartPoint.X);
|
||||
}
|
||||
else if (line.IsVertical())
|
||||
return false;
|
||||
|
||||
if (!YIntercept().IsEqualTo(line.YIntercept()))
|
||||
return false;
|
||||
|
||||
if (!Slope().IsEqualTo(line.Slope()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Angle of the line from start point to end point.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Angle()
|
||||
{
|
||||
return StartPoint.AngleTo(EndPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle between the two lines in radians.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleBetween(Line line)
|
||||
{
|
||||
var m1 = Slope();
|
||||
var m2 = line.Slope();
|
||||
return Math.Atan(Math.Abs((m2 - m1) / (1 + m2 * m1)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Slope of the line.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Slope()
|
||||
{
|
||||
if (IsVertical())
|
||||
throw new DivideByZeroException();
|
||||
|
||||
return (EndPoint.Y - StartPoint.Y) / (EndPoint.X - StartPoint.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the y-axis intersection coordinate.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double YIntercept()
|
||||
{
|
||||
return StartPoint.Y - Slope() * StartPoint.X;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Length of the line from start point to end point.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get
|
||||
{
|
||||
var x = EndPoint.X - StartPoint.X;
|
||||
var y = EndPoint.Y - StartPoint.Y;
|
||||
return Math.Sqrt(x * x + y * y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reversed the line.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
Generic.Swap<Vector>(ref pt1, ref pt2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
var xoffset = pt1.X - x;
|
||||
var yoffset = pt1.Y - y;
|
||||
|
||||
pt2.X += xoffset;
|
||||
pt2.Y += yoffset;
|
||||
pt1.X = x;
|
||||
pt1.Y = y;
|
||||
boundingBox.Offset(xoffset, yoffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
var offset = pt1 - pt;
|
||||
|
||||
pt2 += offset;
|
||||
pt1 = pt;
|
||||
boundingBox.Offset(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the line location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
pt2.X += x;
|
||||
pt2.Y += y;
|
||||
pt1.X += x;
|
||||
pt1.Y += y;
|
||||
boundingBox.Offset(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the line location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
pt1 += voffset;
|
||||
pt2 += voffset;
|
||||
boundingBox.Offset(voffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the line from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
pt1 *= factor;
|
||||
pt2 *= factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the line from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
pt1 = (pt1 - origin) * factor + origin;
|
||||
pt2 = (pt2 - origin) * factor + origin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the line from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
StartPoint = StartPoint.Rotate(angle);
|
||||
EndPoint = EndPoint.Rotate(angle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the line from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
StartPoint = StartPoint.Rotate(angle, origin);
|
||||
EndPoint = EndPoint.Rotate(angle, origin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override sealed void UpdateBounds()
|
||||
{
|
||||
if (StartPoint.X < EndPoint.X)
|
||||
{
|
||||
boundingBox.X = StartPoint.X;
|
||||
boundingBox.Width = EndPoint.X - StartPoint.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
boundingBox.X = EndPoint.X;
|
||||
boundingBox.Width = StartPoint.X - EndPoint.X;
|
||||
}
|
||||
|
||||
if (StartPoint.Y < EndPoint.Y)
|
||||
{
|
||||
boundingBox.Y = StartPoint.Y;
|
||||
boundingBox.Height = EndPoint.Y - StartPoint.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
boundingBox.Y = EndPoint.Y;
|
||||
boundingBox.Height = StartPoint.Y - EndPoint.Y;
|
||||
}
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
var angle = OpenNest.Angle.NormalizeRad(Angle() + OpenNest.Angle.HalfPI);
|
||||
|
||||
var x = Math.Cos(angle) * distance;
|
||||
var y = Math.Sin(angle) * distance;
|
||||
|
||||
var pt = new Vector(x, y);
|
||||
|
||||
return side == OffsetSide.Left
|
||||
? new Line(StartPoint + pt, EndPoint + pt)
|
||||
: new Line(EndPoint + pt, StartPoint + pt);
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
var a = pt - StartPoint;
|
||||
var b = EndPoint - StartPoint;
|
||||
var c = a.DotProduct(b);
|
||||
var side = c < 0 ? OffsetSide.Left : OffsetSide.Right;
|
||||
|
||||
return OffsetEntity(distance, side);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the line to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
var perpendicularPt = PointPerpendicularFrom(pt);
|
||||
|
||||
if (BoundingBox.Contains(perpendicularPt))
|
||||
return perpendicularPt;
|
||||
else
|
||||
return pt.DistanceTo(StartPoint) <= pt.DistanceTo(EndPoint) ? StartPoint : EndPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
Vector pt;
|
||||
return Intersects(line, out pt);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
Vector pt;
|
||||
var success = Helper.Intersects(this, line, out pt);
|
||||
pts = new List<Vector>(new[] { pt });
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Line; }
|
||||
}
|
||||
}
|
||||
}
|
||||
500
Source/OpenNest.Core/Geometry/Polygon.cs
Normal file
500
Source/OpenNest.Core/Geometry/Polygon.cs
Normal file
@@ -0,0 +1,500 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Polygon : Entity
|
||||
{
|
||||
public List<Vector> Vertices;
|
||||
|
||||
public Polygon()
|
||||
{
|
||||
Vertices = new List<Vector>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the polygon if it's not already.
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
return;
|
||||
|
||||
var first = Vertices.First();
|
||||
var last = Vertices.Last();
|
||||
|
||||
if (first != last)
|
||||
Vertices.Add(first);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the polygon is closed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsClosed()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
return false;
|
||||
|
||||
return (Vertices.First() == Vertices.Last());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the polygon is self intersecting.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsComplex()
|
||||
{
|
||||
var lines = ToLines();
|
||||
|
||||
for (int i = 0; i < lines.Count; ++i)
|
||||
{
|
||||
var line1 = lines[i];
|
||||
|
||||
for (int j = i; j < lines.Count; ++j)
|
||||
{
|
||||
var line2 = lines[j];
|
||||
|
||||
if (line1.Intersects(line2))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Area of the polygon.
|
||||
/// </summary>
|
||||
/// <returns>Returns the area or 0 if the polygon is NOT closed.</returns>
|
||||
public double Area()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
return 0.0;
|
||||
|
||||
return Math.Abs(CalculateArea());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Distance around the polygon.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public double Perimeter()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
return 0.0;
|
||||
|
||||
double sum = 0.0;
|
||||
|
||||
var last = Vertices[0];
|
||||
|
||||
for (int i = 1; i < Vertices.Count; ++i)
|
||||
{
|
||||
var current = Vertices[i];
|
||||
sum += last.DistanceTo(current);
|
||||
last = current;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation direction of the polygon.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public RotationType RotationDirection()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
throw new Exception("Not enough points to determine direction. Must have at least 3 points.");
|
||||
|
||||
return CalculateArea() > 0 ? RotationType.CCW : RotationType.CW;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the polygon to a group of lines.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<Line> ToLines()
|
||||
{
|
||||
var list = new List<Line>();
|
||||
|
||||
if (Vertices.Count < 2)
|
||||
return list;
|
||||
|
||||
var last = Vertices[0];
|
||||
|
||||
for (int i = 1; i < Vertices.Count; ++i)
|
||||
{
|
||||
var current = Vertices[i];
|
||||
list.Add(new Line(last, current));
|
||||
last = current;
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the area of the polygon.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the area of the polygon.
|
||||
/// * Positive number = counter-clockwise rotation
|
||||
/// * Negative number = clockwise rotation
|
||||
/// </returns>
|
||||
private double CalculateArea()
|
||||
{
|
||||
double xsum = 0;
|
||||
double ysum = 0;
|
||||
|
||||
for (int i = 0; i < Vertices.Count - 1; ++i)
|
||||
{
|
||||
var current = Vertices[i];
|
||||
var next = Vertices[i + 1];
|
||||
|
||||
xsum += current.X * next.Y;
|
||||
ysum += current.Y * next.X;
|
||||
}
|
||||
|
||||
return (xsum - ysum) * 0.5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Distance around the polygon.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get { return Perimeter(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction of the polygon.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
Vertices.Reverse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
if (Vertices.Count == 0)
|
||||
return;
|
||||
|
||||
var first = Vertices[0];
|
||||
var offset = new Vector(x - first.X, y - first.Y);
|
||||
|
||||
Vertices.ForEach(vertex => vertex += offset);
|
||||
boundingBox.Offset(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
if (Vertices.Count == 0)
|
||||
return;
|
||||
|
||||
var first = Vertices[0];
|
||||
var offset = pt - first;
|
||||
|
||||
Vertices.ForEach(vertex => vertex += offset);
|
||||
boundingBox.Offset(offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = Vertices[i].Offset(x, y);
|
||||
|
||||
boundingBox.Offset(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = Vertices[i].Offset(voffset);
|
||||
|
||||
boundingBox.Offset(voffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the polygon from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] *= factor;
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the polygon from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = (Vertices[i] - origin) * factor + origin;
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the polygon from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = Vertices[i].Rotate(angle);
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the polygon from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
Vertices[i] = Vertices[i].Rotate(angle, origin);
|
||||
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
if (Vertices.Count == 0)
|
||||
return;
|
||||
|
||||
var first = Vertices[0];
|
||||
var minX = first.X;
|
||||
var maxX = first.X;
|
||||
var minY = first.Y;
|
||||
var maxY = first.Y;
|
||||
|
||||
for (int i = 1; i < Vertices.Count; ++i)
|
||||
{
|
||||
var vertex = Vertices[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
}
|
||||
|
||||
boundingBox.X = minX;
|
||||
boundingBox.Y = minY;
|
||||
boundingBox.Width = maxX - minX;
|
||||
boundingBox.Height = maxY - minY;
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the polygon to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
var lines = ToLines();
|
||||
|
||||
if (lines.Count == 0)
|
||||
return Vector.Invalid;
|
||||
|
||||
Vector closestPt = lines[0].ClosestPointTo(pt);
|
||||
double distance = closestPt.DistanceTo(pt);
|
||||
|
||||
for (int i = 1; i < lines.Count; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
var closestPt2 = line.ClosestPointTo(pt);
|
||||
var distance2 = closestPt2.DistanceTo(pt);
|
||||
|
||||
if (distance2 < distance)
|
||||
{
|
||||
closestPt = closestPt2;
|
||||
distance = distance2;
|
||||
}
|
||||
}
|
||||
|
||||
return closestPt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(line, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(line, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(shape, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(shape, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Polygon; }
|
||||
}
|
||||
|
||||
internal void Cleanup()
|
||||
{
|
||||
for (int i = Vertices.Count - 1; i > 0; i--)
|
||||
{
|
||||
var vertex = Vertices[i];
|
||||
var nextVertex = Vertices[i - 1];
|
||||
|
||||
if (vertex == nextVertex)
|
||||
Vertices.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public double FindBestRotation(double stepAngle)
|
||||
{
|
||||
var entities = new List<Entity>(ToLines());
|
||||
return entities.FindBestRotation(stepAngle);
|
||||
}
|
||||
|
||||
public double FindBestRotation(double stepAngle, double startAngle, double endAngle)
|
||||
{
|
||||
var entities = new List<Entity>(ToLines());
|
||||
return entities.FindBestRotation(stepAngle, startAngle, endAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
569
Source/OpenNest.Core/Geometry/Shape.cs
Normal file
569
Source/OpenNest.Core/Geometry/Shape.cs
Normal file
@@ -0,0 +1,569 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public class Shape : Entity
|
||||
{
|
||||
/// <summary>
|
||||
/// Entities that make up the shape.
|
||||
/// </summary>
|
||||
public List<Entity> Entities;
|
||||
|
||||
public Shape()
|
||||
{
|
||||
Entities = new List<Entity>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the shape is closed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsClosed()
|
||||
{
|
||||
if (Entities.Count == 0)
|
||||
return false;
|
||||
|
||||
var first = Entities[0];
|
||||
Vector firstStartPoint;
|
||||
Vector firstEndPoint;
|
||||
|
||||
switch (first.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)first;
|
||||
firstStartPoint = arc.StartPoint();
|
||||
firstEndPoint = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
return Entities.Count == 1;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)first;
|
||||
firstStartPoint = line.StartPoint;
|
||||
firstEndPoint = line.EndPoint;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
return false;
|
||||
}
|
||||
|
||||
var endpt = firstEndPoint;
|
||||
|
||||
Entity geo = null;
|
||||
|
||||
for (int i = 1; i < Entities.Count; ++i)
|
||||
{
|
||||
geo = Entities[i];
|
||||
|
||||
switch (geo.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)geo;
|
||||
|
||||
if (arc.StartPoint() != endpt)
|
||||
return false;
|
||||
|
||||
endpt = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
return Entities.Count == 1;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)geo;
|
||||
|
||||
if (line.StartPoint != endpt)
|
||||
return false;
|
||||
|
||||
endpt = line.EndPoint;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (geo == null)
|
||||
return false;
|
||||
|
||||
var last = geo;
|
||||
Vector lastEndPoint;
|
||||
|
||||
switch (last.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)last;
|
||||
lastEndPoint = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)last;
|
||||
lastEndPoint = line.EndPoint;
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
return false;
|
||||
}
|
||||
|
||||
return lastEndPoint == firstStartPoint;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the area.
|
||||
/// </summary>
|
||||
/// <returns>Returns the area or 0 if the shape is NOT closed.</returns>
|
||||
public double Area()
|
||||
{
|
||||
// Check if the shape is closed so we can get the area.
|
||||
if (!IsClosed())
|
||||
return 0;
|
||||
|
||||
// If the shape is closed and only one entity in the geometry
|
||||
// then that entity would have to be a circle.
|
||||
if (Entities.Count == 1)
|
||||
{
|
||||
var circle = Entities[0] as Circle;
|
||||
return circle == null ? 0 : circle.Area();
|
||||
}
|
||||
|
||||
return ToPolygon().Area();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Joins all overlapping lines and arcs.
|
||||
/// </summary>
|
||||
public void Optimize()
|
||||
{
|
||||
var lines = new List<Line>();
|
||||
var arcs = new List<Arc>();
|
||||
|
||||
foreach (var geo in Entities)
|
||||
{
|
||||
switch (geo.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
arcs.Add((Arc)geo);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
lines.Add((Line)geo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Helper.Optimize(lines);
|
||||
Helper.Optimize(arcs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the shape to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <param name="entity">Entity that contains the point.</param>
|
||||
/// <returns></returns>
|
||||
public Vector ClosestPointTo(Vector pt, out Entity entity)
|
||||
{
|
||||
if (Entities.Count == 0)
|
||||
{
|
||||
entity = null;
|
||||
return Vector.Invalid;
|
||||
}
|
||||
|
||||
var first = Entities[0];
|
||||
|
||||
Vector closestPt = first.ClosestPointTo(pt);
|
||||
double distance = closestPt.DistanceTo(pt);
|
||||
|
||||
entity = first;
|
||||
|
||||
for (int i = 1; i < Entities.Count; i++)
|
||||
{
|
||||
var entity2 = Entities[i];
|
||||
var closestPt2 = entity2.ClosestPointTo(pt);
|
||||
var distance2 = closestPt2.DistanceTo(pt);
|
||||
|
||||
if (distance2 < distance)
|
||||
{
|
||||
closestPt = closestPt2;
|
||||
distance = distance2;
|
||||
entity = entity2;
|
||||
}
|
||||
}
|
||||
|
||||
return closestPt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the shape to a polygon.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Polygon ToPolygon(int arcSegments = 1000)
|
||||
{
|
||||
var polygon = new Polygon();
|
||||
|
||||
foreach (var entity in Entities)
|
||||
{
|
||||
switch (entity.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)entity;
|
||||
polygon.Vertices.AddRange(arc.ToPoints(arcSegments));
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)entity;
|
||||
polygon.Vertices.AddRange(new[]
|
||||
{
|
||||
line.StartPoint,
|
||||
line.EndPoint
|
||||
});
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
var circle = (Circle)entity;
|
||||
polygon.Vertices.AddRange(circle.ToPoints(arcSegments));
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
polygon.Close();
|
||||
polygon.Cleanup();
|
||||
|
||||
return polygon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the rotation direction of the shape.
|
||||
/// </summary>
|
||||
public override void Reverse()
|
||||
{
|
||||
Entities.ForEach(e => e.Reverse());
|
||||
Entities.Reverse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linear distance of the shape.
|
||||
/// </summary>
|
||||
public override double Length
|
||||
{
|
||||
get { return Entities.Sum(geo => geo.Length); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void MoveTo(double x, double y)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the start point to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
public override void MoveTo(Vector pt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the shape location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="x"></param>
|
||||
/// <param name="y"></param>
|
||||
public override void Offset(double x, double y)
|
||||
{
|
||||
Entities.ForEach(e => e.Offset(x, y));
|
||||
boundingBox.Offset(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the shape location by the given distances.
|
||||
/// </summary>
|
||||
/// <param name="voffset"></param>
|
||||
public override void Offset(Vector voffset)
|
||||
{
|
||||
Entities.ForEach(e => e.Offset(voffset));
|
||||
boundingBox.Offset(voffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the shape from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
public override void Scale(double factor)
|
||||
{
|
||||
Entities.ForEach(e => e.Scale(factor));
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the shape from the origin.
|
||||
/// </summary>
|
||||
/// <param name="factor"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Scale(double factor, Vector origin)
|
||||
{
|
||||
Entities.ForEach(e => e.Scale(factor, origin));
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the shape from the zero point.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
public override void Rotate(double angle)
|
||||
{
|
||||
Entities.ForEach(e => e.Rotate(angle));
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the shape from the origin.
|
||||
/// </summary>
|
||||
/// <param name="angle"></param>
|
||||
/// <param name="origin"></param>
|
||||
public override void Rotate(double angle, Vector origin)
|
||||
{
|
||||
Entities.ForEach(e => e.Rotate(angle, origin));
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
boundingBox = Entities.Select(geo => geo.BoundingBox)
|
||||
.ToList()
|
||||
.GetBoundingBox();
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
{
|
||||
var offsetShape = new Shape();
|
||||
var definedShape = new DefinedShape(this);
|
||||
|
||||
Entity lastEntity = null;
|
||||
Entity lastOffsetEntity = null;
|
||||
|
||||
foreach (var entity in definedShape.Perimeter.Entities)
|
||||
{
|
||||
var offsetEntity = entity.OffsetEntity(distance, side);
|
||||
|
||||
if (offsetEntity == null)
|
||||
continue;
|
||||
|
||||
switch (entity.Type)
|
||||
{
|
||||
case EntityType.Line:
|
||||
{
|
||||
var line = (Line)entity;
|
||||
var offsetLine = (Line)offsetEntity;
|
||||
|
||||
if (lastOffsetEntity != null && lastOffsetEntity.Type == EntityType.Line)
|
||||
{
|
||||
var lastLine = lastEntity as Line;
|
||||
var lastOffsetLine = lastOffsetEntity as Line;
|
||||
|
||||
if (lastLine == null || lastOffsetLine == null)
|
||||
continue;
|
||||
|
||||
Vector intersection;
|
||||
|
||||
if (Helper.Intersects(offsetLine, lastOffsetLine, out intersection))
|
||||
{
|
||||
offsetLine.StartPoint = intersection;
|
||||
lastOffsetLine.EndPoint = intersection;
|
||||
}
|
||||
else
|
||||
{
|
||||
var arc = new Arc(
|
||||
line.StartPoint,
|
||||
distance,
|
||||
line.StartPoint.AngleTo(lastOffsetLine.EndPoint),
|
||||
line.StartPoint.AngleTo(offsetLine.StartPoint),
|
||||
side == OffsetSide.Left
|
||||
);
|
||||
|
||||
offsetShape.Entities.Add(arc);
|
||||
}
|
||||
}
|
||||
|
||||
offsetShape.Entities.Add(offsetLine);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
offsetShape.Entities.Add(offsetEntity);
|
||||
break;
|
||||
}
|
||||
|
||||
lastOffsetEntity = offsetEntity;
|
||||
lastEntity = entity;
|
||||
}
|
||||
|
||||
foreach (var cutout in definedShape.Cutouts)
|
||||
offsetShape.Entities.AddRange(((Shape)cutout.OffsetEntity(distance, side)).Entities);
|
||||
|
||||
return offsetShape;
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, Vector pt)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the shape to the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public override Vector ClosestPointTo(Vector pt)
|
||||
{
|
||||
Entity entity;
|
||||
return ClosestPointTo(pt, out entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given arc is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="arc"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(arc, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given circle is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="circle"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Circle circle, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(circle, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(line, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given line is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Line line, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(line, this, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given polygon is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="polygon"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, polygon, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape)
|
||||
{
|
||||
List<Vector> pts;
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given shape is intersecting this.
|
||||
/// </summary>
|
||||
/// <param name="shape"></param>
|
||||
/// <param name="pts"></param>
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Shape shape, out List<Vector> pts)
|
||||
{
|
||||
return Helper.Intersects(this, shape, out pts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of entity.
|
||||
/// </summary>
|
||||
public override EntityType Type
|
||||
{
|
||||
get { return EntityType.Shape; }
|
||||
}
|
||||
|
||||
public double FindBestRotation(double stepAngle)
|
||||
{
|
||||
return Entities.FindBestRotation(stepAngle);
|
||||
}
|
||||
|
||||
public double FindBestRotation(double stepAngle, double startAngle, double endAngle)
|
||||
{
|
||||
return Entities.FindBestRotation(stepAngle, startAngle, endAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
990
Source/OpenNest.Core/Helper.cs
Normal file
990
Source/OpenNest.Core/Helper.cs
Normal file
@@ -0,0 +1,990 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// Rounds a number down to the nearest factor.
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <param name="factor"></param>
|
||||
/// <returns></returns>
|
||||
public static double RoundDownToNearest(double num, double factor)
|
||||
{
|
||||
return factor.IsEqualTo(0) ? num : Math.Floor(num / factor) * factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds a number up to the nearest factor.
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <param name="factor"></param>
|
||||
/// <returns></returns>
|
||||
public static double RoundUpToNearest(double num, double factor)
|
||||
{
|
||||
return factor.IsEqualTo(0) ? num : Math.Ceiling(num / factor) * factor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rounds a number to the nearest factor using midpoint rounding convention.
|
||||
/// </summary>
|
||||
/// <param name="num"></param>
|
||||
/// <param name="factor"></param>
|
||||
/// <returns></returns>
|
||||
public static double RoundToNearest(double num, double factor)
|
||||
{
|
||||
return factor.IsEqualTo(0) ? num : Math.Round(num / factor) * factor;
|
||||
}
|
||||
|
||||
public static void Optimize(IList<Arc> arcs)
|
||||
{
|
||||
for (int i = 0; i < arcs.Count; ++i)
|
||||
{
|
||||
var arc = arcs[i];
|
||||
|
||||
var coradialArcs = arcs.GetCoradialArs(arc, i);
|
||||
int index = 0;
|
||||
|
||||
while (index < coradialArcs.Count)
|
||||
{
|
||||
Arc arc2 = coradialArcs[index];
|
||||
Arc joinArc;
|
||||
|
||||
if (!TryJoinArcs(arc, arc2, out joinArc))
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
coradialArcs.Remove(arc2);
|
||||
arcs.Remove(arc2);
|
||||
|
||||
arc = joinArc;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
arcs[i] = arc;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Optimize(IList<Line> lines)
|
||||
{
|
||||
for (int i = 0; i < lines.Count; ++i)
|
||||
{
|
||||
var line = lines[i];
|
||||
|
||||
var collinearLines = lines.GetCollinearLines(line, i);
|
||||
var index = 0;
|
||||
|
||||
while (index < collinearLines.Count)
|
||||
{
|
||||
Line line2 = collinearLines[index];
|
||||
Line joinLine;
|
||||
|
||||
if (!TryJoinLines(line, line2, out joinLine))
|
||||
{
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
collinearLines.Remove(line2);
|
||||
lines.Remove(line2);
|
||||
|
||||
line = joinLine;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
lines[i] = line;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryJoinLines(Line line1, Line line2, out Line lineOut)
|
||||
{
|
||||
lineOut = null;
|
||||
|
||||
if (line1 == line2)
|
||||
return false;
|
||||
|
||||
if (!line1.IsCollinearTo(line2))
|
||||
return false;
|
||||
|
||||
bool onPoint = false;
|
||||
|
||||
if (line1.StartPoint == line2.StartPoint)
|
||||
onPoint = true;
|
||||
else if (line1.StartPoint == line2.EndPoint)
|
||||
onPoint = true;
|
||||
else if (line1.EndPoint == line2.StartPoint)
|
||||
onPoint = true;
|
||||
else if (line1.EndPoint == line2.EndPoint)
|
||||
onPoint = true;
|
||||
|
||||
var t1 = line1.StartPoint.Y > line1.EndPoint.Y ? line1.StartPoint.Y : line1.EndPoint.Y;
|
||||
var t2 = line2.StartPoint.Y > line2.EndPoint.Y ? line2.StartPoint.Y : line2.EndPoint.Y;
|
||||
var b1 = line1.StartPoint.Y < line1.EndPoint.Y ? line1.StartPoint.Y : line1.EndPoint.Y;
|
||||
var b2 = line2.StartPoint.Y < line2.EndPoint.Y ? line2.StartPoint.Y : line2.EndPoint.Y;
|
||||
var l1 = line1.StartPoint.X < line1.EndPoint.X ? line1.StartPoint.X : line1.EndPoint.X;
|
||||
var l2 = line2.StartPoint.X < line2.EndPoint.X ? line2.StartPoint.X : line2.EndPoint.X;
|
||||
var r1 = line1.StartPoint.X > line1.EndPoint.X ? line1.StartPoint.X : line1.EndPoint.X;
|
||||
var r2 = line2.StartPoint.X > line2.EndPoint.X ? line2.StartPoint.X : line2.EndPoint.X;
|
||||
|
||||
if (!onPoint)
|
||||
{
|
||||
if (t1 < b2 - Tolerance.Epsilon) return false;
|
||||
if (b1 > t2 + Tolerance.Epsilon) return false;
|
||||
if (l1 > r2 + Tolerance.Epsilon) return false;
|
||||
if (r1 < l2 - Tolerance.Epsilon) return false;
|
||||
}
|
||||
|
||||
var l = l1 < l2 ? l1 : l2;
|
||||
var r = r1 > r2 ? r1 : r2;
|
||||
var t = t1 > t2 ? t1 : t2;
|
||||
var b = b1 < b2 ? b1 : b2;
|
||||
|
||||
if (!line1.IsVertical() && line1.Slope() < 0)
|
||||
lineOut = new Line(new Vector(l, t), new Vector(r, b));
|
||||
else
|
||||
lineOut = new Line(new Vector(l, b), new Vector(r, t));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryJoinArcs(Arc arc1, Arc arc2, out Arc arcOut)
|
||||
{
|
||||
arcOut = null;
|
||||
|
||||
if (arc1 == arc2)
|
||||
return false;
|
||||
|
||||
if (arc1.Center != arc2.Center)
|
||||
return false;
|
||||
|
||||
if (!arc1.Radius.IsEqualTo(arc2.Radius))
|
||||
return false;
|
||||
|
||||
if (arc1.StartAngle > arc1.EndAngle)
|
||||
arc1.StartAngle -= Angle.TwoPI;
|
||||
|
||||
if (arc2.StartAngle > arc2.EndAngle)
|
||||
arc2.StartAngle -= Angle.TwoPI;
|
||||
|
||||
if (arc1.EndAngle < arc2.StartAngle || arc1.StartAngle > arc2.EndAngle)
|
||||
return false;
|
||||
|
||||
var startAngle = arc1.StartAngle < arc2.StartAngle ? arc1.StartAngle : arc2.StartAngle;
|
||||
var endAngle = arc1.EndAngle > arc2.EndAngle ? arc1.EndAngle : arc2.EndAngle;
|
||||
|
||||
if (startAngle < 0) startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0) endAngle += Angle.TwoPI;
|
||||
|
||||
arcOut = new Arc(arc1.Center, arc1.Radius, startAngle, endAngle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<Line> GetCollinearLines(this IList<Line> lines, Line line, int startIndex)
|
||||
{
|
||||
var collinearLines = new List<Line>();
|
||||
|
||||
Parallel.For(startIndex, lines.Count, index =>
|
||||
{
|
||||
var compareLine = lines[index];
|
||||
|
||||
if (Object.ReferenceEquals(line, compareLine))
|
||||
return;
|
||||
|
||||
if (!line.IsCollinearTo(compareLine))
|
||||
return;
|
||||
|
||||
lock (collinearLines)
|
||||
{
|
||||
collinearLines.Add(compareLine);
|
||||
}
|
||||
});
|
||||
|
||||
return collinearLines;
|
||||
}
|
||||
|
||||
private static List<Arc> GetCoradialArs(this IList<Arc> arcs, Arc arc, int startIndex)
|
||||
{
|
||||
var coradialArcs = new List<Arc>();
|
||||
|
||||
Parallel.For(startIndex, arcs.Count, index =>
|
||||
{
|
||||
var compareArc = arcs[index];
|
||||
|
||||
if (Object.ReferenceEquals(arc, compareArc))
|
||||
return;
|
||||
|
||||
if (!arc.IsCoradialTo(compareArc))
|
||||
return;
|
||||
|
||||
lock (coradialArcs)
|
||||
{
|
||||
coradialArcs.Add(compareArc);
|
||||
}
|
||||
});
|
||||
|
||||
return coradialArcs;
|
||||
}
|
||||
|
||||
public static List<Shape> GetShapes(IEnumerable<Entity> entities)
|
||||
{
|
||||
var lines = new List<Line>();
|
||||
var arcs = new List<Arc>();
|
||||
var circles = new List<Circle>();
|
||||
var shapes = new List<Shape>();
|
||||
|
||||
var entities2 = new Queue<Entity>(entities);
|
||||
|
||||
while (entities2.Count > 0)
|
||||
{
|
||||
var entity = entities2.Dequeue();
|
||||
|
||||
switch (entity.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
arcs.Add((Arc)entity);
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
circles.Add((Circle)entity);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
lines.Add((Line)entity);
|
||||
break;
|
||||
|
||||
case EntityType.Shape:
|
||||
var shape = (Shape)entity;
|
||||
shape.Entities.ForEach(e => entities2.Enqueue(e));
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug.Fail("Unhandled geometry type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var circle in circles)
|
||||
{
|
||||
var shape = new Shape();
|
||||
shape.Entities.Add(circle);
|
||||
shape.UpdateBounds();
|
||||
shapes.Add(shape);
|
||||
}
|
||||
|
||||
var entityList = new List<Entity>();
|
||||
|
||||
entityList.AddRange(lines);
|
||||
entityList.AddRange(arcs);
|
||||
|
||||
while (entityList.Count > 0)
|
||||
{
|
||||
var next = entityList[0];
|
||||
var shape = new Shape();
|
||||
shape.Entities.Add(next);
|
||||
|
||||
entityList.RemoveAt(0);
|
||||
|
||||
Vector startPoint = new Vector();
|
||||
Entity connected;
|
||||
|
||||
switch (next.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)next;
|
||||
startPoint = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)next;
|
||||
startPoint = line.EndPoint;
|
||||
break;
|
||||
}
|
||||
|
||||
while ((connected = GetConnected(startPoint, entityList)) != null)
|
||||
{
|
||||
shape.Entities.Add(connected);
|
||||
entityList.Remove(connected);
|
||||
|
||||
switch (connected.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)connected;
|
||||
startPoint = arc.EndPoint();
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)connected;
|
||||
startPoint = line.EndPoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
shape.UpdateBounds();
|
||||
shapes.Add(shape);
|
||||
}
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
internal static Entity GetConnected(Vector pt, IEnumerable<Entity> geometry)
|
||||
{
|
||||
foreach (var geo in geometry)
|
||||
{
|
||||
switch (geo.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)geo;
|
||||
|
||||
if (arc.StartPoint() == pt)
|
||||
return arc;
|
||||
|
||||
if (arc.EndPoint() == pt)
|
||||
{
|
||||
arc.Reverse();
|
||||
return arc;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)geo;
|
||||
|
||||
if (line.StartPoint == pt)
|
||||
return line;
|
||||
|
||||
if (line.EndPoint == pt)
|
||||
{
|
||||
line.Reverse();
|
||||
return line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc1, Arc arc2, out List<Vector> pts)
|
||||
{
|
||||
var c1 = new Circle(arc1.Center, arc1.Radius);
|
||||
var c2 = new Circle(arc2.Center, arc2.Radius);
|
||||
|
||||
if (!Intersects(c1, c2, out pts))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt =>
|
||||
Angle.IsBetweenRad(arc1.Center.AngleTo(pt), arc1.StartAngle, arc1.EndAngle, arc1.IsReversed) &&
|
||||
Angle.IsBetweenRad(arc2.Center.AngleTo(pt), arc2.StartAngle, arc2.EndAngle, arc2.IsReversed))
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc, Circle circle, out List<Vector> pts)
|
||||
{
|
||||
var c1 = new Circle(arc.Center, arc.Radius);
|
||||
|
||||
if (!Intersects(c1, circle, out pts))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc, Line line, out List<Vector> pts)
|
||||
{
|
||||
var c1 = new Circle(arc.Center, arc.Radius);
|
||||
|
||||
if (!Intersects(c1, line, out pts))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc, Shape shape, out List<Vector> pts)
|
||||
{
|
||||
var pts2 = new List<Vector>();
|
||||
|
||||
foreach (var geo in shape.Entities)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
geo.Intersects(arc, out pts3);
|
||||
pts2.AddRange(pts3);
|
||||
}
|
||||
|
||||
pts = pts2.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Arc arc, Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
var pts2 = new List<Vector>();
|
||||
var lines = polygon.ToLines();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
Intersects(arc, line, out pts3);
|
||||
pts2.AddRange(pts3);
|
||||
}
|
||||
|
||||
pts = pts2.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Circle circle1, Circle circle2, out List<Vector> pts)
|
||||
{
|
||||
var distance = circle1.Center.DistanceTo(circle2.Center);
|
||||
|
||||
// check if circles are too far apart
|
||||
if (distance > circle1.Radius + circle2.Radius)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if one circle contains the other
|
||||
if (distance < Math.Abs(circle1.Radius - circle2.Radius))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
var d = circle2.Center - circle1.Center;
|
||||
var a = (circle1.Radius * circle1.Radius - circle2.Radius * circle2.Radius + distance * distance) / (2.0 * distance);
|
||||
var h = Math.Sqrt(circle1.Radius * circle1.Radius - a * a);
|
||||
|
||||
var pt = new Vector(
|
||||
circle1.Center.X + (a * d.X) / distance,
|
||||
circle1.Center.Y + (a * d.Y) / distance);
|
||||
|
||||
var i1 = new Vector(
|
||||
pt.X + (h * d.Y) / distance,
|
||||
pt.Y - (h * d.X) / distance);
|
||||
|
||||
var i2 = new Vector(
|
||||
pt.X - (h * d.Y) / distance,
|
||||
pt.Y + (h * d.X) / distance);
|
||||
|
||||
pts = i1 != i2 ? new List<Vector> { i1, i2 } : new List<Vector> { i1 };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Circle circle, Line line, out List<Vector> pts)
|
||||
{
|
||||
var d1 = line.EndPoint - line.StartPoint;
|
||||
var d2 = line.StartPoint - circle.Center;
|
||||
|
||||
var a = d1.X * d1.X + d1.Y * d1.Y;
|
||||
var b = (d1.X * d2.X + d1.Y * d2.Y) * 2;
|
||||
var c = (d2.X * d2.X + d2.Y * d2.Y) - circle.Radius * circle.Radius;
|
||||
|
||||
var det = b * b - 4 * a * c;
|
||||
|
||||
if ((a <= Tolerance.Epsilon) || (det < 0))
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
return false;
|
||||
}
|
||||
|
||||
double t;
|
||||
pts = new List<Vector>();
|
||||
|
||||
if (det.IsEqualTo(0))
|
||||
{
|
||||
t = -b / (2 * a);
|
||||
var pt1 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y);
|
||||
|
||||
if (line.BoundingBox.Contains(pt1))
|
||||
pts.Add(pt1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
t = (-b + Math.Sqrt(det)) / (2 * a);
|
||||
var pt2 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y);
|
||||
|
||||
if (line.BoundingBox.Contains(pt2))
|
||||
pts.Add(pt2);
|
||||
|
||||
t = (-b - Math.Sqrt(det)) / (2 * a);
|
||||
var pt3 = new Vector(line.StartPoint.X + t * d1.X, line.StartPoint.Y + t * d1.Y);
|
||||
|
||||
if (line.BoundingBox.Contains(pt3))
|
||||
pts.Add(pt3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Circle circle, Shape shape, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
foreach (var geo in shape.Entities)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
geo.Intersects(circle, out pts3);
|
||||
pts.AddRange(pts3);
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Circle circle, Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
var lines = polygon.ToLines();
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
Intersects(circle, line, out pts3);
|
||||
pts.AddRange(pts3);
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Line line1, Line line2, out Vector pt)
|
||||
{
|
||||
var a1 = line1.EndPoint.Y - line1.StartPoint.Y;
|
||||
var b1 = line1.StartPoint.X - line1.EndPoint.X;
|
||||
var c1 = a1 * line1.StartPoint.X + b1 * line1.StartPoint.Y;
|
||||
|
||||
var a2 = line2.EndPoint.Y - line2.StartPoint.Y;
|
||||
var b2 = line2.StartPoint.X - line2.EndPoint.X;
|
||||
var c2 = a2 * line2.StartPoint.X + b2 * line2.StartPoint.Y;
|
||||
|
||||
var d = a1 * b2 - a2 * b1;
|
||||
|
||||
if (d.IsEqualTo(0.0))
|
||||
{
|
||||
pt = Vector.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
var x = (b2 * c1 - b1 * c2) / d;
|
||||
var y = (a1 * c2 - a2 * c1) / d;
|
||||
|
||||
pt = new Vector(x, y);
|
||||
return line1.BoundingBox.Contains(pt) && line2.BoundingBox.Contains(pt);
|
||||
}
|
||||
|
||||
internal static bool Intersects(Line line, Shape shape, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
foreach (var geo in shape.Entities)
|
||||
{
|
||||
List<Vector> pts3;
|
||||
geo.Intersects(line, out pts3);
|
||||
pts.AddRange(pts3);
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Line line, Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
var lines = polygon.ToLines();
|
||||
|
||||
foreach (var line2 in lines)
|
||||
{
|
||||
Vector pt;
|
||||
|
||||
if (Intersects(line, line2, out pt))
|
||||
pts.Add(pt);
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Shape shape1, Shape shape2, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
for (int i = 0; i < shape1.Entities.Count; i++)
|
||||
{
|
||||
var geo1 = shape1.Entities[i];
|
||||
|
||||
for (int j = 0; j < shape2.Entities.Count; j++)
|
||||
{
|
||||
List<Vector> pts2;
|
||||
bool success = false;
|
||||
|
||||
var geo2 = shape2.Entities[j];
|
||||
|
||||
switch (geo2.Type)
|
||||
{
|
||||
case EntityType.Arc:
|
||||
success = geo1.Intersects((Arc)geo2, out pts2);
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
success = geo1.Intersects((Circle)geo2, out pts2);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
success = geo1.Intersects((Line)geo2, out pts2);
|
||||
break;
|
||||
|
||||
case EntityType.Shape:
|
||||
success = geo1.Intersects((Shape)geo2, out pts2);
|
||||
break;
|
||||
|
||||
case EntityType.Polygon:
|
||||
success = geo1.Intersects((Polygon)geo2, out pts2);
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (success)
|
||||
pts.AddRange(pts2);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Shape shape, Polygon polygon, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
var lines = polygon.ToLines();
|
||||
|
||||
for (int i = 0; i < shape.Entities.Count; i++)
|
||||
{
|
||||
var geo = shape.Entities[i];
|
||||
|
||||
for (int j = 0; j < lines.Count; j++)
|
||||
{
|
||||
var line = lines[j];
|
||||
|
||||
List<Vector> pts2;
|
||||
|
||||
if (geo.Intersects(line, out pts2))
|
||||
pts.AddRange(pts2);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
internal static bool Intersects(Polygon polygon1, Polygon polygon2, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
var lines1 = polygon1.ToLines();
|
||||
var lines2 = polygon2.ToLines();
|
||||
|
||||
for (int i = 0; i < lines1.Count; i++)
|
||||
{
|
||||
var line1 = lines1[i];
|
||||
|
||||
for (int j = 0; j < lines2.Count; j++)
|
||||
{
|
||||
var line2 = lines2[j];
|
||||
Vector pt;
|
||||
|
||||
if (Intersects(line1, line2, out pt))
|
||||
pts.Add(pt);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
public static double ClosestDistanceLeft(Box box, List<Box> boxes)
|
||||
{
|
||||
var closestDistance = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
var compareBox = boxes[i];
|
||||
|
||||
RelativePosition pos;
|
||||
|
||||
if (!box.IsHorizontalTo(compareBox, out pos))
|
||||
continue;
|
||||
|
||||
if (pos != RelativePosition.Right)
|
||||
continue;
|
||||
|
||||
var distance = box.Left - compareBox.Right;
|
||||
|
||||
if (distance < closestDistance)
|
||||
closestDistance = distance;
|
||||
}
|
||||
|
||||
return closestDistance == double.MaxValue ? double.NaN : closestDistance;
|
||||
}
|
||||
|
||||
public static double ClosestDistanceRight(Box box, List<Box> boxes)
|
||||
{
|
||||
var closestDistance = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
var compareBox = boxes[i];
|
||||
|
||||
RelativePosition pos;
|
||||
|
||||
if (!box.IsHorizontalTo(compareBox, out pos))
|
||||
continue;
|
||||
|
||||
if (pos != RelativePosition.Left)
|
||||
continue;
|
||||
|
||||
var distance = compareBox.Left - box.Right;
|
||||
|
||||
if (distance < closestDistance)
|
||||
closestDistance = distance;
|
||||
}
|
||||
|
||||
return closestDistance == double.MaxValue ? double.NaN : closestDistance;
|
||||
}
|
||||
|
||||
public static double ClosestDistanceUp(Box box, List<Box> boxes)
|
||||
{
|
||||
var closestDistance = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
var compareBox = boxes[i];
|
||||
|
||||
RelativePosition pos;
|
||||
|
||||
if (!box.IsVerticalTo(compareBox, out pos))
|
||||
continue;
|
||||
|
||||
if (pos != RelativePosition.Bottom)
|
||||
continue;
|
||||
|
||||
var distance = compareBox.Bottom - box.Top;
|
||||
|
||||
if (distance < closestDistance)
|
||||
closestDistance = distance;
|
||||
}
|
||||
|
||||
return closestDistance == double.MaxValue ? double.NaN : closestDistance;
|
||||
}
|
||||
|
||||
public static double ClosestDistanceDown(Box box, List<Box> boxes)
|
||||
{
|
||||
var closestDistance = double.MaxValue;
|
||||
|
||||
for (int i = 0; i < boxes.Count; i++)
|
||||
{
|
||||
var compareBox = boxes[i];
|
||||
|
||||
RelativePosition pos;
|
||||
|
||||
if (!box.IsVerticalTo(compareBox, out pos))
|
||||
continue;
|
||||
|
||||
if (pos != RelativePosition.Top)
|
||||
continue;
|
||||
|
||||
var distance = box.Bottom - compareBox.Top;
|
||||
|
||||
if (distance < closestDistance)
|
||||
closestDistance = distance;
|
||||
}
|
||||
|
||||
return closestDistance == double.MaxValue ? double.NaN : closestDistance;
|
||||
}
|
||||
|
||||
public static Box GetLargestBoxVertically(Vector pt, Box bounds, IEnumerable<Box> boxes)
|
||||
{
|
||||
var verticalBoxes = boxes.Where(b => !(b.Left > pt.X || b.Right < pt.X)).ToList();
|
||||
|
||||
#region Find Top/Bottom Limits
|
||||
|
||||
var top = double.MaxValue;
|
||||
var btm = double.MinValue;
|
||||
|
||||
foreach (var box in verticalBoxes)
|
||||
{
|
||||
var boxBtm = box.Bottom;
|
||||
var boxTop = box.Top;
|
||||
|
||||
if (boxBtm > pt.Y && boxBtm < top)
|
||||
top = boxBtm;
|
||||
|
||||
else if (box.Top < pt.Y && boxTop > btm)
|
||||
btm = boxTop;
|
||||
}
|
||||
|
||||
if (top == double.MaxValue)
|
||||
{
|
||||
if (bounds.Top > pt.Y)
|
||||
top = bounds.Top;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
if (btm == double.MinValue)
|
||||
{
|
||||
if (bounds.Bottom < pt.Y)
|
||||
btm = bounds.Bottom;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
var horizontalBoxes = boxes.Where(b => !(b.Bottom >= top || b.Top <= btm)).ToList();
|
||||
|
||||
#region Find Left/Right Limits
|
||||
|
||||
var lft = double.MinValue;
|
||||
var rgt = double.MaxValue;
|
||||
|
||||
foreach (var box in horizontalBoxes)
|
||||
{
|
||||
var boxLft = box.Left;
|
||||
var boxRgt = box.Right;
|
||||
|
||||
if (boxLft > pt.X && boxLft < rgt)
|
||||
rgt = boxLft;
|
||||
|
||||
else if (boxRgt < pt.X && boxRgt > lft)
|
||||
lft = boxRgt;
|
||||
}
|
||||
|
||||
if (rgt == double.MaxValue)
|
||||
{
|
||||
if (bounds.Right > pt.X)
|
||||
rgt = bounds.Right;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
if (lft == double.MinValue)
|
||||
{
|
||||
if (bounds.Left < pt.X)
|
||||
lft = bounds.Left;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return new Box(lft, btm, rgt - lft, top - btm);
|
||||
}
|
||||
|
||||
public static Box GetLargestBoxHorizontally(Vector pt, Box bounds, IEnumerable<Box> boxes)
|
||||
{
|
||||
var horizontalBoxes = boxes.Where(b => !(b.Bottom > pt.Y || b.Top < pt.Y)).ToList();
|
||||
|
||||
#region Find Left/Right Limits
|
||||
|
||||
var lft = double.MinValue;
|
||||
var rgt = double.MaxValue;
|
||||
|
||||
foreach (var box in horizontalBoxes)
|
||||
{
|
||||
var boxLft = box.Left;
|
||||
var boxRgt = box.Right;
|
||||
|
||||
if (boxLft > pt.X && boxLft < rgt)
|
||||
rgt = boxLft;
|
||||
|
||||
else if (boxRgt < pt.X && boxRgt > lft)
|
||||
lft = boxRgt;
|
||||
}
|
||||
|
||||
if (rgt == double.MaxValue)
|
||||
{
|
||||
if (bounds.Right > pt.X)
|
||||
rgt = bounds.Right;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
if (lft == double.MinValue)
|
||||
{
|
||||
if (bounds.Left < pt.X)
|
||||
lft = bounds.Left;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
var verticalBoxes = boxes.Where(b => !(b.Left >= rgt || b.Right <= lft)).ToList();
|
||||
|
||||
#region Find Top/Bottom Limits
|
||||
|
||||
var top = double.MaxValue;
|
||||
var btm = double.MinValue;
|
||||
|
||||
foreach (var box in verticalBoxes)
|
||||
{
|
||||
var boxBtm = box.Bottom;
|
||||
var boxTop = box.Top;
|
||||
|
||||
if (boxBtm > pt.Y && boxBtm < top)
|
||||
top = boxBtm;
|
||||
|
||||
else if (box.Top < pt.Y && boxTop > btm)
|
||||
btm = boxTop;
|
||||
}
|
||||
|
||||
if (top == double.MaxValue)
|
||||
{
|
||||
if (bounds.Top > pt.Y)
|
||||
top = bounds.Top;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
if (btm == double.MinValue)
|
||||
{
|
||||
if (bounds.Bottom < pt.Y)
|
||||
btm = bounds.Bottom;
|
||||
else return Box.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return new Box(lft, btm, rgt - lft, top - btm);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Source/OpenNest.Core/IBoundable.cs
Normal file
18
Source/OpenNest.Core/IBoundable.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface IBoundable
|
||||
{
|
||||
Box BoundingBox { get; }
|
||||
|
||||
double Left { get; }
|
||||
|
||||
double Right { get; }
|
||||
|
||||
double Top { get; }
|
||||
|
||||
double Bottom { get; }
|
||||
|
||||
void UpdateBounds();
|
||||
}
|
||||
}
|
||||
17
Source/OpenNest.Core/IPostProcessor.cs
Normal file
17
Source/OpenNest.Core/IPostProcessor.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.IO;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface IPostProcessor
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
string Author { get; }
|
||||
|
||||
string Description { get; }
|
||||
|
||||
void Post(Nest nest, Stream outputStream);
|
||||
|
||||
void Post(Nest nest, string outputFile);
|
||||
}
|
||||
}
|
||||
33
Source/OpenNest.Core/Material.cs
Normal file
33
Source/OpenNest.Core/Material.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Material
|
||||
{
|
||||
public Material()
|
||||
{
|
||||
}
|
||||
|
||||
public Material(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public Material(string name, string grade)
|
||||
{
|
||||
Name = name;
|
||||
Grade = grade;
|
||||
}
|
||||
|
||||
public Material(string name, string grade, double density)
|
||||
{
|
||||
Name = name;
|
||||
Grade = grade;
|
||||
Density = density;
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Grade { get; set; }
|
||||
|
||||
public double Density { get; set; }
|
||||
}
|
||||
}
|
||||
139
Source/OpenNest.Core/Nest.cs
Normal file
139
Source/OpenNest.Core/Nest.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Source/OpenNest.Core/NestConstraints.cs
Normal file
46
Source/OpenNest.Core/NestConstraints.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class NestConstraints
|
||||
{
|
||||
/// <summary>
|
||||
/// The rotation step in radians.
|
||||
/// </summary>
|
||||
public double StepAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rotation start angle in radians.
|
||||
/// </summary>
|
||||
public double StartAngle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The rotation end angle in radians.
|
||||
/// </summary>
|
||||
public double EndAngle { get; set; }
|
||||
|
||||
public bool Allow180Equivalent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the StartAngle and EndAngle to allow 360 degree rotation.
|
||||
/// </summary>
|
||||
public void AllowAnyRotation()
|
||||
{
|
||||
StartAngle = 0;
|
||||
EndAngle = Angle.TwoPI;
|
||||
}
|
||||
|
||||
public bool HasLimitedRotation()
|
||||
{
|
||||
var diff = EndAngle - StartAngle;
|
||||
|
||||
if (diff.IsEqualTo(Angle.TwoPI))
|
||||
return false;
|
||||
|
||||
if ((diff > Math.PI || diff.IsEqualTo(Math.PI)) && Allow180Equivalent)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Source/OpenNest.Core/OffsetSide.cs
Normal file
9
Source/OpenNest.Core/OffsetSide.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum OffsetSide
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
109
Source/OpenNest.Core/OpenNest.Core.csproj
Normal file
109
Source/OpenNest.Core/OpenNest.Core.csproj
Normal 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>
|
||||
190
Source/OpenNest.Core/Part.cs
Normal file
190
Source/OpenNest.Core/Part.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface IPart : IBoundable
|
||||
{
|
||||
Vector Location { get; set; }
|
||||
double Rotation { get; }
|
||||
void Rotate(double angle);
|
||||
void Rotate(double angle, Vector origin);
|
||||
void Offset(double x, double y);
|
||||
void Offset(Vector voffset);
|
||||
void Update();
|
||||
}
|
||||
|
||||
public class Part : IPart, IBoundable
|
||||
{
|
||||
private Vector location;
|
||||
|
||||
public readonly Drawing BaseDrawing;
|
||||
|
||||
public Part(Drawing baseDrawing)
|
||||
: this(baseDrawing, new Vector())
|
||||
{
|
||||
}
|
||||
|
||||
public Part(Drawing baseDrawing, Vector location)
|
||||
{
|
||||
BaseDrawing = baseDrawing;
|
||||
Program = baseDrawing.Program.Clone() as Program;
|
||||
this.location = location;
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Location of the part.
|
||||
/// </summary>
|
||||
public Vector Location
|
||||
{
|
||||
get { return location; }
|
||||
set
|
||||
{
|
||||
BoundingBox.Offset(value - location);
|
||||
location = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Program Program { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation of the part in radians.
|
||||
/// </summary>
|
||||
public double Rotation
|
||||
{
|
||||
get { return Program.Rotation; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the part.
|
||||
/// </summary>
|
||||
/// <param name="angle">Angle of rotation in radians.</param>
|
||||
public void Rotate(double angle)
|
||||
{
|
||||
Program.Rotate(angle);
|
||||
location = Location.Rotate(angle);
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the part around the specified origin.
|
||||
/// </summary>
|
||||
/// <param name="angle">Angle of rotation in radians.</param>
|
||||
/// <param name="origin">The origin to rotate the part around.</param>
|
||||
public void Rotate(double angle, Vector origin)
|
||||
{
|
||||
Program.Rotate(angle);
|
||||
location = Location.Rotate(angle, origin);
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the part.
|
||||
/// </summary>
|
||||
/// <param name="x">The x-axis offset distance.</param>
|
||||
/// <param name="y">The y-axis offset distance.</param>
|
||||
public void Offset(double x, double y)
|
||||
{
|
||||
location = new Vector(location.X + x, location.Y + y);
|
||||
BoundingBox.Offset(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the part.
|
||||
/// </summary>
|
||||
/// <param name="voffset">The vector containing the x-axis & y-axis offset distances.</param>
|
||||
public void Offset(Vector voffset)
|
||||
{
|
||||
location += voffset;
|
||||
BoundingBox.Offset(voffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the bounding box of the part.
|
||||
/// </summary>
|
||||
public void UpdateBounds()
|
||||
{
|
||||
BoundingBox = Program.BoundingBox();
|
||||
BoundingBox.Offset(Location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the part from the drawing it was derived from.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
var rotation = Rotation;
|
||||
Program = BaseDrawing.Program.Clone() as Program;
|
||||
Program.Rotate(Program.Rotation - rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The smallest box that contains the part.
|
||||
/// </summary>
|
||||
public Box BoundingBox { get; protected set; }
|
||||
|
||||
public bool Intersects(Part part, out List<Vector> pts)
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
var entities1 = ConvertProgram.ToGeometry(Program);
|
||||
var entities2 = ConvertProgram.ToGeometry(part.Program);
|
||||
|
||||
var shapes1 = Helper.GetShapes(entities1);
|
||||
var shapes2 = Helper.GetShapes(entities2);
|
||||
|
||||
shapes1.ForEach(shape => shape.Offset(Location));
|
||||
shapes2.ForEach(shape => shape.Offset(part.Location));
|
||||
|
||||
for (int i = 0; i < shapes1.Count; i++)
|
||||
{
|
||||
var shape1 = shapes1[i];
|
||||
|
||||
for (int j = 0; j < shapes2.Count; j++)
|
||||
{
|
||||
var shape2 = shapes2[j];
|
||||
List<Vector> pts2;
|
||||
|
||||
if (shape1.Intersects(shape2, out pts2))
|
||||
pts.AddRange(pts2);
|
||||
}
|
||||
}
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
public double Left
|
||||
{
|
||||
get { return BoundingBox.Left; }
|
||||
}
|
||||
|
||||
public double Right
|
||||
{
|
||||
get { return BoundingBox.Right; }
|
||||
}
|
||||
|
||||
public double Top
|
||||
{
|
||||
get { return BoundingBox.Top; }
|
||||
}
|
||||
|
||||
public double Bottom
|
||||
{
|
||||
get { return BoundingBox.Bottom; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a deep copy of the part.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object Clone()
|
||||
{
|
||||
var part = new Part(BaseDrawing);
|
||||
part.Rotate(Rotation);
|
||||
part.Location = Location;
|
||||
|
||||
return part;
|
||||
}
|
||||
}
|
||||
}
|
||||
475
Source/OpenNest.Core/Plate.cs
Normal file
475
Source/OpenNest.Core/Plate.cs
Normal file
@@ -0,0 +1,475 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Collections;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public class Plate
|
||||
{
|
||||
private int quadrant;
|
||||
|
||||
public event EventHandler<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Source/OpenNest.Core/Properties/AssemblyInfo.cs
Normal file
35
Source/OpenNest.Core/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenNest.Core")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("OpenNest.Core")]
|
||||
[assembly: AssemblyCopyright("Copyright © AJ Isaacs 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("13d3c141-e430-4f27-9098-60d6f93e2a7b")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.4.0.0")]
|
||||
[assembly: AssemblyFileVersion("0.4.0.0")]
|
||||
14
Source/OpenNest.Core/Quantity.cs
Normal file
14
Source/OpenNest.Core/Quantity.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Source/OpenNest.Core/RelativePosition.cs
Normal file
13
Source/OpenNest.Core/RelativePosition.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum RelativePosition
|
||||
{
|
||||
Intersecting,
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
None
|
||||
}
|
||||
}
|
||||
16
Source/OpenNest.Core/RotationType.cs
Normal file
16
Source/OpenNest.Core/RotationType.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum RotationType
|
||||
{
|
||||
/// <summary>
|
||||
/// Clockwise
|
||||
/// </summary>
|
||||
CW,
|
||||
|
||||
/// <summary>
|
||||
/// Counter-Clockwise
|
||||
/// </summary>
|
||||
CCW
|
||||
}
|
||||
}
|
||||
67
Source/OpenNest.Core/Sequence.cs
Normal file
67
Source/OpenNest.Core/Sequence.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public interface ISequencer
|
||||
{
|
||||
List<Part> SequenceParts(IList<Part> parts);
|
||||
List<Part> SequenceParts(IList<Part> parts, Vector origin);
|
||||
}
|
||||
|
||||
public class SequenceByNearest : ISequencer
|
||||
{
|
||||
public List<Part> SequenceParts(IList<Part> parts)
|
||||
{
|
||||
return SequenceParts(parts, Vector.Zero);
|
||||
}
|
||||
|
||||
public List<Part> SequenceParts(IList<Part> parts, Vector origin)
|
||||
{
|
||||
if (parts.Count == 0)
|
||||
return new List<Part>();
|
||||
|
||||
var dupList = new List<Part>(parts);
|
||||
var seqList = new List<Part>(parts.Count);
|
||||
|
||||
var lastPart = GetClosestPart(origin, dupList);
|
||||
seqList.Add(lastPart);
|
||||
dupList.Remove(lastPart);
|
||||
|
||||
for (int i = 0; i < parts.Count - 1 /*STOP BEFORE LAST PART*/; i++)
|
||||
{
|
||||
var nextPart = GetClosestPart(lastPart.Location, dupList);
|
||||
|
||||
if (nextPart == null)
|
||||
break;
|
||||
|
||||
seqList.Add(nextPart);
|
||||
dupList.Remove(nextPart);
|
||||
lastPart = nextPart;
|
||||
}
|
||||
|
||||
return seqList;
|
||||
}
|
||||
|
||||
private Part GetClosestPart(Vector pt, IList<Part> parts)
|
||||
{
|
||||
if (parts.Count == 0)
|
||||
return null;
|
||||
|
||||
var closestPart = parts[0];
|
||||
var closestDistance = parts[0].Location.DistanceTo(pt);
|
||||
|
||||
for (int i = 1; i < parts.Count; i++)
|
||||
{
|
||||
var distance = parts[i].Location.DistanceTo(pt);
|
||||
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestPart = parts[i];
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return closestPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Source/OpenNest.Core/Size.cs
Normal file
55
Source/OpenNest.Core/Size.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public struct Size
|
||||
{
|
||||
public Size(double width, double height)
|
||||
{
|
||||
Height = height;
|
||||
Width = width;
|
||||
}
|
||||
|
||||
public double Height;
|
||||
|
||||
public double Width;
|
||||
|
||||
public static Size Parse(string size)
|
||||
{
|
||||
var a = size.ToUpper().Split('X');
|
||||
|
||||
if (a.Length > 2)
|
||||
throw new FormatException("Invalid size format.");
|
||||
|
||||
var height = double.Parse(a[0]);
|
||||
var width = double.Parse(a[1]);
|
||||
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
public static bool TryParse(string s, out Size size)
|
||||
{
|
||||
try
|
||||
{
|
||||
size = Parse(s);
|
||||
}
|
||||
catch
|
||||
{
|
||||
size = new Size(0, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} x {1}", Height, Width);
|
||||
}
|
||||
|
||||
public string ToString(int decimalPlaces)
|
||||
{
|
||||
return string.Format("{0} x {1}", Math.Round(Height, decimalPlaces), Math.Round(Width, decimalPlaces));
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Source/OpenNest.Core/Spacing.cs
Normal file
27
Source/OpenNest.Core/Spacing.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public struct Spacing
|
||||
{
|
||||
public Spacing(double topBottom, double leftRight)
|
||||
{
|
||||
Top = Bottom = topBottom;
|
||||
Left = Right = leftRight;
|
||||
}
|
||||
|
||||
public Spacing(double left, double bottom, double right, double top)
|
||||
{
|
||||
Left = left;
|
||||
Bottom = bottom;
|
||||
Right = right;
|
||||
Top = top;
|
||||
}
|
||||
|
||||
public double Left;
|
||||
|
||||
public double Bottom;
|
||||
|
||||
public double Right;
|
||||
|
||||
public double Top;
|
||||
}
|
||||
}
|
||||
21
Source/OpenNest.Core/SpecialLayers.cs
Normal file
21
Source/OpenNest.Core/SpecialLayers.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class SpecialLayers
|
||||
{
|
||||
public static readonly Layer Default = new Layer("0");
|
||||
|
||||
public static readonly Layer Cut = new Layer("CUT");
|
||||
|
||||
public static readonly Layer Rapid = new Layer("RAPID");
|
||||
|
||||
public static readonly Layer Display = new Layer("DISPLAY");
|
||||
|
||||
public static readonly Layer Leadin = new Layer("LEADIN");
|
||||
|
||||
public static readonly Layer Leadout = new Layer("LEADOUT");
|
||||
|
||||
public static readonly Layer Scribe = new Layer("SCRIBE");
|
||||
}
|
||||
}
|
||||
91
Source/OpenNest.Core/Timing.cs
Normal file
91
Source/OpenNest.Core/Timing.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Timing
|
||||
{
|
||||
public static TimingInfo GetTimingInfo(Program pgm)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(pgm);
|
||||
var shapes = Helper.GetShapes(entities.Where(entity => entity.Layer != SpecialLayers.Rapid));
|
||||
var info = new TimingInfo { PierceCount = shapes.Count };
|
||||
|
||||
var last = entities[0];
|
||||
ProcessEntity(info, last, null);
|
||||
|
||||
for (int i = 1; i < entities.Count; i++)
|
||||
{
|
||||
var entity = entities[i];
|
||||
ProcessEntity(info, entity, last);
|
||||
last = entity;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static TimingInfo GetTimingInfo(Plate plate)
|
||||
{
|
||||
var info = new TimingInfo();
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
foreach (var part in plate.Parts)
|
||||
{
|
||||
var endpt = part.Program.EndPoint() + part.Location;
|
||||
info += GetTimingInfo(part.Program);
|
||||
info.TravelDistance += pos.DistanceTo(endpt);
|
||||
pos = endpt;
|
||||
}
|
||||
|
||||
info *= plate.Quantity;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static TimingInfo GetTimingInfo(Nest nest)
|
||||
{
|
||||
var info = new TimingInfo();
|
||||
return nest.Plates.Aggregate(info, (current, plate) => current + GetTimingInfo(plate));
|
||||
}
|
||||
|
||||
private static void ProcessEntity(TimingInfo info, Entity entity, Entity lastEntity)
|
||||
{
|
||||
if (entity.Layer == SpecialLayers.Cut)
|
||||
{
|
||||
info.CutDistance += entity.Length;
|
||||
|
||||
if (entity.Type == EntityType.Line &&
|
||||
lastEntity != null &&
|
||||
lastEntity.Type == EntityType.Line &&
|
||||
lastEntity.Layer == SpecialLayers.Cut)
|
||||
info.IntersectionCount++;
|
||||
}
|
||||
else if (entity.Layer == SpecialLayers.Rapid)
|
||||
info.TravelDistance += entity.Length;
|
||||
}
|
||||
|
||||
public static TimeSpan CalculateTime(TimingInfo info, CutParameters cutParams)
|
||||
{
|
||||
var time = new TimeSpan();
|
||||
|
||||
switch (cutParams.Units)
|
||||
{
|
||||
case Units.Inches:
|
||||
time += TimeSpan.FromMinutes(info.CutDistance / cutParams.Feedrate);
|
||||
time += TimeSpan.FromMinutes(info.TravelDistance / cutParams.RapidTravelRate);
|
||||
break;
|
||||
|
||||
case Units.Millimeters:
|
||||
time += TimeSpan.FromSeconds(info.CutDistance / cutParams.Feedrate);
|
||||
time += TimeSpan.FromSeconds(info.TravelDistance / cutParams.RapidTravelRate);
|
||||
break;
|
||||
}
|
||||
|
||||
time += TimeSpan.FromTicks(info.PierceCount * cutParams.PierceTime.Ticks);
|
||||
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Source/OpenNest.Core/TimingInfo.cs
Normal file
57
Source/OpenNest.Core/TimingInfo.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
namespace OpenNest
|
||||
{
|
||||
public class TimingInfo
|
||||
{
|
||||
public int PierceCount;
|
||||
|
||||
public int IntersectionCount;
|
||||
|
||||
public double TravelDistance;
|
||||
|
||||
public double CutDistance;
|
||||
|
||||
public static TimingInfo operator +(TimingInfo info1, TimingInfo info2)
|
||||
{
|
||||
return new TimingInfo
|
||||
{
|
||||
CutDistance = info1.CutDistance + info2.CutDistance,
|
||||
IntersectionCount = info1.IntersectionCount + info2.IntersectionCount,
|
||||
TravelDistance = info1.TravelDistance + info2.TravelDistance,
|
||||
PierceCount = info1.PierceCount + info2.PierceCount
|
||||
};
|
||||
}
|
||||
|
||||
public static TimingInfo operator -(TimingInfo info1, TimingInfo info2)
|
||||
{
|
||||
return new TimingInfo
|
||||
{
|
||||
CutDistance = info1.CutDistance - info2.CutDistance,
|
||||
IntersectionCount = info1.IntersectionCount - info2.IntersectionCount,
|
||||
TravelDistance = info1.TravelDistance - info2.TravelDistance,
|
||||
PierceCount = info1.PierceCount - info2.PierceCount
|
||||
};
|
||||
}
|
||||
|
||||
public static TimingInfo operator *(TimingInfo info1, TimingInfo info2)
|
||||
{
|
||||
return new TimingInfo
|
||||
{
|
||||
CutDistance = info1.CutDistance * info2.CutDistance,
|
||||
IntersectionCount = info1.IntersectionCount * info2.IntersectionCount,
|
||||
TravelDistance = info1.TravelDistance * info2.TravelDistance,
|
||||
PierceCount = info1.PierceCount * info2.PierceCount
|
||||
};
|
||||
}
|
||||
|
||||
public static TimingInfo operator *(TimingInfo info1, int factor)
|
||||
{
|
||||
return new TimingInfo
|
||||
{
|
||||
CutDistance = info1.CutDistance * factor,
|
||||
IntersectionCount = info1.IntersectionCount * factor,
|
||||
TravelDistance = info1.TravelDistance * factor,
|
||||
PierceCount = info1.PierceCount * factor
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Source/OpenNest.Core/Tolerance.cs
Normal file
14
Source/OpenNest.Core/Tolerance.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Tolerance
|
||||
{
|
||||
public const double Epsilon = 0.00001;
|
||||
|
||||
public static bool IsEqualTo(this double a, double b, double tolerance = Epsilon)
|
||||
{
|
||||
return Math.Abs(b - a) <= tolerance;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Source/OpenNest.Core/Trigonometry.cs
Normal file
40
Source/OpenNest.Core/Trigonometry.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public static class Trigonometry
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="height">Height</param>
|
||||
/// <param name="hypotenuse">Hypotenuse</param>
|
||||
/// <returns></returns>
|
||||
public static double Base(double height, double hypotenuse)
|
||||
{
|
||||
return Math.Sqrt(hypotenuse * hypotenuse - height * height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="bottom">Base</param>
|
||||
/// <param name="hypotenuse">Hypotenuse</param>
|
||||
/// <returns></returns>
|
||||
public static double Height(double bottom, double hypotenuse)
|
||||
{
|
||||
return Math.Sqrt(hypotenuse * hypotenuse - bottom * bottom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="height">Height</param>
|
||||
/// <param name="bottom">Base</param>
|
||||
/// <returns></returns>
|
||||
public static double Hypotenuse(double height, double bottom)
|
||||
{
|
||||
return Math.Sqrt(height * height + bottom * bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Source/OpenNest.Core/Units.cs
Normal file
82
Source/OpenNest.Core/Units.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum Units
|
||||
{
|
||||
Inches,
|
||||
Millimeters
|
||||
}
|
||||
|
||||
public static class UnitsHelper
|
||||
{
|
||||
public static string GetShortString(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case Units.Inches:
|
||||
return "in";
|
||||
|
||||
case Units.Millimeters:
|
||||
return "mm";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLongString(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case Units.Inches:
|
||||
return "inches";
|
||||
|
||||
case Units.Millimeters:
|
||||
return "millimeters";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetShortTimeUnit(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case Units.Inches:
|
||||
return "min";
|
||||
|
||||
case Units.Millimeters:
|
||||
return "sec";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetLongTimeUnit(Units units)
|
||||
{
|
||||
switch (units)
|
||||
{
|
||||
case Units.Inches:
|
||||
return "minute";
|
||||
|
||||
case Units.Millimeters:
|
||||
return "second";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetShortTimeUnitPair(Units units)
|
||||
{
|
||||
return GetShortString(units) + "/" + GetShortTimeUnit(units);
|
||||
}
|
||||
|
||||
public static string GetLongTimeUnitPair(Units units)
|
||||
{
|
||||
return GetLongString(units) + "/" + GetLongTimeUnit(units);
|
||||
}
|
||||
}
|
||||
}
|
||||
213
Source/OpenNest.Core/Vector.cs
Normal file
213
Source/OpenNest.Core/Vector.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public struct Vector
|
||||
{
|
||||
public static readonly Vector Invalid = new Vector(double.NaN, double.NaN);
|
||||
public static readonly Vector Zero = new Vector(0, 0);
|
||||
|
||||
public double X;
|
||||
public double Y;
|
||||
|
||||
public Vector(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public double DistanceTo(Vector pt)
|
||||
{
|
||||
var vx = pt.X - X;
|
||||
var vy = pt.Y - Y;
|
||||
|
||||
return Math.Sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
|
||||
public double DistanceTo(double x, double y)
|
||||
{
|
||||
var vx = x - X;
|
||||
var vy = y - Y;
|
||||
|
||||
return Math.Sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
|
||||
public double DotProduct(Vector pt)
|
||||
{
|
||||
return X * pt.X + Y * pt.Y;
|
||||
}
|
||||
|
||||
public double Angle()
|
||||
{
|
||||
return OpenNest.Angle.NormalizeRad(Math.Atan2(Y, X));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle to the given point when the origin is this point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleTo(Vector pt)
|
||||
{
|
||||
return (pt - this).Angle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle when the origin is set at the given point.
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleFrom(Vector pt)
|
||||
{
|
||||
return (this - pt).Angle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the angle between this point and the given point.
|
||||
/// Source: http://math.stackexchange.com/questions/878785/how-to-find-an-angle-in-range0-360-between-2-vectors
|
||||
/// </summary>
|
||||
/// <param name="pt"></param>
|
||||
/// <returns></returns>
|
||||
public double AngleBetween(Vector pt)
|
||||
{
|
||||
var v1 = Normalize();
|
||||
var v2 = pt.Normalize();
|
||||
var dot = v1.X * v2.X + v1.Y + v2.Y;
|
||||
var det = v1.X * v2.X - v1.Y + v2.Y;
|
||||
|
||||
return Math.Atan2(det, dot);
|
||||
}
|
||||
|
||||
public static Vector operator +(Vector pt1, Vector pt2)
|
||||
{
|
||||
return new Vector(pt1.X + pt2.X, pt1.Y + pt2.Y);
|
||||
}
|
||||
|
||||
public static Vector operator -(Vector pt1, Vector pt2)
|
||||
{
|
||||
return new Vector(pt1.X - pt2.X, pt1.Y - pt2.Y);
|
||||
}
|
||||
|
||||
public static Vector operator -(Vector pt)
|
||||
{
|
||||
return new Vector(-pt.X, -pt.Y);
|
||||
}
|
||||
|
||||
public static Vector operator *(Vector pt, double factor)
|
||||
{
|
||||
return new Vector(pt.X * factor, pt.Y * factor);
|
||||
}
|
||||
|
||||
public static Vector operator *(double factor, Vector pt)
|
||||
{
|
||||
return new Vector(pt.X * factor, pt.Y * factor);
|
||||
}
|
||||
|
||||
public static Vector operator *(Vector pt, Vector factor)
|
||||
{
|
||||
return new Vector(pt.X * factor.X, pt.Y * factor.Y);
|
||||
}
|
||||
|
||||
public static Vector operator /(Vector pt, double divisor)
|
||||
{
|
||||
return new Vector(pt.X / divisor, pt.Y / divisor);
|
||||
}
|
||||
|
||||
public static bool operator ==(Vector pt1, Vector pt2)
|
||||
{
|
||||
return pt1.X.IsEqualTo(pt2.X) && pt1.Y.IsEqualTo(pt2.Y);
|
||||
}
|
||||
|
||||
public static bool operator !=(Vector pt1, Vector pt2)
|
||||
{
|
||||
return !(pt1 == pt2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the unit vector equivalent to this point.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Vector Normalize()
|
||||
{
|
||||
var d = DistanceTo(Vector.Zero);
|
||||
return new Vector(X / d, Y / d);
|
||||
}
|
||||
|
||||
public Vector Rotate(double angle)
|
||||
{
|
||||
var v = new Vector();
|
||||
|
||||
var cos = Math.Cos(angle);
|
||||
var sin = Math.Sin(angle);
|
||||
|
||||
v.X = X * cos - Y * sin;
|
||||
v.Y = X * sin + Y * cos;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public Vector Rotate(double angle, Vector origin)
|
||||
{
|
||||
var v = new Vector();
|
||||
var pt = this - origin;
|
||||
|
||||
var cos = Math.Cos(angle);
|
||||
var sin = Math.Sin(angle);
|
||||
|
||||
v.X = pt.X * cos - pt.Y * sin + origin.X;
|
||||
v.Y = pt.X * sin + pt.Y * cos + origin.Y;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
public Vector Offset(double x, double y)
|
||||
{
|
||||
return new Vector(X + x, Y + y);
|
||||
}
|
||||
|
||||
public Vector Offset(Vector voffset)
|
||||
{
|
||||
return this + voffset;
|
||||
}
|
||||
|
||||
public Vector Scale(double factor)
|
||||
{
|
||||
return new Vector(X * factor, Y * factor);
|
||||
}
|
||||
|
||||
public Vector Scale(double factor, Vector origin)
|
||||
{
|
||||
return (this - origin) * factor + origin;
|
||||
}
|
||||
|
||||
public Vector Clone()
|
||||
{
|
||||
return new Vector(X, Y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is Vector))
|
||||
return false;
|
||||
|
||||
var pt = (Vector)obj;
|
||||
|
||||
return (X.IsEqualTo(pt.X)) && (Y.IsEqualTo(pt.Y));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return base.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[Vector: X:{0}, Y:{1}]", X, Y);
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return !double.IsNaN(X) && !double.IsNaN(Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Source/OpenNest.Engine/BestCombination.cs
Normal file
80
Source/OpenNest.Engine/BestCombination.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Source/OpenNest.Engine/CirclePacking/Bin.cs
Normal file
30
Source/OpenNest.Engine/CirclePacking/Bin.cs
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Source/OpenNest.Engine/CirclePacking/FillEndEven.cs
Normal file
79
Source/OpenNest.Engine/CirclePacking/FillEndEven.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Source/OpenNest.Engine/CirclePacking/FillEndOdd.cs
Normal file
100
Source/OpenNest.Engine/CirclePacking/FillEndOdd.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Source/OpenNest.Engine/CirclePacking/FillEngine.cs
Normal file
17
Source/OpenNest.Engine/CirclePacking/FillEngine.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
19
Source/OpenNest.Engine/CirclePacking/Item.cs
Normal file
19
Source/OpenNest.Engine/CirclePacking/Item.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Source/OpenNest.Engine/NestDirection.cs
Normal file
9
Source/OpenNest.Engine/NestDirection.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum NestDirection
|
||||
{
|
||||
Vertical,
|
||||
Horizontal
|
||||
}
|
||||
}
|
||||
176
Source/OpenNest.Engine/NestEngine.cs
Normal file
176
Source/OpenNest.Engine/NestEngine.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Source/OpenNest.Engine/NestItem.cs
Normal file
35
Source/OpenNest.Engine/NestItem.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
70
Source/OpenNest.Engine/OpenNest.Engine.csproj
Normal file
70
Source/OpenNest.Engine/OpenNest.Engine.csproj
Normal 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>
|
||||
35
Source/OpenNest.Engine/Properties/AssemblyInfo.cs
Normal file
35
Source/OpenNest.Engine/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("OpenNest.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")]
|
||||
30
Source/OpenNest.Engine/RectanglePacking/Bin.cs
Normal file
30
Source/OpenNest.Engine/RectanglePacking/Bin.cs
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Source/OpenNest.Engine/RectanglePacking/FillBestFit.cs
Normal file
88
Source/OpenNest.Engine/RectanglePacking/FillBestFit.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Source/OpenNest.Engine/RectanglePacking/FillEngine.cs
Normal file
82
Source/OpenNest.Engine/RectanglePacking/FillEngine.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Source/OpenNest.Engine/RectanglePacking/FillNoRotation.cs
Normal file
68
Source/OpenNest.Engine/RectanglePacking/FillNoRotation.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Source/OpenNest.Engine/RectanglePacking/FillSameRotation.cs
Normal file
65
Source/OpenNest.Engine/RectanglePacking/FillSameRotation.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Source/OpenNest.Engine/RectanglePacking/Item.cs
Normal file
52
Source/OpenNest.Engine/RectanglePacking/Item.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Source/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs
Normal file
115
Source/OpenNest.Engine/RectanglePacking/PackBottomLeft.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Source/OpenNest.Engine/RectanglePacking/PackEngine.cs
Normal file
16
Source/OpenNest.Engine/RectanglePacking/PackEngine.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
34
Source/OpenNest.sln
Normal 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
|
||||
20
Source/OpenNest/Actions/Action.cs
Normal file
20
Source/OpenNest/Actions/Action.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
136
Source/OpenNest/Actions/ActionAddPart.cs
Normal file
136
Source/OpenNest/Actions/ActionAddPart.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
120
Source/OpenNest/Actions/ActionClone.cs
Normal file
120
Source/OpenNest/Actions/ActionClone.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Source/OpenNest/Actions/ActionFillArea.cs
Normal file
43
Source/OpenNest/Actions/ActionFillArea.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
252
Source/OpenNest/Actions/ActionSelect.cs
Normal file
252
Source/OpenNest/Actions/ActionSelect.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
166
Source/OpenNest/Actions/ActionSelectArea.cs
Normal file
166
Source/OpenNest/Actions/ActionSelectArea.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
155
Source/OpenNest/Actions/ActionSetSequence.cs
Normal file
155
Source/OpenNest/Actions/ActionSetSequence.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
205
Source/OpenNest/Actions/ActionZoomWindow.cs
Normal file
205
Source/OpenNest/Actions/ActionZoomWindow.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Source/OpenNest/ColorScheme.cs
Normal file
140
Source/OpenNest/ColorScheme.cs
Normal 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
|
||||
}
|
||||
}
|
||||
36
Source/OpenNest/Controls/BottomPanel.cs
Normal file
36
Source/OpenNest/Controls/BottomPanel.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
247
Source/OpenNest/Controls/DrawControl.cs
Normal file
247
Source/OpenNest/Controls/DrawControl.cs
Normal 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
Reference in New Issue
Block a user