├── src ├── Optimize.cpp ├── Polygon.cpp ├── SvgPath.cpp ├── PlacedPiece.cpp ├── SavePlacedPiece.cpp ├── ConvexHull.cpp └── CachePosition.cpp ├── include ├── ConvexHull.h ├── PlacedPiece.h ├── SavePlacedPiece.h ├── SvgPath.h ├── CachePosition.h ├── SvgDoc.h └── Geometry.h ├── LICENSE ├── CutOptim.depend ├── cutoptim.inx ├── TestCroix.svg ├── Test ├── TestCroix.svg ├── pentagon_4.svg ├── pentagon_6.svg ├── TestPoly3.svg ├── TestPoly9.svg ├── shape0-path.svg ├── TestPoly4.svg └── coeur26.svg ├── CutOptim └── CutOptim │ ├── CutOptim.vcxproj.filters │ └── CutOptim.vcxproj ├── CutOptim.cbp ├── Makefile ├── shape0.svg ├── main.cpp └── README.md /src/Optimize.cpp: -------------------------------------------------------------------------------- 1 | #include "Geometry.h" 2 | #include 3 | #include 4 | 5 | -------------------------------------------------------------------------------- /src/Polygon.cpp: -------------------------------------------------------------------------------- 1 | #include "Polygon.h" 2 | 3 | Polygon::Polygon() 4 | { 5 | //ctor 6 | } 7 | 8 | Polygon::~Polygon() 9 | { 10 | //dtor 11 | } 12 | -------------------------------------------------------------------------------- /src/SvgPath.cpp: -------------------------------------------------------------------------------- 1 | #include "SvgPath.h" 2 | 3 | SvgPath::SvgPath() 4 | { 5 | //ctor 6 | } 7 | 8 | SvgPath::~SvgPath() 9 | { 10 | //dtor 11 | } 12 | -------------------------------------------------------------------------------- /src/PlacedPiece.cpp: -------------------------------------------------------------------------------- 1 | #include "PlacedPiece.h" 2 | 3 | PlacedPiece::PlacedPiece() 4 | { 5 | //ctor 6 | } 7 | 8 | PlacedPiece::~PlacedPiece() 9 | { 10 | //dtor 11 | } 12 | -------------------------------------------------------------------------------- /src/SavePlacedPiece.cpp: -------------------------------------------------------------------------------- 1 | #include "SavePlacedPiece.h" 2 | 3 | SavePlacedPiece::SavePlacedPiece() 4 | { 5 | //ctor 6 | } 7 | 8 | SavePlacedPiece::~SavePlacedPiece() 9 | { 10 | //dtor 11 | } 12 | -------------------------------------------------------------------------------- /include/ConvexHull.h: -------------------------------------------------------------------------------- 1 | #ifndef CONVEXHULL_H_INCLUDED 2 | #define CONVEXHULL_H_INCLUDED 3 | 4 | 5 | vector makeConvexHull(const vector &points); 6 | vector CombineHull(const vector &points_1, const vector &points_2); 7 | double HullArea(const vector &points); 8 | Rectangle Hull2BoundingBox(const vector &points); 9 | 10 | 11 | #endif // CONVEXHULL_H_INCLUDED 12 | -------------------------------------------------------------------------------- /include/PlacedPiece.h: -------------------------------------------------------------------------------- 1 | #ifndef PLACEDPIECE_H 2 | #define PLACEDPIECE_H 3 | 4 | 5 | class PlacedPiece 6 | { 7 | public: 8 | PlacedPiece(int nRot, int nEdges); 9 | virtual ~PlacedPiece(); 10 | 11 | int isPlaceOK(iRot, iEdge, iPoly, jRot, jEdge, jPoly); 12 | 13 | protected: 14 | Polygon **PlacedPoly; // Array used to store the placed polygon with rotation and translation. 15 | int numRot; 16 | int numEdges; 17 | size_t size_PlacedPoly; 18 | private: 19 | }; 20 | 21 | #endif // PLACEDPIECE_H 22 | -------------------------------------------------------------------------------- /include/SavePlacedPiece.h: -------------------------------------------------------------------------------- 1 | #ifndef SAVEPLACEDPIECE_H 2 | #define SAVEPLACEDPIECE_H 3 | 4 | #include "Geometry.h" 5 | 6 | struct PiecePosition { 7 | unsigned short idx_Rot1; 8 | unsigned short idx_Vertex1; 9 | unsigned short idxPoly2; 10 | unsigned short idx_Rot2; 11 | unsigned short idx_Vertex2; 12 | }; 13 | 14 | 15 | class SavePlacedPiece 16 | { 17 | public: 18 | SavePlacedPiece(Polygon *Poly, int idxPoly, int numVertex); 19 | virtual ~SavePlacedPiece(); 20 | int isOK(int idx_rot1, int idx_vertex1, int idxPoly2, int idx_rot2, int idx_vertex2); 21 | 22 | protected: 23 | unsigned char CachedOK 24 | 25 | private: 26 | }; 27 | 28 | #endif // SAVEPLACEDPIECE_H 29 | -------------------------------------------------------------------------------- /include/SvgPath.h: -------------------------------------------------------------------------------- 1 | #ifndef SVGPATH_H 2 | #define SVGPATH_H 3 | 4 | #include "Geometry.h" 5 | 6 | class PathElt 7 | { 8 | public: 9 | int TypeElt; 10 | Point EndPoint; 11 | Point EltBezier1, EltBezier2; 12 | PathElt(int Type, Point End, Point Bezier1=Point(0,0), Point Bezier2=Point(0,0)) 13 | { 14 | TypeElt = Type; 15 | EndPoint = End; 16 | EltBezier1 = Bezier1; 17 | EltBezier1 = Bezier2; 18 | } 19 | }; 20 | 21 | class SvgPath 22 | { 23 | public: 24 | SvgPath(); 25 | virtual ~SvgPath(); 26 | 27 | inline int isClosed() { return(ClosedPath); } 28 | 29 | 30 | 31 | protected: 32 | int ClosedPath; 33 | Point StartPath; 34 | Rectangle BoundingBox; 35 | private: 36 | }; 37 | 38 | #endif // SVGPATH_H 39 | -------------------------------------------------------------------------------- /include/CachePosition.h: -------------------------------------------------------------------------------- 1 | #ifndef CACHEPOSITION_H 2 | #define CACHEPOSITION_H 3 | 4 | #include "Geometry.h" 5 | 6 | class CachePosition 7 | { 8 | public: 9 | CachePosition(int numVertex, int num_rot, int numPlacedVertex); 10 | virtual ~CachePosition(); 11 | int isOKPlaced(int iVertex, int iRot, int iPlacedVertex, Polygon **PlacedPoly); 12 | void addOKPlaced(int iVertex, int iRot, int iPlacedVertex, Polygon *Poly); 13 | int isOKPlacedFreeRot(int iVertex, int iRot, int iPlacedVertex, Polygon **PlacedPoly); 14 | void addOKPlacedFreeRot(int iVertex, int iRot, int iPlacedVertex, Polygon *Poly); 15 | protected: 16 | size_t sizeCacheEntry1; 17 | size_t sizeCacheEntry2; 18 | size_t memCache; 19 | int currentIdxCache; 20 | Polygon **CachePlaced; // Polygon cached, if impossible set to 0 21 | int *FixedPerRot; // Index of cached value per rotation. Used when vertices are added (new polygons) 22 | 23 | private: 24 | }; 25 | 26 | #endif // CACHEPOSITION_H 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 thierry7100 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 | -------------------------------------------------------------------------------- /CutOptim.depend: -------------------------------------------------------------------------------- 1 | # depslib dependency file v1.0 2 | 1554279590 source:/home/thierry/Programmes/CutOptim/main.cpp 3 | 4 | "cxxopts.hpp" 5 | "SvgDoc.h" 6 | "Geometry.h" 7 | 8 | 9 | 10 | 1554135735 source:/home/thierry/Programmes/CutOptim/src/Geometry.cpp 11 | "Geometry.h" 12 | 13 | 1554136169 /home/thierry/Programmes/CutOptim/include/Geometry.h 14 | 15 | 16 | 17 | 18 | 19 | 1554280994 source:/home/thierry/Programmes/CutOptim/src/SvgDoc.cpp 20 | "SvgDoc.h" 21 | 22 | 23 | 24 | 25 | 26 | "Geometry.h" 27 | "ConvexHull.h" 28 | 29 | 30 | 1554025412 /home/thierry/Programmes/CutOptim/include/SvgDoc.h 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | "nanosvg.h" 39 | 40 | 1547658309 source:/home/thierry/Programmes/CutOptim/src/SvgPath.cpp 41 | "SvgPath.h" 42 | 43 | 1547660714 /home/thierry/Programmes/CutOptim/include/SvgPath.h 44 | "Geometry.h" 45 | 46 | 1547416001 /home/thierry/Programmes/CutOptim/include/cxxopts.hpp 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 1553091648 /home/thierry/Programmes/CutOptim/include/nanosvg.h 63 | "Geometry.h" 64 | "CachePosition.h" 65 | 66 | 67 | 68 | 69 | 1548928589 source:/home/thierry/Programmes/CutOptim/src/ConvexHull.cpp 70 | 71 | "Geometry.h" 72 | 73 | 1548837095 /home/thierry/Programmes/CutOptim/include/ConvexHull.h 74 | 75 | 1552066086 source:/home/thierry/Programmes/CutOptim/src/SavePlacedPiece.cpp 76 | "SavePlacedPiece.h" 77 | 78 | 1552476998 /home/thierry/Programmes/CutOptim/include/SavePlacedPiece.h 79 | 80 | 81 | 1554279000 source:/home/thierry/Programmes/CutOptim/src/CachePosition.cpp 82 | "CachePosition.h" 83 | 84 | 85 | 1554030181 /home/thierry/Programmes/CutOptim/include/CachePosition.h 86 | "Geometry.h" 87 | 88 | -------------------------------------------------------------------------------- /cutoptim.inx: -------------------------------------------------------------------------------- 1 | 2 | <_name>Laser Cutting Optmizer 3 | fr.fablab-lannion.inkscape.cutopimiser 4 | CutOptim 5 | 6 | <_item value="mm">mm 7 | <_item value="cm">cm 8 | <_item value="m">m 9 | <_item value="km">km 10 | <_item value="in">in 11 | <_item value="ft">ft 12 | <_item value="yd">yd 13 | <_item value="pt">pt 14 | <_item value="px">px 15 | <_item value="pc">pc 16 | 17 | 2.0 18 | 1000 19 | 1 20 | false 21 | 22 | <_item value="TL">Top Left 23 | <_item value="TC">Top Center 24 | <_item value="TR">Top Right 25 | <_item value="CL">Sheet center 26 | <_item value="CC">Center Left 27 | <_item value="CR">Center Right 28 | <_item value="BL">Bottom Left 29 | <_item value="BC">Bottom Center 30 | <_item value="BR">Bottom Right 31 | 32 | true 33 | 0 34 | true 35 | true 36 | 37 | all 38 | 39 | 40 | 41 | 42 | 45 | 46 | -------------------------------------------------------------------------------- /TestCroix.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 58 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Test/TestCroix.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 58 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Test/pentagon_4.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 42 | 48 | 54 | 60 | 66 | 67 | -------------------------------------------------------------------------------- /CutOptim/CutOptim/CutOptim.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Fichiers d%27en-tête 20 | 21 | 22 | Fichiers d%27en-tête 23 | 24 | 25 | Fichiers d%27en-tête 26 | 27 | 28 | Fichiers d%27en-tête 29 | 30 | 31 | Fichiers d%27en-tête 32 | 33 | 34 | Fichiers d%27en-tête 35 | 36 | 37 | Fichiers d%27en-tête 38 | 39 | 40 | Fichiers d%27en-tête 41 | 42 | 43 | 44 | 45 | Fichiers sources 46 | 47 | 48 | Fichiers sources 49 | 50 | 51 | Fichiers sources 52 | 53 | 54 | Fichiers sources 55 | 56 | 57 | Fichiers sources 58 | 59 | 60 | Fichiers sources 61 | 62 | 63 | Fichiers sources 64 | 65 | 66 | Fichiers sources 67 | 68 | 69 | Fichiers sources 70 | 71 | 72 | -------------------------------------------------------------------------------- /CutOptim.cbp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 88 | 89 | -------------------------------------------------------------------------------- /Test/pentagon_6.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 40 | 46 | 52 | 58 | 64 | 70 | 77 | 78 | -------------------------------------------------------------------------------- /include/SvgDoc.h: -------------------------------------------------------------------------------- 1 | #ifndef SVGDOC_H 2 | #define SVGDOC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "nanosvg.h" 13 | 14 | using namespace std; 15 | 16 | class SvgDoc 17 | { 18 | public: 19 | SvgDoc(string &FileName); 20 | virtual ~SvgDoc(); 21 | 22 | NSVGimage *SvgData; 23 | double SheetSizeX, SheetSizeY; 24 | void TransformPaths(double flat_factor, bool KeepNested); 25 | void EnlargePaths(double Diff); 26 | void BreakLongerEdges(double max_length, double Diff); 27 | void BuilSingleListPath(); 28 | void SortbyArea(); 29 | void ComputeConvexHulls(); 30 | double Optimize(double StepAngle, int OptimizingLevel, int FirstPos, int Flag_File); 31 | double OptimizeFreeRot(int OptimizingLevel, int FirstPos, int Flag_File); 32 | void setDebugLevel(int level, string DebugFileName) { debug_level = level; OutDebug.open(DebugFileName); } 33 | void setUseCache(int Val) { UseCache = Val;} 34 | void setRectCost(double Val) { RectCostFactor = Val;} 35 | 36 | int WriteDoc(string &FileName, int Flag_File, int Output_Layers); 37 | 38 | std::list listPath; 39 | 40 | protected: 41 | NSVGpath *OptimizeLevel(Polygon *FixedPolyHull, std::list FixedList, std::list CurFloating, int num_rot, double StepAngle, double FixedArea, double &BestCost, double InBestCost, int FloatPolygon, int nbFixedPoly, int LevelOptimize); 42 | NSVGpath *OptimizeLevelFreeRot(Polygon *FixedPolyHull, std::list FixedList, std::list CurFloating, double FixedArea, double &BestCost, double InBestCost, int FloatPolygon, int nbFixedPoly, int LevelOptimize); 43 | void SimplifyPath(NSVGpath *path, double max_error); 44 | void WriteOrginalLayer(ostream& Out); 45 | void WritePolygonLayer(ostream& Out); 46 | void WriteLargePolygonLayer(ostream& Out); 47 | void WriteHullLargePolygonLayer(ostream& Out); 48 | void WritePlacedPolygonLayer(ostream& Out); 49 | void WritePlacedLayer(ostream& Out); 50 | void WriteHeader(ostream& Out); 51 | void WritePath(ostream &Out, NSVGpath *path, NSVGshape *shape); 52 | void WritePlacedPath(ostream &Out, NSVGpath *ref_path, NSVGpath *out_path, NSVGshape *shape, int hasgroup); 53 | void WriteFile(ostream &Out, int output_layers); 54 | Polygon *PlaceFirst(Polygon *p, int FirstPos, double StepAngle); 55 | int PlacementNotPossible(Polygon *CurPoly, std::list FixedPath, Rectangle *OverAll, const Point *RefPoint, int Cached); 56 | int RemoveIfIncluded(NSVGpath *path, NSVGshape *shape, NSVGpath *OldPath, int nShape, int nPath ); 57 | int NbChildren(NSVGpath *path); 58 | int getFixedNbVertex(std::list FixedList); 59 | Point ComputeRefPoint(int FirstPos, int xSize, int ySize); 60 | 61 | double RectCostFactor; 62 | string Name; 63 | int debug_level; 64 | int UseCache; 65 | 66 | ofstream OutDebug; 67 | clock_t StartClock; 68 | 69 | long int nbTranslation; 70 | long int nbRotation; 71 | long int nbCheckAngles; 72 | long int nbPointInPoly; 73 | long int nbIntersectPoly; 74 | long int nbPlacementImpossible; 75 | 76 | long int CacheMiss; 77 | long int CacheHit_OK; 78 | long int CacheHit_KO; 79 | 80 | private: 81 | }; 82 | 83 | #endif // SVGDOC_H 84 | -------------------------------------------------------------------------------- /Test/TestPoly3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 60 | 66 | 72 | 77 | 83 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/ConvexHull.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Convex hull algorithm - Library (C++) 3 | * 4 | * Copyright (c) 2017 Project Nayuki 5 | * https://www.nayuki.io/page/convex-hull-algorithm 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this program (see COPYING.txt and COPYING.LESSER.txt). 19 | * If not, see . 20 | */ 21 | 22 | #include 23 | #include "Geometry.h" 24 | 25 | using std::vector; 26 | 27 | 28 | 29 | 30 | vector makeConvexHullPresorted(const vector &points) { 31 | if (points.size() <= 1) 32 | return vector(points); 33 | 34 | // Andrew's monotone chain algorithm. Positive y coordinates correspond to "up" 35 | // as per the mathematical convention, instead of "down" as per the computer 36 | // graphics convention. This doesn't affect the correctness of the result. 37 | 38 | vector upperHull; 39 | for (const Point &p : points) { 40 | while (upperHull.size() >= 2) { 41 | const Point &q = *(upperHull.cend() - 1); // Same as .back() 42 | const Point &r = *(upperHull.cend() - 2); 43 | if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) 44 | upperHull.pop_back(); 45 | else 46 | break; 47 | } 48 | upperHull.push_back(p); 49 | } 50 | upperHull.pop_back(); 51 | 52 | vector lowerHull; 53 | for (vector::const_reverse_iterator it = points.crbegin(); it != points.crend(); ++it) { 54 | const Point &p = *it; 55 | while (lowerHull.size() >= 2) { 56 | const Point &q = *(lowerHull.cend() - 1); // Same as .back() 57 | const Point &r = *(lowerHull.cend() - 2); 58 | if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) 59 | lowerHull.pop_back(); 60 | else 61 | break; 62 | } 63 | lowerHull.push_back(p); 64 | } 65 | lowerHull.pop_back(); 66 | 67 | if (!(upperHull.size() == 1 && upperHull == lowerHull)) 68 | upperHull.insert(upperHull.end(), lowerHull.cbegin(), lowerHull.cend()); 69 | return upperHull; 70 | } 71 | 72 | 73 | vector makeConvexHull(const vector &points) 74 | { 75 | vector newPoints = points; 76 | std::sort(newPoints.begin(), newPoints.end()); 77 | return makeConvexHullPresorted(newPoints); 78 | } 79 | 80 | vector CombineHull(const vector &points_1, const vector &points_2) 81 | { 82 | vector newPoints = points_1; 83 | newPoints.insert (newPoints.end(), points_2.begin(), points_2.end()); 84 | std::sort(newPoints.begin(), newPoints.end()); 85 | return makeConvexHullPresorted(newPoints); 86 | } 87 | 88 | double HullArea(const vector &points) 89 | { 90 | double A = 0.0; 91 | int n = points.size(); 92 | 93 | if ( n < 3) return 0.0; 94 | for (int i = 0; i < n - 1; i++) 95 | { 96 | A += points[i].x*points[i+1].y - points[i+1].x*points[i].y; 97 | } 98 | // Then close the polygon, last point is the first one. 99 | A += points[n-1].x*points[0].y - points[0].x*points[n-1].y; 100 | A = fabs(A); 101 | return A/2; 102 | } 103 | 104 | Rectangle Hull2BoundingBox(const vector &points) 105 | { 106 | double xmin, xmax, ymin, ymax; 107 | int n = points.size(); 108 | Rectangle BBox; 109 | 110 | xmax = xmin = points[0].x; 111 | ymax = ymin = points[0].y; 112 | for (int i = 1; i < n - 1; i++) 113 | { 114 | xmin = fmin(xmin, points[i].x); 115 | ymin = fmin(ymin, points[i].y); 116 | xmax = fmax(xmax, points[i].x); 117 | ymax = fmax(ymax, points[i].y); 118 | } 119 | BBox.A = Point(xmin, ymin); 120 | BBox.B = Point(xmax, ymax); 121 | return BBox; 122 | } 123 | 124 | -------------------------------------------------------------------------------- /src/CachePosition.cpp: -------------------------------------------------------------------------------- 1 | #include "CachePosition.h" 2 | #include 3 | 4 | /* 5 | * This class implements the caching used to speed of computations. 6 | * When the level of optimization is greater than 2 (and only if greater than 2), we will eventually recompute if a current floating polygon 7 | * intersects the fixed ones. 8 | * So we will store the result to skip the next computations. 9 | * Beware, only computations against the fixed polygons are meaningful 10 | * This will happen when LevelOptimize is at least 2, because when LevelOptimize is 1, the floating polygon will move or rotate at each iteration 11 | * But with level 2, when there are for example 5 fixed polygons. We will try all positions and rotations for polygon 6 and then all positions 12 | * and rotations for polygon 7. But we will test polygon 7 positioned on vertices belonging to fixed polygons for each position/rotation of 13 | * polygon 6. And if there is an impossibility with polygon 7 and vertex X of fixed polygon N, this will always be the case. 14 | */ 15 | 16 | // Constructor. 17 | // The object is built once (for each floating polygon) and remains active for all iterations for the placement of the biggest remaining polygon 18 | // It stays alive until this polygon is placed and become fixed. 19 | // This save time, as when an entry is impossible with X fixed polygons it is also impossible with X+1 fixed polygons 20 | // numVertex : number of vertex of this Polygon 21 | // num_rot : possible number of rotations 22 | // numPLacedVertex : number of vertex of all fixed polygons before this one 23 | // Allocate a (large) array of pointers to Polygon objects 24 | // The size of the array is numPlacedVertex * num_rot * numVertex, one entry per vertex of current polygon with every rotation and every vertices already placed. 25 | // This array will be accessed as memCache[idxRot][iPlacedVertex][numVertex] 26 | 27 | CachePosition::CachePosition(int numVertex, int num_rot, int numPlacedVertex) 28 | { 29 | sizeCacheEntry1 = numVertex; // Used later to compute offsets for iPlacedVertex so multiply by numVertex 30 | sizeCacheEntry2 = numVertex*numPlacedVertex; // Used later to compute offsets for idxRot so multiply by numVertex*numPlacedVertex 31 | currentIdxCache = -1; // Init state, nothing in cache 32 | memCache = numPlacedVertex * num_rot * numVertex; // Memory claim 33 | CachePlaced = new Polygon *[memCache]; 34 | // The fake value 0xCACACACACACACA is impossible as a pointer 35 | // It will be used to detect entries which are not yet processed. 36 | memset(CachePlaced, 0xCA, memCache*sizeof(Polygon *)); // Init all entries, such as impossible ! 37 | FixedPerRot = new int[num_rot]; 38 | memset(FixedPerRot, -1, num_rot*sizeof(int)); // Init all entries, such as not already computed ! 39 | } 40 | 41 | // Just release the memory 42 | 43 | CachePosition::~CachePosition() 44 | { 45 | for ( int i = 0; i < currentIdxCache; i++ ) 46 | { 47 | uint64_t val = (uint64_t )CachePlaced[i]; // default value, skip, DO NOT try to free this fake memory pointer 48 | if ( val == 0xCACACACACACACACA ) 49 | continue; 50 | if ( CachePlaced[i] != NULL) delete CachePlaced[i]; 51 | } 52 | delete [] CachePlaced; 53 | delete [] FixedPerRot; 54 | } 55 | 56 | // Return status of position polygon such as Polygon is Rotated iRot and vertex #iVertex is placed on Placed Polygon vertex iPlacedVertex 57 | // Return -1 if impossible, > 0 OK and 0 don't know 58 | // If OK, update PlacedPoly parameter with the vertexes placed at the right position 59 | // If OK return the current value of MaxFixedPoly +1 60 | 61 | 62 | int CachePosition::isOKPlaced(int iVertex, int iRot, int iPlacedVertex, Polygon **PlacedPoly) 63 | { 64 | int idx = iRot * sizeCacheEntry2 + iPlacedVertex * sizeCacheEntry1 + iVertex; 65 | 66 | if ( idx > currentIdxCache ) 67 | return 0; // Not yet computed, return don't know 68 | if ( iPlacedVertex > FixedPerRot[iRot]) 69 | return 0; // Not yet computed 70 | uint64_t val = (uint64_t )CachePlaced[idx]; 71 | if ( val == 0xCACACACACACACACA ) // Check if default value (not yet processed) ? 72 | { 73 | return 0; // If so, return don't know 74 | } 75 | if ( CachePlaced[idx] != NULL ) // Value not null, cache is OK 76 | { 77 | *PlacedPoly = CachePlaced[idx]; // Update pointer to polygon to avoid recompute 78 | return FixedPerRot[iRot] + 1; // Return positive value 79 | } 80 | return -1; 81 | } 82 | 83 | // Add the polygon corresponding to the position where Polygon Poly is rotated with an index iRot and placed on vertex iPlacedVertex of the fixed polygons list 84 | // If this entry is invalid, Poly should be NULL 85 | 86 | void CachePosition::addOKPlaced(int iVertex, int iRot, int iPlacedVertex, Polygon *Poly) 87 | { 88 | if ( iPlacedVertex > FixedPerRot[iRot] ) 89 | FixedPerRot[iRot] = iPlacedVertex; 90 | int idx = iRot * sizeCacheEntry2 + iPlacedVertex * sizeCacheEntry1 + iVertex; 91 | if ( currentIdxCache < idx) 92 | currentIdxCache = idx; 93 | CachePlaced[idx] = Poly; 94 | if ( Poly != NULL ) 95 | { 96 | Poly->TypePoly = CachedPoly; 97 | nbCreatedCachedPoly++; 98 | nbCachedPoly++; 99 | nbTranslatedPoly--; 100 | } 101 | } 102 | 103 | // Return status of position polygon such as Polygon is Rotated iRot and vertex #iVertex is placed on Placed Polygon vertex iPlacedVertex 104 | // Return -1 if impossible, > 0 OK and 0 don't know 105 | // If OK, update PlacedPoly parameter with the vertexes placed at the right position 106 | // If OK return the current value of MAxFixedPoly +1 107 | 108 | int CachePosition::isOKPlacedFreeRot(int iVertex, int iRot, int iPlacedVertex, Polygon **PlacedPoly) 109 | { 110 | int idx = (iPlacedVertex * sizeCacheEntry1 + iVertex)*4 + iRot; 111 | 112 | if ( idx > currentIdxCache ) 113 | return 0; // Not yet computed 114 | if ( CachePlaced[idx] != NULL ) 115 | { 116 | *PlacedPoly = CachePlaced[idx]; 117 | return 1; 118 | } 119 | return -1; 120 | } 121 | 122 | // Add the polygon corresponding to the position where Polygon Poly is rotated with an index iRot and placed on vertex iPlacedVertex of the fixed polygons list 123 | // If this entry is invalid, Poly should be NULL 124 | 125 | void CachePosition::addOKPlacedFreeRot(int iVertex, int iRot, int iPlacedVertex, Polygon *Poly) 126 | { 127 | int idx = ( iPlacedVertex * sizeCacheEntry1 + iVertex) * 4 + iRot; 128 | if ( currentIdxCache < idx) 129 | currentIdxCache = idx; 130 | CachePlaced[idx] = Poly; 131 | if ( Poly != NULL ) 132 | { 133 | Poly->TypePoly = CachedPoly; 134 | nbCreatedCachedPoly++; 135 | nbCachedPoly++; 136 | nbTranslatedPoly--; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Test/TestPoly9.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 71 | 89 | 107 | 125 | 143 | 161 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /CutOptim/CutOptim/CutOptim.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {4F2505CD-26B1-4A24-B7CB-182CE074B430} 24 | Win32Proj 25 | CutOptim 26 | 10.0.16299.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v141 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v141 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v141 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v141 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | true 78 | 79 | 80 | false 81 | 82 | 83 | false 84 | 85 | 86 | 87 | NotUsing 88 | Level3 89 | Disabled 90 | true 91 | WIN32;_DEBUG;_CONSOLE;_USE_MATH_DEFINES;%(PreprocessorDefinitions) 92 | ..\..\include;%(AdditionalIncludeDirectories) 93 | 94 | 95 | 96 | 97 | Console 98 | true 99 | 100 | 101 | 102 | 103 | NotUsing 104 | Level3 105 | Disabled 106 | true 107 | _USE_MATH_DEFINES;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 108 | ..\..\Include 109 | 110 | 111 | Console 112 | true 113 | 114 | 115 | 116 | 117 | NotUsing 118 | Level3 119 | MaxSpeed 120 | true 121 | true 122 | true 123 | _USE_MATH_DEFINES;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 124 | ..\..\Include 125 | 126 | 127 | Console 128 | true 129 | true 130 | true 131 | 132 | 133 | 134 | 135 | NotUsing 136 | Level3 137 | MaxSpeed 138 | true 139 | true 140 | true 141 | _USE_MATH_DEFINES;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 142 | ..\..\include;%(AdditionalIncludeDirectories) 143 | 144 | 145 | Console 146 | true 147 | true 148 | true 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | Create 172 | Create 173 | Create 174 | Create 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------# 2 | # This makefile was generated by 'cbp2make' tool rev.147 # 3 | #------------------------------------------------------------------------------# 4 | 5 | 6 | WORKDIR = `pwd` 7 | 8 | CC = gcc 9 | CXX = g++ 10 | AR = ar 11 | LD = g++ 12 | WINDRES = windres 13 | 14 | INC = 15 | CFLAGS = -Wall -fexceptions 16 | RESINC = 17 | LIBDIR = 18 | LIB = 19 | LDFLAGS = 20 | 21 | INC_DEBUG = $(INC) -Iinclude 22 | CFLAGS_DEBUG = $(CFLAGS) -g 23 | RESINC_DEBUG = $(RESINC) 24 | RCFLAGS_DEBUG = $(RCFLAGS) 25 | LIBDIR_DEBUG = $(LIBDIR) 26 | LIB_DEBUG = $(LIB) 27 | LDFLAGS_DEBUG = $(LDFLAGS) 28 | OBJDIR_DEBUG = obj/Debug 29 | DEP_DEBUG = 30 | OUT_DEBUG = bin/Debug/CutOptim 31 | 32 | INC_RELEASE = $(INC) -Iinclude 33 | CFLAGS_RELEASE = $(CFLAGS) -O2 34 | RESINC_RELEASE = $(RESINC) 35 | RCFLAGS_RELEASE = $(RCFLAGS) 36 | LIBDIR_RELEASE = $(LIBDIR) 37 | LIB_RELEASE = $(LIB) 38 | LDFLAGS_RELEASE = $(LDFLAGS) -s 39 | OBJDIR_RELEASE = obj/Release 40 | DEP_RELEASE = 41 | OUT_RELEASE = bin/Release/CutOptim 42 | 43 | INC_DEBUG_PROFILE = $(INC) -Iinclude 44 | CFLAGS_DEBUG_PROFILE = $(CFLAGS) -pg -g 45 | RESINC_DEBUG_PROFILE = $(RESINC) 46 | RCFLAGS_DEBUG_PROFILE = $(RCFLAGS) 47 | LIBDIR_DEBUG_PROFILE = $(LIBDIR) 48 | LIB_DEBUG_PROFILE = $(LIB) 49 | LDFLAGS_DEBUG_PROFILE = $(LDFLAGS) -pg 50 | OBJDIR_DEBUG_PROFILE = obj/Debug_Profile 51 | DEP_DEBUG_PROFILE = 52 | OUT_DEBUG_PROFILE = bin/Debug_Profile/CutOptim 53 | 54 | INC_RELEASE_PROFILE = $(INC) -Iinclude 55 | CFLAGS_RELEASE_PROFILE = $(CFLAGS) -O2 -pg -g 56 | RESINC_RELEASE_PROFILE = $(RESINC) 57 | RCFLAGS_RELEASE_PROFILE = $(RCFLAGS) 58 | LIBDIR_RELEASE_PROFILE = $(LIBDIR) 59 | LIB_RELEASE_PROFILE = $(LIB) 60 | LDFLAGS_RELEASE_PROFILE = $(LDFLAGS) -pg 61 | OBJDIR_RELEASE_PROFILE = obj/Release_Profile 62 | DEP_RELEASE_PROFILE = 63 | OUT_RELEASE_PROFILE = bin/Release_Profile/CutOptim 64 | 65 | OBJ_DEBUG = $(OBJDIR_DEBUG)/main.o $(OBJDIR_DEBUG)/src/CachePosition.o $(OBJDIR_DEBUG)/src/ConvexHull.o $(OBJDIR_DEBUG)/src/Geometry.o $(OBJDIR_DEBUG)/src/SvgDoc.o $(OBJDIR_DEBUG)/src/SvgPath.o 66 | 67 | OBJ_RELEASE = $(OBJDIR_RELEASE)/main.o $(OBJDIR_RELEASE)/src/CachePosition.o $(OBJDIR_RELEASE)/src/ConvexHull.o $(OBJDIR_RELEASE)/src/Geometry.o $(OBJDIR_RELEASE)/src/SvgDoc.o $(OBJDIR_RELEASE)/src/SvgPath.o 68 | 69 | OBJ_DEBUG_PROFILE = $(OBJDIR_DEBUG_PROFILE)/main.o $(OBJDIR_DEBUG_PROFILE)/src/CachePosition.o $(OBJDIR_DEBUG_PROFILE)/src/ConvexHull.o $(OBJDIR_DEBUG_PROFILE)/src/Geometry.o $(OBJDIR_DEBUG_PROFILE)/src/SvgDoc.o $(OBJDIR_DEBUG_PROFILE)/src/SvgPath.o 70 | 71 | OBJ_RELEASE_PROFILE = $(OBJDIR_RELEASE_PROFILE)/main.o $(OBJDIR_RELEASE_PROFILE)/src/CachePosition.o $(OBJDIR_RELEASE_PROFILE)/src/ConvexHull.o $(OBJDIR_RELEASE_PROFILE)/src/Geometry.o $(OBJDIR_RELEASE_PROFILE)/src/SvgDoc.o $(OBJDIR_RELEASE_PROFILE)/src/SvgPath.o 72 | 73 | all: debug release debug_profile release_profile 74 | 75 | clean: clean_debug clean_release clean_debug_profile clean_release_profile 76 | 77 | before_debug: 78 | test -d bin/Debug || mkdir -p bin/Debug 79 | test -d $(OBJDIR_DEBUG) || mkdir -p $(OBJDIR_DEBUG) 80 | test -d $(OBJDIR_DEBUG)/src || mkdir -p $(OBJDIR_DEBUG)/src 81 | 82 | after_debug: 83 | 84 | debug: before_debug out_debug after_debug 85 | 86 | out_debug: before_debug $(OBJ_DEBUG) $(DEP_DEBUG) 87 | $(LD) $(LIBDIR_DEBUG) -o $(OUT_DEBUG) $(OBJ_DEBUG) $(LDFLAGS_DEBUG) $(LIB_DEBUG) 88 | 89 | $(OBJDIR_DEBUG)/main.o: main.cpp 90 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c main.cpp -o $(OBJDIR_DEBUG)/main.o 91 | 92 | $(OBJDIR_DEBUG)/src/CachePosition.o: src/CachePosition.cpp 93 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/CachePosition.cpp -o $(OBJDIR_DEBUG)/src/CachePosition.o 94 | 95 | $(OBJDIR_DEBUG)/src/ConvexHull.o: src/ConvexHull.cpp 96 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/ConvexHull.cpp -o $(OBJDIR_DEBUG)/src/ConvexHull.o 97 | 98 | $(OBJDIR_DEBUG)/src/Geometry.o: src/Geometry.cpp 99 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/Geometry.cpp -o $(OBJDIR_DEBUG)/src/Geometry.o 100 | 101 | $(OBJDIR_DEBUG)/src/SvgDoc.o: src/SvgDoc.cpp 102 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/SvgDoc.cpp -o $(OBJDIR_DEBUG)/src/SvgDoc.o 103 | 104 | $(OBJDIR_DEBUG)/src/SvgPath.o: src/SvgPath.cpp 105 | $(CXX) $(CFLAGS_DEBUG) $(INC_DEBUG) -c src/SvgPath.cpp -o $(OBJDIR_DEBUG)/src/SvgPath.o 106 | 107 | clean_debug: 108 | rm -f $(OBJ_DEBUG) $(OUT_DEBUG) 109 | rm -rf bin/Debug 110 | rm -rf $(OBJDIR_DEBUG) 111 | rm -rf $(OBJDIR_DEBUG)/src 112 | 113 | before_release: 114 | test -d bin/Release || mkdir -p bin/Release 115 | test -d $(OBJDIR_RELEASE) || mkdir -p $(OBJDIR_RELEASE) 116 | test -d $(OBJDIR_RELEASE)/src || mkdir -p $(OBJDIR_RELEASE)/src 117 | 118 | after_release: 119 | 120 | release: before_release out_release after_release 121 | 122 | out_release: before_release $(OBJ_RELEASE) $(DEP_RELEASE) 123 | $(LD) $(LIBDIR_RELEASE) -o $(OUT_RELEASE) $(OBJ_RELEASE) $(LDFLAGS_RELEASE) $(LIB_RELEASE) 124 | 125 | $(OBJDIR_RELEASE)/main.o: main.cpp 126 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c main.cpp -o $(OBJDIR_RELEASE)/main.o 127 | 128 | $(OBJDIR_RELEASE)/src/CachePosition.o: src/CachePosition.cpp 129 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/CachePosition.cpp -o $(OBJDIR_RELEASE)/src/CachePosition.o 130 | 131 | $(OBJDIR_RELEASE)/src/ConvexHull.o: src/ConvexHull.cpp 132 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/ConvexHull.cpp -o $(OBJDIR_RELEASE)/src/ConvexHull.o 133 | 134 | $(OBJDIR_RELEASE)/src/Geometry.o: src/Geometry.cpp 135 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/Geometry.cpp -o $(OBJDIR_RELEASE)/src/Geometry.o 136 | 137 | $(OBJDIR_RELEASE)/src/SvgDoc.o: src/SvgDoc.cpp 138 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/SvgDoc.cpp -o $(OBJDIR_RELEASE)/src/SvgDoc.o 139 | 140 | $(OBJDIR_RELEASE)/src/SvgPath.o: src/SvgPath.cpp 141 | $(CXX) $(CFLAGS_RELEASE) $(INC_RELEASE) -c src/SvgPath.cpp -o $(OBJDIR_RELEASE)/src/SvgPath.o 142 | 143 | clean_release: 144 | rm -f $(OBJ_RELEASE) $(OUT_RELEASE) 145 | rm -rf bin/Release 146 | rm -rf $(OBJDIR_RELEASE) 147 | rm -rf $(OBJDIR_RELEASE)/src 148 | 149 | before_debug_profile: 150 | test -d bin/Debug_Profile || mkdir -p bin/Debug_Profile 151 | test -d $(OBJDIR_DEBUG_PROFILE) || mkdir -p $(OBJDIR_DEBUG_PROFILE) 152 | test -d $(OBJDIR_DEBUG_PROFILE)/src || mkdir -p $(OBJDIR_DEBUG_PROFILE)/src 153 | 154 | after_debug_profile: 155 | 156 | debug_profile: before_debug_profile out_debug_profile after_debug_profile 157 | 158 | out_debug_profile: before_debug_profile $(OBJ_DEBUG_PROFILE) $(DEP_DEBUG_PROFILE) 159 | $(LD) $(LIBDIR_DEBUG_PROFILE) -o $(OUT_DEBUG_PROFILE) $(OBJ_DEBUG_PROFILE) $(LDFLAGS_DEBUG_PROFILE) $(LIB_DEBUG_PROFILE) 160 | 161 | $(OBJDIR_DEBUG_PROFILE)/main.o: main.cpp 162 | $(CXX) $(CFLAGS_DEBUG_PROFILE) $(INC_DEBUG_PROFILE) -c main.cpp -o $(OBJDIR_DEBUG_PROFILE)/main.o 163 | 164 | $(OBJDIR_DEBUG_PROFILE)/src/CachePosition.o: src/CachePosition.cpp 165 | $(CXX) $(CFLAGS_DEBUG_PROFILE) $(INC_DEBUG_PROFILE) -c src/CachePosition.cpp -o $(OBJDIR_DEBUG_PROFILE)/src/CachePosition.o 166 | 167 | $(OBJDIR_DEBUG_PROFILE)/src/ConvexHull.o: src/ConvexHull.cpp 168 | $(CXX) $(CFLAGS_DEBUG_PROFILE) $(INC_DEBUG_PROFILE) -c src/ConvexHull.cpp -o $(OBJDIR_DEBUG_PROFILE)/src/ConvexHull.o 169 | 170 | $(OBJDIR_DEBUG_PROFILE)/src/Geometry.o: src/Geometry.cpp 171 | $(CXX) $(CFLAGS_DEBUG_PROFILE) $(INC_DEBUG_PROFILE) -c src/Geometry.cpp -o $(OBJDIR_DEBUG_PROFILE)/src/Geometry.o 172 | 173 | $(OBJDIR_DEBUG_PROFILE)/src/SvgDoc.o: src/SvgDoc.cpp 174 | $(CXX) $(CFLAGS_DEBUG_PROFILE) $(INC_DEBUG_PROFILE) -c src/SvgDoc.cpp -o $(OBJDIR_DEBUG_PROFILE)/src/SvgDoc.o 175 | 176 | $(OBJDIR_DEBUG_PROFILE)/src/SvgPath.o: src/SvgPath.cpp 177 | $(CXX) $(CFLAGS_DEBUG_PROFILE) $(INC_DEBUG_PROFILE) -c src/SvgPath.cpp -o $(OBJDIR_DEBUG_PROFILE)/src/SvgPath.o 178 | 179 | clean_debug_profile: 180 | rm -f $(OBJ_DEBUG_PROFILE) $(OUT_DEBUG_PROFILE) 181 | rm -rf bin/Debug_Profile 182 | rm -rf $(OBJDIR_DEBUG_PROFILE) 183 | rm -rf $(OBJDIR_DEBUG_PROFILE)/src 184 | 185 | before_release_profile: 186 | test -d bin/Release_Profile || mkdir -p bin/Release_Profile 187 | test -d $(OBJDIR_RELEASE_PROFILE) || mkdir -p $(OBJDIR_RELEASE_PROFILE) 188 | test -d $(OBJDIR_RELEASE_PROFILE)/src || mkdir -p $(OBJDIR_RELEASE_PROFILE)/src 189 | 190 | after_release_profile: 191 | 192 | release_profile: before_release_profile out_release_profile after_release_profile 193 | 194 | out_release_profile: before_release_profile $(OBJ_RELEASE_PROFILE) $(DEP_RELEASE_PROFILE) 195 | $(LD) $(LIBDIR_RELEASE_PROFILE) -o $(OUT_RELEASE_PROFILE) $(OBJ_RELEASE_PROFILE) $(LDFLAGS_RELEASE_PROFILE) $(LIB_RELEASE_PROFILE) 196 | 197 | $(OBJDIR_RELEASE_PROFILE)/main.o: main.cpp 198 | $(CXX) $(CFLAGS_RELEASE_PROFILE) $(INC_RELEASE_PROFILE) -c main.cpp -o $(OBJDIR_RELEASE_PROFILE)/main.o 199 | 200 | $(OBJDIR_RELEASE_PROFILE)/src/CachePosition.o: src/CachePosition.cpp 201 | $(CXX) $(CFLAGS_RELEASE_PROFILE) $(INC_RELEASE_PROFILE) -c src/CachePosition.cpp -o $(OBJDIR_RELEASE_PROFILE)/src/CachePosition.o 202 | 203 | $(OBJDIR_RELEASE_PROFILE)/src/ConvexHull.o: src/ConvexHull.cpp 204 | $(CXX) $(CFLAGS_RELEASE_PROFILE) $(INC_RELEASE_PROFILE) -c src/ConvexHull.cpp -o $(OBJDIR_RELEASE_PROFILE)/src/ConvexHull.o 205 | 206 | $(OBJDIR_RELEASE_PROFILE)/src/Geometry.o: src/Geometry.cpp 207 | $(CXX) $(CFLAGS_RELEASE_PROFILE) $(INC_RELEASE_PROFILE) -c src/Geometry.cpp -o $(OBJDIR_RELEASE_PROFILE)/src/Geometry.o 208 | 209 | $(OBJDIR_RELEASE_PROFILE)/src/SvgDoc.o: src/SvgDoc.cpp 210 | $(CXX) $(CFLAGS_RELEASE_PROFILE) $(INC_RELEASE_PROFILE) -c src/SvgDoc.cpp -o $(OBJDIR_RELEASE_PROFILE)/src/SvgDoc.o 211 | 212 | $(OBJDIR_RELEASE_PROFILE)/src/SvgPath.o: src/SvgPath.cpp 213 | $(CXX) $(CFLAGS_RELEASE_PROFILE) $(INC_RELEASE_PROFILE) -c src/SvgPath.cpp -o $(OBJDIR_RELEASE_PROFILE)/src/SvgPath.o 214 | 215 | clean_release_profile: 216 | rm -f $(OBJ_RELEASE_PROFILE) $(OUT_RELEASE_PROFILE) 217 | rm -rf bin/Release_Profile 218 | rm -rf $(OBJDIR_RELEASE_PROFILE) 219 | rm -rf $(OBJDIR_RELEASE_PROFILE)/src 220 | 221 | install: 222 | cp -f $(OUT_RELEASE) ~/.local/bin 223 | 224 | install_inkscape: 225 | cp -f cutoptim.inx $(OUT_RELEASE) ~/.config/inkscape/extensions 226 | 227 | .PHONY: before_debug after_debug clean_debug before_release after_release clean_release before_debug_profile after_debug_profile clean_debug_profile before_release_profile after_release_profile clean_release_profile 228 | 229 | -------------------------------------------------------------------------------- /Test/shape0-path.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 58 | 62 | 66 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 175 | 180 | 185 | 190 | 195 | 200 | 205 | 210 | 215 | 220 | 225 | 230 | 235 | 240 | 245 | 250 | 255 | 260 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /shape0.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 38 | 40 | 41 | 43 | image/svg+xml 44 | 46 | 47 | 48 | 49 | 50 | 54 | 60 | 66 | 72 | 78 | 85 | 92 | 99 | 106 | 113 | 120 | 127 | 134 | 141 | 148 | 155 | 162 | 169 | 176 | 183 | 190 | 197 | 204 | 211 | 218 | 225 | 232 | 239 | 246 | 253 | 260 | 267 | 274 | 281 | 288 | 295 | 302 | 309 | 316 | 323 | 330 | 337 | 344 | 351 | 352 | 353 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "cxxopts.hpp" 3 | #include "SvgDoc.h" 4 | #include "Geometry.h" 5 | #include 6 | 7 | #define _USE_MATH_DEFINES 8 | #include 9 | 10 | using namespace std; 11 | 12 | 13 | 14 | cxxopts::ParseResult parse(int argc, char* argv[]) 15 | { 16 | try 17 | { 18 | cxxopts::Options options(argv[0], " - example command line options"); 19 | options 20 | .positional_help("[optional args]") 21 | .show_positional_help(); 22 | 23 | options 24 | .allow_unrecognised_options() 25 | .add_options() 26 | ("f, file", "File", cxxopts::value()->default_value("TestPoly1.svg"), "SVG Input File") 27 | ("o,output", "Output file", cxxopts::value(), "SVG Output File") 28 | ("positional", "File to be processed", cxxopts::value>()) 29 | ("h,help", "Print help") 30 | ("d,distance", "Min distance between paths to be cut", cxxopts::value(), "1.0") 31 | ("q,rect_cost", "Overall Rectangle cost", cxxopts::value(), "0.0") 32 | ("m,max_length", "Max length of one segment, break than longer", cxxopts::value(), "1000.0") 33 | ("l,optimizing_level", "Optimizing level, process list_size elements together", cxxopts::value(), "1") 34 | ("debug_level", "Level of debug info in specific debug file", cxxopts::value(), "0") 35 | ("debug_file", "Generate debug info from inkscape", cxxopts::value()->default_value("true")) 36 | ("df_name", "Name of debug file", cxxopts::value()->default_value("Debug_CutOptim.txt")) 37 | ("k,original", "Output Original layer", cxxopts::value()->default_value("false")) 38 | ("n,nested", "Keep nested path together", cxxopts::value()->default_value("true")) 39 | ("c,use_cache", "USe cache to speed up processing", cxxopts::value()->default_value("false")) 40 | ("y,layer_output", "Output internal layers : 1 Input layer, 2 Polygon, 4 Large polygon, 8 Hull layer, 16 Placed Polygon layer, OR these values to output multiple layers", cxxopts::value(), "0") 41 | ("a,angle", "Rotation step", cxxopts::value(), "90.0") 42 | ("r,free_rot", "allow free rotation", cxxopts::value()->default_value("true")) 43 | ("p,firstpos", "Position of largest object", cxxopts::value(), "Position of largest object on the sheet") 44 | ; 45 | options.parse_positional({"file"}); 46 | auto result = options.parse(argc, argv); 47 | 48 | if (result.count("help")) 49 | { 50 | std::cout << options.help({""}) << std::endl; 51 | exit(0); 52 | } 53 | 54 | return result; 55 | 56 | } catch (const cxxopts::OptionException& e) 57 | { 58 | std::cout << "error parsing options: " << e.what() << std::endl; 59 | exit(1); 60 | } 61 | } 62 | 63 | static int ConvertPos(string Pos) 64 | { 65 | int rPos = 0; 66 | 67 | if ( Pos.length() < 2 ) return(TopLeft); 68 | if ( Pos[0] == 'T' || Pos[0] == 't') 69 | { 70 | rPos = 0; 71 | } 72 | else if ( Pos[0] == 'C' || Pos[0] == 'c' ) 73 | { 74 | rPos = 4; 75 | } 76 | else if ( Pos[0] == 'B' || Pos[0] == 'b') 77 | { 78 | rPos = 8; 79 | } 80 | else 81 | { 82 | return TopLeft; // Top left if unknown 83 | } 84 | if ( Pos[1] == 'L' || Pos[1] == 'l') 85 | { 86 | rPos += 0; 87 | } 88 | else if ( Pos[1] == 'C' || Pos[1] == 'c' ) 89 | { 90 | rPos += 1; 91 | } 92 | else if ( Pos[1] == 'R' || Pos[1] == 'r' ) 93 | { 94 | rPos += 2; 95 | } 96 | else 97 | { 98 | return TopLeft; // Top left if unknown 99 | } 100 | return rPos; 101 | } 102 | #define TEST_ANGLES 0 103 | 104 | extern bool CheckOKAngles(double sf1, double sf2, double sm1); 105 | 106 | int main(int argc, char *argv[]) 107 | { 108 | string InputFileName; 109 | string OutputFileName; 110 | double MinCutDistance = 1.0; 111 | double StepAngle = 0; 112 | double rect_cost = 0.0; 113 | int Flag_file = 0; 114 | int debug_level = 0; 115 | int Output_Layer = 0; 116 | int KeepNested = 1; 117 | int OptimizingLevel = 1; 118 | int FirstPos = CenterCenter; 119 | int UseCache = 0; 120 | bool Flag_free_rot = 1; 121 | string debug_file_name = "Debug_CutOptim.txt"; 122 | 123 | double max_segment_length = 1000.0; 124 | #ifdef UNDEF 125 | // Used to check param passing from inkscape 126 | ofstream ParamDbg("/home/thierry/Programmes/CutOptim/DebgParam.txt"); 127 | if ( ParamDbg.is_open() == false ) 128 | { 129 | cerr << "unable to open /home/thierry/Programmes/CutOptim/DebgParam.txt\n"; 130 | exit(1); 131 | } 132 | ParamDbg << "--- Params ------\n"; 133 | ParamDbg << " argc =" << argc << "\n"; 134 | for ( int i = 0; i < argc; i++) 135 | { 136 | ParamDbg << "Argv[" << i << "]=" << argv[i] << "\n"; 137 | } 138 | ParamDbg.close(); 139 | #endif 140 | auto result = parse(argc, argv); 141 | InputFileName = result["f"].as(); 142 | if (result.count("output")) 143 | { 144 | OutputFileName = result["output"].as(); 145 | Flag_file = 1; 146 | } 147 | if (result.count("distance")) 148 | MinCutDistance = result["distance"].as(); 149 | if (result.count("angle")) 150 | { 151 | StepAngle = result["angle"].as(); 152 | Flag_free_rot = 0; 153 | } 154 | if (result.count("max_length")) 155 | max_segment_length = result["max_length"].as(); 156 | if (result.count("rect_cost")) 157 | rect_cost = result["rect_cost"].as(); 158 | if (result.count("debug_level")) 159 | debug_level = result["debug_level"].as(); 160 | if (result.count("layer_output")) 161 | Output_Layer = result["layer_output"].as(); 162 | if (result.count("optimizing_level")) 163 | OptimizingLevel = result["optimizing_level"].as(); 164 | if (result.count("original")) 165 | { 166 | Output_Layer |= result["original"].as(); 167 | } 168 | if ( result.count("firstpos")) 169 | { 170 | FirstPos = ConvertPos(result["firstpos"].as() ); 171 | } 172 | if ( result.count("df_name")) 173 | { 174 | debug_file_name = result["df_name"].as(); 175 | } 176 | if (result.count("nested")) 177 | { 178 | KeepNested = result["nested"].as(); 179 | } 180 | 181 | if (result.count("use_cache")) 182 | { 183 | UseCache = result["use_cache"].as(); 184 | } 185 | 186 | if (result.count("free_rot")) 187 | { 188 | Flag_free_rot = result["free_rot"].as(); 189 | } 190 | if (result.count("debug_file")) 191 | debug_level += result["debug_file"].as();; 192 | if ( Flag_file ) 193 | { 194 | cout << "Parse options complete" << endl; 195 | cout << "Input File = " << InputFileName << std::endl; 196 | cout << "Output File = " << OutputFileName << std::endl; 197 | cout << "angle = " << StepAngle << std::endl; 198 | cout << "MinCutDistance = " << MinCutDistance << std::endl; 199 | cout << "Debug Level = " << debug_level << std::endl; 200 | cout << "Output layers = " << Output_Layer << std::endl; 201 | cout << "Optimizing level = " << OptimizingLevel << std::endl; 202 | cout << "Max_segment_length = " << max_segment_length << std::endl; 203 | cout << "Use free rotation = " << Flag_free_rot << std::endl; 204 | cout << "Rectangle cost factor = " << rect_cost << std::endl; 205 | cout << "Keep nested paths = " << KeepNested << std::endl; 206 | cout << "Use Cache = " << UseCache << std::endl; 207 | } 208 | 209 | // Read and process input file 210 | 211 | SvgDoc InputSVG(InputFileName); 212 | InputSVG.setDebugLevel(debug_level, debug_file_name); 213 | InputSVG.setUseCache(UseCache); 214 | InputSVG.setRectCost(rect_cost); 215 | if ( InputSVG.SvgData == NULL ) 216 | { 217 | cerr << " No input to process aborting \n"; 218 | return 1; 219 | } 220 | // Change paths to polygones 221 | InputSVG.TransformPaths(MinCutDistance / 10.0, KeepNested); 222 | // Make larger polygons 223 | InputSVG.EnlargePaths(MinCutDistance); 224 | // Break longer edges if necessary 225 | InputSVG.BreakLongerEdges(max_segment_length, MinCutDistance / 10.0); 226 | // Sort polygons by area 227 | InputSVG.BuilSingleListPath(); 228 | // Compute convex hulls of each path 229 | // InputSVG.ComputeConvexHulls(); 230 | // Optimize 231 | if ( StepAngle == 0 ) StepAngle = 360; 232 | if ( Flag_free_rot ) 233 | InputSVG.OptimizeFreeRot(OptimizingLevel, FirstPos, Flag_file); 234 | else 235 | InputSVG.Optimize(StepAngle * 2 * M_PI / 360, OptimizingLevel, FirstPos, Flag_file); 236 | InputSVG.WriteDoc(OutputFileName, Flag_file, Output_Layer); 237 | 238 | 239 | #ifdef TEST_GEO 240 | // Now test distance from point to Segment 241 | Point P00 = Point(0,0); 242 | Point P11 = Point(1,1); 243 | Segment Seg1 = Segment(P00, P11); 244 | Point P01 = Point(0, 1); 245 | Point P10 = Point(1, 0); 246 | printf("Distance from P10 to Seg1 =%.3f\n", sqrt(Seg1.sqrDistancePoint(P10))); 247 | printf("Distance from P01 to Seg1 =%.3f\n", sqrt(Seg1.sqrDistancePoint(P01))); 248 | Point P2 = Point(2,0); 249 | printf("Distance from P2 to Seg1 =%.3f\n", sqrt(Seg1.sqrDistancePoint(P2))); 250 | Point P3 = Point(-1,-1); 251 | printf("Distance from P3 to Seg1 =%.3f\n", sqrt(Seg1.sqrDistancePoint(P3))); 252 | Point P4 = Point(2,2); 253 | printf("Distance from P4 to Seg1 =%.3f\n", sqrt(Seg1.sqrDistancePoint(P4))); 254 | Point P5 = Point(-1,0); 255 | printf("Distance from P5 to Seg1 =%.3f\n", sqrt(Seg1.sqrDistancePoint(P5))); 256 | Segment Seg2 = Segment(P11, P00); 257 | printf("Distance from P1 to Seg2 =%.3f\n", sqrt(Seg2.sqrDistancePoint(P10))); 258 | printf("Distance from P2 to Seg2 =%.3f\n", sqrt(Seg2.sqrDistancePoint(P2))); 259 | printf("Distance from P3 to Seg2 =%.3f\n", sqrt(Seg2.sqrDistancePoint(P3))); 260 | printf("Distance from P4 to Seg2 =%.3f\n", sqrt(Seg2.sqrDistancePoint(P4))); 261 | printf("Distance from P5 to Seg2 =%.3f\n", sqrt(Seg2.sqrDistancePoint(P5))); 262 | 263 | // Test Polygons 264 | Polygon Poly1 = Polygon(); 265 | Polygon Poly2 = Polygon(); 266 | // First a rectangle 267 | Poly1.addVertice(P00); 268 | Poly1.addVertice(P01); 269 | Poly1.addVertice(P11); 270 | Poly1.addVertice(P10); 271 | Poly1.addVertice(P00); 272 | printf("Polygon 1 nVertices = %d clockwise =%d, area = %.3f\n", Poly1.nVertices, Poly1.isClockWise(), Poly1.area()); 273 | 274 | Poly2.addVertice(P00); 275 | Poly2.addVertice(P10); 276 | Poly2.addVertice(P11); 277 | Poly2.addVertice(P01); 278 | Poly2.addVertice(P00); 279 | printf("Polygon 2 nVertices = %d clockwise =%d, area = %.3f\n", Poly2.nVertices, Poly2.isClockWise(), Poly2.area()); 280 | printf("Distance P01 to Poly2 =%.3f\n", sqrt(Poly2.distance(P01))); 281 | printf("Distance P10 to Poly2 =%.3f\n", sqrt(Poly2.distance(P10))); 282 | printf("Distance P2 to Poly2 =%.3f\n", sqrt(Poly2.distance(P2))); 283 | printf("Distance P3 to Poly2 =%.3f\n", sqrt(Poly2.distance(P3))); 284 | printf("Distance P4 to Poly2 =%.3f\n", sqrt(Poly2.distance(P4))); 285 | printf("Distance P5 to Poly2 =%.3f\n", sqrt(Poly2.distance(P5))); 286 | 287 | Polygon Poly8 = Polygon(); 288 | for (int i = 0; i <= 8; i++) 289 | { 290 | Point p = Point(cos(i*M_PI/4.0), sin(i*M_PI/4.0)); 291 | Poly8.addVertice(p); 292 | } 293 | printf("Polygon 8 nVertices = %d clockwise =%d, area = %.3f\n", Poly8.nVertices, Poly8.isClockWise(), Poly8.area()); 294 | 295 | printf("Distance P01 to Poly8 =%.3f\n", sqrt(Poly8.distance(P01))); 296 | printf("Distance P10 to Poly8 =%.3f\n", sqrt(Poly8.distance(P10))); 297 | printf("Distance P2 to Poly8 =%.3f\n", sqrt(Poly8.distance(P2))); 298 | printf("Distance P3 to Poly8 =%.3f\n", sqrt(Poly8.distance(P3))); 299 | printf("Distance P4 to Poly8 =%.3f\n", sqrt(Poly8.distance(P4))); 300 | printf("Distance P5 to Poly8 =%.3f\n", sqrt(Poly8.distance(P5))); 301 | #endif 302 | #if TEST_ANGLES > 0 303 | printf("Cas 1 : Départ en -1,0, arrivée en 0,0, puis va en 0,1\n"); 304 | double sf1 = atan2(-1, 0); 305 | double sf2 = atan2(-0.2, -1); 306 | 307 | if ( sf1 < 0 ) sf1 += 2*M_PI; 308 | if ( sf2 < 0 ) sf2 += 2*M_PI; 309 | double sm2 = 0; 310 | printf("sf1 = %.1f, sf2 = %.1f\n", sf1 * 180 / M_PI, sf2 *180.0 / M_PI); 311 | 312 | printf("Essai sm1 sm2\n"); 313 | 314 | sm2 = 0; 315 | for ( double a = 1.0; a < 360; a+= 20.0) 316 | { 317 | sm2 = a/180.0 * M_PI; 318 | printf("Angle %.1f, SM1 %d SM2 %d\n", a, CheckOKAngles(sf1, sf2, sm2), CheckOKAngles(sf2, sf1, sm2)); 319 | } 320 | #endif 321 | return(0); 322 | } 323 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Presentation 2 | 3 | All the users of laser cutter have probably been confronted with the problem: my drawing does not fit in the wood sheet at my disposal or even in the laser cutter! To try to solve this problem, I started writing an optimization program for placing objects on a sheet. 4 | For interested readers, this is a variant of a well known problem in operational research (bin packing problem). By doing a little search on the Web, there are also links to applications performing this task, but the free versions are often very limited, they are limited optimizing the cutting of rectangles. 5 | 6 | This program reads an input SVG file containing the objects to be placed and outputs a second SVG file containing the placed objects. 7 | It can be used as is (command line, no graphical interface!) Or as an extension inkscape which then provides the GUI. 8 | 9 | ## Environment 10 | 11 | As mentioned above, this program can be used alone or as an inkscape extension. 12 | The processing can take a relatively long time, it is better to run this program on a modern processor, but if you are in no hurry ... 13 | The memory consumption is reasonable, no need to rush to buy new RAMs! 14 | The program has been tested both on Linux and Windows, so it should work on both systems. 15 | 16 | 17 | ## The Software 18 | 19 | First of all as it is a program written in C ++ for a performance issue, so it must be compiled on your machine. 20 | I wrote this program under Linux / Ubuntu (compiled with gcc), but since there is no system dependency, it should work as is under any other version of Linux. For fans of Windows (there are so many!), I created a Visual Studio project that allows to compile on this platform. For Mac users, sorry I do not have access to a Mac, you will have to make the adaptation yourself, but the C ++ used is really standard, it should work as soon as you have access to a compiler. For information, I did not change the code between Linux and Windows, that's saying! 21 | 22 | ### Linux installation 23 | 24 | The code is available here: https://github.com/thierry7100/CutOptim 25 | For the beginner, you clone (or download) the directory, it comes in the form of a .zip archive, which must be extracted. 26 | Then you open a terminal, go to the created directory and launch the commands: 27 | - make release 28 | - make install: this will copy the software into the directory ~/.local / bin which is in the list of executable directories, which will allow you to use it directly (this may be specific to Ubuntu, to you to put the program elsewhere on another system. 29 | - make install_inkscape: this will copy the program to the inkscape extension directory (~/.config/inkscape / extensions). If you want to make this extension available for all accounts on your machine, copy the file cutoptim.inx + the executable into /usr/share/inkscape / extensions (you must be root). 30 | 31 | If you have opted for the inkscape extension, at the next start you will have a Fablab / Laser Cutting Optimizer extension 32 | 33 | 34 | ### Windows installation 35 | 36 | The code is available here: https://github.com/thierry7100/CutOptim 37 | For the uninitiated, you clone (or download) the directory, it comes in the form of a .zip archive, which must be extracted. 38 | Then you launch Visual Studio, you can get a free version for a specific purpose, see https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=16 39 | Then, once Visual Studio is started, you open the CutOptim project, then: 40 | - You ask to generate the Release version of the project if it is not the one that appears in the menu bar. 41 | - You choose the platform (x86 or x64) of your choice. By default the file is configured in x64, if you have a 32bit version of Windows, change to x86. 42 | - Then choose Generate / Generate Solution, compilation starts and after a few seconds, your program is available. 43 | - Then, under windows, it is better to use this program like extension inkscape, the command line hardly being used? ! To do this, copy the cutoptim.inx and CutOptm / x64 / Release / CutOptim.exe files to the inkscape extensions directory. This can be found via the Edit / Preferences / System command, but it is usually under C: \ USERS \\ Your user name\\ AppData \ Roaming / inkscape / extensions. Attention, to see this directory, you will have to validate the visualization of hidden files under the explorer of files, if it were not done. 44 | As in Linux, the next startup of inkscape you will have a Fablab / Laser Cutting Optimizer 45 | 46 | ## Operation 47 | 48 | The input and output format of the files is the SVG format, available on many software programs. If you use inkscape, it is the native format, the program has been tested in this context. It should also work with files from other drawing software that generates SVG. If you have a problem tell me (thierry@fablab-lannion.org)
49 | 50 | Description of the process: 51 | - The inkscape document (at least its size) can generally be of importance, here it sets the size of the sheet used for cutting. You will need to set a document size compatible with your material (and the cutter, of course!). 52 | - First, the program reads the inkscape document, it only considers paths or simple objects. The text strings not transformed into paths, the pictures ... are simply ignored. I therefore advise to turn everything into a path before launching the program. Inkscape Ctrl + A then Objects in Path (SHIFT + Ctrl + C). Unclosed paths are also ignored, the software is only able to process shapes with a closed outline. 53 | - The paths can be placed anywhere, on the sheet or outside, it does not matter. To a certain extent, they can even be superimposed (see below). 54 | - Then, from these paths, the program creates polygons approaching the paths (with an error of less than 0.1mm on average). 55 | - Then the program "enlarges" these polygons to prevent paths from touching each other in the final result. The size of the enlargement is configurable. 56 | - The program then takes these enlarged polygons and will try to place them in a way that is not optimal but good. Why not optimal because the problem is difficult (complete NP in mathematical terms) and requires a very long time even for simple configurations. The basic idea here is to start from the largest polygon, then to place the sorted polygons by decreasing size such that a vertex of the polygon to be placed is positioned on a vertex of an already placed polygon. This reduces the space of possibilities, even if it remains very large! 57 | - The "best" configuration is obtained when the size of the convex hull is minimal. Another mathematical term! The convex hull is the smallest convex form containing all points of all plotted polygons. Intuitively, this maximizes the free space on the sheet, which is the desired result. Be careful, it is not necessarily the smallest rectangle, the convex hull is not usually a rectangle! 58 | - To place the paths, the software is allowed to rotate the objects, unless you block this possibility. Depending on your needs (non-homogeneous material) you may have to limit rotations to 0 and 180 ° for example, or even to block any rotation (this will be the case for example with printed fabric). 59 | 60 | ### Program options as inkscape extension 61 | ![Options dialog box](CutOptim_optionsinkscape.png) 62 | The program has many options detailed below: 63 | - Units: Always use mm, the program is not tested for other choices. Sorry for inches users, you will have to test this ! 64 | - Min distance between objects: This is the size at which the created polygons will be enlarged. This value must be greater than 0.8mm, the approximation by polygons is not perfect. 65 | - Max length of single segment: As explained above the software will try to find a good configuration by positioning vertices on other vertices. It can be interesting in some cases to "add" vertices to have more possibilities. If an edge is longer than the specified size, it will be broken into multiple segments, with additional vertices. Do not abuse this option, too low a value will slow down the treatment tremendously. Do not go below 100mm in most cases, even if the value 0 is allowed to indicate that you do NOT want to use this possibility. 66 | - Optimizing level: as indicated above the program places the polygons in order of decreasing size (we place the largest pebbles first ...). This sometimes leads to clearly suboptimal situations. By increasing this parameter, the software will optimize the placement of a group of N polygons. This gives better results, but be careful, it considerably increases the treatment time. Do not exceed 2 or 3, if the default value of 1 does not give good results. If you draw has 300 vertices already placed (rather low value actually) and you allow rotations in steps of 10 °, use N = 2 will multiply operations by 36 * 300! And for N = 3 by (36 * 300) ²! 67 | - Keep original layer in output: If you do not trust the program (!), you can check this option, the original shapes will be kept as well as those placed, but placed in different layers. You will be able to check the work done. 68 | - Select option for largest element placement: The first item can be placed where you want on the page. Usually at the top left, but the center also gives good results. 69 | - Allow free rotation of paths, angle parameter not used : If this option is checked, the angle of rotation of each object will be chosen from 4 to make the edge coincide with one of the two of the vertex on which the object will be positioned. This option is economical in processing time (at most 4 tests) but can give less good results than the fixing of the angle of rotation. The results are worse when the segments are very short, if the input form is not a polygon for example. 70 | - Try rotation by (0 no rotation allowed): This option is incompatible with the previous one, it is only valid if the previous option is NOT checked. In this case, the objects are positioned on discrete rotation steps. 0 means that rotations are prohibited, this is useful when the material is not homogeneous. For MDF, no restrictions, but for wood or even plywood, if you want to respect the direction of the wood, rotations are not advisable. Choose 180 ° in this case. Attention, low values ​​greatly increase the calculation time. With 10 °, there are 36 times more calculations than with 0 °! If the input shapes are rectangular, a value of 90 ° gives good results. 71 | - Attach nested path to the bigger one : In practice, we often deal with situations or related objects. For example, a plate with fixing holes. If this box is checked, the software checks if the path is included in another one, and if it is, it will not process it but link it to the larger path. Once it is placed, the same transformation (rotation / translation) will be applied to the "small" included object. Attention the software is not able to recover the space released in holes, you have to leave a little work anyway. 72 | - Debug file generation: If this box is checked, a debug file (Debug_CutOptim.txt) is created in the inkscape extension directory. This can be used to understand what happened when it goes wrong. 73 | 74 | ### Comand line options 75 | 76 | The program has the following options which are listed when typing CutOptim -h from the command line. For the explanations, please refer to the previous chapter, the command line options are the same as when used as an inkscape extension. 77 | 78 | thierry@thierry-UX410UAR:~/Programmes/CutOptim$ CutOptim -h 79 | - example command line options 80 | Usage: 81 | CutOptim [OPTION...] [optional args] 82 | 83 | -f, --file SVG Input File File (default: TestPoly1.svg) 84 | -o, --output SVG Output File Output file 85 | --positional arg File to be processed 86 | -h, --help Print help 87 | -d, --distance 1.0 Min distance between paths to be cut 88 | -m, --max_length 1000.0 Max length of one segment, break than longer 89 | -l, --optimizing_level 1 Optimizing level, process list_size elements 90 | together 91 | --debug_level 0 Level of debug info in specific debug file 92 | --debug_file Generate debug info from inkscape (default: true) 93 | -k, --original Output Original layer 94 | -n, --nested Keep nested path together (default: true) 95 | -y, --layer_output 0 Output internal layers : 1 Input layer, 2 96 | Polygon, 4 Large polygon, 8 Hull layer, 16 Placed 97 | Polygon layer, OR these values to output multiple layers 98 | -a, --angle 90.0 Rotation step 99 | -r, --free_rot allow free rotation (default: true) 100 | -c, --use_cache Set to 1 to enable cache. Cache operation is currently bugged and should NOT be used, default is 0 101 | -q --rect_cost" Add overall rectangle area * factor to cost function. Default factor is 0.0 102 | -p, --firstpos Position of largest object on the sheet 103 | 104 | 105 | ## Next steps 106 | 107 | The program could be improved, both in performance and optimization result. I will work loosely on both topics, but you want to contribute, you are welcome ! 108 | 109 | ## References 110 | As mentionned, this is a well known research topic, I have used to scientific papers when designing this software 111 | 1. Waste minimization in irregular stock cutting published in 2014 by Doraid Dalalah, Samir Khrais and Khaled Bataineh. I have used this paper as the main input of this work. 112 | 2. Jostle heuristics for the 2D-irregular shapes bin packing problems with free rotation published in 2018 by Ranga P. Abeysooriya, Julia A. Bennell and Antonio Martinez-Sykora. 113 | 114 | ## Libraries and other contributions 115 | 116 | - For command line processing I have used cxxopts (https://github.com/jarro2783/cxxopts) 117 | - For SVG processing I have used nanosvg (https://github.com/memononen/nanosvg) 118 | - For convex hull computation I have used a result from tyhe project Nayuki (https://www.nayuki.io/page/convex-hull-algorithm) 119 | 120 | Thanks a lot for sharing these contributions ! 121 | -------------------------------------------------------------------------------- /include/Geometry.h: -------------------------------------------------------------------------------- 1 | #ifndef GEOMETRY_H 2 | #define GEOMETRY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | using namespace std; 9 | 10 | const double my_Default_Precision = 1e-4; 11 | const double angle_precision = 1e-6; 12 | 13 | enum PositionType { 14 | TopLeft = 0, 15 | TopCenter = 1, 16 | TopRight = 2, 17 | CenterLeft = 4, 18 | CenterCenter = 5, 19 | CenterRight = 6, 20 | BottomLeft = 8, 21 | BottomCenter = 9, 22 | BottomRight = 10 23 | }; 24 | 25 | 26 | class Point 27 | { 28 | public: 29 | double x; 30 | double y; 31 | Point (double newx=0,double newy=0) 32 | { 33 | x=newx; 34 | y=newy; 35 | } 36 | /* 37 | Overloaded == operator to check for equality between 2 objects of class point 38 | */ 39 | inline friend bool operator== (const Point& p1,const Point& p2) 40 | { 41 | return (p1.x==p2.x && p1.y==p2.y); 42 | } 43 | /* 44 | Overloaded != operator to check for non-equality between 2 objects of class point 45 | */ 46 | inline friend bool operator!= (const Point& p1,const Point& p2) 47 | { 48 | return (!(p1.x==p2.x && p1.y==p2.y)); 49 | } 50 | 51 | /* 52 | Overloaded != operator to check if a point is "lower" 53 | Return true if X is lower. if X is equal return true if Y is lower 54 | */ 55 | inline friend bool operator< (const Point& p1,const Point& p2) 56 | { 57 | if ( p1.x != p2.x ) return(p1.x < p2.x); 58 | return(p1.y < p2.y); 59 | } 60 | /* 61 | Overloaded != operator to check if a point is "lower or equal" 62 | Return true if X is lower. if X is equal return true if Y is lower or equal 63 | */ 64 | inline friend bool operator<= (const Point& p1,const Point& p2) 65 | { 66 | if ( p1.x != p2.x ) return(p1.x < p2.x); 67 | return(p1.y <= p2.y); 68 | } 69 | /* 70 | Overloaded != operator to check if a point is "greater" 71 | Return true if X is greater. if X is equal return true if Y is greater 72 | */ 73 | inline friend bool operator> (const Point& p1,const Point& p2) 74 | { 75 | if ( p1.x != p2.x ) return(p1.x > p2.x); 76 | return(p1.y > p2.y); 77 | } 78 | 79 | /* 80 | Overloaded != operator to check if a point is "greater or equal" 81 | Return true if X is greater. if X is equal return true if Y is greater 82 | */ 83 | inline friend bool operator>= (const Point& p1,const Point& p2) 84 | { 85 | if ( p1.x != p2.x ) return(p1.x > p2.x); 86 | return(p1.y >= p2.y); 87 | } 88 | 89 | /* 90 | Overloaded + operator to returna point with coordinates which are the difference between the 2 points 91 | */ 92 | friend Point operator+ (const Point& p1,const Point& p2) 93 | { 94 | return (Point(p1.x+p2.x , p1.y+p2.y)); 95 | } 96 | /* 97 | Overloaded + operator to returna point with coordinates which are the sum of the 2 points 98 | */ 99 | inline friend Point operator- (const Point& p1,const Point& p2) 100 | { 101 | return (Point(p1.x-p2.x , p1.y-p2.y)); 102 | } 103 | /* 104 | Overloaded * operator multiplication by a scalar 105 | */ 106 | inline friend Point operator* (double Coeff,const Point& p2) 107 | { 108 | return (Point(Coeff*p2.x , Coeff*p2.y)); 109 | } 110 | /* 111 | Overloaded ostream << operator to check for print object of class point to STDOUT 112 | */ 113 | friend ostream& operator<<(ostream& output,const Point& p) 114 | { 115 | output<<"("<a * b - a*Line2->b; 213 | if ( fabs(det) < my_Default_Precision ) // Line are parallel 214 | { 215 | return(0); // Parallel lines, no intersection 216 | } 217 | ResIntersect = Point((Line2->b*c - Line2->c*b)/det, (a*Line2->c - Line2->a*c)/det); 218 | return 1; 219 | } 220 | }; 221 | 222 | /** 223 | * This class is derived from line, but adds two ends 224 | */ 225 | class Segment:Line 226 | { 227 | protected: 228 | double xA, xB,yA, yB; 229 | public: 230 | 231 | double xm, xM, ym, yM; 232 | 233 | Segment() 234 | { 235 | } 236 | 237 | Segment(Point &A, Point &B) 238 | { 239 | xA = A.x; 240 | xB = B.x; 241 | yA = A.y; 242 | yB = B.y; 243 | xm = fmin(xA, xB) - my_Default_Precision; 244 | xM = fmax(xA, xB) + my_Default_Precision; 245 | ym = fmin(yA, yB) - my_Default_Precision; 246 | yM = fmax(yA, yB) + my_Default_Precision; 247 | a = A.y - B.y; 248 | b = B.x - A.x; 249 | c = A.x * B.y - B.x * A.y; 250 | } 251 | inline Point getPointA() 252 | { 253 | return(Point(xA, yA)); 254 | } 255 | inline Point getPointB() 256 | { 257 | return(Point(xB, yB)); 258 | } 259 | inline void setPointA(Point &A) 260 | { 261 | xA = A.x; 262 | yA = A.y; 263 | a = yA - yB; 264 | b = xB - xA; 265 | c = xA*yB - xB*yA; 266 | } 267 | inline void setPointB(Point &B) 268 | { 269 | xB = B.x; 270 | yB = B.y; 271 | a = yA - yB; 272 | b = xB - xA; 273 | c = xA*yB - xB*yA; 274 | } 275 | 276 | // Compute segment size 277 | inline double SegmentSize() 278 | { 279 | return(sqrt((xA-xB)*(xA-xB) + (yA-yB)*(yA-yB))); 280 | } 281 | 282 | /** 283 | * Return true if point p is in segment. p should be on the line 284 | */ 285 | inline int InSegment(Point &p) const 286 | { 287 | if ( p.x < xm ) return 0; // Impossible lower than xmin 288 | if ( p.x > xM ) return 0; // Impossible greater than xmax 289 | if ( p.y < ym) return 0; // Impossible lower than ymin 290 | if ( p.y > yM ) return 0; // Impossible greater than ymax 291 | return(1); // OK 292 | } 293 | 294 | /** 295 | * Return true if point p is in segment. p should be on the line, segment ends are not taken into account 296 | * So return false if the point is an end. 297 | */ 298 | inline int InSegmentNoEnd(Point &p) const 299 | { 300 | if ( p.x < xm ) return 0; // Impossible lower than xmin 301 | if ( p.x > xM ) return 0; // Impossible greater than xmax 302 | if ( p.y < ym) return 0; // Impossible lower than ymin 303 | if ( p.y > yM ) return 0; // Impossible greater than ymax 304 | // Now checks if p is an end of the segment 305 | double d1 = fabs(p.x - xA) + fabs(p.y - yA); 306 | if ( d1 < 2*my_Default_Precision) return 0; // End of segment return 0 307 | double d2 = fabs(p.x - xB) + fabs(p.y - yB); 308 | if ( d2 < 2*my_Default_Precision) return 0; // End of segment return 0 309 | return(1); // OK 310 | } 311 | 312 | inline double sqrDistancePoint(const Point &p) const 313 | { 314 | // If dot product AB.AC si < 0 return distance(A, C) 315 | // Indeed, if this is the case C is in the half plane which is outer segment 316 | if ( (xB - xA)*(p.x - xA) + (yB-yA)*(p.y-yA) < 0 ) 317 | return( (p.x - xA)*(p.x- xA) + (p.y-yA)*(p.y-yA)); 318 | // If dot product BA.BC si > 0 return distance(A, C) 319 | if ( (xA - xB)*(p.x - xB) + (yA-yB)*(p.y-yB) < 0 ) 320 | return( (p.x - xB)*(p.x- xB) + (p.y-yB)*(p.y-yB)); 321 | // Distance point to line is a * p.x + b * p.y + c)*(a * p.x + b * p.y + c)/(a*a + b*b 322 | double d1 = (a * p.x + b * p.y + c)*(a * p.x + b * p.y + c)/(a*a + b*b); 323 | return d1; 324 | } 325 | // Check if segment this cross segment s1 326 | // In any case, set ResIntersect to the point of intersection of the two lines 327 | // If this point belongs to the 2 segments, return true 328 | // If one segment is included in another, set the bool IncludedSegment but return false 329 | inline int isCrossing(const Segment *s1, Point *ResIntersect, bool *IncludedSegment=nullptr) 330 | { 331 | Point Ai; 332 | if ( IncludedSegment != nullptr ) *IncludedSegment = false; // Set default answer 333 | if ( !hasIntersect(s1, Ai) ) 334 | { 335 | // Check if same segment (same line...) 336 | if ( IncludedSegment != nullptr && fabs(s1->a - a) < my_Default_Precision && fabs(s1->b - b) < my_Default_Precision && fabs(s1->c - c) < my_Default_Precision ) 337 | *IncludedSegment = true; 338 | return 0; // No intersection, 339 | } 340 | if ( ResIntersect != NULL) *ResIntersect = Ai; 341 | return InSegment(Ai) && s1->InSegment(Ai); 342 | } 343 | // Check if segment this cross segment s1 344 | // In any case, set ResIntersect to the point of intersection of the two lines 345 | // If this point belongs to the 2 segments, return true 346 | // If one segment is included in another, set the bool IncludedSegment but return false 347 | inline int isCrossingNoEnd(const Segment *s1, Point *ResIntersect, bool *IncludedSegment=nullptr) const 348 | { 349 | Point Ai; 350 | if ( IncludedSegment != nullptr ) *IncludedSegment = false; // Set default answer 351 | if ( !hasIntersect(s1, Ai) ) 352 | { 353 | // Check if same segment (same line...), that is A point of this is on s1 354 | if ( IncludedSegment != nullptr && fabs(s1->a*xA + s1->b*yA + s1->c) < my_Default_Precision ) 355 | *IncludedSegment = true; 356 | return 0; // No intersection, 357 | } 358 | if ( ResIntersect != NULL) *ResIntersect = Ai; 359 | return InSegmentNoEnd(Ai) && s1->InSegmentNoEnd(Ai); 360 | } 361 | 362 | /* 363 | Overloaded ostream << operator to check for print object of class point to STDOUT 364 | */ 365 | friend ostream& operator<<(ostream& output,const Segment& Seg) 366 | { 367 | output<<"Segment("< &points); 387 | virtual ~Polygon(); 388 | void addVertice(Point &p); 389 | double area(); 390 | void setPrecision(double epsilon); 391 | double distance(Point &p); 392 | 393 | int nVertices; 394 | inline int isClockWise() 395 | { 396 | // 1 : clockwise, -1 counter clockwise, 0 not computed yet 397 | return(poly_clockwise); 398 | } 399 | inline int isClosed() // Return true if polygon is closed, that is first and last oint are the same 400 | { 401 | return poly_closed; 402 | } 403 | int isInPoly(const Point *p, int *SegmentIdx = NULL); 404 | int Poly_in_Poly(Polygon *Big); 405 | 406 | Rectangle GetBoundingBox(); 407 | void ReCalcBoundingBox(); 408 | double ReCalcArea(); 409 | void delVertex(int index); 410 | void Reverse(); // set vertices order in order to have counter clockwise ordering 411 | void setStartPointMinY(); // Set vertices order such as first point has min Y 412 | Polygon *enlarge(double Diff); 413 | Point getCentroid(); 414 | inline Point *GetVertex(int idx) { return(&_Vertices[idx]); } 415 | inline vector GetVertices() { return _Vertices;} 416 | inline Segment GetSegment(int idx) { return(_Segments[idx]);} 417 | inline int CheckClosed() { if (_Vertices[0] == _Vertices[nVertices-1]) poly_closed = 1; else poly_closed = 0; return poly_closed; } 418 | Polygon *ConvexHull(); 419 | Polygon *Translate(const Point *pT, int idx_vertex); 420 | Polygon *Translate(const Point *pT); 421 | Polygon *Rotate(double angle); 422 | void ComputeAngles(); 423 | Polygon *Transform(double angle, const Point *pT); 424 | bool Intersect(Polygon *Poly2); 425 | inline double getRotation() {return angle_rotation;} 426 | inline Point getTranslation() { return pt_Translation;} 427 | inline double getSlope(int idx) { return _Angles[idx]; } 428 | void align_vertices(); 429 | void BreakLongerEdges(double max_length, double Diff); 430 | inline double getAngle(int idx) 431 | { 432 | double a; 433 | if ( idx == 0 ) 434 | a = _Angles[0] - _Angles[nVertices-2]; 435 | else 436 | a = _Angles[idx] - _Angles[idx-1]; 437 | if ( a < 0 ) a += 2*M_PI; // Return value between 0 and 2*pi 438 | return a; 439 | } 440 | void Simplify(double max_error); 441 | void CalcSegments(); 442 | int TypePoly; 443 | 444 | /* 445 | Overloaded ostream << operator to check for print object of class point to STDOUT 446 | */ 447 | friend ostream& operator<<(ostream& output,const Polygon& Poly) 448 | { 449 | output<<"Polygon with " << Poly.nVertices << " vertices : " ; 450 | output << Poly._Vertices[0]; 451 | for ( int i = 1; i < Poly.nVertices; i++ ) 452 | output << ", " << Poly._Vertices[i]; 453 | output << '\n'; 454 | return output; 455 | } 456 | 457 | 458 | protected: 459 | std::vector _Vertices; 460 | std::vector _Segments; 461 | std::vector _Angles; 462 | Rectangle BoundingBox; 463 | double poly_area; 464 | Point poly_centroid; 465 | int poly_centroid_ok; 466 | int poly_clockwise; 467 | int poly_closed; 468 | int poly_changed; 469 | double precision; 470 | double angle_rotation; 471 | Point pt_Translation; 472 | 473 | private: 474 | }; 475 | 476 | extern uint64_t nbEmptyPoly; 477 | extern uint64_t nbVectorPoly; 478 | extern uint64_t nbRotatedPoly; 479 | extern uint64_t nbTranslatedPoly; 480 | extern uint64_t nbTransformedPoly; 481 | extern uint64_t nbCachedPoly; 482 | extern uint64_t nbCreatedEmptyPoly; 483 | extern uint64_t nbCreatedVectorPoly; 484 | extern uint64_t nbCreatedRotatedPoly; 485 | extern uint64_t nbCreatedTranslatedPoly; 486 | extern uint64_t nbCreatedTransformedPoly; 487 | extern uint64_t nbCreatedCachedPoly; 488 | 489 | 490 | #endif // GEOMETRY_H 491 | -------------------------------------------------------------------------------- /Test/TestPoly4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 55 | 62 | 69 | 76 | 92 | 108 | 126 | 133 | 140 | 147 | 151 | 156 | 161 | 166 | 171 | 176 | 181 | 186 | 187 | 192 | 197 | 202 | 207 | 212 | 217 | 222 | 227 | 228 | 232 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /Test/coeur26.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 39 | 41 | 42 | 44 | image/svg+xml 45 | 47 | 48 | 49 | 50 | 51 | 57 | 63 | 69 | 75 | 81 | 87 | 93 | 99 | 105 | 111 | 117 | 123 | 129 | 135 | 141 | 147 | 153 | 159 | 165 | 171 | 177 | 183 | 189 | 195 | 201 | 207 | 213 | 214 | 215 | --------------------------------------------------------------------------------