diff --git a/src/libigl/CMakeLists.txt b/src/libigl/CMakeLists.txt index 0852fad72..3daac757b 100644 --- a/src/libigl/CMakeLists.txt +++ b/src/libigl/CMakeLists.txt @@ -10,5 +10,5 @@ if(libigl_FOUND) target_link_libraries(libigl INTERFACE igl::core) else() message(STATUS "IGL NOT found, using bundled version...") - target_include_directories(libigl INTERFACE SYSTEM ${LIBDIR}/libigl) + target_include_directories(libigl SYSTEM BEFORE INTERFACE ${LIBDIR}/libigl) endif() diff --git a/src/libnest2d/tests/test.cpp b/src/libnest2d/tests/test.cpp index 5741e87b4..363a3930c 100644 --- a/src/libnest2d/tests/test.cpp +++ b/src/libnest2d/tests/test.cpp @@ -10,10 +10,6 @@ #include "boost/multiprecision/integer.hpp" #include "boost/rational.hpp" -//#include "../tools/Int128.hpp" - -//#include "gte/Mathematics/GteMinimumAreaBox2.h" - //#include "../tools/libnfpglue.hpp" //#include "../tools/nfp_svgnest_glue.hpp" @@ -42,151 +38,151 @@ struct NfpImpl std::vector& prusaParts() { static std::vector ret; - + if(ret.empty()) { ret.reserve(PRINTER_PART_POLYGONS.size()); for(auto& inp : PRINTER_PART_POLYGONS) ret.emplace_back(inp); } - + return ret; } TEST(BasicFunctionality, Angles) { - + using namespace libnest2d; - + Degrees deg(180); Radians rad(deg); Degrees deg2(rad); - + ASSERT_DOUBLE_EQ(rad, Pi); ASSERT_DOUBLE_EQ(deg, 180); ASSERT_DOUBLE_EQ(deg2, 180); ASSERT_DOUBLE_EQ(rad, Radians(deg)); ASSERT_DOUBLE_EQ( Degrees(rad), deg); - + ASSERT_TRUE(rad == deg); - + Segment seg = {{0, 0}, {12, -10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 270 && Degrees(seg.angleToXaxis()) < 360); - + seg = {{0, 0}, {12, 10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 0 && Degrees(seg.angleToXaxis()) < 90); - + seg = {{0, 0}, {-12, 10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 90 && Degrees(seg.angleToXaxis()) < 180); - + seg = {{0, 0}, {-12, -10}}; - + ASSERT_TRUE(Degrees(seg.angleToXaxis()) > 180 && Degrees(seg.angleToXaxis()) < 270); - + seg = {{0, 0}, {1, 0}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 0); - + seg = {{0, 0}, {0, 1}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 90); - - + + seg = {{0, 0}, {-1, 0}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 180); - - + + seg = {{0, 0}, {0, -1}}; - + ASSERT_DOUBLE_EQ(Degrees(seg.angleToXaxis()), 270); - + } // Simple test, does not use gmock TEST(BasicFunctionality, creationAndDestruction) { using namespace libnest2d; - + Item sh = { {0, 0}, {1, 0}, {1, 1}, {0, 1} }; - + ASSERT_EQ(sh.vertexCount(), 4u); - + Item sh2 ({ {0, 0}, {1, 0}, {1, 1}, {0, 1} }); - + ASSERT_EQ(sh2.vertexCount(), 4u); - + // copy Item sh3 = sh2; - + ASSERT_EQ(sh3.vertexCount(), 4u); - + sh2 = {}; - + ASSERT_EQ(sh2.vertexCount(), 0u); ASSERT_EQ(sh3.vertexCount(), 4u); - + } TEST(GeometryAlgorithms, boundingCircle) { using namespace libnest2d; using placers::boundingCircle; - + PolygonImpl p = {{{0, 10}, {10, 0}, {0, -10}, {0, 10}}, {}}; Circle c = boundingCircle(p); - + ASSERT_EQ(c.center().X, 0); ASSERT_EQ(c.center().Y, 0); ASSERT_DOUBLE_EQ(c.radius(), 10); - + shapelike::translate(p, PointImpl{10, 10}); c = boundingCircle(p); - + ASSERT_EQ(c.center().X, 10); ASSERT_EQ(c.center().Y, 10); ASSERT_DOUBLE_EQ(c.radius(), 10); - + auto parts = prusaParts(); - + int i = 0; for(auto& part : parts) { c = boundingCircle(part.transformedShape()); if(std::isnan(c.radius())) std::cout << "fail: radius is nan" << std::endl; - + else for(auto v : shapelike::contour(part.transformedShape()) ) { - auto d = pointlike::distance(v, c.center()); - if(d > c.radius() ) { - auto e = std::abs( 1.0 - d/c.radius()); - ASSERT_LE(e, 1e-3); + auto d = pointlike::distance(v, c.center()); + if(d > c.radius() ) { + auto e = std::abs( 1.0 - d/c.radius()); + ASSERT_LE(e, 1e-3); + } } - } i++; } - + } TEST(GeometryAlgorithms, Distance) { using namespace libnest2d; - + Point p1 = {0, 0}; - + Point p2 = {10, 0}; Point p3 = {10, 10}; - + ASSERT_DOUBLE_EQ(pointlike::distance(p1, p2), 10); ASSERT_DOUBLE_EQ(pointlike::distance(p1, p3), sqrt(200)); - + Segment seg(p1, p3); - -// ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); - + + // ASSERT_DOUBLE_EQ(pointlike::distance(p2, seg), 7.0710678118654755); + auto result = pointlike::horizontalDistance(p2, seg); - + auto check = [](TCompute val, TCompute expected) { if(std::is_floating_point>::value) ASSERT_DOUBLE_EQ(static_cast(val), @@ -194,44 +190,44 @@ TEST(GeometryAlgorithms, Distance) { else ASSERT_EQ(val, expected); }; - + ASSERT_TRUE(result.second); check(result.first, 10); - + result = pointlike::verticalDistance(p2, seg); ASSERT_TRUE(result.second); check(result.first, -10); - + result = pointlike::verticalDistance(Point{10, 20}, seg); ASSERT_TRUE(result.second); check(result.first, 10); - - + + Point p4 = {80, 0}; Segment seg2 = { {0, 0}, {0, 40} }; - + result = pointlike::horizontalDistance(p4, seg2); - + ASSERT_TRUE(result.second); check(result.first, 80); - + result = pointlike::verticalDistance(p4, seg2); // Point should not be related to the segment ASSERT_FALSE(result.second); - + } TEST(GeometryAlgorithms, Area) { using namespace libnest2d; - + Rectangle rect(10, 10); - + ASSERT_EQ(rect.area(), 100); - + Rectangle rect2 = {100, 100}; - + ASSERT_EQ(rect2.area(), 10000); - + Item item = { {61, 97}, {70, 151}, @@ -242,33 +238,33 @@ TEST(GeometryAlgorithms, Area) { {61, 77}, {61, 97} }; - + ASSERT_TRUE(shapelike::area(item.transformedShape()) > 0 ); } TEST(GeometryAlgorithms, IsPointInsidePolygon) { using namespace libnest2d; - + Rectangle rect(10, 10); - + Point p = {1, 1}; - + ASSERT_TRUE(rect.isInside(p)); - + p = {11, 11}; - + ASSERT_FALSE(rect.isInside(p)); - - + + p = {11, 12}; - + ASSERT_FALSE(rect.isInside(p)); - - + + p = {3, 3}; - + ASSERT_TRUE(rect.isInside(p)); - + } //TEST(GeometryAlgorithms, Intersections) { @@ -294,21 +290,21 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) { using namespace libnest2d; using namespace libnest2d; - + Box bin(100, 100); BottomLeftPlacer placer(bin); - + Item item = {{70, 75}, {88, 60}, {65, 50}, {60, 30}, {80, 20}, {42, 20}, {35, 35}, {35, 55}, {40, 75}, {70, 75}}; - + Item leftControl = { {40, 75}, - {35, 55}, - {35, 35}, - {42, 20}, - {0, 20}, - {0, 75}, - {40, 75}}; - + {35, 55}, + {35, 35}, + {42, 20}, + {0, 20}, + {0, 75}, + {40, 75}}; + Item downControl = {{88, 60}, {88, 0}, {35, 0}, @@ -318,22 +314,22 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) {60, 30}, {65, 50}, {88, 60}}; - + Item leftp(placer.leftPoly(item)); - + ASSERT_TRUE(shapelike::isValid(leftp.rawShape()).first); ASSERT_EQ(leftp.vertexCount(), leftControl.vertexCount()); - + for(unsigned long i = 0; i < leftControl.vertexCount(); i++) { ASSERT_EQ(getX(leftp.vertex(i)), getX(leftControl.vertex(i))); ASSERT_EQ(getY(leftp.vertex(i)), getY(leftControl.vertex(i))); } - + Item downp(placer.downPoly(item)); - + ASSERT_TRUE(shapelike::isValid(downp.rawShape()).first); ASSERT_EQ(downp.vertexCount(), downControl.vertexCount()); - + for(unsigned long i = 0; i < downControl.vertexCount(); i++) { ASSERT_EQ(getX(downp.vertex(i)), getX(downControl.vertex(i))); ASSERT_EQ(getY(downp.vertex(i)), getY(downControl.vertex(i))); @@ -344,7 +340,7 @@ TEST(GeometryAlgorithms, LeftAndDownPolygon) TEST(GeometryAlgorithms, ArrangeRectanglesTight) { using namespace libnest2d; - + std::vector rects = { {80, 80}, {60, 90}, @@ -366,17 +362,17 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) {5, 5}, {5, 5}, {20, 20} }; - - + + Nester arrange(Box(210, 250)); - + auto groups = arrange(rects.begin(), rects.end()); - + ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); - + // check for no intersections, no containment: - + for(auto result : groups) { bool valid = true; for(Item& r1 : result) { @@ -389,14 +385,14 @@ TEST(GeometryAlgorithms, ArrangeRectanglesTight) } } } - + } TEST(GeometryAlgorithms, ArrangeRectanglesLoose) { using namespace libnest2d; - -// std::vector rects = { {40, 40}, {10, 10}, {20, 20} }; + + // std::vector rects = { {40, 40}, {10, 10}, {20, 20} }; std::vector rects = { {80, 80}, {60, 90}, @@ -418,17 +414,17 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) {5, 5}, {5, 5}, {20, 20} }; - + Coord min_obj_distance = 5; - + Nester arrange(Box(210, 250), - min_obj_distance); - + min_obj_distance); + auto groups = arrange(rects.begin(), rects.end()); - + ASSERT_EQ(groups.size(), 1u); ASSERT_EQ(groups[0].size(), rects.size()); - + // check for no intersections, no containment: auto result = groups[0]; bool valid = true; @@ -441,7 +437,7 @@ TEST(GeometryAlgorithms, ArrangeRectanglesLoose) } } } - + } namespace { @@ -449,68 +445,68 @@ using namespace libnest2d; template void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { - - + + std::string loc = "out"; - + static std::string svg_header = -R"raw( + R"raw( )raw"; - + int i = idx; auto r = result; -// for(auto r : result) { - std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); - if(out.is_open()) { - out << svg_header; - Item rbin( Rectangle(bin.width(), bin.height()) ); - for(unsigned i = 0; i < rbin.vertexCount(); i++) { - auto v = rbin.vertex(i); - setY(v, -getY(v)/SCALE + 500 ); - setX(v, getX(v)/SCALE); - rbin.setVertex(i, v); - } - out << shapelike::serialize(rbin.rawShape()) << std::endl; - for(Item& sh : r) { - Item tsh(sh.transformedShape()); - for(unsigned i = 0; i < tsh.vertexCount(); i++) { - auto v = tsh.vertex(i); - setY(v, -getY(v)/SCALE + 500); - setX(v, getX(v)/SCALE); - tsh.setVertex(i, v); - } - out << shapelike::serialize(tsh.rawShape()) << std::endl; - } - out << "\n" << std::endl; + // for(auto r : result) { + std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); + if(out.is_open()) { + out << svg_header; + Item rbin( Rectangle(bin.width(), bin.height()) ); + for(unsigned i = 0; i < rbin.vertexCount(); i++) { + auto v = rbin.vertex(i); + setY(v, -getY(v)/SCALE + 500 ); + setX(v, getX(v)/SCALE); + rbin.setVertex(i, v); } - out.close(); - -// i++; -// } + out << shapelike::serialize(rbin.rawShape()) << std::endl; + for(Item& sh : r) { + Item tsh(sh.transformedShape()); + for(unsigned i = 0; i < tsh.vertexCount(); i++) { + auto v = tsh.vertex(i); + setY(v, -getY(v)/SCALE + 500); + setX(v, getX(v)/SCALE); + tsh.setVertex(i, v); + } + out << shapelike::serialize(tsh.rawShape()) << std::endl; + } + out << "\n" << std::endl; + } + out.close(); + + // i++; + // } } } TEST(GeometryAlgorithms, BottomLeftStressTest) { using namespace libnest2d; - + const Coord SCALE = 1000000; auto& input = prusaParts(); - + Box bin(210*SCALE, 250*SCALE); BottomLeftPlacer placer(bin); - + auto it = input.begin(); auto next = it; int i = 0; while(it != input.end() && ++next != input.end()) { placer.pack(*it); placer.pack(*next); - + auto result = placer.getItems(); bool valid = true; - + if(result.size() == 2) { Item& r1 = result[0]; Item& r2 = result[1]; @@ -525,7 +521,7 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { std::cout << "something went terribly wrong!" << std::endl; FAIL(); } - + placer.clearItems(); it++; i++; @@ -534,9 +530,9 @@ TEST(GeometryAlgorithms, BottomLeftStressTest) { TEST(GeometryAlgorithms, convexHull) { using namespace libnest2d; - + ClipperLib::Path poly = PRINTER_PART_POLYGONS[0]; - + auto chull = sl::convexHull(poly); ASSERT_EQ(chull.size(), poly.size()); @@ -545,7 +541,7 @@ TEST(GeometryAlgorithms, convexHull) { TEST(GeometryAlgorithms, NestTest) { std::vector input = prusaParts(); - + PackGroup result = libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { @@ -553,16 +549,17 @@ TEST(GeometryAlgorithms, NestTest) { << "parts left: " << cnt << std::endl; }); - + ASSERT_LE(result.size(), 2); - - int partsum = std::accumulate(result.begin(), - result.end(), - 0, - [](int s, - const decltype(result)::value_type &bin) { - return s += bin.size(); - }); + + size_t partsum = std::accumulate(result.begin(), + result.end(), + size_t(0), + [](int s, + const decltype( + result)::value_type &bin) { + return s += bin.size(); + }); ASSERT_EQ(input.size(), partsum); } @@ -647,7 +644,7 @@ std::vector nfp_testdata = { {118, 101}, {117, 103}, {117, 107} - }, + }, { {102, 116}, {111, 126}, @@ -658,7 +655,7 @@ std::vector nfp_testdata = { {147, 84}, {102, 84}, {102, 116}, - } + } }, { { @@ -674,7 +671,7 @@ std::vector nfp_testdata = { {108, 70}, {99, 102}, {99, 122}, - }, + }, { {107, 124}, {128, 125}, @@ -691,7 +688,7 @@ std::vector nfp_testdata = { {108, 85}, {107, 86}, {107, 124}, - } + } }, { { @@ -706,7 +703,7 @@ std::vector nfp_testdata = { {132, 57}, {91, 98}, {91, 100}, - }, + }, { {101, 90}, {103, 98}, @@ -724,74 +721,74 @@ std::vector nfp_testdata = { {102, 87}, {101, 89}, {101, 90}, - } + } } }; -std::vector nfp_concave_testdata = { - { // ItemPair - { - { - {533726, 142141}, - {532359, 143386}, - {530141, 142155}, - {528649, 160091}, - {533659, 157607}, - {538669, 160091}, - {537178, 142155}, - {534959, 143386}, - {533726, 142141}, - } - }, - { - { - {118305, 11603}, - {118311, 26616}, - {113311, 26611}, - {109311, 29604}, - {109300, 44608}, - {109311, 49631}, - {113300, 52636}, - {118311, 52636}, - {118308, 103636}, - {223830, 103636}, - {236845, 90642}, - {236832, 11630}, - {232825, 11616}, - {210149, 11616}, - {211308, 13625}, - {209315, 17080}, - {205326, 17080}, - {203334, 13629}, - {204493, 11616}, - {118305, 11603}, - } - }, - } + std::vector nfp_concave_testdata = { + { // ItemPair + { + { + {533726, 142141}, + {532359, 143386}, + {530141, 142155}, + {528649, 160091}, + {533659, 157607}, + {538669, 160091}, + {537178, 142155}, + {534959, 143386}, + {533726, 142141}, + } + }, + { + { + {118305, 11603}, + {118311, 26616}, + {113311, 26611}, + {109311, 29604}, + {109300, 44608}, + {109311, 49631}, + {113300, 52636}, + {118311, 52636}, + {118308, 103636}, + {223830, 103636}, + {236845, 90642}, + {236832, 11630}, + {232825, 11616}, + {210149, 11616}, + {211308, 13625}, + {209315, 17080}, + {205326, 17080}, + {203334, 13629}, + {204493, 11616}, + {118305, 11603}, + } + }, + } }; template void testNfp(const std::vector& testdata) { using namespace libnest2d; - + Box bin(210*SCALE, 250*SCALE); - + int testcase = 0; - + auto& exportfun = exportSVG; - + auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ testcase++; - + orbiter.translate({210*SCALE, 0}); - + auto&& nfp = nfp::noFitPolygon(stationary.rawShape(), orbiter.transformedShape()); - + placers::correctNfpPosition(nfp, stationary, orbiter); - + auto valid = shapelike::isValid(nfp.first); - + /*Item infp(nfp.first); if(!valid.first) { std::cout << "test instance: " << testidx << " " @@ -799,46 +796,46 @@ void testNfp(const std::vector& testdata) { std::vector> inp = {std::ref(infp)}; exportfun(inp, bin, testidx); }*/ - + ASSERT_TRUE(valid.first); - + Item infp(nfp.first); - + int i = 0; auto rorbiter = orbiter.transformedShape(); auto vo = nfp::referenceVertex(rorbiter); - + ASSERT_TRUE(stationary.isInside(infp)); - + for(auto v : infp) { auto dx = getX(v) - getX(vo); auto dy = getY(v) - getY(vo); - + Item tmp = orbiter; - + tmp.translate({dx, dy}); - + bool touching = Item::touches(tmp, stationary); - + if(!touching || !valid.first) { std::vector> inp = { std::ref(stationary), std::ref(tmp), std::ref(infp) }; - + exportfun(inp, bin, testcase*i++); } - + ASSERT_TRUE(touching); } }; - + unsigned tidx = 0; for(auto& td : testdata) { auto orbiter = td.orbiter; auto stationary = td.stationary; onetest(orbiter, stationary, tidx++); } - + tidx = 0; for(auto& td : testdata) { auto orbiter = td.stationary; @@ -858,19 +855,19 @@ TEST(GeometryAlgorithms, nfpConvexConvex) { TEST(GeometryAlgorithms, pointOnPolygonContour) { using namespace libnest2d; - + Rectangle input(10, 10); - + placers::EdgeCache ecache(input); - + auto first = *input.begin(); ASSERT_TRUE(getX(first) == getX(ecache.coords(0))); ASSERT_TRUE(getY(first) == getY(ecache.coords(0))); - + auto last = *std::prev(input.end()); ASSERT_TRUE(getX(last) == getX(ecache.coords(1.0))); ASSERT_TRUE(getY(last) == getY(ecache.coords(1.0))); - + for(int i = 0; i <= 100; i++) { auto v = ecache.coords(i*(0.01)); ASSERT_TRUE(shapelike::touches(v, input.transformedShape())); @@ -879,24 +876,24 @@ TEST(GeometryAlgorithms, pointOnPolygonContour) { TEST(GeometryAlgorithms, mergePileWithPolygon) { using namespace libnest2d; - + Rectangle rect1(10, 15); Rectangle rect2(15, 15); Rectangle rect3(20, 15); - + rect2.translate({10, 0}); rect3.translate({25, 0}); - + TMultiShape pile; pile.push_back(rect1.transformedShape()); pile.push_back(rect2.transformedShape()); - + auto result = nfp::merge(pile, rect3.transformedShape()); - + ASSERT_EQ(result.size(), 1); - + Rectangle ref(45, 15); - + ASSERT_EQ(shapelike::area(result.front()), ref.area()); } @@ -908,7 +905,7 @@ long double refMinAreaBox(const PolygonImpl& p) { long double min_area = std::numeric_limits::max(); - + auto update_min = [&min_area, &it, &itx, &p]() { Segment s(*it, *itx); @@ -935,67 +932,39 @@ template struct BoostGCD { }; using Unit = int64_t; -using Ratio = boost::rational;// Rational; - -//double gteMinAreaBox(const PolygonImpl& p) { - -// using GteCoord = ClipperLib::cInt; -// using GtePoint = gte::Vector2; - -// gte::MinimumAreaBox2 mb; - -// std::vector points; -// points.reserve(p.Contour.size()); - -// for(auto& pt : p.Contour) points.emplace_back(GtePoint{GteCoord(pt.X), GteCoord(pt.Y)}); - -// mb(int(points.size()), points.data(), 0, nullptr, true); - -// auto min_area = double(mb.GetArea()); - -// return min_area; -//} +using Ratio = boost::rational; } TEST(RotatingCalipers, MinAreaBBCClk) { -// PolygonImpl poly({{-50, 30}, {-50, -50}, {50, -50}, {50, 50}, {-40, 50}}); - -// PolygonImpl poly({{-50, 0}, {50, 0}, {0, 100}}); - auto u = [](ClipperLib::cInt n) { return n*1000000; }; PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); - long double arearef = refMinAreaBox(poly); long double area = minAreaBoundingBox(poly).area(); -// double gtearea = gteMinAreaBox(poly); ASSERT_LE(std::abs(area - arearef), 500e6 ); -// ASSERT_LE(std::abs(gtearea - arearef), 500 ); -// ASSERT_DOUBLE_EQ(gtearea, arearef); } TEST(RotatingCalipers, AllPrusaMinBB) { - size_t idx = 0; + // /size_t idx = 0; long double err_epsilon = 500e6l; for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { -// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx]; -// rinput.pop_back(); -// std::reverse(rinput.begin(), rinput.end()); + // ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx]; + // rinput.pop_back(); + // std::reverse(rinput.begin(), rinput.end()); -// PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); + // PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); PolygonImpl poly(rinput); long double arearef = refMinAreaBox(poly); auto bb = minAreaBoundingBox(rinput); long double area = cast(bb.area()); -// double area = gteMinAreaBox(poly); bool succ = std::abs(arearef - area) < err_epsilon; - std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " - << arearef << " actual: " << area << std::endl; + // std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " +// << arearef << " actual: " << area << std::endl; ASSERT_TRUE(succ); } @@ -1006,21 +975,20 @@ TEST(RotatingCalipers, AllPrusaMinBB) { PolygonImpl poly(removeCollinearPoints(rinput, 1000000)); - long double arearef = refMinAreaBox(poly); auto bb = minAreaBoundingBox(poly); long double area = cast(bb.area()); -// double area = gteMinAreaBox(poly); + bool succ = std::abs(arearef - area) < err_epsilon; - std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " - << arearef << " actual: " << area << std::endl; + // std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: " +// << arearef << " actual: " << area << std::endl; ASSERT_TRUE(succ); } } int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index b261a79be..70603cd15 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -7,6 +7,9 @@ #include // for std::forward #include +#include "libslic3r.h" +#include "Point.hpp" + namespace Slic3r { /// Handy little spin mutex for the cached meshes. @@ -239,13 +242,92 @@ template bool all_of(const C &container) }); } -template inline X ceil_i(X x, Y y) -{ - static_assert(std::is_integral::value && - std::is_integral::value && sizeof(X) >= sizeof(Y), - ""); +// A shorter C++14 style form of the enable_if metafunction +template +using enable_if_t = typename std::enable_if::type; - return (x % y) ? x / y + 1 : x / y; +// ///////////////////////////////////////////////////////////////////////////// +// Type safe conversions to and from scaled and unscaled coordinates +// ///////////////////////////////////////////////////////////////////////////// + +// A meta-predicate which is true for integers wider than or equal to coord_t +template struct is_scaled_coord +{ + static const SLIC3R_CONSTEXPR bool value = + std::is_integral::value && + std::numeric_limits::digits >= + std::numeric_limits::digits; +}; + +// Meta predicates for floating, 'scaled coord' and generic arithmetic types +template +using FloatingOnly = enable_if_t::value, T>; + +template +using ScaledCoordOnly = enable_if_t::value, T>; + +template +using ArithmeticOnly = enable_if_t::value, T>; + +// A shorter form for a generic Eigen vector which is widely used in PrusaSlicer +template +using EigenVec = Eigen::Matrix; + +// Semantics are the following: +// Upscaling (scaled()): only from floating point types (or Vec) to either +// floating point or integer 'scaled coord' coordinates. +// Downscaling (unscaled()): from arithmetic types (or Vec) to either +// floating point only + +// Conversion definition from unscaled to floating point scaled +template, + class = FloatingOnly> +inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT +{ + return static_cast(v / static_cast(SCALING_FACTOR)); +} + +// Conversion definition from unscaled to integer 'scaled coord'. +// TODO: is the rounding necessary ? Here it is to show that it can be different +// but it does not have to be. Using std::round means loosing noexcept and +// constexpr modifiers +template> +inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCEPT +{ + //return static_cast(std::round(v / SCALING_FACTOR)); + return static_cast(v / static_cast(SCALING_FACTOR)); +} + +// Conversion for Eigen vectors (N dimensional points) +template> +inline EigenVec, N> scaled(const EigenVec &v) +{ + return v.template cast() / SCALING_FACTOR; +} + +// Conversion from arithmetic scaled type to floating point unscaled +template, + class = FloatingOnly> +inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT +{ + return static_cast(v * static_cast(SCALING_FACTOR)); +} + +// Unscaling for Eigen vectors. Input base type can be arithmetic, output base +// type can only be floating point. +template, + class = FloatingOnly> +inline SLIC3R_CONSTEXPR EigenVec unscaled( + const EigenVec &v) SLIC3R_NOEXCEPT +{ + return v.template cast() * SCALING_FACTOR; } } // namespace Slic3r diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp index 6fc1b3327..fafb54a58 100644 --- a/src/libslic3r/MinAreaBoundingBox.cpp +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -39,7 +39,7 @@ template<> inline Slic3r::Points& contour(Slic3r::Polygon& sh) { return sh.point template<> inline const Slic3r::Points& contour(const Slic3r::Polygon& sh) { return sh.points; } template<> Slic3r::Points::iterator begin(Slic3r::Points& pts, const PathTag&) { return pts.begin();} -template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.begin(); } +template<> Slic3r::Points::const_iterator cbegin(const Slic3r::Points& pts, const PathTag&) { return pts.cbegin(); } template<> Slic3r::Points::iterator end(Slic3r::Points& pts, const PathTag&) { return pts.end();} template<> Slic3r::Points::const_iterator cend(const Slic3r::Points& pts, const PathTag&) { return pts.cend(); } @@ -71,62 +71,67 @@ using Rational = boost::rational<__int128>; MinAreaBoundigBox::MinAreaBoundigBox(const Polygon &p, PolygonLevel pc) { - const Polygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); - - m_right = box.right_extent(); - m_bottom = box.bottom_extent(); - m_axis = box.axis(); + const Polygon &chull = pc == pcConvex ? p : + libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); } MinAreaBoundigBox::MinAreaBoundigBox(const ExPolygon &p, PolygonLevel pc) { - const ExPolygon& chull = pc == pcConvex ? p : libnest2d::sl::convexHull(p); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); - - m_right = box.right_extent(); - m_bottom = box.bottom_extent(); - m_axis = box.axis(); + const ExPolygon &chull = pc == pcConvex ? p : + libnest2d::sl::convexHull(p); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); } MinAreaBoundigBox::MinAreaBoundigBox(const Points &pts, PolygonLevel pc) { - const Points& chull = pc == pcConvex ? pts : libnest2d::sl::convexHull(pts); - - libnest2d::RotatedBox box = - libnest2d::minAreaBoundingBox(chull); - - m_right = box.right_extent(); - m_bottom = box.bottom_extent(); - m_axis = box.axis(); + const Points &chull = pc == pcConvex ? pts : + libnest2d::sl::convexHull(pts); + + libnest2d::RotatedBox box = + libnest2d::minAreaBoundingBox(chull); + + m_right = libnest2d::cast(box.right_extent()); + m_bottom = libnest2d::cast(box.bottom_extent()); + m_axis = box.axis(); } double MinAreaBoundigBox::angle_to_X() const { double ret = std::atan2(m_axis.y(), m_axis.x()); - auto s = std::signbit(ret); - if(s) ret += 2 * PI; + auto s = std::signbit(ret); + if (s) ret += 2 * PI; return -ret; } long double MinAreaBoundigBox::width() const { - return std::abs(m_bottom) / std::sqrt(libnest2d::pl::magnsq(m_axis)); + return std::abs(m_bottom) / + std::sqrt(libnest2d::pl::magnsq(m_axis)); } long double MinAreaBoundigBox::height() const { - return std::abs(m_right) / std::sqrt(libnest2d::pl::magnsq(m_axis)); + return std::abs(m_right) / + std::sqrt(libnest2d::pl::magnsq(m_axis)); } long double MinAreaBoundigBox::area() const { long double asq = libnest2d::pl::magnsq(m_axis); - return m_bottom * m_right / asq; + return m_bottom * m_right / asq; } void remove_collinear_points(Polygon &p) @@ -138,5 +143,4 @@ void remove_collinear_points(ExPolygon &p) { p = libnest2d::removeCollinearPoints(p, Unit(0)); } - -} +} // namespace Slic3r diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index c9573344c..173638c03 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1564,8 +1564,10 @@ void ModelVolume::center_geometry_after_creation() Vec3d shift = this->mesh().bounding_box().center(); if (!shift.isApprox(Vec3d::Zero())) { - m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); - m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + if (m_mesh) + m_mesh->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); + if (m_convex_hull) + m_convex_hull->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2)); translate(shift); } } diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 36f7e3971..d3586651b 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -62,10 +62,10 @@ std::string toString(const Model& model, bool holes = true) { objinst->transform_mesh(&tmpmesh); ExPolygons expolys = tmpmesh.horizontal_projection(); for(auto& expoly_complex : expolys) { - - auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); + + ExPolygons tmp = expoly_complex.simplify(scaled(1.)); if(tmp.empty()) continue; - auto expoly = tmp.front(); + ExPolygon expoly = tmp.front(); expoly.contour.make_clockwise(); for(auto& h : expoly.holes) h.make_counter_clockwise(); @@ -610,7 +610,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, if(tolerance > EPSILON) { Polygons pp { p }; - pp = p.simplify(double(scaled(tolerance))); + pp = p.simplify(scaled(tolerance)); if (!pp.empty()) p = pp.front(); } @@ -633,8 +633,8 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, if(item.vertexCount() > 3) { item.rotation(Geometry::rotation_diff_z(rotation0, objinst->get_rotation())); item.translation({ - ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), - ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) + scaled(objinst->get_offset(X)), + scaled(objinst->get_offset(Y)) }); ret.emplace_back(objinst, item); } @@ -658,8 +658,8 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model, Item item(std::move(pn)); item.rotation(wti.rotation), item.translation({ - ClipperLib::cInt(wti.pos(0)/SCALING_FACTOR), - ClipperLib::cInt(wti.pos(1)/SCALING_FACTOR) + scaled(wti.pos(0)), + scaled(wti.pos(1)) }); ret.emplace_back(nullptr, item); } @@ -822,7 +822,9 @@ bool arrange(Model &model, // The model with the geometries auto& cfn = stopcondition; - coord_t md = ceil_i(min_obj_distance, 2) - SCALED_EPSILON; + // Integer ceiling the min distance from the bed perimeters + coord_t md = min_obj_distance - SCALED_EPSILON; + md = (md % 2) ? md / 2 + 1 : md / 2; auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, libnest2d::Coord{bbb.min(1)} - md}, @@ -916,7 +918,9 @@ void find_new_position(const Model &model, BoundingBox bbb(bed); - coord_t md = ceil_i(min_obj_distance, 2) - SCALED_EPSILON; + // Integer ceiling the min distance from the bed perimeters + coord_t md = min_obj_distance - SCALED_EPSILON; + md = (md % 2) ? md / 2 + 1 : md / 2; auto binbb = Box({libnest2d::Coord{bbb.min(0)} - md, libnest2d::Coord{bbb.min(1)} - md}, diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 3b199c4eb..04cbd7824 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -5,6 +5,7 @@ #include "SLABoostAdapter.hpp" #include "ClipperUtils.hpp" #include "Tesselate.hpp" +#include "MTUtils.hpp" // For debugging: //#include @@ -203,7 +204,7 @@ void offset(ExPolygon& sh, coord_t distance) { } ClipperOffset offs; - offs.ArcTolerance = 0.01*scaled(1.0); + offs.ArcTolerance = scaled(0.01); Paths result; offs.AddPath(ctour, jtRound, etClosedPolygon); offs.AddPaths(holes, jtRound, etClosedPolygon); @@ -351,7 +352,7 @@ Contour3D round_edges(const ExPolygon& base_plate, double x2 = xx*xx; double stepy = std::sqrt(r2 - x2); - offset(ob, s*scaled(xx)); + offset(ob, s * scaled(xx)); wh = ceilheight_mm - radius_mm + stepy; Contour3D pwalls; @@ -375,7 +376,7 @@ Contour3D round_edges(const ExPolygon& base_plate, double xx = radius_mm - i*stepx; double x2 = xx*xx; double stepy = std::sqrt(r2 - x2); - offset(ob, s*scaled(xx)); + offset(ob, s * scaled(xx)); wh = ceilheight_mm - radius_mm - stepy; Contour3D pwalls; @@ -476,7 +477,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; - double max_dist = scaled(max_dist_mm); + double max_dist = scaled(max_dist_mm); ExPolygon& expo = punion[idx++]; BoundingBox querybb(expo); @@ -492,7 +493,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, ctour.reserve(3); ctour.emplace_back(cc); - Point d(coord_t(scaled(1.)*nx), coord_t(scaled(1.)*ny)); + Point d(scaled(nx), scaled(ny)); ctour.emplace_back(c + Point( -y(d), x(d) )); ctour.emplace_back(c + Point( y(d), -x(d) )); offset(r, scaled(1.)); @@ -529,14 +530,14 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, ExPolygons tmp; tmp.reserve(count); for(ExPolygons& o : out) for(ExPolygon& e : o) { - auto&& exss = e.simplify(scaled(0.1)); + auto&& exss = e.simplify(scaled(0.1)); for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); } ExPolygons utmp = unify(tmp); for(auto& o : utmp) { - auto&& smp = o.simplify(scaled(0.1)); + auto&& smp = o.simplify(scaled(0.1)); output.insert(output.end(), smp.begin(), smp.end()); } } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index c73ae5650..7ae481ffb 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -668,7 +668,7 @@ void SLAPrint::process() double ilhd = m_material_config.initial_layer_height.getFloat(); auto ilh = float(ilhd); - auto ilhs = scaled(ilhd); + coord_t ilhs = scaled(ilhd); const size_t objcount = m_objects.size(); static const unsigned min_objstatus = 0; // where the per object operations start @@ -694,17 +694,15 @@ void SLAPrint::process() // We need to prepare the slice index... - double lhd = m_objects.front()->m_config.layer_height.getFloat(); - float lh = float(lhd); - auto lhs = scaled(lhd); - - auto &&bb3d = mesh.bounding_box(); - double minZ = bb3d.min(Z) - po.get_elevation(); - double maxZ = bb3d.max(Z); - auto minZf = float(minZ); - - auto minZs = scaled(minZ); - auto maxZs = scaled(maxZ); + double lhd = m_objects.front()->m_config.layer_height.getFloat(); + float lh = float(lhd); + coord_t lhs = scaled(lhd); + auto && bb3d = mesh.bounding_box(); + double minZ = bb3d.min(Z) - po.get_elevation(); + double maxZ = bb3d.max(Z); + auto minZf = float(minZ); + coord_t minZs = scaled(minZ); + coord_t maxZs = scaled(maxZ); po.m_slice_index.clear(); @@ -1013,9 +1011,6 @@ void SLAPrint::process() using ClipperPolygons = std::vector; namespace sl = libnest2d::shapelike; // For algorithms - // If the raster has vertical orientation, we will flip the coordinates -// bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; - // Set up custom union and diff functions for clipper polygons auto polyunion = [] (const ClipperPolygons& subjects) { @@ -1066,8 +1061,8 @@ void SLAPrint::process() const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20] - const double width = scaled(m_printer_config.display_width.getFloat()); - const double height = scaled(m_printer_config.display_height.getFloat()); + const auto width = scaled(m_printer_config.display_width.getFloat()); + const auto height = scaled(m_printer_config.display_height.getFloat()); const double display_area = width*height; // get polygons for all instances in the object @@ -1123,11 +1118,6 @@ void SLAPrint::process() sl::translate(poly, ClipperPoint{instances[i].shift(X), instances[i].shift(Y)}); -// if (flpXY) { -// for(auto& p : poly.Contour) std::swap(p.X, p.Y); -// for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y); -// } - polygons.emplace_back(std::move(poly)); } } diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 8cafae17c..dc2b6a4ec 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -61,20 +61,6 @@ typedef double coordf_t; #define SLIC3R_NOEXCEPT noexcept #endif -template inline SLIC3R_CONSTEXPR coord_t scaled(Tf val) -{ - static_assert (std::is_floating_point::value, "Floating point only"); - return coord_t(val / Tf(SCALING_FACTOR)); -} - -template inline SLIC3R_CONSTEXPR Tf unscaled(coord_t val) -{ - static_assert (std::is_floating_point::value, "Floating point only"); - return Tf(val * Tf(SCALING_FACTOR)); -} - -inline SLIC3R_CONSTEXPR float unscaledf(coord_t val) { return unscaled(val); } - inline std::string debug_out_path(const char *name, ...) { char buffer[2048]; diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index edc9845a1..3b6210058 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -54,10 +54,9 @@ void AppConfig::set_defaults() if (get("preset_update").empty()) set("preset_update", "1"); - // Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers. - // github.com/prusa3d/PrusaSlicer/issues/233 - if (get("use_legacy_opengl").empty()) - set("use_legacy_opengl", "0"); + // remove old 'use_legacy_opengl' parameter from this config, if present + if (!get("use_legacy_opengl").empty()) + erase("", "use_legacy_opengl"); #if __APPLE__ if (get("use_retina_opengl").empty()) @@ -73,8 +72,8 @@ void AppConfig::set_defaults() if (get("custom_toolbar_size").empty()) set("custom_toolbar_size", "100"); - if (get("camera_type").empty()) - set("camera_type", "1"); + if (get("use_perspective_camera").empty()) + set("use_perspective_camera", "1"); // Remove legacy window positions/sizes erase("", "main_frame_maximized"); diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 1f8513ac2..6cb8ff520 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -31,7 +31,7 @@ Camera::Camera() : phi(45.0f) , requires_zoom_to_bed(false) , inverted_phi(false) - , m_type(Ortho) + , m_type(Perspective) , m_target(Vec3d::Zero()) , m_theta(45.0f) , m_zoom(1.0) @@ -61,20 +61,17 @@ void Camera::set_type(EType type) if (m_type != type) { m_type = type; - - wxGetApp().app_config->set("camera_type", std::to_string(m_type)); + wxGetApp().app_config->set("use_perspective_camera", (m_type == Perspective) ? "1" : "0"); wxGetApp().app_config->save(); } } void Camera::set_type(const std::string& type) { - if (!type.empty() && (type != "1")) - { - unsigned char type_id = atoi(type.c_str()); - if (((unsigned char)Ortho < type_id) && (type_id < (unsigned char)Num_types)) - set_type((Camera::EType)type_id); - } + if (type == "1") + set_type(Perspective); + else + set_type(Ortho); } void Camera::select_next_type() diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index bd2541ce2..79e87c726 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -49,6 +49,7 @@ public: EType get_type() const { return m_type; } std::string get_type_as_string() const; void set_type(EType type); + // valid values for type: "0" -> ortho, "1" -> perspective void set_type(const std::string& type); void select_next_type(); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 138946166..60afcb653 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -198,8 +198,7 @@ void GLCanvas3D::Shader::_reset() #endif // !ENABLE_TEXTURES_FROM_SVG GLCanvas3D::LayersEditing::LayersEditing() - : m_use_legacy_opengl(false) - , m_enabled(false) + : m_enabled(false) , m_z_texture_id(0) , m_model_object(nullptr) , m_object_max_z(0.f) @@ -274,12 +273,7 @@ void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) bool GLCanvas3D::LayersEditing::is_allowed() const { - return !m_use_legacy_opengl && m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; -} - -void GLCanvas3D::LayersEditing::set_use_legacy_opengl(bool use_legacy_opengl) -{ - m_use_legacy_opengl = use_legacy_opengl; + return m_shader.is_initialized() && m_shader.get_shader()->shader_program_id > 0 && m_z_texture_id > 0; } bool GLCanvas3D::LayersEditing::is_enabled() const @@ -463,8 +457,10 @@ void GLCanvas3D::LayersEditing::_render_profile(const Rect& bar_rect) const { //FIXME show some kind of legend. + if (!m_slicing_parameters) + return; + // Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region. - assert(m_slicing_parameters != nullptr); float scale_x = bar_rect.get_width() / (float)(1.12 * m_slicing_parameters->max_layer_height); float scale_y = bar_rect.get_height() / m_object_max_z; float x = bar_rect.get_left() + (float)m_slicing_parameters->layer_height * scale_x; @@ -1253,7 +1249,7 @@ void GLCanvas3D::post_event(wxEvent &&event) wxPostEvent(m_canvas, event); } -bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) +bool GLCanvas3D::init(bool useVBOs) { if (m_initialized) return true; @@ -1311,7 +1307,6 @@ bool GLCanvas3D::init(bool useVBOs, bool use_legacy_opengl) return false; m_use_VBOs = useVBOs; - m_layers_editing.set_use_legacy_opengl(use_legacy_opengl); // on linux the gl context is not valid until the canvas is not shown on screen // we defer the geometry finalization of volumes until the first call to render() @@ -3317,6 +3312,9 @@ void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range void GLCanvas3D::update_ui_from_settings() { + m_camera.set_type(wxGetApp().app_config->get("use_perspective_camera")); + m_dirty = true; + #if ENABLE_RETINA_GL const float orig_scaling = m_retina_helper->get_scale_factor(); @@ -4527,17 +4525,12 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool color_by_tool() const { return tool_colors != nullptr; } size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() / 4 : 0; } const float* color_tool(size_t tool) const { return tool_colors->data() + tool * 4; } - int volume_idx(int extruder, int feature) const - { - return this->color_by_color_print() ? 0 : this->color_by_tool() ? std::min(this->number_tools() - 1, std::max(extruder - 1, 0)) : feature; - } // For coloring by a color_print(M600), return a parsed color. bool color_by_color_print() const { return color_print_values!=nullptr; } - const float* color_print_by_layer_idx(const size_t layer_idx) const - { + const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), layers[layer_idx]->print_z + EPSILON); - return color_tool((it - color_print_values->begin()) % number_tools()); + return (it - color_print_values->begin()) % number_tools(); } } ctxt; @@ -4570,7 +4563,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start"; //FIXME Improve the heuristics for a grain size. - size_t grain_size = ctxt.color_by_color_print() ? size_t(1) : std::max(ctxt.layers.size() / 16, size_t(1)); + size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1)); tbb::spin_mutex new_volume_mutex; auto new_volume = [this, &new_volume_mutex](const float *color) -> GLVolume* { auto *volume = new GLVolume(color); @@ -4579,14 +4572,34 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c new_volume_mutex.unlock(); return volume; }; - const size_t volumes_cnt_initial = m_volumes.volumes.size(); - std::vector volumes_per_thread(ctxt.layers.size()); + const size_t volumes_cnt_initial = m_volumes.volumes.size(); tbb::parallel_for( tbb::blocked_range(0, ctxt.layers.size(), grain_size), [&ctxt, &new_volume](const tbb::blocked_range& range) { - GLVolumePtrs vols; - if (ctxt.color_by_color_print()) - vols.emplace_back(new_volume(ctxt.color_print_by_layer_idx(range.begin()))); + GLVolumePtrs vols; + std::vector color_print_layer_to_glvolume; + auto volume = [&ctxt, &vols, &color_print_layer_to_glvolume, &range](size_t layer_idx, int extruder, int feature) -> GLVolume& { + return *vols[ctxt.color_by_color_print() ? + color_print_layer_to_glvolume[layer_idx - range.begin()] : + ctxt.color_by_tool() ? + std::min(ctxt.number_tools() - 1, std::max(extruder - 1, 0)) : + feature + ]; + }; + if (ctxt.color_by_color_print()) { + // Create a map from the layer index to a GLVolume, which is initialized with the correct layer span color. + std::vector color_print_tool_to_glvolume(ctxt.number_tools(), -1); + color_print_layer_to_glvolume.reserve(range.end() - range.begin()); + vols.reserve(ctxt.number_tools()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + int idx_tool = (int)ctxt.color_print_color_idx_by_layer_idx(idx_layer); + if (color_print_tool_to_glvolume[idx_tool] == -1) { + color_print_tool_to_glvolume[idx_tool] = (int)vols.size(); + vols.emplace_back(new_volume(ctxt.color_tool(idx_tool))); + } + color_print_layer_to_glvolume.emplace_back(color_print_tool_to_glvolume[idx_tool]); + } + } else if (ctxt.color_by_tool()) { for (size_t i = 0; i < ctxt.number_tools(); ++i) vols.emplace_back(new_volume(ctxt.color_tool(i))); @@ -4594,33 +4607,31 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c else vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) }; for (GLVolume *vol : vols) - vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) { + vol->indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const Layer *layer = ctxt.layers[idx_layer]; - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { - vol.print_zs.push_back(layer->print_z); - vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); - vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); + for (GLVolume *vol : vols) + if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) { + vol->print_zs.push_back(layer->print_z); + vol->offsets.push_back(vol->indexed_vertex_array.quad_indices.size()); + vol->offsets.push_back(vol->indexed_vertex_array.triangle_indices.size()); } - } for (const Point © : *ctxt.shifted_copies) { for (const LayerRegion *layerm : layer->regions()) { if (ctxt.has_perimeters) _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - *vols[ctxt.volume_idx(layerm->region()->config().perimeter_extruder.value, 0)]); + volume(idx_layer, layerm->region()->config().perimeter_extruder.value, 0)); if (ctxt.has_infill) { for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); - if (!fill->entities.empty()) + if (! fill->entities.empty()) _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - is_solid_infill(fill->entities.front()->role()) ? - layerm->region()->config().solid_infill_extruder : - layerm->region()->config().infill_extruder, - 1)]); + volume(idx_layer, + is_solid_infill(fill->entities.front()->role()) ? + layerm->region()->config().solid_infill_extruder : + layerm->region()->config().infill_extruder, + 1)); } } } @@ -4629,36 +4640,37 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c if (support_layer) { for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities) _3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy, - *vols[ctxt.volume_idx( - (extrusion_entity->role() == erSupportMaterial) ? - support_layer->object()->config().support_material_extruder : - support_layer->object()->config().support_material_interface_extruder, - 2)]); + volume(idx_layer, + (extrusion_entity->role() == erSupportMaterial) ? + support_layer->object()->config().support_material_extruder : + support_layer->object()->config().support_material_interface_extruder, + 2)); } } } - for (size_t i = 0; i < vols.size(); ++i) { - GLVolume &vol = *vols[i]; - if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { - // Store the vertex arrays and restart their containers, - vols[i] = new_volume(vol.color); - GLVolume &vol_new = *vols[i]; - // Assign the large pre-allocated buffers to the new GLVolume. - vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); - // Copy the content back to the old GLVolume. - vol.indexed_vertex_array = vol_new.indexed_vertex_array; - // Finalize a bounding box of the old GLVolume. - vol.bounding_box = vol.indexed_vertex_array.bounding_box(); - // Clear the buffers, but keep them pre-allocated. - vol_new.indexed_vertex_array.clear(); - // Just make sure that clear did not clear the reserved memory. - vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); - } - } + // Ensure that no volume grows over the limits. If the volume is too large, allocate a new one. + for (size_t i = 0; i < vols.size(); ++i) { + GLVolume &vol = *vols[i]; + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { + // Store the vertex arrays and restart their containers, + vols[i] = new_volume(vol.color); + GLVolume &vol_new = *vols[i]; + // Assign the large pre-allocated buffers to the new GLVolume. + vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); + // Copy the content back to the old GLVolume. + vol.indexed_vertex_array = vol_new.indexed_vertex_array; + // Finalize a bounding box of the old GLVolume. + vol.bounding_box = vol.indexed_vertex_array.bounding_box(); + // Clear the buffers, but keep them pre-allocated. + vol_new.indexed_vertex_array.clear(); + // Just make sure that clear did not clear the reserved memory. + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); + } + } } for (GLVolume *vol : vols) { - vol->bounding_box = vol->indexed_vertex_array.bounding_box(); - vol->indexed_vertex_array.shrink_to_fit(); + vol->bounding_box = vol->indexed_vertex_array.bounding_box(); + vol->indexed_vertex_array.shrink_to_fit(); } }); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 95fd123eb..d1141b550 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -198,7 +198,6 @@ class GLCanvas3D static const float THICKNESS_BAR_WIDTH; static const float THICKNESS_RESET_BUTTON_HEIGHT; - bool m_use_legacy_opengl; bool m_enabled; Shader m_shader; unsigned int m_z_texture_id; @@ -251,7 +250,6 @@ class GLCanvas3D void select_object(const Model &model, int object_id); bool is_allowed() const; - void set_use_legacy_opengl(bool use_legacy_opengl); bool is_enabled() const; void set_enabled(bool enabled); @@ -493,7 +491,7 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas; } const wxGLCanvas* get_wxglcanvas() const { return m_canvas; } - bool init(bool useVBOs, bool use_legacy_opengl); + bool init(bool useVBOs); void post_event(wxEvent &&event); void set_as_dirty(); diff --git a/src/slic3r/GUI/GLCanvas3DManager.cpp b/src/slic3r/GUI/GLCanvas3DManager.cpp index 4f64b4e87..a1430ef22 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.cpp +++ b/src/slic3r/GUI/GLCanvas3DManager.cpp @@ -192,7 +192,6 @@ GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info; GLCanvas3DManager::GLCanvas3DManager() : m_context(nullptr) , m_gl_initialized(false) - , m_use_legacy_opengl(false) , m_use_VBOs(false) { } @@ -268,8 +267,7 @@ void GLCanvas3DManager::init_gl() { glewInit(); const AppConfig* config = GUI::get_app_config(); - m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1"); - m_use_VBOs = !m_use_legacy_opengl && s_gl_info.is_version_greater_or_equal_to(2, 0); + m_use_VBOs = s_gl_info.is_version_greater_or_equal_to(2, 0); m_gl_initialized = true; if (GLEW_EXT_texture_compression_s3tc) s_compressed_textures_supported = true; @@ -325,16 +323,14 @@ bool GLCanvas3DManager::init(GLCanvas3D& canvas) if (!m_gl_initialized) init_gl(); - return canvas.init(m_use_VBOs, m_use_legacy_opengl); + return canvas.init(m_use_VBOs); } void GLCanvas3DManager::detect_multisample(int* attribList) { int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER; const AppConfig* app_config = GUI::get_app_config(); - bool enable_multisample = app_config != nullptr - && app_config->get("use_legacy_opengl") != "1" - && wxVersion >= 30003; + bool enable_multisample = wxVersion >= 30003; s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? MS_Enabled : MS_Disabled; // Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows diff --git a/src/slic3r/GUI/GLCanvas3DManager.hpp b/src/slic3r/GUI/GLCanvas3DManager.hpp index 26c2558d0..7a600dcbd 100644 --- a/src/slic3r/GUI/GLCanvas3DManager.hpp +++ b/src/slic3r/GUI/GLCanvas3DManager.hpp @@ -75,7 +75,6 @@ private: wxGLContext* m_context; static GLInfo s_gl_info; bool m_gl_initialized; - bool m_use_legacy_opengl; bool m_use_VBOs; static EMultisampleState s_multisample; static bool s_compressed_textures_supported; diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 327cb1fde..684563bff 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -68,7 +68,8 @@ namespace GUI { if (!is_dragging()) return; - float zoom = (float)canvas.get_camera().get_zoom(); + const Camera& camera = canvas.get_camera(); + float zoom = (float)camera.get_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; Size cnv_size = canvas.get_canvas_size(); @@ -96,6 +97,11 @@ namespace GUI { glsafe(::glPushMatrix()); glsafe(::glLoadIdentity()); + // ensure that the rectangle is renderered inside the frustrum + glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5))); + // ensure that the overlay fits the frustrum near z plane + double gui_scale = camera.get_gui_scale(); + glsafe(::glScaled(gui_scale, gui_scale, 1.0)); glsafe(::glPushAttrib(GL_ENABLE_BIT)); glsafe(::glLineStipple(4, 0xAAAA)); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index f25fe3e1d..03f9cd45f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -469,7 +469,7 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) if (!m_config || selection.empty()) return; - const int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); + const int extruder = /*selection.size() > 1 ? 0 : */atoi(selection.c_str()); m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); // update scene diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 2f5e10962..671c49eef 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -313,6 +313,12 @@ Preview::~Preview() } } +void Preview::set_as_dirty() +{ + if (m_canvas != nullptr) + m_canvas->set_as_dirty(); +} + void Preview::set_number_extruders(unsigned int number_extruders) { if (m_number_extruders != number_extruders) diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 93038b0e5..993e260e4 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -110,6 +110,8 @@ public: wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } GLCanvas3D* get_canvas3d() { return m_canvas; } + void set_as_dirty(); + void set_number_extruders(unsigned int number_extruders); void set_canvas_as_dirty(); void set_enabled(bool enabled); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 966e55ba6..290be33a3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1273,6 +1273,7 @@ struct Plater::priv Preview *preview; BackgroundSlicingProcess background_process; + bool suppressed_backround_processing_update { false }; // A class to handle UI jobs like arranging and optimizing rotation. // These are not instant jobs, the user has to be informed about their @@ -1707,7 +1708,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) panels.push_back(preview); this->background_process_timer.SetOwner(this->q, 0); - this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->update_restart_background_process(false, false); }); + this->q->Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) + { + if (!this->suppressed_backround_processing_update) + this->update_restart_background_process(false, false); + }); update(); @@ -1787,7 +1792,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) set_current_panel(view3D); // updates camera type from .ini file - camera.set_type(get_config("camera_type")); + camera.set_type(get_config("use_perspective_camera")); } void Plater::priv::update(bool force_full_scene_refresh) @@ -1855,10 +1860,8 @@ void Plater::priv::update_ui_from_settings() // $self->{buttons_sizer}->Layout; // } -#if ENABLE_RETINA_GL view3D->get_canvas3d()->update_ui_from_settings(); preview->get_canvas3d()->update_ui_from_settings(); -#endif } ProgressStatusBar* Plater::priv::statusbar() @@ -4196,9 +4199,25 @@ void Plater::changed_objects(const std::vector& object_idxs) this->p->schedule_background_process(); } -void Plater::schedule_background_process() +void Plater::schedule_background_process(bool schedule/* = true*/) { - this->p->schedule_background_process(); + if (schedule) + this->p->schedule_background_process(); + + this->p->suppressed_backround_processing_update = false; +} + +bool Plater::is_background_process_running() const +{ + return this->p->background_process_timer.IsRunning(); +} + +void Plater::suppress_background_process(const bool stop_background_process) +{ + if (stop_background_process) + this->p->background_process_timer.Stop(); + + this->p->suppressed_backround_processing_update = true; } void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); } @@ -4274,4 +4293,15 @@ bool Plater::can_copy_to_clipboard() const return true; } +SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() : + m_was_running(wxGetApp().plater()->is_background_process_running()) +{ + wxGetApp().plater()->suppress_background_process(m_was_running); +} + +SuppressBackgroundProcessingUpdate::~SuppressBackgroundProcessingUpdate() +{ + wxGetApp().plater()->schedule_background_process(m_was_running); +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 46fe38ef6..c3f8927c0 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -177,7 +177,9 @@ public: void reslice_SLA_supports(const ModelObject &object); void changed_object(int obj_idx); void changed_objects(const std::vector& object_idxs); - void schedule_background_process(); + void schedule_background_process(bool schedule = true); + bool is_background_process_running() const; + void suppress_background_process(const bool stop_background_process) ; void fix_through_netfabb(const int obj_idx, const int vol_idx = -1); void send_gcode(); @@ -221,8 +223,18 @@ public: private: struct priv; std::unique_ptr p; + + friend class SuppressBackgroundProcessingUpdate; }; +class SuppressBackgroundProcessingUpdate +{ +public: + SuppressBackgroundProcessingUpdate(); + ~SuppressBackgroundProcessingUpdate(); +private: + bool m_was_running; +}; }} diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 52cbdbb1d..827d1f4e0 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -28,7 +28,7 @@ void PreferencesDialog::build() m_icon_size_sizer->ShowItems(boost::any_cast(value)); this->layout(); } - }; + }; // TODO // $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( @@ -97,16 +97,6 @@ void PreferencesDialog::build() option = Option (def,"show_incompatible_presets"); m_optgroup->append_single_option_line(option); - // TODO: remove? - def.label = L("Use legacy OpenGL 1.1 rendering"); - def.type = coBool; - def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, " - "you may try to check this checkbox. This will disable the layer height " - "editing and anti aliasing, so it is likely better to upgrade your graphics driver."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_legacy_opengl") == "1" }); - option = Option (def,"use_legacy_opengl"); - m_optgroup->append_single_option_line(option); - #if __APPLE__ def.label = L("Use Retina resolution for the 3D scene"); def.type = coBool; @@ -117,6 +107,13 @@ void PreferencesDialog::build() m_optgroup->append_single_option_line(option); #endif + def.label = L("Use perspective camera"); + def.type = coBool; + def.tooltip = L("If enabled, use perspective camera. If not enabled, use orthographic camera."); + def.set_default_value(new ConfigOptionBool{ app_config->get("use_perspective_camera") == "1" }); + option = Option(def, "use_perspective_camera"); + m_optgroup->append_single_option_line(option); + def.label = L("Use custom size for toolbar icons"); def.type = coBool; def.tooltip = L("If enabled, you can change size of toolbar icons manually."); @@ -143,8 +140,7 @@ void PreferencesDialog::build() void PreferencesDialog::accept() { - if (m_values.find("no_defaults") != m_values.end() || - m_values.find("use_legacy_opengl") != m_values.end()) { + if (m_values.find("no_defaults") != m_values.end()) { warning_catcher(this, wxString::Format(_(L("You need to restart %s to make the changes effective.")), SLIC3R_APP_NAME)); } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ef3878b5a..85ff345ba 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2314,6 +2314,40 @@ void TabPrinter::build_unregular_pages() auto optgroup = page->new_optgroup(_(L("Size"))); optgroup->append_single_option_line("nozzle_diameter", extruder_idx); + + optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value) + { + if (m_extruders_count > 1 && opt_key.find_first_of("nozzle_diameter") != std::string::npos) + { + SuppressBackgroundProcessingUpdate sbpu; + const double new_nd = boost::any_cast(value); + std::vector nozzle_diameters = static_cast(m_config->option("nozzle_diameter"))->values; + + // if value was changed + if (fabs(nozzle_diameters[extruder_idx == 0 ? 1 : 0] - new_nd) > EPSILON) + { + const wxString msg_text = _(L("Do you want to change the diameter for all extruders?")); + auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Nozzle diameter")), wxICON_WARNING | wxYES_NO); + + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_YES) { + for (size_t i = 0; i < nozzle_diameters.size(); i++) { + if (i==extruder_idx) + continue; + nozzle_diameters[i] = new_nd; + } + } + else + nozzle_diameters[extruder_idx] = nozzle_diameters[extruder_idx == 0 ? 1 : 0]; + + new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + load_config(new_conf); + } + } + + update_dirty(); + update(); + }; optgroup = page->new_optgroup(_(L("Layer height limits"))); optgroup->append_single_option_line("min_layer_height", extruder_idx);