diff --git a/CMakeLists.txt b/CMakeLists.txt index fd8a27e86..c4e599f36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,11 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STRE endif () endif() +if (APPLE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=partial-availability -Werror=unguarded-availability -Werror=unguarded-availability-new") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=partial-availability -Werror=unguarded-availability -Werror=unguarded-availability-new") +endif () + # Where all the bundled libraries reside? set(LIBDIR ${CMAKE_CURRENT_SOURCE_DIR}/src) set(LIBDIR_BIN ${CMAKE_CURRENT_BINARY_DIR}/src) diff --git a/resources/models/sl1_bed.stl b/resources/models/sl1_bed.stl index 28601b288..b2cadde4b 100644 Binary files a/resources/models/sl1_bed.stl and b/resources/models/sl1_bed.stl differ diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 1faf542dd..514498ee8 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -71,6 +71,11 @@ if(TBB_FOUND) target_compile_definitions(libnest2d INTERFACE -DTBB_USE_CAPTURED_EXCEPTION=0) target_link_libraries(libnest2d INTERFACE tbb) + # The following breaks compilation on Visual Studio in Debug mode. + #find_package(Threads REQUIRED) + #target_link_libraries(libnest2d INTERFACE ${TBB_LIBRARIES} ${CMAKE_DL_LIBS} + # Threads::Threads + # ) else() find_package(OpenMP QUIET) @@ -88,7 +93,7 @@ endif() add_subdirectory(${SRC_DIR}/libnest2d/backends/${LIBNEST2D_GEOMETRIES}) add_subdirectory(${SRC_DIR}/libnest2d/optimizers/${LIBNEST2D_OPTIMIZER}) -#target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) +target_sources(libnest2d INTERFACE ${LIBNEST2D_SRCFILES}) target_include_directories(libnest2d INTERFACE ${SRC_DIR}) if(NOT LIBNEST2D_HEADER_ONLY) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt index aa53f957e..995afcc76 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt @@ -62,9 +62,9 @@ if(NOT Boost_INCLUDE_DIRS_FOUND) endif() target_include_directories(ClipperBackend INTERFACE ${Boost_INCLUDE_DIRS} ) -#target_sources(ClipperBackend INTERFACE -# ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp -# ${SRC_DIR}/libnest2d/utils/boost_alg.hpp ) +target_sources(ClipperBackend INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp + ${SRC_DIR}/libnest2d/utils/boost_alg.hpp ) target_compile_definitions(ClipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index c05d08d0d..9f881e7e0 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -113,6 +113,7 @@ template<> struct CountourType { template<> struct ShapeTag { using Type = PolygonTag; }; template<> struct ShapeTag { using Type = PathTag; }; +template<> struct ShapeTag { using Type = PointTag; }; template<> struct ShapeTag> { using Type = MultiPolygonTag; diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 828044afe..917f5280d 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -69,12 +69,14 @@ struct PointPair { RawPoint p2; }; +struct PointTag {}; struct PolygonTag {}; struct PathTag {}; struct MultiPolygonTag {}; struct BoxTag {}; struct CircleTag {}; +/// Meta-functions to derive the tags template struct ShapeTag { using Type = typename Shape::Tag; }; template using Tag = typename ShapeTag>::Type; @@ -131,7 +133,7 @@ public: _Circle(const RawPoint& center, double r): center_(center), radius_(r) {} inline const RawPoint& center() const BP2D_NOEXCEPT { return center_; } - inline const void center(const RawPoint& c) { center_ = c; } + inline void center(const RawPoint& c) { center_ = c; } inline double radius() const BP2D_NOEXCEPT { return radius_; } inline void radius(double r) { radius_ = r; } @@ -518,21 +520,19 @@ inline bool intersects(const RawShape& /*sh*/, const RawShape& /*sh*/) return false; } -template -inline bool isInside(const TPoint& /*point*/, - const RawShape& /*shape*/) -{ - static_assert(always_false::value, - "shapelike::isInside(point, shape) unimplemented!"); +template +inline bool isInside(const TGuest&, const THost&, + const PointTag&, const PolygonTag&) { + static_assert(always_false::value, + "shapelike::isInside(point, path) unimplemented!"); return false; } -template -inline bool isInside(const RawShape& /*shape*/, - const RawShape& /*shape*/) -{ - static_assert(always_false::value, - "shapelike::isInside(shape, shape) unimplemented!"); +template +inline bool isInside(const TGuest&, const THost&, + const PolygonTag&, const PolygonTag&) { + static_assert(always_false::value, + "shapelike::isInside(shape, shape) unimplemented!"); return false; } @@ -651,7 +651,7 @@ template inline bool isConvex(const RawPath& sh, const PathTag&) template inline typename TContour::iterator -begin(RawShape& sh, const PolygonTag& t) +begin(RawShape& sh, const PolygonTag&) { return begin(contour(sh), PathTag()); } @@ -818,16 +818,16 @@ inline auto convexHull(const RawShape& sh) return convexHull(sh, Tag()); } -template -inline bool isInside(const TPoint& point, - const _Circle>& circ) +template +inline bool isInside(const TP& point, const TC& circ, + const PointTag&, const CircleTag&) { return pointlike::distance(point, circ.center()) < circ.radius(); } -template -inline bool isInside(const TPoint& point, - const _Box>& box) +template +inline bool isInside(const TP& point, const TB& box, + const PointTag&, const BoxTag&) { auto px = getX(point); auto py = getY(point); @@ -839,27 +839,27 @@ inline bool isInside(const TPoint& point, return px > minx && px < maxx && py > miny && py < maxy; } -template -inline bool isInside(const RawShape& sh, - const _Circle>& circ) +template +inline bool isInside(const RawShape& sh, const TC& circ, + const PolygonTag&, const CircleTag&) { - return std::all_of(cbegin(sh), cend(sh), - [&circ](const TPoint& p){ - return isInside(p, circ); + return std::all_of(cbegin(sh), cend(sh), [&circ](const TPoint& p) + { + return isInside(p, circ, PointTag(), CircleTag()); }); } -template -inline bool isInside(const _Box>& box, - const _Circle>& circ) +template +inline bool isInside(const TB& box, const TC& circ, + const BoxTag&, const CircleTag&) { - return isInside(box.minCorner(), circ) && - isInside(box.maxCorner(), circ); + return isInside(box.minCorner(), circ, BoxTag(), CircleTag()) && + isInside(box.maxCorner(), circ, BoxTag(), CircleTag()); } -template -inline bool isInside(const _Box>& ibb, - const _Box>& box) +template +inline bool isInside(const TBGuest& ibb, const TBHost& box, + const BoxTag&, const BoxTag&) { auto iminX = getX(ibb.minCorner()); auto imaxX = getX(ibb.maxCorner()); @@ -874,6 +874,18 @@ inline bool isInside(const _Box>& ibb, return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; } +template +inline bool isInside(const RawShape& poly, const TB& box, + const PolygonTag&, const BoxTag&) +{ + return isInside(boundingBox(poly), box, BoxTag(), BoxTag()); +} + +template +inline bool isInside(const TGuest& guest, const THost& host) { + return isInside(guest, host, Tag(), Tag()); +} + template // Potential O(1) implementation may exist inline TPoint& vertex(RawShape& sh, unsigned long idx, const PolygonTag&) diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index b57b8dc53..cb0580ef4 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -251,6 +251,460 @@ inline NfpResult nfpConvexOnly(const RawShape& sh, return {rsh, top_nfp}; } +template +NfpResult nfpSimpleSimple(const RawShape& cstationary, + const RawShape& cother) +{ + + // Algorithms are from the original algorithm proposed in paper: + // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 1: Obtaining the minkowski sum + // ///////////////////////////////////////////////////////////////////////// + + // I guess this is not a full minkowski sum of the two input polygons by + // definition. This yields a subset that is compatible with the next 2 + // algorithms. + + using Result = NfpResult; + using Vertex = TPoint; + using Coord = TCoord; + using Edge = _Segment; + namespace sl = shapelike; + using std::signbit; + using std::sort; + using std::vector; + using std::ref; + using std::reference_wrapper; + + // TODO The original algorithms expects the stationary polygon in + // counter clockwise and the orbiter in clockwise order. + // So for preventing any further complication, I will make the input + // the way it should be, than make my way around the orientations. + + // Reverse the stationary contour to counter clockwise + auto stcont = sl::contour(cstationary); + { + std::reverse(sl::begin(stcont), sl::end(stcont)); + stcont.pop_back(); + auto it = std::min_element(sl::begin(stcont), sl::end(stcont), + [](const Vertex& v1, const Vertex& v2) { + return getY(v1) < getY(v2); + }); + std::rotate(sl::begin(stcont), it, sl::end(stcont)); + sl::addVertex(stcont, sl::front(stcont)); + } + RawShape stationary; + sl::contour(stationary) = stcont; + + // Reverse the orbiter contour to counter clockwise + auto orbcont = sl::contour(cother); + { + std::reverse(orbcont.begin(), orbcont.end()); + + // Step 1: Make the orbiter reverse oriented + + orbcont.pop_back(); + auto it = std::min_element(orbcont.begin(), orbcont.end(), + [](const Vertex& v1, const Vertex& v2) { + return getY(v1) < getY(v2); + }); + + std::rotate(orbcont.begin(), it, orbcont.end()); + orbcont.emplace_back(orbcont.front()); + + for(auto &v : orbcont) v = -v; + + } + + // Copy the orbiter (contour only), we will have to work on it + RawShape orbiter; + sl::contour(orbiter) = orbcont; + + // An edge with additional data for marking it + struct MarkedEdge { + Edge e; Radians turn_angle = 0; bool is_turning_point = false; + MarkedEdge() = default; + MarkedEdge(const Edge& ed, Radians ta, bool tp): + e(ed), turn_angle(ta), is_turning_point(tp) {} + + // debug + std::string label; + }; + + // Container for marked edges + using EdgeList = vector; + + EdgeList A, B; + + // This is how an edge list is created from the polygons + auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) { + auto& poly = sl::contour(ppoly); + + L.reserve(sl::contourVertexCount(poly)); + + if(dir > 0) { + auto it = poly.begin(); + auto nextit = std::next(it); + + double turn_angle = 0; + bool is_turn_point = false; + + while(nextit != poly.end()) { + L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); + it++; nextit++; + } + } else { + auto it = sl::rbegin(poly); + auto nextit = std::next(it); + + double turn_angle = 0; + bool is_turn_point = false; + + while(nextit != sl::rend(poly)) { + L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point); + it++; nextit++; + } + } + + auto getTurnAngle = [](const Edge& e1, const Edge& e2) { + auto phi = e1.angleToXaxis(); + auto phi_prev = e2.angleToXaxis(); + auto turn_angle = phi-phi_prev; + if(turn_angle > Pi) turn_angle -= TwoPi; + if(turn_angle < -Pi) turn_angle += TwoPi; + return turn_angle; + }; + + auto eit = L.begin(); + auto enext = std::next(eit); + + eit->turn_angle = getTurnAngle(L.front().e, L.back().e); + + while(enext != L.end()) { + enext->turn_angle = getTurnAngle( enext->e, eit->e); + eit->is_turning_point = + signbit(enext->turn_angle) != signbit(eit->turn_angle); + ++eit; ++enext; + } + + L.back().is_turning_point = signbit(L.back().turn_angle) != + signbit(L.front().turn_angle); + + }; + + // Step 2: Fill the edgelists + fillEdgeList(A, stationary, 1); + fillEdgeList(B, orbiter, 1); + + int i = 1; + for(MarkedEdge& me : A) { + std::cout << "a" << i << ":\n\t" + << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" + << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" + << "Turning point: " << (me.is_turning_point ? "yes" : "no") + << std::endl; + + me.label = "a"; me.label += std::to_string(i); + i++; + } + + i = 1; + for(MarkedEdge& me : B) { + std::cout << "b" << i << ":\n\t" + << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t" + << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t" + << "Turning point: " << (me.is_turning_point ? "yes" : "no") + << std::endl; + me.label = "b"; me.label += std::to_string(i); + i++; + } + + // A reference to a marked edge that also knows its container + struct MarkedEdgeRef { + reference_wrapper eref; + reference_wrapper> container; + Coord dir = 1; // Direction modifier + + inline Radians angleX() const { return eref.get().e.angleToXaxis(); } + inline const Edge& edge() const { return eref.get().e; } + inline Edge& edge() { return eref.get().e; } + inline bool isTurningPoint() const { + return eref.get().is_turning_point; + } + inline bool isFrom(const vector& cont ) { + return &(container.get()) == &cont; + } + inline bool eq(const MarkedEdgeRef& mr) { + return &(eref.get()) == &(mr.eref.get()); + } + + MarkedEdgeRef(reference_wrapper er, + reference_wrapper> ec): + eref(er), container(ec), dir(1) {} + + MarkedEdgeRef(reference_wrapper er, + reference_wrapper> ec, + Coord d): + eref(er), container(ec), dir(d) {} + }; + + using EdgeRefList = vector; + + // Comparing two marked edges + auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) { + return e1.angleX() < e2.angleX(); + }; + + EdgeRefList Aref, Bref; // We create containers for the references + Aref.reserve(A.size()); Bref.reserve(B.size()); + + // Fill reference container for the stationary polygon + std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) { + Aref.emplace_back( ref(me), ref(Aref) ); + }); + + // Fill reference container for the orbiting polygon + std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) { + Bref.emplace_back( ref(me), ref(Bref) ); + }); + + auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure + (const EdgeRefList& Q, const EdgeRefList& R, bool positive) + { + + // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)" + // Sort the containers of edge references and merge them. + // Q could be sorted only once and be reused here but we would still + // need to merge it with sorted(R). + + EdgeRefList merged; + EdgeRefList S, seq; + merged.reserve(Q.size() + R.size()); + + merged.insert(merged.end(), R.begin(), R.end()); + std::stable_sort(merged.begin(), merged.end(), sortfn); + merged.insert(merged.end(), Q.begin(), Q.end()); + std::stable_sort(merged.begin(), merged.end(), sortfn); + + // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1" + // we don't use i, instead, q is an iterator into Q. k would be an index + // into the merged sequence but we use "it" as an iterator for that + + // here we obtain references for the containers for later comparisons + const auto& Rcont = R.begin()->container.get(); + const auto& Qcont = Q.begin()->container.get(); + + // Set the initial direction + Coord dir = 1; + + // roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q; + if(positive) { + auto q = Q.begin(); + S.emplace_back(*q); + + // Roughly step 3 + + std::cout << "merged size: " << merged.size() << std::endl; + auto mit = merged.begin(); + for(bool finish = false; !finish && q != Q.end();) { + ++q; // "Set i = i + 1" + + while(!finish && mit != merged.end()) { + if(mit->isFrom(Rcont)) { + auto s = *mit; + s.dir = dir; + S.emplace_back(s); + } + + if(mit->eq(*q)) { + S.emplace_back(*q); + if(mit->isTurningPoint()) dir = -dir; + if(q == Q.begin()) finish = true; + break; + } + + mit += dir; + // __nfp::advance(mit, merged, dir > 0); + } + } + } else { + auto q = Q.rbegin(); + S.emplace_back(*q); + + // Roughly step 3 + + std::cout << "merged size: " << merged.size() << std::endl; + auto mit = merged.begin(); + for(bool finish = false; !finish && q != Q.rend();) { + ++q; // "Set i = i + 1" + + while(!finish && mit != merged.end()) { + if(mit->isFrom(Rcont)) { + auto s = *mit; + s.dir = dir; + S.emplace_back(s); + } + + if(mit->eq(*q)) { + S.emplace_back(*q); + S.back().dir = -1; + if(mit->isTurningPoint()) dir = -dir; + if(q == Q.rbegin()) finish = true; + break; + } + + mit += dir; + // __nfp::advance(mit, merged, dir > 0); + } + } + } + + + // Step 4: + + // "Let starting edge r1 be in position si in sequence" + // whaaat? I guess this means the following: + auto it = S.begin(); + while(!it->eq(*R.begin())) ++it; + + // "Set j = 1, next = 2, direction = 1, seq1 = si" + // we don't use j, seq is expanded dynamically. + dir = 1; + auto next = std::next(R.begin()); seq.emplace_back(*it); + + // Step 5: + // "If all si edges have been allocated to seqj" should mean that + // we loop until seq has equal size with S + auto send = it; //it == S.begin() ? it : std::prev(it); + while(it != S.end()) { + ++it; if(it == S.end()) it = S.begin(); + if(it == send) break; + + if(it->isFrom(Qcont)) { + seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si" + + // "If si is a turning point in Q, + // direction = - direction, next = next + direction" + if(it->isTurningPoint()) { + dir = -dir; + next += dir; +// __nfp::advance(next, R, dir > 0); + } + } + + if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext" + // "j = j + 1, seqj = si, next = next + direction" + seq.emplace_back(*it); + next += dir; +// __nfp::advance(next, R, dir > 0); + } + } + + return seq; + }; + + std::vector seqlist; + seqlist.reserve(Bref.size()); + + EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram + + // make the slope diagram of B + std::sort(Bslope.begin(), Bslope.end(), sortfn); + + auto slopeit = Bslope.begin(); // search for the first turning point + while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++; + + if(slopeit == Bslope.end()) { + // no turning point means convex polygon. + seqlist.emplace_back(mink(Aref, Bref, true)); + } else { + int dir = 1; + + auto firstturn = Bref.begin(); + while(!firstturn->eq(*slopeit)) ++firstturn; + + assert(firstturn != Bref.end()); + + EdgeRefList bgroup; bgroup.reserve(Bref.size()); + bgroup.emplace_back(*slopeit); + + auto b_it = std::next(firstturn); + while(b_it != firstturn) { + if(b_it == Bref.end()) b_it = Bref.begin(); + + while(!slopeit->eq(*b_it)) { + __nfp::advance(slopeit, Bslope, dir > 0); + } + + if(!slopeit->isTurningPoint()) { + bgroup.emplace_back(*slopeit); + } else { + if(!bgroup.empty()) { + if(dir > 0) bgroup.emplace_back(*slopeit); + for(auto& me : bgroup) { + std::cout << me.eref.get().label << ", "; + } + std::cout << std::endl; + seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false)); + bgroup.clear(); + if(dir < 0) bgroup.emplace_back(*slopeit); + } else { + bgroup.emplace_back(*slopeit); + } + + dir *= -1; + } + ++b_it; + } + } + +// while(it != Bref.end()) // This is step 3 and step 4 in one loop +// if(it->isTurningPoint()) { +// R = {R.last, it++}; +// auto seq = mink(Q, R, orientation); + +// // TODO step 6 (should be 5 shouldn't it?): linking edges from A +// // I don't get this step + +// seqlist.insert(seqlist.end(), seq.begin(), seq.end()); +// orientation = !orientation; +// } else ++it; + +// if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true); + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 2: breaking Minkowski sums into track line trips + // ///////////////////////////////////////////////////////////////////////// + + + // ///////////////////////////////////////////////////////////////////////// + // Algorithm 3: finding the boundary of the NFP from track line trips + // ///////////////////////////////////////////////////////////////////////// + + + for(auto& seq : seqlist) { + std::cout << "seqlist size: " << seq.size() << std::endl; + for(auto& s : seq) { + std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", "; + } + std::cout << std::endl; + } + + auto& seq = seqlist.front(); + RawShape rsh; + Vertex top_nfp; + std::vector edgelist; edgelist.reserve(seq.size()); + for(auto& s : seq) { + edgelist.emplace_back(s.eref.get().e); + } + + __nfp::buildPolygon(edgelist, rsh, top_nfp); + + return Result(rsh, top_nfp); +} + // Specializable NFP implementation class. Specialize it if you have a faster // or better NFP implementation template diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index aac62e094..49baa65f2 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -482,17 +482,40 @@ public: template inline bool _Item::isInside(const _Box>& box) const { - return sl::isInside(boundingBox(), box); + return sl::isInside(boundingBox(), box); } template inline bool _Item::isInside(const _Circle>& circ) const { - return sl::isInside(transformedShape(), circ); + return sl::isInside(transformedShape(), circ); } +template using _ItemRef = std::reference_wrapper<_Item>; +template using _ItemGroup = std::vector<_ItemRef>; -template using _ItemRef = std::reference_wrapper; -template using _ItemGroup = std::vector<_ItemRef>; +/** + * \brief A list of packed item vectors. Each vector represents a bin. + */ +template +using _PackGroup = std::vector>>; + +/** + * \brief A list of packed (index, item) pair vectors. Each vector represents a + * bin. + * + * The index is points to the position of the item in the original input + * sequence. This way the caller can use the items as a transformation data + * carrier and transform the original objects manually. + */ +template +using _IndexedPackGroup = std::vector< + std::vector< + std::pair< + unsigned, + _ItemRef + > + > + >; template struct ConstItemRange { @@ -524,8 +547,10 @@ class PlacementStrategyLike { PlacementStrategy impl_; public: + using RawShape = typename PlacementStrategy::ShapeType; + /// The item type that the placer works with. - using Item = typename PlacementStrategy::Item; + using Item = _Item; /// The placer's config type. Should be a simple struct but can be anything. using Config = typename PlacementStrategy::Config; @@ -544,8 +569,7 @@ public: */ using PackResult = typename PlacementStrategy::PackResult; - using ItemRef = _ItemRef; - using ItemGroup = _ItemGroup; + using ItemGroup = _ItemGroup; using DefaultIterator = typename ItemGroup::const_iterator; /** @@ -619,6 +643,15 @@ public: return impl_.pack(item, remaining); } + /** + * This method makes possible to "preload" some items into the placer. It + * will not move these items but will consider them as already packed. + */ + inline void preload(const ItemGroup& packeditems) + { + impl_.preload(packeditems); + } + /// Unpack the last element (remove it from the list of packed items). inline void unpackLast() { impl_.unpackLast(); } @@ -649,11 +682,11 @@ template class SelectionStrategyLike { SelectionStrategy impl_; public: - using Item = typename SelectionStrategy::Item; + using RawShape = typename SelectionStrategy::ShapeType; + using Item = _Item; + using PackGroup = _PackGroup; using Config = typename SelectionStrategy::Config; - using ItemRef = std::reference_wrapper; - using ItemGroup = std::vector; /** * @brief Provide a different configuration for the selection strategy. @@ -703,60 +736,29 @@ public: std::forward(config)); } - /** - * \brief Get the number of bins opened by the selection algorithm. - * - * Initially it is zero and after the call to packItems it will return - * the number of bins opened by the packing procedure. - * - * \return The number of bins opened. - */ - inline size_t binCount() const { return impl_.binCount(); } - /** * @brief Get the items for a particular bin. * @param binIndex The index of the requested bin. * @return Returns a list of all items packed into the requested bin. */ - inline ItemGroup itemsForBin(size_t binIndex) { - return impl_.itemsForBin(binIndex); + inline const PackGroup& getResult() const { + return impl_.getResult(); } - /// Same as itemsForBin but for a const context. - inline const ItemGroup itemsForBin(size_t binIndex) const { - return impl_.itemsForBin(binIndex); - } + /** + * @brief Loading a group of already packed bins. It is best to use a result + * from a previous packing. The algorithm will consider this input as if the + * objects are already packed and not move them. If any of these items are + * outside the bin, it is up to the placer algorithm what will happen. + * Packing additional items can fail for the bottom-left and nfp placers. + * @param pckgrp A packgroup which is a vector of item vectors. Each item + * vector corresponds to a packed bin. + */ + inline void preload(const PackGroup& pckgrp) { impl_.preload(pckgrp); } + + void clear() { impl_.clear(); } }; - -/** - * \brief A list of packed item vectors. Each vector represents a bin. - */ -template -using _PackGroup = std::vector< - std::vector< - std::reference_wrapper<_Item> - > - >; - -/** - * \brief A list of packed (index, item) pair vectors. Each vector represents a - * bin. - * - * The index is points to the position of the item in the original input - * sequence. This way the caller can use the items as a transformation data - * carrier and transform the original objects manually. - */ -template -using _IndexedPackGroup = std::vector< - std::vector< - std::pair< - unsigned, - std::reference_wrapper<_Item> - > - > - >; - /** * The Arranger is the front-end class for the libnest2d library. It takes the * input items and outputs the items with the proper transformations to be @@ -868,17 +870,29 @@ public: } /// Set a predicate to tell when to abort nesting. - inline Nester& stopCondition(StopCondition fn) { + inline Nester& stopCondition(StopCondition fn) + { selector_.stopCondition(fn); return *this; } - inline PackGroup lastResult() { - PackGroup ret; - for(size_t i = 0; i < selector_.binCount(); i++) { - auto items = selector_.itemsForBin(i); - ret.push_back(items); + inline const PackGroup& lastResult() const + { + return selector_.getResult(); + } + + inline void preload(const PackGroup& pgrp) + { + selector_.preload(pgrp); + } + + inline void preload(const IndexedPackGroup& ipgrp) + { + PackGroup pgrp; pgrp.reserve(ipgrp.size()); + for(auto& ig : ipgrp) { + pgrp.emplace_back(); pgrp.back().reserve(ig.size()); + for(auto& r : ig) pgrp.back().emplace_back(r.second); } - return ret; + preload(pgrp); } private: @@ -892,7 +906,7 @@ private: // have to exist for the lifetime of this call. class T = enable_if_t< std::is_convertible::value, IT> > - inline PackGroup _execute(TIterator from, TIterator to, bool = false) + inline const PackGroup& _execute(TIterator from, TIterator to, bool = false) { __execute(from, to); return lastResult(); @@ -902,7 +916,7 @@ private: class IT = remove_cvref_t, class T = enable_if_t::value, IT> > - inline PackGroup _execute(TIterator from, TIterator to, int = false) + inline const PackGroup& _execute(TIterator from, TIterator to, int = false) { item_cache_ = {from, to}; @@ -946,10 +960,12 @@ private: TSel& selector) { IndexedPackGroup pg; - pg.reserve(selector.binCount()); + pg.reserve(selector.getResult().size()); - for(size_t i = 0; i < selector.binCount(); i++) { - auto items = selector.itemsForBin(i); + const PackGroup& pckgrp = selector.getResult(); + + for(size_t i = 0; i < pckgrp.size(); i++) { + auto items = pckgrp[i]; pg.push_back({}); pg[i].reserve(items.size()); diff --git a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt index 2a32019f4..5559ad645 100644 --- a/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/optimizers/nlopt/CMakeLists.txt @@ -48,12 +48,12 @@ else() target_link_libraries(NloptOptimizer INTERFACE Nlopt::Nlopt) endif() -#target_sources( NloptOptimizer INTERFACE -#${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp -#${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp -#${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp -#${CMAKE_CURRENT_SOURCE_DIR}/nlopt_boilerplate.hpp -#) +target_sources( NloptOptimizer INTERFACE +${CMAKE_CURRENT_SOURCE_DIR}/simplex.hpp +${CMAKE_CURRENT_SOURCE_DIR}/subplex.hpp +${CMAKE_CURRENT_SOURCE_DIR}/genetic.hpp +${CMAKE_CURRENT_SOURCE_DIR}/nlopt_boilerplate.hpp +) target_compile_definitions(NloptOptimizer INTERFACE LIBNEST2D_OPTIMIZER_NLOPT) diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 28659c512..6fb717a7a 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -130,7 +130,7 @@ namespace placers { template struct NfpPConfig { - using ItemGroup = _ItemGroup<_Item>; + using ItemGroup = _ItemGroup; enum class Alignment { CENTER, @@ -138,6 +138,8 @@ struct NfpPConfig { BOTTOM_RIGHT, TOP_LEFT, TOP_RIGHT, + DONT_ALIGN //!> Warning: parts may end up outside the bin with the + //! default object function. }; /// Which angles to try out for better results. @@ -545,8 +547,8 @@ public: _NofitPolyPlacer& operator=(const _NofitPolyPlacer&) = default; #ifndef BP2D_COMPILER_MSVC12 // MSVC2013 does not support default move ctors - _NofitPolyPlacer(_NofitPolyPlacer&&) /*BP2D_NOEXCEPT*/ = default; - _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) /*BP2D_NOEXCEPT*/ = default; + _NofitPolyPlacer(_NofitPolyPlacer&&) = default; + _NofitPolyPlacer& operator=(_NofitPolyPlacer&&) = default; #endif static inline double overfit(const Box& bb, const RawShape& bin) { @@ -905,26 +907,44 @@ private: // This is the kernel part of the object function that is // customizable by the library client - auto _objfunc = config_.object_function? - config_.object_function : - [norm, bin, binbb, pbb](const Item& item) - { - auto ibb = item.boundingBox(); - auto fullbb = boundingBox(pbb, ibb); + std::function _objfunc; + if(config_.object_function) _objfunc = config_.object_function; + else { - double score = pl::distance(ibb.center(), binbb.center()); - score /= norm; + // Inside check has to be strict if no alignment was enabled + std::function ins_check; + if(config_.alignment == Config::Alignment::DONT_ALIGN) + ins_check = [&binbb, norm](const Box& fullbb) { + double ret = 0; + if(!sl::isInside(fullbb, binbb)) + ret += norm; + return ret; + }; + else + ins_check = [&bin](const Box& fullbb) { + double miss = overfit(fullbb, bin); + miss = miss > 0? miss : 0; + return std::pow(miss, 2); + }; - double miss = overfit(fullbb, bin); - miss = miss > 0? miss : 0; - score += std::pow(miss, 2); + _objfunc = [norm, binbb, pbb, ins_check](const Item& item) + { + auto ibb = item.boundingBox(); + auto fullbb = boundingBox(pbb, ibb); - return score; - }; + double score = pl::distance(ibb.center(), + binbb.center()); + score /= norm; + + score += ins_check(fullbb); + + return score; + }; + } // Our object function for placement - auto rawobjfunc = - [_objfunc, iv, startpos] (Vertex v, Item& itm) + auto rawobjfunc = [_objfunc, iv, startpos] + (Vertex v, Item& itm) { auto d = v - iv; d += startpos; @@ -938,9 +958,10 @@ private: ecache[opt.nfpidx].coords(opt.hidx, opt.relpos); }; - auto boundaryCheck = - [&merged_pile, &getNfpPoint, &item, &bin, &iv, &startpos] - (const Optimum& o) + auto alignment = config_.alignment; + + auto boundaryCheck = [alignment, &merged_pile, &getNfpPoint, + &item, &bin, &iv, &startpos] (const Optimum& o) { auto v = getNfpPoint(o); auto d = v - iv; @@ -951,7 +972,12 @@ private: auto chull = sl::convexHull(merged_pile); merged_pile.pop_back(); - return overfit(chull, bin); + double miss = 0; + if(alignment == Config::Alignment::DONT_ALIGN) + miss = sl::isInside(chull, bin) ? -1.0 : 1.0; + else miss = overfit(chull, bin); + + return miss; }; Optimum optimum(0, 0); @@ -1101,7 +1127,9 @@ private: } inline void finalAlign(_Circle> cbin) { - if(items_.empty()) return; + if(items_.empty() || + config_.alignment == Config::Alignment::DONT_ALIGN) return; + nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); @@ -1113,7 +1141,9 @@ private: } inline void finalAlign(Box bbin) { - if(items_.empty()) return; + if(items_.empty() || + config_.alignment == Config::Alignment::DONT_ALIGN) return; + nfp::Shapes m; m.reserve(items_.size()); for(Item& item : items_) m.emplace_back(item.transformedShape()); @@ -1147,6 +1177,7 @@ private: cb = bbin.maxCorner(); break; } + default: ; // DONT_ALIGN } auto d = cb - ci; @@ -1184,6 +1215,7 @@ private: cb = bbin.maxCorner(); break; } + default:; } auto d = cb - ci; diff --git a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp index 9f940af4d..309a5007d 100644 --- a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp @@ -12,6 +12,7 @@ class PlacerBoilerplate { mutable bool farea_valid_ = false; mutable double farea_ = 0.0; public: + using ShapeType = RawShape; using Item = _Item; using Vertex = TPoint; using Segment = _Segment; @@ -19,7 +20,7 @@ public: using Coord = TCoord; using Unit = Coord; using Config = Cfg; - using ItemGroup = _ItemGroup; + using ItemGroup = _ItemGroup; using DefaultIter = typename ItemGroup::const_iterator; class PackResult { @@ -59,8 +60,7 @@ public: } template> - bool pack(Item& item, - const Range& rem = Range()) { + bool pack(Item& item, const Range& rem = Range()) { auto&& r = static_cast(this)->trypack(item, rem); if(r) { items_.push_back(*(r.item_ptr_)); @@ -69,6 +69,11 @@ public: return r; } + void preload(const ItemGroup& packeditems) { + items_.insert(items_.end(), packeditems.begin(), packeditems.end()); + farea_valid_ = false; + } + void accept(PackResult& r) { if(r) { r.item_ptr_->translation(r.move_); @@ -117,6 +122,7 @@ using Base::bin_; \ using Base::items_; \ using Base::config_; \ public: \ +using typename Base::ShapeType; \ using typename Base::Item; \ using typename Base::ItemGroup; \ using typename Base::BinType; \ diff --git a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp index 39761f557..b03534dc4 100644 --- a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp +++ b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp @@ -33,7 +33,7 @@ class _DJDHeuristic: public SelectionBoilerplate { public: using typename Base::Item; - using typename Base::ItemRef; + using ItemRef = std::reference_wrapper; /** * @brief The Config for DJD heuristic. @@ -126,6 +126,8 @@ public: store_.clear(); store_.reserve(last-first); + + // TODO: support preloading packed_bins_.clear(); std::copy(first, last, std::back_inserter(store_)); diff --git a/src/libnest2d/include/libnest2d/selections/filler.hpp b/src/libnest2d/include/libnest2d/selections/filler.hpp index 5f95a6eff..19c44bfaa 100644 --- a/src/libnest2d/include/libnest2d/selections/filler.hpp +++ b/src/libnest2d/include/libnest2d/selections/filler.hpp @@ -34,6 +34,10 @@ public: store_.clear(); auto total = last-first; store_.reserve(total); + + // TODO: support preloading + packed_bins_.clear(); + packed_bins_.emplace_back(); auto makeProgress = [this, &total]( diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index d25487d6b..d521673b4 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -36,11 +36,19 @@ public: store_.clear(); store_.reserve(last-first); - packed_bins_.clear(); std::vector placers; placers.reserve(last-first); + // If the packed_items array is not empty we have to create as many + // placers as there are elements in packed bins and preload each item + // into the appropriate placer + for(ItemGroup& ig : packed_bins_) { + placers.emplace_back(bin); + placers.back().configure(pconfig); + placers.back().preload(ig); + } + std::copy(first, last, std::back_inserter(store_)); auto sortfunc = [](Item& i1, Item& i2) { diff --git a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp index 8351a99e7..fd6577d97 100644 --- a/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/selections/selection_boilerplate.hpp @@ -9,27 +9,23 @@ namespace libnest2d { namespace selections { template class SelectionBoilerplate { public: + using ShapeType = RawShape; using Item = _Item; - using ItemRef = std::reference_wrapper; - using ItemGroup = std::vector; - using PackGroup = std::vector; + using ItemGroup = _ItemGroup; + using PackGroup = _PackGroup; - size_t binCount() const { return packed_bins_.size(); } - - ItemGroup itemsForBin(size_t binIndex) { - assert(binIndex < packed_bins_.size()); - return packed_bins_[binIndex]; - } - - inline const ItemGroup itemsForBin(size_t binIndex) const { - assert(binIndex < packed_bins_.size()); - return packed_bins_[binIndex]; + inline const PackGroup& getResult() const { + return packed_bins_; } inline void progressIndicator(ProgressFunction fn) { progress_ = fn; } inline void stopCondition(StopCondition cond) { stopcond_ = cond; } + inline void preload(const PackGroup& pckgrp) { packed_bins_ = pckgrp; } + + inline void clear() { packed_bins_.clear(); } + protected: PackGroup packed_bins_; diff --git a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp index c573edb47..a6988ca00 100644 --- a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp +++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp @@ -356,13 +356,15 @@ inline double area(const PolygonImpl& shape, const PolygonTag&) #endif template<> -inline bool isInside(const PointImpl& point, const PolygonImpl& shape) +inline bool isInside(const PointImpl& point, const PolygonImpl& shape, + const PointTag&, const PolygonTag&) { return boost::geometry::within(point, shape); } template<> -inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2) +inline bool isInside(const PolygonImpl& sh1, const PolygonImpl& sh2, + const PolygonTag&, const PolygonTag&) { return boost::geometry::within(sh1, sh2); } diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index b3a1c2f5c..26302f702 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -32,6 +32,7 @@ public: } this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); } + void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); } void merge(const PointClass &point); void merge(const std::vector &points); void merge(const BoundingBoxBase &bb); diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index b84e2f13d..58324893d 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -207,8 +207,7 @@ static bool sort_pointfs(const Vec3d& a, const Vec3d& b) } // This implementation is based on Andrew's monotone chain 2D convex hull algorithm -Polygon -convex_hull(Points points) +Polygon convex_hull(Points points) { assert(points.size() >= 3); // sort input points @@ -1189,16 +1188,16 @@ Vec3d extract_euler_angles(const Eigen::Matrix& Vec3d angles2 = Vec3d::Zero(); if (is_approx(std::abs(rotation_matrix(2, 0)), 1.0)) { - angles1(0) = 0.0; - if (rotation_matrix(2, 0) > 0.0) // == +1.0 + angles1(2) = 0.0; + if (rotation_matrix(2, 0) < 0.0) // == -1.0 { angles1(1) = 0.5 * (double)PI; - angles1(2) = angles1(0) + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); + angles1(0) = angles1(2) + ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); } - else // == -1.0 + else // == 1.0 { - angles1(1) = 0.5 * (double)PI; - angles1(2) = -angles1(0) - ::atan2(rotation_matrix(0, 1), rotation_matrix(0, 2)); + angles1(1) = - 0.5 * (double)PI; + angles1(0) = - angles1(2) + ::atan2(- rotation_matrix(0, 1), - rotation_matrix(0, 2)); } angles2 = angles1; } diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index deefaa789..b43d59143 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -237,7 +237,7 @@ public: void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); - Vec3d get_scaling_factor() const { return m_scaling_factor; } + const Vec3d& get_scaling_factor() const { return m_scaling_factor; } double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } void set_scaling_factor(const Vec3d& scaling_factor); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b13b00e57..d86a180b8 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -573,6 +573,8 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs) this->origin_translation = rhs.origin_translation; m_bounding_box = rhs.m_bounding_box; m_bounding_box_valid = rhs.m_bounding_box_valid; + m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box; + m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid; this->clear_volumes(); this->volumes.reserve(rhs.volumes.size()); @@ -604,6 +606,8 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs) this->origin_translation = std::move(rhs.origin_translation); m_bounding_box = std::move(rhs.m_bounding_box); m_bounding_box_valid = std::move(rhs.m_bounding_box_valid); + m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box; + m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid; this->clear_volumes(); this->volumes = std::move(rhs.volumes); @@ -783,19 +787,11 @@ void ModelObject::clear_instances() const BoundingBoxf3& ModelObject::bounding_box() const { if (! m_bounding_box_valid) { - BoundingBoxf3 raw_bbox; - for (const ModelVolume *v : this->volumes) - if (v->is_model_part()) - { - TriangleMesh m = v->mesh; - m.transform(v->get_matrix()); - raw_bbox.merge(m.bounding_box()); - } - BoundingBoxf3 bb; - for (const ModelInstance *i : this->instances) - bb.merge(i->transform_bounding_box(raw_bbox)); - m_bounding_box = bb; m_bounding_box_valid = true; + BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box(); + m_bounding_box.reset(); + for (const ModelInstance *i : this->instances) + m_bounding_box.merge(i->transform_bounding_box(raw_bbox)); } return m_bounding_box; } @@ -842,6 +838,26 @@ TriangleMesh ModelObject::full_raw_mesh() const return mesh; } +BoundingBoxf3 ModelObject::raw_mesh_bounding_box() const +{ + if (! m_raw_mesh_bounding_box_valid) { + m_raw_mesh_bounding_box_valid = true; + m_raw_mesh_bounding_box.reset(); + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) + m_raw_mesh_bounding_box.merge(v->mesh.transformed_bounding_box(v->get_matrix())); + } + return m_raw_mesh_bounding_box; +} + +BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const +{ + BoundingBoxf3 bb; + for (const ModelVolume *v : this->volumes) + bb.merge(v->mesh.transformed_bounding_box(v->get_matrix())); + return bb; +} + // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. // This bounding box is only used for the actual slicing. BoundingBoxf3 ModelObject::raw_bounding_box() const @@ -896,19 +912,59 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_ return bb; } -#if ENABLE_VOLUMES_CENTERING_FIXES -BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const +// Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane. +// This method is cheap in that it does not make any unnecessary copy of the volume meshes. +// This method is used by the auto arrange function. +Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) { - BoundingBoxf3 bb; + Points pts; for (const ModelVolume *v : this->volumes) - { - TriangleMesh vol_mesh(v->mesh); - vol_mesh.transform(v->get_matrix()); - bb.merge(vol_mesh.bounding_box()); + if (v->is_model_part()) { + const stl_file &stl = v->mesh.stl; + Transform3d trafo = trafo_instance * v->get_matrix(); + if (stl.v_shared == nullptr) { + // Using the STL faces. + for (unsigned int i = 0; i < stl.stats.number_of_facets; ++ i) { + const stl_facet &facet = stl.facet_start[i]; + for (size_t j = 0; j < 3; ++ j) { + Vec3d p = trafo * facet.vertex[j].cast(); + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + } + } + } else { + // Using the shared vertices should be a bit quicker than using the STL faces. + for (int i = 0; i < stl.stats.shared_vertices; ++ i) { + Vec3d p = trafo * stl.v_shared[i].cast(); + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + } + } + } + std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) < b(0) || (a(0) == b(0) && a(1) < b(1)); }); + pts.erase(std::unique(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) == b(0) && a(1) == b(1); }), pts.end()); + + Polygon hull; + int n = (int)pts.size(); + if (n >= 3) { + int k = 0; + hull.points.resize(2 * n); + // Build lower hull + for (int i = 0; i < n; ++ i) { + while (k >= 2 && pts[i].ccw(hull[k-2], hull[k-1]) <= 0) + -- k; + hull[k ++] = pts[i]; + } + // Build upper hull + for (int i = n-2, t = k+1; i >= 0; i--) { + while (k >= t && pts[i].ccw(hull[k-2], hull[k-1]) <= 0) + -- k; + hull[k ++] = pts[i]; + } + hull.points.resize(k); + assert(hull.points.front() == hull.points.back()); + hull.points.pop_back(); } - return bb; + return hull; } -#endif // ENABLE_VOLUMES_CENTERING_FIXES void ModelObject::center_around_origin() { @@ -1097,7 +1153,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b // Perform cut TriangleMeshSlicer tms(&volume->mesh); - tms.cut(z, &upper_mesh, &lower_mesh); + tms.cut(float(z), &upper_mesh, &lower_mesh); // Reset volume transformation except for offset const Vec3d offset = volume->get_offset(); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index f13baee08..0ae238510 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -205,7 +205,7 @@ public: // This bounding box is approximate and not snug. // This bounding box is being cached. const BoundingBoxf3& bounding_box() const; - void invalidate_bounding_box() { m_bounding_box_valid = false; } + void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; } // A mesh containing all transformed instances of this object. TriangleMesh mesh() const; @@ -219,10 +219,16 @@ public: BoundingBoxf3 raw_bounding_box() const; // A snug bounding box around the transformed non-modifier object volumes. BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const; -#if ENABLE_VOLUMES_CENTERING_FIXES - // Bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. + // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. + BoundingBoxf3 raw_mesh_bounding_box() const; + // A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. BoundingBoxf3 full_raw_mesh_bounding_box() const; -#endif // ENABLE_VOLUMES_CENTERING_FIXES + + // Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane. + // This method is cheap in that it does not make any unnecessary copy of the volume meshes. + // This method is used by the auto arrange function. + Polygon convex_hull_2d(const Transform3d &trafo_instance); + void center_around_origin(); void ensure_on_bed(); void translate_instances(const Vec3d& vector); @@ -261,7 +267,8 @@ protected: void set_model(Model *model) { m_model = model; } private: - ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false) {} + ModelObject(Model *model) : m_model(model), origin_translation(Vec3d::Zero()), + m_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {} ~ModelObject(); /* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */ @@ -280,6 +287,8 @@ private: // Bounding box, cached. mutable BoundingBoxf3 m_bounding_box; mutable bool m_bounding_box_valid; + mutable BoundingBoxf3 m_raw_mesh_bounding_box; + mutable bool m_raw_mesh_bounding_box_valid; }; // An object STL, or a modifier volume, over which a different set of parameters shall be applied. @@ -464,7 +473,7 @@ public: void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); } void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); } - Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); } + const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); } double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); } void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); } diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 1f517375c..fd3cf8648 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -358,6 +358,29 @@ public: m_rtree.clear(); return m_pck.executeIndexed(std::forward(args)...); } + + inline void preload(const PackGroup& pg) { + m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; + m_pconf.object_function = nullptr; // drop the special objectfunction + m_pck.preload(pg); + + // Build the rtree for queries to work + for(const ItemGroup& grp : pg) + for(unsigned idx = 0; idx < grp.size(); ++idx) { + Item& itm = grp[idx]; + m_rtree.insert({itm.boundingBox(), idx}); + } + + m_pck.configure(m_pconf); + } + + bool is_colliding(const Item& item) { + if(m_rtree.empty()) return false; + std::vector result; + m_rtree.query(bgi::intersects(item.boundingBox()), + std::back_inserter(result)); + return !result.empty(); + } }; // Arranger specialization for a Box shaped bin. @@ -365,8 +388,8 @@ template<> class AutoArranger: public _ArrBase { public: AutoArranger(const Box& bin, Distance dist, - std::function progressind, - std::function stopcond): + std::function progressind = [](unsigned){}, + std::function stopcond = [](){return false;}): _ArrBase(bin, dist, progressind, stopcond) { @@ -411,8 +434,8 @@ template<> class AutoArranger: public _ArrBase { public: AutoArranger(const lnCircle& bin, Distance dist, - std::function progressind, - std::function stopcond): + std::function progressind = [](unsigned){}, + std::function stopcond = [](){return false;}): _ArrBase(bin, dist, progressind, stopcond) { // As with the box, only the inside check is different. @@ -456,8 +479,8 @@ public: template<> class AutoArranger: public _ArrBase { public: AutoArranger(const PolygonImpl& bin, Distance dist, - std::function progressind, - std::function stopcond): + std::function progressind = [](unsigned){}, + std::function stopcond = [](){return false;}): _ArrBase(bin, dist, progressind, stopcond) { m_pconf.object_function = [this, &bin] (const Item &item) { @@ -530,24 +553,47 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { for(ModelObject* objptr : model.objects) { if(objptr) { - TriangleMesh rmesh = objptr->raw_mesh(); - - ModelInstance * finst = objptr->instances.front(); - - // Object instances should carry the same scaling and - // x, y rotation that is why we use the first instance. - // The next line will apply only the full mirroring and scaling - rmesh.transform(finst->get_matrix(true, true, false, false)); - rmesh.rotate_x(float(finst->get_rotation()(X))); - rmesh.rotate_y(float(finst->get_rotation()(Y))); - // TODO export the exact 2D projection. Cannot do it as libnest2d // does not support concave shapes (yet). - auto p = rmesh.convex_hull(); + ClipperLib::Path clpath; +//WIP Vojtech's optimization of the calculation of the convex hull is not working correctly yet. +#if 1 + { + TriangleMesh rmesh = objptr->raw_mesh(); - p.make_clockwise(); - p.append(p.first_point()); - auto clpath = Slic3rMultiPoint_to_ClipperPath(p); + ModelInstance * finst = objptr->instances.front(); + + // Object instances should carry the same scaling and + // x, y rotation that is why we use the first instance. + // The next line will apply only the full mirroring and scaling + rmesh.transform(finst->get_matrix(true, true, false, false)); + rmesh.rotate_x(float(finst->get_rotation()(X))); + rmesh.rotate_y(float(finst->get_rotation()(Y))); + + // TODO export the exact 2D projection. Cannot do it as libnest2d + // does not support concave shapes (yet). + auto p = rmesh.convex_hull(); + + p.make_clockwise(); + p.append(p.first_point()); + clpath = Slic3rMultiPoint_to_ClipperPath(p); + } +#else + // Object instances should carry the same scaling and + // x, y rotation that is why we use the first instance. + { + ModelInstance *finst = objptr->instances.front(); + Vec3d rotation = finst->get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = Geometry::assemble_transform(Vec3d::Zero(), rotation, finst->get_scaling_factor(), finst->get_mirror()); + Polygon p = objptr->convex_hull_2d(trafo_instance); + assert(! p.points.empty()); + p.reverse(); + assert(! p.is_counter_clockwise()); + p.append(p.first_point()); + clpath = Slic3rMultiPoint_to_ClipperPath(p); + } +#endif for(ModelInstance* objinst : objptr->instances) { if(objinst) { @@ -791,5 +837,174 @@ bool arrange(Model &model, // The model with the geometries return ret && result.size() == 1; } +void find_new_position(const Model &model, + ModelInstancePtrs toadd, + coord_t min_obj_distance, + const Polyline &bed) +{ + // Get the 2D projected shapes with their 3D model instance pointers + auto shapemap = arr::projectModelFromTop(model); + + // Copy the references for the shapes only as the arranger expects a + // sequence of objects convertible to Item or ClipperPolygon + PackGroup preshapes; preshapes.emplace_back(); + ItemGroup shapes; + preshapes.front().reserve(shapemap.size()); + + std::vector shapes_ptr; shapes_ptr.reserve(toadd.size()); + IndexedPackGroup result; + + // If there is no hint about the shape, we will try to guess + BedShapeHint bedhint = bedShape(bed); + + BoundingBox bbb(bed); + + auto binbb = Box({ + static_cast(bbb.min(0)), + static_cast(bbb.min(1)) + }, + { + static_cast(bbb.max(0)), + static_cast(bbb.max(1)) + }); + + for(auto it = shapemap.begin(); it != shapemap.end(); ++it) { + if(std::find(toadd.begin(), toadd.end(), it->first) == toadd.end()) { + if(it->second.isInside(binbb)) // just ignore items which are outside + preshapes.front().emplace_back(std::ref(it->second)); + } + else { + shapes_ptr.emplace_back(it->first); + shapes.emplace_back(std::ref(it->second)); + } + } + + auto try_first_to_center = [&shapes, &shapes_ptr, &binbb] + (std::function is_colliding, + std::function preload) + { + // Try to put the first item to the center, as the arranger will not + // do this for us. + auto shptrit = shapes_ptr.begin(); + for(auto shit = shapes.begin(); shit != shapes.end(); ++shit, ++shptrit) + { + // Try to place items to the center + Item& itm = *shit; + auto ibb = itm.boundingBox(); + auto d = binbb.center() - ibb.center(); + itm.translate(d); + if(!is_colliding(itm)) { + preload(itm); + + auto offset = itm.translation(); + Radians rot = itm.rotation(); + ModelInstance *minst = *shptrit; + Vec3d foffset(offset.X*SCALING_FACTOR, + offset.Y*SCALING_FACTOR, + minst->get_offset()(Z)); + + // write the transformation data into the model instance + minst->set_rotation(Z, rot); + minst->set_offset(foffset); + + shit = shapes.erase(shit); + shptrit = shapes_ptr.erase(shptrit); + break; + } + } + }; + + switch(bedhint.type) { + case BedShapeType::BOX: { + + // Create the arranger for the box shaped bed + AutoArranger arrange(binbb, min_obj_distance); + + if(!preshapes.front().empty()) { // If there is something on the plate + arrange.preload(preshapes); + try_first_to_center( + [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, + [&arrange](Item& itm) { arrange.preload({{itm}}); } + ); + } + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } + case BedShapeType::CIRCLE: { + + auto c = bedhint.shape.circ; + auto cc = to_lnCircle(c); + + // Create the arranger for the box shaped bed + AutoArranger arrange(cc, min_obj_distance); + + if(!preshapes.front().empty()) { // If there is something on the plate + arrange.preload(preshapes); + try_first_to_center( + [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, + [&arrange](Item& itm) { arrange.preload({{itm}}); } + ); + } + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } + case BedShapeType::IRREGULAR: + case BedShapeType::WHO_KNOWS: { + using P = libnest2d::PolygonImpl; + + auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); + P irrbed = sl::create(std::move(ctour)); + + AutoArranger

arrange(irrbed, min_obj_distance); + + if(!preshapes.front().empty()) { // If there is something on the plate + arrange.preload(preshapes); + try_first_to_center( + [&arrange](const Item& itm) {return arrange.is_colliding(itm);}, + [&arrange](Item& itm) { arrange.preload({{itm}}); } + ); + } + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } + }; + + // Now we go through the result which will contain the fixed and the moving + // polygons as well. We will have to search for our item. + + const auto STRIDE_PADDING = 1.2; + Coord stride = Coord(STRIDE_PADDING*binbb.width()*SCALING_FACTOR); + Coord batch_offset = 0; + + for(auto& group : result) { + for(auto& r : group) if(r.first < shapes.size()) { + Item& resultitem = r.second; + unsigned idx = r.first; + auto offset = resultitem.translation(); + Radians rot = resultitem.rotation(); + ModelInstance *minst = shapes_ptr[idx]; + Vec3d foffset(offset.X*SCALING_FACTOR + batch_offset, + offset.Y*SCALING_FACTOR, + minst->get_offset()(Z)); + + // write the transformation data into the model instance + minst->set_rotation(Z, rot); + minst->set_offset(foffset); + } + batch_offset += stride; + } } + +} + + } diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index d62e0df30..d76769081 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -73,7 +73,13 @@ bool arrange(Model &model, coord_t min_obj_distance, std::function progressind, std::function stopcondition); -} +/// This will find a suitable position for a new object instance and leave the +/// old items untouched. +void find_new_position(const Model& model, + ModelInstancePtrs instances_to_add, + coord_t min_obj_distance, + const Slic3r::Polyline& bed); -} +} // arr +} // Slic3r #endif // MODELARRANGE_HPP diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp index 1436be17f..c1096206a 100644 --- a/src/libslic3r/SLA/SLABoilerPlate.hpp +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -5,8 +5,8 @@ #include #include -#include "ExPolygon.hpp" -#include "TriangleMesh.hpp" +#include +#include namespace Slic3r { namespace sla { @@ -53,7 +53,7 @@ struct Contour3D { void merge(const Contour3D& ctr) { auto s3 = coord_t(points.size()); - auto s = coord_t(indices.size()); + auto s = indices.size(); points.insert(points.end(), ctr.points.begin(), ctr.points.end()); indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end()); @@ -62,6 +62,17 @@ struct Contour3D { auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3; } } + + // Write the index triangle structure to OBJ file for debugging purposes. + void to_obj(std::ostream& stream) { + for(auto& p : points) { + stream << "v " << p.transpose() << "\n"; + } + + for(auto& f : indices) { + stream << "f " << (f + Vec3i(1, 1, 1)).transpose() << "\n"; + } + } }; //using PointSet = Eigen::Matrix; //Eigen::MatrixXd; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 1d7858ead..b7ae95fda 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -9,8 +9,8 @@ #include "SLASpatIndex.hpp" #include "SLABasePool.hpp" -#include "ClipperUtils.hpp" -#include "Model.hpp" +#include +#include #include @@ -176,6 +176,7 @@ Contour3D cylinder(double r, double h, size_t ssteps) { Vec3d jp = {0, 0, 0}; Vec3d endp = {0, 0, h}; + // Upper circle points for(int i = 0; i < steps; ++i) { double phi = i*a; double ex = endp(X) + r*std::cos(phi); @@ -183,6 +184,7 @@ Contour3D cylinder(double r, double h, size_t ssteps) { points.emplace_back(ex, ey, endp(Z)); } + // Lower circle points for(int i = 0; i < steps; ++i) { double phi = i*a; double x = jp(X) + r*std::cos(phi); @@ -190,6 +192,7 @@ Contour3D cylinder(double r, double h, size_t ssteps) { points.emplace_back(x, y, jp(Z)); } + // Now create long triangles connecting upper and lower circles indices.reserve(2*ssteps); auto offs = steps; for(int i = 0; i < steps - 1; ++i) { @@ -197,10 +200,26 @@ Contour3D cylinder(double r, double h, size_t ssteps) { indices.emplace_back(i, offs + i + 1, i + 1); } + // Last triangle connecting the first and last vertices auto last = steps - 1; indices.emplace_back(0, last, offs); indices.emplace_back(last, offs + last, offs); + // According to the slicing algorithms, we need to aid them with generating + // a watertight body. So we create a triangle fan for the upper and lower + // ending of the cylinder to close the geometry. + points.emplace_back(jp); size_t ci = points.size() - 1; + for(int i = 0; i < steps - 1; ++i) + indices.emplace_back(i + offs + 1, i + offs, ci); + + indices.emplace_back(offs, steps + offs - 1, ci); + + points.emplace_back(endp); ci = points.size() - 1; + for(int i = 0; i < steps - 1; ++i) + indices.emplace_back(ci, i, i + 1); + + indices.emplace_back(steps - 1, 0, ci); + return ret; } @@ -352,6 +371,8 @@ struct Pillar { r(radius), steps(st), endpoint(endp), starts_from_head(false) { assert(steps > 0); + assert(jp(Z) > endp(Z)); // Endpoint is below the starting point + int steps_1 = int(steps - 1); auto& points = mesh.points; @@ -382,6 +403,22 @@ struct Pillar { indices.emplace_back(0, steps_1, offs); indices.emplace_back(steps_1, offs + steps_1, offs); + + // According to the slicing algorithms, we need to aid them with + // generating a watertight body. So we create a triangle fan for the + // upper and lower ending of the cylinder to close the geometry. + points.emplace_back(jp); size_t ci = points.size() - 1; + int stepsi = int(steps); + for(int i = 0; i < stepsi - 1; ++i) + indices.emplace_back(ci, i, i + 1); + + indices.emplace_back(stepsi - 1, 0, ci); + + points.emplace_back(endp); ci = points.size() - 1; + for(int i = 0; i < stepsi - 1; ++i) + indices.emplace_back(i + offs + 1, i + offs, ci); + + indices.emplace_back(offs, stepsi + offs - 1, ci); } Pillar(const Junction& junc, const Vec3d& endp): @@ -461,6 +498,8 @@ struct Bridge { Vec3d dir = (j2 - j1).normalized(); double d = distance(j2, j1); + assert(d > 0); + mesh = cylinder(r, d, steps); auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 4648b95c0..8b69a9e5c 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -525,67 +525,22 @@ BoundingBoxf3 TriangleMesh::bounding_box() const return bb; } -BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& t) const +BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const { - bool has_shared = (stl.v_shared != nullptr); - if (!has_shared) - stl_generate_shared_vertices(const_cast(&stl)); - - unsigned int vertices_count = (stl.stats.shared_vertices > 0) ? (unsigned int)stl.stats.shared_vertices : 3 * (unsigned int)stl.stats.number_of_facets; - - if (vertices_count == 0) - return BoundingBoxf3(); - - Eigen::MatrixXd src_vertices(3, vertices_count); - - if (stl.stats.shared_vertices > 0) - { - assert(stl.v_shared != nullptr); - stl_vertex* vertex_ptr = stl.v_shared; - for (int i = 0; i < stl.stats.shared_vertices; ++i) - { - src_vertices(0, i) = (double)(*vertex_ptr)(0); - src_vertices(1, i) = (double)(*vertex_ptr)(1); - src_vertices(2, i) = (double)(*vertex_ptr)(2); - vertex_ptr += 1; + BoundingBoxf3 bbox; + if (stl.v_shared == nullptr) { + // Using the STL faces. + for (int i = 0; i < this->facets_count(); ++ i) { + const stl_facet &facet = this->stl.facet_start[i]; + for (size_t j = 0; j < 3; ++ j) + bbox.merge(trafo * facet.vertex[j].cast()); } + } else { + // Using the shared vertices should be a bit quicker than using the STL faces. + for (int i = 0; i < stl.stats.shared_vertices; ++ i) + bbox.merge(trafo * this->stl.v_shared[i].cast()); } - else - { - stl_facet* facet_ptr = stl.facet_start; - unsigned int v_id = 0; - while (facet_ptr < stl.facet_start + stl.stats.number_of_facets) - { - for (int i = 0; i < 3; ++i) - { - src_vertices(0, v_id) = (double)facet_ptr->vertex[i](0); - src_vertices(1, v_id) = (double)facet_ptr->vertex[i](1); - src_vertices(2, v_id) = (double)facet_ptr->vertex[i](2); - ++v_id; - } - facet_ptr += 1; - } - } - - if (!has_shared && (stl.stats.shared_vertices > 0)) - stl_invalidate_shared_vertices(const_cast(&stl)); - - Eigen::MatrixXd dst_vertices(3, vertices_count); - dst_vertices = t * src_vertices.colwise().homogeneous(); - - Vec3d v_min(dst_vertices(0, 0), dst_vertices(1, 0), dst_vertices(2, 0)); - Vec3d v_max = v_min; - - for (int i = 1; i < vertices_count; ++i) - { - for (int j = 0; j < 3; ++j) - { - v_min(j) = std::min(v_min(j), dst_vertices(j, i)); - v_max(j) = std::max(v_max(j), dst_vertices(j, i)); - } - } - - return BoundingBoxf3(v_min, v_max); + return bbox; } TriangleMesh TriangleMesh::convex_hull_3d() const @@ -2010,4 +1965,5 @@ TriangleMesh make_sphere(double rho, double fa) { TriangleMesh mesh(vertices, facets); return mesh; } + } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index fc2b40013..be70ee79d 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -60,7 +60,7 @@ public: Polygon convex_hull(); BoundingBoxf3 bounding_box() const; // Returns the bbox of this TriangleMesh transformed by the given transformation - BoundingBoxf3 transformed_bounding_box(const Transform3d& t) const; + BoundingBoxf3 transformed_bounding_box(const Transform3d &trafo) const; // Returns the convex hull of this TriangleMesh TriangleMesh convex_hull_3d() const; void reset_repair_stats(); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 783c65293..9d65a479f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.6) include(PrecompiledHeader) -add_library(libslic3r_gui STATIC +set(SLIC3R_GUI_SOURCES pchheader.cpp pchheader.hpp GUI/AboutDialog.cpp @@ -127,6 +127,12 @@ add_library(libslic3r_gui STATIC Utils/HexFile.hpp ) +if (APPLE) + list(APPEND SLIC3R_GUI_SOURCES Utils/RetinaHelperImpl.mm) +endif () + +add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES}) + target_link_libraries(libslic3r_gui libslic3r avrdude imgui) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 28e6e1018..a537870ee 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -59,6 +59,11 @@ void AppConfig::set_defaults() if (get("use_legacy_opengl").empty()) set("use_legacy_opengl", "0"); +#if __APPLE__ + if (get("use_retina_opengl").empty()) + set("use_retina_opengl", "1"); +#endif + if (get("remember_output_path").empty()) set("remember_output_path", "1"); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ee8a7ebbd..7366516d1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -9,6 +9,7 @@ #include "libslic3r/GCode/PreviewData.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Utils.hpp" +#include "libslic3r/Technologies.hpp" #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/BackgroundSlicingProcess.hpp" #include "slic3r/GUI/GLShader.hpp" @@ -20,6 +21,10 @@ #include "GUI_ObjectManipulation.hpp" #include "I18N.hpp" +#if ENABLE_RETINA_GL +#include "slic3r/Utils/RetinaHelper.hpp" +#endif + #include #include @@ -45,6 +50,7 @@ #include #include #include +#include static const float TRACKBALLSIZE = 0.8f; static const float GIMBALL_LOCK_THETA_MAX = 180.0f; @@ -59,8 +65,6 @@ static const float VIEW_BOTTOM[2] = { 0.0f, 180.0f }; static const float VIEW_FRONT[2] = { 0.0f, 90.0f }; static const float VIEW_REAR[2] = { 180.0f, 90.0f }; -static const float VARIABLE_LAYER_THICKNESS_BAR_WIDTH = 70.0f; -static const float VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT = 22.0f; static const float GIZMO_RESET_BUTTON_HEIGHT = 22.0f; static const float GIZMO_RESET_BUTTON_WIDTH = 70.f; @@ -191,9 +195,10 @@ Size::Size() { } -Size::Size(int width, int height) +Size::Size(int width, int height, float scale_factor) : m_width(width) , m_height(height) + , m_scale_factor(scale_factor) { } @@ -217,6 +222,16 @@ void Size::set_height(int height) m_height = height; } +int Size::get_scale_factor() const +{ + return m_scale_factor; +} + +void Size::set_scale_factor(int scale_factor) +{ + m_scale_factor = scale_factor; +} + Rect::Rect() : m_left(0.0f) , m_top(0.0f) @@ -330,6 +345,7 @@ void GLCanvas3D::Camera::set_scene_box(const BoundingBoxf3& box, GLCanvas3D& can GLCanvas3D::Bed::Bed() : m_type(Custom) + , m_scale_factor(1.0f) { } @@ -392,8 +408,10 @@ Point GLCanvas3D::Bed::point_projection(const Point& point) const } #if ENABLE_PRINT_BED_MODELS -void GLCanvas3D::Bed::render(float theta, bool useVBOs) const +void GLCanvas3D::Bed::render(float theta, bool useVBOs, float scale_factor) const { + m_scale_factor = scale_factor; + switch (m_type) { case MK2: @@ -420,8 +438,10 @@ void GLCanvas3D::Bed::render(float theta, bool useVBOs) const } } #else -void GLCanvas3D::Bed::render(float theta) const +void GLCanvas3D::Bed::render(float theta, float scale_factor) const { + m_scale_factor = scale_factor; + switch (m_type) { case MK2: @@ -675,7 +695,7 @@ void GLCanvas3D::Bed::_render_custom() const // we need depth test for grid, otherwise it would disappear when looking the object from below ::glEnable(GL_DEPTH_TEST); - ::glLineWidth(3.0f); + ::glLineWidth(3.0f * m_scale_factor); ::glColor4f(0.2f, 0.2f, 0.2f, 0.4f); ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices()); ::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount); @@ -873,6 +893,9 @@ GLCanvas3D::LayersEditing::~LayersEditing() delete m_slicing_parameters; } +const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; +const float GLCanvas3D::LayersEditing::THICKNESS_RESET_BUTTON_HEIGHT = 22.0f; + bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) { if (!m_shader.init(vertex_shader_filename, fragment_shader_filename)) @@ -993,7 +1016,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) float w = (float)cnv_size.get_width(); float h = (float)cnv_size.get_height(); - return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, 0.0f, w, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT); + return Rect(w - thickness_bar_width(canvas), 0.0f, w, h - reset_button_height(canvas)); } Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas) @@ -1002,7 +1025,7 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas) float w = (float)cnv_size.get_width(); float h = (float)cnv_size.get_height(); - return Rect(w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH, h - VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT, w, h); + return Rect(w - thickness_bar_width(canvas), h - reset_button_height(canvas), w, h); } Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) @@ -1014,7 +1037,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) float zoom = canvas.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom); + return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom); } Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas) @@ -1026,7 +1049,7 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas float zoom = canvas.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - return Rect((half_w - VARIABLE_LAYER_THICKNESS_BAR_WIDTH) * inv_zoom, (-half_h + VARIABLE_LAYER_THICKNESS_RESET_BUTTON_HEIGHT) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); + return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); } @@ -1037,6 +1060,8 @@ bool GLCanvas3D::LayersEditing::_is_initialized() const void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const { + // TODO: do this with ImGui + if (m_tooltip_texture.get_id() == 0) { std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; @@ -1044,6 +1069,15 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas return; } +#if ENABLE_RETINA_GL + const float scale = canvas.get_canvas_size().get_scale_factor(); + const float width = (float)m_tooltip_texture.get_width() * scale; + const float height = (float)m_tooltip_texture.get_height() * scale; +#else + const float width = (float)m_tooltip_texture.get_width(); + const float height = (float)m_tooltip_texture.get_height(); +#endif + float zoom = canvas.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float gap = 10.0f * inv_zoom; @@ -1051,9 +1085,9 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas float bar_left = bar_rect.get_left(); float reset_bottom = reset_rect.get_bottom(); - float l = bar_left - (float)m_tooltip_texture.get_width() * inv_zoom - gap; + float l = bar_left - width * inv_zoom - gap; float r = bar_left - gap; - float t = reset_bottom + (float)m_tooltip_texture.get_height() * inv_zoom + gap; + float t = reset_bottom + height * inv_zoom + gap; float b = reset_bottom + gap; GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t); @@ -1082,11 +1116,6 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas // The shader requires the original model coordinates when rendering to the texture, so we pass it the unit matrix m_shader.set_uniform("volume_world_matrix", UNIT_MATRIX); - GLsizei w = (GLsizei)m_layers_texture.width; - GLsizei h = (GLsizei)m_layers_texture.height; - GLsizei half_w = w / 2; - GLsizei half_h = h / 2; - ::glPixelStorei(GL_UNPACK_ALIGNMENT, 1); ::glBindTexture(GL_TEXTURE_2D, m_z_texture_id); @@ -1267,6 +1296,25 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters() } } +float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas) +{ +#if ENABLE_RETINA_GL + return canvas.get_canvas_size().get_scale_factor() * THICKNESS_BAR_WIDTH; +#else + return THICKNESS_BAR_WIDTH; +#endif +} + +float GLCanvas3D::LayersEditing::reset_button_height(const GLCanvas3D &canvas) +{ +#if ENABLE_RETINA_GL + return canvas.get_canvas_size().get_scale_factor() * THICKNESS_RESET_BUTTON_HEIGHT; +#else + return THICKNESS_RESET_BUTTON_HEIGHT; +#endif +} + + const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); #if ENABLE_MOVE_MIN_THRESHOLD @@ -1329,6 +1377,7 @@ GLCanvas3D::Selection::Selection() , m_valid(false) , m_bounding_box_dirty(true) , m_curved_arrow(16) + , m_scale_factor(1.0f) { #if ENABLE_RENDER_SELECTION_CENTER m_quadric = ::gluNewQuadric(); @@ -1721,7 +1770,7 @@ void GLCanvas3D::Selection::translate(const Vec3d& displacement, bool local) #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - _synchronize_unselected_instances(); + _synchronize_unselected_instances(SYNC_ROTATION_NONE); else if (m_mode == Volume) _synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1729,64 +1778,100 @@ void GLCanvas3D::Selection::translate(const Vec3d& displacement, bool local) m_bounding_box_dirty = true; } +static Eigen::Quaterniond rotation_xyz_diff(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + return + // From the current coordinate system to world. + Eigen::AngleAxisd(rot_xyz_to(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(rot_xyz_to(1), Vec3d::UnitY()) * Eigen::AngleAxisd(rot_xyz_to(0), Vec3d::UnitX()) * + // From world to the initial coordinate system. + Eigen::AngleAxisd(-rot_xyz_from(0), Vec3d::UnitX()) * Eigen::AngleAxisd(-rot_xyz_from(1), Vec3d::UnitY()) * Eigen::AngleAxisd(-rot_xyz_from(2), Vec3d::UnitZ()); +} + +// This should only be called if it is known, that the two rotations only differ in rotation around the Z axis. +static double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + Vec3d axis = angle_axis.axis(); + double angle = angle_axis.angle(); +#ifdef _DEBUG + if (std::abs(angle) > 1e-8) { + assert(std::abs(axis.x()) < 1e-8); + assert(std::abs(axis.y()) < 1e-8); + } +#endif /* _DEBUG */ + return (axis.z() < 0) ? -angle : angle; +} + void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local) { if (!m_valid) return; + int rot_axis_max; + rotation.cwiseAbs().maxCoeff(&rot_axis_max); + + // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. + std::vector object_instance_first(m_model->objects.size(), -1); + auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, local](GLVolume &volume, int i) { + int first_volume_idx = object_instance_first[volume.object_idx()]; + if (rot_axis_max != 2 && first_volume_idx != -1) { + // Generic rotation, but no rotation around the Z axis. + // Always do a local rotation (do not consider the selection to be a rigid body). + assert(rotation.z() == 0); + const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; + const Vec3d &rotation = first_volume.get_instance_rotation(); + double z_diff = rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); + volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + } else { + // extracts rotations from the composed transformation + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); + if (rot_axis_max == 2 && !local) + // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. + volume.set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); + volume.set_instance_rotation(new_rotation); + object_instance_first[volume.object_idx()] = i; + } + }; + for (unsigned int i : m_list) { + GLVolume &volume = *(*m_volumes)[i]; if (is_single_full_instance()) - { - if (local) - (*m_volumes)[i]->set_instance_rotation(rotation); - else - { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); - (*m_volumes)[i]->set_instance_rotation(new_rotation); - } - } + rotate_instance(volume, i); else if (is_single_volume() || is_single_modifier()) { if (local) - (*m_volumes)[i]->set_volume_rotation(rotation); + volume.set_volume_rotation(rotation); else { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - (*m_volumes)[i]->set_volume_rotation(new_rotation); + volume.set_volume_rotation(new_rotation); } } else { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); if (m_mode == Instance) - { - // extracts rotations from the composed transformation - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_instance_rotation_matrix()); - if (!local) - (*m_volumes)[i]->set_instance_offset(m_cache.dragging_center + m * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); - - (*m_volumes)[i]->set_instance_rotation(new_rotation); - } + rotate_instance(volume, i); else if (m_mode == Volume) { // extracts rotations from the composed transformation + Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); if (!local) { Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); - (*m_volumes)[i]->set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); + volume.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); } - (*m_volumes)[i]->set_volume_rotation(new_rotation); + volume.set_volume_rotation(new_rotation); } } } #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - _synchronize_unselected_instances(); + _synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); else if (m_mode == Volume) _synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1829,7 +1914,7 @@ void GLCanvas3D::Selection::flattening_rotate(const Vec3d& normal) // we want to synchronize z-rotation as well, otherwise the flattening behaves funny // when applied on one of several identical instances if (m_mode == Instance) - _synchronize_unselected_instances(true); + _synchronize_unselected_instances(SYNC_ROTATION_FULL); #endif // !DISABLE_INSTANCES_SYNCH m_bounding_box_dirty = true; @@ -1876,7 +1961,7 @@ void GLCanvas3D::Selection::scale(const Vec3d& scale, bool local) #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - _synchronize_unselected_instances(); + _synchronize_unselected_instances(SYNC_ROTATION_NONE); else if (m_mode == Volume) _synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1903,7 +1988,7 @@ void GLCanvas3D::Selection::mirror(Axis axis) #if !DISABLE_INSTANCES_SYNCH if (m_mode == Instance) - _synchronize_unselected_instances(); + _synchronize_unselected_instances(SYNC_ROTATION_NONE); else if (m_mode == Volume) _synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -2115,11 +2200,13 @@ void GLCanvas3D::Selection::erase() } } -void GLCanvas3D::Selection::render() const +void GLCanvas3D::Selection::render(float scale_factor) const { if (!m_valid || is_empty()) return; + m_scale_factor = scale_factor; + // render cumulative bounding box of selected volumes _render_selected_volumes(); _render_synchronized_volumes(); @@ -2564,7 +2651,7 @@ void GLCanvas3D::Selection::_render_bounding_box(const BoundingBoxf3& box, float ::glEnable(GL_DEPTH_TEST); ::glColor3fv(color); - ::glLineWidth(2.0f); + ::glLineWidth(2.0f * m_scale_factor); ::glBegin(GL_LINES); @@ -2699,7 +2786,43 @@ void GLCanvas3D::Selection::_render_sidebar_size_hint(Axis axis, double length) { } -void GLCanvas3D::Selection::_synchronize_unselected_instances(bool including_z) +#ifdef _DEBUG +static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) +{ + Eigen::AngleAxisd angle_axis(rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + Vec3d axis = angle_axis.axis(); + double angle = angle_axis.angle(); + if (std::abs(angle) < 1e-8) + return true; + assert(std::abs(axis.x()) < 1e-8); + assert(std::abs(axis.y()) < 1e-8); + assert(std::abs(std::abs(axis.z()) - 1.) < 1e-8); + return std::abs(axis.x()) < 1e-8 && std::abs(axis.y()) < 1e-8 && std::abs(std::abs(axis.z()) - 1.) < 1e-8; +} +static void verify_instances_rotation_synchronized(const Model &model, const GLVolumePtrs &volumes) +{ + for (size_t idx_object = 0; idx_object < model.objects.size(); ++ idx_object) { + int idx_volume_first = -1; + for (int i = 0; i < (int)volumes.size(); ++ i) { + if (volumes[i]->object_idx() == idx_object) { + idx_volume_first = i; + break; + } + } + assert(idx_volume_first != -1); // object without instances? + if (idx_volume_first == -1) + continue; + const Vec3d &rotation0 = volumes[idx_volume_first]->get_instance_rotation(); + for (int i = idx_volume_first + 1; i < (int)volumes.size(); ++ i) + if (volumes[i]->object_idx() == idx_object) { + const Vec3d &rotation = volumes[i]->get_instance_rotation(); + assert(is_rotation_xy_synchronized(rotation, rotation0)); + } + } +} +#endif /* _DEBUG */ + +void GLCanvas3D::Selection::_synchronize_unselected_instances(SyncRotationType sync_rotation_type) { std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); @@ -2715,7 +2838,7 @@ void GLCanvas3D::Selection::_synchronize_unselected_instances(bool including_z) continue; int instance_idx = volume->instance_idx(); - const Vec3d& rotation = volume->get_instance_rotation(); + const Vec3d& rotation = volume->get_instance_rotation(); const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); const Vec3d& mirror = volume->get_instance_mirror(); @@ -2732,26 +2855,34 @@ void GLCanvas3D::Selection::_synchronize_unselected_instances(bool including_z) if ((v->object_idx() != object_idx) || (v->instance_idx() == instance_idx)) continue; - auto is_approx = [](double value, double test_value) -> bool { return std::abs(value - test_value) < EPSILON; }; - - double z; - if (including_z) + assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); + switch (sync_rotation_type) { + case SYNC_ROTATION_NONE: + // z only rotation -> keep instance z + // The X,Y rotations should be synchronized from start to end of the rotation. + assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); + break; + case SYNC_ROTATION_FULL: // rotation comes from place on face -> force given z - z = rotation(2); - else if (is_approx(rotation(0), m_cache.volumes_data[j].get_instance_rotation()(0)) && is_approx(rotation(1), m_cache.volumes_data[j].get_instance_rotation()(1))) - // z only rotation -> keep instance z - z = v->get_instance_rotation()(2); - else - // generic rotation -> update instance z - z = m_cache.volumes_data[j].get_instance_rotation()(2) + rotation(2); + v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2))); + break; + case SYNC_ROTATION_GENERAL: + // generic rotation -> update instance z with the delta of the rotation. + double z_diff = rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + break; + } - v->set_instance_rotation(Vec3d(rotation(0), rotation(1), z)); v->set_instance_scaling_factor(scaling_factor); v->set_instance_mirror(mirror); done.insert(j); } } + +#ifdef _DEBUG + verify_instances_rotation_synchronized(*m_model, *m_volumes); +#endif /* _DEBUG */ } void GLCanvas3D::Selection::_synchronize_unselected_volumes() @@ -2815,14 +2946,11 @@ void GLCanvas3D::Selection::_ensure_on_bed() } } -const float GLCanvas3D::Gizmos::OverlayIconsScale = 1.0f; -const float GLCanvas3D::Gizmos::OverlayBorder = 5.0f; -const float GLCanvas3D::Gizmos::OverlayGapY = 5.0f * OverlayIconsScale; - GLCanvas3D::Gizmos::Gizmos() : m_enabled(false) , m_current(Undefined) { + set_overlay_scale(1.0); } GLCanvas3D::Gizmos::~Gizmos() @@ -2926,6 +3054,13 @@ void GLCanvas3D::Gizmos::set_enabled(bool enable) m_enabled = enable; } +void GLCanvas3D::Gizmos::set_overlay_scale(float scale) +{ + m_overlay_icons_scale = scale; + m_overlay_border = 5.0f * scale; + m_overlay_gap_y = 5.0f * scale; +} + std::string GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const GLCanvas3D::Selection& selection) { std::string name = ""; @@ -2935,22 +3070,22 @@ std::string GLCanvas3D::Gizmos::update_hover_state(const GLCanvas3D& canvas, con float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height) + OverlayBorder; + float top_y = 0.5f * (cnv_h - height) + m_overlay_border; for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; + float icon_size = (float)it->second->get_textures_size() * m_overlay_icons_scale; - bool inside = (OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); + bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); if (inside) name = it->second->get_name(); if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); - top_y += (icon_size + OverlayGapY); + top_y += (icon_size + m_overlay_gap_y); } return name; @@ -2963,15 +3098,15 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height) + OverlayBorder; + float top_y = 0.5f * (cnv_h - height) + m_overlay_border; for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; + float icon_size = (float)it->second->get_textures_size() * m_overlay_icons_scale; - bool inside = (OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); + bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size); if (it->second->is_activable(selection) && inside) { if ((it->second->get_state() == GLGizmoBase::On)) @@ -2988,7 +3123,7 @@ void GLCanvas3D::Gizmos::update_on_off_state(const GLCanvas3D& canvas, const Vec else it->second->set_state(GLGizmoBase::Off); - top_y += (icon_size + OverlayGapY); + top_y += (icon_size + m_overlay_gap_y); } GizmosMap::iterator it = m_gizmos.find(m_current); @@ -3060,18 +3195,18 @@ bool GLCanvas3D::Gizmos::overlay_contains_mouse(const GLCanvas3D& canvas, const float cnv_h = (float)canvas.get_canvas_size().get_height(); float height = _get_total_overlay_height(); - float top_y = 0.5f * (cnv_h - height) + OverlayBorder; + float top_y = 0.5f * (cnv_h - height) + m_overlay_border; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale; + float icon_size = (float)it->second->get_textures_size() * m_overlay_icons_scale; - if ((OverlayBorder <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= OverlayBorder + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size)) + if ((m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + icon_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + icon_size)) return true; - top_y += (icon_size + OverlayGapY); + top_y += (icon_size + m_overlay_gap_y); } return false; @@ -3344,7 +3479,7 @@ void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanva float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; float height = _get_total_overlay_height(); - float scaled_border = OverlayBorder * inv_zoom; + float scaled_border = m_overlay_border * inv_zoom; float top_x = (-0.5f * cnv_w) * inv_zoom; float top_y = (0.5f * height) * inv_zoom; @@ -3389,7 +3524,7 @@ void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanva bg_uv_left = bg_uv_i_left; bg_i_left = bg_left; - if ((OverlayBorder > 0) && (bg_uv_top != bg_uv_i_top)) + if ((m_overlay_border > 0) && (bg_uv_top != bg_uv_i_top)) { if (bg_uv_left != bg_uv_i_left) GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_top, bg_top, { { bg_uv_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_i_left, bg_uv_top }, { bg_uv_left, bg_uv_top } }); @@ -3400,15 +3535,15 @@ void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanva GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_top, bg_top, { { bg_uv_i_right, bg_uv_i_top }, { bg_uv_right, bg_uv_i_top }, { bg_uv_right, bg_uv_top }, { bg_uv_i_right, bg_uv_top } }); } - if ((OverlayBorder > 0) && (bg_uv_left != bg_uv_i_left)) + if ((m_overlay_border > 0) && (bg_uv_left != bg_uv_i_left)) GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_i_bottom, bg_i_top, { { bg_uv_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_left, bg_uv_i_top }, { bg_uv_left, bg_uv_i_top } }); GLTexture::render_sub_texture(bg_tex_id, bg_i_left, bg_i_right, bg_i_bottom, bg_i_top, { { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_i_right, bg_uv_i_top }, { bg_uv_i_left, bg_uv_i_top } }); - if ((OverlayBorder > 0) && (bg_uv_right != bg_uv_i_right)) + if ((m_overlay_border > 0) && (bg_uv_right != bg_uv_i_right)) GLTexture::render_sub_texture(bg_tex_id, bg_i_right, bg_right, bg_i_bottom, bg_i_top, { { bg_uv_i_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_bottom }, { bg_uv_right, bg_uv_i_top }, { bg_uv_i_right, bg_uv_i_top } }); - if ((OverlayBorder > 0) && (bg_uv_bottom != bg_uv_i_bottom)) + if ((m_overlay_border > 0) && (bg_uv_bottom != bg_uv_i_bottom)) { if (bg_uv_left != bg_uv_i_left) GLTexture::render_sub_texture(bg_tex_id, bg_left, bg_i_left, bg_bottom, bg_i_bottom, { { bg_uv_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_bottom }, { bg_uv_i_left, bg_uv_i_bottom }, { bg_uv_left, bg_uv_i_bottom } }); @@ -3420,19 +3555,19 @@ void GLCanvas3D::Gizmos::_render_overlay(const GLCanvas3D& canvas, const GLCanva } } - top_x += OverlayBorder * inv_zoom; - top_y -= OverlayBorder * inv_zoom; - float scaled_gap_y = OverlayGapY * inv_zoom; + top_x += m_overlay_border * inv_zoom; + top_y -= m_overlay_border * inv_zoom; + float scaled_gap_y = m_overlay_gap_y * inv_zoom; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - float icon_size = (float)it->second->get_textures_size() * OverlayIconsScale * inv_zoom; + float icon_size = (float)it->second->get_textures_size() * m_overlay_icons_scale * inv_zoom; GLTexture::render_texture(it->second->get_texture_id(), top_x, top_x + icon_size, top_y - icon_size, top_y); #if ENABLE_IMGUI if (it->second->get_state() == GLGizmoBase::On) - it->second->render_input_window(2.0f * OverlayBorder + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, selection); + it->second->render_input_window(2.0f * m_overlay_border + icon_size * zoom, 0.5f * cnv_h - top_y * zoom, selection); #endif // ENABLE_IMGUI top_y -= (icon_size + scaled_gap_y); } @@ -3447,17 +3582,17 @@ void GLCanvas3D::Gizmos::_render_current_gizmo(const GLCanvas3D::Selection& sele float GLCanvas3D::Gizmos::_get_total_overlay_height() const { - float height = 2.0f * OverlayBorder; + float height = 2.0f * m_overlay_border; for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { if ((it->second == nullptr) || !it->second->is_selectable()) continue; - height += (float)it->second->get_textures_size() * OverlayIconsScale + OverlayGapY; + height += (float)it->second->get_textures_size() * m_overlay_icons_scale + m_overlay_gap_y; } - return height - OverlayGapY; + return height - m_overlay_gap_y; } float GLCanvas3D::Gizmos::_get_total_overlay_width() const @@ -3468,10 +3603,10 @@ float GLCanvas3D::Gizmos::_get_total_overlay_width() const if ((it->second == nullptr) || !it->second->is_selectable()) continue; - max_icon_width = std::max(max_icon_width, (float)it->second->get_textures_size() * OverlayIconsScale); + max_icon_width = std::max(max_icon_width, (float)it->second->get_textures_size() * m_overlay_icons_scale); } - return max_icon_width + 2.0f * OverlayBorder; + return max_icon_width + 2.0f * m_overlay_border; } GLGizmoBase* GLCanvas3D::Gizmos::_get_current() const @@ -3490,7 +3625,7 @@ GLCanvas3D::WarningTexture::WarningTexture() { } -bool GLCanvas3D::WarningTexture::generate(const std::string& msg) +bool GLCanvas3D::WarningTexture::generate(const std::string& msg, const GLCanvas3D& canvas) { reset(); @@ -3499,7 +3634,8 @@ bool GLCanvas3D::WarningTexture::generate(const std::string& msg) wxMemoryDC memDC; // select default font - wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + const float scale = canvas.get_canvas_size().get_scale_factor(); + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale); font.MakeLarger(); font.MakeBold(); memDC.SetFont(font); @@ -3647,9 +3783,18 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c wxMemoryDC memDC; wxMemoryDC mask_memDC; + // calculate scaling + const float scale = canvas.get_canvas_size().get_scale_factor(); + const int scaled_square = std::floor((float)Px_Square * scale); + const int scaled_title_offset = Px_Title_Offset * scale; + const int scaled_text_offset = Px_Text_Offset * scale; + const int scaled_square_contour = Px_Square_Contour * scale; + const int scaled_border = Px_Border * scale; + // select default font - memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); - mask_memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); + const wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale); + memDC.SetFont(font); + mask_memDC.SetFont(font); // calculates texture size wxCoord w, h; @@ -3666,10 +3811,10 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c max_text_height = std::max(max_text_height, (int)h); } - m_original_width = std::max(2 * Px_Border + title_width, 2 * (Px_Border + Px_Square_Contour) + Px_Square + Px_Text_Offset + max_text_width); - m_original_height = 2 * (Px_Border + Px_Square_Contour) + title_height + Px_Title_Offset + items_count * Px_Square; + m_original_width = std::max(2 * scaled_border + title_width, 2 * (scaled_border + scaled_square_contour) + scaled_square + scaled_text_offset + max_text_width); + m_original_height = 2 * (scaled_border + scaled_square_contour) + title_height + scaled_title_offset + items_count * scaled_square; if (items_count > 1) - m_original_height += (items_count - 1) * Px_Square_Contour; + m_original_height += (items_count - 1) * scaled_square_contour; int pow_of_two_size = (int)next_highest_power_of_2(std::max(m_original_width, m_original_height)); @@ -3693,8 +3838,8 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c memDC.SetTextForeground(use_error_colors ? *wxWHITE : *wxBLACK); mask_memDC.SetTextForeground(*wxWHITE); - int title_x = Px_Border; - int title_y = Px_Border; + int title_x = scaled_border; + int title_y = scaled_border; memDC.DrawText(title, title_x, title_y); mask_memDC.DrawText(title, title_x, title_y); @@ -3702,12 +3847,12 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c mask_memDC.SetBrush(wxBrush(*wxWHITE)); // draw icons contours as background - int squares_contour_x = Px_Border; - int squares_contour_y = Px_Border + title_height + Px_Title_Offset; - int squares_contour_width = Px_Square + 2 * Px_Square_Contour; - int squares_contour_height = items_count * Px_Square + 2 * Px_Square_Contour; + int squares_contour_x = scaled_border; + int squares_contour_y = scaled_border + title_height + scaled_title_offset; + int squares_contour_width = scaled_square + 2 * scaled_square_contour; + int squares_contour_height = items_count * scaled_square + 2 * scaled_square_contour; if (items_count > 1) - squares_contour_height += (items_count - 1) * Px_Square_Contour; + squares_contour_height += (items_count - 1) * scaled_square_contour; wxColour color(Squares_Border_Color[0], Squares_Border_Color[1], Squares_Border_Color[2]); wxPen pen(color); @@ -3718,15 +3863,15 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c mask_memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height)); // draw items (colored icon + text) - int icon_x = squares_contour_x + Px_Square_Contour; + int icon_x = squares_contour_x + scaled_square_contour; int icon_x_inner = icon_x + 1; - int icon_y = squares_contour_y + Px_Square_Contour; - int icon_y_step = Px_Square + Px_Square_Contour; + int icon_y = squares_contour_y + scaled_square_contour; + int icon_y_step = scaled_square + scaled_square_contour; - int text_x = icon_x + Px_Square + Px_Text_Offset; - int text_y_offset = (Px_Square - max_text_height) / 2; + int text_x = icon_x + scaled_square + scaled_text_offset; + int text_y_offset = (scaled_square - max_text_height) / 2; - int px_inner_square = Px_Square - 2; + int px_inner_square = scaled_square - 2; for (const GCodePreviewData::LegendItem& item : items) { @@ -3740,7 +3885,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c brush.SetColour(color); memDC.SetPen(pen); memDC.SetBrush(brush); - memDC.DrawRectangle(wxRect(icon_x, icon_y, Px_Square, Px_Square)); + memDC.DrawRectangle(wxRect(icon_x, icon_y, scaled_square, scaled_square)); // draw icon interior color.Set(item_color_bytes[0], item_color_bytes[1], item_color_bytes[2], item_color_bytes[3]); @@ -3849,6 +3994,9 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) : m_canvas(canvas) , m_context(nullptr) +#if ENABLE_RETINA_GL + , m_retina_helper(nullptr) +#endif , m_in_render(false) , m_toolbar(GLToolbar::Normal) , m_view_toolbar(nullptr) @@ -3882,8 +4030,12 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_external_gizmo_widgets_parent(nullptr) #endif // not ENABLE_IMGUI { - if (m_canvas != nullptr) + if (m_canvas != nullptr) { m_timer.SetOwner(m_canvas); +#if ENABLE_RETINA_GL + m_retina_helper.reset(new RetinaHelper(canvas)); +#endif + } m_selection.set_volumes(&m_volumes.volumes); } @@ -4238,9 +4390,6 @@ void GLCanvas3D::set_viewport_from_scene(const GLCanvas3D& other) m_camera.set_scene_box(other.m_camera.get_scene_box(), *this); m_camera.set_target(other.m_camera.get_target(), *this); m_camera.zoom = other.m_camera.zoom; -#if ENABLE_REWORKED_BED_SHAPE_CHANGE - m_requires_zoom_to_bed = false; -#endif // ENABLE_REWORKED_BED_SHAPE_CHANGE m_dirty = true; } @@ -4519,7 +4668,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } if (printer_technology == ptSLA) { const SLAPrint *sla_print = this->sla_print(); - #ifdef _DEBUG + #ifdef _DEBUG // Verify that the SLAPrint object is synchronized with m_model. check_model_ids_equal(*m_model, sla_print->model()); #endif /* _DEBUG */ @@ -5010,6 +5159,12 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt) void GLCanvas3D::on_mouse(wxMouseEvent& evt) { +#if ENABLE_RETINA_GL + const float scale = m_retina_helper->get_scale_factor(); + evt.SetX(evt.GetX() * scale); + evt.SetY(evt.GetY() * scale); +#endif + #if ENABLE_IMGUI auto imgui = wxGetApp().imgui(); if (imgui->update_mouse_data(evt)) { @@ -5232,7 +5387,13 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) wxGetApp().obj_manipul()->update_settings_value(m_selection); // forces a frame render to update the view before the context menu is shown render(); - post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, pos.cast())); + + Vec2d logical_pos = pos.cast(); +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); +#endif + post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); } } } @@ -5498,7 +5659,15 @@ Size GLCanvas3D::get_canvas_size() const if (m_canvas != nullptr) m_canvas->GetSize(&w, &h); - return Size(w, h); +#if ENABLE_RETINA_GL + const float factor = m_retina_helper->get_scale_factor(); + w *= factor; + h *= factor; +#else + const float factor = 1.0; +#endif + + return Size(w, h, factor); } Point GLCanvas3D::get_local_mouse_position() const @@ -5805,6 +5974,26 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc } } +void GLCanvas3D::update_ui_from_settings() +{ +#if ENABLE_RETINA_GL + const float orig_scaling = m_retina_helper->get_scale_factor(); + + const bool use_retina = wxGetApp().app_config->get("use_retina_opengl") == "1"; + BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Use Retina OpenGL: " << use_retina; + m_retina_helper->set_use_retina(use_retina); + const float new_scaling = m_retina_helper->get_scale_factor(); + + if (new_scaling != orig_scaling) { + BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling; + + m_camera.zoom /= orig_scaling; + m_camera.zoom *= new_scaling; + _refresh_if_shown_on_screen(); + } +#endif +} + bool GLCanvas3D::_is_shown_on_screen() const { return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false; @@ -5958,6 +6147,9 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) #if ENABLE_IMGUI wxGetApp().imgui()->set_display_size((float)w, (float)h); +#if ENABLE_RETINA_GL + wxGetApp().imgui()->set_style_scaling(m_retina_helper->get_scale_factor()); +#endif // ENABLE_RETINA_GL #endif // ENABLE_IMGUI // ensures that this canvas is current @@ -6238,10 +6430,15 @@ void GLCanvas3D::_render_background() const void GLCanvas3D::_render_bed(float theta) const { + float scale_factor = 1.0; +#if ENABLE_RETINA_GL + scale_factor = m_retina_helper->get_scale_factor(); +#endif + #if ENABLE_PRINT_BED_MODELS - m_bed.render(theta, m_use_VBOs); + m_bed.render(theta, m_use_VBOs, scale_factor); #else - m_bed.render(theta); + m_bed.render(theta, scale_factor); #endif // ENABLE_PRINT_BED_MODELS } @@ -6320,8 +6517,13 @@ void GLCanvas3D::_render_objects() const void GLCanvas3D::_render_selection() const { + float scale_factor = 1.0; +#if ENABLE_RETINA_GL + scale_factor = m_retina_helper->get_scale_factor(); +#endif + if (!m_gizmos.is_running()) - m_selection.render(); + m_selection.render(scale_factor); } #if ENABLE_RENDER_SELECTION_CENTER @@ -6404,18 +6606,28 @@ void GLCanvas3D::_render_current_gizmo() const void GLCanvas3D::_render_gizmos_overlay() const { +#if ENABLE_RETINA_GL + m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor()); +#endif m_gizmos.render_overlay(*this, m_selection); } void GLCanvas3D::_render_toolbar() const { +#if ENABLE_RETINA_GL + m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); +#endif m_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const { - if (m_view_toolbar != nullptr) + if (m_view_toolbar != nullptr) { +#if ENABLE_RETINA_GL + m_view_toolbar->set_icons_scale(m_retina_helper->get_scale_factor()); +#endif m_view_toolbar->render(*this); + } } #if ENABLE_SHOW_CAMERA_TARGET @@ -8116,7 +8328,7 @@ void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, void GLCanvas3D::_generate_warning_texture(const std::string& msg) { - m_warning_texture.generate(msg); + m_warning_texture.generate(msg, *this); } void GLCanvas3D::_reset_warning_texture() @@ -8141,6 +8353,10 @@ void GLCanvas3D::_resize_toolbars() const float zoom = get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; +#if ENABLE_RETINA_GL + m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); +#endif + GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); switch (m_toolbar.get_layout_type()) @@ -8184,6 +8400,10 @@ void GLCanvas3D::_resize_toolbars() const if (m_view_toolbar != nullptr) { +#if ENABLE_RETINA_GL + m_view_toolbar->set_icons_scale(m_retina_helper->get_scale_factor()); +#endif + // places the toolbar on the bottom-left corner of the 3d scene float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar->get_height()) * inv_zoom; float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index fb4a465d2..8bdbf89b0 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -2,7 +2,9 @@ #define slic3r_GLCanvas3D_hpp_ #include +#include +#include "libslic3r/Technologies.hpp" #include "3DScene.hpp" #include "GLToolbar.hpp" #include "Event.hpp" @@ -20,6 +22,9 @@ class wxTimerEvent; class wxPaintEvent; class wxGLCanvas; +// Support for Retina OpenGL on Mac OS +#define ENABLE_RETINA_GL __APPLE__ + class GLUquadric; typedef class GLUquadric GLUquadricObj; @@ -36,6 +41,10 @@ namespace GUI { class GLGizmoBase; +#if ENABLE_RETINA_GL +class RetinaHelper; +#endif + class GeometryBuffer { std::vector m_vertices; @@ -55,16 +64,20 @@ class Size { int m_width; int m_height; + float m_scale_factor; public: Size(); - Size(int width, int height); + Size(int width, int height, float scale_factor = 1.0); int get_width() const; void set_width(int width); int get_height() const; void set_height(int height); + + int get_scale_factor() const; + void set_scale_factor(int height); }; class Rect @@ -209,6 +222,8 @@ class GLCanvas3D mutable GLBed m_model; #endif // ENABLE_PRINT_BED_MODELS + mutable float m_scale_factor; + public: Bed(); @@ -224,9 +239,9 @@ class GLCanvas3D Point point_projection(const Point& point) const; #if ENABLE_PRINT_BED_MODELS - void render(float theta, bool useVBOs) const; + void render(float theta, bool useVBOs, float scale_factor) const; #else - void render(float theta) const; + void render(float theta, float scale_factor) const; #endif // ENABLE_PRINT_BED_MODELS private: @@ -297,6 +312,9 @@ class GLCanvas3D }; private: + static const float THICKNESS_BAR_WIDTH; + static const float THICKNESS_RESET_BUTTON_HEIGHT; + bool m_use_legacy_opengl; bool m_enabled; Shader m_shader; @@ -380,6 +398,9 @@ class GLCanvas3D void _render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const; void _render_profile(const Rect& bar_rect) const; void update_slicing_parameters(); + + static float thickness_bar_width(const GLCanvas3D &canvas); + static float reset_button_height(const GLCanvas3D &canvas); }; struct Mouse @@ -536,6 +557,8 @@ public: mutable GLArrow m_arrow; mutable GLCurvedArrow m_curved_arrow; + mutable float m_scale_factor; + public: Selection(); #if ENABLE_RENDER_SELECTION_CENTER @@ -617,7 +640,7 @@ public: void erase(); - void render() const; + void render(float scale_factor = 1.0) const; #if ENABLE_RENDER_SELECTION_CENTER void render_center() const; #endif // ENABLE_RENDER_SELECTION_CENTER @@ -647,7 +670,15 @@ public: void _render_sidebar_rotation_hint(Axis axis) const; void _render_sidebar_scale_hint(Axis axis) const; void _render_sidebar_size_hint(Axis axis, double length) const; - void _synchronize_unselected_instances(bool including_z = false); + enum SyncRotationType { + // Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis. + SYNC_ROTATION_NONE = 0, + // Synchronize fully. Used from "place on bed" feature. + SYNC_ROTATION_FULL = 1, + // Synchronize after rotation by an axis not parallel with Z. + SYNC_ROTATION_GENERAL = 2, + }; + void _synchronize_unselected_instances(SyncRotationType sync_rotation_type); void _synchronize_unselected_volumes(); void _ensure_on_bed(); }; @@ -680,10 +711,6 @@ public: private: class Gizmos { - static const float OverlayIconsScale; - static const float OverlayBorder; - static const float OverlayGapY; - public: enum EType : unsigned char { @@ -704,6 +731,10 @@ private: BackgroundTexture m_background_texture; EType m_current; + float m_overlay_icons_scale; + float m_overlay_border; + float m_overlay_gap_y; + public: Gizmos(); ~Gizmos(); @@ -713,6 +744,8 @@ private: bool is_enabled() const; void set_enabled(bool enable); + void set_overlay_scale(float scale); + std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); void update_on_off_state(const Selection& selection); @@ -802,7 +835,7 @@ private: public: WarningTexture(); - bool generate(const std::string& msg); + bool generate(const std::string& msg, const GLCanvas3D& canvas); void render(const GLCanvas3D& canvas) const; }; @@ -832,6 +865,9 @@ private: wxGLCanvas* m_canvas; wxGLContext* m_context; +#if ENABLE_RETINA_GL + std::unique_ptr m_retina_helper; +#endif bool m_in_render; LegendTexture m_legend_texture; WarningTexture m_warning_texture; @@ -1030,6 +1066,8 @@ public: void handle_sidebar_focus_event(const std::string& opt_key, bool focus_on); + void update_ui_from_settings(); + private: bool _is_shown_on_screen() const; #if !ENABLE_REWORKED_BED_SHAPE_CHANGE diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index cb9a5abc9..1660976a1 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -1428,6 +1428,7 @@ void GLGizmoFlatten::on_start_dragging(const GLCanvas3D::Selection& selection) { if (m_hover_id != -1) { + assert(m_planes_valid); m_normal = m_planes[m_hover_id].normal; m_starting_center = selection.get_bounding_box().center(); } @@ -1446,6 +1447,8 @@ void GLGizmoFlatten::on_render(const GLCanvas3D::Selection& selection) const ::glPushMatrix(); ::glMultMatrixd(m.data()); ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); + if (this->is_plane_update_necessary()) + const_cast(this)->update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { if (i == m_hover_id) @@ -1478,6 +1481,8 @@ void GLGizmoFlatten::on_render_for_picking(const GLCanvas3D::Selection& selectio ::glPushMatrix(); ::glMultMatrixd(m.data()); ::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z()); + if (this->is_plane_update_necessary()) + const_cast(this)->update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { ::glColor3f(1.0f, 1.0f, picking_color_component(i)); @@ -1497,11 +1502,11 @@ void GLGizmoFlatten::on_render_for_picking(const GLCanvas3D::Selection& selectio void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) { m_starting_center = Vec3d::Zero(); - bool object_changed = m_model_object != model_object; + if (m_model_object != model_object) { + m_planes.clear(); + m_planes_valid = false; + } m_model_object = model_object; - - if (model_object && (object_changed || is_plane_update_necessary())) - update_planes(); } void GLGizmoFlatten::update_planes() @@ -1701,6 +1706,8 @@ void GLGizmoFlatten::update_planes() } m_first_instance_scale = m_model_object->instances.front()->get_scaling_factor(); m_first_instance_mirror = m_model_object->instances.front()->get_mirror(); + + m_planes_valid = true; } @@ -1709,7 +1716,7 @@ bool GLGizmoFlatten::is_plane_update_necessary() const if (m_state != On || !m_model_object || m_model_object->instances.empty()) return false; - if (m_model_object->volumes.size() != m_volumes_matrices.size()) + if (! m_planes_valid || m_model_object->volumes.size() != m_volumes_matrices.size()) return true; // We want to recalculate when the scale changes - some planes could (dis)appear. diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp index 02b637a35..7a55a2392 100644 --- a/src/slic3r/GUI/GLGizmo.hpp +++ b/src/slic3r/GUI/GLGizmo.hpp @@ -408,6 +408,7 @@ private: Vec3d m_first_instance_mirror; std::vector m_planes; + bool m_planes_valid = false; mutable Vec3d m_starting_center; const ModelObject* m_model_object = nullptr; std::vector instances_matrices; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 8a9c12f26..b15048ec6 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -468,12 +468,12 @@ float GLToolbar::get_width_horizontal() const float GLToolbar::get_width_vertical() const { - return 2.0f * m_layout.border + m_icons_texture.metadata.icon_size * m_layout.icons_scale; + return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; } float GLToolbar::get_height_horizontal() const { - return 2.0f * m_layout.border + m_icons_texture.metadata.icon_size * m_layout.icons_scale; + return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; } float GLToolbar::get_height_vertical() const @@ -483,33 +483,36 @@ float GLToolbar::get_height_vertical() const float GLToolbar::get_main_size() const { - float size = 2.0f * m_layout.border; + float size = 2.0f * m_layout.border * m_layout.icons_scale; for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) { if (m_items[i]->is_separator()) - size += m_layout.separator_size; + size += m_layout.separator_size * m_layout.icons_scale; else size += (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale; } if (m_items.size() > 1) - size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; + size += ((float)m_items.size() - 1.0f) * m_layout.gap_size * m_layout.icons_scale; return size; } std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) { + // NB: mouse_pos is already scaled appropriately + float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float factor = m_layout.icons_scale * inv_zoom; Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; - float scaled_separator_size = m_layout.separator_size * inv_zoom; - float scaled_gap_size = m_layout.gap_size * inv_zoom; - float scaled_border = m_layout.border * inv_zoom; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; + float scaled_separator_size = m_layout.separator_size * factor; + float scaled_gap_size = m_layout.gap_size * factor; + float scaled_border = m_layout.border * factor; float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; @@ -591,16 +594,19 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent) { + // NB: mouse_pos is already scaled appropriately + float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float factor = m_layout.icons_scale * inv_zoom; Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; - float scaled_separator_size = m_layout.separator_size * inv_zoom; - float scaled_gap_size = m_layout.gap_size * inv_zoom; - float scaled_border = m_layout.border * inv_zoom; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; + float scaled_separator_size = m_layout.separator_size * factor; + float scaled_gap_size = m_layout.gap_size * factor; + float scaled_border = m_layout.border * factor; float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; @@ -682,16 +688,19 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const { + // NB: mouse_pos is already scaled appropriately + float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float factor = m_layout.icons_scale * inv_zoom; Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; - float scaled_separator_size = m_layout.separator_size * inv_zoom; - float scaled_gap_size = m_layout.gap_size * inv_zoom; - float scaled_border = m_layout.border * inv_zoom; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; + float scaled_separator_size = m_layout.separator_size * factor; + float scaled_gap_size = m_layout.gap_size * factor; + float scaled_border = m_layout.border * factor; float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; @@ -724,16 +733,19 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const { + // NB: mouse_pos is already scaled appropriately + float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float factor = m_layout.icons_scale * inv_zoom; Size cnv_size = parent.get_canvas_size(); Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; - float scaled_separator_size = m_layout.separator_size * inv_zoom; - float scaled_gap_size = m_layout.gap_size * inv_zoom; - float scaled_border = m_layout.border * inv_zoom; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; + float scaled_separator_size = m_layout.separator_size * factor; + float scaled_gap_size = m_layout.gap_size * factor; + float scaled_border = m_layout.border * factor; float separator_stride = scaled_separator_size + scaled_gap_size; float icon_stride = scaled_icons_size + scaled_gap_size; @@ -774,11 +786,12 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float factor = inv_zoom * m_layout.icons_scale; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; - float scaled_separator_size = m_layout.separator_size * inv_zoom; - float scaled_gap_size = m_layout.gap_size * inv_zoom; - float scaled_border = m_layout.border * inv_zoom; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; + float scaled_separator_size = m_layout.separator_size * factor; + float scaled_gap_size = m_layout.gap_size * factor; + float scaled_border = m_layout.border * factor; float scaled_width = get_width() * inv_zoom; float scaled_height = get_height() * inv_zoom; @@ -899,11 +912,12 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const float zoom = parent.get_camera_zoom(); float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float factor = inv_zoom * m_layout.icons_scale; - float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * inv_zoom; - float scaled_separator_size = m_layout.separator_size * inv_zoom; - float scaled_gap_size = m_layout.gap_size * inv_zoom; - float scaled_border = m_layout.border * inv_zoom; + float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * factor; + float scaled_separator_size = m_layout.separator_size * factor; + float scaled_gap_size = m_layout.gap_size * factor; + float scaled_border = m_layout.border * factor; float scaled_width = get_width() * inv_zoom; float scaled_height = get_height() * inv_zoom; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index c22219fea..9991d98ea 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -270,16 +270,11 @@ void GUI_App::set_label_clr_sys(const wxColour& clr) { void GUI_App::recreate_GUI() { - std::cerr << "recreate_GUI" << std::endl; + // to make sure nobody accesses data from the soon-to-be-destroyed widgets: + tabs_list.clear(); + plater_ = nullptr; - clear_tabs_list(); - if (plater_) { - // before creating a new plater let's delete old one - plater_->Destroy(); - plater_ = nullptr; - } - - MainFrame* topwindow = dynamic_cast(GetTopWindow()); + MainFrame* topwindow = mainframe; mainframe = new MainFrame(); sidebar().obj_list()->init_objects(); // propagate model objects to object list @@ -691,15 +686,6 @@ void GUI_App::load_current_presets() } } -void GUI_App::clear_tabs_list() -{ - for (auto tab : tabs_list) { - tab->Destroy(); - tab = nullptr; - } - tabs_list.clear(); -} - #ifdef __APPLE__ // wxWidgets override to get an event on open files. void GUI_App::MacOpenFiles(const wxArrayString &fileNames) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index a5b96598f..79da4531d 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -134,7 +134,6 @@ public: bool check_unsaved_changes(); bool checked_tab(Tab* tab); void load_current_presets(); - void clear_tabs_list(); #ifdef __APPLE__ // wxWidgets override to get an event on open files. diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 24e864498..00b074f7d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -626,7 +626,8 @@ const std::vector& ObjectList::get_options_for_bundle(const wxStrin } #endif - return std::vector {}; + static std::vector empty; + return empty; } void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part) @@ -1370,15 +1371,12 @@ bool ObjectList::is_splittable() if (!get_volume_by_item(item, volume) || !volume) return false; - if (volume->is_splittable() != -1) // if is_splittable value is already known - return volume->is_splittable() == 0 ? false : true; - - TriangleMeshPtrs meshptrs = volume->mesh.split(); - bool splittable = meshptrs.size() > 1; - for (TriangleMesh* m : meshptrs) { delete m; } - - volume->set_splittable(splittable ? 1 : 0); - return splittable; + int splittable = volume->is_splittable(); + if (splittable == -1) { + splittable = (int)volume->mesh.has_multiple_patches(); + volume->set_splittable(splittable); + } + return splittable != 0; } bool ObjectList::selected_instances_of_same_object() diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 363294ce8..5157dc9c5 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -267,7 +267,7 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele bool changed_box = false; if (!m_cache.instance.matches_object(obj_idx)) { - m_cache.instance.set(obj_idx, instance_idx, (*wxGetApp().model_objects())[obj_idx]->raw_mesh().bounding_box().size()); + m_cache.instance.set(obj_idx, instance_idx, (*wxGetApp().model_objects())[obj_idx]->raw_mesh_bounding_box().size()); changed_box = true; } if (changed_box || !m_cache.instance.matches_instance(instance_idx) || !m_cache.scale.isApprox(100.0 * m_new_scale)) @@ -278,7 +278,7 @@ void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& sele m_new_size = Vec3d::Zero(); #else if ((0 <= obj_idx) && (obj_idx < (int)wxGetApp().model_objects()->size())) - m_new_size = volume->get_instance_transformation().get_matrix(true, true) * (*wxGetApp().model_objects())[obj_idx]->raw_mesh().bounding_box().size(); + m_new_size = volume->get_instance_transformation().get_matrix(true, true) * (*wxGetApp().model_objects())[obj_idx]->raw_mesh_bounding_box().size(); else // this should never happen m_new_size = Vec3d::Zero(); diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index ccff885f2..d4410c589 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -109,6 +109,7 @@ public: virtual ~Preview(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } + GLCanvas3D* get_canvas3d() { return m_canvas; } void set_view_toolbar(GLToolbar* toolbar); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 7ce34d4e2..e36a68eda 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include @@ -24,6 +26,7 @@ namespace GUI { ImGuiWrapper::ImGuiWrapper() : m_font_texture(0) + , m_style_scaling(1.0) , m_mouse_buttons(0) , m_disabled(false) { @@ -39,18 +42,9 @@ bool ImGuiWrapper::init() { ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), 18.0f); - if (font == nullptr) { - font = io.Fonts->AddFontDefault(); - if (font == nullptr) - return false; - } - else { - m_fonts.insert(FontsMap::value_type("Noto Sans Regular 18", font)); - } + init_default_font(m_style_scaling); - io.IniFilename = nullptr; + ImGui::GetIO().IniFilename = nullptr; return true; } @@ -62,6 +56,15 @@ void ImGuiWrapper::set_display_size(float w, float h) io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); } +void ImGuiWrapper::set_style_scaling(float scaling) +{ + if (!std::isnan(scaling) && !std::isinf(scaling) && scaling != m_style_scaling) { + ImGui::GetStyle().ScaleAllSizes(scaling / m_style_scaling); + init_default_font(scaling); + m_style_scaling = scaling; + } +} + bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) { ImGuiIO& io = ImGui::GetIO(); @@ -93,6 +96,7 @@ void ImGuiWrapper::render() void ImGuiWrapper::set_next_window_pos(float x, float y, int flag) { ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag); + ImGui::SetNextWindowSize(ImVec2(0.0, 0.0)); } void ImGuiWrapper::set_next_window_bg_alpha(float alpha) @@ -198,6 +202,23 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } +void ImGuiWrapper::init_default_font(float scaling) +{ + static const float font_size = 18.0f; + + destroy_fonts_texture(); + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size * scaling); + if (font == nullptr) { + font = io.Fonts->AddFontDefault(); + if (font == nullptr) { + throw std::runtime_error("ImGui: Could not load deafult font"); + } + } +} + void ImGuiWrapper::create_device_objects() { create_fonts_texture(); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 5293bee26..47a1fb937 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -21,6 +21,7 @@ class ImGuiWrapper FontsMap m_fonts; unsigned m_font_texture; + float m_style_scaling; unsigned m_mouse_buttons; bool m_disabled; @@ -32,6 +33,7 @@ public: void read_glsl_version(); void set_display_size(float w, float h); + void set_style_scaling(float scaling); bool update_mouse_data(wxMouseEvent &evt); void new_frame(); @@ -58,6 +60,7 @@ public: bool want_text_input() const; bool want_any_input() const; private: + void init_default_font(float scaling); void create_device_objects(); void create_fonts_texture(); void render_draw_data(ImDrawData *draw_data); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index d47c7f4e8..2df3429fe 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -95,14 +95,10 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL _3DScene::remove_all_canvases(); // Slic3r::GUI::deregister_on_request_update_callback(); - // destroy and set to null tabs and a platter + // set to null tabs and a platter // to avoid any manipulations with them from App->wxEVT_IDLE after of the mainframe closing - wxGetApp().clear_tabs_list(); - if (wxGetApp().plater_) { - // before creating a new plater let's delete old one - wxGetApp().plater_->Destroy(); - wxGetApp().plater_ = nullptr; - } + wxGetApp().tabs_list.clear(); + wxGetApp().plater_ = nullptr; // propagate event event.Skip(); @@ -113,6 +109,7 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL update_ui_from_settings(); // FIXME (?) } + void MainFrame::init_tabpanel() { m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2a80844e3..42e404106 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1272,6 +1272,11 @@ void Plater::priv::update_ui_from_settings() // $self->{buttons_sizer}->Show($self->{btn_reslice}, ! wxTheApp->{app_config}->get("background_processing")); // $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() @@ -1463,29 +1468,40 @@ std::vector Plater::priv::load_files(const std::vector& input_ return obj_idxs; } +// #define AUTOPLACEMENT_ON_LOAD + std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects) { const BoundingBoxf bed_shape = bed_shape_bb(); const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0) - 2.0 * Vec3d::Ones(); +#ifndef AUTOPLACEMENT_ON_LOAD bool need_arrange = false; +#endif /* AUTOPLACEMENT_ON_LOAD */ bool scaled_down = false; std::vector obj_idxs; unsigned int obj_count = model.objects.size(); +#ifdef AUTOPLACEMENT_ON_LOAD + ModelInstancePtrs new_instances; +#endif /* AUTOPLACEMENT_ON_LOAD */ for (ModelObject *model_object : model_objects) { auto *object = model.add_object(*model_object); std::string object_name = object->name.empty() ? fs::path(object->input_file).filename().string() : object->name; obj_idxs.push_back(obj_count++); if (model_object->instances.empty()) { - // if object has no defined position(s) we need to rearrange everything after loading - need_arrange = true; - - // add a default instance and center object around origin - object->center_around_origin(); // also aligns object to Z = 0 - ModelInstance* instance = object->add_instance(); +#ifdef AUTOPLACEMENT_ON_LOAD + object->center_around_origin(); + new_instances.emplace_back(object->add_instance()); +#else /* AUTOPLACEMENT_ON_LOAD */ + // if object has no defined position(s) we need to rearrange everything after loading object->center_around_origin(); + need_arrange = true; + // add a default instance and center object around origin + object->center_around_origin(); // also aligns object to Z = 0 + ModelInstance* instance = object->add_instance(); instance->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -object->origin_translation(2))); +#endif /* AUTOPLACEMENT_ON_LOAD */ } const Vec3d size = object->bounding_box().size(); @@ -1513,6 +1529,18 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode // print.add_model_object(object); } +#ifdef AUTOPLACEMENT_ON_LOAD + // FIXME distance should be a config value ///////////////////////////////// + auto min_obj_distance = static_cast(6/SCALING_FACTOR); + const auto *bed_shape_opt = config->opt("bed_shape"); + assert(bed_shape_opt); + auto& bedpoints = bed_shape_opt->values; + Polyline bed; bed.points.reserve(bedpoints.size()); + for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + + arr::find_new_position(model, new_instances, min_obj_distance, bed); +#endif /* AUTOPLACEMENT_ON_LOAD */ + if (scaled_down) { GUI::show_info(q, _(L("Your object appears to be too large, so it was automatically scaled down to fit your print bed.")), @@ -2094,7 +2122,7 @@ void Plater::priv::fix_through_netfabb(const int obj_idx) o->clear_instances(); for (auto instance: model_object->instances) o->add_instance(*instance); - // o->invalidate_bounding_box(); + o->invalidate_bounding_box(); if (o->volumes.size() == model_object->volumes.size()) { for (int i = 0; i < o->volumes.size(); i++) { diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 5f5da8bd4..b58ce5900 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -87,6 +87,7 @@ 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, " @@ -96,6 +97,16 @@ void PreferencesDialog::build() 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; + def.tooltip = L("If enabled, the 3D scene will be rendered in Retina resolution. " + "If you are experiencing 3D performance problems, disabling this option may help."); + def.default_value = new ConfigOptionBool{ app_config->get("use_retina_opengl") == "1" }; + option = Option (def, "use_retina_opengl"); + m_optgroup->append_single_option_line(option); +#endif + auto sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); @@ -110,8 +121,8 @@ 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() || + m_values.find("use_legacy_opengl") != m_values.end()) { warning_catcher(this, _(L("You need to restart Slic3r to make the changes effective."))); } diff --git a/src/slic3r/Utils/RetinaHelper.hpp b/src/slic3r/Utils/RetinaHelper.hpp new file mode 100644 index 000000000..659bc7f56 --- /dev/null +++ b/src/slic3r/Utils/RetinaHelper.hpp @@ -0,0 +1,29 @@ +#ifndef slic3r_RetinaHelper_hpp_ +#define slic3r_RetinaHelper_hpp_ + +class wxWindow; + + +namespace Slic3r { +namespace GUI { + +class RetinaHelper +{ +public: + RetinaHelper(wxWindow* window); + ~RetinaHelper(); + + void set_use_retina(bool value); + bool get_use_retina(); + float get_scale_factor(); + +private: + wxWindow* m_window; + void* m_self; +}; + + +} // namespace GUI +} // namespace Slic3r + +#endif // RetinaHelper_h diff --git a/src/slic3r/Utils/RetinaHelperImpl.hmm b/src/slic3r/Utils/RetinaHelperImpl.hmm new file mode 100644 index 000000000..0edde2990 --- /dev/null +++ b/src/slic3r/Utils/RetinaHelperImpl.hmm @@ -0,0 +1,15 @@ +#import + +class wxEvtHandler; + +@interface RetinaHelperImpl : NSObject +{ + NSView *view; + wxEvtHandler* handler; +} + +-(id)initWithView:(NSView *)view handler:(wxEvtHandler *)handler; +-(void)setViewWantsBestResolutionOpenGLSurface:(BOOL)value; +-(BOOL)getViewWantsBestResolutionOpenGLSurface; +-(float)getBackingScaleFactor; +@end diff --git a/src/slic3r/Utils/RetinaHelperImpl.mm b/src/slic3r/Utils/RetinaHelperImpl.mm new file mode 100644 index 000000000..de0402d34 --- /dev/null +++ b/src/slic3r/Utils/RetinaHelperImpl.mm @@ -0,0 +1,111 @@ +// The RetinaHelper was originally written by Andreas Stahl, 2013 + +#import "RetinaHelper.hpp" +#import "RetinaHelperImpl.hmm" +#import + +#import "wx/window.h" + +@implementation RetinaHelperImpl + +namespace Slic3r { +namespace GUI { + +RetinaHelper::RetinaHelper(wxWindow* window) : + m_window(window) +{ + m_self = nullptr; + m_self = [[RetinaHelperImpl alloc] initWithView:window->GetHandle() handler:window->GetEventHandler()]; +} + +RetinaHelper::~RetinaHelper() +{ + [m_self release]; +} + +void RetinaHelper::set_use_retina(bool aValue) +{ + [(id)m_self setViewWantsBestResolutionOpenGLSurface:aValue]; +} + +bool RetinaHelper::get_use_retina() +{ + return [(id)m_self getViewWantsBestResolutionOpenGLSurface]; +} + +float RetinaHelper::get_scale_factor() +{ + return [(id)m_self getViewWantsBestResolutionOpenGLSurface] ? [(id)m_self getBackingScaleFactor] : 1.0f; +} + +} // namespace GUI +} // namespace Slic3r + + +-(id)initWithView:(NSView *)aView handler:(wxEvtHandler *)aHandler +{ + self = [super init]; + if (self) { + handler = aHandler; + view = aView; + // register for backing change notifications + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + if (nc) { + [nc addObserver:self selector:@selector(windowDidChangeBackingProperties:) + name:NSWindowDidChangeBackingPropertiesNotification object:nil]; + } + } + return self; +} + +-(void) dealloc +{ + // unregister from all notifications + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + if (nc) { + [nc removeObserver:self]; + } + [super dealloc]; +} + +-(void)setViewWantsBestResolutionOpenGLSurface:(BOOL)value +{ + [view setWantsBestResolutionOpenGLSurface:value]; +} + +-(BOOL)getViewWantsBestResolutionOpenGLSurface +{ + return [view wantsBestResolutionOpenGLSurface]; +} + +-(float)getBackingScaleFactor +{ + return [[view window] backingScaleFactor]; +} + +- (void)windowDidChangeBackingProperties:(NSNotification *)notification +{ + NSWindow *theWindow = (NSWindow *)[notification object]; + + if (theWindow == [view window]) { + CGFloat newBackingScaleFactor = [theWindow backingScaleFactor]; + CGFloat oldBackingScaleFactor = [[[notification userInfo] + objectForKey:@"NSBackingPropertyOldScaleFactorKey"] + doubleValue]; + + if (newBackingScaleFactor != oldBackingScaleFactor) { + // generate a wx resize event and pass it to the handler's queue + wxSizeEvent *event = new wxSizeEvent(); + // use the following line if this resize event should have the physical pixel resolution + // but that is not recommended, because ordinary resize events won't do so either + // which would necessitate a case-by-case switch in the resize handler method. + // NSRect nsrect = [view convertRectToBacking:[view bounds]]; + NSRect nsrect = [view bounds]; + wxRect rect = wxRect(nsrect.origin.x, nsrect.origin.y, nsrect.size.width, nsrect.size.height); + event->SetRect(rect); + event->SetSize(rect.GetSize()); + handler->QueueEvent(event); + } + } +} +@end