├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── installer ├── x64 │ └── minkowski.dll └── x86 │ └── minkowski.dll └── src ├── DeepNest.sln ├── DeepNestLib ├── Background.cs ├── D3.cs ├── DeepNestLib.csproj ├── GeneticAlgorithm.cs ├── GeometryUtil.cs ├── NFP.cs ├── NestingContext.cs ├── Properties │ └── AssemblyInfo.cs ├── RawDetail.cs ├── Simplify.cs ├── SvgNest.cs └── SvgParser.cs ├── DeepNestPlugin ├── DeepNestPlugin.csproj ├── EmbeddedResources │ └── plugin-utility.ico ├── NestingOpenSourceCommand.cs ├── NestingOpenSourcePlugIn.cs ├── Panel │ ├── SampleCsDockbarCommand.cs │ ├── SampleCsWpfPanel.xaml │ └── SampleCsWpfPanel.xaml.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx └── Resources │ └── SampleCsDockBar.ico ├── MinkowskiWrapper ├── MinkowskiWrapper.cs ├── MinkowskiWrapper.csproj └── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings └── clipper ├── Properties └── AssemblyInfo.cs ├── clipper.cs └── clipper_library.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | ############################################################################### 15 | # Set the merge driver for project and solution files 16 | # 17 | # Merging from the command prompt will add diff markers to the files if there 18 | # are conflicts (Merging from VS is not affected by the settings below, in VS 19 | # the diff markers are never inserted). Diff markers may cause the following 20 | # file extensions to fail to load in VS. An alternative would be to treat 21 | # these files as binary and thus will always conflict and require user 22 | # intervention with every merge. To do so, just uncomment the entries below 23 | ############################################################################### 24 | #*.sln merge=binary 25 | #*.csproj merge=binary 26 | #*.vbproj merge=binary 27 | #*.vcxproj merge=binary 28 | #*.vcproj merge=binary 29 | #*.dbproj merge=binary 30 | #*.fsproj merge=binary 31 | #*.lsproj merge=binary 32 | #*.wixproj merge=binary 33 | #*.modelproj merge=binary 34 | #*.sqlproj merge=binary 35 | #*.wwaproj merge=binary 36 | 37 | ############################################################################### 38 | # behavior for image files 39 | # 40 | # image files are treated as binary by default. 41 | ############################################################################### 42 | #*.jpg binary 43 | #*.png binary 44 | #*.gif binary 45 | 46 | ############################################################################### 47 | # diff behavior for common document formats 48 | # 49 | # Convert binary document formats to text before diffing them. This feature 50 | # is only available from the command line. Turn it on by uncommenting the 51 | # entries below. 52 | ############################################################################### 53 | #*.doc diff=astextplain 54 | #*.DOC diff=astextplain 55 | #*.docx diff=astextplain 56 | #*.DOCX diff=astextplain 57 | #*.dot diff=astextplain 58 | #*.DOT diff=astextplain 59 | #*.pdf diff=astextplain 60 | #*.PDF diff=astextplain 61 | #*.rtf diff=astextplain 62 | #*.RTF diff=astextplain 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | bld/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | [Ll]og/ 22 | 23 | # Visual Studio 2015 cache/options directory 24 | .vs/ 25 | # Uncomment if you have tasks that create the project's static files in wwwroot 26 | #wwwroot/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | project.fragment.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | *.VC.db 83 | *.VC.VC.opendb 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | # TODO: Comment the next line if you want to checkin your web deploy settings 143 | # but database connection strings (with potential passwords) will be unencrypted 144 | #*.pubxml 145 | *.publishproj 146 | 147 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 148 | # checkin your Azure Web App publish settings, but sensitive information contained 149 | # in these scripts will be unencrypted 150 | PublishScripts/ 151 | 152 | # NuGet Packages 153 | *.nupkg 154 | # The packages folder can be ignored because of Package Restore 155 | **/packages/* 156 | # except build/, which is used as an MSBuild target. 157 | !**/packages/build/ 158 | # Uncomment if necessary however generally it will be regenerated when needed 159 | #!**/packages/repositories.config 160 | # NuGet v3's project.json files produces more ignoreable files 161 | *.nuget.props 162 | *.nuget.targets 163 | 164 | # Microsoft Azure Build Output 165 | csx/ 166 | *.build.csdef 167 | 168 | # Microsoft Azure Emulator 169 | ecf/ 170 | rcf/ 171 | 172 | # Windows Store app package directories and files 173 | AppPackages/ 174 | BundleArtifacts/ 175 | Package.StoreAssociation.xml 176 | _pkginfo.txt 177 | 178 | # Visual Studio cache files 179 | # files ending in .cache can be ignored 180 | *.[Cc]ache 181 | # but keep track of directories ending in .cache 182 | !*.[Cc]ache/ 183 | 184 | # Others 185 | ClientBin/ 186 | ~$* 187 | *~ 188 | *.dbmdl 189 | *.dbproj.schemaview 190 | *.jfm 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | # CodeRush 255 | .cr/ 256 | 257 | # Python Tools for Visual Studio (PTVS) 258 | __pycache__/ 259 | *.pyc 260 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Rafael del Molino 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeepNest for Rhino 2 | Open source nesting plugin for Rhino, based on Deepnest: https://github.com/Jack000/Deepnest 3 | 4 | Deepnest is an open source nesting application, great for laser cutters, plasma cutters, and other CNC machines. 5 | 6 | ## Automatic line merging 7 | Deepnest packs your parts into a compact area to save material and time. It automatically merges common lines so the laser doesn't cut the same path twice. 8 | This not only saves time but improves part quality by avoiding heat warping from multiple laser passes. 9 | 10 | ## Automatic line merging 11 | Deepnest employs a state of the art part layout engine with part-in-part placement and the ability to nest bitmap images for laser engraving 12 | 13 | ## Task list 14 | - [x] Integrate Deepnest library 15 | - [ ] Create a basic command to manage the nesting 16 | - [ ] Create a basic component for Grasshopper 17 | 18 | -------------------------------------------------------------------------------- /installer/x64/minkowski.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RafaeldelM/nesting/fdf5aead5a31744980a6d0f7184d1914432a5380/installer/x64/minkowski.dll -------------------------------------------------------------------------------- /installer/x86/minkowski.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RafaeldelM/nesting/fdf5aead5a31744980a6d0f7184d1914432a5380/installer/x86/minkowski.dll -------------------------------------------------------------------------------- /src/DeepNest.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.852 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepNestPlugin", "DeepNestPlugin\DeepNestPlugin.csproj", "{4801C040-147F-4273-A406-0B04FBF50F0B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clipper_library", "clipper\clipper_library.csproj", "{9B062971-A88E-4A3D-B3C9-12B78D15FA66}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinkowskiWrapper", "MinkowskiWrapper\MinkowskiWrapper.csproj", "{E09E5402-28CF-4C96-9410-1AF2A44833E0}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepNestLib", "DeepNestLib\DeepNestLib.csproj", "{32F6DD67-EF43-49E7-A6B3-4786344BAF14}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {4801C040-147F-4273-A406-0B04FBF50F0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {4801C040-147F-4273-A406-0B04FBF50F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {4801C040-147F-4273-A406-0B04FBF50F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {4801C040-147F-4273-A406-0B04FBF50F0B}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {9B062971-A88E-4A3D-B3C9-12B78D15FA66}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {E09E5402-28CF-4C96-9410-1AF2A44833E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {E09E5402-28CF-4C96-9410-1AF2A44833E0}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {E09E5402-28CF-4C96-9410-1AF2A44833E0}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {E09E5402-28CF-4C96-9410-1AF2A44833E0}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {32F6DD67-EF43-49E7-A6B3-4786344BAF14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {32F6DD67-EF43-49E7-A6B3-4786344BAF14}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {32F6DD67-EF43-49E7-A6B3-4786344BAF14}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {32F6DD67-EF43-49E7-A6B3-4786344BAF14}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {00ED0DC0-1B6B-4010-ACAE-FB86054B6A15} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /src/DeepNestLib/D3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace DeepNestLib 7 | { 8 | public class D3 9 | { 10 | 11 | // Returns the 2D cross product of AB and AC vectors, i.e., the z-component of 12 | // the 3D cross product in a quadrant I Cartesian coordinate system (+x is 13 | // right, +y is up). Returns a positive value if ABC is counter-clockwise, 14 | // negative if clockwise, and zero if the points are collinear. 15 | public static double cross(double[] a, double[] b, double[] c) 16 | { 17 | return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); 18 | } 19 | // Computes the upper convex hull per the monotone chain algorithm. 20 | // Assumes points.length >= 3, is sorted by x, unique in y. 21 | // Returns an array of indices into points in left-to-right order. 22 | public static int[] computeUpperHullIndexes(double[][] points) 23 | { 24 | Dictionary indexes = new Dictionary(); 25 | indexes.Add(0, 0); 26 | indexes.Add(1, 1); 27 | var n = points.Count(); 28 | var size = 2; 29 | 30 | for (var i = 2; i < n; ++i) 31 | { 32 | while (size > 1 && cross(points[indexes[size - 2]], points[indexes[size - 1]], points[i]) <= 0) --size; 33 | 34 | if (!indexes.ContainsKey(size)) 35 | { 36 | indexes.Add(size, -1); 37 | } 38 | indexes[size++] = i; 39 | } 40 | List ret = new List(); 41 | for (int i = 0; i < size; i++) 42 | { 43 | ret.Add(indexes[i]); 44 | } 45 | return ret.ToArray(); 46 | //return indexes.slice(0, size); // remove popped points 47 | } 48 | 49 | public class HullInfoPoint 50 | { 51 | public double x; 52 | public double y; 53 | public int index; 54 | } 55 | public static double[][] polygonHull(double[][] points) 56 | { 57 | int n; 58 | n = points.Count(); 59 | if ((n) < 3) return null; 60 | 61 | 62 | 63 | HullInfoPoint[] sortedPoints = new HullInfoPoint[n]; 64 | double[][] flippedPoints = new double[n][]; 65 | 66 | 67 | 68 | for (int i = 0; i < n; ++i) sortedPoints[i] = new HullInfoPoint { x = points[i][0], y = points[i][1], index = i }; 69 | sortedPoints = sortedPoints.OrderBy(x => x.x).ThenBy(z => z.y).ToArray(); 70 | 71 | for (int i = 0; i < n; ++i) flippedPoints[i] = new double[] { sortedPoints[i].x, -sortedPoints[i].y }; 72 | 73 | var upperIndexes = computeUpperHullIndexes(sortedPoints.Select(z => new double[] { z.x, z.y, z.index }).ToArray()); 74 | var lowerIndexes = computeUpperHullIndexes(flippedPoints); 75 | 76 | 77 | // Construct the hull polygon, removing possible duplicate endpoints. 78 | var skipLeft = lowerIndexes[0] == upperIndexes[0]; 79 | var skipRight = lowerIndexes[lowerIndexes.Length - 1] == upperIndexes[upperIndexes.Length - 1]; 80 | List hull = new List(); 81 | 82 | // Add upper hull in right-to-l order. 83 | // Then add lower hull in left-to-right order. 84 | for (int i = upperIndexes.Length - 1; i >= 0; --i) 85 | hull.Add(points[sortedPoints[upperIndexes[i]].index]); 86 | //for (int i = +skipLeft; i < lowerIndexes.Length - skipRight; ++i) hull.push(points[sortedPoints[lowerIndexes[i]][2]]); 87 | for (int i = skipLeft ? 1 : 0; i < lowerIndexes.Length - (skipRight ? 1 : 0); ++i) hull.Add(points[sortedPoints[lowerIndexes[i]].index]); 88 | 89 | return hull.ToArray(); 90 | } 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/DeepNestLib/DeepNestLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {32F6DD67-EF43-49E7-A6B3-4786344BAF14} 8 | Library 9 | Properties 10 | DeepNestLib 11 | DeepNestLib 12 | v4.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | x64 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {9b062971-a88e-4a3d-b3c9-12b78d15fa66} 61 | clipper_library 62 | 63 | 64 | {e09e5402-28cf-4c96-9410-1af2a44833e0} 65 | MinkowskiWrapper 66 | 67 | 68 | 69 | 76 | -------------------------------------------------------------------------------- /src/DeepNestLib/GeneticAlgorithm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace DeepNestLib 8 | { 9 | public class GeneticAlgorithm 10 | { 11 | SvgNestConfig Config; 12 | public List population; 13 | 14 | public static bool StrictAngles = false; 15 | float[] defaultAngles = new float[] { 16 | 0, 17 | 0, 18 | 90, 19 | 0, 20 | 0, 21 | 270, 22 | 180, 23 | 180, 24 | 180, 25 | 90 26 | 27 | }; 28 | 29 | public GeneticAlgorithm(NFP[] adam, SvgNestConfig config) 30 | { 31 | 32 | List ang2 = new List(); 33 | for (int i = 0; i < adam.Length; i++) 34 | { 35 | ang2.Add((i * 90) % 360); 36 | } 37 | defaultAngles = ang2.ToArray(); 38 | Config = config; 39 | 40 | 41 | List angles = new List(); 42 | for (int i = 0; i < adam.Length; i++) 43 | { 44 | if (StrictAngles) 45 | { 46 | angles.Add(defaultAngles[i]); 47 | } 48 | else 49 | { 50 | var angle = (float)Math.Floor(r.NextDouble() * Config.rotations) * (360f / Config.rotations); 51 | angles.Add(angle); 52 | } 53 | 54 | //angles.Add(randomAngle(adam[i])); 55 | } 56 | population = new List(); 57 | population.Add(new PopulationItem() { placements = adam.ToList(), Rotation = angles.ToArray() }); 58 | while (population.Count() < config.populationSize) 59 | { 60 | var mutant = this.mutate(population[0]); 61 | population.Add(mutant); 62 | } 63 | } 64 | 65 | 66 | public PopulationItem mutate(PopulationItem p) 67 | { 68 | var clone = new PopulationItem(); 69 | 70 | clone.placements = p.placements.ToArray().ToList(); 71 | clone.Rotation = p.Rotation.Clone() as float[]; 72 | for (int i = 0; i < clone.placements.Count(); i++) 73 | { 74 | var rand = r.NextDouble(); 75 | if (rand < 0.01 * Config.mutationRate) 76 | { 77 | var j = i + 1; 78 | if (j < clone.placements.Count) 79 | { 80 | var temp = clone.placements[i]; 81 | clone.placements[i] = clone.placements[j]; 82 | clone.placements[j] = temp; 83 | } 84 | } 85 | rand = r.NextDouble(); 86 | if (rand < 0.01 * Config.mutationRate) 87 | { 88 | clone.Rotation[i] = (float)Math.Floor(r.NextDouble() * Config.rotations) * (360f / Config.rotations); 89 | } 90 | } 91 | 92 | 93 | return clone; 94 | } 95 | Random r = new Random(); 96 | public float[] shuffleArray(float[] array) 97 | { 98 | for (var i = array.Length - 1; i > 0; i--) 99 | { 100 | var j = (int)Math.Floor(r.NextDouble() * (i + 1)); 101 | var temp = array[i]; 102 | array[i] = array[j]; 103 | array[j] = temp; 104 | } 105 | return array; 106 | } 107 | 108 | 109 | // returns a random individual from the population, weighted to the front of the list (lower fitness value is more likely to be selected) 110 | public PopulationItem randomWeightedIndividual(PopulationItem exclude = null) 111 | { 112 | //var pop = this.population.slice(0); 113 | var pop = this.population.ToArray(); 114 | 115 | if (exclude != null && Array.IndexOf(pop, exclude) >= 0) 116 | { 117 | pop.splice(Array.IndexOf(pop, exclude), 1); 118 | } 119 | 120 | var rand = r.NextDouble(); 121 | 122 | float lower = 0; 123 | var weight = 1 / (float)pop.Length; 124 | float upper = weight; 125 | 126 | for (var i = 0; i < pop.Length; i++) 127 | { 128 | // if the random number falls between lower and upper bounds, select this individual 129 | if (rand > lower && rand < upper) 130 | { 131 | return pop[i]; 132 | } 133 | lower = upper; 134 | upper += 2 * weight * ((pop.Length - i) / (float)pop.Length); 135 | } 136 | 137 | return pop[0]; 138 | } 139 | 140 | // single point crossover 141 | public PopulationItem[] mate(PopulationItem male, PopulationItem female) 142 | { 143 | var cutpoint = (int)Math.Round(Math.Min(Math.Max(r.NextDouble(), 0.1), 0.9) * (male.placements.Count - 1)); 144 | 145 | var gene1 = new List(male.placements.Take(cutpoint).ToArray()); 146 | var rot1 = new List(male.Rotation.Take(cutpoint).ToArray()); 147 | 148 | var gene2 = new List(female.placements.Take(cutpoint).ToArray()); 149 | var rot2 = new List(female.Rotation.Take(cutpoint).ToArray()); 150 | 151 | int i = 0; 152 | 153 | for (i = 0; i < female.placements.Count; i++) 154 | { 155 | if (!gene1.Any(z => z.id == female.placements[i].id)) 156 | { 157 | gene1.Add(female.placements[i]); 158 | rot1.Add(female.Rotation[i]); 159 | } 160 | } 161 | 162 | for (i = 0; i < male.placements.Count; i++) 163 | { 164 | if (!gene2.Any(z => z.id == male.placements[i].id)) 165 | { 166 | gene2.Add(male.placements[i]); 167 | rot2.Add(male.Rotation[i]); 168 | } 169 | } 170 | 171 | 172 | 173 | 174 | return new[] {new PopulationItem() { 175 | placements= gene1, Rotation= rot1.ToArray()}, 176 | new PopulationItem(){ placements= gene2, Rotation= rot2.ToArray()}}; 177 | } 178 | 179 | public void generation() 180 | { 181 | // Individuals with higher fitness are more likely to be selected for mating 182 | population = population.OrderBy(z => z.fitness).ToList(); 183 | 184 | // fittest individual is preserved in the new generation (elitism) 185 | 186 | List newpopulation = new List(); 187 | newpopulation.Add(this.population[0]); 188 | while (newpopulation.Count() < this.population.Count) 189 | { 190 | var male = randomWeightedIndividual(); 191 | var female = randomWeightedIndividual(male); 192 | 193 | // each mating produces two children 194 | var children = mate(male, female); 195 | 196 | // slightly mutate children 197 | newpopulation.Add(this.mutate(children[0])); 198 | 199 | if (newpopulation.Count < this.population.Count) 200 | { 201 | newpopulation.Add(this.mutate(children[1])); 202 | } 203 | } 204 | 205 | this.population = newpopulation; 206 | } 207 | } 208 | 209 | 210 | } 211 | -------------------------------------------------------------------------------- /src/DeepNestLib/GeometryUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | 6 | namespace DeepNestLib 7 | { 8 | public class GeometryUtil 9 | { 10 | // returns true if points are within the given distance 11 | public static bool _withinDistance(SvgPoint p1, SvgPoint p2, double distance) 12 | { 13 | var dx = p1.x - p2.x; 14 | var dy = p1.y - p2.y; 15 | return ((dx * dx + dy * dy) < distance * distance); 16 | } 17 | 18 | // returns an interior NFP for the special case where A is a rectangle 19 | public static NFP[] noFitPolygonRectangle(NFP A, NFP B) 20 | { 21 | var minAx = A[0].x; 22 | var minAy = A[0].y; 23 | var maxAx = A[0].x; 24 | var maxAy = A[0].y; 25 | 26 | for (var i = 1; i < A.Length; i++) 27 | { 28 | if (A[i].x < minAx) 29 | { 30 | minAx = A[i].x; 31 | } 32 | if (A[i].y < minAy) 33 | { 34 | minAy = A[i].y; 35 | } 36 | if (A[i].x > maxAx) 37 | { 38 | maxAx = A[i].x; 39 | } 40 | if (A[i].y > maxAy) 41 | { 42 | maxAy = A[i].y; 43 | } 44 | } 45 | 46 | var minBx = B[0].x; 47 | var minBy = B[0].y; 48 | var maxBx = B[0].x; 49 | var maxBy = B[0].y; 50 | for (int i = 1; i < B.Length; i++) 51 | { 52 | if (B[i].x < minBx) 53 | { 54 | minBx = B[i].x; 55 | } 56 | if (B[i].y < minBy) 57 | { 58 | minBy = B[i].y; 59 | } 60 | if (B[i].x > maxBx) 61 | { 62 | maxBx = B[i].x; 63 | } 64 | if (B[i].y > maxBy) 65 | { 66 | maxBy = B[i].y; 67 | } 68 | } 69 | 70 | if (maxBx - minBx > maxAx - minAx) 71 | { 72 | return null; 73 | } 74 | if (maxBy - minBy > maxAy - minAy) 75 | { 76 | return null; 77 | } 78 | 79 | 80 | var pnts = new NFP[] { new NFP() { Points=new SvgPoint[]{ 81 | 82 | new SvgPoint(minAx - minBx + B[0].x, minAy - minBy + B[0].y), 83 | new SvgPoint(maxAx - maxBx + B[0].x, minAy - minBy + B[0].y), 84 | new SvgPoint( maxAx - maxBx + B[0].x, maxAy - maxBy + B[0].y), 85 | new SvgPoint( minAx - minBx + B[0].x, maxAy - maxBy + B[0].y) 86 | } } }; 87 | return pnts; 88 | } 89 | 90 | 91 | // returns the rectangular bounding box of the given polygon 92 | public static PolygonBounds getPolygonBounds(NFP _polygon) 93 | { 94 | return getPolygonBounds(_polygon.Points); 95 | } 96 | public static PolygonBounds getPolygonBounds(List polygon) 98 | { 99 | return getPolygonBounds(polygon.ToArray()); 100 | } 101 | public static PolygonBounds getPolygonBounds(SvgPoint[] polygon) 102 | { 103 | 104 | if (polygon == null || polygon.Count() < 3) 105 | { 106 | throw new ArgumentException("null"); 107 | } 108 | 109 | var xmin = polygon[0].x; 110 | var xmax = polygon[0].x; 111 | var ymin = polygon[0].y; 112 | var ymax = polygon[0].y; 113 | 114 | for (var i = 1; i < polygon.Length; i++) 115 | { 116 | if (polygon[i].x > xmax) 117 | { 118 | xmax = polygon[i].x; 119 | } 120 | else if (polygon[i].x < xmin) 121 | { 122 | xmin = polygon[i].x; 123 | } 124 | 125 | if (polygon[i].y > ymax) 126 | { 127 | ymax = polygon[i].y; 128 | } 129 | else if (polygon[i].y < ymin) 130 | { 131 | ymin = polygon[i].y; 132 | } 133 | } 134 | 135 | var w = xmax - xmin; 136 | var h = ymax - ymin; 137 | //return new rectanglef(xmin, ymin, xmax - xmin, ymax - ymin); 138 | return new PolygonBounds(xmin, ymin, w, h); 139 | 140 | 141 | } 142 | 143 | public static bool isRectangle(NFP poly, double? tolerance = null) 144 | { 145 | var bb = getPolygonBounds(poly); 146 | if (tolerance == null) 147 | { 148 | tolerance = TOL; 149 | } 150 | 151 | 152 | for (var i = 0; i < poly.Points.Length; i++) 153 | { 154 | if (!_almostEqual(poly.Points[i].x, bb.x) && !_almostEqual(poly.Points[i].x, bb.x + bb.width)) 155 | { 156 | return false; 157 | } 158 | if (!_almostEqual(poly.Points[i].y, bb.y) && !_almostEqual(poly.Points[i].y, bb.y + bb.height)) 159 | { 160 | return false; 161 | } 162 | } 163 | 164 | return true; 165 | } 166 | 167 | public static PolygonWithBounds rotatePolygon(NFP polygon, float angle) 168 | { 169 | 170 | List rotated = new List(); 171 | angle = (float)(angle * Math.PI / 180.0f); 172 | for (var i = 0; i < polygon.Points.Length; i++) 173 | { 174 | var x = polygon.Points[i].x; 175 | var y = polygon.Points[i].y; 176 | var x1 = (float)(x * Math.Cos(angle) - y * Math.Sin(angle)); 177 | var y1 = (float)(x * Math.Sin(angle) + y * Math.Cos(angle)); 178 | 179 | rotated.Add(new SvgPoint(x1, y1)); 180 | } 181 | // reset bounding box 182 | RectangleF rr = new RectangleF(); 183 | 184 | var ret = new PolygonWithBounds() 185 | { 186 | Points = rotated.ToArray() 187 | }; 188 | var bounds = GeometryUtil.getPolygonBounds(ret); 189 | ret.x = bounds.x; 190 | ret.y = bounds.y; 191 | ret.width = bounds.width; 192 | ret.height = bounds.height; 193 | return ret; 194 | 195 | } 196 | 197 | public class PolygonWithBounds : NFP 198 | { 199 | public double x; 200 | public double y; 201 | public double width; 202 | public double height; 203 | } 204 | public static bool _almostEqual(double a, double b, double? tolerance = null) 205 | { 206 | if (tolerance == null) 207 | { 208 | tolerance = TOL; 209 | } 210 | return Math.Abs(a - b) < tolerance; 211 | } 212 | public static bool _almostEqual(double? a, double? b, double? tolerance = null) 213 | { 214 | return _almostEqual(a.Value, b.Value, tolerance); 215 | } 216 | // returns true if point already exists in the given nfp 217 | public static bool inNfp(SvgPoint p, NFP[] nfp) 218 | { 219 | if (nfp == null || nfp.Length == 0) 220 | { 221 | return false; 222 | } 223 | 224 | for (var i = 0; i < nfp.Length; i++) 225 | { 226 | for (var j = 0; j < nfp[i].length; j++) 227 | { 228 | if (_almostEqual(p.x, nfp[i][j].x) && _almostEqual(p.y, nfp[i][j].y)) 229 | { 230 | return true; 231 | } 232 | } 233 | } 234 | 235 | return false; 236 | } 237 | // normalize vector into a unit vector 238 | public static SvgPoint _normalizeVector(SvgPoint v) 239 | { 240 | if (_almostEqual(v.x * v.x + v.y * v.y, 1)) 241 | { 242 | return v; // given vector was already a unit vector 243 | } 244 | var len = Math.Sqrt(v.x * v.x + v.y * v.y); 245 | var inverse = (float)(1 / len); 246 | 247 | return new SvgPoint(v.x * inverse, v.y * inverse 248 | ); 249 | } 250 | public static double? pointDistance(SvgPoint p, SvgPoint s1, SvgPoint s2, SvgPoint normal, bool infinite = false) 251 | { 252 | normal = _normalizeVector(normal); 253 | 254 | var dir = new SvgPoint(normal.y, -normal.x); 255 | 256 | var pdot = p.x * dir.x + p.y * dir.y; 257 | var s1dot = s1.x * dir.x + s1.y * dir.y; 258 | var s2dot = s2.x * dir.x + s2.y * dir.y; 259 | 260 | var pdotnorm = p.x * normal.x + p.y * normal.y; 261 | var s1dotnorm = s1.x * normal.x + s1.y * normal.y; 262 | var s2dotnorm = s2.x * normal.x + s2.y * normal.y; 263 | 264 | if (!infinite) 265 | { 266 | if (((pdot < s1dot || _almostEqual(pdot, s1dot)) && (pdot < s2dot || _almostEqual(pdot, s2dot))) || ((pdot > s1dot || _almostEqual(pdot, s1dot)) && (pdot > s2dot || _almostEqual(pdot, s2dot)))) 267 | { 268 | return null; // dot doesn't collide with segment, or lies directly on the vertex 269 | } 270 | if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm > s1dotnorm && pdotnorm > s2dotnorm)) 271 | { 272 | return Math.Min(pdotnorm - s1dotnorm, pdotnorm - s2dotnorm); 273 | } 274 | if ((_almostEqual(pdot, s1dot) && _almostEqual(pdot, s2dot)) && (pdotnorm < s1dotnorm && pdotnorm < s2dotnorm)) 275 | { 276 | return -Math.Min(s1dotnorm - pdotnorm, s2dotnorm - pdotnorm); 277 | } 278 | } 279 | 280 | return -(pdotnorm - s1dotnorm + (s1dotnorm - s2dotnorm) * (s1dot - pdot) / (s1dot - s2dot)); 281 | } 282 | static double TOL = (float)Math.Pow(10, -9); // Floating point error is likely to be above 1 epsilon 283 | // returns true if p lies on the line segment defined by AB, but not at any endpoints 284 | // may need work! 285 | public static bool _onSegment(SvgPoint A, SvgPoint B, SvgPoint p) 286 | { 287 | 288 | // vertical line 289 | if (_almostEqual(A.x, B.x) && _almostEqual(p.x, A.x)) 290 | { 291 | if (!_almostEqual(p.y, B.y) && !_almostEqual(p.y, A.y) && p.y < Math.Max(B.y, A.y) && p.y > Math.Min(B.y, A.y)) 292 | { 293 | return true; 294 | } 295 | else 296 | { 297 | return false; 298 | } 299 | } 300 | 301 | // horizontal line 302 | if (_almostEqual(A.y, B.y) && _almostEqual(p.y, A.y)) 303 | { 304 | if (!_almostEqual(p.x, B.x) && !_almostEqual(p.x, A.x) && p.x < Math.Max(B.x, A.x) && p.x > Math.Min(B.x, A.x)) 305 | { 306 | return true; 307 | } 308 | else 309 | { 310 | return false; 311 | } 312 | } 313 | 314 | //range check 315 | if ((p.x < A.x && p.x < B.x) || (p.x > A.x && p.x > B.x) || (p.y < A.y && p.y < B.y) || (p.y > A.y && p.y > B.y)) 316 | { 317 | return false; 318 | } 319 | 320 | 321 | // exclude end points 322 | if ((_almostEqual(p.x, A.x) && _almostEqual(p.y, A.y)) || (_almostEqual(p.x, B.x) && _almostEqual(p.y, B.y))) 323 | { 324 | return false; 325 | } 326 | 327 | var cross = (p.y - A.y) * (B.x - A.x) - (p.x - A.x) * (B.y - A.y); 328 | 329 | if (Math.Abs(cross) > TOL) 330 | { 331 | return false; 332 | } 333 | 334 | var dot = (p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y); 335 | 336 | 337 | 338 | if (dot < 0 || _almostEqual(dot, 0)) 339 | { 340 | return false; 341 | } 342 | 343 | var len2 = (B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y); 344 | 345 | 346 | 347 | if (dot > len2 || _almostEqual(dot, len2)) 348 | { 349 | return false; 350 | } 351 | 352 | return true; 353 | } 354 | 355 | 356 | // project each point of B onto A in the given direction, and return the 357 | public static double? polygonProjectionDistance(NFP A, NFP B, SvgPoint direction) 358 | { 359 | var Boffsetx = B.offsetx ?? 0; 360 | var Boffsety = B.offsety ?? 0; 361 | 362 | var Aoffsetx = A.offsetx ?? 0; 363 | var Aoffsety = A.offsety ?? 0; 364 | 365 | A = A.slice(0); 366 | B = B.slice(0); 367 | 368 | // close the loop for polygons 369 | if (A[0] != A[A.length - 1]) 370 | { 371 | A.push(A[0]); 372 | } 373 | 374 | if (B[0] != B[B.length - 1]) 375 | { 376 | B.push(B[0]); 377 | } 378 | 379 | var edgeA = A; 380 | var edgeB = B; 381 | 382 | double? distance = null; 383 | SvgPoint p, s1, s2; 384 | double? d; 385 | 386 | 387 | for (var i = 0; i < edgeB.length; i++) 388 | { 389 | // the shortest/most negative projection of B onto A 390 | double? minprojection = null; 391 | SvgPoint minp = null; 392 | for (var j = 0; j < edgeA.length - 1; j++) 393 | { 394 | p = new SvgPoint(edgeB[i].x + Boffsetx, edgeB[i].y + Boffsety); 395 | s1 = new SvgPoint(edgeA[j].x + Aoffsetx, edgeA[j].y + Aoffsety); 396 | s2 = new SvgPoint(edgeA[j + 1].x + Aoffsetx, edgeA[j + 1].y + Aoffsety); 397 | 398 | if (Math.Abs((s2.y - s1.y) * direction.x - (s2.x - s1.x) * direction.y) < TOL) 399 | { 400 | continue; 401 | } 402 | 403 | // project point, ignore edge boundaries 404 | d = pointDistance(p, s1, s2, direction); 405 | 406 | if (d != null && (minprojection == null || d < minprojection)) 407 | { 408 | minprojection = d; 409 | minp = p; 410 | } 411 | } 412 | if (minprojection != null && (distance == null || minprojection > distance)) 413 | { 414 | distance = minprojection; 415 | } 416 | } 417 | 418 | return distance; 419 | } 420 | 421 | public static double polygonArea(NFP polygon) 422 | { 423 | double area = 0; 424 | int i, j; 425 | for (i = 0, j = polygon.Points.Length - 1; i < polygon.Points.Length; j = i++) 426 | { 427 | area += (polygon.Points[j].x + polygon.Points[i].x) * (polygon.Points[j].y 428 | - polygon.Points[i].y); 429 | } 430 | return 0.5f * area; 431 | } 432 | 433 | // return true if point is in the polygon, false if outside, and null if exactly on a point or edge 434 | public static bool? pointInPolygon(SvgPoint point, NFP polygon) 435 | { 436 | if (polygon == null || polygon.Points.Length < 3) 437 | { 438 | throw new ArgumentException(); 439 | } 440 | 441 | var inside = false; 442 | //var offsetx = polygon.offsetx || 0; 443 | //var offsety = polygon.offsety || 0; 444 | double offsetx = polygon.offsetx == null ? 0 : polygon.offsetx.Value; 445 | double offsety = polygon.offsety == null ? 0 : polygon.offsety.Value; 446 | 447 | int i, j; 448 | for (i = 0, j = polygon.Points.Count() - 1; i < polygon.Points.Length; j = i++) 449 | { 450 | var xi = polygon.Points[i].x + offsetx; 451 | var yi = polygon.Points[i].y + offsety; 452 | var xj = polygon.Points[j].x + offsetx; 453 | var yj = polygon.Points[j].y + offsety; 454 | 455 | if (_almostEqual(xi, point.x) && _almostEqual(yi, point.y)) 456 | { 457 | 458 | return null; // no result 459 | } 460 | 461 | if (_onSegment(new SvgPoint(xi, yi), new SvgPoint(xj, yj), point)) 462 | { 463 | return null; // exactly on the segment 464 | } 465 | 466 | if (_almostEqual(xi, xj) && _almostEqual(yi, yj)) 467 | { // ignore very small lines 468 | continue; 469 | } 470 | 471 | var intersect = ((yi > point.y) != (yj > point.y)) && (point.x < (xj - xi) * (point.y - yi) / (yj - yi) + xi); 472 | if (intersect) inside = !inside; 473 | } 474 | 475 | return inside; 476 | } 477 | // todo: swap this for a more efficient sweep-line implementation 478 | // returnEdges: if set, return all edges on A that have intersections 479 | 480 | public static bool intersect(NFP A, NFP B) 481 | { 482 | var Aoffsetx = A.offsetx ?? 0; 483 | var Aoffsety = A.offsety ?? 0; 484 | 485 | var Boffsetx = B.offsetx ?? 0; 486 | var Boffsety = B.offsety ?? 0; 487 | 488 | A = A.slice(0); 489 | B = B.slice(0); 490 | 491 | for (var i = 0; i < A.length - 1; i++) 492 | { 493 | for (var j = 0; j < B.length - 1; j++) 494 | { 495 | var a1 = new SvgPoint(A[i].x + Aoffsetx, A[i].y + Aoffsety); 496 | var a2 = new SvgPoint(A[i + 1].x + Aoffsetx, A[i + 1].y + Aoffsety); 497 | var b1 = new SvgPoint(B[j].x + Boffsetx, B[j].y + Boffsety); 498 | var b2 = new SvgPoint(B[j + 1].x + Boffsetx, B[j + 1].y + Boffsety); 499 | 500 | var prevbindex = (j == 0) ? B.length - 1 : j - 1; 501 | var prevaindex = (i == 0) ? A.length - 1 : i - 1; 502 | var nextbindex = (j + 1 == B.length - 1) ? 0 : j + 2; 503 | var nextaindex = (i + 1 == A.length - 1) ? 0 : i + 2; 504 | 505 | // go even further back if we happen to hit on a loop end point 506 | if (B[prevbindex] == B[j] || (_almostEqual(B[prevbindex].x, B[j].x) && _almostEqual(B[prevbindex].y, B[j].y))) 507 | { 508 | prevbindex = (prevbindex == 0) ? B.length - 1 : prevbindex - 1; 509 | } 510 | 511 | if (A[prevaindex] == A[i] || (_almostEqual(A[prevaindex].x, A[i].x) && _almostEqual(A[prevaindex].y, A[i].y))) 512 | { 513 | prevaindex = (prevaindex == 0) ? A.length - 1 : prevaindex - 1; 514 | } 515 | 516 | // go even further forward if we happen to hit on a loop end point 517 | if (B[nextbindex] == B[j + 1] || (_almostEqual(B[nextbindex].x, B[j + 1].x) && _almostEqual(B[nextbindex].y, B[j + 1].y))) 518 | { 519 | nextbindex = (nextbindex == B.length - 1) ? 0 : nextbindex + 1; 520 | } 521 | 522 | if (A[nextaindex] == A[i + 1] || (_almostEqual(A[nextaindex].x, A[i + 1].x) && _almostEqual(A[nextaindex].y, A[i + 1].y))) 523 | { 524 | nextaindex = (nextaindex == A.length - 1) ? 0 : nextaindex + 1; 525 | } 526 | 527 | var a0 = new SvgPoint(A[prevaindex].x + Aoffsetx, A[prevaindex].y + Aoffsety); 528 | var b0 = new SvgPoint(B[prevbindex].x + Boffsetx, B[prevbindex].y + Boffsety); 529 | 530 | var a3 = new SvgPoint(A[nextaindex].x + Aoffsetx, A[nextaindex].y + Aoffsety); 531 | var b3 = new SvgPoint(B[nextbindex].x + Boffsetx, B[nextbindex].y + Boffsety); 532 | 533 | if (_onSegment(a1, a2, b1) || (_almostEqual(a1.x, b1.x) && _almostEqual(a1.y, b1.y))) 534 | { 535 | // if a point is on a segment, it could intersect or it could not. Check via the neighboring points 536 | var b0in = pointInPolygon(b0, A); 537 | var b2in = pointInPolygon(b2, A); 538 | if ((b0in == true && b2in == false) || (b0in == false && b2in == true)) 539 | { 540 | return true; 541 | } 542 | else 543 | { 544 | continue; 545 | } 546 | } 547 | 548 | if (_onSegment(a1, a2, b2) || (_almostEqual(a2.x, b2.x) && _almostEqual(a2.y, b2.y))) 549 | { 550 | // if a point is on a segment, it could intersect or it could not. Check via the neighboring points 551 | var b1in = pointInPolygon(b1, A); 552 | var b3in = pointInPolygon(b3, A); 553 | 554 | if ((b1in == true && b3in == false) || (b1in == false && b3in == true)) 555 | { 556 | return true; 557 | } 558 | else 559 | { 560 | continue; 561 | } 562 | } 563 | 564 | if (_onSegment(b1, b2, a1) || (_almostEqual(a1.x, b2.x) && _almostEqual(a1.y, b2.y))) 565 | { 566 | // if a point is on a segment, it could intersect or it could not. Check via the neighboring points 567 | var a0in = pointInPolygon(a0, B); 568 | var a2in = pointInPolygon(a2, B); 569 | 570 | if ((a0in == true && a2in == false) || (a0in == false && a2in == true)) 571 | { 572 | return true; 573 | } 574 | else 575 | { 576 | continue; 577 | } 578 | } 579 | 580 | if (_onSegment(b1, b2, a2) || (_almostEqual(a2.x, b1.x) && _almostEqual(a2.y, b1.y))) 581 | { 582 | // if a point is on a segment, it could intersect or it could not. Check via the neighboring points 583 | var a1in = pointInPolygon(a1, B); 584 | var a3in = pointInPolygon(a3, B); 585 | 586 | if ((a1in == true && a3in == false) || (a1in == false && a3in == true)) 587 | { 588 | return true; 589 | } 590 | else 591 | { 592 | continue; 593 | } 594 | } 595 | 596 | var p = _lineIntersect(b1, b2, a1, a2); 597 | 598 | if (p != null) 599 | { 600 | return true; 601 | } 602 | } 603 | } 604 | 605 | return false; 606 | } 607 | 608 | public static bool isFinite(object obj) 609 | { 610 | return true; 611 | } 612 | // returns the intersection of AB and EF 613 | // or null if there are no intersections or other numerical error 614 | // if the infinite flag is set, AE and EF describe infinite lines without endpoints, they are finite line segments otherwise 615 | public static SvgPoint _lineIntersect(SvgPoint A, SvgPoint B, SvgPoint E, SvgPoint F, bool infinite = false) 616 | { 617 | double a1, a2, b1, b2, c1, c2, x, y; 618 | 619 | a1 = B.y - A.y; 620 | b1 = A.x - B.x; 621 | c1 = B.x * A.y - A.x * B.y; 622 | a2 = F.y - E.y; 623 | b2 = E.x - F.x; 624 | c2 = F.x * E.y - E.x * F.y; 625 | 626 | var denom = a1 * b2 - a2 * b1; 627 | 628 | x = (b1 * c2 - b2 * c1) / denom; 629 | y = (a2 * c1 - a1 * c2) / denom; 630 | 631 | 632 | if (!isFinite(x) || !isFinite(y)) 633 | { 634 | return null; 635 | } 636 | 637 | // lines are colinear 638 | /*var crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y); 639 | var crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y); 640 | if(_almostEqual(crossABE,0) && _almostEqual(crossABF,0)){ 641 | return null; 642 | }*/ 643 | 644 | if (!infinite) 645 | { 646 | // coincident points do not count as intersecting 647 | if (Math.Abs(A.x - B.x) > TOL && ((A.x < B.x) ? x < A.x || x > B.x : x > A.x || x < B.x)) return null; 648 | if (Math.Abs(A.y - B.y) > TOL && ((A.y < B.y) ? y < A.y || y > B.y : y > A.y || y < B.y)) return null; 649 | 650 | if (Math.Abs(E.x - F.x) > TOL && ((E.x < F.x) ? x < E.x || x > F.x : x > E.x || x < F.x)) return null; 651 | if (Math.Abs(E.y - F.y) > TOL && ((E.y < F.y) ? y < E.y || y > F.y : y > E.y || y < F.y)) return null; 652 | } 653 | 654 | return new SvgPoint(x, y); 655 | } 656 | 657 | // searches for an arrangement of A and B such that they do not overlap 658 | // if an NFP is given, only search for startpoints that have not already been traversed in the given NFP 659 | 660 | public static SvgPoint searchStartPoint(NFP A, NFP B, bool inside, NFP[] NFP = null) 661 | { 662 | // clone arrays 663 | A = A.slice(0); 664 | B = B.slice(0); 665 | 666 | // close the loop for polygons 667 | if (A[0] != A[A.length - 1]) 668 | { 669 | A.push(A[0]); 670 | } 671 | 672 | if (B[0] != B[B.length - 1]) 673 | { 674 | B.push(B[0]); 675 | } 676 | 677 | for (var i = 0; i < A.length - 1; i++) 678 | { 679 | if (!A[i].marked) 680 | { 681 | A[i].marked = true; 682 | for (var j = 0; j < B.length; j++) 683 | { 684 | B.offsetx = A[i].x - B[j].x; 685 | B.offsety = A[i].y - B[j].y; 686 | 687 | bool? Binside = null; 688 | for (var k = 0; k < B.length; k++) 689 | { 690 | var inpoly = pointInPolygon(new SvgPoint(B[k].x + B.offsetx.Value, 691 | B[k].y + B.offsety.Value), A); 692 | if (inpoly != null) 693 | { 694 | Binside = inpoly; 695 | break; 696 | } 697 | } 698 | 699 | if (Binside == null) 700 | { // A and B are the same 701 | return null; 702 | } 703 | 704 | var startPoint = new SvgPoint(B.offsetx.Value, B.offsety.Value); 705 | if (((Binside.Value && inside) || (!Binside.Value && !inside)) && 706 | !intersect(A, B) && !inNfp(startPoint, NFP)) 707 | { 708 | return startPoint; 709 | } 710 | 711 | // slide B along vector 712 | var vx = A[i + 1].x - A[i].x; 713 | var vy = A[i + 1].y - A[i].y; 714 | 715 | var d1 = polygonProjectionDistance(A, B, new SvgPoint(vx, vy)); 716 | var d2 = polygonProjectionDistance(B, A, new SvgPoint(-vx, -vy)); 717 | 718 | double? d = null; 719 | 720 | // todo: clean this up 721 | if (d1 == null && d2 == null) 722 | { 723 | // nothin 724 | } 725 | else if (d1 == null) 726 | { 727 | d = d2; 728 | } 729 | else if (d2 == null) 730 | { 731 | d = d1; 732 | } 733 | else 734 | { 735 | d = Math.Min(d1.Value, d2.Value); 736 | } 737 | 738 | // only slide until no longer negative 739 | // todo: clean this up 740 | if (d != null && !_almostEqual(d, 0) && d > 0) 741 | { 742 | 743 | } 744 | else 745 | { 746 | continue; 747 | } 748 | 749 | var vd2 = vx * vx + vy * vy; 750 | 751 | if (d * d < vd2 && !_almostEqual(d * d, vd2)) 752 | { 753 | var vd = (float)Math.Sqrt(vx * vx + vy * vy); 754 | vx *= d.Value / vd; 755 | vy *= d.Value / vd; 756 | } 757 | 758 | B.offsetx += vx; 759 | B.offsety += vy; 760 | 761 | for (var k = 0; k < B.length; k++) 762 | { 763 | var inpoly = pointInPolygon( 764 | new SvgPoint( 765 | B[k].x + B.offsetx.Value, B[k].y + B.offsety.Value), A); 766 | if (inpoly != null) 767 | { 768 | Binside = inpoly; 769 | break; 770 | } 771 | } 772 | startPoint = 773 | new SvgPoint(B.offsetx.Value, B.offsety.Value); 774 | if (((Binside.Value && inside) || (!Binside.Value && !inside)) && 775 | !intersect(A, B) && !inNfp(startPoint, NFP)) 776 | { 777 | return startPoint; 778 | } 779 | } 780 | } 781 | } 782 | 783 | 784 | 785 | return null; 786 | } 787 | 788 | public class TouchingItem 789 | { 790 | public TouchingItem(int _type, int _a, int _b) 791 | { 792 | A = _a; 793 | B = _b; 794 | type = _type; 795 | } 796 | public int A; 797 | public int B; 798 | public int type; 799 | 800 | } 801 | 802 | public static double? segmentDistance(SvgPoint A, SvgPoint B, SvgPoint E, SvgPoint F, SvgPoint direction) 803 | { 804 | var normal = new SvgPoint( 805 | direction.y, 806 | -direction.x 807 | 808 | ); 809 | 810 | var reverse = new SvgPoint( 811 | -direction.x, 812 | -direction.y 813 | ); 814 | 815 | var dotA = A.x * normal.x + A.y * normal.y; 816 | var dotB = B.x * normal.x + B.y * normal.y; 817 | var dotE = E.x * normal.x + E.y * normal.y; 818 | var dotF = F.x * normal.x + F.y * normal.y; 819 | 820 | var crossA = A.x * direction.x + A.y * direction.y; 821 | var crossB = B.x * direction.x + B.y * direction.y; 822 | var crossE = E.x * direction.x + E.y * direction.y; 823 | var crossF = F.x * direction.x + F.y * direction.y; 824 | 825 | var crossABmin = Math.Min(crossA, crossB); 826 | var crossABmax = Math.Max(crossA, crossB); 827 | 828 | var crossEFmax = Math.Max(crossE, crossF); 829 | var crossEFmin = Math.Min(crossE, crossF); 830 | 831 | var ABmin = Math.Min(dotA, dotB); 832 | var ABmax = Math.Max(dotA, dotB); 833 | 834 | var EFmax = Math.Max(dotE, dotF); 835 | var EFmin = Math.Min(dotE, dotF); 836 | 837 | // segments that will merely touch at one point 838 | if (_almostEqual(ABmax, EFmin, TOL) || _almostEqual(ABmin, EFmax, TOL)) 839 | { 840 | return null; 841 | } 842 | // segments miss eachother completely 843 | if (ABmax < EFmin || ABmin > EFmax) 844 | { 845 | return null; 846 | } 847 | 848 | double overlap; 849 | 850 | if ((ABmax > EFmax && ABmin < EFmin) || (EFmax > ABmax && EFmin < ABmin)) 851 | { 852 | overlap = 1; 853 | } 854 | else 855 | { 856 | var minMax = Math.Min(ABmax, EFmax); 857 | var maxMin = Math.Max(ABmin, EFmin); 858 | 859 | var maxMax = Math.Max(ABmax, EFmax); 860 | var minMin = Math.Min(ABmin, EFmin); 861 | 862 | overlap = (minMax - maxMin) / (maxMax - minMin); 863 | } 864 | 865 | var crossABE = (E.y - A.y) * (B.x - A.x) - (E.x - A.x) * (B.y - A.y); 866 | var crossABF = (F.y - A.y) * (B.x - A.x) - (F.x - A.x) * (B.y - A.y); 867 | 868 | // lines are colinear 869 | if (_almostEqual(crossABE, 0) && _almostEqual(crossABF, 0)) 870 | { 871 | 872 | var ABnorm = new SvgPoint(B.y - A.y, A.x - B.x); 873 | var EFnorm = new SvgPoint(F.y - E.y, E.x - F.x); 874 | 875 | var ABnormlength = (float)Math.Sqrt(ABnorm.x * ABnorm.x + ABnorm.y * ABnorm.y); 876 | ABnorm.x /= ABnormlength; 877 | ABnorm.y /= ABnormlength; 878 | 879 | var EFnormlength = (float)Math.Sqrt(EFnorm.x * EFnorm.x + EFnorm.y * EFnorm.y); 880 | EFnorm.x /= EFnormlength; 881 | EFnorm.y /= EFnormlength; 882 | 883 | // segment normals must point in opposite directions 884 | if (Math.Abs(ABnorm.y * EFnorm.x - ABnorm.x * EFnorm.y) < TOL && ABnorm.y * EFnorm.y + ABnorm.x * EFnorm.x < 0) 885 | { 886 | // normal of AB segment must point in same direction as given direction vector 887 | var normdot = ABnorm.y * direction.y + ABnorm.x * direction.x; 888 | // the segments merely slide along eachother 889 | if (_almostEqual(normdot, 0, TOL)) 890 | { 891 | return null; 892 | } 893 | if (normdot < 0) 894 | { 895 | return 0; 896 | } 897 | } 898 | return null; 899 | } 900 | 901 | var distances = new List(); 902 | 903 | // coincident points 904 | if (_almostEqual(dotA, dotE)) 905 | { 906 | distances.Add(crossA - crossE); 907 | } 908 | else if (_almostEqual(dotA, dotF)) 909 | { 910 | distances.Add(crossA - crossF); 911 | } 912 | else if (dotA > EFmin && dotA < EFmax) 913 | { 914 | var d = pointDistance(A, E, F, reverse); 915 | if (d != null && _almostEqual(d, 0)) 916 | { // A currently touches EF, but AB is moving away from EF 917 | var dB = pointDistance(B, E, F, reverse, true); 918 | if (dB < 0 || _almostEqual(dB * overlap, 0)) 919 | { 920 | d = null; 921 | } 922 | } 923 | if (d != null) 924 | { 925 | distances.Add(d.Value); 926 | } 927 | } 928 | 929 | if (_almostEqual(dotB, dotE)) 930 | { 931 | distances.Add(crossB - crossE); 932 | } 933 | else if (_almostEqual(dotB, dotF)) 934 | { 935 | distances.Add(crossB - crossF); 936 | } 937 | else if (dotB > EFmin && dotB < EFmax) 938 | { 939 | var d = pointDistance(B, E, F, reverse); 940 | 941 | if (d != null && _almostEqual(d, 0)) 942 | { // crossA>crossB A currently touches EF, but AB is moving away from EF 943 | var dA = pointDistance(A, E, F, reverse, true); 944 | if (dA < 0 || _almostEqual(dA * overlap, 0)) 945 | { 946 | d = null; 947 | } 948 | } 949 | if (d != null) 950 | { 951 | distances.Add(d.Value); 952 | } 953 | } 954 | 955 | if (dotE > ABmin && dotE < ABmax) 956 | { 957 | var d = pointDistance(E, A, B, direction); 958 | if (d != null && _almostEqual(d, 0)) 959 | { // crossF ABmin && dotF < ABmax) 973 | { 974 | var d = pointDistance(F, A, B, direction); 975 | if (d != null && _almostEqual(d, 0)) 976 | { // && crossE 0 || _almostEqual(d, 0)) 1064 | { 1065 | distance = d; 1066 | } 1067 | } 1068 | } 1069 | } 1070 | return distance; 1071 | } 1072 | public class nVector 1073 | { 1074 | public SvgPoint start; 1075 | public SvgPoint end; 1076 | public double x; 1077 | public double y; 1078 | 1079 | 1080 | public nVector(double v1, double v2, SvgPoint _start, SvgPoint _end) 1081 | { 1082 | this.x = v1; 1083 | this.y = v2; 1084 | this.start = _start; 1085 | this.end = _end; 1086 | } 1087 | } 1088 | 1089 | // given a static polygon A and a movable polygon B, compute a no fit polygon by orbiting B about A 1090 | // if the inside flag is set, B is orbited inside of A rather than outside 1091 | // if the searchEdges flag is set, all edges of A are explored for NFPs - multiple 1092 | public static NFP[] noFitPolygon(NFP A, NFP B, bool inside, bool searchEdges) 1093 | { 1094 | if (A == null || A.length < 3 || B == null || B.length < 3) 1095 | { 1096 | return null; 1097 | } 1098 | 1099 | A.offsetx = 0; 1100 | A.offsety = 0; 1101 | 1102 | int i = 0, j = 0; 1103 | 1104 | var minA = A[0].y; 1105 | var minAindex = 0; 1106 | 1107 | var maxB = B[0].y; 1108 | var maxBindex = 0; 1109 | 1110 | for (i = 1; i < A.length; i++) 1111 | { 1112 | A[i].marked = false; 1113 | if (A[i].y < minA) 1114 | { 1115 | minA = A[i].y; 1116 | minAindex = i; 1117 | } 1118 | } 1119 | 1120 | for (i = 1; i < B.length; i++) 1121 | { 1122 | B[i].marked = false; 1123 | if (B[i].y > maxB) 1124 | { 1125 | maxB = B[i].y; 1126 | maxBindex = i; 1127 | } 1128 | } 1129 | SvgPoint startpoint; 1130 | if (!inside) 1131 | { 1132 | // shift B such that the bottom-most point of B is at the top-most point of A. This guarantees an initial placement with no intersections 1133 | startpoint = new SvgPoint( 1134 | A[minAindex].x - B[maxBindex].x, 1135 | A[minAindex].y - B[maxBindex].y); 1136 | } 1137 | else 1138 | { 1139 | // no reliable heuristic for inside 1140 | 1141 | startpoint = searchStartPoint(A, B, true); 1142 | } 1143 | 1144 | List NFPlist = new List(); 1145 | 1146 | 1147 | 1148 | while (startpoint != null) 1149 | { 1150 | 1151 | B.offsetx = startpoint.x; 1152 | B.offsety = startpoint.y; 1153 | 1154 | // maintain a list of touching points/edges 1155 | List touching = null; 1156 | 1157 | nVector prevvector = null; // keep track of previous vector 1158 | NFP NFP = new NFP(); 1159 | /*var NFP = [{ 1160 | x: B[0].x + B.offsetx, 1161 | y: B[0].y + B.offsety 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | }];*/ 1168 | NFP.push(new SvgPoint(B[0].x + B.offsetx.Value, B[0].y + B.offsety.Value)); 1169 | 1170 | double referencex = B[0].x + B.offsetx.Value; 1171 | double referencey = B[0].y + B.offsety.Value; 1172 | var startx = referencex; 1173 | var starty = referencey; 1174 | var counter = 0; 1175 | 1176 | while (counter < 10 * (A.length + B.length)) 1177 | { // sanity check, prevent infinite loop 1178 | touching = new List(); 1179 | // find touching vertices/edges 1180 | for (i = 0; i < A.length; i++) 1181 | { 1182 | var nexti = (i == A.length - 1) ? 0 : i + 1; 1183 | for (j = 0; j < B.length; j++) 1184 | { 1185 | var nextj = (j == B.length - 1) ? 0 : j + 1; 1186 | if (_almostEqual(A[i].x, B[j].x + B.offsetx) && _almostEqual(A[i].y, B[j].y + B.offsety)) 1187 | { 1188 | touching.Add(new TouchingItem(0, i, j)); 1189 | } 1190 | else if (_onSegment(A[i], A[nexti], 1191 | new SvgPoint(B[j].x + B.offsetx.Value, B[j].y + B.offsety.Value))) 1192 | { 1193 | touching.Add(new TouchingItem(1, nexti, j)); 1194 | } 1195 | else if (_onSegment( 1196 | new SvgPoint( 1197 | B[j].x + B.offsetx.Value, B[j].y + B.offsety.Value), 1198 | new SvgPoint( 1199 | B[nextj].x + B.offsetx.Value, B[nextj].y + B.offsety.Value), A[i])) 1200 | { 1201 | touching.Add(new TouchingItem(2, i, nextj)); 1202 | } 1203 | } 1204 | } 1205 | 1206 | // generate translation vectors from touching vertices/edges 1207 | var vectors = new List(); 1208 | for (i = 0; i < touching.Count; i++) 1209 | { 1210 | var vertexA = A[touching[i].A]; 1211 | vertexA.marked = true; 1212 | 1213 | // adjacent A vertices 1214 | var prevAindex = touching[i].A - 1; 1215 | var nextAindex = touching[i].A + 1; 1216 | 1217 | prevAindex = (prevAindex < 0) ? A.length - 1 : prevAindex; // loop 1218 | nextAindex = (nextAindex >= A.length) ? 0 : nextAindex; // loop 1219 | 1220 | var prevA = A[prevAindex]; 1221 | var nextA = A[nextAindex]; 1222 | 1223 | // adjacent B vertices 1224 | var vertexB = B[touching[i].B]; 1225 | 1226 | var prevBindex = touching[i].B - 1; 1227 | var nextBindex = touching[i].B + 1; 1228 | 1229 | prevBindex = (prevBindex < 0) ? B.length - 1 : prevBindex; // loop 1230 | nextBindex = (nextBindex >= B.length) ? 0 : nextBindex; // loop 1231 | 1232 | var prevB = B[prevBindex]; 1233 | var nextB = B[nextBindex]; 1234 | 1235 | if (touching[i].type == 0) 1236 | { 1237 | 1238 | var vA1 = new nVector( 1239 | prevA.x - vertexA.x, 1240 | prevA.y - vertexA.y, 1241 | vertexA, 1242 | prevA 1243 | ); 1244 | 1245 | var vA2 = new nVector( 1246 | nextA.x - vertexA.x, 1247 | nextA.y - vertexA.y, 1248 | vertexA, 1249 | nextA 1250 | ); 1251 | 1252 | // B vectors need to be inverted 1253 | var vB1 = new nVector( 1254 | vertexB.x - prevB.x, 1255 | vertexB.y - prevB.y, 1256 | prevB, 1257 | vertexB 1258 | ); 1259 | 1260 | var vB2 = new nVector( 1261 | vertexB.x - nextB.x, 1262 | vertexB.y - nextB.y, 1263 | nextB, 1264 | vertexB 1265 | ); 1266 | 1267 | vectors.Add(vA1); 1268 | vectors.Add(vA2); 1269 | vectors.Add(vB1); 1270 | vectors.Add(vB2); 1271 | } 1272 | else if (touching[i].type == 1) 1273 | { 1274 | vectors.Add(new nVector( 1275 | vertexA.x - (vertexB.x + B.offsetx.Value), 1276 | vertexA.y - (vertexB.y + B.offsety.Value), 1277 | prevA, 1278 | vertexA 1279 | )); 1280 | 1281 | vectors.Add(new nVector( 1282 | prevA.x - (vertexB.x + B.offsetx.Value), 1283 | prevA.y - (vertexB.y + B.offsety.Value), 1284 | vertexA, 1285 | prevA 1286 | )); 1287 | } 1288 | else if (touching[i].type == 2) 1289 | { 1290 | vectors.Add(new nVector( 1291 | vertexA.x - (vertexB.x + B.offsetx.Value), 1292 | vertexA.y - (vertexB.y + B.offsety.Value), 1293 | prevB, 1294 | vertexB 1295 | )); 1296 | 1297 | vectors.Add(new nVector( 1298 | vertexA.x - (prevB.x + B.offsetx.Value), 1299 | vertexA.y - (prevB.y + B.offsety.Value), 1300 | vertexB, 1301 | prevB 1302 | 1303 | )); 1304 | } 1305 | } 1306 | 1307 | // todo: there should be a faster way to reject vectors that will cause immediate intersection. For now just check them all 1308 | 1309 | nVector translate = null; 1310 | double maxd = 0; 1311 | 1312 | for (i = 0; i < vectors.Count; i++) 1313 | { 1314 | if (vectors[i].x == 0 && vectors[i].y == 0) 1315 | { 1316 | continue; 1317 | } 1318 | 1319 | // if this vector points us back to where we came from, ignore it. 1320 | // ie cross product = 0, dot product < 0 1321 | if (prevvector != null && 1322 | vectors[i].y * prevvector.y + vectors[i].x * prevvector.x < 0) 1323 | { 1324 | 1325 | // compare magnitude with unit vectors 1326 | var vectorlength = (float)Math.Sqrt(vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y); 1327 | var unitv = new SvgPoint(vectors[i].x / vectorlength, vectors[i].y / vectorlength); 1328 | 1329 | var prevlength = (float)Math.Sqrt(prevvector.x * prevvector.x + prevvector.y * prevvector.y); 1330 | var prevunit = new SvgPoint(prevvector.x / prevlength, prevvector.y / prevlength); 1331 | 1332 | // we need to scale down to unit vectors to normalize vector length. Could also just do a tan here 1333 | if (Math.Abs(unitv.y * prevunit.x - unitv.x * prevunit.y) < 0.0001) 1334 | { 1335 | continue; 1336 | } 1337 | } 1338 | 1339 | var d = polygonSlideDistance(A, B, vectors[i], true); 1340 | var vecd2 = vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y; 1341 | 1342 | if (d == null || d * d > vecd2) 1343 | { 1344 | var vecd = (float)Math.Sqrt(vectors[i].x * vectors[i].x + vectors[i].y * vectors[i].y); 1345 | d = vecd; 1346 | } 1347 | 1348 | if (d != null && d > maxd) 1349 | { 1350 | maxd = d.Value; 1351 | translate = vectors[i]; 1352 | } 1353 | } 1354 | 1355 | 1356 | if (translate == null || _almostEqual(maxd, 0)) 1357 | { 1358 | // didn't close the loop, something went wrong here 1359 | NFP = null; 1360 | break; 1361 | } 1362 | 1363 | translate.start.marked = true; 1364 | translate.end.marked = true; 1365 | 1366 | prevvector = translate; 1367 | 1368 | // trim 1369 | var vlength2 = translate.x * translate.x + translate.y * translate.y; 1370 | if (maxd * maxd < vlength2 && !_almostEqual(maxd * maxd, vlength2)) 1371 | { 1372 | var scale = (float)Math.Sqrt((maxd * maxd) / vlength2); 1373 | translate.x *= scale; 1374 | translate.y *= scale; 1375 | } 1376 | 1377 | referencex += translate.x; 1378 | referencey += translate.y; 1379 | 1380 | if (_almostEqual(referencex, startx) && _almostEqual(referencey, starty)) 1381 | { 1382 | // we've made a full loop 1383 | break; 1384 | } 1385 | 1386 | // if A and B start on a touching horizontal line, the end point may not be the start point 1387 | var looped = false; 1388 | if (NFP.length > 0) 1389 | { 1390 | for (i = 0; i < NFP.length - 1; i++) 1391 | { 1392 | if (_almostEqual(referencex, NFP[i].x) && _almostEqual(referencey, NFP[i].y)) 1393 | { 1394 | looped = true; 1395 | } 1396 | } 1397 | } 1398 | 1399 | if (looped) 1400 | { 1401 | // we've made a full loop 1402 | break; 1403 | } 1404 | 1405 | NFP.push(new SvgPoint( 1406 | referencex, referencey 1407 | )); 1408 | 1409 | B.offsetx += translate.x; 1410 | B.offsety += translate.y; 1411 | 1412 | counter++; 1413 | } 1414 | 1415 | if (NFP != null && NFP.length > 0) 1416 | { 1417 | NFPlist.Add(NFP); 1418 | 1419 | } 1420 | 1421 | if (!searchEdges) 1422 | { 1423 | // only get outer NFP or first inner NFP 1424 | break; 1425 | } 1426 | startpoint = searchStartPoint(A, B, inside, NFPlist.ToArray()); 1427 | } 1428 | 1429 | return NFPlist.ToArray(); 1430 | } 1431 | 1432 | 1433 | 1434 | } 1435 | public class PolygonBounds 1436 | { 1437 | public double x; 1438 | public double y; 1439 | public double width; 1440 | public double height; 1441 | public PolygonBounds(double _x, double _y, double _w, double _h) 1442 | { 1443 | x = _x; 1444 | y = _y; 1445 | width = _w; 1446 | height = _h; 1447 | } 1448 | } 1449 | } 1450 | -------------------------------------------------------------------------------- /src/DeepNestLib/NFP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml.Linq; 6 | 7 | namespace DeepNestLib 8 | { 9 | public class NFP : IStringify 10 | { 11 | public bool fitted { get { return sheet != null; } } 12 | public NFP sheet; 13 | public override string ToString() 14 | { 15 | var str1 = (Points != null) ? Points.Count() + "" : "null"; 16 | return $"nfp: id: {id}; source: {source}; rotation: {rotation}; points: {str1}"; 17 | } 18 | public NFP() 19 | { 20 | Points = new SvgPoint[] { }; 21 | } 22 | 23 | public string Name { get; set; } 24 | public void AddPoint(SvgPoint point) 25 | { 26 | var list = Points.ToList(); 27 | list.Add(point); 28 | Points = list.ToArray(); 29 | } 30 | 31 | #region gdi section 32 | public bool isBin; 33 | 34 | #endregion 35 | public void reverse() 36 | { 37 | Points = Points.Reverse().ToArray(); 38 | } 39 | 40 | public double x { get; set; } 41 | public double y { get; set; } 42 | 43 | public double WidthCalculated 44 | { 45 | get 46 | { 47 | var maxx = Points.Max(z => z.x); 48 | var minx = Points.Min(z => z.x); 49 | 50 | return maxx - minx; 51 | } 52 | } 53 | 54 | public double HeightCalculated 55 | { 56 | get 57 | { 58 | var maxy = Points.Max(z => z.y); 59 | var miny = Points.Min(z => z.y); 60 | return maxy - miny; 61 | } 62 | } 63 | 64 | public SvgPoint this[int ind] 65 | { 66 | get 67 | { 68 | return Points[ind]; 69 | } 70 | } 71 | 72 | public List children; 73 | 74 | 75 | 76 | 77 | public int Length 78 | { 79 | get 80 | { 81 | return Points.Length; 82 | } 83 | } 84 | 85 | //public float? width; 86 | //public float? height; 87 | public int length 88 | { 89 | get 90 | { 91 | return Points.Length; 92 | } 93 | } 94 | 95 | public int Id; 96 | public int id 97 | { 98 | get 99 | { 100 | return Id; 101 | } 102 | set 103 | { 104 | Id = value; 105 | } 106 | } 107 | 108 | public double? offsetx; 109 | public double? offsety; 110 | public int? source = null; 111 | public float Rotation; 112 | 113 | 114 | public float rotation 115 | { 116 | get 117 | { 118 | return Rotation; 119 | } 120 | set 121 | { 122 | Rotation = value; 123 | } 124 | } 125 | public SvgPoint[] Points; 126 | public float Area 127 | { 128 | get 129 | { 130 | float ret = 0; 131 | if (Points.Length < 3) return 0; 132 | List pp = new List(); 133 | pp.AddRange(Points); 134 | pp.Add(Points[0]); 135 | for (int i = 1; i < pp.Count; i++) 136 | { 137 | var s0 = pp[i - 1]; 138 | var s1 = pp[i]; 139 | ret += (float)(s0.x * s1.y - s0.y * s1.x); 140 | } 141 | return (float)Math.Abs(ret / 2); 142 | } 143 | } 144 | 145 | internal void push(SvgPoint svgPoint) 146 | { 147 | List points = new List(); 148 | if (Points == null) 149 | { 150 | Points = new SvgPoint[] { }; 151 | } 152 | points.AddRange(Points); 153 | points.Add(svgPoint); 154 | Points = points.ToArray(); 155 | 156 | } 157 | 158 | public NFP slice(int v) 159 | { 160 | var ret = new NFP(); 161 | List pp = new List(); 162 | for (int i = v; i < length; i++) 163 | { 164 | pp.Add(new SvgPoint(this[i].x, this[i].y)); 165 | 166 | } 167 | ret.Points = pp.ToArray(); 168 | return ret; 169 | } 170 | 171 | public string stringify() 172 | { 173 | throw new NotImplementedException(); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/DeepNestLib/NestingContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Xml.Linq; 8 | 9 | namespace DeepNestLib 10 | { 11 | public class NestingContext 12 | { 13 | public List Polygons { get; private set; } = new List(); 14 | public List Sheets { get; private set; } = new List(); 15 | 16 | 17 | public double MaterialUtilization { get; private set; } = 0; 18 | public int PlacedPartsCount { get; private set; } = 0; 19 | 20 | 21 | SheetPlacement current = null; 22 | public SheetPlacement Current { get { return current; } } 23 | public SvgNest Nest { get; private set; } 24 | 25 | public int Iterations { get; private set; } = 0; 26 | 27 | public void StartNest() 28 | { 29 | current = null; 30 | Nest = new SvgNest(); 31 | Background.cacheProcess = new Dictionary(); 32 | Background.window = new windowUnk(); 33 | Background.callCounter = 0; 34 | Iterations = 0; 35 | } 36 | 37 | bool offsetTreePhase = true; 38 | public void NestIterate() 39 | { 40 | List lsheets = new List(); 41 | List lpoly = new List(); 42 | 43 | for (int i = 0; i < Polygons.Count; i++) 44 | { 45 | Polygons[i].id = i; 46 | } 47 | for (int i = 0; i < Sheets.Count; i++) 48 | { 49 | Sheets[i].id = i; 50 | } 51 | foreach (var item in Polygons) 52 | { 53 | NFP clone = new NFP(); 54 | clone.id = item.id; 55 | clone.source = item.source; 56 | clone.Points = item.Points.Select(z => new SvgPoint(z.x, z.y) { exact = z.exact }).ToArray(); 57 | if (item.children != null) 58 | { 59 | clone.children = new List(); 60 | foreach (var citem in item.children) 61 | { 62 | clone.children.Add(new NFP()); 63 | var l = clone.children.Last(); 64 | l.id = citem.id; 65 | l.source = citem.source; 66 | l.Points = citem.Points.Select(z => new SvgPoint(z.x, z.y) { exact = z.exact }).ToArray(); 67 | } 68 | } 69 | lpoly.Add(clone); 70 | } 71 | 72 | 73 | foreach (var item in Sheets) 74 | { 75 | NFP clone = new NFP(); 76 | clone.id = item.id; 77 | clone.source = item.source; 78 | clone.Points = item.Points.Select(z => new SvgPoint(z.x, z.y) { exact = z.exact }).ToArray(); 79 | if (item.children != null) 80 | { 81 | clone.children = new List(); 82 | foreach (var citem in item.children) 83 | { 84 | clone.children.Add(new NFP()); 85 | var l = clone.children.Last(); 86 | l.id = citem.id; 87 | l.source = citem.source; 88 | l.Points = citem.Points.Select(z => new SvgPoint(z.x, z.y) { exact = z.exact }).ToArray(); 89 | } 90 | } 91 | lsheets.Add(clone); 92 | } 93 | 94 | if (offsetTreePhase) 95 | { 96 | var grps = lpoly.GroupBy(z => z.source).ToArray(); 97 | if (Background.UseParallel) 98 | { 99 | Parallel.ForEach(grps, (item) => 100 | { 101 | SvgNest.offsetTree(item.First(), 0.5 * SvgNest.Config.spacing, SvgNest.Config); 102 | foreach (var zitem in item) 103 | { 104 | zitem.Points = item.First().Points.ToArray(); 105 | } 106 | 107 | }); 108 | 109 | } 110 | else 111 | { 112 | 113 | foreach (var item in grps) 114 | { 115 | SvgNest.offsetTree(item.First(), 0.5 * SvgNest.Config.spacing, SvgNest.Config); 116 | foreach (var zitem in item) 117 | { 118 | zitem.Points = item.First().Points.ToArray(); 119 | } 120 | } 121 | } 122 | 123 | foreach (var item in lsheets) 124 | { 125 | SvgNest.offsetTree(item, -0.5 * SvgNest.Config.spacing, SvgNest.Config, true); 126 | } 127 | } 128 | 129 | 130 | 131 | List partsLocal = new List(); 132 | var p1 = lpoly.GroupBy(z => z.source).Select(z => new NestItem() 133 | { 134 | Polygon = z.First(), 135 | IsSheet = false, 136 | Quanity = z.Count() 137 | }); 138 | 139 | var p2 = lsheets.GroupBy(z => z.source).Select(z => new NestItem() 140 | { 141 | Polygon = z.First(), 142 | IsSheet = true, 143 | Quanity = z.Count() 144 | }); 145 | 146 | 147 | partsLocal.AddRange(p1); 148 | partsLocal.AddRange(p2); 149 | int srcc = 0; 150 | foreach (var item in partsLocal) 151 | { 152 | item.Polygon.source = srcc++; 153 | } 154 | 155 | 156 | Nest.launchWorkers(partsLocal.ToArray()); 157 | var plcpr = Nest.nests.First(); 158 | 159 | if (current == null || plcpr.fitness < current.fitness) 160 | { 161 | AssignPlacement(plcpr); 162 | } 163 | Iterations++; 164 | } 165 | 166 | public void ExportSvg(string v) 167 | { 168 | SvgParser.Export(v, Polygons, Sheets); 169 | } 170 | 171 | 172 | public void AssignPlacement(SheetPlacement plcpr) 173 | { 174 | current = plcpr; 175 | double totalSheetsArea = 0; 176 | double totalPartsArea = 0; 177 | 178 | PlacedPartsCount = 0; 179 | List placed = new List(); 180 | foreach (var item in Polygons) 181 | { 182 | item.sheet = null; 183 | } 184 | List sheetsIds = new List(); 185 | 186 | foreach (var item in plcpr.placements) 187 | { 188 | foreach (var zitem in item) 189 | { 190 | var sheetid = zitem.sheetId; 191 | if (!sheetsIds.Contains(sheetid)) 192 | { 193 | sheetsIds.Add(sheetid); 194 | } 195 | 196 | var sheet = Sheets.First(z => z.id == sheetid); 197 | totalSheetsArea += GeometryUtil.polygonArea(sheet); 198 | 199 | foreach (var ssitem in zitem.sheetplacements) 200 | { 201 | PlacedPartsCount++; 202 | var poly = Polygons.First(z => z.id == ssitem.id); 203 | totalPartsArea += GeometryUtil.polygonArea(poly); 204 | placed.Add(poly); 205 | poly.sheet = sheet; 206 | poly.x = ssitem.x + sheet.x; 207 | poly.y = ssitem.y + sheet.y; 208 | poly.rotation = ssitem.rotation; 209 | } 210 | } 211 | } 212 | 213 | var emptySheets = Sheets.Where(z => !sheetsIds.Contains(z.id)).ToArray(); 214 | 215 | MaterialUtilization = Math.Abs(totalPartsArea / totalSheetsArea); 216 | 217 | var ppps = Polygons.Where(z => !placed.Contains(z)); 218 | foreach (var item in ppps) 219 | { 220 | item.x = -1000; 221 | item.y = 0; 222 | } 223 | } 224 | 225 | public void ReorderSheets() 226 | { 227 | double x = 0; 228 | double y = 0; 229 | int gap = 10; 230 | for (int i = 0; i < Sheets.Count; i++) 231 | { 232 | Sheets[i].x = x; 233 | Sheets[i].y = y; 234 | if (Sheets[i] is Sheet) 235 | { 236 | var r = Sheets[i] as Sheet; 237 | x += r.Width + gap; 238 | } 239 | else 240 | { 241 | var maxx = Sheets[i].Points.Max(z => z.x); 242 | var minx = Sheets[i].Points.Min(z => z.x); 243 | var w = maxx - minx; 244 | x += w + gap; 245 | } 246 | } 247 | } 248 | 249 | public void AddSheet(int w, int h, int src) 250 | { 251 | var tt = new RectangleSheet(); 252 | tt.Name = "sheet" + (Sheets.Count + 1); 253 | Sheets.Add(tt); 254 | 255 | tt.source = src; 256 | tt.Height = h; 257 | tt.Width = w; 258 | tt.Rebuild(); 259 | ReorderSheets(); 260 | } 261 | 262 | Random r = new Random(); 263 | 264 | 265 | public void LoadSampleData() 266 | { 267 | Console.WriteLine("Adding sheets.."); 268 | //add sheets 269 | for (int i = 0; i < 5; i++) 270 | { 271 | AddSheet(3000, 1500, 0); 272 | } 273 | 274 | Console.WriteLine("Adding parts.."); 275 | //add parts 276 | int src1 = GetNextSource(); 277 | for (int i = 0; i < 200; i++) 278 | { 279 | AddRectanglePart(src1, 250, 220); 280 | } 281 | 282 | } 283 | public void LoadInputData(string path, int count) 284 | { 285 | var dir = new DirectoryInfo(path); 286 | foreach (var item in dir.GetFiles("*.svg")) 287 | { 288 | try 289 | { 290 | var src = GetNextSource(); 291 | for (int i = 0; i < count; i++) 292 | { 293 | ImportFromRawDetail(SvgParser.LoadSvg(item.FullName), src); 294 | } 295 | } 296 | catch (Exception ex) 297 | { 298 | Console.WriteLine("Error loading " + item.FullName + ". skip"); 299 | } 300 | } 301 | } 302 | public NFP ImportFromRawDetail(RawDetail raw, int src) 303 | { 304 | NFP po = null; 305 | List nfps = new List(); 306 | foreach (var item in raw.Outers) 307 | { 308 | var nn = new NFP(); 309 | nfps.Add(nn); 310 | foreach (var pitem in item.Points) 311 | { 312 | nn.AddPoint(new SvgPoint(pitem.X, pitem.Y)); 313 | } 314 | } 315 | 316 | if (nfps.Any()) 317 | { 318 | var tt = nfps.OrderByDescending(z => z.Area).First(); 319 | po = tt; 320 | po.Name = raw.Name; 321 | 322 | foreach (var r in nfps) 323 | { 324 | if (r == tt) continue; 325 | if (po.children == null) 326 | { 327 | po.children = new List(); 328 | } 329 | po.children.Add(r); 330 | } 331 | 332 | po.source = src; 333 | Polygons.Add(po); 334 | } 335 | return po; 336 | } 337 | public int GetNextSource() 338 | { 339 | if (Polygons.Any()) 340 | { 341 | return Polygons.Max(z => z.source.Value) + 1; 342 | } 343 | return 0; 344 | } 345 | public int GetNextSheetSource() 346 | { 347 | if (Sheets.Any()) 348 | { 349 | return Sheets.Max(z => z.source.Value) + 1; 350 | } 351 | return 0; 352 | } 353 | public void AddRectanglePart(int src, int ww = 50, int hh = 80) 354 | { 355 | int xx = 0; 356 | int yy = 0; 357 | NFP pl = new NFP(); 358 | 359 | Polygons.Add(pl); 360 | pl.source = src; 361 | pl.Points = new SvgPoint[] { }; 362 | pl.AddPoint(new SvgPoint(xx, yy)); 363 | pl.AddPoint(new SvgPoint(xx + ww, yy)); 364 | pl.AddPoint(new SvgPoint(xx + ww, yy + hh)); 365 | pl.AddPoint(new SvgPoint(xx, yy + hh)); 366 | } 367 | public void LoadXml(string v) 368 | { 369 | var d = XDocument.Load(v); 370 | var f = d.Descendants().First(); 371 | var gap = int.Parse(f.Attribute("gap").Value); 372 | SvgNest.Config.spacing = gap; 373 | 374 | foreach (var item in d.Descendants("sheet")) 375 | { 376 | int src = GetNextSheetSource(); 377 | var cnt = int.Parse(item.Attribute("count").Value); 378 | var ww = int.Parse(item.Attribute("width").Value); 379 | var hh = int.Parse(item.Attribute("height").Value); 380 | 381 | for (int i = 0; i < cnt; i++) 382 | { 383 | AddSheet(ww, hh, src); 384 | } 385 | } 386 | foreach (var item in d.Descendants("part")) 387 | { 388 | var cnt = int.Parse(item.Attribute("count").Value); 389 | var path = item.Attribute("path").Value; 390 | var r = SvgParser.LoadSvg(path); 391 | var src = GetNextSource(); 392 | 393 | for (int i = 0; i < cnt; i++) 394 | { 395 | ImportFromRawDetail(r, src); 396 | } 397 | } 398 | } 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /src/DeepNestLib/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DeepNestLib")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DeepNestLib")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("32f6dd67-ef43-49e7-a6b3-4786344baf14")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/DeepNestLib/RawDetail.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace DeepNestLib 7 | { 8 | public class RawDetail 9 | { 10 | public List Outers = new List(); 11 | public List Holes = new List(); 12 | 13 | public string Name { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/DeepNestLib/Simplify.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace DeepNestLib 7 | { 8 | public class Simplify 9 | { 10 | 11 | // to suit your point format, run search/replace for '.x' and '.y'; 12 | // for 3D version, see 3d branch (configurability would draw significant performance overhead) 13 | 14 | // square distance between 2 points 15 | public static double getSqDist(SvgPoint p1, SvgPoint p2) 16 | { 17 | 18 | var dx = p1.x - p2.x; 19 | var dy = p1.y - p2.y; 20 | 21 | return dx * dx + dy * dy; 22 | } 23 | 24 | // square distance from a point to a segment 25 | public static double getSqSegDist(SvgPoint p, SvgPoint p1, SvgPoint p2) 26 | { 27 | 28 | var x = p1.x; 29 | var y = p1.y; 30 | var dx = p2.x - x; 31 | var dy = p2.y - y; 32 | 33 | if (dx != 0 || dy != 0) 34 | { 35 | 36 | var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); 37 | 38 | if (t > 1) 39 | { 40 | x = p2.x; 41 | y = p2.y; 42 | 43 | } 44 | else if (t > 0) 45 | { 46 | x += dx * t; 47 | y += dy * t; 48 | } 49 | } 50 | 51 | dx = p.x - x; 52 | dy = p.y - y; 53 | 54 | return dx * dx + dy * dy; 55 | } 56 | // rest of the code doesn't care about point format 57 | 58 | // basic distance-based simplification 59 | public static NFP simplifyRadialDist(NFP points, double? sqTolerance) 60 | { 61 | 62 | var prevPoint = points[0]; 63 | var newPoints = new NFP(); 64 | newPoints.AddPoint(prevPoint); 65 | 66 | SvgPoint point = null; 67 | int i = 1; 68 | for (var len = points.length; i < len; i++) 69 | { 70 | point = points[i]; 71 | 72 | if (point.marked || getSqDist(point, prevPoint) > sqTolerance) 73 | { 74 | newPoints.AddPoint(point); 75 | prevPoint = point; 76 | } 77 | } 78 | 79 | if (prevPoint != point) newPoints.AddPoint(point); 80 | return newPoints; 81 | } 82 | 83 | 84 | public static void simplifyDPStep(NFP points, int first, int last, double? sqTolerance, NFP simplified) 85 | { 86 | var maxSqDist = sqTolerance; 87 | var index = -1; 88 | var marked = false; 89 | for (var i = first + 1; i < last; i++) 90 | { 91 | var sqDist = getSqSegDist(points[i], points[first], points[last]); 92 | 93 | if (sqDist > maxSqDist) 94 | { 95 | index = i; 96 | maxSqDist = sqDist; 97 | } 98 | /*if(points[i].marked && maxSqDist <= sqTolerance){ 99 | index = i; 100 | marked = true; 101 | }*/ 102 | } 103 | 104 | /*if(!points[index] && maxSqDist > sqTolerance){ 105 | console.log('shit shit shit'); 106 | }*/ 107 | 108 | if (maxSqDist > sqTolerance || marked) 109 | { 110 | if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified); 111 | simplified.push(points[index]); 112 | if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified); 113 | } 114 | } 115 | 116 | // simplification using Ramer-Douglas-Peucker algorithm 117 | public static NFP simplifyDouglasPeucker(NFP points, double? sqTolerance) 118 | { 119 | var last = points.length - 1; 120 | 121 | var simplified = new NFP(); 122 | simplified.AddPoint(points[0]); 123 | simplifyDPStep(points, 0, last, sqTolerance, simplified); 124 | simplified.push(points[last]); 125 | 126 | return simplified; 127 | } 128 | 129 | // both algorithms combined for awesome performance 130 | public static NFP simplify(NFP points, double? tolerance, bool highestQuality) 131 | { 132 | 133 | if (points.length <= 2) return points; 134 | 135 | var sqTolerance = (tolerance != null) ? (tolerance * tolerance) : 1; 136 | 137 | points = highestQuality ? points : simplifyRadialDist(points, sqTolerance); 138 | points = simplifyDouglasPeucker(points, sqTolerance); 139 | 140 | return points; 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/DeepNestLib/SvgNest.cs: -------------------------------------------------------------------------------- 1 | using ClipperLib; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Xml.Linq; 9 | 10 | namespace DeepNestLib 11 | { 12 | public class SvgNest 13 | { 14 | 15 | public SvgNest() 16 | { 17 | 18 | } 19 | public class InrangeItem 20 | { 21 | public SvgPoint point; 22 | public double distance; 23 | } 24 | public static SvgPoint getTarget(SvgPoint o, NFP simple, double tol) 25 | { 26 | List inrange = new List(); 27 | // find closest points within 2 offset deltas 28 | for (var j = 0; j < simple.length; j++) 29 | { 30 | var s = simple[j]; 31 | var d2 = (o.x - s.x) * (o.x - s.x) + (o.y - s.y) * (o.y - s.y); 32 | if (d2 < tol * tol) 33 | { 34 | inrange.Add(new InrangeItem() { point = s, distance = d2 }); 35 | } 36 | } 37 | 38 | SvgPoint target = null; 39 | if (inrange.Count > 0) 40 | { 41 | var filtered = inrange.Where((p) => 42 | { 43 | return p.point.exact; 44 | }).ToList(); 45 | 46 | // use exact points when available, normal points when not 47 | inrange = filtered.Count > 0 ? filtered : inrange; 48 | 49 | 50 | inrange = inrange.OrderBy((b) => 51 | { 52 | return b.distance; 53 | }).ToList(); 54 | 55 | target = inrange[0].point; 56 | } 57 | else 58 | { 59 | double? mind = null; 60 | for (int j = 0; j < simple.length; j++) 61 | { 62 | var s = simple[j]; 63 | var d2 = (o.x - s.x) * (o.x - s.x) + (o.y - s.y) * (o.y - s.y); 64 | if (mind == null || d2 < mind) 65 | { 66 | target = s; 67 | mind = d2; 68 | } 69 | } 70 | } 71 | 72 | return target; 73 | } 74 | 75 | public static SvgNestConfig Config = new SvgNestConfig(); 76 | 77 | 78 | public static NFP clone(NFP p) 79 | { 80 | var newp = new NFP(); 81 | for (var i = 0; i < p.length; i++) 82 | { 83 | newp.AddPoint(new SvgPoint( 84 | 85 | p[i].x, 86 | p[i].y 87 | 88 | )); 89 | } 90 | 91 | return newp; 92 | } 93 | 94 | 95 | public static bool pointInPolygon(SvgPoint point, NFP polygon) 96 | { 97 | // scaling is deliberately coarse to filter out points that lie *on* the polygon 98 | 99 | var p = svgToClipper2(polygon, 1000); 100 | var pt = new ClipperLib.IntPoint(1000 * point.x, 1000 * point.y); 101 | 102 | return ClipperLib.Clipper.PointInPolygon(pt, p.ToList()) > 0; 103 | } 104 | 105 | // returns true if any complex vertices fall outside the simple polygon 106 | public static bool exterior(NFP simple, NFP complex, bool inside) 107 | { 108 | // find all protruding vertices 109 | for (var i = 0; i < complex.length; i++) 110 | { 111 | var v = complex[i]; 112 | if (!inside && !pointInPolygon(v, simple) && find(v, simple) == null) 113 | { 114 | return true; 115 | } 116 | if (inside && pointInPolygon(v, simple) && find(v, simple) != null) 117 | { 118 | return true; 119 | } 120 | } 121 | return false; 122 | } 123 | 124 | public static NFP simplifyFunction(NFP polygon, bool inside) 125 | { 126 | var tolerance = 4 * Config.curveTolerance; 127 | 128 | // give special treatment to line segments above this length (squared) 129 | var fixedTolerance = 40 * Config.curveTolerance * 40 * Config.curveTolerance; 130 | int i, j, k; 131 | 132 | 133 | if (Config.simplify) 134 | { 135 | /* 136 | // use convex hull 137 | var hull = new ConvexHullGrahamScan(); 138 | for(var i=0; i 1) 156 | { 157 | polygon = cleaned; 158 | } 159 | else 160 | { 161 | return polygon; 162 | } 163 | // polygon to polyline 164 | var copy = polygon.slice(0); 165 | copy.push(copy[0]); 166 | // mark all segments greater than ~0.25 in to be kept 167 | // the PD simplification algo doesn't care about the accuracy of long lines, only the absolute distance of each point 168 | // we care a great deal 169 | for (i = 0; i < copy.length - 1; i++) 170 | { 171 | var p1 = copy[i]; 172 | var p2 = copy[i + 1]; 173 | var sqd = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y); 174 | if (sqd > fixedTolerance) 175 | { 176 | p1.marked = true; 177 | p2.marked = true; 178 | } 179 | } 180 | 181 | var simple = Simplify.simplify(copy, tolerance, true); 182 | // now a polygon again 183 | //simple.pop(); 184 | simple.Points = simple.Points.Take(simple.Points.Count() - 1).ToArray(); 185 | 186 | // could be dirty again (self intersections and/or coincident points) 187 | simple = cleanPolygon2(simple); 188 | 189 | // simplification process reduced poly to a line or point 190 | if (simple == null) 191 | { 192 | simple = polygon; 193 | } 194 | 195 | 196 | 197 | var offsets = polygonOffsetDeepNest(simple, inside ? -tolerance : tolerance); 198 | 199 | NFP offset = null; 200 | double offsetArea = 0; 201 | List holes = new List(); 202 | for (i = 0; i < offsets.Length; i++) 203 | { 204 | var area = GeometryUtil.polygonArea(offsets[i]); 205 | if (offset == null || area < offsetArea) 206 | { 207 | offset = offsets[i]; 208 | offsetArea = area; 209 | } 210 | if (area > 0) 211 | { 212 | holes.Add(offsets[i]); 213 | } 214 | } 215 | 216 | // mark any points that are exact 217 | for (i = 0; i < simple.length; i++) 218 | { 219 | var seg = new NFP(); 220 | seg.AddPoint(simple[i]); 221 | seg.AddPoint(simple[i + 1 == simple.length ? 0 : i + 1]); 222 | 223 | var index1 = find(seg[0], polygon); 224 | var index2 = find(seg[1], polygon); 225 | 226 | if (index1 + 1 == index2 || index2 + 1 == index1 || (index1 == 0 && index2 == polygon.length - 1) || (index2 == 0 && index1 == polygon.length - 1)) 227 | { 228 | seg[0].exact = true; 229 | seg[1].exact = true; 230 | } 231 | } 232 | var numshells = 4; 233 | NFP[] shells = new NFP[numshells]; 234 | 235 | for (j = 1; j < numshells; j++) 236 | { 237 | var delta = j * (tolerance / numshells); 238 | delta = inside ? -delta : delta; 239 | var shell = polygonOffsetDeepNest(simple, delta); 240 | if (shell.Count() > 0) 241 | { 242 | shells[j] = shell.First(); 243 | } 244 | else 245 | { 246 | //shells[j] = shell; 247 | } 248 | } 249 | 250 | if (offset == null) 251 | { 252 | return polygon; 253 | } 254 | // selective reversal of offset 255 | for (i = 0; i < offset.length; i++) 256 | { 257 | var o = offset[i]; 258 | var target = getTarget(o, simple, 2 * tolerance); 259 | 260 | // reverse point offset and try to find exterior points 261 | var test = clone(offset); 262 | test.Points[i] = new SvgPoint(target.x, target.y); 263 | 264 | if (!exterior(test, polygon, inside)) 265 | { 266 | o.x = target.x; 267 | o.y = target.y; 268 | } 269 | else 270 | { 271 | // a shell is an intermediate offset between simple and offset 272 | for (j = 1; j < numshells; j++) 273 | { 274 | if (shells[j] != null) 275 | { 276 | var shell = shells[j]; 277 | var delta = j * (tolerance / numshells); 278 | target = getTarget(o, shell, 2 * delta); 279 | test = clone(offset); 280 | test.Points[i] = new SvgPoint(target.x, target.y); 281 | if (!exterior(test, polygon, inside)) 282 | { 283 | o.x = target.x; 284 | o.y = target.y; 285 | break; 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | // straighten long lines 293 | // a rounded rectangle would still have issues at this point, as the long sides won't line up straight 294 | 295 | var straightened = false; 296 | 297 | for (i = 0; i < offset.length; i++) 298 | { 299 | var p1 = offset[i]; 300 | var p2 = offset[i + 1 == offset.length ? 0 : i + 1]; 301 | 302 | var sqd = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y); 303 | 304 | if (sqd < fixedTolerance) 305 | { 306 | continue; 307 | } 308 | for (j = 0; j < simple.length; j++) 309 | { 310 | var s1 = simple[j]; 311 | var s2 = simple[j + 1 == simple.length ? 0 : j + 1]; 312 | 313 | var sqds = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y); 314 | 315 | if (sqds < fixedTolerance) 316 | { 317 | continue; 318 | } 319 | 320 | if ((GeometryUtil._almostEqual(s1.x, s2.x) || GeometryUtil._almostEqual(s1.y, s2.y)) && // we only really care about vertical and horizontal lines 321 | GeometryUtil._withinDistance(p1, s1, 2 * tolerance) && 322 | GeometryUtil._withinDistance(p2, s2, 2 * tolerance) && 323 | (!GeometryUtil._withinDistance(p1, s1, Config.curveTolerance / 1000) || 324 | !GeometryUtil._withinDistance(p2, s2, Config.curveTolerance / 1000))) 325 | { 326 | p1.x = s1.x; 327 | p1.y = s1.y; 328 | p2.x = s2.x; 329 | p2.y = s2.y; 330 | straightened = true; 331 | } 332 | } 333 | } 334 | 335 | //if(straightened){ 336 | 337 | var Ac = _Clipper.ScaleUpPaths(offset, 10000000); 338 | var Bc = _Clipper.ScaleUpPaths(polygon, 10000000); 339 | 340 | var combined = new List>(); 341 | var clipper = new ClipperLib.Clipper(); 342 | 343 | clipper.AddPath(Ac.ToList(), ClipperLib.PolyType.ptSubject, true); 344 | clipper.AddPath(Bc.ToList(), ClipperLib.PolyType.ptSubject, true); 345 | 346 | // the line straightening may have made the offset smaller than the simplified 347 | if (clipper.Execute(ClipperLib.ClipType.ctUnion, combined, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero)) 348 | { 349 | double? largestArea = null; 350 | for (i = 0; i < combined.Count; i++) 351 | { 352 | var n = Background.toNestCoordinates(combined[i].ToArray(), 10000000); 353 | var sarea = -GeometryUtil.polygonArea(n); 354 | if (largestArea == null || largestArea < sarea) 355 | { 356 | offset = n; 357 | largestArea = sarea; 358 | } 359 | } 360 | } 361 | //} 362 | 363 | cleaned = cleanPolygon2(offset); 364 | if (cleaned != null && cleaned.length > 1) 365 | { 366 | offset = cleaned; 367 | } 368 | 369 | // mark any points that are exact (for line merge detection) 370 | for (i = 0; i < offset.length; i++) 371 | { 372 | var seg = new SvgPoint[] { offset[i], offset[i + 1 == offset.length ? 0 : i + 1] }; 373 | var index1 = find(seg[0], polygon); 374 | var index2 = find(seg[1], polygon); 375 | if (index1 == null) 376 | { 377 | index1 = 0; 378 | } 379 | if (index2 == null) 380 | { 381 | index2 = 0; 382 | } 383 | if (index1 + 1 == index2 || index2 + 1 == index1 384 | || (index1 == 0 && index2 == polygon.length - 1) || 385 | (index2 == 0 && index1 == polygon.length - 1)) 386 | { 387 | seg[0].exact = true; 388 | seg[1].exact = true; 389 | } 390 | } 391 | 392 | if (!inside && holes != null && holes.Count > 0) 393 | { 394 | offset.children = holes; 395 | } 396 | 397 | return offset; 398 | 399 | } 400 | public static int? find(SvgPoint v, NFP p) 401 | { 402 | for (var i = 0; i < p.length; i++) 403 | { 404 | if (GeometryUtil._withinDistance(v, p[i], Config.curveTolerance / 1000)) 405 | { 406 | return i; 407 | } 408 | } 409 | return null; 410 | } 411 | // offset tree recursively 412 | public static void offsetTree(NFP t, double offset, SvgNestConfig config, bool? inside = null) 413 | { 414 | 415 | var simple = t; 416 | 417 | simple = simplifyFunction(t, (inside == null) ? false : inside.Value); 418 | 419 | var offsetpaths = new NFP[] { simple }; 420 | if (offset > 0) 421 | { 422 | offsetpaths = polygonOffsetDeepNest(simple, offset); 423 | } 424 | 425 | if (offsetpaths.Count() > 0) 426 | { 427 | 428 | List rett = new List(); 429 | rett.AddRange(offsetpaths[0].Points); 430 | rett.AddRange(t.Points.Skip(t.length)); 431 | t.Points = rett.ToArray(); 432 | 433 | // replace array items in place 434 | 435 | //Array.prototype.splice.apply(t, [0, t.length].concat(offsetpaths[0])); 436 | } 437 | 438 | if (simple.children != null && simple.children.Count > 0) 439 | { 440 | if (t.children == null) 441 | { 442 | t.children = new List(); 443 | } 444 | 445 | for (var i = 0; i < simple.children.Count; i++) 446 | { 447 | t.children.Add(simple.children[i]); 448 | } 449 | } 450 | 451 | if (t.children != null && t.children.Count > 0) 452 | { 453 | for (var i = 0; i < t.children.Count; i++) 454 | { 455 | 456 | offsetTree(t.children[i], -offset, config, (inside == null) ? true : (!inside)); 457 | } 458 | } 459 | } 460 | 461 | 462 | // use the clipper library to return an offset to the given polygon. Positive offset expands the polygon, negative contracts 463 | // note that this returns an array of polygons 464 | public static NFP[] polygonOffsetDeepNest(NFP polygon, double offset) 465 | { 466 | 467 | if (offset == 0 || GeometryUtil._almostEqual(offset, 0)) 468 | { 469 | return new[] { polygon }; 470 | } 471 | 472 | var p = svgToClipper(polygon).ToList(); 473 | 474 | var miterLimit = 4; 475 | var co = new ClipperLib.ClipperOffset(miterLimit, Config.curveTolerance * Config.clipperScale); 476 | co.AddPath(p.ToList(), ClipperLib.JoinType.jtMiter, ClipperLib.EndType.etClosedPolygon); 477 | 478 | var newpaths = new List>(); 479 | co.Execute(ref newpaths, offset * Config.clipperScale); 480 | 481 | 482 | var result = new List(); 483 | for (var i = 0; i < newpaths.Count; i++) 484 | { 485 | result.Add(clipperToSvg(newpaths[i])); 486 | } 487 | 488 | 489 | return result.ToArray(); 490 | } 491 | 492 | 493 | 494 | // converts a polygon from normal float coordinates to integer coordinates used by clipper, as well as x/y -> X/Y 495 | public static IntPoint[] svgToClipper2(NFP polygon, double? scale = null) 496 | { 497 | 498 | 499 | var d = _Clipper.ScaleUpPaths(polygon, scale == null ? Config.clipperScale : scale.Value); 500 | return d.ToArray(); 501 | 502 | } 503 | 504 | // converts a polygon from normal float coordinates to integer coordinates used by clipper, as well as x/y -> X/Y 505 | public static ClipperLib.IntPoint[] svgToClipper(NFP polygon) 506 | { 507 | 508 | 509 | 510 | var d = _Clipper.ScaleUpPaths(polygon, Config.clipperScale); 511 | return d.ToArray(); 512 | 513 | return polygon.Points.Select(z => new IntPoint((long)z.x, (long)z.y)).ToArray(); 514 | } 515 | // returns a less complex polygon that satisfies the curve tolerance 516 | public static NFP cleanPolygon(NFP polygon) 517 | { 518 | var p = svgToClipper2(polygon); 519 | // remove self-intersections and find the biggest polygon that's left 520 | var simple = ClipperLib.Clipper.SimplifyPolygon(p.ToList(), ClipperLib.PolyFillType.pftNonZero); 521 | 522 | if (simple == null || simple.Count == 0) 523 | { 524 | return null; 525 | } 526 | 527 | var biggest = simple[0]; 528 | var biggestarea = Math.Abs(ClipperLib.Clipper.Area(biggest)); 529 | for (var i = 1; i < simple.Count; i++) 530 | { 531 | var area = Math.Abs(ClipperLib.Clipper.Area(simple[i])); 532 | if (area > biggestarea) 533 | { 534 | biggest = simple[i]; 535 | biggestarea = area; 536 | } 537 | } 538 | 539 | // clean up singularities, coincident points and edges 540 | var clean = ClipperLib.Clipper.CleanPolygon(biggest, 0.01 * 541 | Config.curveTolerance * Config.clipperScale); 542 | 543 | if (clean == null || clean.Count == 0) 544 | { 545 | return null; 546 | } 547 | return clipperToSvg(clean); 548 | 549 | } 550 | 551 | public static NFP cleanPolygon2(NFP polygon) 552 | { 553 | var p = svgToClipper(polygon); 554 | // remove self-intersections and find the biggest polygon that's left 555 | var simple = ClipperLib.Clipper.SimplifyPolygon(p.ToList(), ClipperLib.PolyFillType.pftNonZero); 556 | 557 | if (simple == null || simple.Count == 0) 558 | { 559 | return null; 560 | } 561 | 562 | var biggest = simple[0]; 563 | var biggestarea = Math.Abs(ClipperLib.Clipper.Area(biggest)); 564 | for (var i = 1; i < simple.Count; i++) 565 | { 566 | var area = Math.Abs(ClipperLib.Clipper.Area(simple[i])); 567 | if (area > biggestarea) 568 | { 569 | biggest = simple[i]; 570 | biggestarea = area; 571 | } 572 | } 573 | 574 | // clean up singularities, coincident points and edges 575 | var clean = ClipperLib.Clipper.CleanPolygon(biggest, 0.01 * 576 | Config.curveTolerance * Config.clipperScale); 577 | 578 | if (clean == null || clean.Count == 0) 579 | { 580 | return null; 581 | } 582 | var cleaned = clipperToSvg(clean); 583 | 584 | // remove duplicate endpoints 585 | var start = cleaned[0]; 586 | var end = cleaned[cleaned.length - 1]; 587 | if (start == end || (GeometryUtil._almostEqual(start.x, end.x) 588 | && GeometryUtil._almostEqual(start.y, end.y))) 589 | { 590 | cleaned.Points = cleaned.Points.Take(cleaned.Points.Count() - 1).ToArray(); 591 | } 592 | 593 | return cleaned; 594 | 595 | } 596 | 597 | public static NFP clipperToSvg(IList polygon) 598 | { 599 | List ret = new List(); 600 | 601 | for (var i = 0; i < polygon.Count; i++) 602 | { 603 | ret.Add(new SvgPoint(polygon[i].X / Config.clipperScale, polygon[i].Y / Config.clipperScale)); 604 | } 605 | 606 | return new NFP() { Points = ret.ToArray() }; 607 | } 608 | 609 | 610 | public int toTree(PolygonTreeItem[] list, int idstart = 0) 611 | { 612 | List parents = new List(); 613 | int i, j; 614 | 615 | // assign a unique id to each leaf 616 | //var id = idstart || 0; 617 | var id = idstart; 618 | 619 | for (i = 0; i < list.Length; i++) 620 | { 621 | var p = list[i]; 622 | 623 | var ischild = false; 624 | for (j = 0; j < list.Length; j++) 625 | { 626 | if (j == i) 627 | { 628 | continue; 629 | } 630 | if (GeometryUtil.pointInPolygon(p.Polygon.Points[0], list[j].Polygon).Value) 631 | { 632 | if (list[j].Childs == null) 633 | { 634 | list[j].Childs = new List(); 635 | } 636 | list[j].Childs.Add(p); 637 | p.Parent = list[j]; 638 | ischild = true; 639 | break; 640 | } 641 | } 642 | 643 | if (!ischild) 644 | { 645 | parents.Add(p); 646 | } 647 | } 648 | 649 | for (i = 0; i < list.Length; i++) 650 | { 651 | if (parents.IndexOf(list[i]) < 0) 652 | { 653 | list = list.Skip(i).Take(1).ToArray(); 654 | i--; 655 | } 656 | } 657 | 658 | for (i = 0; i < parents.Count; i++) 659 | { 660 | parents[i].Polygon.Id = id; 661 | id++; 662 | } 663 | 664 | for (i = 0; i < parents.Count; i++) 665 | { 666 | if (parents[i].Childs != null) 667 | { 668 | id = toTree(parents[i].Childs.ToArray(), id); 669 | } 670 | } 671 | 672 | return id; 673 | } 674 | 675 | public static NFP cloneTree(NFP tree) 676 | { 677 | NFP newtree = new NFP(); 678 | foreach (var t in tree.Points) 679 | { 680 | newtree.AddPoint(new SvgPoint(t.x, t.y) { exact = t.exact }); 681 | } 682 | 683 | 684 | if (tree.children != null && tree.children.Count > 0) 685 | { 686 | newtree.children = new List(); 687 | foreach (var c in tree.children) 688 | { 689 | newtree.children.Add(cloneTree(c)); 690 | } 691 | 692 | } 693 | 694 | return newtree; 695 | } 696 | 697 | 698 | public Background background = new Background(); 699 | 700 | 701 | PopulationItem individual = null; 702 | NFP[] placelist; 703 | GeneticAlgorithm ga; 704 | 705 | public List nests = new List(); 706 | 707 | public void ResponseProcessor(SheetPlacement payload) 708 | { 709 | //console.log('ipc response', payload); 710 | if (ga == null) 711 | { 712 | // user might have quit while we're away 713 | return; 714 | } 715 | ga.population[payload.index].processing = null; 716 | ga.population[payload.index].fitness = payload.fitness; 717 | 718 | // render placement 719 | if (this.nests.Count == 0 || this.nests[0].fitness > payload.fitness) 720 | { 721 | this.nests.Insert(0, payload); 722 | 723 | if (this.nests.Count > Config.populationSize) 724 | { 725 | this.nests.RemoveAt(nests.Count - 1); 726 | } 727 | //if (displayCallback) 728 | { 729 | // displayCallback(); 730 | } 731 | } 732 | } 733 | public void launchWorkers(NestItem[] parts) 734 | { 735 | 736 | background.ResponseAction = ResponseProcessor; 737 | if (ga == null) 738 | { 739 | List adam = new List(); 740 | var id = 0; 741 | for (int i = 0; i < parts.Count(); i++) 742 | { 743 | if (!parts[i].IsSheet) 744 | { 745 | 746 | for (int j = 0; j < parts[i].Quanity; j++) 747 | { 748 | var poly = cloneTree(parts[i].Polygon); // deep copy 749 | poly.id = id; // id is the unique id of all parts that will be nested, including cloned duplicates 750 | poly.source = i; // source is the id of each unique part from the main part list 751 | 752 | adam.Add(poly); 753 | id++; 754 | } 755 | } 756 | } 757 | 758 | adam = adam.OrderByDescending(z => Math.Abs(GeometryUtil.polygonArea(z))).ToList(); 759 | /*List shuffle = new List(); 760 | Random r = new Random(DateTime.Now.Millisecond); 761 | while (adam.Any()) 762 | { 763 | var rr = r.Next(adam.Count); 764 | shuffle.Add(adam[rr]); 765 | adam.RemoveAt(rr); 766 | } 767 | adam = shuffle;*/ 768 | 769 | /*#region special case 770 | var temp = adam[1]; 771 | adam.RemoveAt(1); 772 | adam.Insert(9, temp); 773 | 774 | #endregion*/ 775 | ga = new GeneticAlgorithm(adam.ToArray(), Config); 776 | } 777 | individual = null; 778 | 779 | // check if current generation is finished 780 | var finished = true; 781 | for (int i = 0; i < ga.population.Count; i++) 782 | { 783 | if (ga.population[i].fitness == null) 784 | { 785 | finished = false; 786 | break; 787 | } 788 | } 789 | if (finished) 790 | { 791 | //console.log('new generation!'); 792 | // all individuals have been evaluated, start next generation 793 | ga.generation(); 794 | } 795 | 796 | var running = ga.population.Where((p) => 797 | { 798 | return p.processing != null; 799 | }).Count(); 800 | 801 | List sheets = new List(); 802 | List sheetids = new List(); 803 | List sheetsources = new List(); 804 | List> sheetchildren = new List>(); 805 | var sid = 0; 806 | for (int i = 0; i < parts.Count(); i++) 807 | { 808 | if (parts[i].IsSheet) 809 | { 810 | var poly = parts[i].Polygon; 811 | for (int j = 0; j < parts[i].Quanity; j++) 812 | { 813 | var cln = cloneTree(poly); 814 | cln.id = sid; // id is the unique id of all parts that will be nested, including cloned duplicates 815 | cln.source = poly.source; // source is the id of each unique part from the main part list 816 | 817 | sheets.Add(cln); 818 | sheetids.Add(sid); 819 | sheetsources.Add(i); 820 | sheetchildren.Add(poly.children); 821 | sid++; 822 | } 823 | } 824 | } 825 | for (int i = 0; i < ga.population.Count; i++) 826 | { 827 | //if(running < config.threads && !GA.population[i].processing && !GA.population[i].fitness){ 828 | // only one background window now... 829 | if (running < 1 && ga.population[i].processing == null && ga.population[i].fitness == null) 830 | { 831 | ga.population[i].processing = true; 832 | 833 | // hash values on arrays don't make it across ipc, store them in an array and reassemble on the other side.... 834 | List ids = new List(); 835 | List sources = new List(); 836 | List> children = new List>(); 837 | 838 | for (int j = 0; j < ga.population[i].placements.Count; j++) 839 | { 840 | var id = ga.population[i].placements[j].id; 841 | var source = ga.population[i].placements[j].source; 842 | var child = ga.population[i].placements[j].children; 843 | //ids[j] = id; 844 | ids.Add(id); 845 | //sources[j] = source; 846 | sources.Add(source.Value); 847 | //children[j] = child; 848 | children.Add(child); 849 | } 850 | 851 | DataInfo data = new DataInfo() 852 | { 853 | index = i, 854 | sheets = sheets, 855 | sheetids = sheetids.ToArray(), 856 | sheetsources = sheetsources.ToArray(), 857 | sheetchildren = sheetchildren, 858 | individual = ga.population[i], 859 | config = Config, 860 | ids = ids.ToArray(), 861 | sources = sources.ToArray(), 862 | children = children 863 | 864 | }; 865 | 866 | background.BackgroundStart(data); 867 | //ipcRenderer.send('background-start', { index: i, sheets: sheets, sheetids: sheetids, sheetsources: sheetsources, sheetchildren: sheetchildren, individual: GA.population[i], config: config, ids: ids, sources: sources, children: children}); 868 | running++; 869 | } 870 | } 871 | 872 | 873 | 874 | } 875 | 876 | 877 | 878 | public PolygonTreeItem[] tree; 879 | 880 | 881 | public static IntPoint[] toClipperCoordinates(NFP polygon) 882 | { 883 | var clone = new List(); 884 | for (var i = 0; i < polygon.length; i++) 885 | { 886 | clone.Add 887 | (new IntPoint( 888 | polygon[i].x, 889 | polygon[i].y 890 | 891 | )); 892 | } 893 | 894 | return clone.ToArray(); 895 | } 896 | 897 | 898 | 899 | public bool useHoles; 900 | public bool searchEdges; 901 | } 902 | 903 | 904 | public class _Clipper 905 | { 906 | public static ClipperLib.IntPoint[] ScaleUpPaths(NFP p, double scale = 1) 907 | { 908 | List ret = new List(); 909 | 910 | for (int i = 0; i < p.Points.Count(); i++) 911 | { 912 | //p.Points[i] = new SvgNestPort.SvgPoint((float)Math.Round(p.Points[i].x * scale), (float)Math.Round(p.Points[i].y * scale)); 913 | ret.Add(new ClipperLib.IntPoint( 914 | (long)Math.Round((decimal)p.Points[i].x * (decimal)scale), 915 | (long)Math.Round((decimal)p.Points[i].y * (decimal)scale) 916 | )); 917 | 918 | } 919 | return ret.ToArray(); 920 | } 921 | /*public static IntPoint[] ScaleUpPath(IntPoint[] p, double scale = 1) 922 | { 923 | for (int i = 0; i < p.Length; i++) 924 | { 925 | 926 | //p[i] = new IntPoint(p[i].X * scale, p[i].Y * scale); 927 | p[i] = new IntPoint( 928 | (long)Math.Round((decimal)p[i].X * (decimal)scale), 929 | (long)Math.Round((decimal)p[i].Y * (decimal)scale)); 930 | } 931 | return p.ToArray(); 932 | } 933 | public static void ScaleUpPaths(List> p, double scale = 1) 934 | { 935 | for (int i = 0; i < p.Count; i++) 936 | { 937 | for (int j = 0; j < p[i].Count; j++) 938 | { 939 | p[i][j] = new IntPoint(p[i][j].X * scale, p[i][j].Y * scale); 940 | 941 | } 942 | } 943 | 944 | 945 | }*/ 946 | } 947 | 948 | public static class Extensions 949 | { 950 | 951 | public static double DistTo(this SvgPoint p, SvgPoint p2) 952 | { 953 | return Math.Sqrt(Math.Pow(p.x - p2.x, 2) + Math.Pow(p.y - p2.y, 2)); 954 | } 955 | 956 | public static T[] splice(this T[] p, int a, int b) 957 | { 958 | List ret = new List(); 959 | for (int i = 0; i < p.Length; i++) 960 | { 961 | if (i >= a && i < (a + b)) continue; 962 | ret.Add(p[i]); 963 | } 964 | return ret.ToArray(); 965 | } 966 | 967 | public static List> splice(this List> p, int a, int b) 968 | { 969 | List> ret = new List>(); 970 | for (int i = a; i < (a + b); i++) 971 | { 972 | if (i >= a && i < (a + b)) continue; 973 | ret.Add(p[i]); 974 | } 975 | return ret; 976 | } 977 | 978 | public static NFP[] splice(this NFP[] p, int a, int b) 979 | { 980 | List ret = new List(); 981 | for (int i = 0; i < p.Length; i++) 982 | { 983 | if (i >= a && i < (a + b)) continue; 984 | ret.Add(p[i]); 985 | } 986 | 987 | return ret.ToArray(); 988 | } 989 | } 990 | 991 | public class DataInfo 992 | { 993 | 994 | public int index; 995 | public List sheets; 996 | public int[] sheetids; 997 | public int[] sheetsources; 998 | public List> sheetchildren; 999 | public PopulationItem individual; 1000 | public SvgNestConfig config; 1001 | public int[] ids; 1002 | public int[] sources; 1003 | public List> children; 1004 | //ipcRenderer.send('background-start', { index: i, sheets: sheets, sheetids: sheetids, sheetsources: sheetsources, sheetchildren: sheetchildren, 1005 | //individual: GA.population[i], config: config, ids: ids, sources: sources, children: children}); 1006 | 1007 | } 1008 | public class PolygonTreeItem 1009 | { 1010 | public NFP Polygon; 1011 | public PolygonTreeItem Parent; 1012 | public List Childs = new List(); 1013 | } 1014 | 1015 | public enum PlacementTypeEnum 1016 | { 1017 | box, gravity, squeeze 1018 | } 1019 | public class SvgNestConfig 1020 | { 1021 | public PlacementTypeEnum placementType = PlacementTypeEnum.box; 1022 | public double curveTolerance = 0.72; 1023 | public double scale = 25; 1024 | public double clipperScale = 10000000; 1025 | public bool exploreConcave = false; 1026 | public int mutationRate = 10; 1027 | public int populationSize = 10; 1028 | public int rotations = 4; 1029 | public double spacing = 10; 1030 | public double sheetSpacing = 0; 1031 | public bool useHoles = false; 1032 | public double timeRatio = 0.5; 1033 | public bool mergeLines = false; 1034 | public bool simplify; 1035 | } 1036 | 1037 | public class DbCacheKey 1038 | { 1039 | public int? A; 1040 | public int? B; 1041 | public float ARotation; 1042 | public float BRotation; 1043 | public NFP[] nfp; 1044 | public int Type; 1045 | } 1046 | 1047 | public class NfpPair 1048 | { 1049 | public NFP A; 1050 | public NFP B; 1051 | public NfpKey Key; 1052 | public NFP nfp; 1053 | 1054 | public float ARotation; 1055 | public float BRotation; 1056 | 1057 | public int Asource { get; internal set; } 1058 | public int Bsource { get; internal set; } 1059 | } 1060 | 1061 | public class NonameReturn 1062 | { 1063 | public NfpKey key; 1064 | public NFP[] nfp; 1065 | public NFP[] value 1066 | { 1067 | get 1068 | { 1069 | return nfp; 1070 | } 1071 | } 1072 | 1073 | public NonameReturn(NfpKey key, NFP[] nfp) 1074 | { 1075 | this.key = key; 1076 | this.nfp = nfp; 1077 | } 1078 | } 1079 | 1080 | public interface IStringify 1081 | { 1082 | string stringify(); 1083 | } 1084 | public class NfpKey : IStringify 1085 | { 1086 | 1087 | public NFP A; 1088 | public NFP B; 1089 | public float ARotation { get; set; } 1090 | public float BRotation { get; set; } 1091 | public bool Inside { get; set; } 1092 | 1093 | public int AIndex { get; set; } 1094 | public int BIndex { get; set; } 1095 | public object Asource; 1096 | public object Bsource; 1097 | 1098 | 1099 | public string stringify() 1100 | { 1101 | return $"A:{AIndex} B:{BIndex} inside:{Inside} Arotation:{ARotation} Brotation:{BRotation}"; 1102 | } 1103 | } 1104 | 1105 | 1106 | public class SvgPoint 1107 | { 1108 | public bool exact = true; 1109 | public override string ToString() 1110 | { 1111 | return "x: " + x + "; y: " + y; 1112 | } 1113 | public int id; 1114 | public SvgPoint(double _x, double _y) 1115 | { 1116 | x = _x; 1117 | y = _y; 1118 | } 1119 | public bool marked; 1120 | public double x; 1121 | public double y; 1122 | 1123 | } 1124 | 1125 | public class PopulationItem 1126 | { 1127 | public object processing = null; 1128 | 1129 | public double? fitness; 1130 | 1131 | public float[] Rotation; 1132 | public List placements; 1133 | 1134 | public NFP[] paths; 1135 | public double area; 1136 | } 1137 | 1138 | 1139 | public class SheetPlacementItem 1140 | { 1141 | public int sheetId; 1142 | public int sheetSource; 1143 | 1144 | public List sheetplacements = new List(); 1145 | public List placements = new List(); 1146 | } 1147 | 1148 | public class PlacementItem 1149 | { 1150 | public double? mergedLength; 1151 | public object mergedSegments; 1152 | public List> nfp; 1153 | public int id; 1154 | public NFP hull; 1155 | public NFP hullsheet; 1156 | 1157 | public float rotation; 1158 | public double x; 1159 | public double y; 1160 | public int source; 1161 | } 1162 | 1163 | public class SheetPlacement 1164 | { 1165 | public double? fitness; 1166 | 1167 | public float[] Rotation; 1168 | public List[] placements; 1169 | 1170 | public NFP[] paths; 1171 | public double area; 1172 | public double mergedLength; 1173 | internal int index; 1174 | } 1175 | 1176 | 1177 | 1178 | public class Sheet : NFP 1179 | { 1180 | public double Width; 1181 | public double Height; 1182 | } 1183 | 1184 | public class RectangleSheet : Sheet 1185 | { 1186 | 1187 | public void Rebuild() 1188 | { 1189 | Points = new SvgPoint[] { }; 1190 | AddPoint(new SvgPoint(x, y)); 1191 | AddPoint(new SvgPoint(x + Width, y)); 1192 | AddPoint(new SvgPoint(x + Width, y + Height)); 1193 | AddPoint(new SvgPoint(x, y + Height)); 1194 | } 1195 | } 1196 | public class NestItem 1197 | { 1198 | public NFP Polygon; 1199 | public int Quanity; 1200 | public bool IsSheet; 1201 | } 1202 | } 1203 | -------------------------------------------------------------------------------- /src/DeepNestLib/SvgParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Drawing.Drawing2D; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Xml.Linq; 10 | 11 | namespace DeepNestLib 12 | { 13 | public class SvgParser 14 | { 15 | public static RawDetail LoadSvg(string path) 16 | { 17 | XDocument doc = XDocument.Load(path); 18 | var fi = new FileInfo(path); 19 | RawDetail s = new RawDetail(); 20 | s.Name = fi.Name; 21 | List paths = new List(); 22 | var ns = doc.Descendants().First().Name.Namespace.NamespaceName; 23 | 24 | 25 | foreach (var item in doc.Descendants("path")) 26 | { 27 | var dd = (item.Attribute("d").Value); 28 | 29 | List cmnds = new List(); 30 | StringBuilder sb = new StringBuilder(); 31 | for (int i = 0; i < dd.Length; i++) 32 | { 33 | if (char.IsLetter(dd[i])) 34 | { 35 | if (sb.Length > 0) 36 | { 37 | cmnds.Add(sb.ToString()); 38 | } 39 | sb = new StringBuilder(); 40 | } 41 | sb.Append(dd[i]); 42 | } 43 | if (sb.Length > 0) 44 | { 45 | cmnds.Add(sb.ToString()); 46 | } 47 | //GraphicsPath p = new GraphicsPath(); 48 | 49 | 50 | //polygons.Add(new SvgNestPort.Polygon() { orig = item, 51 | // /*Points = p.PathPoints.Select(z => new SvgPoint(z.X, z.Y)).ToArray()*/ }); 52 | 53 | } 54 | foreach (var item in doc.Descendants("rect")) 55 | { 56 | float xx = 0; 57 | float yy = 0; 58 | if (item.Attribute("x") != null) 59 | { 60 | xx = float.Parse(item.Attribute("x").Value); 61 | } 62 | if (item.Attribute("y") != null) 63 | { 64 | yy = float.Parse(item.Attribute("y").Value); 65 | } 66 | var ww = float.Parse(item.Attribute("width").Value); 67 | var hh = float.Parse(item.Attribute("height").Value); 68 | GraphicsPath p = new GraphicsPath(); 69 | p.AddRectangle(new RectangleF(xx, yy, ww, hh)); 70 | s.Outers.Add(new LocalContour() { Points = p.PathPoints.ToList() }); 71 | 72 | } 73 | 74 | foreach (var item in doc.Descendants(XName.Get("polygon", ns))) 75 | { 76 | var str = item.Attribute("points").Value.ToString(); 77 | var spl = str.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); 78 | List points = new List(); 79 | foreach (var sitem in spl) 80 | { 81 | var spl2 = sitem.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries).ToArray(); 82 | var ar = spl2.Select(z => float.Parse(z, CultureInfo.InvariantCulture)).ToArray(); 83 | points.Add(new PointF(ar[0], ar[1])); 84 | } 85 | s.Outers.Add(new LocalContour() { Points = points.ToList() }); 86 | } 87 | 88 | 89 | return s; 90 | } 91 | public static void Export(string path, IEnumerable polygons, IEnumerable sheets) 92 | { 93 | StringBuilder sb = new StringBuilder(); 94 | sb.AppendLine(" "); 95 | 96 | foreach (var item in polygons.Union(sheets)) 97 | { 98 | if (!sheets.Contains(item)) 99 | { 100 | if (!item.fitted) continue; 101 | } 102 | var m = new Matrix(); 103 | m.Translate((float)item.x, (float)item.y); 104 | m.Rotate(item.rotation); 105 | 106 | PointF[] pp = item.Points.Select(z => new PointF((float)z.x, (float)z.y)).ToArray(); 107 | m.TransformPoints(pp); 108 | var points = pp.Select(z => new SvgPoint(z.X, z.Y)).ToArray(); 109 | 110 | string fill = "lightblue"; 111 | if (sheets.Contains(item)) 112 | { 113 | fill = "none"; 114 | } 115 | 116 | sb.AppendLine($" new PointF((float)z.x, (float)z.y)).ToArray(); 135 | m.TransformPoints(pp); 136 | points = pp.Select(z => new SvgPoint(z.X, z.Y)).Reverse().ToArray(); 137 | 138 | for (int i = 0; i < points.Count(); i++) 139 | { 140 | var p = points[i]; 141 | string coord = p.x.ToString().Replace(",", ".") + " " + p.y.ToString().Replace(",", "."); 142 | if (i == 0) 143 | { 144 | sb.Append("M" + coord + " "); 145 | continue; 146 | } 147 | 148 | sb.Append("L" + coord + " "); 149 | } 150 | sb.Append("z "); 151 | } 152 | } 153 | sb.Append("\"/>"); 154 | 155 | } 156 | sb.AppendLine(""); 157 | File.WriteAllText(path, sb.ToString()); 158 | } 159 | 160 | public static SvgConfig Conf = new SvgConfig(); 161 | // return a polygon from the given SVG element in the form of an array of points 162 | public static NFP polygonify(XElement element) 163 | { 164 | List poly = new List(); 165 | int i; 166 | 167 | switch (element.Name.LocalName) 168 | { 169 | case "polygon": 170 | case "polyline": 171 | { 172 | var pp = element.Attribute("points").Value; 173 | var spl = pp.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); 174 | foreach (var item in spl) 175 | { 176 | var spl2 = item.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(); 177 | var x = float.Parse(spl2[0], CultureInfo.InvariantCulture); 178 | var y = float.Parse(spl2[1], CultureInfo.InvariantCulture); 179 | poly.Add(new SvgPoint(x, y)); 180 | } 181 | 182 | } 183 | break; 184 | case "rect": 185 | { 186 | float x = 0; 187 | float y = 0; 188 | if (element.Attribute("x") != null) 189 | { 190 | x = float.Parse(element.Attribute("x").Value, CultureInfo.InvariantCulture); 191 | } 192 | if (element.Attribute("y") != null) 193 | { 194 | y = float.Parse(element.Attribute("y").Value, CultureInfo.InvariantCulture); 195 | } 196 | var w = float.Parse(element.Attribute("width").Value, CultureInfo.InvariantCulture); 197 | var h = float.Parse(element.Attribute("height").Value, CultureInfo.InvariantCulture); 198 | poly.Add(new SvgPoint(x, y)); 199 | poly.Add(new SvgPoint(x + w, y)); 200 | poly.Add(new SvgPoint(x + w, y + h)); 201 | poly.Add(new SvgPoint(x, y + h)); 202 | } 203 | 204 | 205 | break; 206 | case "circle": 207 | throw new NotImplementedException(); 208 | 209 | break; 210 | case "ellipse": 211 | throw new NotImplementedException(); 212 | 213 | break; 214 | case "path": 215 | throw new NotImplementedException(); 216 | 217 | // // we'll assume that splitpath has already been run on this path, and it only has one M/m command 218 | // var seglist = element.pathSegList; 219 | 220 | // var firstCommand = seglist.getItem(0); 221 | // var lastCommand = seglist.getItem(seglist.numberOfItems - 1); 222 | 223 | // var x = 0, y = 0, x0 = 0, y0 = 0, x1 = 0, y1 = 0, x2 = 0, y2 = 0, prevx = 0, prevy = 0, prevx1 = 0, prevy1 = 0, prevx2 = 0, prevy2 = 0; 224 | 225 | // for (var i = 0; i < seglist.numberOfItems; i++) 226 | // { 227 | // var s = seglist.getItem(i); 228 | // var command = s.pathSegTypeAsLetter; 229 | 230 | // prevx = x; 231 | // prevy = y; 232 | 233 | // prevx1 = x1; 234 | // prevy1 = y1; 235 | 236 | // prevx2 = x2; 237 | // prevy2 = y2; 238 | 239 | // if (/[MLHVCSQTA] /.test(command)) 240 | // { 241 | // if ('x1' in s) x1 = s.x1; 242 | // if ('x2' in s) x2 = s.x2; 243 | // if ('y1' in s) y1 = s.y1; 244 | // if ('y2' in s) y2 = s.y2; 245 | // if ('x' in s) x = s.x; 246 | // if ('y' in s) y = s.y; 247 | // } 248 | // else{ 249 | // if ('x1' in s) x1=x+s.x1; 250 | // if ('x2' in s) x2=x+s.x2; 251 | // if ('y1' in s) y1=y+s.y1; 252 | // if ('y2' in s) y2=y+s.y2; 253 | // if ('x' in s) x+=s.x; 254 | // if ('y' in s) y+=s.y; 255 | // } 256 | // switch(command){ 257 | // // linear line types 258 | // case 'm': 259 | // case 'M': 260 | // case 'l': 261 | // case 'L': 262 | // case 'h': 263 | // case 'H': 264 | // case 'v': 265 | // case 'V': 266 | // var point = { }; 267 | // point.x = x; 268 | // point.y = y; 269 | // poly.push(point); 270 | // break; 271 | // // Quadratic Beziers 272 | // case 't': 273 | // case 'T': 274 | // // implicit control point 275 | // if(i > 0 && /[QqTt]/.test(seglist.getItem(i-1).pathSegTypeAsLetter)){ 276 | // x1 = prevx + (prevx-prevx1); 277 | // y1 = prevy + (prevy-prevy1); 278 | // } 279 | // else{ 280 | // x1 = prevx; 281 | // y1 = prevy; 282 | // } 283 | // case 'q': 284 | // case 'Q': 285 | // var pointlist = GeometryUtil.QuadraticBezier.linearize({x: prevx, y: prevy}, {x: x, y: y}, {x: x1, y: y1}, this.conf.tolerance); 286 | //pointlist.shift(); // firstpoint would already be in the poly 287 | // for(var j=0; j 0 && /[CcSs]/.test(seglist.getItem(i-1).pathSegTypeAsLetter)){ 297 | // x1 = prevx + (prevx - prevx2); 298 | // y1 = prevy + (prevy - prevy2); 299 | //} 300 | // else{ 301 | // x1 = prevx; 302 | // y1 = prevy; 303 | //} 304 | // case 'c': 305 | // case 'C': 306 | // var pointlist = GeometryUtil.CubicBezier.linearize({ x: prevx, y: prevy}, { x: x, y: y}, { x: x1, y: y1}, { x: x2, y: y2}, this.conf.tolerance); 307 | //pointlist.shift(); // firstpoint would already be in the poly 308 | // for(var j=0; j 0 && GeometryUtil._almostEqual(poly[0].x, poly[poly.Count - 1].x, Conf.toleranceSvg) 338 | && GeometryUtil._almostEqual(poly[0].y, poly[poly.Count - 1].y, Conf.toleranceSvg)) 339 | { 340 | poly.RemoveAt(0); 341 | } 342 | 343 | return new NFP() { Points = poly.ToArray() }; 344 | } 345 | 346 | 347 | 348 | } 349 | 350 | public class SvgConfig 351 | { 352 | public float tolerance = 2f; // max bound for bezier->line segment conversion, in native SVG units 353 | public float toleranceSvg = 0.005f;// fudge factor for browser inaccuracy in SVG unit handling 354 | 355 | } 356 | public class LocalContour 357 | { 358 | public float Len 359 | { 360 | get 361 | { 362 | float len = 0; 363 | for (int i = 1; i <= Points.Count; i++) 364 | { 365 | var p1 = Points[i - 1]; 366 | var p2 = Points[i % Points.Count]; 367 | len += (float)Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2)); 368 | } 369 | return len; 370 | } 371 | } 372 | public List Points = new List(); 373 | public bool Enable = true; 374 | } 375 | 376 | 377 | } 378 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/DeepNestPlugin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {4801C040-147F-4273-A406-0B04FBF50F0B} 9 | Library 10 | Properties 11 | NestingOpenSource 12 | NestingOpenSource 13 | v4.5 14 | 512 15 | false 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\ 22 | DEBUG;TRACE 23 | prompt 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | False 37 | ..\MinkowskiWrapper\bin\Debug\MinkowskiWrapper.dll 38 | 39 | 40 | 41 | 42 | ..\..\..\..\..\..\Program Files\Rhino 6\System\RhinoWindows.dll 43 | False 44 | 45 | 46 | 47 | 48 | 49 | 50 | False 51 | C:\Program Files\Rhino 6\System\rhinocommon.dll 52 | False 53 | 54 | 55 | False 56 | C:\Program Files\Rhino 6\System\Eto.dll 57 | False 58 | 59 | 60 | False 61 | C:\Program Files\Rhino 6\System\Rhino.UI.dll 62 | False 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | True 77 | True 78 | Resources.resx 79 | 80 | 81 | 82 | 83 | 84 | ResXFileCodeGenerator 85 | Resources.Designer.cs 86 | 87 | 88 | 89 | 90 | {9b062971-a88e-4a3d-b3c9-12b78d15fa66} 91 | clipper_library 92 | 93 | 94 | {32f6dd67-ef43-49e7-a6b3-4786344baf14} 95 | DeepNestLib 96 | 97 | 98 | 99 | 100 | Designer 101 | XamlIntelliSenseFileGenerator 102 | 103 | 104 | 105 | 106 | 107 | 108 | 115 | 116 | Copy "$(TargetPath)" "$(TargetDir)$(ProjectName).rhp" 117 | Erase "$(TargetPath)" 118 | 119 | 120 | en-US 121 | 122 | 123 | C:\Program Files\Rhino 6\System\Rhino.exe 124 | 125 | 126 | Program 127 | x64 128 | 129 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/EmbeddedResources/plugin-utility.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RafaeldelM/nesting/fdf5aead5a31744980a6d0f7184d1914432a5380/src/DeepNestPlugin/EmbeddedResources/plugin-utility.ico -------------------------------------------------------------------------------- /src/DeepNestPlugin/NestingOpenSourceCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net.Mime; 7 | using System.Threading; 8 | using DeepNestLib; 9 | using Eto.Forms; 10 | using Rhino; 11 | using Rhino.Commands; 12 | using Rhino.Geometry; 13 | using Rhino.Input; 14 | using Rhino.Input.Custom; 15 | using Command = Rhino.Commands.Command; 16 | using Result = Rhino.Commands.Result; 17 | 18 | namespace NestingOpenSource 19 | { 20 | public class NestingOpenSourceCommand : Command 21 | { 22 | public NestingOpenSourceCommand() 23 | { 24 | // Rhino only creates one instance of each command class defined in a 25 | // plug-in, so it is safe to store a refence in a static property. 26 | Instance = this; 27 | } 28 | 29 | ///The only instance of this command. 30 | public static NestingOpenSourceCommand Instance 31 | { 32 | get; private set; 33 | } 34 | 35 | ///The command name as it appears on the Rhino command line. 36 | public override string EnglishName 37 | { 38 | get { return "Nesting"; } 39 | } 40 | 41 | NestingContext context = new NestingContext(); 42 | Thread th; 43 | 44 | List sheets { get { return context.Sheets; } } 45 | List polygons { get { return context.Polygons; } } 46 | public SvgNest nest { get { return context.Nest; } } 47 | 48 | bool stop = false; 49 | protected override Result RunCommand(RhinoDoc doc, RunMode mode) 50 | { 51 | 52 | // var cnt = GetCountFromDialog(); 53 | Random r = new Random(); 54 | for (int i = 0; i < 5; i++) 55 | { 56 | var xx = r.Next(2000) + 100; 57 | var yy = r.Next(2000); 58 | var ww = r.Next(60) + 10; 59 | var hh = r.Next(60) + 5; 60 | NFP pl = new NFP(); 61 | int src = 0; 62 | if (polygons.Any()) 63 | { 64 | src = polygons.Max(z => z.source.Value) + 1; 65 | } 66 | polygons.Add(pl); 67 | pl.source = src; 68 | pl.x = xx; 69 | pl.y = yy; 70 | pl.Points = new SvgPoint[] { }; 71 | pl.AddPoint(new SvgPoint(0, 0)); 72 | pl.AddPoint(new SvgPoint(ww, 0)); 73 | pl.AddPoint(new SvgPoint(ww, hh)); 74 | pl.AddPoint(new SvgPoint(0, hh)); 75 | } 76 | // UpdateList(); 77 | 78 | 79 | List sh = new List(); 80 | var srcAA = context.GetNextSheetSource(); 81 | 82 | sh.Add(NewSheet(3000, 2000)); 83 | foreach (var item in sh) 84 | { 85 | item.source = srcAA; 86 | context.Sheets.Add(item); 87 | } 88 | 89 | 90 | if (sheets.Count == 0 || polygons.Count == 0) 91 | { 92 | MessageBox.Show("There are no sheets or parts", MessageBoxButtons.OK); 93 | return Result.Success; ; 94 | } 95 | stop = false; 96 | // progressBar1.Value = 0; 97 | // tabControl1.SelectedTab = tabPage4; 98 | context.ReorderSheets(); 99 | RunDeepnest(); 100 | 101 | return Result.Success; 102 | } 103 | 104 | public Sheet NewSheet(int w = 3000, int h = 1500) 105 | { 106 | var tt = new RectangleSheet(); 107 | tt.Name = "rectSheet" + (sheets.Count + 1); 108 | tt.Height = h; 109 | tt.Width = w; 110 | tt.Rebuild(); 111 | 112 | return tt; 113 | } 114 | 115 | public void RunDeepnest() 116 | { 117 | 118 | if (th == null) 119 | { 120 | th = new Thread(() => 121 | { 122 | context.StartNest(); 123 | UpdateNestsList(); 124 | Background.displayProgress = displayProgress; 125 | 126 | while (true) 127 | { 128 | Stopwatch sw = new Stopwatch(); 129 | sw.Start(); 130 | 131 | context.NestIterate(); 132 | UpdateNestsList(); 133 | displayProgress(1.0f); 134 | sw.Stop(); 135 | //toolStripStatusLabel1.Text = "Nesting time: " + sw.ElapsedMilliseconds + "ms"; 136 | if (stop) break; 137 | } 138 | th = null; 139 | }); 140 | th.IsBackground = true; 141 | th.Start(); 142 | } 143 | } 144 | 145 | internal void displayProgress(float progress) 146 | { 147 | RhinoApp.WriteLine(progress.ToString()); 148 | progressVal = progress; 149 | 150 | } 151 | public float progressVal = 0; 152 | public void UpdateNestsList() 153 | { 154 | //if (nest != null) 155 | //{ 156 | // listView4.Invoke((Action)(() => 157 | // { 158 | // listView4.BeginUpdate(); 159 | // listView4.Items.Clear(); 160 | // foreach (var item in nest.nests) 161 | // { 162 | // listView4.Items.Add(new ListViewItem(new string[] { item.fitness + "" }) { Tag = item }); 163 | // } 164 | // listView4.EndUpdate(); 165 | // })); 166 | //} 167 | } 168 | 169 | 170 | 171 | 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/NestingOpenSourcePlugIn.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rhino.PlugIns; 3 | using RhinoWindows.Controls; 4 | using DockBarPanel = NestingOpenSource.Panel.DockBarPanel; 5 | 6 | namespace NestingOpenSource 7 | { 8 | /// 9 | /// Every RhinoCommon .rhp assembly must have one and only one PlugIn-derived 10 | /// class. DO NOT create instances of this class yourself. It is the 11 | /// responsibility of Rhino to create an instance of this class. 12 | /// To complete plug-in information, please also see all PlugInDescription 13 | /// attributes in AssemblyInfo.cs (you might need to click "Project" -> 14 | /// "Show All Files" to see it in the "Solution Explorer" window). 15 | /// 16 | public class NestingOpenSourcePlugIn : Rhino.PlugIns.PlugIn 17 | { 18 | 19 | private WpfDockBar m_wpf_bar; 20 | 21 | 22 | public NestingOpenSourcePlugIn() 23 | { 24 | Instance = this; 25 | } 26 | 27 | ///Gets the only instance of the NestingOpenSourcePlugIn plug-in. 28 | public static NestingOpenSourcePlugIn Instance 29 | { 30 | get; private set; 31 | } 32 | 33 | protected override LoadReturnCode OnLoad(ref string errorMessage) 34 | { 35 | 36 | CreateMyDockBar(); 37 | 38 | return base.OnLoad(ref errorMessage); 39 | } 40 | 41 | private void CreateMyDockBar() 42 | { 43 | var create_options = new DockBarCreateOptions 44 | { 45 | DockLocation = DockBarDockLocation.Right, 46 | Visible = false, 47 | DockStyle = DockBarDockStyle.Any, 48 | FloatPoint = new System.Drawing.Point(100, 100) 49 | }; 50 | 51 | 52 | m_wpf_bar = new WpfDockBar(); 53 | m_wpf_bar.Create(create_options); 54 | } 55 | 56 | 57 | // You can override methods here to change the plug-in behavior on 58 | // loading and shut down, add options pages to the Rhino _Option command 59 | // and maintain plug-in wide options in a document. 60 | } 61 | 62 | 63 | /// 64 | /// WpfDockBar dockbar class 65 | /// 66 | internal class WpfDockBar : DockBar 67 | { 68 | public static Guid BarId => new Guid("{5faf6cd0-c387-46cd-83b3-1445c126cf55}"); 69 | public WpfDockBar() : base(NestingOpenSourcePlugIn.Instance, BarId, "DeepNest") 70 | { 71 | SetContentControl(new WpfHost(new DockBarPanel(), null)); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/DeepNestPlugin/Panel/SampleCsDockbarCommand.cs: -------------------------------------------------------------------------------- 1 | using Rhino; 2 | using Rhino.Commands; 3 | 4 | namespace NestingOpenSource.Panel 5 | { 6 | public class SampleCsDockbarCommand : Command 7 | { 8 | public override string EnglishName => "Deepnest"; 9 | 10 | protected override Result RunCommand(RhinoDoc doc, RunMode mode) 11 | { 12 | RhinoWindows.Controls.DockBar.Show(WpfDockBar.BarId, false); 13 | return Result.Success; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/Panel/SampleCsWpfPanel.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/Panel/SampleCsWpfPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace NestingOpenSource.Panel 5 | { 6 | /// 7 | /// Interaction logic for DockBarPanel.xaml 8 | /// 9 | public partial class DockBarPanel : UserControl 10 | { 11 | public DockBarPanel() 12 | { 13 | InitializeComponent(); 14 | Visibility = Visibility.Visible; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using Rhino.PlugIns; 5 | 6 | // Plug-in Description Attributes - all of these are optional. 7 | // These will show in Rhino's option dialog, in the tab Plug-ins. 8 | [assembly: PlugInDescription(DescriptionType.Address, "Av. Ernest Lluc 32, Mataro, Barcelona")] 9 | [assembly: PlugInDescription(DescriptionType.Country, "Spain")] 10 | [assembly: PlugInDescription(DescriptionType.Email, "rafaeldelmolino@gmail.com")] 11 | [assembly: PlugInDescription(DescriptionType.Phone, "-")] 12 | [assembly: PlugInDescription(DescriptionType.Fax, "-")] 13 | [assembly: PlugInDescription(DescriptionType.Organization, "-")] 14 | [assembly: PlugInDescription(DescriptionType.UpdateUrl, "-")] 15 | [assembly: PlugInDescription(DescriptionType.WebSite, "https://github.com/RafaeldelM/nesting")] 16 | 17 | // Icons should be Windows .ico files and contain 32-bit images in the following sizes: 16, 24, 32, 48, and 256. 18 | // This is a Rhino 6-only description. 19 | [assembly: PlugInDescription(DescriptionType.Icon, "NestingOpenSource.EmbeddedResources.plugin-utility.ico")] 20 | 21 | // General Information about an assembly is controlled through the following 22 | // set of attributes. Change these attribute values to modify the information 23 | // associated with an assembly. 24 | [assembly: AssemblyTitle("DeepNest")] 25 | 26 | // This will be used also for the plug-in description. 27 | [assembly: AssemblyDescription("DeepNest utility plug-in")] 28 | 29 | [assembly: AssemblyConfiguration("")] 30 | [assembly: AssemblyCompany("")] 31 | [assembly: AssemblyProduct("DeepNest")] 32 | [assembly: AssemblyCopyright("Copyright © 2019")] 33 | [assembly: AssemblyTrademark("")] 34 | [assembly: AssemblyCulture("")] 35 | 36 | // Setting ComVisible to false makes the types in this assembly not visible 37 | // to COM components. If you need to access a type in this assembly from 38 | // COM, set the ComVisible attribute to true on that type. 39 | [assembly: ComVisible(false)] 40 | 41 | // The following GUID is for the ID of the typelib if this project is exposed to COM 42 | [assembly: Guid("4801c040-147f-4273-a406-0b04fbf50f0b")] // This will also be the Guid of the Rhino plug-in 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | 55 | [assembly: AssemblyVersion("1.0.0.0")] 56 | [assembly: AssemblyFileVersion("1.0.0.0")] 57 | 58 | // Make compatible with Rhino Installer Engine 59 | [assembly: AssemblyInformationalVersion("2")] 60 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // Este código fue generado por una herramienta. 4 | // Versión de runtime:4.0.30319.42000 5 | // 6 | // Los cambios en este archivo podrían causar un comportamiento incorrecto y se perderán si 7 | // se vuelve a generar el código. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace NestingOpenSource.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// Clase de recurso fuertemente tipado, para buscar cadenas traducidas, etc. 17 | /// 18 | // StronglyTypedResourceBuilder generó automáticamente esta clase 19 | // a través de una herramienta como ResGen o Visual Studio. 20 | // Para agregar o quitar un miembro, edite el archivo .ResX y, a continuación, vuelva a ejecutar ResGen 21 | // con la opción /str o recompile su proyecto de VS. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Devuelve la instancia de ResourceManager almacenada en caché utilizada por esta clase. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NestingOpenSource.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Reemplaza la propiedad CurrentUICulture del subproceso actual para todas las 51 | /// búsquedas de recursos mediante esta clase de recurso fuertemente tipado. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Busca un recurso adaptado de tipo System.Drawing.Icon similar a (Icono). 65 | /// 66 | internal static System.Drawing.Icon SampleCsDockBar { 67 | get { 68 | object obj = ResourceManager.GetObject("SampleCsDockBar", resourceCulture); 69 | return ((System.Drawing.Icon)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\SampleCsDockBar.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /src/DeepNestPlugin/Resources/SampleCsDockBar.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RafaeldelM/nesting/fdf5aead5a31744980a6d0f7184d1914432a5380/src/DeepNestPlugin/Resources/SampleCsDockBar.ico -------------------------------------------------------------------------------- /src/MinkowskiWrapper/MinkowskiWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace Minkowski 4 | { 5 | public class MinkowskiWrapper 6 | { 7 | [DllImport("minkowski.dll", CallingConvention = CallingConvention.Cdecl)] 8 | public static extern void setData(int cntA, double[] pntsA, int holesCnt, int[] holesSizes, double[] holesPoints, int cntB, double[] pntsB); 9 | 10 | [DllImport("minkowski.dll", CallingConvention = CallingConvention.Cdecl)] 11 | public static extern void calculateNFP(); 12 | 13 | [DllImport("minkowski.dll", CallingConvention = CallingConvention.Cdecl)] 14 | public static extern void getSizes1(int[] sizes); 15 | 16 | [DllImport("minkowski.dll", CallingConvention = CallingConvention.Cdecl)] 17 | public static extern void getSizes2(int[] sizes1, int[] sizes2); 18 | 19 | [DllImport("minkowski.dll", CallingConvention = CallingConvention.Cdecl)] 20 | public static extern void getResults(double[] data, double[] holesData); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/MinkowskiWrapper/MinkowskiWrapper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E09E5402-28CF-4C96-9410-1AF2A44833E0} 8 | Library 9 | Properties 10 | Minkowski 11 | MinkowskiWrapper 12 | v4.0 13 | 512 14 | 15 | 16 | x64 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ResXFileCodeGenerator 54 | Resources.Designer.cs 55 | Designer 56 | 57 | 58 | True 59 | Resources.resx 60 | True 61 | 62 | 63 | SettingsSingleFileGenerator 64 | Settings.Designer.cs 65 | 66 | 67 | True 68 | Settings.settings 69 | True 70 | 71 | 72 | 73 | 80 | -------------------------------------------------------------------------------- /src/MinkowskiWrapper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("minkowskyServer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("minkowskyServer")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e09e5402-28cf-4c96-9410-1af2a44833e0")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/MinkowskiWrapper/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Minkowski.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Minkowski.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/MinkowskiWrapper/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /src/MinkowskiWrapper/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Minkowski.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/MinkowskiWrapper/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/clipper/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("clipper_library")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Angus Johnson")] 12 | [assembly: AssemblyProduct("clipper_library")] 13 | [assembly: AssemblyCopyright("Copyright © Angus Johnson 2010-14")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("51a6bdca-bc4e-4b2c-ae69-36e2497204f2")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/clipper/clipper_library.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {9B062971-A88E-4A3D-B3C9-12B78D15FA66} 9 | Library 10 | Properties 11 | ClipperLib 12 | clipper_library 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | x64 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | --------------------------------------------------------------------------------