ClipperUtils.cpp 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308
  1. #include "ClipperUtils.hpp"
  2. #include "Geometry.hpp"
  3. #include "ShortestPath.hpp"
  4. // #define CLIPPER_UTILS_DEBUG
  5. #ifdef CLIPPER_UTILS_DEBUG
  6. #include "SVG.hpp"
  7. #endif /* CLIPPER_UTILS_DEBUG */
  8. // Profiling support using the Shiny intrusive profiler
  9. //#define CLIPPER_UTILS_PROFILE
  10. #if defined(SLIC3R_PROFILE) && defined(CLIPPER_UTILS_PROFILE)
  11. #include <Shiny/Shiny.h>
  12. #define CLIPPERUTILS_PROFILE_FUNC() PROFILE_FUNC()
  13. #define CLIPPERUTILS_PROFILE_BLOCK(name) PROFILE_BLOCK(name)
  14. #else
  15. #define CLIPPERUTILS_PROFILE_FUNC()
  16. #define CLIPPERUTILS_PROFILE_BLOCK(name)
  17. #endif
  18. #define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
  19. namespace Slic3r {
  20. #ifdef CLIPPER_UTILS_DEBUG
  21. bool clipper_export_enabled = false;
  22. // For debugging the Clipper library, for providing bug reports to the Clipper author.
  23. bool export_clipper_input_polygons_bin(const char *path, const ClipperLib::Paths &input_subject, const ClipperLib::Paths &input_clip)
  24. {
  25. FILE *pfile = fopen(path, "wb");
  26. if (pfile == NULL)
  27. return false;
  28. uint32_t sz = uint32_t(input_subject.size());
  29. fwrite(&sz, 1, sizeof(sz), pfile);
  30. for (size_t i = 0; i < input_subject.size(); ++i) {
  31. const ClipperLib::Path &path = input_subject[i];
  32. sz = uint32_t(path.size());
  33. ::fwrite(&sz, 1, sizeof(sz), pfile);
  34. ::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile);
  35. }
  36. sz = uint32_t(input_clip.size());
  37. ::fwrite(&sz, 1, sizeof(sz), pfile);
  38. for (size_t i = 0; i < input_clip.size(); ++i) {
  39. const ClipperLib::Path &path = input_clip[i];
  40. sz = uint32_t(path.size());
  41. ::fwrite(&sz, 1, sizeof(sz), pfile);
  42. ::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile);
  43. }
  44. ::fclose(pfile);
  45. return true;
  46. err:
  47. ::fclose(pfile);
  48. return false;
  49. }
  50. #endif /* CLIPPER_UTILS_DEBUG */
  51. void scaleClipperPolygon(ClipperLib::Path &polygon)
  52. {
  53. CLIPPERUTILS_PROFILE_FUNC();
  54. for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) {
  55. pit->X <<= CLIPPER_OFFSET_POWER_OF_2;
  56. pit->Y <<= CLIPPER_OFFSET_POWER_OF_2;
  57. }
  58. }
  59. void scaleClipperPolygons(ClipperLib::Paths &polygons)
  60. {
  61. CLIPPERUTILS_PROFILE_FUNC();
  62. for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it)
  63. for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) {
  64. pit->X <<= CLIPPER_OFFSET_POWER_OF_2;
  65. pit->Y <<= CLIPPER_OFFSET_POWER_OF_2;
  66. }
  67. }
  68. void unscaleClipperPolygon(ClipperLib::Path &polygon)
  69. {
  70. CLIPPERUTILS_PROFILE_FUNC();
  71. for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) {
  72. pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
  73. pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
  74. pit->X >>= CLIPPER_OFFSET_POWER_OF_2;
  75. pit->Y >>= CLIPPER_OFFSET_POWER_OF_2;
  76. }
  77. }
  78. void unscaleClipperPolygons(ClipperLib::Paths &polygons)
  79. {
  80. CLIPPERUTILS_PROFILE_FUNC();
  81. for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it)
  82. for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) {
  83. pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
  84. pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
  85. pit->X >>= CLIPPER_OFFSET_POWER_OF_2;
  86. pit->Y >>= CLIPPER_OFFSET_POWER_OF_2;
  87. }
  88. }
  89. //-----------------------------------------------------------
  90. // legacy code from Clipper documentation
  91. void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* expolygons)
  92. {
  93. size_t cnt = expolygons->size();
  94. expolygons->resize(cnt + 1);
  95. (*expolygons)[cnt].contour = ClipperPath_to_Slic3rPolygon(polynode.Contour);
  96. (*expolygons)[cnt].holes.resize(polynode.ChildCount());
  97. for (int i = 0; i < polynode.ChildCount(); ++i)
  98. {
  99. (*expolygons)[cnt].holes[i] = ClipperPath_to_Slic3rPolygon(polynode.Childs[i]->Contour);
  100. //Add outer polygons contained by (nested within) holes ...
  101. for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j)
  102. AddOuterPolyNodeToExPolygons(*polynode.Childs[i]->Childs[j], expolygons);
  103. }
  104. }
  105. ExPolygons PolyTreeToExPolygons(ClipperLib::PolyTree& polytree)
  106. {
  107. ExPolygons retval;
  108. for (int i = 0; i < polytree.ChildCount(); ++i)
  109. AddOuterPolyNodeToExPolygons(*polytree.Childs[i], &retval);
  110. return retval;
  111. }
  112. //-----------------------------------------------------------
  113. Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input)
  114. {
  115. Polygon retval;
  116. for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit)
  117. retval.points.emplace_back(pit->X, pit->Y);
  118. return retval;
  119. }
  120. Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input)
  121. {
  122. Polyline retval;
  123. for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit)
  124. retval.points.emplace_back(pit->X, pit->Y);
  125. return retval;
  126. }
  127. Slic3r::Polygons ClipperPaths_to_Slic3rPolygons(const ClipperLib::Paths &input)
  128. {
  129. Slic3r::Polygons retval;
  130. retval.reserve(input.size());
  131. for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it)
  132. retval.emplace_back(ClipperPath_to_Slic3rPolygon(*it));
  133. return retval;
  134. }
  135. Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input)
  136. {
  137. Slic3r::Polylines retval;
  138. retval.reserve(input.size());
  139. for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it)
  140. retval.emplace_back(ClipperPath_to_Slic3rPolyline(*it));
  141. return retval;
  142. }
  143. ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input)
  144. {
  145. // init Clipper
  146. ClipperLib::Clipper clipper;
  147. clipper.Clear();
  148. // perform union
  149. clipper.AddPaths(input, ClipperLib::ptSubject, true);
  150. ClipperLib::PolyTree polytree;
  151. clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero
  152. // write to ExPolygons object
  153. return PolyTreeToExPolygons(polytree);
  154. }
  155. ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input)
  156. {
  157. ClipperLib::Path retval;
  158. for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit)
  159. retval.emplace_back((*pit)(0), (*pit)(1));
  160. return retval;
  161. }
  162. ClipperLib::Path Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input)
  163. {
  164. ClipperLib::Path output;
  165. output.reserve(input.points.size());
  166. for (Slic3r::Points::const_reverse_iterator pit = input.points.rbegin(); pit != input.points.rend(); ++pit)
  167. output.emplace_back((*pit)(0), (*pit)(1));
  168. return output;
  169. }
  170. ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input)
  171. {
  172. ClipperLib::Paths retval;
  173. for (Polygons::const_iterator it = input.begin(); it != input.end(); ++it)
  174. retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(*it));
  175. return retval;
  176. }
  177. ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const ExPolygons &input)
  178. {
  179. ClipperLib::Paths retval;
  180. for (auto &ep : input) {
  181. retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(ep.contour));
  182. for (auto &h : ep.holes)
  183. retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(h));
  184. }
  185. return retval;
  186. }
  187. ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input)
  188. {
  189. ClipperLib::Paths retval;
  190. for (Polylines::const_iterator it = input.begin(); it != input.end(); ++it)
  191. retval.emplace_back(Slic3rMultiPoint_to_ClipperPath(*it));
  192. return retval;
  193. }
  194. ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const double delta, ClipperLib::JoinType joinType, double miterLimit)
  195. {
  196. // scale input
  197. scaleClipperPolygons(input);
  198. // perform offset
  199. ClipperLib::ClipperOffset co;
  200. if (joinType == jtRound)
  201. co.ArcTolerance = miterLimit;
  202. else
  203. co.MiterLimit = miterLimit;
  204. double delta_scaled = delta * double(CLIPPER_OFFSET_SCALE);
  205. co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
  206. co.AddPaths(input, joinType, endType);
  207. ClipperLib::Paths retval;
  208. co.Execute(retval, delta_scaled);
  209. // unscale output
  210. unscaleClipperPolygons(retval);
  211. return retval;
  212. }
  213. ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const double delta, ClipperLib::JoinType joinType, double miterLimit)
  214. {
  215. ClipperLib::Paths paths;
  216. paths.emplace_back(std::move(input));
  217. return _offset(std::move(paths), endType, delta, joinType, miterLimit);
  218. }
  219. // This is a safe variant of the polygon offset, tailored for a single ExPolygon:
  220. // a single polygon with multiple non-overlapping holes.
  221. // Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours.
  222. ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const double delta,
  223. ClipperLib::JoinType joinType, double miterLimit)
  224. {
  225. // printf("new ExPolygon offset\n");
  226. // 1) Offset the outer contour.
  227. const double delta_scaled = delta * float(CLIPPER_OFFSET_SCALE);
  228. ClipperLib::Paths contours;
  229. {
  230. ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(expolygon.contour);
  231. scaleClipperPolygon(input);
  232. ClipperLib::ClipperOffset co;
  233. if (joinType == jtRound)
  234. co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
  235. else
  236. co.MiterLimit = miterLimit;
  237. co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
  238. co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
  239. co.Execute(contours, delta_scaled);
  240. }
  241. // 2) Offset the holes one by one, collect the results.
  242. ClipperLib::Paths holes;
  243. {
  244. holes.reserve(expolygon.holes.size());
  245. for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) {
  246. ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole);
  247. scaleClipperPolygon(input);
  248. ClipperLib::ClipperOffset co;
  249. if (joinType == jtRound)
  250. co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
  251. else
  252. co.MiterLimit = miterLimit;
  253. co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
  254. co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
  255. ClipperLib::Paths out;
  256. co.Execute(out, - delta_scaled);
  257. holes.insert(holes.end(), out.begin(), out.end());
  258. }
  259. }
  260. // 3) Subtract holes from the contours.
  261. ClipperLib::Paths output;
  262. if (holes.empty()) {
  263. output = std::move(contours);
  264. } else {
  265. ClipperLib::Clipper clipper;
  266. clipper.Clear();
  267. clipper.AddPaths(contours, ClipperLib::ptSubject, true);
  268. clipper.AddPaths(holes, ClipperLib::ptClip, true);
  269. clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  270. }
  271. // 4) Unscale the output.
  272. unscaleClipperPolygons(output);
  273. return output;
  274. }
  275. // This is a safe variant of the polygons offset, tailored for multiple ExPolygons.
  276. // It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours.
  277. // Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united.
  278. ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const double delta,
  279. ClipperLib::JoinType joinType, double miterLimit)
  280. {
  281. const double delta_scaled = delta * float(CLIPPER_OFFSET_SCALE);
  282. // Offsetted ExPolygons before they are united.
  283. ClipperLib::Paths contours_cummulative;
  284. contours_cummulative.reserve(expolygons.size());
  285. // How many non-empty offsetted expolygons were actually collected into contours_cummulative?
  286. // If only one, then there is no need to do a final union.
  287. size_t expolygons_collected = 0;
  288. for (Slic3r::ExPolygons::const_iterator it_expoly = expolygons.begin(); it_expoly != expolygons.end(); ++ it_expoly) {
  289. // 1) Offset the outer contour.
  290. ClipperLib::Paths contours;
  291. {
  292. ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(it_expoly->contour);
  293. scaleClipperPolygon(input);
  294. ClipperLib::ClipperOffset co;
  295. if (joinType == jtRound)
  296. co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
  297. else
  298. co.MiterLimit = miterLimit;
  299. co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
  300. co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
  301. co.Execute(contours, delta_scaled);
  302. }
  303. if (contours.empty())
  304. // No need to try to offset the holes.
  305. continue;
  306. if (it_expoly->holes.empty()) {
  307. // No need to subtract holes from the offsetted expolygon, we are done.
  308. contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end());
  309. ++ expolygons_collected;
  310. } else {
  311. // 2) Offset the holes one by one, collect the offsetted holes.
  312. ClipperLib::Paths holes;
  313. {
  314. for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) {
  315. ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole);
  316. scaleClipperPolygon(input);
  317. ClipperLib::ClipperOffset co;
  318. if (joinType == jtRound)
  319. co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE);
  320. else
  321. co.MiterLimit = miterLimit;
  322. co.ShortestEdgeLength = double(std::abs(delta_scaled * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
  323. co.AddPath(input, joinType, ClipperLib::etClosedPolygon);
  324. ClipperLib::Paths out;
  325. co.Execute(out, - delta_scaled);
  326. holes.insert(holes.end(), out.begin(), out.end());
  327. }
  328. }
  329. // 3) Subtract holes from the contours.
  330. if (holes.empty()) {
  331. // No hole remaining after an offset. Just copy the outer contour.
  332. contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end());
  333. ++ expolygons_collected;
  334. } else if (delta < 0) {
  335. // Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
  336. // Subtract the offsetted holes from the offsetted contours.
  337. ClipperLib::Clipper clipper;
  338. clipper.Clear();
  339. clipper.AddPaths(contours, ClipperLib::ptSubject, true);
  340. clipper.AddPaths(holes, ClipperLib::ptClip, true);
  341. ClipperLib::Paths output;
  342. clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  343. if (! output.empty()) {
  344. contours_cummulative.insert(contours_cummulative.end(), output.begin(), output.end());
  345. ++ expolygons_collected;
  346. } else {
  347. // The offsetted holes have eaten up the offsetted outer contour.
  348. }
  349. } else {
  350. // Positive offset. As long as the Clipper offset does what one expects it to do, the offsetted hole will have a smaller
  351. // area than the original hole or even disappear, therefore there will be no new intersections.
  352. // Just collect the reversed holes.
  353. contours_cummulative.reserve(contours.size() + holes.size());
  354. contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end());
  355. // Reverse the holes in place.
  356. for (size_t i = 0; i < holes.size(); ++ i)
  357. std::reverse(holes[i].begin(), holes[i].end());
  358. contours_cummulative.insert(contours_cummulative.end(), holes.begin(), holes.end());
  359. ++ expolygons_collected;
  360. }
  361. }
  362. }
  363. // 4) Unite the offsetted expolygons.
  364. ClipperLib::Paths output;
  365. if (expolygons_collected > 1 && delta > 0) {
  366. // There is a chance that the outwards offsetted expolygons may intersect. Perform a union.
  367. ClipperLib::Clipper clipper;
  368. clipper.Clear();
  369. clipper.AddPaths(contours_cummulative, ClipperLib::ptSubject, true);
  370. clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  371. } else {
  372. // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output.
  373. output = std::move(contours_cummulative);
  374. }
  375. // 4) Unscale the output.
  376. unscaleClipperPolygons(output);
  377. return output;
  378. }
  379. ClipperLib::Paths
  380. _offset2(const Polygons &polygons, const double delta1, const double delta2,
  381. const ClipperLib::JoinType joinType, const double miterLimit)
  382. {
  383. // read input
  384. ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons);
  385. // scale input
  386. scaleClipperPolygons(input);
  387. // prepare ClipperOffset object
  388. ClipperLib::ClipperOffset co;
  389. if (joinType == jtRound) {
  390. co.ArcTolerance = miterLimit;
  391. } else {
  392. co.MiterLimit = miterLimit;
  393. }
  394. double delta_scaled1 = delta1 * float(CLIPPER_OFFSET_SCALE);
  395. double delta_scaled2 = delta2 * float(CLIPPER_OFFSET_SCALE);
  396. co.ShortestEdgeLength = double(std::max(std::abs(delta_scaled1), std::abs(delta_scaled2)) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR);
  397. // perform first offset
  398. ClipperLib::Paths output1;
  399. co.AddPaths(input, joinType, ClipperLib::etClosedPolygon);
  400. co.Execute(output1, delta_scaled1);
  401. // perform second offset
  402. co.Clear();
  403. co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon);
  404. ClipperLib::Paths retval;
  405. co.Execute(retval, delta_scaled2);
  406. // unscale output
  407. unscaleClipperPolygons(retval);
  408. return retval;
  409. }
  410. Polygons
  411. offset2(const Polygons &polygons, const double delta1, const double delta2,
  412. const ClipperLib::JoinType joinType, const double miterLimit)
  413. {
  414. // perform offset
  415. ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit);
  416. // convert into ExPolygons
  417. return ClipperPaths_to_Slic3rPolygons(output);
  418. }
  419. ExPolygons
  420. offset2_ex(const Polygons &polygons, const double delta1, const double delta2,
  421. const ClipperLib::JoinType joinType, const double miterLimit)
  422. {
  423. // perform offset
  424. ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit);
  425. // convert into ExPolygons
  426. return ClipperPaths_to_Slic3rExPolygons(output);
  427. }
  428. //FIXME Vojtech: This functon may likely be optimized to avoid some of the Slic3r to Clipper
  429. // conversions and unnecessary Clipper calls.
  430. ExPolygons offset2_ex(const ExPolygons &expolygons, const double delta1,
  431. const double delta2, ClipperLib::JoinType joinType, double miterLimit)
  432. {
  433. Polygons polys;
  434. for (const ExPolygon &expoly : expolygons)
  435. append(polys,
  436. offset(offset_ex(expoly, delta1, joinType, miterLimit),
  437. delta2, joinType, miterLimit));
  438. return union_ex(polys);
  439. }
  440. template<class T, class TSubj, class TClip>
  441. T _clipper_do(const ClipperLib::ClipType clipType,
  442. TSubj && subject,
  443. TClip && clip,
  444. const ClipperLib::PolyFillType fillType,
  445. const bool safety_offset_)
  446. {
  447. // read input
  448. ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(std::forward<TSubj>(subject));
  449. ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(std::forward<TClip>(clip));
  450. // perform safety offset
  451. if (safety_offset_) {
  452. if (clipType == ClipperLib::ctUnion) {
  453. safety_offset(&input_subject);
  454. } else {
  455. safety_offset(&input_clip);
  456. }
  457. }
  458. // init Clipper
  459. ClipperLib::Clipper clipper;
  460. clipper.Clear();
  461. // add polygons
  462. clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
  463. clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
  464. // perform operation
  465. T retval;
  466. clipper.Execute(clipType, retval, fillType, fillType);
  467. return retval;
  468. }
  469. bool test_path(const ClipperLib::Path &path) {
  470. double area = std::abs(ClipperLib::Area(path));
  471. // get highest dist, but as it's n² in complexity, i use 2*dist to center wich is 2n in complexity
  472. ClipperLib::cInt max_dist_sqrd = 0;
  473. ClipperLib::IntPoint centroid = ClipperLib::Centroid(path, area);
  474. for (const ClipperLib::IntPoint& pt : path) {
  475. // &0x3FFFFFFF to let (dx * dx + dy * dy) be storable into a int64
  476. ClipperLib::cInt dx = std::abs(pt.X - centroid.X) & 0x3FFFFFFF;
  477. ClipperLib::cInt dy = std::abs(pt.Y - centroid.Y) & 0x3FFFFFFF;
  478. ClipperLib::cInt dist_sqrd = (dx * dx + dy * dy);
  479. max_dist_sqrd = std::max(max_dist_sqrd, dist_sqrd);
  480. }
  481. return (area < (SCALED_EPSILON + SCALED_EPSILON) * std::sqrt(max_dist_sqrd));
  482. }
  483. // Fix of #117: A large fractal pyramid takes ages to slice
  484. // The Clipper library has difficulties processing overlapping polygons.
  485. // Namely, the function ClipperLib::JoinCommonEdges() has potentially a terrible time complexity if the output
  486. // of the operation is of the PolyTree type.
  487. // This function implmenets a following workaround:
  488. // 1) Peform the Clipper operation with the output to Paths. This method handles overlaps in a reasonable time.
  489. // 2) Run Clipper Union once again to extract the PolyTree from the result of 1).
  490. inline ClipperLib::PolyTree _clipper_do_polytree2(const ClipperLib::ClipType clipType, const Polygons &subject,
  491. const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_)
  492. {
  493. // read input
  494. ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
  495. ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
  496. // perform safety offset
  497. if (safety_offset_)
  498. safety_offset((clipType == ClipperLib::ctUnion) ? &input_subject : &input_clip);
  499. ClipperLib::Clipper clipper;
  500. clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
  501. clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
  502. // Perform the operation with the output to input_subject.
  503. // This pass does not generate a PolyTree, which is a very expensive operation with the current Clipper library
  504. // if there are overapping edges.
  505. clipper.Execute(clipType, input_subject, fillType, fillType);
  506. // Perform an additional Union operation to generate the PolyTree ordering.
  507. clipper.Clear();
  508. clipper.AddPaths(input_subject, ClipperLib::ptSubject, true);
  509. ClipperLib::PolyTree retval;
  510. clipper.Execute(ClipperLib::ctUnion, retval, fillType, fillType);
  511. // if safety_offset_, remove too small polygons & holes
  512. if (safety_offset_)
  513. for (int idx_poly = 0; idx_poly < retval.ChildCount(); ++idx_poly) {
  514. ClipperLib::PolyNode* ex_polygon = retval.Childs[idx_poly];
  515. if (test_path(ex_polygon->Contour)) {
  516. retval.Childs.erase(retval.Childs.begin() + idx_poly);
  517. --idx_poly;
  518. } else {
  519. for (int i = 0; i < ex_polygon->ChildCount(); ++i)
  520. {
  521. if (test_path(ex_polygon->Childs[i]->Contour)) {
  522. ex_polygon->Childs.erase(ex_polygon->Childs.begin() + i);
  523. --i;
  524. }
  525. }
  526. }
  527. }
  528. return retval;
  529. }
  530. ClipperLib::PolyTree _clipper_do_pl(const ClipperLib::ClipType clipType, const Polylines &subject,
  531. const Polygons &clip, const ClipperLib::PolyFillType fillType,
  532. const bool safety_offset_)
  533. {
  534. // read input
  535. ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
  536. ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip);
  537. // perform safety offset (before scaling because it scale & unscale)
  538. if (safety_offset_) safety_offset(&input_clip);
  539. //scale to have some more precision to do some Y-bugfix
  540. scaleClipperPolygons(input_subject);
  541. scaleClipperPolygons(input_clip);
  542. //perform y safing : if a line is on the same Y, clipper may not pick the good point.
  543. //note: if not enough, next time, add some of the X coordinate (modulo it so it's contained in the scaling part)
  544. for (ClipperLib::Paths* input : { &input_subject, &input_clip })
  545. for (ClipperLib::Path& path : *input) {
  546. coord_t lasty = 0;
  547. for (ClipperLib::IntPoint& pt : path) {
  548. if (lasty == pt.Y) {
  549. pt.Y += 50;// well below CLIPPER_OFFSET_POWER_OF_2
  550. }
  551. lasty = pt.Y;
  552. }
  553. }
  554. // init Clipper
  555. ClipperLib::Clipper clipper;
  556. clipper.Clear();
  557. // add polygons
  558. clipper.AddPaths(input_subject, ClipperLib::ptSubject, false);
  559. clipper.AddPaths(input_clip, ClipperLib::ptClip, true);
  560. // perform operation
  561. ClipperLib::PolyTree retval;
  562. clipper.Execute(clipType, retval, fillType, fillType);
  563. //restore good y
  564. std::vector<ClipperLib::PolyNode*> to_check;
  565. to_check.push_back(&retval);
  566. while (!to_check.empty()) {
  567. ClipperLib::PolyNode* node = to_check.back();
  568. to_check.pop_back();
  569. for (ClipperLib::IntPoint& pit : node->Contour) {
  570. pit.X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
  571. pit.Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA;
  572. pit.X >>= CLIPPER_OFFSET_POWER_OF_2;
  573. pit.Y >>= CLIPPER_OFFSET_POWER_OF_2;
  574. }
  575. //note: moving in Y may create 0-length segment, so it needs an extra post-processing step to remove these duplicate points.
  576. for (size_t idx = 1; idx < node->Contour.size(); ++idx) {
  577. ClipperLib::IntPoint& pit = node->Contour[idx];
  578. ClipperLib::IntPoint& previous = node->Contour[idx - 1];
  579. // unscaling remove too small differences. The equality is enough.
  580. if (pit.X == previous.X && pit.Y == previous.Y) {
  581. node->Contour.erase(node->Contour.begin() + idx);
  582. --idx;
  583. }
  584. }
  585. //be sure you don't save 1-point paths
  586. if (node->Contour.size() == 1)
  587. node->Contour.clear();
  588. to_check.insert(to_check.end(), node->Childs.begin(), node->Childs.end());
  589. }
  590. return retval;
  591. }
  592. Polygons _clipper(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
  593. {
  594. return ClipperPaths_to_Slic3rPolygons(_clipper_do<ClipperLib::Paths>(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_));
  595. }
  596. ExPolygons _clipper_ex(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
  597. {
  598. ClipperLib::PolyTree polytree = _clipper_do_polytree2(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_);
  599. return PolyTreeToExPolygons(polytree);
  600. }
  601. Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, const Polygons &clip, bool safety_offset_)
  602. {
  603. ClipperLib::Paths output;
  604. ClipperLib::PolyTreeToPaths(_clipper_do_pl(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_), output);
  605. return ClipperPaths_to_Slic3rPolylines(output);
  606. }
  607. Polylines _clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, const Polygons &clip, bool safety_offset_)
  608. {
  609. // transform input polygons into polylines
  610. Polylines polylines;
  611. polylines.reserve(subject.size());
  612. for (Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon)
  613. polylines.emplace_back(polygon->operator Polyline()); // implicit call to split_at_first_point()
  614. // perform clipping
  615. Polylines retval = _clipper_pl(clipType, polylines, clip, safety_offset_);
  616. /* If the split_at_first_point() call above happens to split the polygon inside the clipping area
  617. we would get two consecutive polylines instead of a single one, so we go through them in order
  618. to recombine continuous polylines. */
  619. for (size_t i = 0; i < retval.size(); ++i) {
  620. for (size_t j = i+1; j < retval.size(); ++j) {
  621. if (retval[i].points.back() == retval[j].points.front()) {
  622. /* If last point of i coincides with first point of j,
  623. append points of j to i and delete j */
  624. retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end());
  625. retval.erase(retval.begin() + j);
  626. --j;
  627. } else if (retval[i].points.front() == retval[j].points.back()) {
  628. /* If first point of i coincides with last point of j,
  629. prepend points of j to i and delete j */
  630. retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1);
  631. retval.erase(retval.begin() + j);
  632. --j;
  633. } else if (retval[i].points.front() == retval[j].points.front()) {
  634. /* Since Clipper does not preserve orientation of polylines,
  635. also check the case when first point of i coincides with first point of j. */
  636. retval[j].reverse();
  637. retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1);
  638. retval.erase(retval.begin() + j);
  639. --j;
  640. } else if (retval[i].points.back() == retval[j].points.back()) {
  641. /* Since Clipper does not preserve orientation of polylines,
  642. also check the case when last point of i coincides with last point of j. */
  643. retval[j].reverse();
  644. retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end());
  645. retval.erase(retval.begin() + j);
  646. --j;
  647. }
  648. }
  649. }
  650. return retval;
  651. }
  652. Lines
  653. _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip,
  654. bool safety_offset_)
  655. {
  656. // convert Lines to Polylines
  657. Polylines polylines;
  658. polylines.reserve(subject.size());
  659. for (const Line &line : subject)
  660. polylines.emplace_back(Polyline(line.a, line.b));
  661. // perform operation
  662. polylines = _clipper_pl(clipType, polylines, clip, safety_offset_);
  663. // convert Polylines to Lines
  664. Lines retval;
  665. for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline)
  666. retval.emplace_back(polyline->operator Line());
  667. return retval;
  668. }
  669. ClipperLib::PolyTree union_pt(const Polygons &subject, bool safety_offset_)
  670. {
  671. return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
  672. }
  673. ClipperLib::PolyTree union_pt(const ExPolygons &subject, bool safety_offset_)
  674. {
  675. return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
  676. }
  677. ClipperLib::PolyTree union_pt(Polygons &&subject, bool safety_offset_)
  678. {
  679. return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
  680. }
  681. ClipperLib::PolyTree union_pt(ExPolygons &&subject, bool safety_offset_)
  682. {
  683. return _clipper_do<ClipperLib::PolyTree>(ClipperLib::ctUnion, std::move(subject), Polygons(), ClipperLib::pftEvenOdd, safety_offset_);
  684. }
  685. // Simple spatial ordering of Polynodes
  686. ClipperLib::PolyNodes order_nodes(const ClipperLib::PolyNodes &nodes)
  687. {
  688. // collect ordering points
  689. Points ordering_points;
  690. ordering_points.reserve(nodes.size());
  691. for (const ClipperLib::PolyNode *node : nodes)
  692. ordering_points.emplace_back(
  693. Point(node->Contour.front().X, node->Contour.front().Y));
  694. // perform the ordering
  695. ClipperLib::PolyNodes ordered_nodes =
  696. chain_clipper_polynodes(ordering_points, nodes);
  697. return ordered_nodes;
  698. }
  699. static void traverse_pt_noholes(const ClipperLib::PolyNodes &nodes, Polygons *out)
  700. {
  701. foreach_node<e_ordering::ON>(nodes, [&out](const ClipperLib::PolyNode *node)
  702. {
  703. traverse_pt_noholes(node->Childs, out);
  704. out->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour));
  705. if (node->IsHole()) out->back().reverse(); // ccw
  706. });
  707. }
  708. static void traverse_pt_outside_in(const ClipperLib::PolyNodes &nodes, Polygons *retval)
  709. {
  710. // collect ordering points
  711. Points ordering_points;
  712. ordering_points.reserve(nodes.size());
  713. for (const ClipperLib::PolyNode *node : nodes)
  714. ordering_points.emplace_back(node->Contour.front().X, node->Contour.front().Y);
  715. // Perform the ordering, push results recursively.
  716. //FIXME pass the last point to chain_clipper_polynodes?
  717. for (const ClipperLib::PolyNode *node : chain_clipper_polynodes(ordering_points, nodes)) {
  718. retval->emplace_back(ClipperPath_to_Slic3rPolygon(node->Contour));
  719. if (node->IsHole())
  720. // Orient a hole, which is clockwise oriented, to CCW.
  721. retval->back().reverse();
  722. // traverse the next depth
  723. traverse_pt_outside_in(node->Childs, retval);
  724. }
  725. }
  726. Polygons union_pt_chained_outside_in(const Polygons &subject, bool safety_offset_)
  727. {
  728. ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_);
  729. Polygons retval;
  730. traverse_pt_outside_in(polytree.Childs, &retval);
  731. return retval;
  732. }
  733. Polygons simplify_polygons(const Polygons &subject, bool preserve_collinear)
  734. {
  735. // convert into Clipper polygons
  736. ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
  737. ClipperLib::Paths output;
  738. if (preserve_collinear) {
  739. ClipperLib::Clipper c;
  740. c.PreserveCollinear(true);
  741. c.StrictlySimple(true);
  742. c.AddPaths(input_subject, ClipperLib::ptSubject, true);
  743. c.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  744. } else {
  745. ClipperLib::SimplifyPolygons(input_subject, output, ClipperLib::pftNonZero);
  746. }
  747. // convert into Slic3r polygons
  748. return ClipperPaths_to_Slic3rPolygons(output);
  749. }
  750. ExPolygons simplify_polygons_ex(const Polygons &subject, bool preserve_collinear)
  751. {
  752. if (! preserve_collinear)
  753. return union_ex(simplify_polygons(subject, false));
  754. // convert into Clipper polygons
  755. ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject);
  756. ClipperLib::PolyTree polytree;
  757. ClipperLib::Clipper c;
  758. c.PreserveCollinear(true);
  759. c.StrictlySimple(true);
  760. c.AddPaths(input_subject, ClipperLib::ptSubject, true);
  761. c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  762. // convert into ExPolygons
  763. return PolyTreeToExPolygons(polytree);
  764. }
  765. void safety_offset(ClipperLib::Paths* paths)
  766. {
  767. CLIPPERUTILS_PROFILE_FUNC();
  768. // scale input
  769. scaleClipperPolygons(*paths);
  770. // perform offset (delta = scale 1e-05)
  771. ClipperLib::ClipperOffset co;
  772. #ifdef CLIPPER_UTILS_DEBUG
  773. if (clipper_export_enabled) {
  774. static int iRun = 0;
  775. export_clipper_input_polygons_bin(debug_out_path("safety_offset-polygons-%d", ++iRun).c_str(), *paths, ClipperLib::Paths());
  776. }
  777. #endif /* CLIPPER_UTILS_DEBUG */
  778. ClipperLib::Paths out;
  779. for (size_t i = 0; i < paths->size(); ++ i) {
  780. ClipperLib::Path &path = (*paths)[i];
  781. co.Clear();
  782. co.MiterLimit = 2;
  783. bool ccw = ClipperLib::Orientation(path);
  784. if (! ccw)
  785. std::reverse(path.begin(), path.end());
  786. {
  787. CLIPPERUTILS_PROFILE_BLOCK(safety_offset_AddPaths);
  788. co.AddPath((*paths)[i], ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
  789. }
  790. {
  791. CLIPPERUTILS_PROFILE_BLOCK(safety_offset_Execute);
  792. // offset outside by 10um
  793. ClipperLib::Paths out_this;
  794. co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE));
  795. if (! ccw) {
  796. // Reverse the resulting contours once again.
  797. for (ClipperLib::Paths::iterator it = out_this.begin(); it != out_this.end(); ++ it)
  798. std::reverse(it->begin(), it->end());
  799. }
  800. if (out.empty())
  801. out = std::move(out_this);
  802. else
  803. std::move(std::begin(out_this), std::end(out_this), std::back_inserter(out));
  804. }
  805. }
  806. *paths = std::move(out);
  807. // unscale output
  808. unscaleClipperPolygons(*paths);
  809. }
  810. Polygons top_level_islands(const Slic3r::Polygons &polygons)
  811. {
  812. // init Clipper
  813. ClipperLib::Clipper clipper;
  814. clipper.Clear();
  815. // perform union
  816. clipper.AddPaths(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::ptSubject, true);
  817. ClipperLib::PolyTree polytree;
  818. clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd);
  819. // Convert only the top level islands to the output.
  820. Polygons out;
  821. out.reserve(polytree.ChildCount());
  822. for (int i = 0; i < polytree.ChildCount(); ++i)
  823. out.emplace_back(ClipperPath_to_Slic3rPolygon(polytree.Childs[i]->Contour));
  824. return out;
  825. }
  826. // Outer offset shall not split the input contour into multiples. It is expected, that the solution will be non empty and it will contain just a single polygon.
  827. ClipperLib::Paths fix_after_outer_offset(
  828. const ClipperLib::Path &input,
  829. // combination of default prameters to correspond to void ClipperOffset::Execute(Paths& solution, double delta)
  830. // to produce a CCW output contour from CCW input contour for a positive offset.
  831. ClipperLib::PolyFillType filltype, // = ClipperLib::pftPositive
  832. bool reverse_result) // = false
  833. {
  834. ClipperLib::Paths solution;
  835. if (! input.empty()) {
  836. ClipperLib::Clipper clipper;
  837. clipper.AddPath(input, ClipperLib::ptSubject, true);
  838. clipper.ReverseSolution(reverse_result);
  839. clipper.Execute(ClipperLib::ctUnion, solution, filltype, filltype);
  840. }
  841. return solution;
  842. }
  843. // Inner offset may split the source contour into multiple contours, but one resulting contour shall not lie inside the other.
  844. ClipperLib::Paths fix_after_inner_offset(
  845. const ClipperLib::Path &input,
  846. // combination of default prameters to correspond to void ClipperOffset::Execute(Paths& solution, double delta)
  847. // to produce a CCW output contour from CCW input contour for a negative offset.
  848. ClipperLib::PolyFillType filltype, // = ClipperLib::pftNegative
  849. bool reverse_result) // = true
  850. {
  851. ClipperLib::Paths solution;
  852. if (! input.empty()) {
  853. ClipperLib::Clipper clipper;
  854. clipper.AddPath(input, ClipperLib::ptSubject, true);
  855. ClipperLib::IntRect r = clipper.GetBounds();
  856. r.left -= 10; r.top -= 10; r.right += 10; r.bottom += 10;
  857. if (filltype == ClipperLib::pftPositive)
  858. clipper.AddPath({ ClipperLib::IntPoint(r.left, r.bottom), ClipperLib::IntPoint(r.left, r.top), ClipperLib::IntPoint(r.right, r.top), ClipperLib::IntPoint(r.right, r.bottom) }, ClipperLib::ptSubject, true);
  859. else
  860. clipper.AddPath({ ClipperLib::IntPoint(r.left, r.bottom), ClipperLib::IntPoint(r.right, r.bottom), ClipperLib::IntPoint(r.right, r.top), ClipperLib::IntPoint(r.left, r.top) }, ClipperLib::ptSubject, true);
  861. clipper.ReverseSolution(reverse_result);
  862. clipper.Execute(ClipperLib::ctUnion, solution, filltype, filltype);
  863. if (! solution.empty())
  864. solution.erase(solution.begin());
  865. }
  866. return solution;
  867. }
  868. ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector<float> &deltas, double miter_limit)
  869. {
  870. assert(contour.size() == deltas.size());
  871. #ifndef NDEBUG
  872. // Verify that the deltas are either all positive, or all negative.
  873. bool positive = false;
  874. bool negative = false;
  875. for (float delta : deltas)
  876. if (delta < 0.f)
  877. negative = true;
  878. else if (delta > 0.f)
  879. positive = true;
  880. assert(! (negative && positive));
  881. #endif /* NDEBUG */
  882. ClipperLib::Path out;
  883. if (deltas.size() > 2)
  884. {
  885. out.reserve(contour.size() * 2);
  886. // Clamp miter limit to 2.
  887. miter_limit = (miter_limit > 2.) ? 2. / (miter_limit * miter_limit) : 0.5;
  888. // perpenduclar vector
  889. auto perp = [](const Vec2d &v) -> Vec2d { return Vec2d(v.y(), - v.x()); };
  890. // Add a new point to the output, scale by CLIPPER_OFFSET_SCALE and round to ClipperLib::cInt.
  891. auto add_offset_point = [&out](Vec2d pt) {
  892. pt *= double(CLIPPER_OFFSET_SCALE);
  893. pt += Vec2d(0.5 - (pt.x() < 0), 0.5 - (pt.y() < 0));
  894. out.emplace_back(ClipperLib::cInt(pt.x()), ClipperLib::cInt(pt.y()));
  895. };
  896. // Minimum edge length, squared.
  897. double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR;
  898. double l2min = lmin * lmin;
  899. // Minimum angle to consider two edges to be parallel.
  900. // Vojtech's estimate.
  901. // const double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE);
  902. // Implementation equal to Clipper.
  903. const double sin_min_parallel = 1.;
  904. // Find the last point further from pt by l2min.
  905. Vec2d pt = contour.front().cast<double>();
  906. size_t iprev = contour.size() - 1;
  907. Vec2d ptprev;
  908. for (; iprev > 0; -- iprev) {
  909. ptprev = contour[iprev].cast<double>();
  910. if ((ptprev - pt).squaredNorm() > l2min)
  911. break;
  912. }
  913. if (iprev != 0) {
  914. size_t ilast = iprev;
  915. // Normal to the (pt - ptprev) segment.
  916. Vec2d nprev = perp(pt - ptprev).normalized();
  917. for (size_t i = 0; ; ) {
  918. // Find the next point further from pt by l2min.
  919. size_t j = i + 1;
  920. Vec2d ptnext;
  921. for (; j <= ilast; ++ j) {
  922. ptnext = contour[j].cast<double>();
  923. double l2 = (ptnext - pt).squaredNorm();
  924. if (l2 > l2min)
  925. break;
  926. }
  927. if (j > ilast) {
  928. assert(i <= ilast);
  929. // If the last edge is too short, merge it with the previous edge.
  930. i = ilast;
  931. ptnext = contour.front().cast<double>();
  932. }
  933. // Normal to the (ptnext - pt) segment.
  934. Vec2d nnext = perp(ptnext - pt).normalized();
  935. double delta = deltas[i];
  936. double sin_a = clamp(-1., 1., cross2(nprev, nnext));
  937. double convex = sin_a * delta;
  938. if (convex <= - sin_min_parallel) {
  939. // Concave corner.
  940. add_offset_point(pt + nprev * delta);
  941. add_offset_point(pt);
  942. add_offset_point(pt + nnext * delta);
  943. } else {
  944. double dot = nprev.dot(nnext);
  945. if (convex < sin_min_parallel && dot > 0.) {
  946. // Nearly parallel.
  947. add_offset_point((nprev.dot(nnext) > 0.) ? (pt + nprev * delta) : pt);
  948. } else {
  949. // Convex corner, possibly extremely sharp if convex < sin_min_parallel.
  950. double r = 1. + dot;
  951. if (r >= miter_limit)
  952. add_offset_point(pt + (nprev + nnext) * (delta / r));
  953. else {
  954. double dx = std::tan(std::atan2(sin_a, dot) / 4.);
  955. Vec2d newpt1 = pt + (nprev - perp(nprev) * dx) * delta;
  956. Vec2d newpt2 = pt + (nnext + perp(nnext) * dx) * delta;
  957. #ifndef NDEBUG
  958. Vec2d vedge = 0.5 * (newpt1 + newpt2) - pt;
  959. double dist_norm = vedge.norm();
  960. assert(std::abs(dist_norm - std::abs(delta)) < SCALED_EPSILON);
  961. #endif /* NDEBUG */
  962. add_offset_point(newpt1);
  963. add_offset_point(newpt2);
  964. }
  965. }
  966. }
  967. if (i == ilast)
  968. break;
  969. ptprev = pt;
  970. nprev = nnext;
  971. pt = ptnext;
  972. i = j;
  973. }
  974. }
  975. }
  976. #if 0
  977. {
  978. ClipperLib::Path polytmp(out);
  979. unscaleClipperPolygon(polytmp);
  980. Slic3r::Polygon offsetted = ClipperPath_to_Slic3rPolygon(polytmp);
  981. BoundingBox bbox = get_extents(contour);
  982. bbox.merge(get_extents(offsetted));
  983. static int iRun = 0;
  984. SVG svg(debug_out_path("mittered_offset_path_scaled-%d.svg", iRun ++).c_str(), bbox);
  985. svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
  986. svg.draw_outline(offsetted, "red", scale_(0.01));
  987. svg.draw(contour, "blue", scale_(0.03));
  988. svg.draw((Points)offsetted, "blue", scale_(0.03));
  989. }
  990. #endif
  991. return out;
  992. }
  993. Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
  994. {
  995. #ifndef NDEBUG
  996. // Verify that the deltas are all non positive.
  997. for (const std::vector<float> &ds : deltas)
  998. for (float delta : ds)
  999. assert(delta <= 0.);
  1000. assert(expoly.holes.size() + 1 == deltas.size());
  1001. #endif /* NDEBUG */
  1002. // 1) Offset the outer contour.
  1003. ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, true);
  1004. #ifndef NDEBUG
  1005. for (auto &c : contours)
  1006. assert(ClipperLib::Area(c) > 0.);
  1007. #endif /* NDEBUG */
  1008. // 2) Offset the holes one by one, collect the results.
  1009. ClipperLib::Paths holes;
  1010. holes.reserve(expoly.holes.size());
  1011. for (const Polygon& hole : expoly.holes)
  1012. append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, false));
  1013. #ifndef NDEBUG
  1014. for (auto &c : holes)
  1015. assert(ClipperLib::Area(c) > 0.);
  1016. #endif /* NDEBUG */
  1017. // 3) Subtract holes from the contours.
  1018. ClipperLib::Paths output;
  1019. if (holes.empty())
  1020. output = std::move(contours);
  1021. else {
  1022. ClipperLib::Clipper clipper;
  1023. clipper.Clear();
  1024. clipper.AddPaths(contours, ClipperLib::ptSubject, true);
  1025. clipper.AddPaths(holes, ClipperLib::ptClip, true);
  1026. clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  1027. }
  1028. // 4) Unscale the output.
  1029. unscaleClipperPolygons(output);
  1030. return ClipperPaths_to_Slic3rPolygons(output);
  1031. }
  1032. Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
  1033. {
  1034. #ifndef NDEBUG
  1035. // Verify that the deltas are all non positive.
  1036. for (const std::vector<float>& ds : deltas)
  1037. for (float delta : ds)
  1038. assert(delta >= 0.);
  1039. assert(expoly.holes.size() + 1 == deltas.size());
  1040. #endif /* NDEBUG */
  1041. // 1) Offset the outer contour.
  1042. ClipperLib::Paths contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false);
  1043. #ifndef NDEBUG
  1044. for (auto &c : contours)
  1045. assert(ClipperLib::Area(c) > 0.);
  1046. #endif /* NDEBUG */
  1047. // 2) Offset the holes one by one, collect the results.
  1048. ClipperLib::Paths holes;
  1049. holes.reserve(expoly.holes.size());
  1050. for (const Polygon& hole : expoly.holes)
  1051. append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true));
  1052. #ifndef NDEBUG
  1053. for (auto &c : holes)
  1054. assert(ClipperLib::Area(c) > 0.);
  1055. #endif /* NDEBUG */
  1056. // 3) Subtract holes from the contours.
  1057. ClipperLib::Paths output;
  1058. if (holes.empty())
  1059. output = std::move(contours);
  1060. else {
  1061. ClipperLib::Clipper clipper;
  1062. clipper.Clear();
  1063. clipper.AddPaths(contours, ClipperLib::ptSubject, true);
  1064. clipper.AddPaths(holes, ClipperLib::ptClip, true);
  1065. clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  1066. }
  1067. // 4) Unscale the output.
  1068. unscaleClipperPolygons(output);
  1069. return ClipperPaths_to_Slic3rPolygons(output);
  1070. }
  1071. ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
  1072. {
  1073. #ifndef NDEBUG
  1074. // Verify that the deltas are all non positive.
  1075. for (const std::vector<float>& ds : deltas)
  1076. for (float delta : ds)
  1077. assert(delta >= 0.);
  1078. assert(expoly.holes.size() + 1 == deltas.size());
  1079. #endif /* NDEBUG */
  1080. // 1) Offset the outer contour.
  1081. ClipperLib::Paths contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false);
  1082. #ifndef NDEBUG
  1083. for (auto &c : contours)
  1084. assert(ClipperLib::Area(c) > 0.);
  1085. #endif /* NDEBUG */
  1086. // 2) Offset the holes one by one, collect the results.
  1087. ClipperLib::Paths holes;
  1088. holes.reserve(expoly.holes.size());
  1089. for (const Polygon& hole : expoly.holes)
  1090. append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true));
  1091. #ifndef NDEBUG
  1092. for (auto &c : holes)
  1093. assert(ClipperLib::Area(c) > 0.);
  1094. #endif /* NDEBUG */
  1095. // 3) Subtract holes from the contours.
  1096. unscaleClipperPolygons(contours);
  1097. ExPolygons output;
  1098. if (holes.empty()) {
  1099. output.reserve(contours.size());
  1100. for (ClipperLib::Path &path : contours)
  1101. output.emplace_back(ClipperPath_to_Slic3rPolygon(path));
  1102. } else {
  1103. ClipperLib::Clipper clipper;
  1104. unscaleClipperPolygons(holes);
  1105. clipper.AddPaths(contours, ClipperLib::ptSubject, true);
  1106. clipper.AddPaths(holes, ClipperLib::ptClip, true);
  1107. ClipperLib::PolyTree polytree;
  1108. clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  1109. output = PolyTreeToExPolygons(polytree);
  1110. }
  1111. return output;
  1112. }
  1113. ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit)
  1114. {
  1115. #ifndef NDEBUG
  1116. // Verify that the deltas are all non positive.
  1117. for (const std::vector<float>& ds : deltas)
  1118. for (float delta : ds)
  1119. assert(delta <= 0.);
  1120. assert(expoly.holes.size() + 1 == deltas.size());
  1121. #endif /* NDEBUG */
  1122. // 1) Offset the outer contour.
  1123. ClipperLib::Paths contours = fix_after_inner_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftNegative, true);
  1124. #ifndef NDEBUG
  1125. for (auto &c : contours)
  1126. assert(ClipperLib::Area(c) > 0.);
  1127. #endif /* NDEBUG */
  1128. // 2) Offset the holes one by one, collect the results.
  1129. ClipperLib::Paths holes;
  1130. holes.reserve(expoly.holes.size());
  1131. for (const Polygon& hole : expoly.holes)
  1132. append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, false));
  1133. //tiny holes can be reduced to giberish, get rid of them.
  1134. for (auto it = holes.begin(); it != holes.end();)
  1135. if (ClipperLib::Area(*it) < double(CLIPPER_OFFSET_SCALE) * double(CLIPPER_OFFSET_SCALE)) {
  1136. it = holes.erase(it);
  1137. }
  1138. else ++it;
  1139. #ifndef NDEBUG
  1140. for (auto &c : holes)
  1141. assert(ClipperLib::Area(c) > 0.);
  1142. #endif /* NDEBUG */
  1143. // 3) Subtract holes from the contours.
  1144. unscaleClipperPolygons(contours);
  1145. ExPolygons output;
  1146. if (holes.empty()) {
  1147. output.reserve(contours.size());
  1148. for (ClipperLib::Path &path : contours)
  1149. output.emplace_back(ClipperPath_to_Slic3rPolygon(path));
  1150. } else {
  1151. ClipperLib::Clipper clipper;
  1152. unscaleClipperPolygons(holes);
  1153. clipper.AddPaths(contours, ClipperLib::ptSubject, true);
  1154. clipper.AddPaths(holes, ClipperLib::ptClip, true);
  1155. ClipperLib::PolyTree polytree;
  1156. clipper.Execute(ClipperLib::ctDifference, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero);
  1157. output = PolyTreeToExPolygons(polytree);
  1158. }
  1159. return output;
  1160. }
  1161. }