Merge branch 'master' into time_estimate

This commit is contained in:
bubnikv 2018-08-03 23:04:44 +02:00
commit ac2b20b54b
60 changed files with 2959 additions and 1548 deletions

View file

@ -136,6 +136,7 @@ add_library(libslic3r STATIC
${LIBDIR}/libslic3r/Line.hpp
${LIBDIR}/libslic3r/Model.cpp
${LIBDIR}/libslic3r/Model.hpp
${LIBDIR}/libslic3r/ModelArrange.hpp
${LIBDIR}/libslic3r/MotionPlanner.cpp
${LIBDIR}/libslic3r/MotionPlanner.hpp
${LIBDIR}/libslic3r/MultiPoint.cpp
@ -729,6 +730,7 @@ set(LIBNEST2D_UNITTESTS ON CACHE BOOL "Force generating unittests for libnest2d"
add_subdirectory(${LIBDIR}/libnest2d)
target_include_directories(libslic3r PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
target_include_directories(libslic3r_gui PUBLIC BEFORE ${LIBNEST2D_INCLUDES})
message(STATUS "Libnest2D Libraries: ${LIBNEST2D_LIBRARIES}")
target_link_libraries(libslic3r ${LIBNEST2D_LIBRARIES})

View file

@ -35,8 +35,9 @@ struct AvrDude::priv
{
std::string sys_config;
std::deque<std::vector<std::string>> args;
size_t current_args_set = 0;
bool cancelled = false;
int exit_code = 0;
size_t current_args_set = 0;
RunFn run_fn;
MessageFn message_fn;
ProgressFn progress_fn;
@ -146,15 +147,15 @@ AvrDude::Ptr AvrDude::run()
int res = -1;
if (self->p->run_fn) {
self->p->run_fn(*self);
self->p->run_fn();
}
if (! self->p->cancelled) {
res = self->p->run();
self->p->exit_code = self->p->run();
}
if (self->p->complete_fn) {
self->p->complete_fn(res, self->p->current_args_set);
self->p->complete_fn();
}
});
@ -179,5 +180,20 @@ void AvrDude::join()
}
}
bool AvrDude::cancelled()
{
return p ? p->cancelled : false;
}
int AvrDude::exit_code()
{
return p ? p->exit_code : 0;
}
size_t AvrDude::last_args_set()
{
return p ? p->current_args_set : 0;
}
}

View file

@ -12,10 +12,10 @@ class AvrDude
{
public:
typedef std::shared_ptr<AvrDude> Ptr;
typedef std::function<void(AvrDude&)> RunFn;
typedef std::function<void()> RunFn;
typedef std::function<void(const char * /* msg */, unsigned /* size */)> MessageFn;
typedef std::function<void(const char * /* task */, unsigned /* progress */)> ProgressFn;
typedef std::function<void(int /* exit status */, size_t /* args_id */)> CompleteFn;
typedef std::function<void()> CompleteFn;
// Main c-tor, sys_config is the location of avrdude's main configuration file
AvrDude(std::string sys_config);
@ -54,6 +54,10 @@ public:
void cancel();
void join();
bool cancelled(); // Whether avrdude run was cancelled
int exit_code(); // The exit code of the last invocation
size_t last_args_set(); // Index of the last argument set that was processsed
private:
struct priv;
std::unique_ptr<priv> p;

View file

@ -2,8 +2,6 @@ cmake_minimum_required(VERSION 2.8)
project(Libnest2D)
enable_testing()
if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
# Update if necessary
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long ")
@ -32,6 +30,7 @@ set(LIBNEST2D_SRCFILES
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/geometry_traits.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/common.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/metaloop.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/placer_boilerplate.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/bottomleftplacer.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/placers/nfpplacer.hpp
@ -60,8 +59,7 @@ if(LIBNEST2D_GEOMETRIES_BACKEND STREQUAL "clipper")
include_directories(BEFORE ${CLIPPER_INCLUDE_DIRS})
include_directories(${Boost_INCLUDE_DIRS})
list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp
list(APPEND LIBNEST2D_SRCFILES ${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/clipper_backend/clipper_backend.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/boost_alg.hpp)
list(APPEND LIBNEST2D_LIBRARIES ${CLIPPER_LIBRARIES})
list(APPEND LIBNEST2D_HEADERS ${CLIPPER_INCLUDE_DIRS}
@ -81,22 +79,12 @@ if(LIBNEST2D_OPTIMIZER_BACKEND STREQUAL "nlopt")
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/subplex.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/genetic.hpp
${CMAKE_CURRENT_SOURCE_DIR}/libnest2d/optimizers/nlopt_boilerplate.hpp)
list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS}
# Threads::Threads
)
list(APPEND LIBNEST2D_LIBRARIES ${NLopt_LIBS})
list(APPEND LIBNEST2D_HEADERS ${NLopt_INCLUDE_DIR})
endif()
# Currently we are outsourcing the non-convex NFP implementation from
# libnfporb and it needs libgmp to work
#find_package(GMP)
#if(GMP_FOUND)
# list(APPEND LIBNEST2D_LIBRARIES ${GMP_LIBRARIES})
# list(APPEND LIBNEST2D_HEADERS ${GMP_INCLUDE_DIR})
# add_definitions(-DLIBNFP_USE_RATIONAL)
#endif()
if(LIBNEST2D_UNITTESTS)
enable_testing()
add_subdirectory(tests)
endif()

View file

@ -27,5 +27,6 @@ set(NLOPT_LINK_PYTHON OFF CACHE BOOL "" FORCE)
add_subdirectory(${nlopt_SOURCE_DIR} ${nlopt_BINARY_DIR})
set(NLopt_LIBS nlopt)
set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR})
set(NLopt_INCLUDE_DIR ${nlopt_BINARY_DIR}
${nlopt_BINARY_DIR}/src/api)
set(SHARED_LIBS_STATE ${SHARED_STATE})

View file

@ -1,35 +0,0 @@
# Try to find the GMP libraries:
# GMP_FOUND - System has GMP lib
# GMP_INCLUDE_DIR - The GMP include directory
# GMP_LIBRARIES - Libraries needed to use GMP
if (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
# Force search at every time, in case configuration changes
unset(GMP_INCLUDE_DIR CACHE)
unset(GMP_LIBRARIES CACHE)
endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES)
find_path(GMP_INCLUDE_DIR NAMES gmp.h)
if(WIN32)
find_library(GMP_LIBRARIES NAMES libgmp.a gmp gmp.lib mpir mpir.lib)
else(WIN32)
if(STBIN)
message(STATUS "STBIN: ${STBIN}")
find_library(GMP_LIBRARIES NAMES libgmp.a gmp)
else(STBIN)
find_library(GMP_LIBRARIES NAMES libgmp.so gmp)
endif(STBIN)
endif(WIN32)
if(GMP_INCLUDE_DIR AND GMP_LIBRARIES)
set(GMP_FOUND TRUE)
endif(GMP_INCLUDE_DIR AND GMP_LIBRARIES)
if(GMP_FOUND)
message(STATUS "Configured GMP: ${GMP_LIBRARIES}")
else(GMP_FOUND)
message(STATUS "Could NOT find GMP")
endif(GMP_FOUND)
mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES)

View file

@ -535,19 +535,34 @@ void arrangeRectangles() {
proba[0].rotate(Pi/3);
proba[1].rotate(Pi-Pi/3);
// std::vector<Item> input(25, Rectangle(70*SCALE, 10*SCALE));
std::vector<Item> input;
input.insert(input.end(), prusaParts().begin(), prusaParts().end());
// input.insert(input.end(), prusaExParts().begin(), prusaExParts().end());
input.insert(input.end(), stegoParts().begin(), stegoParts().end());
// input.insert(input.end(), stegoParts().begin(), stegoParts().end());
// input.insert(input.end(), rects.begin(), rects.end());
input.insert(input.end(), proba.begin(), proba.end());
// input.insert(input.end(), proba.begin(), proba.end());
// input.insert(input.end(), crasher.begin(), crasher.end());
Box bin(250*SCALE, 210*SCALE);
// PolygonImpl bin = {
// {
// {25*SCALE, 0},
// {0, 25*SCALE},
// {0, 225*SCALE},
// {25*SCALE, 250*SCALE},
// {225*SCALE, 250*SCALE},
// {250*SCALE, 225*SCALE},
// {250*SCALE, 25*SCALE},
// {225*SCALE, 0},
// {25*SCALE, 0}
// },
// {}
// };
Coord min_obj_distance = 6*SCALE;
auto min_obj_distance = static_cast<Coord>(0*SCALE);
using Placer = NfpPlacer;
using Placer = strategies::_NofitPolyPlacer<PolygonImpl, Box>;
using Packer = Arranger<Placer, FirstFitSelection>;
Packer arrange(bin, min_obj_distance);
@ -556,28 +571,107 @@ void arrangeRectangles() {
pconf.alignment = Placer::Config::Alignment::CENTER;
pconf.starting_point = Placer::Config::Alignment::CENTER;
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
pconf.object_function = [&bin](Placer::Pile pile, double area,
double norm, double penality) {
pconf.accuracy = 0.5f;
auto bb = ShapeLike::boundingBox(pile);
// auto bincenter = ShapeLike::boundingBox(bin).center();
// pconf.object_function = [&bin, bincenter](
// Placer::Pile pile, const Item& item,
// double /*area*/, double norm, double penality) {
auto& sh = pile.back();
auto rv = Nfp::referenceVertex(sh);
auto c = bin.center();
auto d = PointLike::distance(rv, c);
double score = double(d)/norm;
// using pl = PointLike;
// If it does not fit into the print bed we will beat it
// with a large penality
if(!NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score;
// static const double BIG_ITEM_TRESHOLD = 0.2;
// static const double GRAVITY_RATIO = 0.5;
// static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
return score;
};
// // We will treat big items (compared to the print bed) differently
// NfpPlacer::Pile bigs;
// bigs.reserve(pile.size());
// for(auto& p : pile) {
// auto pbb = ShapeLike::boundingBox(p);
// auto na = std::sqrt(pbb.width()*pbb.height())/norm;
// if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
// }
// // Candidate item bounding box
// auto ibb = item.boundingBox();
// // Calculate the full bounding box of the pile with the candidate item
// pile.emplace_back(item.transformedShape());
// auto fullbb = ShapeLike::boundingBox(pile);
// pile.pop_back();
// // The bounding box of the big items (they will accumulate in the center
// // of the pile
// auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
// // The size indicator of the candidate item. This is not the area,
// // but almost...
// auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
// // Will hold the resulting score
// double score = 0;
// if(itemnormarea > BIG_ITEM_TRESHOLD) {
// // This branch is for the bigger items..
// // Here we will use the closest point of the item bounding box to
// // the already arranged pile. So not the bb center nor the a choosen
// // corner but whichever is the closest to the center. This will
// // prevent unwanted strange arrangements.
// auto minc = ibb.minCorner(); // bottom left corner
// auto maxc = ibb.maxCorner(); // top right corner
// // top left and bottom right corners
// auto top_left = PointImpl{getX(minc), getY(maxc)};
// auto bottom_right = PointImpl{getX(maxc), getY(minc)};
// auto cc = fullbb.center(); // The gravity center
// // Now the distnce of the gravity center will be calculated to the
// // five anchor points and the smallest will be chosen.
// std::array<double, 5> dists;
// dists[0] = pl::distance(minc, cc);
// dists[1] = pl::distance(maxc, cc);
// dists[2] = pl::distance(ibb.center(), cc);
// dists[3] = pl::distance(top_left, cc);
// dists[4] = pl::distance(bottom_right, cc);
// auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
// // Density is the pack density: how big is the arranged pile
// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
// // The score is a weighted sum of the distance from pile center
// // and the pile size
// score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
// } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
// // If there are no big items, only small, we should consider the
// // density here as well to not get silly results
// auto bindist = pl::distance(ibb.center(), bincenter) / norm;
// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
// score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
// } else {
// // Here there are the small items that should be placed around the
// // already processed bigger items.
// // No need to play around with the anchor points, the center will be
// // just fine for small items
// score = pl::distance(ibb.center(), bigbb.center()) / norm;
// }
// // If it does not fit into the print bed we will beat it
// // with a large penality. If we would not do this, there would be only
// // one big pile that doesn't care whether it fits onto the print bed.
// if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
// return score;
// };
Packer::SelectionConfig sconf;
// sconf.allow_parallel = false;
// sconf.force_parallel = false;
// sconf.try_triplets = false;
// sconf.try_triplets = true;
// sconf.try_reverse_order = true;
// sconf.waste_increment = 0.005;
@ -613,7 +707,7 @@ void arrangeRectangles() {
std::vector<double> eff;
eff.reserve(result.size());
auto bin_area = double(bin.height()*bin.width());
auto bin_area = ShapeLike::area<PolygonImpl>(bin);
for(auto& r : result) {
double a = 0;
std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); });
@ -630,7 +724,7 @@ void arrangeRectangles() {
<< " %" << std::endl;
std::cout << "Bin usage: (";
unsigned total = 0;
size_t total = 0;
for(auto& r : result) { std::cout << r.size() << " "; total += r.size(); }
std::cout << ") Total: " << total << std::endl;
@ -643,10 +737,12 @@ void arrangeRectangles() {
<< input.size() - total << " elements!"
<< std::endl;
svg::SVGWriter::Config conf;
using SVGWriter = svg::SVGWriter<PolygonImpl>;
SVGWriter::Config conf;
conf.mm_in_coord_units = SCALE;
svg::SVGWriter svgw(conf);
svgw.setSize(bin);
SVGWriter svgw(conf);
svgw.setSize(Box(250*SCALE, 210*SCALE));
svgw.writePackGroup(result);
// std::for_each(input.begin(), input.end(), [&svgw](Item& item){ svgw.writeItem(item);});
svgw.save("out");

View file

@ -6,7 +6,7 @@
#include <libnest2d/clipper_backend/clipper_backend.hpp>
// We include the stock optimizers for local and global optimization
#include <libnest2d/optimizers/simplex.hpp> // Local subplex for NfpPlacer
#include <libnest2d/optimizers/subplex.hpp> // Local subplex for NfpPlacer
#include <libnest2d/optimizers/genetic.hpp> // Genetic for min. bounding box
#include <libnest2d/libnest2d.hpp>

View file

@ -8,8 +8,16 @@
#ifdef __clang__
#undef _MSC_EXTENSIONS
#endif
#include <boost/geometry.hpp>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4267)
#endif
#include <boost/geometry.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// this should be removed to not confuse the compiler
// #include <libnest2d.h>
@ -350,7 +358,7 @@ inline double ShapeLike::area(const PolygonImpl& shape)
#endif
template<>
inline bool ShapeLike::isInside(const PointImpl& point,
inline bool ShapeLike::isInside<PolygonImpl>(const PointImpl& point,
const PolygonImpl& shape)
{
return boost::geometry::within(point, shape);
@ -461,15 +469,6 @@ inline bp2d::Shapes Nfp::merge(const bp2d::Shapes& shapes,
}
#endif
//#ifndef DISABLE_BOOST_MINKOWSKI_ADD
//template<>
//inline PolygonImpl& Nfp::minkowskiAdd(PolygonImpl& sh,
// const PolygonImpl& /*other*/)
//{
// return sh;
//}
//#endif
#ifndef DISABLE_BOOST_SERIALIZE
template<> inline std::string ShapeLike::serialize<libnest2d::Formats::SVG>(
const PolygonImpl& sh, double scale)

View file

@ -1,58 +0,0 @@
//#include "clipper_backend.hpp"
//#include <atomic>
//namespace libnest2d {
//namespace {
//class SpinLock {
// std::atomic_flag& lck_;
//public:
// inline SpinLock(std::atomic_flag& flg): lck_(flg) {}
// inline void lock() {
// while(lck_.test_and_set(std::memory_order_acquire)) {}
// }
// inline void unlock() { lck_.clear(std::memory_order_release); }
//};
//class HoleCache {
// friend struct libnest2d::ShapeLike;
// std::unordered_map< const PolygonImpl*, ClipperLib::Paths> map;
// ClipperLib::Paths& _getHoles(const PolygonImpl* p) {
// static std::atomic_flag flg = ATOMIC_FLAG_INIT;
// SpinLock lock(flg);
// lock.lock();
// ClipperLib::Paths& paths = map[p];
// lock.unlock();
// if(paths.size() != p->Childs.size()) {
// paths.reserve(p->Childs.size());
// for(auto np : p->Childs) {
// paths.emplace_back(np->Contour);
// }
// }
// return paths;
// }
// ClipperLib::Paths& getHoles(PolygonImpl& p) {
// return _getHoles(&p);
// }
// const ClipperLib::Paths& getHoles(const PolygonImpl& p) {
// return _getHoles(&p);
// }
//};
//}
//HoleCache holeCache;
//}

View file

@ -21,7 +21,7 @@ struct PolygonImpl {
PathImpl Contour;
HoleStore Holes;
inline PolygonImpl() {}
inline PolygonImpl() = default;
inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {}
inline explicit PolygonImpl(const HoleStore& holes):
@ -66,6 +66,19 @@ inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) {
ret -= p2;
return ret;
}
inline PointImpl& operator *=(PointImpl& p, const PointImpl& pa ) {
p.X *= pa.X;
p.Y *= pa.Y;
return p;
}
inline PointImpl operator*(const PointImpl& p1, const PointImpl& p2) {
PointImpl ret = p1;
ret *= p2;
return ret;
}
}
namespace libnest2d {
@ -135,7 +148,7 @@ inline void ShapeLike::reserve(PolygonImpl& sh, size_t vertex_capacity)
namespace _smartarea {
template<Orientation o>
inline double area(const PolygonImpl& sh) {
inline double area(const PolygonImpl& /*sh*/) {
return std::nan("");
}
@ -220,22 +233,6 @@ inline void ShapeLike::offset(PolygonImpl& sh, TCoord<PointImpl> distance) {
}
}
//template<> // TODO make it support holes if this method will ever be needed.
//inline PolygonImpl Nfp::minkowskiDiff(const PolygonImpl& sh,
// const PolygonImpl& other)
//{
// #define DISABLE_BOOST_MINKOWSKI_ADD
// ClipperLib::Paths solution;
// ClipperLib::MinkowskiDiff(sh.Contour, other.Contour, solution);
// PolygonImpl ret;
// ret.Contour = solution.front();
// return sh;
//}
// Tell libnest2d how to make string out of a ClipperPolygon object
template<> inline std::string ShapeLike::toString(const PolygonImpl& sh) {
std::stringstream ss;
@ -406,35 +403,12 @@ inline void ShapeLike::rotate(PolygonImpl& sh, const Radians& rads)
}
#define DISABLE_BOOST_NFP_MERGE
template<> inline Nfp::Shapes<PolygonImpl>
Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
{
inline Nfp::Shapes<PolygonImpl> _merge(ClipperLib::Clipper& clipper) {
Nfp::Shapes<PolygonImpl> retv;
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
bool closed = true;
bool valid = false;
valid = clipper.AddPath(sh.Contour, ClipperLib::ptSubject, closed);
for(auto& hole : sh.Holes) {
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
}
for(auto& path : shapes) {
valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
for(auto& hole : path.Holes) {
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
}
}
if(!valid) throw GeometryException(GeomErr::MERGE);
ClipperLib::PolyTree result;
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero);
retv.reserve(result.Total());
clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative);
retv.reserve(static_cast<size_t>(result.Total()));
std::function<void(ClipperLib::PolyNode*, PolygonImpl&)> processHole;
@ -445,7 +419,8 @@ Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
retv.push_back(poly);
};
processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) {
processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly)
{
poly.Holes.push_back(pptr->Contour);
poly.Holes.back().push_back(poly.Holes.back().front());
for(auto c : pptr->Childs) processPoly(c);
@ -463,6 +438,27 @@ Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes, const PolygonImpl& sh)
return retv;
}
template<> inline Nfp::Shapes<PolygonImpl>
Nfp::merge(const Nfp::Shapes<PolygonImpl>& shapes)
{
ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
bool closed = true;
bool valid = true;
for(auto& path : shapes) {
valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
for(auto& hole : path.Holes) {
valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed);
}
}
if(!valid) throw GeometryException(GeomErr::MERGE);
return _merge(clipper);
}
}
//#define DISABLE_BOOST_SERIALIZE

View file

@ -13,6 +13,7 @@
#if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L
#define BP2D_NOEXCEPT
#define BP2D_CONSTEXPR
#define BP2D_COMPILER_MSVC12
#elif __cplusplus >= 201103L
#define BP2D_NOEXCEPT noexcept
#define BP2D_CONSTEXPR constexpr
@ -84,44 +85,6 @@ struct invoke_result {
template<class F, class...Args>
using invoke_result_t = typename invoke_result<F, Args...>::type;
/* ************************************************************************** */
/* C++14 std::index_sequence implementation: */
/* ************************************************************************** */
/**
* \brief C++11 conformant implementation of the index_sequence type from C++14
*/
template<size_t...Ints> struct index_sequence {
using value_type = size_t;
BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); }
};
// A Help structure to generate the integer list
template<size_t...Nseq> struct genSeq;
// Recursive template to generate the list
template<size_t I, size_t...Nseq> struct genSeq<I, Nseq...> {
// Type will contain a genSeq with Nseq appended by one element
using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type;
};
// Terminating recursion
template <size_t ... Nseq> struct genSeq<0, Nseq...> {
// If I is zero, Type will contain index_sequence with the fuly generated
// integer list.
using Type = index_sequence<Nseq...>;
};
/// Helper alias to make an index sequence from 0 to N
template<size_t N> using make_index_sequence = typename genSeq<N>::Type;
/// Helper alias to make an index sequence for a parameter pack
template<class...Args>
using index_sequence_for = make_index_sequence<sizeof...(Args)>;
/* ************************************************************************** */
/**
* A useful little tool for triggering static_assert error messages e.g. when
* a mandatory template specialization (implementation) is missing.
@ -229,7 +192,7 @@ public:
GeomErr errcode() const { return errcode_; }
virtual const char * what() const BP2D_NOEXCEPT override {
const char * what() const BP2D_NOEXCEPT override {
return errorstr(errcode_).c_str();
}
};

View file

@ -3,6 +3,7 @@
#include <string>
#include <type_traits>
#include <algorithm>
#include <array>
#include <vector>
#include <numeric>
@ -68,7 +69,7 @@ class _Box: PointPair<RawPoint> {
using PointPair<RawPoint>::p2;
public:
inline _Box() {}
inline _Box() = default;
inline _Box(const RawPoint& p, const RawPoint& pp):
PointPair<RawPoint>({p, pp}) {}
@ -85,6 +86,31 @@ public:
inline TCoord<RawPoint> height() const BP2D_NOEXCEPT;
inline RawPoint center() const BP2D_NOEXCEPT;
inline double area() const BP2D_NOEXCEPT {
return double(width()*height());
}
};
template<class RawPoint>
class _Circle {
RawPoint center_;
double radius_ = 0;
public:
_Circle() = default;
_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 double radius() const BP2D_NOEXCEPT { return radius_; }
inline void radius(double r) { radius_ = r; }
inline double area() const BP2D_NOEXCEPT {
return 2.0*Pi*radius_;
}
};
/**
@ -97,7 +123,7 @@ class _Segment: PointPair<RawPoint> {
mutable Radians angletox_ = std::nan("");
public:
inline _Segment() {}
inline _Segment() = default;
inline _Segment(const RawPoint& p, const RawPoint& pp):
PointPair<RawPoint>({p, pp}) {}
@ -188,7 +214,7 @@ struct PointLike {
if( (y < y1 && y < y2) || (y > y1 && y > y2) )
return {0, false};
else if ((y == y1 && y == y2) && (x > x1 && x > x2))
if ((y == y1 && y == y2) && (x > x1 && x > x2))
ret = std::min( x-x1, x -x2);
else if( (y == y1 && y == y2) && (x < x1 && x < x2))
ret = -std::min(x1 - x, x2 - x);
@ -214,7 +240,7 @@ struct PointLike {
if( (x < x1 && x < x2) || (x > x1 && x > x2) )
return {0, false};
else if ((x == x1 && x == x2) && (y > y1 && y > y2))
if ((x == x1 && x == x2) && (y > y1 && y > y2))
ret = std::min( y-y1, y -y2);
else if( (x == x1 && x == x2) && (y < y1 && y < y2))
ret = -std::min(y1 - y, y2 - y);
@ -329,7 +355,7 @@ enum class Formats {
};
// This struct serves as a namespace. The only difference is that it can be
// used in friend declarations.
// used in friend declarations and can be aliased at class scope.
struct ShapeLike {
template<class RawShape>
@ -361,6 +387,51 @@ struct ShapeLike {
return create<RawShape>(contour, {});
}
template<class RawShape>
static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static const TContour<RawShape>& getHole(const RawShape& sh,
unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static size_t holeCount(const RawShape& sh)
{
return holes(sh).size();
}
template<class RawShape>
static TContour<RawShape>& getContour(RawShape& sh)
{
return sh;
}
template<class RawShape>
static const TContour<RawShape>& getContour(const RawShape& sh)
{
return sh;
}
// Optional, does nothing by default
template<class RawShape>
static void reserve(RawShape& /*sh*/, size_t /*vertex_capacity*/) {}
@ -402,7 +473,7 @@ struct ShapeLike {
}
template<Formats, class RawShape>
static std::string serialize(const RawShape& /*sh*/, double scale=1)
static std::string serialize(const RawShape& /*sh*/, double /*scale*/=1)
{
static_assert(always_false<RawShape>::value,
"ShapeLike::serialize() unimplemented!");
@ -498,51 +569,6 @@ struct ShapeLike {
return RawShape();
}
template<class RawShape>
static THolesContainer<RawShape>& holes(RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static const THolesContainer<RawShape>& holes(const RawShape& /*sh*/)
{
static THolesContainer<RawShape> empty;
return empty;
}
template<class RawShape>
static TContour<RawShape>& getHole(RawShape& sh, unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static const TContour<RawShape>& getHole(const RawShape& sh,
unsigned long idx)
{
return holes(sh)[idx];
}
template<class RawShape>
static size_t holeCount(const RawShape& sh)
{
return holes(sh).size();
}
template<class RawShape>
static TContour<RawShape>& getContour(RawShape& sh)
{
return sh;
}
template<class RawShape>
static const TContour<RawShape>& getContour(const RawShape& sh)
{
return sh;
}
template<class RawShape>
static void rotate(RawShape& /*sh*/, const Radians& /*rads*/)
{
@ -614,6 +640,22 @@ struct ShapeLike {
return box;
}
template<class RawShape>
static inline _Box<TPoint<RawShape>> boundingBox(
const _Circle<TPoint<RawShape>>& circ)
{
using Coord = TCoord<TPoint<RawShape>>;
TPoint<RawShape> pmin = {
static_cast<Coord>(getX(circ.center()) - circ.radius()),
static_cast<Coord>(getY(circ.center()) - circ.radius()) };
TPoint<RawShape> pmax = {
static_cast<Coord>(getX(circ.center()) + circ.radius()),
static_cast<Coord>(getY(circ.center()) + circ.radius()) };
return {pmin, pmax};
}
template<class RawShape>
static inline double area(const _Box<TPoint<RawShape>>& box)
{
@ -621,14 +663,74 @@ struct ShapeLike {
}
template<class RawShape>
static double area(const Shapes<RawShape>& shapes)
static inline double area(const _Circle<TPoint<RawShape>>& circ)
{
double ret = 0;
std::accumulate(shapes.first(), shapes.end(),
[](const RawShape& a, const RawShape& b) {
return area(a) + area(b);
return circ.area();
}
template<class RawShape>
static inline double area(const Shapes<RawShape>& shapes)
{
return std::accumulate(shapes.begin(), shapes.end(), 0.0,
[](double a, const RawShape& b) {
return a += area(b);
});
return ret;
}
template<class RawShape>
static bool isInside(const TPoint<RawShape>& point,
const _Circle<TPoint<RawShape>>& circ)
{
return PointLike::distance(point, circ.center()) < circ.radius();
}
template<class RawShape>
static bool isInside(const TPoint<RawShape>& point,
const _Box<TPoint<RawShape>>& box)
{
auto px = getX(point);
auto py = getY(point);
auto minx = getX(box.minCorner());
auto miny = getY(box.minCorner());
auto maxx = getX(box.maxCorner());
auto maxy = getY(box.maxCorner());
return px > minx && px < maxx && py > miny && py < maxy;
}
template<class RawShape>
static bool isInside(const RawShape& sh,
const _Circle<TPoint<RawShape>>& circ)
{
return std::all_of(cbegin(sh), cend(sh),
[&circ](const TPoint<RawShape>& p){
return isInside<RawShape>(p, circ);
});
}
template<class RawShape>
static bool isInside(const _Box<TPoint<RawShape>>& box,
const _Circle<TPoint<RawShape>>& circ)
{
return isInside<RawShape>(box.minCorner(), circ) &&
isInside<RawShape>(box.maxCorner(), circ);
}
template<class RawShape>
static bool isInside(const _Box<TPoint<RawShape>>& ibb,
const _Box<TPoint<RawShape>>& box)
{
auto iminX = getX(ibb.minCorner());
auto imaxX = getX(ibb.maxCorner());
auto iminY = getY(ibb.minCorner());
auto imaxY = getY(ibb.maxCorner());
auto minX = getX(box.minCorner());
auto maxX = getX(box.maxCorner());
auto minY = getY(box.minCorner());
auto maxY = getY(box.maxCorner());
return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY;
}
template<class RawShape> // Potential O(1) implementation may exist

View file

@ -3,7 +3,9 @@
#include "geometry_traits.hpp"
#include <algorithm>
#include <functional>
#include <vector>
#include <iterator>
namespace libnest2d {
@ -23,64 +25,22 @@ struct Nfp {
template<class RawShape>
using Shapes = typename ShapeLike::Shapes<RawShape>;
/// Minkowski addition (not used yet)
/**
* Merge a bunch of polygons with the specified additional polygon.
*
* \tparam RawShape the Polygon data type.
* \param shc The pile of polygons that will be unified with sh.
* \param sh A single polygon to unify with shc.
*
* \return A set of polygons that is the union of the input polygons. Note that
* mostly it will be a set containing only one big polygon but if the input
* polygons are disjuct than the resulting set will contain more polygons.
*/
template<class RawShape>
static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother)
static Shapes<RawShape> merge(const Shapes<RawShape>& /*shc*/)
{
using Vertex = TPoint<RawShape>;
//using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
using sl = ShapeLike;
using std::signbit;
// Copy the orbiter (controur only), we will have to work on it
RawShape orbiter = sl::create(sl::getContour(cother));
// Make the orbiter reverse oriented
for(auto &v : sl::getContour(orbiter)) v = -v;
// An egde with additional data for marking it
struct MarkedEdge { Edge e; Radians turn_angle; bool is_turning_point; };
// Container for marked edges
using EdgeList = std::vector<MarkedEdge>;
EdgeList A, B;
auto fillEdgeList = [](EdgeList& L, const RawShape& poly) {
L.reserve(sl::contourVertexCount(poly));
auto it = sl::cbegin(poly);
auto nextit = std::next(it);
L.emplace_back({Edge(*it, *nextit), 0, false});
it++; nextit++;
while(nextit != sl::cend(poly)) {
Edge e(*it, *nextit);
auto& L_prev = L.back();
auto phi = L_prev.e.angleToXaxis();
auto phi_prev = e.angleToXaxis();
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= 2*Pi;
L.emplace_back({
e,
turn_angle,
signbit(turn_angle) != signbit(L_prev.turn_angle)
});
it++; nextit++;
}
L.front().turn_angle = L.front().e.angleToXaxis() -
L.back().e.angleToXaxis();
if(L.front().turn_angle > Pi) L.front().turn_angle -= 2*Pi;
};
fillEdgeList(A, sh);
fillEdgeList(B, orbiter);
return sh;
static_assert(always_false<RawShape>::value,
"Nfp::merge(shapes, shape) unimplemented!");
}
/**
@ -95,10 +55,12 @@ static RawShape minkowskiDiff(const RawShape& sh, const RawShape& cother)
* polygons are disjuct than the resulting set will contain more polygons.
*/
template<class RawShape>
static Shapes<RawShape> merge(const Shapes<RawShape>& shc, const RawShape& sh)
static Shapes<RawShape> merge(const Shapes<RawShape>& shc,
const RawShape& sh)
{
static_assert(always_false<RawShape>::value,
"Nfp::merge(shapes, shape) unimplemented!");
auto m = merge(shc);
m.push_back(sh);
return merge(m);
}
/**
@ -139,16 +101,20 @@ template<class RawShape>
static TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
{
// find min x and min y vertex
// find max x and max y vertex
auto it = std::max_element(ShapeLike::cbegin(sh), ShapeLike::cend(sh),
_vsort<RawShape>);
return *it;
}
template<class RawShape>
using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
/// Helper function to get the NFP
template<NfpLevel nfptype, class RawShape>
static RawShape noFitPolygon(const RawShape& sh, const RawShape& other)
static NfpResult<RawShape> noFitPolygon(const RawShape& sh,
const RawShape& other)
{
NfpImpl<RawShape, nfptype> nfp;
return nfp(sh, other);
@ -167,44 +133,46 @@ static RawShape noFitPolygon(const RawShape& sh, const RawShape& other)
* \tparam RawShape the Polygon data type.
* \param sh The stationary polygon
* \param cother The orbiting polygon
* \return Returns the NFP of the two input polygons which have to be strictly
* convex. The resulting NFP is proven to be convex as well in this case.
* \return Returns a pair of the NFP and its reference vertex of the two input
* polygons which have to be strictly convex. The resulting NFP is proven to be
* convex as well in this case.
*
*/
template<class RawShape>
static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
static NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
const RawShape& other)
{
using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>;
RawShape other = cother;
// Make the other polygon counter-clockwise
std::reverse(ShapeLike::begin(other), ShapeLike::end(other));
using sl = ShapeLike;
RawShape rsh; // Final nfp placeholder
Vertex top_nfp;
std::vector<Edge> edgelist;
auto cap = ShapeLike::contourVertexCount(sh) +
ShapeLike::contourVertexCount(other);
auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other);
// Reserve the needed memory
edgelist.reserve(cap);
ShapeLike::reserve(rsh, static_cast<unsigned long>(cap));
sl::reserve(rsh, static_cast<unsigned long>(cap));
{ // place all edges from sh into edgelist
auto first = ShapeLike::cbegin(sh);
auto next = first + 1;
auto endit = ShapeLike::cend(sh);
auto first = sl::cbegin(sh);
auto next = std::next(first);
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
while(next != sl::cend(sh)) {
edgelist.emplace_back(*(first), *(next));
++first; ++next;
}
}
{ // place all edges from other into edgelist
auto first = ShapeLike::cbegin(other);
auto next = first + 1;
auto endit = ShapeLike::cend(other);
auto first = sl::cbegin(other);
auto next = std::next(first);
while(next != endit) edgelist.emplace_back(*(first++), *(next++));
while(next != sl::cend(other)) {
edgelist.emplace_back(*(next), *(first));
++first; ++next;
}
}
// Sort the edges by angle to X axis.
@ -215,10 +183,16 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
});
// Add the two vertices from the first edge into the final polygon.
ShapeLike::addVertex(rsh, edgelist.front().first());
ShapeLike::addVertex(rsh, edgelist.front().second());
sl::addVertex(rsh, edgelist.front().first());
sl::addVertex(rsh, edgelist.front().second());
auto tmp = std::next(ShapeLike::begin(rsh));
// Sorting function for the nfp reference vertex search
auto& cmp = _vsort<RawShape>;
// the reference (rightmost top) vertex so far
top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp );
auto tmp = std::next(sl::begin(rsh));
// Construct final nfp by placing each edge to the end of the previous
for(auto eit = std::next(edgelist.begin());
@ -226,56 +200,325 @@ static RawShape nfpConvexOnly(const RawShape& sh, const RawShape& cother)
++eit)
{
auto d = *tmp - eit->first();
auto p = eit->second() + d;
Vertex p = eit->second() + d;
ShapeLike::addVertex(rsh, p);
sl::addVertex(rsh, p);
// Set the new reference vertex
if(cmp(top_nfp, p)) top_nfp = p;
tmp = std::next(tmp);
}
// Now we have an nfp somewhere in the dark. We need to get it
// to the right position around the stationary shape.
// This is done by choosing the leftmost lowest vertex of the
// orbiting polygon to be touched with the rightmost upper
// vertex of the stationary polygon. In this configuration, the
// reference vertex of the orbiting polygon (which can be dragged around
// the nfp) will be its rightmost upper vertex that coincides with the
// rightmost upper vertex of the nfp. No proof provided other than Jonas
// Lindmark's reasoning about the reference vertex of nfp in his thesis
// ("No fit polygon problem" - section 2.1.9)
return {rsh, top_nfp};
}
// TODO: dont do this here. Cache the rmu and lmd in Item and get translate
// the nfp after this call
template<class RawShape>
static NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
const RawShape& cother)
{
auto csh = sh; // Copy sh, we will sort the verices in the copy
auto& cmp = _vsort<RawShape>;
std::sort(ShapeLike::begin(csh), ShapeLike::end(csh), cmp);
std::sort(ShapeLike::begin(other), ShapeLike::end(other), cmp);
// Algorithms are from the original algorithm proposed in paper:
// https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
// leftmost lower vertex of the stationary polygon
auto& touch_sh = *(std::prev(ShapeLike::end(csh)));
// rightmost upper vertex of the orbiting polygon
auto& touch_other = *(ShapeLike::begin(other));
// /////////////////////////////////////////////////////////////////////////
// Algorithm 1: Obtaining the minkowski sum
// /////////////////////////////////////////////////////////////////////////
// Calculate the difference and move the orbiter to the touch position.
auto dtouch = touch_sh - touch_other;
auto top_other = *(std::prev(ShapeLike::end(other))) + dtouch;
// 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.
// Get the righmost upper vertex of the nfp and move it to the RMU of
// the orbiter because they should coincide.
auto&& top_nfp = rightmostUpVertex(rsh);
auto dnfp = top_other - top_nfp;
std::for_each(ShapeLike::begin(rsh), ShapeLike::end(rsh),
[&dnfp](Vertex& v) { v+= dnfp; } );
using Result = NfpResult<RawShape>;
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
using Edge = _Segment<Vertex>;
using sl = ShapeLike;
using std::signbit;
using std::sort;
using std::vector;
using std::ref;
using std::reference_wrapper;
return rsh;
// 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::getContour(cstationary);
std::reverse(stcont.begin(), stcont.end());
RawShape stationary;
sl::getContour(stationary) = stcont;
// Reverse the orbiter contour to counter clockwise
auto orbcont = sl::getContour(cother);
std::reverse(orbcont.begin(), orbcont.end());
// Copy the orbiter (contour only), we will have to work on it
RawShape orbiter;
sl::getContour(orbiter) = orbcont;
// Step 1: Make the orbiter reverse oriented
for(auto &v : sl::getContour(orbiter)) v = -v;
// An egde 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) {}
};
// Container for marked edges
using EdgeList = vector<MarkedEdge>;
EdgeList A, B;
// This is how an edge list is created from the polygons
auto fillEdgeList = [](EdgeList& L, const RawShape& poly, int dir) {
L.reserve(sl::contourVertexCount(poly));
auto it = sl::cbegin(poly);
auto nextit = std::next(it);
double turn_angle = 0;
bool is_turn_point = false;
while(nextit != sl::cend(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 TwoPi = 2.0*Pi;
if(phi > Pi) phi -= TwoPi;
if(phi_prev > Pi) phi_prev -= TwoPi;
auto turn_angle = phi-phi_prev;
if(turn_angle > Pi) turn_angle -= TwoPi;
return phi-phi_prev;
};
if(dir > 0) {
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);
enext->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
++eit; ++enext;
}
L.front().is_turning_point = signbit(L.front().turn_angle) !=
signbit(L.back().turn_angle);
} else {
std::cout << L.size() << std::endl;
auto eit = L.rbegin();
auto enext = std::next(eit);
eit->turn_angle = getTurnAngle(L.back().e, L.front().e);
while(enext != L.rend()) {
enext->turn_angle = getTurnAngle(enext->e, eit->e);
enext->is_turning_point =
signbit(enext->turn_angle) != signbit(eit->turn_angle);
std::cout << enext->is_turning_point << " " << enext->turn_angle << std::endl;
++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);
// A reference to a marked edge that also knows its container
struct MarkedEdgeRef {
reference_wrapper<MarkedEdge> eref;
reference_wrapper<vector<MarkedEdgeRef>> 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<MarkedEdgeRef>& cont ) {
return &(container.get()) == &cont;
}
inline bool eq(const MarkedEdgeRef& mr) {
return &(eref.get()) == &(mr.eref.get());
}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec):
eref(er), container(ec), dir(1) {}
MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
reference_wrapper<vector<MarkedEdgeRef>> ec,
Coord d):
eref(er), container(ec), dir(d) {}
};
using EdgeRefList = vector<MarkedEdgeRef>;
// 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) );
});
struct EdgeGroup { typename EdgeRefList::const_iterator first, last; };
auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
(const EdgeGroup& Q, const EdgeGroup& 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.last - Q.first) + (R.last - R.first));
merged.insert(merged.end(), Q.first, Q.last);
merged.insert(merged.end(), R.first, R.last);
sort(merged.begin(), merged.end(), sortfn);
// Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
// we dont 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.first->container.get();
const auto& Qcont = Q.first->container.get();
// Set the intial direction
Coord dir = positive? 1 : -1;
// roughly i = 1 (so q = Q.first) and s1 = q1 so S[0] = q;
auto q = Q.first;
S.push_back(*q++);
// Roughly step 3
while(q != Q.last) {
auto it = merged.begin();
while(it != merged.end() && !(it->eq(*(Q.first))) ) {
if(it->isFrom(Rcont)) {
auto s = *it;
s.dir = dir;
S.push_back(s);
}
if(it->eq(*q)) {
S.push_back(*q);
if(it->isTurningPoint()) dir = -dir;
if(q != Q.first) it += dir;
}
else it += dir;
}
++q; // "Set i = i + 1"
}
// Step 4:
// "Let starting edge r1 be in position si in sequence"
// whaaat? I guess this means the following:
S[0] = *R.first;
auto it = S.begin();
// "Set j = 1, next = 2, direction = 1, seq1 = si"
// we dont use j, seq is expanded dynamically.
dir = 1; auto next = std::next(R.first);
// Step 5:
// "If all si edges have been allocated to seqj" should mean that
// we loop until seq has equal size with S
while(seq.size() < S.size()) {
++it; if(it == S.end()) it = S.begin();
if(it->isFrom(Qcont)) {
seq.push_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; }
}
if(it->eq(*next) && dir == next->dir) { // "If si = direction.rnext"
// "j = j + 1, seqj = si, next = next + direction"
seq.push_back(*it); next += dir;
}
}
return seq;
};
EdgeGroup R{ Bref.begin(), Bref.begin() }, Q{ Aref.begin(), Aref.end() };
auto it = Bref.begin();
bool orientation = true;
EdgeRefList seqlist;
seqlist.reserve(3*(Aref.size() + Bref.size()));
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
// /////////////////////////////////////////////////////////////////////////
return Result(stationary, Vertex());
}
// Specializable NFP implementation class. Specialize it if you have a faster
// or better NFP implementation
template<class RawShape, NfpLevel nfptype>
struct NfpImpl {
RawShape operator()(const RawShape& sh, const RawShape& other) {
NfpResult<RawShape> operator()(const RawShape& sh, const RawShape& other)
{
static_assert(nfptype == NfpLevel::CONVEX_ONLY,
"Nfp::noFitPolygon() unimplemented!");

View file

@ -9,6 +9,7 @@
#include <functional>
#include "geometry_traits.hpp"
#include "optimizer.hpp"
namespace libnest2d {
@ -27,6 +28,7 @@ class _Item {
using Coord = TCoord<TPoint<RawShape>>;
using Vertex = TPoint<RawShape>;
using Box = _Box<Vertex>;
using sl = ShapeLike;
// The original shape that gets encapsulated.
RawShape sh_;
@ -51,11 +53,18 @@ class _Item {
enum class Convexity: char {
UNCHECKED,
TRUE,
FALSE
C_TRUE,
C_FALSE
};
mutable Convexity convexity_ = Convexity::UNCHECKED;
mutable TVertexConstIterator<RawShape> rmt_; // rightmost top vertex
mutable TVertexConstIterator<RawShape> lmb_; // leftmost bottom vertex
mutable bool rmt_valid_ = false, lmb_valid_ = false;
mutable struct BBCache {
Box bb; bool valid; Vertex tr;
BBCache(): valid(false), tr(0, 0) {}
} bb_cache_;
public:
@ -104,15 +113,15 @@ public:
* @param il The initializer list of vertices.
*/
inline _Item(const std::initializer_list< Vertex >& il):
sh_(ShapeLike::create<RawShape>(il)) {}
sh_(sl::create<RawShape>(il)) {}
inline _Item(const TContour<RawShape>& contour,
const THolesContainer<RawShape>& holes = {}):
sh_(ShapeLike::create<RawShape>(contour, holes)) {}
sh_(sl::create<RawShape>(contour, holes)) {}
inline _Item(TContour<RawShape>&& contour,
THolesContainer<RawShape>&& holes):
sh_(ShapeLike::create<RawShape>(std::move(contour),
sh_(sl::create<RawShape>(std::move(contour),
std::move(holes))) {}
/**
@ -122,31 +131,31 @@ public:
*/
inline std::string toString() const
{
return ShapeLike::toString(sh_);
return sl::toString(sh_);
}
/// Iterator tho the first contour vertex in the polygon.
inline Iterator begin() const
{
return ShapeLike::cbegin(sh_);
return sl::cbegin(sh_);
}
/// Alias to begin()
inline Iterator cbegin() const
{
return ShapeLike::cbegin(sh_);
return sl::cbegin(sh_);
}
/// Iterator to the last contour vertex.
inline Iterator end() const
{
return ShapeLike::cend(sh_);
return sl::cend(sh_);
}
/// Alias to end()
inline Iterator cend() const
{
return ShapeLike::cend(sh_);
return sl::cend(sh_);
}
/**
@ -161,7 +170,7 @@ public:
*/
inline Vertex vertex(unsigned long idx) const
{
return ShapeLike::vertex(sh_, idx);
return sl::vertex(sh_, idx);
}
/**
@ -176,7 +185,7 @@ public:
inline void setVertex(unsigned long idx, const Vertex& v )
{
invalidateCache();
ShapeLike::vertex(sh_, idx) = v;
sl::vertex(sh_, idx) = v;
}
/**
@ -191,7 +200,7 @@ public:
double ret ;
if(area_cache_valid_) ret = area_cache_;
else {
ret = ShapeLike::area(offsettedShape());
ret = sl::area(offsettedShape());
area_cache_ = ret;
area_cache_valid_ = true;
}
@ -203,17 +212,17 @@ public:
switch(convexity_) {
case Convexity::UNCHECKED:
ret = ShapeLike::isConvex<RawShape>(ShapeLike::getContour(transformedShape()));
convexity_ = ret? Convexity::TRUE : Convexity::FALSE;
ret = sl::isConvex<RawShape>(sl::getContour(transformedShape()));
convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE;
break;
case Convexity::TRUE: ret = true; break;
case Convexity::FALSE:;
case Convexity::C_TRUE: ret = true; break;
case Convexity::C_FALSE:;
}
return ret;
}
inline bool isHoleConvex(unsigned holeidx) const {
inline bool isHoleConvex(unsigned /*holeidx*/) const {
return false;
}
@ -223,11 +232,11 @@ public:
/// The number of the outer ring vertices.
inline size_t vertexCount() const {
return ShapeLike::contourVertexCount(sh_);
return sl::contourVertexCount(sh_);
}
inline size_t holeCount() const {
return ShapeLike::holeCount(sh_);
return sl::holeCount(sh_);
}
/**
@ -235,36 +244,39 @@ public:
* @param p
* @return
*/
inline bool isPointInside(const Vertex& p)
inline bool isPointInside(const Vertex& p) const
{
return ShapeLike::isInside(p, sh_);
return sl::isInside(p, transformedShape());
}
inline bool isInside(const _Item& sh) const
{
return ShapeLike::isInside(transformedShape(), sh.transformedShape());
return sl::isInside(transformedShape(), sh.transformedShape());
}
inline bool isInside(const _Box<TPoint<RawShape>>& box);
inline bool isInside(const RawShape& sh) const
{
return sl::isInside(transformedShape(), sh);
}
inline bool isInside(const _Box<TPoint<RawShape>>& box) const;
inline bool isInside(const _Circle<TPoint<RawShape>>& box) const;
inline void translate(const Vertex& d) BP2D_NOEXCEPT
{
translation_ += d; has_translation_ = true;
tr_cache_valid_ = false;
translation(translation() + d);
}
inline void rotate(const Radians& rads) BP2D_NOEXCEPT
{
rotation_ += rads;
has_rotation_ = true;
tr_cache_valid_ = false;
rotation(rotation() + rads);
}
inline void addOffset(Coord distance) BP2D_NOEXCEPT
{
offset_distance_ = distance;
has_offset_ = true;
offset_cache_valid_ = false;
invalidateCache();
}
inline void removeOffset() BP2D_NOEXCEPT {
@ -286,6 +298,8 @@ public:
{
if(rotation_ != rot) {
rotation_ = rot; has_rotation_ = true; tr_cache_valid_ = false;
rmt_valid_ = false; lmb_valid_ = false;
bb_cache_.valid = false;
}
}
@ -293,6 +307,7 @@ public:
{
if(translation_ != tr) {
translation_ = tr; has_translation_ = true; tr_cache_valid_ = false;
bb_cache_.valid = false;
}
}
@ -301,9 +316,10 @@ public:
if(tr_cache_valid_) return tr_cache_;
RawShape cpy = offsettedShape();
if(has_rotation_) ShapeLike::rotate(cpy, rotation_);
if(has_translation_) ShapeLike::translate(cpy, translation_);
if(has_rotation_) sl::rotate(cpy, rotation_);
if(has_translation_) sl::translate(cpy, translation_);
tr_cache_ = cpy; tr_cache_valid_ = true;
rmt_valid_ = false; lmb_valid_ = false;
return tr_cache_;
}
@ -321,23 +337,53 @@ public:
inline void resetTransformation() BP2D_NOEXCEPT
{
has_translation_ = false; has_rotation_ = false; has_offset_ = false;
invalidateCache();
}
inline Box boundingBox() const {
return ShapeLike::boundingBox(transformedShape());
if(!bb_cache_.valid) {
bb_cache_.bb = sl::boundingBox(transformedShape());
bb_cache_.tr = {0, 0};
bb_cache_.valid = true;
}
auto &bb = bb_cache_.bb; auto &tr = bb_cache_.tr;
return {bb.minCorner() + tr, bb.maxCorner() + tr};
}
inline Vertex referenceVertex() const {
return rightmostTopVertex();
}
inline Vertex rightmostTopVertex() const {
if(!rmt_valid_ || !tr_cache_valid_) { // find max x and max y vertex
auto& tsh = transformedShape();
rmt_ = std::max_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
rmt_valid_ = true;
}
return *rmt_;
}
inline Vertex leftmostBottomVertex() const {
if(!lmb_valid_ || !tr_cache_valid_) { // find min x and min y vertex
auto& tsh = transformedShape();
lmb_ = std::min_element(sl::cbegin(tsh), sl::cend(tsh), vsort);
lmb_valid_ = true;
}
return *lmb_;
}
//Static methods:
inline static bool intersects(const _Item& sh1, const _Item& sh2)
{
return ShapeLike::intersects(sh1.transformedShape(),
return sl::intersects(sh1.transformedShape(),
sh2.transformedShape());
}
inline static bool touches(const _Item& sh1, const _Item& sh2)
{
return ShapeLike::touches(sh1.transformedShape(),
return sl::touches(sh1.transformedShape(),
sh2.transformedShape());
}
@ -346,12 +392,11 @@ private:
inline const RawShape& offsettedShape() const {
if(has_offset_ ) {
if(offset_cache_valid_) return offset_cache_;
else {
offset_cache_ = sh_;
ShapeLike::offset(offset_cache_, offset_distance_);
offset_cache_valid_ = true;
return offset_cache_;
}
offset_cache_ = sh_;
sl::offset(offset_cache_, offset_distance_);
offset_cache_valid_ = true;
return offset_cache_;
}
return sh_;
}
@ -359,10 +404,23 @@ private:
inline void invalidateCache() const BP2D_NOEXCEPT
{
tr_cache_valid_ = false;
lmb_valid_ = false; rmt_valid_ = false;
area_cache_valid_ = false;
offset_cache_valid_ = false;
bb_cache_.valid = false;
convexity_ = Convexity::UNCHECKED;
}
static inline bool vsort(const Vertex& v1, const Vertex& v2)
{
Coord &&x1 = getX(v1), &&x2 = getX(v2);
Coord &&y1 = getY(v1), &&y2 = getY(v2);
auto diff = y1 - y2;
if(std::abs(diff) <= std::numeric_limits<Coord>::epsilon())
return x1 < x2;
return diff < 0;
}
};
/**
@ -370,7 +428,6 @@ private:
*/
template<class RawShape>
class _Rectangle: public _Item<RawShape> {
RawShape sh_;
using _Item<RawShape>::vertex;
using TO = Orientation;
public:
@ -415,9 +472,13 @@ public:
};
template<class RawShape>
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) {
_Rectangle<RawShape> rect(box.width(), box.height());
return _Item<RawShape>::isInside(rect);
inline bool _Item<RawShape>::isInside(const _Box<TPoint<RawShape>>& box) const {
return ShapeLike::isInside<RawShape>(boundingBox(), box);
}
template<class RawShape> inline bool
_Item<RawShape>::isInside(const _Circle<TPoint<RawShape>>& circ) const {
return ShapeLike::isInside<RawShape>(transformedShape(), circ);
}
/**
@ -874,9 +935,8 @@ private:
Radians findBestRotation(Item& item) {
opt::StopCriteria stopcr;
stopcr.stoplimit = 0.01;
stopcr.absolute_score_difference = 0.01;
stopcr.max_iterations = 10000;
stopcr.type = opt::StopLimitType::RELATIVE;
opt::TOptimizer<opt::Method::G_GENETIC> solver(stopcr);
auto orig_rot = item.rotation();
@ -910,7 +970,6 @@ private:
if(min_obj_distance_ > 0) std::for_each(from, to, [](Item& item) {
item.removeOffset();
});
}
};

View file

@ -0,0 +1,227 @@
#ifndef METALOOP_HPP
#define METALOOP_HPP
#include "common.hpp"
#include <tuple>
#include <functional>
namespace libnest2d {
/* ************************************************************************** */
/* C++14 std::index_sequence implementation: */
/* ************************************************************************** */
/**
* \brief C++11 conformant implementation of the index_sequence type from C++14
*/
template<size_t...Ints> struct index_sequence {
using value_type = size_t;
BP2D_CONSTEXPR value_type size() const { return sizeof...(Ints); }
};
// A Help structure to generate the integer list
template<size_t...Nseq> struct genSeq;
// Recursive template to generate the list
template<size_t I, size_t...Nseq> struct genSeq<I, Nseq...> {
// Type will contain a genSeq with Nseq appended by one element
using Type = typename genSeq< I - 1, I - 1, Nseq...>::Type;
};
// Terminating recursion
template <size_t ... Nseq> struct genSeq<0, Nseq...> {
// If I is zero, Type will contain index_sequence with the fuly generated
// integer list.
using Type = index_sequence<Nseq...>;
};
/// Helper alias to make an index sequence from 0 to N
template<size_t N> using make_index_sequence = typename genSeq<N>::Type;
/// Helper alias to make an index sequence for a parameter pack
template<class...Args>
using index_sequence_for = make_index_sequence<sizeof...(Args)>;
/* ************************************************************************** */
namespace opt {
using std::forward;
using std::tuple;
using std::get;
using std::tuple_element;
/**
* @brief Helper class to be able to loop over a parameter pack's elements.
*/
class metaloop {
// The implementation is based on partial struct template specializations.
// Basically we need a template type that is callable and takes an integer
// non-type template parameter which can be used to implement recursive calls.
//
// C++11 will not allow the usage of a plain template function that is why we
// use struct with overloaded call operator. At the same time C++11 prohibits
// partial template specialization with a non type parameter such as int. We
// need to wrap that in a type (see metaloop::Int).
/*
* A helper alias to create integer values wrapped as a type. It is nessecary
* because a non type template parameter (such as int) would be prohibited in
* a partial specialization. Also for the same reason we have to use a class
* _Metaloop instead of a simple function as a functor. A function cannot be
* partially specialized in a way that is neccesary for this trick.
*/
template<int N> using Int = std::integral_constant<int, N>;
/*
* Helper class to implement in-place functors.
*
* We want to be able to use inline functors like a lambda to keep the code
* as clear as possible.
*/
template<int N, class Fn> class MapFn {
Fn&& fn_;
public:
// It takes the real functor that can be specified in-place but only
// with C++14 because the second parameter's type will depend on the
// type of the parameter pack element that is processed. In C++14 we can
// specify this second parameter type as auto in the lamda parameter list.
inline MapFn(Fn&& fn): fn_(forward<Fn>(fn)) {}
template<class T> void operator ()(T&& pack_element) {
// We provide the index as the first parameter and the pack (or tuple)
// element as the second parameter to the functor.
fn_(N, forward<T>(pack_element));
}
};
/*
* Implementation of the template loop trick.
* We create a mechanism for looping over a parameter pack in compile time.
* \tparam Idx is the loop index which will be decremented at each recursion.
* \tparam Args The parameter pack that will be processed.
*
*/
template <typename Idx, class...Args>
class _MetaLoop {};
// Implementation for the first element of Args...
template <class...Args>
class _MetaLoop<Int<0>, Args...> {
public:
const static BP2D_CONSTEXPR int N = 0;
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
template<class Tup, class Fn>
void run( Tup&& valtup, Fn&& fn) {
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (get<ARGNUM-N>(valtup));
}
};
// Implementation for the N-th element of Args...
template <int N, class...Args>
class _MetaLoop<Int<N>, Args...> {
public:
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
template<class Tup, class Fn>
void run(Tup&& valtup, Fn&& fn) {
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (std::get<ARGNUM-N>(valtup));
// Recursive call to process the next element of Args
_MetaLoop<Int<N-1>, Args...> ().run(forward<Tup>(valtup),
forward<Fn>(fn));
}
};
/*
* Instantiation: We must instantiate the template with the last index because
* the generalized version calls the decremented instantiations recursively.
* Once the instantiation with the first index is called, the terminating
* version of run is called which does not call itself anymore.
*
* If you are utterly annoyed, at least you have learned a super crazy
* functional metaprogramming pattern.
*/
template<class...Args>
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>;
public:
/**
* \brief The final usable function template.
*
* This is similar to what varags was on C but in compile time C++11.
* You can call:
* apply(<the mapping function>, <arbitrary number of arguments of any type>);
* For example:
*
* struct mapfunc {
* template<class T> void operator()(int N, T&& element) {
* std::cout << "The value of the parameter "<< N <<": "
* << element << std::endl;
* }
* };
*
* apply(mapfunc(), 'a', 10, 151.545);
*
* C++14:
* apply([](int N, auto&& element){
* std::cout << "The value of the parameter "<< N <<": "
* << element << std::endl;
* }, 'a', 10, 151.545);
*
* This yields the output:
* The value of the parameter 0: a
* The value of the parameter 1: 10
* The value of the parameter 2: 151.545
*
* As an addition, the function can be called with a tuple as the second
* parameter holding the arguments instead of a parameter pack.
*
*/
template<class...Args, class Fn>
inline static void apply(Fn&& fn, Args&&...args) {
MetaLoop<Args...>().run(tuple<Args&&...>(forward<Args>(args)...),
forward<Fn>(fn));
}
/// The version of apply with a tuple rvalue reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, tuple<Args...>&& tup) {
MetaLoop<Args...>().run(std::move(tup), forward<Fn>(fn));
}
/// The version of apply with a tuple lvalue reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, tuple<Args...>& tup) {
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
}
/// The version of apply with a tuple const reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, const tuple<Args...>& tup) {
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
}
/**
* Call a function with its arguments encapsualted in a tuple.
*/
template<class Fn, class Tup, std::size_t...Is>
inline static auto
callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence<Is...>) ->
decltype(fn(std::get<Is>(tup)...))
{
return fn(std::get<Is>(tup)...);
}
};
}
}
#endif // METALOOP_HPP

View file

@ -10,8 +10,7 @@ namespace libnest2d { namespace opt {
using std::forward;
using std::tuple;
using std::get;
using std::tuple_element;
using std::make_tuple;
/// A Type trait for upper and lower limit of a numeric type.
template<class T, class B = void >
@ -51,176 +50,7 @@ inline Bound<T> bound(const T& min, const T& max) { return Bound<T>(min, max); }
template<class...Args> using Input = tuple<Args...>;
template<class...Args>
inline tuple<Args...> initvals(Args...args) { return std::make_tuple(args...); }
/**
* @brief Helper class to be able to loop over a parameter pack's elements.
*/
class metaloop {
// The implementation is based on partial struct template specializations.
// Basically we need a template type that is callable and takes an integer
// non-type template parameter which can be used to implement recursive calls.
//
// C++11 will not allow the usage of a plain template function that is why we
// use struct with overloaded call operator. At the same time C++11 prohibits
// partial template specialization with a non type parameter such as int. We
// need to wrap that in a type (see metaloop::Int).
/*
* A helper alias to create integer values wrapped as a type. It is nessecary
* because a non type template parameter (such as int) would be prohibited in
* a partial specialization. Also for the same reason we have to use a class
* _Metaloop instead of a simple function as a functor. A function cannot be
* partially specialized in a way that is neccesary for this trick.
*/
template<int N> using Int = std::integral_constant<int, N>;
/*
* Helper class to implement in-place functors.
*
* We want to be able to use inline functors like a lambda to keep the code
* as clear as possible.
*/
template<int N, class Fn> class MapFn {
Fn&& fn_;
public:
// It takes the real functor that can be specified in-place but only
// with C++14 because the second parameter's type will depend on the
// type of the parameter pack element that is processed. In C++14 we can
// specify this second parameter type as auto in the lamda parameter list.
inline MapFn(Fn&& fn): fn_(forward<Fn>(fn)) {}
template<class T> void operator ()(T&& pack_element) {
// We provide the index as the first parameter and the pack (or tuple)
// element as the second parameter to the functor.
fn_(N, forward<T>(pack_element));
}
};
/*
* Implementation of the template loop trick.
* We create a mechanism for looping over a parameter pack in compile time.
* \tparam Idx is the loop index which will be decremented at each recursion.
* \tparam Args The parameter pack that will be processed.
*
*/
template <typename Idx, class...Args>
class _MetaLoop {};
// Implementation for the first element of Args...
template <class...Args>
class _MetaLoop<Int<0>, Args...> {
public:
const static BP2D_CONSTEXPR int N = 0;
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
template<class Tup, class Fn>
void run( Tup&& valtup, Fn&& fn) {
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (get<ARGNUM-N>(valtup));
}
};
// Implementation for the N-th element of Args...
template <int N, class...Args>
class _MetaLoop<Int<N>, Args...> {
public:
const static BP2D_CONSTEXPR int ARGNUM = sizeof...(Args)-1;
template<class Tup, class Fn>
void run(Tup&& valtup, Fn&& fn) {
MapFn<ARGNUM-N, Fn> {forward<Fn>(fn)} (std::get<ARGNUM-N>(valtup));
// Recursive call to process the next element of Args
_MetaLoop<Int<N-1>, Args...> ().run(forward<Tup>(valtup),
forward<Fn>(fn));
}
};
/*
* Instantiation: We must instantiate the template with the last index because
* the generalized version calls the decremented instantiations recursively.
* Once the instantiation with the first index is called, the terminating
* version of run is called which does not call itself anymore.
*
* If you are utterly annoyed, at least you have learned a super crazy
* functional metaprogramming pattern.
*/
template<class...Args>
using MetaLoop = _MetaLoop<Int<sizeof...(Args)-1>, Args...>;
public:
/**
* \brief The final usable function template.
*
* This is similar to what varags was on C but in compile time C++11.
* You can call:
* apply(<the mapping function>, <arbitrary number of arguments of any type>);
* For example:
*
* struct mapfunc {
* template<class T> void operator()(int N, T&& element) {
* std::cout << "The value of the parameter "<< N <<": "
* << element << std::endl;
* }
* };
*
* apply(mapfunc(), 'a', 10, 151.545);
*
* C++14:
* apply([](int N, auto&& element){
* std::cout << "The value of the parameter "<< N <<": "
* << element << std::endl;
* }, 'a', 10, 151.545);
*
* This yields the output:
* The value of the parameter 0: a
* The value of the parameter 1: 10
* The value of the parameter 2: 151.545
*
* As an addition, the function can be called with a tuple as the second
* parameter holding the arguments instead of a parameter pack.
*
*/
template<class...Args, class Fn>
inline static void apply(Fn&& fn, Args&&...args) {
MetaLoop<Args...>().run(tuple<Args&&...>(forward<Args>(args)...),
forward<Fn>(fn));
}
/// The version of apply with a tuple rvalue reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, tuple<Args...>&& tup) {
MetaLoop<Args...>().run(std::move(tup), forward<Fn>(fn));
}
/// The version of apply with a tuple lvalue reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, tuple<Args...>& tup) {
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
}
/// The version of apply with a tuple const reference.
template<class...Args, class Fn>
inline static void apply(Fn&& fn, const tuple<Args...>& tup) {
MetaLoop<Args...>().run(tup, forward<Fn>(fn));
}
/**
* Call a function with its arguments encapsualted in a tuple.
*/
template<class Fn, class Tup, std::size_t...Is>
inline static auto
callFunWithTuple(Fn&& fn, Tup&& tup, index_sequence<Is...>) ->
decltype(fn(std::get<Is>(tup)...))
{
return fn(std::get<Is>(tup)...);
}
};
inline tuple<Args...> initvals(Args...args) { return make_tuple(args...); }
/**
* @brief Specific optimization methods for which a default optimizer
@ -257,29 +87,20 @@ enum ResultCodes {
template<class...Args>
struct Result {
ResultCodes resultcode;
std::tuple<Args...> optimum;
tuple<Args...> optimum;
double score;
};
/**
* @brief The stop limit can be specified as the absolute error or as the
* relative error, just like in nlopt.
*/
enum class StopLimitType {
ABSOLUTE,
RELATIVE
};
/**
* @brief A type for specifying the stop criteria.
*/
struct StopCriteria {
/// Relative or absolute termination error
StopLimitType type = StopLimitType::RELATIVE;
/// If the absolute value difference between two scores.
double absolute_score_difference = std::nan("");
/// The error value that is interpredted depending on the type property.
double stoplimit = 0.0001;
/// If the relative value difference between two scores.
double relative_score_difference = std::nan("");
unsigned max_iterations = 0;
};
@ -310,11 +131,11 @@ public:
* \return Returns a Result<Args...> structure.
* An example call would be:
* auto result = opt.optimize_min(
* [](std::tuple<double> x) // object function
* [](tuple<double> x) // object function
* {
* return std::pow(std::get<0>(x), 2);
* },
* std::make_tuple(-0.5), // initial value
* make_tuple(-0.5), // initial value
* {-1.0, 1.0} // search space bounds
* );
*/
@ -390,10 +211,14 @@ public:
static_assert(always_false<T>::value, "Optimizer unimplemented!");
}
DummyOptimizer(const StopCriteria&) {
static_assert(always_false<T>::value, "Optimizer unimplemented!");
}
template<class Func, class...Args>
Result<Args...> optimize(Func&& func,
std::tuple<Args...> initvals,
Bound<Args>... args)
Result<Args...> optimize(Func&& /*func*/,
tuple<Args...> /*initvals*/,
Bound<Args>... /*args*/)
{
return Result<Args...>();
}

View file

@ -1,15 +1,25 @@
#ifndef NLOPT_BOILERPLATE_HPP
#define NLOPT_BOILERPLATE_HPP
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4244)
#pragma warning(disable: 4267)
#endif
#include <nlopt.hpp>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include <libnest2d/optimizer.hpp>
#include <cassert>
#include "libnest2d/metaloop.hpp"
#include <utility>
namespace libnest2d { namespace opt {
nlopt::algorithm method2nloptAlg(Method m) {
inline nlopt::algorithm method2nloptAlg(Method m) {
switch(m) {
case Method::L_SIMPLEX: return nlopt::LN_NELDERMEAD;
@ -87,7 +97,7 @@ protected:
template<class Fn, class...Args>
static double optfunc(const std::vector<double>& params,
std::vector<double>& grad,
std::vector<double>& /*grad*/,
void *data)
{
auto fnptr = static_cast<remove_ref_t<Fn>*>(data);
@ -132,12 +142,10 @@ protected:
default: ;
}
switch(this->stopcr_.type) {
case StopLimitType::ABSOLUTE:
opt_.set_ftol_abs(stopcr_.stoplimit); break;
case StopLimitType::RELATIVE:
opt_.set_ftol_rel(stopcr_.stoplimit); break;
}
auto abs_diff = stopcr_.absolute_score_difference;
auto rel_diff = stopcr_.relative_score_difference;
if(!std::isnan(abs_diff)) opt_.set_ftol_abs(abs_diff);
if(!std::isnan(rel_diff)) opt_.set_ftol_rel(rel_diff);
if(this->stopcr_.max_iterations > 0)
opt_.set_maxeval(this->stopcr_.max_iterations );

View file

@ -6,6 +6,10 @@
#endif
#include "placer_boilerplate.hpp"
#include "../geometry_traits_nfp.hpp"
#include "libnest2d/optimizer.hpp"
#include <cassert>
#include "tools/svgtools.hpp"
namespace libnest2d { namespace strategies {
@ -20,15 +24,60 @@ struct NfpPConfig {
TOP_RIGHT,
};
/// Which angles to try out for better results
/// Which angles to try out for better results.
std::vector<Radians> rotations;
/// Where to align the resulting packed pile
/// Where to align the resulting packed pile.
Alignment alignment;
/// Where to start putting objects in the bin.
Alignment starting_point;
std::function<double(const Nfp::Shapes<RawShape>&, double, double, double)>
/**
* @brief A function object representing the fitting function in the
* placement optimization process. (Optional)
*
* This is the most versatile tool to configure the placer. The fitting
* function is evaluated many times when a new item is being placed into the
* bin. The output should be a rated score of the new item's position.
*
* This is not a mandatory option as there is a default fitting function
* that will optimize for the best pack efficiency. With a custom fitting
* function you can e.g. influence the shape of the arranged pile.
*
* \param shapes The first parameter is a container with all the placed
* polygons excluding the current candidate. You can calculate a bounding
* box or convex hull on this pile of polygons without the candidate item
* or push back the candidate item into the container and then calculate
* some features.
*
* \param item The second parameter is the candidate item.
*
* \param occupied_area The third parameter is the sum of areas of the
* items in the first parameter so you don't have to iterate through them
* if you only need their area.
*
* \param norm A norming factor for physical dimensions. E.g. if your score
* is the distance between the item and the bin center, you should divide
* that distance with the norming factor. If the score is an area than
* divide it with the square of the norming factor. Imagine it as a unit of
* distance.
*
* \param penality The fifth parameter is the amount of minimum penality if
* the arranged pile would't fit into the bin. You can use the wouldFit()
* function to check this. Note that the pile can be outside the bin's
* boundaries while the placement algorithm is running. Your job is only to
* check if the pile could be translated into a position in the bin where
* all the items would be inside. For a box shaped bin you can use the
* pile's bounding box to check whether it's width and height is small
* enough. If the pile would not fit, you have to make sure that the
* resulting score will be higher then the penality value. A good solution
* would be to set score = 2*penality-score in case the pile wouldn't fit
* into the bin.
*
*/
std::function<double(Nfp::Shapes<RawShape>&, const _Item<RawShape>&,
double, double, double)>
object_function;
/**
@ -38,11 +87,30 @@ struct NfpPConfig {
*/
float accuracy = 1.0;
/**
* @brief If you want to see items inside other item's holes, you have to
* turn this switch on.
*
* This will only work if a suitable nfp implementation is provided.
* The library has no such implementation right now.
*/
bool explore_holes = false;
NfpPConfig(): rotations({0.0, Pi/2.0, Pi, 3*Pi/2}),
alignment(Alignment::CENTER), starting_point(Alignment::CENTER) {}
};
// A class for getting a point on the circumference of the polygon (in log time)
/**
* A class for getting a point on the circumference of the polygon (in log time)
*
* This is a transformation of the provided polygon to be able to pinpoint
* locations on the circumference. The optimizer will pass a floating point
* value e.g. within <0,1> and we have to transform this value quickly into a
* coordinate on the circumference. By definition 0 should yield the first
* vertex and 1.0 would be the last (which should coincide with first).
*
* We also have to make this work for the holes of the captured polygon.
*/
template<class RawShape> class EdgeCache {
using Vertex = TPoint<RawShape>;
using Coord = TCoord<Vertex>;
@ -57,6 +125,8 @@ template<class RawShape> class EdgeCache {
std::vector<ContourCache> holes_;
double accuracy_ = 1.0;
void createCache(const RawShape& sh) {
{ // For the contour
auto first = ShapeLike::cbegin(sh);
@ -90,21 +160,44 @@ template<class RawShape> class EdgeCache {
}
}
size_t stride(const size_t N) const {
using std::ceil;
using std::round;
using std::pow;
return static_cast<Coord>(
std::round(N/std::pow(N, std::pow(accuracy_, 1.0/3.0)))
);
}
void fetchCorners() const {
if(!contour_.corners.empty()) return;
// TODO Accuracy
contour_.corners = contour_.distances;
for(auto& d : contour_.corners) d /= contour_.full_distance;
const auto N = contour_.distances.size();
const auto S = stride(N);
contour_.corners.reserve(N / S + 1);
auto N_1 = N-1;
contour_.corners.emplace_back(0.0);
for(size_t i = 0; i < N_1; i += S) {
contour_.corners.emplace_back(
contour_.distances.at(i) / contour_.full_distance);
}
}
void fetchHoleCorners(unsigned hidx) const {
auto& hc = holes_[hidx];
if(!hc.corners.empty()) return;
// TODO Accuracy
hc.corners = hc.distances;
for(auto& d : hc.corners) d /= hc.full_distance;
const auto N = hc.distances.size();
const auto S = stride(N);
auto N_1 = N-1;
hc.corners.reserve(N / S + 1);
hc.corners.emplace_back(0.0);
for(size_t i = 0; i < N_1; i += S) {
hc.corners.emplace_back(
hc.distances.at(i) / hc.full_distance);
}
}
inline Vertex coords(const ContourCache& cache, double distance) const {
@ -150,6 +243,9 @@ public:
createCache(sh);
}
/// Resolution of returned corners. The stride is derived from this value.
void accuracy(double a /* within <0.0, 1.0>*/) { accuracy_ = a; }
/**
* @brief Get a point on the circumference of a polygon.
* @param distance A relative distance from the starting point to the end.
@ -176,24 +272,64 @@ public:
return holes_[hidx].full_distance;
}
/// Get the normalized distance values for each vertex
inline const std::vector<double>& corners() const BP2D_NOEXCEPT {
fetchCorners();
return contour_.corners;
}
/// corners for a specific hole
inline const std::vector<double>&
corners(unsigned holeidx) const BP2D_NOEXCEPT {
fetchHoleCorners(holeidx);
return holes_[holeidx].corners;
}
inline unsigned holeCount() const BP2D_NOEXCEPT { return holes_.size(); }
/// The number of holes in the abstracted polygon
inline size_t holeCount() const BP2D_NOEXCEPT { return holes_.size(); }
};
template<NfpLevel lvl>
struct Lvl { static const NfpLevel value = lvl; };
template<class RawShape>
inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
const _Item<RawShape>& stationary,
const _Item<RawShape>& orbiter)
{
// The provided nfp is somewhere in the dark. We need to get it
// to the right position around the stationary shape.
// This is done by choosing the leftmost lowest vertex of the
// orbiting polygon to be touched with the rightmost upper
// vertex of the stationary polygon. In this configuration, the
// reference vertex of the orbiting polygon (which can be dragged around
// the nfp) will be its rightmost upper vertex that coincides with the
// rightmost upper vertex of the nfp. No proof provided other than Jonas
// Lindmark's reasoning about the reference vertex of nfp in his thesis
// ("No fit polygon problem" - section 2.1.9)
auto touch_sh = stationary.rightmostTopVertex();
auto touch_other = orbiter.leftmostBottomVertex();
auto dtouch = touch_sh - touch_other;
auto top_other = orbiter.rightmostTopVertex() + dtouch;
auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point
ShapeLike::translate(nfp.first, dnfp);
}
template<class RawShape>
inline void correctNfpPosition(Nfp::NfpResult<RawShape>& nfp,
const RawShape& stationary,
const _Item<RawShape>& orbiter)
{
auto touch_sh = Nfp::rightmostUpVertex(stationary);
auto touch_other = orbiter.leftmostBottomVertex();
auto dtouch = touch_sh - touch_other;
auto top_other = orbiter.rightmostTopVertex() + dtouch;
auto dnfp = top_other - nfp.second;
ShapeLike::translate(nfp.first, dnfp);
}
template<class RawShape, class Container>
Nfp::Shapes<RawShape> nfp( const Container& polygons,
const _Item<RawShape>& trsh,
@ -203,18 +339,35 @@ Nfp::Shapes<RawShape> nfp( const Container& polygons,
Nfp::Shapes<RawShape> nfps;
//int pi = 0;
for(Item& sh : polygons) {
auto subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
sh.transformedShape(), trsh.transformedShape());
auto subnfp_r = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
sh.transformedShape(), trsh.transformedShape());
#ifndef NDEBUG
auto vv = ShapeLike::isValid(sh.transformedShape());
assert(vv.first);
auto vnfp = ShapeLike::isValid(subnfp);
auto vnfp = ShapeLike::isValid(subnfp_r.first);
assert(vnfp.first);
#endif
nfps = Nfp::merge(nfps, subnfp);
correctNfpPosition(subnfp_r, sh, trsh);
nfps = Nfp::merge(nfps, subnfp_r.first);
// double SCALE = 1000000;
// using SVGWriter = svg::SVGWriter<RawShape>;
// SVGWriter::Config conf;
// conf.mm_in_coord_units = SCALE;
// SVGWriter svgw(conf);
// Box bin(250*SCALE, 210*SCALE);
// svgw.setSize(bin);
// for(int i = 0; i <= pi; i++) svgw.writeItem(polygons[i]);
// svgw.writeItem(trsh);
//// svgw.writeItem(Item(subnfp_r.first));
// for(auto& n : nfps) svgw.writeItem(Item(n));
// svgw.save("nfpout");
// pi++;
}
return nfps;
@ -227,50 +380,73 @@ Nfp::Shapes<RawShape> nfp( const Container& polygons,
{
using Item = _Item<RawShape>;
Nfp::Shapes<RawShape> nfps, stationary;
Nfp::Shapes<RawShape> nfps;
auto& orb = trsh.transformedShape();
bool orbconvex = trsh.isContourConvex();
for(Item& sh : polygons) {
stationary = Nfp::merge(stationary, sh.transformedShape());
}
Nfp::NfpResult<RawShape> subnfp;
auto& stat = sh.transformedShape();
std::cout << "pile size: " << stationary.size() << std::endl;
for(RawShape& sh : stationary) {
if(sh.isContourConvex() && orbconvex)
subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(stat, orb);
else if(orbconvex)
subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(stat, orb);
else
subnfp = Nfp::noFitPolygon<Level::value>(stat, orb);
RawShape subnfp;
// if(sh.isContourConvex() && trsh.isContourConvex()) {
// subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
// sh.transformedShape(), trsh.transformedShape());
// } else {
subnfp = Nfp::noFitPolygon<Level::value>( sh/*.transformedShape()*/,
trsh.transformedShape());
// }
correctNfpPosition(subnfp, sh, trsh);
// #ifndef NDEBUG
// auto vv = ShapeLike::isValid(sh.transformedShape());
// assert(vv.first);
// auto vnfp = ShapeLike::isValid(subnfp);
// assert(vnfp.first);
// #endif
// auto vnfp = ShapeLike::isValid(subnfp);
// if(!vnfp.first) {
// std::cout << vnfp.second << std::endl;
// std::cout << ShapeLike::toString(subnfp) << std::endl;
// }
nfps = Nfp::merge(nfps, subnfp);
nfps = Nfp::merge(nfps, subnfp.first);
}
return nfps;
// using Item = _Item<RawShape>;
// using sl = ShapeLike;
// Nfp::Shapes<RawShape> nfps, stationary;
// for(Item& sh : polygons) {
// stationary = Nfp::merge(stationary, sh.transformedShape());
// }
// for(RawShape& sh : stationary) {
//// auto vv = sl::isValid(sh);
//// std::cout << vv.second << std::endl;
// Nfp::NfpResult<RawShape> subnfp;
// bool shconvex = sl::isConvex<RawShape>(sl::getContour(sh));
// if(shconvex && trsh.isContourConvex()) {
// subnfp = Nfp::noFitPolygon<NfpLevel::CONVEX_ONLY>(
// sh, trsh.transformedShape());
// } else if(trsh.isContourConvex()) {
// subnfp = Nfp::noFitPolygon<NfpLevel::ONE_CONVEX>(
// sh, trsh.transformedShape());
// }
// else {
// subnfp = Nfp::noFitPolygon<Level::value>( sh,
// trsh.transformedShape());
// }
// correctNfpPosition(subnfp, sh, trsh);
// nfps = Nfp::merge(nfps, subnfp.first);
// }
// return nfps;
}
template<class RawShape>
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>> {
template<class RawShape, class TBin = _Box<TPoint<RawShape>>>
class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
RawShape, TBin, NfpPConfig<RawShape>> {
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
RawShape, _Box<TPoint<RawShape>>, NfpPConfig<RawShape>>;
using Base = PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin>,
RawShape, TBin, NfpPConfig<RawShape>>;
DECLARE_PLACER(Base)
@ -280,28 +456,45 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape>,
const double penality_;
using MaxNfpLevel = Nfp::MaxNfpLevel<RawShape>;
using sl = ShapeLike;
public:
using Pile = const Nfp::Shapes<RawShape>&;
using Pile = Nfp::Shapes<RawShape>;
inline explicit _NofitPolyPlacer(const BinType& bin):
Base(bin),
norm_(std::sqrt(ShapeLike::area<RawShape>(bin))),
norm_(std::sqrt(sl::area<RawShape>(bin))),
penality_(1e6*norm_) {}
_NofitPolyPlacer(const _NofitPolyPlacer&) = default;
_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;
#endif
bool static inline wouldFit(const Box& bb, const RawShape& bin) {
auto bbin = sl::boundingBox<RawShape>(bin);
auto d = bbin.center() - bb.center();
_Rectangle<RawShape> rect(bb.width(), bb.height());
rect.translate(bb.minCorner() + d);
return sl::isInside<RawShape>(rect.transformedShape(), bin);
}
bool static inline wouldFit(const RawShape& chull, const RawShape& bin) {
auto bbch = ShapeLike::boundingBox<RawShape>(chull);
auto bbin = ShapeLike::boundingBox<RawShape>(bin);
auto d = bbin.minCorner() - bbch.minCorner();
auto bbch = sl::boundingBox<RawShape>(chull);
auto bbin = sl::boundingBox<RawShape>(bin);
auto d = bbch.center() - bbin.center();
auto chullcpy = chull;
ShapeLike::translate(chullcpy, d);
return ShapeLike::isInside<RawShape>(chullcpy, bbin);
sl::translate(chullcpy, d);
return sl::isInside<RawShape>(chullcpy, bin);
}
bool static inline wouldFit(const RawShape& chull, const Box& bin)
{
auto bbch = ShapeLike::boundingBox<RawShape>(chull);
auto bbch = sl::boundingBox<RawShape>(chull);
return wouldFit(bbch, bin);
}
@ -310,6 +503,17 @@ public:
return bb.width() <= bin.width() && bb.height() <= bin.height();
}
bool static inline wouldFit(const Box& bb, const _Circle<Vertex>& bin)
{
return sl::isInside<RawShape>(bb, bin);
}
bool static inline wouldFit(const RawShape& chull,
const _Circle<Vertex>& bin)
{
return sl::isInside<RawShape>(chull, bin);
}
PackResult trypack(Item& item) {
PackResult ret;
@ -348,7 +552,10 @@ public:
std::vector<EdgeCache<RawShape>> ecache;
ecache.reserve(nfps.size());
for(auto& nfp : nfps ) ecache.emplace_back(nfp);
for(auto& nfp : nfps ) {
ecache.emplace_back(nfp);
ecache.back().accuracy(config_.accuracy);
}
struct Optimum {
double relpos;
@ -363,7 +570,7 @@ public:
auto getNfpPoint = [&ecache](const Optimum& opt)
{
return opt.hidx < 0? ecache[opt.nfpidx].coords(opt.relpos) :
ecache[opt.nfpidx].coords(opt.nfpidx, opt.relpos);
ecache[opt.nfpidx].coords(opt.hidx, opt.relpos);
};
Nfp::Shapes<RawShape> pile;
@ -374,17 +581,25 @@ public:
pile_area += mitem.area();
}
auto merged_pile = Nfp::merge(pile);
// This is the kernel part of the object function that is
// customizable by the library client
auto _objfunc = config_.object_function?
config_.object_function :
[this](const Nfp::Shapes<RawShape>& pile, double occupied_area,
double /*norm*/, double penality)
[this, &merged_pile](
Nfp::Shapes<RawShape>& /*pile*/,
const Item& item,
double occupied_area,
double norm,
double /*penality*/)
{
auto ch = ShapeLike::convexHull(pile);
merged_pile.emplace_back(item.transformedShape());
auto ch = sl::convexHull(merged_pile);
merged_pile.pop_back();
// The pack ratio -- how much is the convex hull occupied
double pack_rate = occupied_area/ShapeLike::area(ch);
double pack_rate = occupied_area/sl::area(ch);
// ratio of waste
double waste = 1.0 - pack_rate;
@ -394,7 +609,7 @@ public:
// (larger) values.
auto score = std::sqrt(waste);
if(!wouldFit(ch, bin_)) score = 2*penality - score;
if(!wouldFit(ch, bin_)) score += norm;
return score;
};
@ -406,23 +621,31 @@ public:
d += startpos;
item.translation(d);
pile.emplace_back(item.transformedShape());
double occupied_area = pile_area + item.area();
double score = _objfunc(pile, occupied_area,
double score = _objfunc(pile, item, occupied_area,
norm_, penality_);
pile.pop_back();
return score;
};
auto boundaryCheck = [&](const Optimum& o) {
auto v = getNfpPoint(o);
auto d = v - iv;
d += startpos;
item.translation(d);
merged_pile.emplace_back(item.transformedShape());
auto chull = sl::convexHull(merged_pile);
merged_pile.pop_back();
return wouldFit(chull, bin_);
};
opt::StopCriteria stopcr;
stopcr.max_iterations = 1000;
stopcr.stoplimit = 0.001;
stopcr.type = opt::StopLimitType::RELATIVE;
opt::TOptimizer<opt::Method::L_SIMPLEX> solver(stopcr);
stopcr.max_iterations = 100;
stopcr.relative_score_difference = 1e-6;
opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
Optimum optimum(0, 0);
double best_score = penality_;
@ -441,7 +664,7 @@ public:
std::for_each(cache.corners().begin(),
cache.corners().end(),
[ch, &contour_ofn, &solver, &best_score,
&optimum] (double pos)
&optimum, &boundaryCheck] (double pos)
{
try {
auto result = solver.optimize_min(contour_ofn,
@ -450,10 +673,11 @@ public:
);
if(result.score < best_score) {
best_score = result.score;
optimum.relpos = std::get<0>(result.optimum);
optimum.nfpidx = ch;
optimum.hidx = -1;
Optimum o(std::get<0>(result.optimum), ch, -1);
if(boundaryCheck(o)) {
best_score = result.score;
optimum = o;
}
}
} catch(std::exception& e) {
derr() << "ERROR: " << e.what() << "\n";
@ -472,7 +696,7 @@ public:
std::for_each(cache.corners(hidx).begin(),
cache.corners(hidx).end(),
[&hole_ofn, &solver, &best_score,
&optimum, ch, hidx]
&optimum, ch, hidx, &boundaryCheck]
(double pos)
{
try {
@ -482,10 +706,12 @@ public:
);
if(result.score < best_score) {
best_score = result.score;
Optimum o(std::get<0>(result.optimum),
ch, hidx);
optimum = o;
if(boundaryCheck(o)) {
best_score = result.score;
optimum = o;
}
}
} catch(std::exception& e) {
derr() << "ERROR: " << e.what() << "\n";
@ -524,34 +750,35 @@ public:
m.reserve(items_.size());
for(Item& item : items_) m.emplace_back(item.transformedShape());
auto&& bb = ShapeLike::boundingBox<RawShape>(m);
auto&& bb = sl::boundingBox<RawShape>(m);
Vertex ci, cb;
auto bbin = sl::boundingBox<RawShape>(bin_);
switch(config_.alignment) {
case Config::Alignment::CENTER: {
ci = bb.center();
cb = bin_.center();
cb = bbin.center();
break;
}
case Config::Alignment::BOTTOM_LEFT: {
ci = bb.minCorner();
cb = bin_.minCorner();
cb = bbin.minCorner();
break;
}
case Config::Alignment::BOTTOM_RIGHT: {
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())};
cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
break;
}
case Config::Alignment::TOP_LEFT: {
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())};
cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
break;
}
case Config::Alignment::TOP_RIGHT: {
ci = bb.maxCorner();
cb = bin_.maxCorner();
cb = bbin.maxCorner();
break;
}
}
@ -567,31 +794,32 @@ private:
void setInitialPosition(Item& item) {
Box&& bb = item.boundingBox();
Vertex ci, cb;
auto bbin = sl::boundingBox<RawShape>(bin_);
switch(config_.starting_point) {
case Config::Alignment::CENTER: {
ci = bb.center();
cb = bin_.center();
cb = bbin.center();
break;
}
case Config::Alignment::BOTTOM_LEFT: {
ci = bb.minCorner();
cb = bin_.minCorner();
cb = bbin.minCorner();
break;
}
case Config::Alignment::BOTTOM_RIGHT: {
ci = {getX(bb.maxCorner()), getY(bb.minCorner())};
cb = {getX(bin_.maxCorner()), getY(bin_.minCorner())};
cb = {getX(bbin.maxCorner()), getY(bbin.minCorner())};
break;
}
case Config::Alignment::TOP_LEFT: {
ci = {getX(bb.minCorner()), getY(bb.maxCorner())};
cb = {getX(bin_.minCorner()), getY(bin_.maxCorner())};
cb = {getX(bbin.minCorner()), getY(bbin.maxCorner())};
break;
}
case Config::Alignment::TOP_RIGHT: {
ci = bb.maxCorner();
cb = bin_.maxCorner();
cb = bbin.maxCorner();
break;
}
}
@ -602,7 +830,7 @@ private:
void placeOutsideOfBin(Item& item) {
auto&& bb = item.boundingBox();
Box binbb = ShapeLike::boundingBox<RawShape>(bin_);
Box binbb = sl::boundingBox<RawShape>(bin_);
Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };

View file

@ -256,14 +256,14 @@ public:
if(not_packed.size() < 2)
return false; // No group of two items
else {
double largest_area = not_packed.front().get().area();
auto itmp = not_packed.begin(); itmp++;
double second_largest = itmp->get().area();
if( free_area - second_largest - largest_area > waste)
return false; // If even the largest two items do not fill
// the bin to the desired waste than we can end here.
}
double largest_area = not_packed.front().get().area();
auto itmp = not_packed.begin(); itmp++;
double second_largest = itmp->get().area();
if( free_area - second_largest - largest_area > waste)
return false; // If even the largest two items do not fill
// the bin to the desired waste than we can end here.
bool ret = false;
auto it = not_packed.begin();
@ -481,7 +481,7 @@ public:
{
std::array<bool, 3> packed = {false};
for(auto id : idx) packed[id] =
for(auto id : idx) packed.at(id) =
placer.pack(candidates[id]);
bool check =
@ -535,10 +535,9 @@ public:
// then it should be removed from the not_packed list
{ auto it = store_.begin();
while (it != store_.end()) {
Placer p(bin);
Placer p(bin); p.configure(pconfig);
if(!p.pack(*it)) {
auto itmp = it++;
store_.erase(itmp);
it = store_.erase(it);
} else it++;
}
}
@ -605,8 +604,7 @@ public:
if(placer.pack(*it)) {
filled_area += it->get().area();
free_area = bin_area - filled_area;
auto itmp = it++;
not_packed.erase(itmp);
it = not_packed.erase(it);
makeProgress(placer, idx, 1);
} else it++;
}

View file

@ -52,17 +52,16 @@ public:
auto total = last-first;
auto makeProgress = [this, &total](Placer& placer, size_t idx) {
packed_bins_[idx] = placer.getItems();
this->progress_(--total);
this->progress_(static_cast<unsigned>(--total));
};
// Safety test: try to pack each item into an empty bin. If it fails
// then it should be removed from the list
{ auto it = store_.begin();
while (it != store_.end()) {
Placer p(bin);
Placer p(bin); p.configure(pconfig);
if(!p.pack(*it)) {
auto itmp = it++;
store_.erase(itmp);
it = store_.erase(it);
} else it++;
}
}

View file

@ -682,7 +682,9 @@ void testNfp(const std::vector<ItemPair>& testdata) {
auto&& nfp = Nfp::noFitPolygon<lvl>(stationary.rawShape(),
orbiter.transformedShape());
auto v = ShapeLike::isValid(nfp);
strategies::correctNfpPosition(nfp, stationary, orbiter);
auto v = ShapeLike::isValid(nfp.first);
if(!v.first) {
std::cout << v.second << std::endl;
@ -690,7 +692,7 @@ void testNfp(const std::vector<ItemPair>& testdata) {
ASSERT_TRUE(v.first);
Item infp(nfp);
Item infp(nfp.first);
int i = 0;
auto rorbiter = orbiter.transformedShape();
@ -742,6 +744,15 @@ TEST(GeometryAlgorithms, nfpConvexConvex) {
// testNfp<NfpLevel::BOTH_CONCAVE, 1000>(nfp_concave_testdata);
//}
TEST(GeometryAlgorithms, nfpConcaveConcave) {
using namespace libnest2d;
// Rectangle r1(10, 10);
// Rectangle r2(20, 20);
// auto result = Nfp::nfpSimpleSimple(r1.transformedShape(),
// r2.transformedShape());
}
TEST(GeometryAlgorithms, pointOnPolygonContour) {
using namespace libnest2d;

View file

@ -49,18 +49,18 @@ libnfporb::point_t scale(const libnfporb::point_t& p, long double factor) {
long double px = p.x_.val();
long double py = p.y_.val();
#endif
return libnfporb::point_t(px*factor, py*factor);
return {px*factor, py*factor};
}
}
PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
NfpR _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
{
using Vertex = PointImpl;
PolygonImpl ret;
NfpR ret;
// try {
try {
libnfporb::polygon_t pstat, porb;
boost::geometry::convert(sh, pstat);
@ -85,7 +85,7 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
// this can throw
auto nfp = libnfporb::generateNFP(pstat, porb, true);
auto &ct = ShapeLike::getContour(ret);
auto &ct = ShapeLike::getContour(ret.first);
ct.reserve(nfp.front().size()+1);
for(auto v : nfp.front()) {
v = scale(v, refactor);
@ -94,10 +94,10 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
ct.push_back(ct.front());
std::reverse(ct.begin(), ct.end());
auto &rholes = ShapeLike::holes(ret);
auto &rholes = ShapeLike::holes(ret.first);
for(size_t hidx = 1; hidx < nfp.size(); ++hidx) {
if(nfp[hidx].size() >= 3) {
rholes.push_back({});
rholes.emplace_back();
auto& h = rholes.back();
h.reserve(nfp[hidx].size()+1);
@ -110,73 +110,48 @@ PolygonImpl _nfp(const PolygonImpl &sh, const PolygonImpl &cother)
}
}
auto& cmp = vsort;
std::sort(pstat.outer().begin(), pstat.outer().end(), cmp);
std::sort(porb.outer().begin(), porb.outer().end(), cmp);
ret.second = Nfp::referenceVertex(ret.first);
// leftmost lower vertex of the stationary polygon
auto& touch_sh = scale(pstat.outer().back(), refactor);
// rightmost upper vertex of the orbiting polygon
auto& touch_other = scale(porb.outer().front(), refactor);
// Calculate the difference and move the orbiter to the touch position.
auto dtouch = touch_sh - touch_other;
auto _top_other = scale(porb.outer().back(), refactor) + dtouch;
Vertex top_other(getX(_top_other), getY(_top_other));
// Get the righmost upper vertex of the nfp and move it to the RMU of
// the orbiter because they should coincide.
auto&& top_nfp = Nfp::rightmostUpVertex(ret);
auto dnfp = top_other - top_nfp;
std::for_each(ShapeLike::begin(ret), ShapeLike::end(ret),
[&dnfp](Vertex& v) { v+= dnfp; } );
for(auto& h : ShapeLike::holes(ret))
std::for_each( h.begin(), h.end(),
[&dnfp](Vertex& v) { v += dnfp; } );
// } catch(std::exception& e) {
// std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
} catch(std::exception& e) {
std::cout << "Error: " << e.what() << "\nTrying with convex hull..." << std::endl;
// auto ch_stat = ShapeLike::convexHull(sh);
// auto ch_orb = ShapeLike::convexHull(cother);
// ret = Nfp::nfpConvexOnly(ch_stat, ch_orb);
// }
ret = Nfp::nfpConvexOnly(sh, cother);
}
return ret;
}
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);//nfpConvexOnly(sh, cother);
}
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);
}
PolygonImpl Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
NfpR Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);
}
PolygonImpl
Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);
}
//PolygonImpl
//Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES>::operator()(
// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
//{
// return _nfp(sh, cother);
//}
PolygonImpl
Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES>::operator()(
const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
{
return _nfp(sh, cother);
}
//PolygonImpl
//Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES>::operator()(
// const PolygonImpl &sh, const ClipperLib::PolygonImpl &cother)
//{
// return _nfp(sh, cother);
//}
}

View file

@ -5,37 +5,39 @@
namespace libnest2d {
PolygonImpl _nfp(const PolygonImpl& sh, const PolygonImpl& cother);
using NfpR = Nfp::NfpResult<PolygonImpl>;
NfpR _nfp(const PolygonImpl& sh, const PolygonImpl& cother);
template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::CONVEX_ONLY> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
NfpR operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
//template<>
//struct Nfp::NfpImpl<PolygonImpl, NfpLevel::ONE_CONVEX_WITH_HOLES> {
// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
//};
template<>
struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES> {
PolygonImpl operator()(const PolygonImpl& sh, const PolygonImpl& cother);
};
//template<>
//struct Nfp::NfpImpl<PolygonImpl, NfpLevel::BOTH_CONCAVE_WITH_HOLES> {
// NfpResult operator()(const PolygonImpl& sh, const PolygonImpl& cother);
//};
template<> struct Nfp::MaxNfpLevel<PolygonImpl> {
static const BP2D_CONSTEXPR NfpLevel value =
// NfpLevel::CONVEX_ONLY;
NfpLevel::BOTH_CONCAVE_WITH_HOLES;
NfpLevel::BOTH_CONCAVE;
};
}

View file

@ -5,11 +5,17 @@
#include <fstream>
#include <string>
#include <libnest2d.h>
#include <libnest2d/libnest2d.hpp>
namespace libnest2d { namespace svg {
template<class RawShape>
class SVGWriter {
using Item = _Item<RawShape>;
using Coord = TCoord<TPoint<RawShape>>;
using Box = _Box<TPoint<RawShape>>;
using PackGroup = _PackGroup<RawShape>;
public:
enum OrigoLocation {

View file

@ -167,6 +167,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
{
std::string gcode;
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
float alpha = m_wipe_tower_rotation/180.f * M_PI;
WipeTower::xy start_pos = tcr.start_pos;
WipeTower::xy end_pos = tcr.end_pos;
start_pos.rotate(alpha);
start_pos.translate(m_wipe_tower_pos);
end_pos.rotate(alpha);
end_pos.translate(m_wipe_tower_pos);
std::string tcr_rotated_gcode = rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha);
// Disable linear advance for the wipe tower operations.
gcode += "M900 K0\n";
// Move over the wipe tower.
@ -174,14 +186,14 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
gcode += gcodegen.retract(true);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, tcr.start_pos),
wipe_tower_point_to_object_point(gcodegen, start_pos),
erMixed,
"Travel to a Wipe Tower");
gcode += gcodegen.unretract();
// Let the tool change be executed by the wipe tower class.
// Inform the G-code writer about the changes done behind its back.
gcode += tcr.gcode;
gcode += tcr_rotated_gcode;
// Let the m_writer know the current extruder_id, but ignore the generated G-code.
if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))
gcodegen.writer().toolchange(new_extruder_id);
@ -195,18 +207,18 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
check_add_eol(gcode);
}
// A phony move to the end position at the wipe tower.
gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y));
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
gcodegen.writer().travel_to_xy(Pointf(end_pos.x, end_pos.y));
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
// Prepare a future wipe.
gcodegen.m_wipe.path.points.clear();
if (new_extruder_id >= 0) {
// Start the wipe at the current position.
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos));
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
// Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
WipeTower::xy((std::abs(m_left - tcr.end_pos.x) < std::abs(m_right - tcr.end_pos.x)) ? m_right : m_left,
tcr.end_pos.y)));
WipeTower::xy((std::abs(m_left - end_pos.x) < std::abs(m_right - end_pos.x)) ? m_right : m_left,
end_pos.y)));
}
// Let the planner know we are traveling between objects.
@ -214,6 +226,57 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
return gcode;
}
// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const
{
std::istringstream gcode_str(gcode_original);
std::string gcode_out;
std::string line;
WipeTower::xy pos = start_pos;
WipeTower::xy transformed_pos;
WipeTower::xy old_pos(-1000.1f, -1000.1f);
while (gcode_str) {
std::getline(gcode_str, line); // we read the gcode line by line
if (line.find("G1 ") == 0) {
std::ostringstream line_out;
std::istringstream line_str(line);
line_str >> std::noskipws; // don't skip whitespace
char ch = 0;
while (line_str >> ch) {
if (ch == 'X')
line_str >> pos.x;
else
if (ch == 'Y')
line_str >> pos.y;
else
line_out << ch;
}
transformed_pos = pos;
transformed_pos.rotate(angle);
transformed_pos.translate(translation);
if (transformed_pos != old_pos) {
line = line_out.str();
char buf[2048] = "G1";
if (transformed_pos.x != old_pos.x)
sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x);
if (transformed_pos.y != old_pos.y)
sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y);
line.replace(line.find("G1 "), 3, buf);
old_pos = transformed_pos;
}
}
gcode_out += line + "\n";
}
return gcode_out;
}
std::string WipeTowerIntegration::prime(GCode &gcodegen)
{
assert(m_layer_idx == 0);
@ -608,15 +671,18 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1)
break;
}
}
else {
} else {
// Find tool ordering for all the objects at once, and the initial extruder ID.
// If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
tool_ordering = print.m_tool_ordering.empty() ?
ToolOrdering(print, initial_extruder_id) :
print.m_tool_ordering;
initial_extruder_id = tool_ordering.first_extruder();
has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
initial_extruder_id = (has_wipe_tower && ! print.config.single_extruder_multi_material_priming) ?
// The priming towers will be skipped.
tool_ordering.all_extruders().back() :
// Don't skip the priming towers.
tool_ordering.first_extruder();
}
if (initial_extruder_id == (unsigned int)-1) {
// Nothing to print!
@ -644,6 +710,7 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
m_placeholder_parser.set("current_object_idx", 0);
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config.single_extruder_multi_material_priming);
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id);
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
@ -724,8 +791,11 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
}
}
// Set initial extruder only after custom start G-code.
_write(file, this->set_extruder(initial_extruder_id));
if (! (has_wipe_tower && print.config.single_extruder_multi_material_priming)) {
// Set initial extruder only after custom start G-code.
// Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
_write(file, this->set_extruder(initial_extruder_id));
}
// Do all objects for each layer.
if (print.config.complete_objects.value) {
@ -803,27 +873,29 @@ void GCode::_do_export(Print &print, FILE *file, GCodePreviewData *preview_data)
if (has_wipe_tower && ! layers_to_print.empty()) {
m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
_write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
_write(file, m_wipe_tower->prime(*this));
// Verify, whether the print overaps the priming extrusions.
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
for (const PrintObject *print_object : printable_objects)
bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
bbox_prime.offset(0.5f);
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
_write(file, this->retract());
_write(file, "M300 S800 P500\n");
if (bbox_prime.overlap(bbox_print)) {
// Wait for the user to remove the priming extrusions, otherwise they would
// get covered by the print.
_write(file, "M1 Remove priming towers and click button.\n");
}
else {
// Just wait for a bit to let the user check, that the priming succeeded.
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
_write(file, "M1 S10\n");
if (print.config.single_extruder_multi_material_priming) {
_write(file, m_wipe_tower->prime(*this));
// Verify, whether the print overaps the priming extrusions.
BoundingBoxf bbox_print(get_print_extrusions_extents(print));
coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
for (const PrintObject *print_object : printable_objects)
bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
bbox_prime.offset(0.5f);
// Beep for 500ms, tone 800Hz. Yet better, play some Morse.
_write(file, this->retract());
_write(file, "M300 S800 P500\n");
if (bbox_prime.overlap(bbox_print)) {
// Wait for the user to remove the priming extrusions, otherwise they would
// get covered by the print.
_write(file, "M1 Remove priming towers and click button.\n");
}
else {
// Just wait for a bit to let the user check, that the priming succeeded.
//TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
_write(file, "M1 S10\n");
}
}
}
// Extrude the layers.
@ -1003,9 +1075,10 @@ void GCode::print_machine_envelope(FILE *file, Print &print)
int(print.config.machine_max_feedrate_y.values.front() + 0.5),
int(print.config.machine_max_feedrate_z.values.front() + 0.5),
int(print.config.machine_max_feedrate_e.values.front() + 0.5));
fprintf(file, "M204 S%d T%d ; sets acceleration (S) and retract acceleration (T), mm/sec^2\n",
fprintf(file, "M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
int(print.config.machine_max_acceleration_extruding.values.front() + 0.5),
int(print.config.machine_max_acceleration_retracting.values.front() + 0.5));
int(print.config.machine_max_acceleration_retracting.values.front() + 0.5),
int(print.config.machine_max_acceleration_extruding.values.front() + 0.5));
fprintf(file, "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n",
print.config.machine_max_jerk_x.values.front(),
print.config.machine_max_jerk_y.values.front(),

View file

@ -83,8 +83,10 @@ public:
const WipeTower::ToolChangeResult &priming,
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
const WipeTower::ToolChangeResult &final_purge) :
m_left(float(print_config.wipe_tower_x.value)),
m_right(float(print_config.wipe_tower_x.value + print_config.wipe_tower_width.value)),
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
m_priming(priming),
m_tool_changes(tool_changes),
m_final_purge(final_purge),
@ -101,9 +103,14 @@ private:
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const;
// Postprocesses gcode: rotates and moves all G1 extrusions and returns result
std::string rotate_wipe_tower_moves(const std::string& gcode_original, const WipeTower::xy& start_pos, const WipeTower::xy& translation, float angle) const;
// Left / right edges of the wipe tower, for the planning of wipe moves.
const float m_left;
const float m_right;
const WipeTower::xy m_wipe_tower_pos;
const float m_wipe_tower_rotation;
// Reference to cached values at the Printer class.
const WipeTower::ToolChangeResult &m_priming;
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
@ -112,6 +119,7 @@ private:
int m_layer_idx;
int m_tool_change_idx;
bool m_brim_done;
bool i_have_brim = false;
};
class GCode {

View file

@ -134,6 +134,11 @@ BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object
// The projection does not contain the priming regions.
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z)
{
// Wipe tower extrusions are saved as if the tower was at the origin with no rotation
// We need to get position and angle of the wipe tower to transform them to actual position.
Pointf wipe_tower_pos(print.config.wipe_tower_x.value, print.config.wipe_tower_y.value);
float wipe_tower_angle = print.config.wipe_tower_rotation_angle.value;
BoundingBoxf bbox;
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.m_wipe_tower_tool_changes) {
if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z)
@ -144,6 +149,11 @@ BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_
if (e.width > 0) {
Pointf p1((&e - 1)->pos.x, (&e - 1)->pos.y);
Pointf p2(e.pos.x, e.pos.y);
p1.rotate(wipe_tower_angle);
p1.translate(wipe_tower_pos);
p2.rotate(wipe_tower_angle);
p2.translate(wipe_tower_pos);
bbox.merge(p1);
coordf_t radius = 0.5 * e.width;
bbox.min.x = std::min(bbox.min.x, std::min(p1.x, p2.x) - radius);

View file

@ -25,18 +25,30 @@ public:
bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; }
bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; }
// Rotate the point around given point about given angle (in degrees)
// shifts the result so that point of rotation is in the middle of the tower
xy rotate(const xy& origin, float width, float depth, float angle) const {
// Rotate the point around center of the wipe tower about given angle (in degrees)
xy rotate(float width, float depth, float angle) const {
xy out(0,0);
float temp_x = x - width / 2.f;
float temp_y = y - depth / 2.f;
angle *= M_PI/180.;
out.x += (temp_x - origin.x) * cos(angle) - (temp_y - origin.y) * sin(angle);
out.y += (temp_x - origin.x) * sin(angle) + (temp_y - origin.y) * cos(angle);
return out + origin;
out.x += temp_x * cos(angle) - temp_y * sin(angle) + width / 2.f;
out.y += temp_x * sin(angle) + temp_y * cos(angle) + depth / 2.f;
return out;
}
// Rotate the point around origin about given angle in degrees
void rotate(float angle) {
float temp_x = x * cos(angle) - y * sin(angle);
y = x * sin(angle) + y * cos(angle);
x = temp_x;
}
void translate(const xy& vect) {
x += vect.x;
y += vect.y;
}
float x;
float y;
};
@ -104,6 +116,9 @@ public:
// This is useful not only for the print time estimation, but also for the control of layer cooling.
float elapsed_time;
// Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
bool priming;
// Sum the total length of the extrusion.
float total_extrusion_length_in_plane() {
float e_length = 0.f;

View file

@ -5,7 +5,7 @@ TODO LIST
1. cooling moves - DONE
2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE
3. priming extrusions (last wipe must clear the color)
3. priming extrusions (last wipe must clear the color) - DONE
4. Peter's wipe tower - layer's are not exactly square
5. Peter's wipe tower - variable width for higher levels
6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer)
@ -17,7 +17,6 @@ TODO LIST
#include <assert.h>
#include <math.h>
#include <fstream>
#include <iostream>
#include <vector>
#include <numeric>
@ -68,8 +67,11 @@ public:
return *this;
}
Writer& set_initial_position(const WipeTower::xy &pos) {
m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg);
Writer& set_initial_position(const WipeTower::xy &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) {
m_wipe_tower_width = width;
m_wipe_tower_depth = depth;
m_internal_angle = internal_angle;
m_start_pos = WipeTower::xy(pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle);
m_current_pos = pos;
return *this;
}
@ -81,9 +83,6 @@ public:
Writer& set_extrusion_flow(float flow)
{ m_extrusion_flow = flow; return *this; }
Writer& set_rotation(WipeTower::xy& pos, float width, float depth, float angle)
{ m_wipe_tower_pos = pos; m_wipe_tower_width = width; m_wipe_tower_depth=depth; m_angle_deg = angle; return (*this); }
Writer& set_y_shift(float shift) {
m_current_pos.y -= shift-m_y_shift;
@ -110,7 +109,7 @@ public:
float y() const { return m_current_pos.y; }
const WipeTower::xy& pos() const { return m_current_pos; }
const WipeTower::xy start_pos_rotated() const { return m_start_pos; }
const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg); }
const WipeTower::xy pos_rotated() const { return WipeTower::xy(m_current_pos, 0.f, m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle); }
float elapsed_time() const { return m_elapsed_time; }
// Extrude with an explicitely provided amount of extrusion.
@ -125,9 +124,9 @@ public:
double len = sqrt(dx*dx+dy*dy);
// For rotated wipe tower, transform position to printer coordinates
WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we are
WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_angle_deg)); // this is where we want to go
// Now do the "internal rotation" with respect to the wipe tower center
WipeTower::xy rotated_current_pos(WipeTower::xy(m_current_pos,0.f,m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we are
WipeTower::xy rot(WipeTower::xy(x,y+m_y_shift).rotate(m_wipe_tower_width, m_wipe_tower_depth, m_internal_angle)); // this is where we want to go
if (! m_preview_suppressed && e > 0.f && len > 0.) {
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
@ -147,6 +146,7 @@ public:
if (std::abs(rot.y - rotated_current_pos.y) > EPSILON)
m_gcode += set_format_Y(rot.y);
if (e != 0.f)
m_gcode += set_format_E(e);
@ -397,9 +397,8 @@ private:
std::string m_gcode;
std::vector<WipeTower::Extrusion> m_extrusions;
float m_elapsed_time;
float m_angle_deg = 0.f;
float m_internal_angle = 0.f;
float m_y_shift = 0.f;
WipeTower::xy m_wipe_tower_pos;
float m_wipe_tower_width = 0.f;
float m_wipe_tower_depth = 0.f;
float m_last_fan_speed = 0.f;
@ -539,6 +538,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime(
m_print_brim = true;
ToolChangeResult result;
result.priming = true;
result.print_z = this->m_z_pos;
result.layer_height = this->m_layer_height;
result.gcode = writer.gcode();
@ -575,7 +575,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
}
box_coordinates cleaning_box(
m_wipe_tower_pos + xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
xy(m_perimeter_width / 2.f, m_perimeter_width / 2.f),
m_wipe_tower_width - m_perimeter_width,
(tool != (unsigned int)(-1) ? /*m_layer_info->depth*/wipe_area+m_depth_traversed-0.5*m_perimeter_width
: m_wipe_tower_depth-m_perimeter_width));
@ -584,7 +584,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_initial_tool(m_current_tool)
.set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
.set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f))
.append(";--------------------\n"
"; CP TOOLCHANGE START\n")
@ -594,7 +593,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
.speed_override(100);
xy initial_position = cleaning_box.ld + WipeTower::xy(0.f,m_depth_traversed);
writer.set_initial_position(initial_position);
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
// Increase the extruder driver current to allow fast ramming.
writer.set_extruder_trimpot(750);
@ -616,11 +615,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
if (last_change_in_layer) {// draw perimeter line
writer.set_y_shift(m_y_shift);
if (m_peters_wipe_tower)
writer.rectangle(m_wipe_tower_pos,m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth);
writer.rectangle(WipeTower::xy(0.f, 0.f),m_layer_info->depth + 3*m_perimeter_width,m_wipe_tower_depth);
else {
writer.rectangle(m_wipe_tower_pos,m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
writer.rectangle(WipeTower::xy(0.f, 0.f),m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle
writer.travel(m_wipe_tower_pos.x + (writer.x()> (m_wipe_tower_pos.x + m_wipe_tower_width) / 2.f ? 0.f : m_wipe_tower_width), writer.y());
writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y());
}
}
}
@ -634,6 +633,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
"\n\n");
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
result.layer_height = this->m_layer_height;
result.gcode = writer.gcode();
@ -647,7 +647,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::tool_change(unsigned int tool, boo
WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, float y_offset)
{
const box_coordinates wipeTower_box(
m_wipe_tower_pos,
WipeTower::xy(0.f, 0.f),
m_wipe_tower_width,
m_wipe_tower_depth);
@ -655,12 +655,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
writer.set_extrusion_flow(m_extrusion_flow * 1.1f)
.set_z(m_z_pos) // Let the writer know the current Z position as a base for Z-hop.
.set_initial_tool(m_current_tool)
.set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
.append(";-------------------------------------\n"
"; CP WIPE TOWER FIRST LAYER BRIM START\n");
xy initial_position = wipeTower_box.lu - xy(m_perimeter_width * 6.f, 0);
writer.set_initial_position(initial_position);
writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
writer.extrude_explicit(wipeTower_box.ld - xy(m_perimeter_width * 6.f, 0), // Prime the extruder left of the wipe tower.
1.5f * m_extrusion_flow * (wipeTower_box.lu.y - wipeTower_box.ld.y), 2400);
@ -685,6 +684,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo
m_print_brim = false; // Mark the brim as extruded
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
result.layer_height = this->m_layer_height;
result.gcode = writer.gcode();
@ -724,7 +724,7 @@ void WipeTowerPrusaMM::toolchange_Unload(
if (m_layer_info > m_plan.begin() && m_layer_info < m_plan.end() && (m_layer_info-1!=m_plan.begin() || !m_adhesion )) {
// this is y of the center of previous sparse infill border
float sparse_beginning_y = m_wipe_tower_pos.y;
float sparse_beginning_y = 0.f;
if (m_current_shape == SHAPE_REVERSED)
sparse_beginning_y += ((m_layer_info-1)->depth - (m_layer_info-1)->toolchanges_depth())
- ((m_layer_info)->depth-(m_layer_info)->toolchanges_depth()) ;
@ -742,7 +742,7 @@ void WipeTowerPrusaMM::toolchange_Unload(
for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange
if (tch.old_tool == m_current_tool) {
sum_of_depths += tch.ramming_depth;
float ramming_end_y = m_wipe_tower_pos.y + sum_of_depths;
float ramming_end_y = sum_of_depths;
ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line
// debugging:
@ -950,7 +950,7 @@ void WipeTowerPrusaMM::toolchange_Wipe(
if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) {
m_left_to_right = !m_left_to_right;
writer.travel(writer.x(), writer.y() - dy)
.travel(m_wipe_tower_pos.x + (m_left_to_right ? m_wipe_tower_width : 0.f), writer.y());
.travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y());
}
writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow.
@ -969,7 +969,6 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
writer.set_extrusion_flow(m_extrusion_flow)
.set_z(m_z_pos)
.set_initial_tool(m_current_tool)
.set_rotation(m_wipe_tower_pos, m_wipe_tower_width, m_wipe_tower_depth, m_wipe_tower_rotation_angle)
.set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED && !m_peters_wipe_tower ? m_layer_info->toolchanges_depth() : 0.f))
.append(";--------------------\n"
"; CP EMPTY GRID START\n")
@ -978,14 +977,12 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
// Slow down on the 1st layer.
float speed_factor = m_is_first_layer ? 0.5f : 1.f;
float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth();
box_coordinates fill_box(m_wipe_tower_pos + xy(m_perimeter_width, m_depth_traversed + m_perimeter_width),
box_coordinates fill_box(xy(m_perimeter_width, m_depth_traversed + m_perimeter_width),
m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width);
if (m_left_to_right) // so there is never a diagonal travel
writer.set_initial_position(fill_box.ru);
else
writer.set_initial_position(fill_box.lu);
writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel
m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
box_coordinates box = fill_box;
for (int i=0;i<2;++i) {
@ -1044,6 +1041,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::finish_layer()
m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
ToolChangeResult result;
result.priming = false;
result.print_z = this->m_z_pos;
result.layer_height = this->m_layer_height;
result.gcode = writer.gcode();
@ -1165,9 +1163,9 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
{
set_layer(layer.z,layer.height,0,layer.z == m_plan.front().z,layer.z == m_plan.back().z);
if (m_peters_wipe_tower)
m_wipe_tower_rotation_angle += 90.f;
m_internal_rotation += 90.f;
else
m_wipe_tower_rotation_angle += 180.f;
m_internal_rotation += 180.f;
if (!m_peters_wipe_tower && m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width)
m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f;
@ -1188,7 +1186,7 @@ void WipeTowerPrusaMM::generate(std::vector<std::vector<WipeTower::ToolChangeRes
last_toolchange.gcode += buf;
}
last_toolchange.gcode += finish_layer_toolchange.gcode;
last_toolchange.extrusions.insert(last_toolchange.extrusions.end(),finish_layer_toolchange.extrusions.begin(),finish_layer_toolchange.extrusions.end());
last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end());
last_toolchange.end_pos = finish_layer_toolchange.end_pos;
}
else

View file

@ -102,6 +102,8 @@ public:
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
void generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &result);
float get_depth() const { return m_wipe_tower_depth; }
// Switch to a next layer.
@ -189,6 +191,7 @@ private:
float m_wipe_tower_width; // Width of the wipe tower.
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
float m_internal_rotation = 0.f;
float m_y_shift = 0.f; // y shift passed to writer
float m_z_pos = 0.f; // Current Z position.
float m_layer_height = 0.f; // Current layer height.

View file

@ -1185,11 +1185,25 @@ namespace Slic3r {
{
PROFILE_FUNC();
float value;
if (line.has_value('S', value))
if (line.has_value('S', value)) {
// Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware,
// and it is also generated by Slic3r to control acceleration per extrusion type
// (there is a separate acceleration settings in Slicer for perimeter, first layer etc).
set_acceleration(value);
if (line.has_value('T', value))
set_retract_acceleration(value);
if (line.has_value('T', value))
set_retract_acceleration(value);
} else {
// New acceleration format, compatible with the upstream Marlin.
if (line.has_value('P', value))
set_acceleration(value);
if (line.has_value('R', value))
set_retract_acceleration(value);
if (line.has_value('T', value)) {
// Interpret the T value as the travel acceleration in the new Marlin format.
//FIXME Prusa3D firmware currently does not support travel acceleration value independent from the extruding acceleration value.
// set_travel_acceleration(value);
}
}
}
void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line)

View file

@ -7,11 +7,6 @@
#include "Format/STL.hpp"
#include "Format/3mf.hpp"
#include <numeric>
#include <libnest2d.h>
#include <ClipperUtils.hpp>
#include "slic3r/GUI/GUI.hpp"
#include <float.h>
#include <boost/algorithm/string/predicate.hpp>
@ -304,369 +299,36 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
return result;
}
namespace arr {
using namespace libnest2d;
std::string toString(const Model& model, bool holes = true) {
std::stringstream ss;
ss << "{\n";
for(auto objptr : model.objects) {
if(!objptr) continue;
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(!objinst) continue;
Slic3r::TriangleMesh tmpmesh = rmesh;
tmpmesh.scale(objinst->scaling_factor);
objinst->transform_mesh(&tmpmesh);
ExPolygons expolys = tmpmesh.horizontal_projection();
for(auto& expoly_complex : expolys) {
auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
if(tmp.empty()) continue;
auto expoly = tmp.front();
expoly.contour.make_clockwise();
for(auto& h : expoly.holes) h.make_counter_clockwise();
ss << "\t{\n";
ss << "\t\t{\n";
for(auto v : expoly.contour.points) ss << "\t\t\t{"
<< v.x << ", "
<< v.y << "},\n";
{
auto v = expoly.contour.points.front();
ss << "\t\t\t{" << v.x << ", " << v.y << "},\n";
}
ss << "\t\t},\n";
// Holes:
ss << "\t\t{\n";
if(holes) for(auto h : expoly.holes) {
ss << "\t\t\t{\n";
for(auto v : h.points) ss << "\t\t\t\t{"
<< v.x << ", "
<< v.y << "},\n";
{
auto v = h.points.front();
ss << "\t\t\t\t{" << v.x << ", " << v.y << "},\n";
}
ss << "\t\t\t},\n";
}
ss << "\t\t},\n";
ss << "\t},\n";
}
}
}
ss << "}\n";
return ss.str();
}
void toSVG(SVG& svg, const Model& model) {
for(auto objptr : model.objects) {
if(!objptr) continue;
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(!objinst) continue;
Slic3r::TriangleMesh tmpmesh = rmesh;
tmpmesh.scale(objinst->scaling_factor);
objinst->transform_mesh(&tmpmesh);
ExPolygons expolys = tmpmesh.horizontal_projection();
svg.draw(expolys);
}
}
}
// A container which stores a pointer to the 3D object and its projected
// 2D shape from top view.
using ShapeData2D =
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
ShapeData2D ret;
auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
[](size_t s, ModelObject* o){
return s + o->instances.size();
});
ret.reserve(s);
for(auto objptr : model.objects) {
if(objptr) {
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(objinst) {
Slic3r::TriangleMesh tmpmesh = rmesh;
ClipperLib::PolygonImpl pn;
tmpmesh.scale(objinst->scaling_factor);
// TODO export the exact 2D projection
auto p = tmpmesh.convex_hull();
p.make_clockwise();
p.append(p.first_point());
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
// Efficient conversion to item.
Item item(std::move(pn));
// Invalid geometries would throw exceptions when arranging
if(item.vertexCount() > 3) {
item.rotation(objinst->rotation);
item.translation( {
ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR),
ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR)
});
ret.emplace_back(objinst, item);
}
}
}
}
}
return ret;
}
/**
* \brief Arranges the model objects on the screen.
*
* The arrangement considers multiple bins (aka. print beds) for placing all
* the items provided in the model argument. If the items don't fit on one
* print bed, the remaining will be placed onto newly created print beds.
* The first_bin_only parameter, if set to true, disables this behaviour and
* makes sure that only one print bed is filled and the remaining items will be
* untouched. When set to false, the items which could not fit onto the
* print bed will be placed next to the print bed so the user should see a
* pile of items on the print bed and some other piles outside the print
* area that can be dragged later onto the print bed as a group.
*
* \param model The model object with the 3D content.
* \param dist The minimum distance which is allowed for any pair of items
* on the print bed in any direction.
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
* for bin packing.
* \param first_bin_only This parameter controls whether to place the
* remaining items which do not fit onto the print area next to the print
* bed or leave them untouched (let the user arrange them by hand or remove
* them).
*/
bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
bool first_bin_only,
std::function<void(unsigned)> progressind)
{
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
bool ret = true;
// Create the arranger config
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
// Get the 2D projected shapes with their 3D model instance pointers
auto shapemap = arr::projectModelFromTop(model);
bool hasbin = bb != nullptr && bb->defined;
double area_max = 0;
// Copy the references for the shapes only as the arranger expects a
// sequence of objects convertible to Item or ClipperPolygon
std::vector<std::reference_wrapper<Item>> shapes;
shapes.reserve(shapemap.size());
std::for_each(shapemap.begin(), shapemap.end(),
[&shapes, min_obj_distance, &area_max, hasbin]
(ShapeData2D::value_type& it)
{
shapes.push_back(std::ref(it.second));
});
Box bin;
if(hasbin) {
// Scale up the bounding box to clipper scale.
BoundingBoxf bbb = *bb;
bbb.scale(1.0/SCALING_FACTOR);
bin = Box({
static_cast<libnest2d::Coord>(bbb.min.x),
static_cast<libnest2d::Coord>(bbb.min.y)
},
{
static_cast<libnest2d::Coord>(bbb.max.x),
static_cast<libnest2d::Coord>(bbb.max.y)
});
}
// Will use the DJD selection heuristic with the BottomLeft placement
// strategy
using Arranger = Arranger<NfpPlacer, FirstFitSelection>;
using PConf = Arranger::PlacementConfig;
using SConf = Arranger::SelectionConfig;
PConf pcfg; // Placement configuration
SConf scfg; // Selection configuration
// Align the arranged pile into the center of the bin
pcfg.alignment = PConf::Alignment::CENTER;
// Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::CENTER;
// TODO cannot use rotations until multiple objects of same geometry can
// handle different rotations
// arranger.useMinimumBoundigBoxRotation();
pcfg.rotations = { 0.0 };
// Magic: we will specify what is the goal of arrangement... In this case
// we override the default object function to make the larger items go into
// the center of the pile and smaller items orbit it so the resulting pile
// has a circle-like shape. This is good for the print bed's heat profile.
// We alse sacrafice a bit of pack efficiency for this to work. As a side
// effect, the arrange procedure is a lot faster (we do not need to
// calculate the convex hulls)
pcfg.object_function = [bin, hasbin](
NfpPlacer::Pile pile, // The currently arranged pile
double /*area*/, // Sum area of items (not needed)
double norm, // A norming factor for physical dimensions
double penality) // Min penality in case of bad arrangement
{
auto bb = ShapeLike::boundingBox(pile);
// We get the current item that's being evaluated.
auto& sh = pile.back();
// We retrieve the reference point of this item
auto rv = Nfp::referenceVertex(sh);
// We get the distance of the reference point from the center of the
// heat bed
auto c = bin.center();
auto d = PointLike::distance(rv, c);
// The score will be the normalized distance which will be minimized,
// effectively creating a circle shaped pile of items
double score = double(d)/norm;
// If it does not fit into the print bed we will beat it
// with a large penality. If we would not do this, there would be only
// one big pile that doesn't care whether it fits onto the print bed.
if(hasbin && !NfpPlacer::wouldFit(bb, bin)) score = 2*penality - score;
return score;
};
// Create the arranger object
Arranger arranger(bin, min_obj_distance, pcfg, scfg);
// Set the progress indicator for the arranger.
arranger.progressIndicator(progressind);
// Arrange and return the items with their respective indices within the
// input sequence.
auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end());
auto applyResult = [&shapemap](ArrangeResult::value_type& group,
Coord batch_offset)
{
for(auto& r : group) {
auto idx = r.first; // get the original item index
Item& item = r.second; // get the item itself
// Get the model instance from the shapemap using the index
ModelInstance *inst_ptr = shapemap[idx].first;
// Get the tranformation data from the item object and scale it
// appropriately
auto off = item.translation();
Radians rot = item.rotation();
Pointf foff(off.X*SCALING_FACTOR + batch_offset,
off.Y*SCALING_FACTOR);
// write the tranformation data into the model instance
inst_ptr->rotation = rot;
inst_ptr->offset = foff;
}
};
if(first_bin_only) {
applyResult(result.front(), 0);
} else {
const auto STRIDE_PADDING = 1.2;
Coord stride = static_cast<Coord>(STRIDE_PADDING*
bin.width()*SCALING_FACTOR);
Coord batch_offset = 0;
for(auto& group : result) {
applyResult(group, batch_offset);
// Only the first pack group can be placed onto the print bed. The
// other objects which could not fit will be placed next to the
// print bed
batch_offset += stride;
}
}
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
return ret && result.size() == 1;
}
}
/* arrange objects preserving their instance count
but altering their instance positions */
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb,
std::function<void(unsigned)> progressind)
bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
{
bool ret = false;
if(bb != nullptr && bb->defined) {
// Despite the new arrange is able to run without a specified bin,
// the perl testsuit still fails for this case. For now the safest
// thing to do is to use the new arrange only when a proper bin is
// specified.
ret = arr::arrange(*this, dist, bb, false, progressind);
} else {
// get the (transformed) size of each instance so that we take
// into account their different transformations when packing
Pointfs instance_sizes;
Pointfs instance_centers;
for (const ModelObject *o : this->objects)
for (size_t i = 0; i < o->instances.size(); ++ i) {
// an accurate snug bounding box around the transformed mesh.
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
instance_sizes.push_back(bbox.size());
instance_centers.push_back(bbox.center());
}
Pointfs positions;
if (! _arrange(instance_sizes, dist, bb, positions))
return false;
size_t idx = 0;
for (ModelObject *o : this->objects) {
for (ModelInstance *i : o->instances) {
i->offset = positions[idx] - instance_centers[idx];
++ idx;
}
o->invalidate_bounding_box();
// get the (transformed) size of each instance so that we take
// into account their different transformations when packing
Pointfs instance_sizes;
Pointfs instance_centers;
for (const ModelObject *o : this->objects)
for (size_t i = 0; i < o->instances.size(); ++ i) {
// an accurate snug bounding box around the transformed mesh.
BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
instance_sizes.push_back(bbox.size());
instance_centers.push_back(bbox.center());
}
Pointfs positions;
if (! _arrange(instance_sizes, dist, bb, positions))
return false;
size_t idx = 0;
for (ModelObject *o : this->objects) {
for (ModelInstance *i : o->instances) {
i->offset = positions[idx] - instance_centers[idx];
++ idx;
}
o->invalidate_bounding_box();
}
return ret;
return true;
}
// Duplicate the entire model preserving instance relative positions.
@ -1109,9 +771,23 @@ void ModelObject::scale(const Pointf3 &versor)
void ModelObject::rotate(float angle, const Axis &axis)
{
float min_z = FLT_MAX;
for (ModelVolume *v : this->volumes)
{
v->mesh.rotate(angle, axis);
this->origin_translation = Pointf3(0,0,0);
min_z = std::min(min_z, v->mesh.stl.stats.min.z);
}
if (min_z != 0.0f)
{
// translate the object so that its minimum z lays on the bed
for (ModelVolume *v : this->volumes)
{
v->mesh.translate(0.0f, 0.0f, -min_z);
}
}
this->origin_translation = Pointf3(0, 0, 0);
this->invalidate_bounding_box();
}

View file

@ -290,8 +290,7 @@ public:
void center_instances_around_point(const Pointf &point);
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
TriangleMesh mesh() const;
bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL,
std::function<void(unsigned)> progressind = [](unsigned){});
bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
// Croaks if the duplicated objects do not fit the print bed.
void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);

View file

@ -0,0 +1,597 @@
#ifndef MODELARRANGE_HPP
#define MODELARRANGE_HPP
#include "Model.hpp"
#include "SVG.hpp"
#include <libnest2d.h>
#include <numeric>
#include <ClipperUtils.hpp>
#include <boost/geometry/index/rtree.hpp>
namespace Slic3r {
namespace arr {
using namespace libnest2d;
std::string toString(const Model& model, bool holes = true) {
std::stringstream ss;
ss << "{\n";
for(auto objptr : model.objects) {
if(!objptr) continue;
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(!objinst) continue;
Slic3r::TriangleMesh tmpmesh = rmesh;
tmpmesh.scale(objinst->scaling_factor);
objinst->transform_mesh(&tmpmesh);
ExPolygons expolys = tmpmesh.horizontal_projection();
for(auto& expoly_complex : expolys) {
auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
if(tmp.empty()) continue;
auto expoly = tmp.front();
expoly.contour.make_clockwise();
for(auto& h : expoly.holes) h.make_counter_clockwise();
ss << "\t{\n";
ss << "\t\t{\n";
for(auto v : expoly.contour.points) ss << "\t\t\t{"
<< v.x << ", "
<< v.y << "},\n";
{
auto v = expoly.contour.points.front();
ss << "\t\t\t{" << v.x << ", " << v.y << "},\n";
}
ss << "\t\t},\n";
// Holes:
ss << "\t\t{\n";
if(holes) for(auto h : expoly.holes) {
ss << "\t\t\t{\n";
for(auto v : h.points) ss << "\t\t\t\t{"
<< v.x << ", "
<< v.y << "},\n";
{
auto v = h.points.front();
ss << "\t\t\t\t{" << v.x << ", " << v.y << "},\n";
}
ss << "\t\t\t},\n";
}
ss << "\t\t},\n";
ss << "\t},\n";
}
}
}
ss << "}\n";
return ss.str();
}
void toSVG(SVG& svg, const Model& model) {
for(auto objptr : model.objects) {
if(!objptr) continue;
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(!objinst) continue;
Slic3r::TriangleMesh tmpmesh = rmesh;
tmpmesh.scale(objinst->scaling_factor);
objinst->transform_mesh(&tmpmesh);
ExPolygons expolys = tmpmesh.horizontal_projection();
svg.draw(expolys);
}
}
}
namespace bgi = boost::geometry::index;
using SpatElement = std::pair<Box, unsigned>;
using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >;
std::tuple<double /*score*/, Box /*farthest point from bin center*/>
objfunc(const PointImpl& bincenter,
double /*bin_area*/,
ShapeLike::Shapes<PolygonImpl>& pile, // The currently arranged pile
double /*pile_area*/,
const Item &item,
double norm, // A norming factor for physical dimensions
std::vector<double>& areacache, // pile item areas will be cached
// a spatial index to quickly get neighbors of the candidate item
SpatIndex& spatindex
)
{
using pl = PointLike;
using sl = ShapeLike;
static const double BIG_ITEM_TRESHOLD = 0.2;
static const double ROUNDNESS_RATIO = 0.5;
static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO;
// We will treat big items (compared to the print bed) differently
auto normarea = [norm](double area) { return std::sqrt(area)/norm; };
// If a new bin has been created:
if(pile.size() < areacache.size()) {
areacache.clear();
spatindex.clear();
}
// We must fill the caches:
int idx = 0;
for(auto& p : pile) {
if(idx == areacache.size()) {
areacache.emplace_back(sl::area(p));
if(normarea(areacache[idx]) > BIG_ITEM_TRESHOLD)
spatindex.insert({sl::boundingBox(p), idx});
}
idx++;
}
// Candidate item bounding box
auto ibb = item.boundingBox();
// Calculate the full bounding box of the pile with the candidate item
pile.emplace_back(item.transformedShape());
auto fullbb = ShapeLike::boundingBox(pile);
pile.pop_back();
// The bounding box of the big items (they will accumulate in the center
// of the pile
Box bigbb;
if(spatindex.empty()) bigbb = fullbb;
else {
auto boostbb = spatindex.bounds();
boost::geometry::convert(boostbb, bigbb);
}
// The size indicator of the candidate item. This is not the area,
// but almost...
double item_normarea = normarea(item.area());
// Will hold the resulting score
double score = 0;
if(item_normarea > BIG_ITEM_TRESHOLD) {
// This branch is for the bigger items..
// Here we will use the closest point of the item bounding box to
// the already arranged pile. So not the bb center nor the a choosen
// corner but whichever is the closest to the center. This will
// prevent some unwanted strange arrangements.
auto minc = ibb.minCorner(); // bottom left corner
auto maxc = ibb.maxCorner(); // top right corner
// top left and bottom right corners
auto top_left = PointImpl{getX(minc), getY(maxc)};
auto bottom_right = PointImpl{getX(maxc), getY(minc)};
// Now the distance of the gravity center will be calculated to the
// five anchor points and the smallest will be chosen.
std::array<double, 5> dists;
auto cc = fullbb.center(); // The gravity center
dists[0] = pl::distance(minc, cc);
dists[1] = pl::distance(maxc, cc);
dists[2] = pl::distance(ibb.center(), cc);
dists[3] = pl::distance(top_left, cc);
dists[4] = pl::distance(bottom_right, cc);
// The smalles distance from the arranged pile center:
auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
// Density is the pack density: how big is the arranged pile
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
// Prepare a variable for the alignment score.
// This will indicate: how well is the candidate item aligned with
// its neighbors. We will check the aligment with all neighbors and
// return the score for the best alignment. So it is enough for the
// candidate to be aligned with only one item.
auto alignment_score = std::numeric_limits<double>::max();
auto& trsh = item.transformedShape();
auto querybb = item.boundingBox();
// Query the spatial index for the neigbours
std::vector<SpatElement> result;
spatindex.query(bgi::intersects(querybb), std::back_inserter(result));
for(auto& e : result) { // now get the score for the best alignment
auto idx = e.second;
auto& p = pile[idx];
auto parea = areacache[idx];
auto bb = sl::boundingBox(sl::Shapes<PolygonImpl>{p, trsh});
auto bbarea = bb.area();
auto ascore = 1.0 - (item.area() + parea)/bbarea;
if(ascore < alignment_score) alignment_score = ascore;
}
// The final mix of the score is the balance between the distance
// from the full pile center, the pack density and the
// alignment with the neigbours
auto C = 0.33;
score = C * dist + C * density + C * alignment_score;
} else if( item_normarea < BIG_ITEM_TRESHOLD && spatindex.empty()) {
// If there are no big items, only small, we should consider the
// density here as well to not get silly results
auto bindist = pl::distance(ibb.center(), bincenter) / norm;
auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
score = ROUNDNESS_RATIO * bindist + DENSITY_RATIO * density;
} else {
// Here there are the small items that should be placed around the
// already processed bigger items.
// No need to play around with the anchor points, the center will be
// just fine for small items
score = pl::distance(ibb.center(), bigbb.center()) / norm;
}
return std::make_tuple(score, fullbb);
}
template<class PConf>
void fillConfig(PConf& pcfg) {
// Align the arranged pile into the center of the bin
pcfg.alignment = PConf::Alignment::CENTER;
// Start placing the items from the center of the print bed
pcfg.starting_point = PConf::Alignment::CENTER;
// TODO cannot use rotations until multiple objects of same geometry can
// handle different rotations
// arranger.useMinimumBoundigBoxRotation();
pcfg.rotations = { 0.0 };
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
pcfg.accuracy = 0.6f;
}
template<class TBin>
class AutoArranger {};
template<class TBin>
class _ArrBase {
protected:
using Placer = strategies::_NofitPolyPlacer<PolygonImpl, TBin>;
using Selector = FirstFitSelection;
using Packer = Arranger<Placer, Selector>;
using PConfig = typename Packer::PlacementConfig;
using Distance = TCoord<PointImpl>;
using Pile = ShapeLike::Shapes<PolygonImpl>;
Packer pck_;
PConfig pconf_; // Placement configuration
double bin_area_;
std::vector<double> areacache_;
SpatIndex rtree_;
public:
_ArrBase(const TBin& bin, Distance dist,
std::function<void(unsigned)> progressind):
pck_(bin, dist), bin_area_(ShapeLike::area<PolygonImpl>(bin))
{
fillConfig(pconf_);
pck_.progressIndicator(progressind);
}
template<class...Args> inline IndexedPackGroup operator()(Args&&...args) {
areacache_.clear();
return pck_.arrangeIndexed(std::forward<Args>(args)...);
}
};
template<>
class AutoArranger<Box>: public _ArrBase<Box> {
public:
AutoArranger(const Box& bin, Distance dist,
std::function<void(unsigned)> progressind):
_ArrBase<Box>(bin, dist, progressind)
{
pconf_.object_function = [this, bin] (
Pile& pile,
const Item &item,
double pile_area,
double norm,
double /*penality*/) {
auto result = objfunc(bin.center(), bin_area_, pile,
pile_area, item, norm, areacache_, rtree_);
double score = std::get<0>(result);
auto& fullbb = std::get<1>(result);
auto wdiff = fullbb.width() - bin.width();
auto hdiff = fullbb.height() - bin.height();
if(wdiff > 0) score += std::pow(wdiff, 2) / norm;
if(hdiff > 0) score += std::pow(hdiff, 2) / norm;
return score;
};
pck_.configure(pconf_);
}
};
template<>
class AutoArranger<PolygonImpl>: public _ArrBase<PolygonImpl> {
public:
AutoArranger(const PolygonImpl& bin, Distance dist,
std::function<void(unsigned)> progressind):
_ArrBase<PolygonImpl>(bin, dist, progressind)
{
pconf_.object_function = [this, &bin] (
Pile& pile,
const Item &item,
double pile_area,
double norm,
double /*penality*/) {
auto binbb = ShapeLike::boundingBox(bin);
auto result = objfunc(binbb.center(), bin_area_, pile,
pile_area, item, norm, areacache_, rtree_);
double score = std::get<0>(result);
pile.emplace_back(item.transformedShape());
auto chull = ShapeLike::convexHull(pile);
pile.pop_back();
// If it does not fit into the print bed we will beat it with a
// large penality. If we would not do this, there would be only one
// big pile that doesn't care whether it fits onto the print bed.
if(!Placer::wouldFit(chull, bin)) score += norm;
return score;
};
pck_.configure(pconf_);
}
};
template<> // Specialization with no bin
class AutoArranger<bool>: public _ArrBase<Box> {
public:
AutoArranger(Distance dist, std::function<void(unsigned)> progressind):
_ArrBase<Box>(Box(0, 0), dist, progressind)
{
this->pconf_.object_function = [this] (
Pile& pile,
const Item &item,
double pile_area,
double norm,
double /*penality*/) {
auto result = objfunc({0, 0}, 0, pile, pile_area,
item, norm, areacache_, rtree_);
return std::get<0>(result);
};
this->pck_.configure(pconf_);
}
};
// A container which stores a pointer to the 3D object and its projected
// 2D shape from top view.
using ShapeData2D =
std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
ShapeData2D ret;
auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
[](size_t s, ModelObject* o){
return s + o->instances.size();
});
ret.reserve(s);
for(auto objptr : model.objects) {
if(objptr) {
auto rmesh = objptr->raw_mesh();
for(auto objinst : objptr->instances) {
if(objinst) {
Slic3r::TriangleMesh tmpmesh = rmesh;
ClipperLib::PolygonImpl pn;
tmpmesh.scale(objinst->scaling_factor);
// TODO export the exact 2D projection
auto p = tmpmesh.convex_hull();
p.make_clockwise();
p.append(p.first_point());
pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
// Efficient conversion to item.
Item item(std::move(pn));
// Invalid geometries would throw exceptions when arranging
if(item.vertexCount() > 3) {
item.rotation(objinst->rotation);
item.translation( {
ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR),
ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR)
});
ret.emplace_back(objinst, item);
}
}
}
}
}
return ret;
}
enum BedShapeHint {
BOX,
CIRCLE,
IRREGULAR,
WHO_KNOWS
};
BedShapeHint bedShape(const Slic3r::Polyline& /*bed*/) {
// Determine the bed shape by hand
return BOX;
}
void applyResult(
IndexedPackGroup::value_type& group,
Coord batch_offset,
ShapeData2D& shapemap)
{
for(auto& r : group) {
auto idx = r.first; // get the original item index
Item& item = r.second; // get the item itself
// Get the model instance from the shapemap using the index
ModelInstance *inst_ptr = shapemap[idx].first;
// Get the tranformation data from the item object and scale it
// appropriately
auto off = item.translation();
Radians rot = item.rotation();
Pointf foff(off.X*SCALING_FACTOR + batch_offset,
off.Y*SCALING_FACTOR);
// write the tranformation data into the model instance
inst_ptr->rotation = rot;
inst_ptr->offset = foff;
}
}
/**
* \brief Arranges the model objects on the screen.
*
* The arrangement considers multiple bins (aka. print beds) for placing all
* the items provided in the model argument. If the items don't fit on one
* print bed, the remaining will be placed onto newly created print beds.
* The first_bin_only parameter, if set to true, disables this behaviour and
* makes sure that only one print bed is filled and the remaining items will be
* untouched. When set to false, the items which could not fit onto the
* print bed will be placed next to the print bed so the user should see a
* pile of items on the print bed and some other piles outside the print
* area that can be dragged later onto the print bed as a group.
*
* \param model The model object with the 3D content.
* \param dist The minimum distance which is allowed for any pair of items
* on the print bed in any direction.
* \param bb The bounding box of the print bed. It corresponds to the 'bin'
* for bin packing.
* \param first_bin_only This parameter controls whether to place the
* remaining items which do not fit onto the print area next to the print
* bed or leave them untouched (let the user arrange them by hand or remove
* them).
*/
bool arrange(Model &model, coordf_t min_obj_distance,
const Slic3r::Polyline& bed,
BedShapeHint bedhint,
bool first_bin_only,
std::function<void(unsigned)> progressind)
{
using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
bool ret = true;
// 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
std::vector<std::reference_wrapper<Item>> shapes;
shapes.reserve(shapemap.size());
std::for_each(shapemap.begin(), shapemap.end(),
[&shapes] (ShapeData2D::value_type& it)
{
shapes.push_back(std::ref(it.second));
});
IndexedPackGroup result;
BoundingBox bbb(bed.points);
auto binbb = Box({
static_cast<libnest2d::Coord>(bbb.min.x),
static_cast<libnest2d::Coord>(bbb.min.y)
},
{
static_cast<libnest2d::Coord>(bbb.max.x),
static_cast<libnest2d::Coord>(bbb.max.y)
});
switch(bedhint) {
case BOX: {
// Create the arranger for the box shaped bed
AutoArranger<Box> arrange(binbb, min_obj_distance, progressind);
// Arrange and return the items with their respective indices within the
// input sequence.
result = arrange(shapes.begin(), shapes.end());
break;
}
case CIRCLE:
break;
case IRREGULAR:
case WHO_KNOWS: {
using P = libnest2d::PolygonImpl;
auto ctour = Slic3rMultiPoint_to_ClipperPath(bed);
P irrbed = ShapeLike::create<PolygonImpl>(std::move(ctour));
// std::cout << ShapeLike::toString(irrbed) << std::endl;
AutoArranger<P> arrange(irrbed, min_obj_distance, progressind);
// Arrange and return the items with their respective indices within the
// input sequence.
result = arrange(shapes.begin(), shapes.end());
break;
}
};
if(first_bin_only) {
applyResult(result.front(), 0, shapemap);
} else {
const auto STRIDE_PADDING = 1.2;
Coord stride = static_cast<Coord>(STRIDE_PADDING*
binbb.width()*SCALING_FACTOR);
Coord batch_offset = 0;
for(auto& group : result) {
applyResult(group, batch_offset, shapemap);
// Only the first pack group can be placed onto the print bed. The
// other objects which could not fit will be placed next to the
// print bed
batch_offset += stride;
}
}
for(auto objptr : model.objects) objptr->invalidate_bounding_box();
return ret && result.size() == 1;
}
}
}
#endif // MODELARRANGE_HPP

View file

@ -155,6 +155,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
"retract_restart_extra",
"retract_restart_extra_toolchange",
"retract_speed",
"single_extruder_multi_material_priming",
"slowdown_below_layer_time",
"standby_temperature_delta",
"start_gcode",
@ -166,7 +167,10 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
"use_relative_e_distances",
"use_volumetric_e",
"variable_layer_height",
"wipe"
"wipe",
"wipe_tower_x",
"wipe_tower_y",
"wipe_tower_rotation_angle"
};
std::vector<PrintStep> steps;
@ -175,7 +179,12 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
// Always invalidate the wipe tower. This is probably necessary because of the wipe_into_infill / wipe_into_objects
// features - nearly anything can influence what should (and could) be wiped into.
steps.emplace_back(psWipeTower);
// Only these three parameters don't invalidate the wipe tower (they only affect the gcode export):
for (const t_config_option_key &opt_key : opt_keys)
if (opt_key != "wipe_tower_x" && opt_key != "wipe_tower_y" && opt_key != "wipe_tower_rotation_angle") {
steps.emplace_back(psWipeTower);
break;
}
for (const t_config_option_key &opt_key : opt_keys) {
if (steps_ignore.find(opt_key) != steps_ignore.end()) {
@ -204,6 +213,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "filament_unloading_speed"
|| opt_key == "filament_toolchange_delay"
|| opt_key == "filament_cooling_moves"
|| opt_key == "filament_minimal_purge_on_wipe_tower"
|| opt_key == "filament_cooling_initial_speed"
|| opt_key == "filament_cooling_final_speed"
|| opt_key == "filament_ramming_parameters"
@ -212,10 +222,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|| opt_key == "spiral_vase"
|| opt_key == "temperature"
|| opt_key == "wipe_tower"
|| opt_key == "wipe_tower_x"
|| opt_key == "wipe_tower_y"
|| opt_key == "wipe_tower_width"
|| opt_key == "wipe_tower_rotation_angle"
|| opt_key == "wipe_tower_bridging"
|| opt_key == "wiping_volumes_matrix"
|| opt_key == "parking_pos_retraction"
@ -1051,6 +1058,8 @@ void Print::_make_wipe_tower()
if (! this->has_wipe_tower())
return;
m_wipe_tower_depth = 0.f;
// Get wiping matrix to get number of extruders and convert vector<double> to vector<float>:
std::vector<float> wiping_matrix((this->config.wiping_volumes_matrix.values).begin(),(this->config.wiping_volumes_matrix.values).end());
// Extract purging volumes for each extruder pair:
@ -1144,12 +1153,19 @@ void Print::_make_wipe_tower()
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id,false);
for (const auto extruder_id : layer_tools.extruders) {
if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) {
float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange
// Not all of that can be used for infill purging:
volume_to_wipe -= config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
// try to assign some infills/objects for the wiping:
volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, wipe_volumes[current_extruder_id][extruder_id]);
volume_to_wipe = layer_tools.wiping_extrusions().mark_wiping_extrusions(*this, current_extruder_id, extruder_id, volume_to_wipe);
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id, first_layer && extruder_id == m_tool_ordering.all_extruders().back(), volume_to_wipe);
// add back the minimal amount toforce on the wipe tower:
volume_to_wipe += config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id);
// request a toolchange at the wipe tower with at least volume_to_wipe purging amount
wipe_tower.plan_toolchange(layer_tools.print_z, layer_tools.wipe_tower_layer_height, current_extruder_id, extruder_id,
first_layer && extruder_id == m_tool_ordering.all_extruders().back(), volume_to_wipe);
current_extruder_id = extruder_id;
}
}
@ -1162,7 +1178,8 @@ void Print::_make_wipe_tower()
// Generate the wipe tower layers.
m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size());
wipe_tower.generate(m_wipe_tower_tool_changes);
m_wipe_tower_depth = wipe_tower.get_depth();
// Unload the current filament over the purge tower.
coordf_t layer_height = this->objects.front()->config.layer_height.value;
if (m_tool_ordering.back().wipe_tower_partitions > 0) {
@ -1183,10 +1200,6 @@ void Print::_make_wipe_tower()
wipe_tower.tool_change((unsigned int)-1, false));
}
std::string Print::output_filename()
{
this->placeholder_parser.update_timestamp();
@ -1225,7 +1238,6 @@ void Print::set_status(int percent, const std::string &message)
printf("Print::status %d => %s\n", percent, message.c_str());
}
// Returns extruder this eec should be printed with, according to PrintRegion config
int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion &region)
{
@ -1233,5 +1245,4 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion
std::max<int>(region.config.perimeter_extruder.value - 1, 0);
}
}

View file

@ -273,6 +273,7 @@ public:
void add_model_object(ModelObject* model_object, int idx = -1);
bool apply_config(DynamicPrintConfig config);
float get_wipe_tower_depth() const { return m_wipe_tower_depth; }
bool has_infinite_skirt() const;
bool has_skirt() const;
// Returns an empty string if valid, otherwise returns an error message.
@ -326,6 +327,9 @@ private:
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume);
// Depth of the wipe tower to pass to GLCanvas3D for exact bounding box:
float m_wipe_tower_depth = 0.f;
// Has the calculation been canceled?
tbb::atomic<bool> m_canceled;
};

View file

@ -504,15 +504,27 @@ PrintConfigDef::PrintConfigDef()
def = this->add("filament_cooling_initial_speed", coFloats);
def->label = L("Speed of the first cooling move");
def->tooltip = L("Cooling moves are gradually accelerating beginning at this speed. ");
def->cli = "filament-cooling-initial-speed=i@";
def->cli = "filament-cooling-initial-speed=f@";
def->sidetext = L("mm/s");
def->min = 0;
def->default_value = new ConfigOptionFloats { 2.2f };
def = this->add("filament_minimal_purge_on_wipe_tower", coFloats);
def->label = L("Minimal purge on wipe tower");
def->tooltip = L("After a toolchange, certain amount of filament is used for purging. This "
"can end up on the wipe tower, infill or sacrificial object. If there was "
"enough infill etc. available, this could result in bad quality at the beginning "
"of purging. This is a minimum that must be wiped on the wipe tower before "
"Slic3r considers moving elsewhere. ");
def->cli = "filament-minimal-purge-on-wipe-tower=f@";
def->sidetext = L("mm³");
def->min = 0;
def->default_value = new ConfigOptionFloats { 5.f };
def = this->add("filament_cooling_final_speed", coFloats);
def->label = L("Speed of the last cooling move");
def->tooltip = L("Cooling moves are gradually accelerating towards this speed. ");
def->cli = "filament-cooling-final-speed=i@";
def->cli = "filament-cooling-final-speed=f@";
def->sidetext = L("mm/s");
def->min = 0;
def->default_value = new ConfigOptionFloats { 3.4f };
@ -1639,6 +1651,12 @@ PrintConfigDef::PrintConfigDef()
def->cli = "single-extruder-multi-material!";
def->default_value = new ConfigOptionBool(false);
def = this->add("single_extruder_multi_material_priming", coBool);
def->label = L("Prime all printing extruders");
def->tooltip = L("If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print.");
def->cli = "single-extruder-multi-material-priming!";
def->default_value = new ConfigOptionBool(true);
def = this->add("support_material", coBool);
def->label = L("Generate support material");
def->category = L("Support material");

View file

@ -534,6 +534,7 @@ public:
ConfigOptionFloats filament_unload_time;
ConfigOptionInts filament_cooling_moves;
ConfigOptionFloats filament_cooling_initial_speed;
ConfigOptionFloats filament_minimal_purge_on_wipe_tower;
ConfigOptionFloats filament_cooling_final_speed;
ConfigOptionStrings filament_ramming_parameters;
ConfigOptionBool gcode_comments;
@ -555,6 +556,7 @@ public:
ConfigOptionString start_gcode;
ConfigOptionStrings start_filament_gcode;
ConfigOptionBool single_extruder_multi_material;
ConfigOptionBool single_extruder_multi_material_priming;
ConfigOptionString toolchange_gcode;
ConfigOptionFloat travel_speed;
ConfigOptionBool use_firmware_retraction;
@ -597,6 +599,7 @@ protected:
OPT_PTR(filament_toolchange_delay);
OPT_PTR(filament_cooling_moves);
OPT_PTR(filament_cooling_initial_speed);
OPT_PTR(filament_minimal_purge_on_wipe_tower);
OPT_PTR(filament_cooling_final_speed);
OPT_PTR(filament_ramming_parameters);
OPT_PTR(gcode_comments);
@ -616,6 +619,7 @@ protected:
OPT_PTR(retract_restart_extra_toolchange);
OPT_PTR(retract_speed);
OPT_PTR(single_extruder_multi_material);
OPT_PTR(single_extruder_multi_material_priming);
OPT_PTR(start_gcode);
OPT_PTR(start_filament_gcode);
OPT_PTR(toolchange_gcode);

View file

@ -75,6 +75,7 @@ bool PrintObject::delete_last_copy()
bool PrintObject::set_copies(const Points &points)
{
bool copies_num_changed = this->_copies.size() != points.size();
this->_copies = points;
// order copies with a nearest neighbor search and translate them by _copies_shift
@ -93,7 +94,8 @@ bool PrintObject::set_copies(const Points &points)
bool invalidated = this->_print->invalidate_step(psSkirt);
invalidated |= this->_print->invalidate_step(psBrim);
invalidated |= this->_print->invalidate_step(psWipeTower);
if (copies_num_changed)
invalidated |= this->_print->invalidate_step(psWipeTower);
return invalidated;
}

View file

@ -8,6 +8,7 @@
#include <unordered_map>
#include <slic3r/GUI/GUI.hpp>
#include <ModelArrange.hpp>
#include <slic3r/GUI/PresetBundle.hpp>
#include <Geometry.hpp>
@ -293,6 +294,8 @@ void AppController::arrange_model()
supports_asynch()? std::launch::async : std::launch::deferred,
[this]()
{
using Coord = libnest2d::TCoord<libnest2d::PointImpl>;
unsigned count = 0;
for(auto obj : model_->objects) count += obj->instances.size();
@ -310,13 +313,25 @@ void AppController::arrange_model()
auto dist = print_ctl()->config().min_object_distance();
BoundingBoxf bb(print_ctl()->config().bed_shape.values);
// Create the arranger config
auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
auto& bedpoints = print_ctl()->config().bed_shape.values;
Polyline bed; bed.points.reserve(bedpoints.size());
for(auto& v : bedpoints)
bed.append(Point::new_scale(v.x, v.y));
if(pind) pind->update(0, _(L("Arranging objects...")));
try {
model_->arrange_objects(dist, &bb, [pind, count](unsigned rem){
if(pind) pind->update(count - rem, _(L("Arranging objects...")));
arr::arrange(*model_,
min_obj_distance,
bed,
arr::BOX,
false, // create many piles not just one pile
[pind, count](unsigned rem) {
if(pind)
pind->update(count - rem, _(L("Arranging objects...")));
});
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;

View file

@ -644,20 +644,62 @@ std::vector<int> GLVolumeCollection::load_object(
return volumes_idx;
}
int GLVolumeCollection::load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs)
{
float color[4] = { 0.5f, 0.5f, 0.0f, 0.5f };
this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back();
int GLVolumeCollection::load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width)
{
if (depth < 0.01f)
return int(this->volumes.size() - 1);
if (height == 0.0f)
height = 0.1f;
auto mesh = make_cube(width, depth, height);
mesh.translate(-width / 2.f, -depth / 2.f, 0.f);
Point origin_of_rotation(0.f, 0.f);
mesh.rotate(rotation_angle,&origin_of_rotation);
TriangleMesh mesh;
float color[4] = { 0.5f, 0.5f, 0.0f, 1.f };
// In case we don't know precise dimensions of the wipe tower yet, we'll draw the box with different color with one side jagged:
if (size_unknown) {
color[0] = 0.9f;
color[1] = 0.6f;
depth = std::max(depth, 10.f); // Too narrow tower would interfere with the teeth. The estimate is not precise anyway.
float min_width = 30.f;
// We'll now create the box with jagged edge. y-coordinates of the pre-generated model are shifted so that the front
// edge has y=0 and centerline of the back edge has y=depth:
Pointf3s points;
std::vector<Point3> facets;
float out_points_idx[][3] = {{0, -depth, 0}, {0, 0, 0}, {38.453, 0, 0}, {61.547, 0, 0}, {100, 0, 0}, {100, -depth, 0}, {55.7735, -10, 0}, {44.2265, 10, 0},
{38.453, 0, 1}, {0, 0, 1}, {0, -depth, 1}, {100, -depth, 1}, {100, 0, 1}, {61.547, 0, 1}, {55.7735, -10, 1}, {44.2265, 10, 1}};
int out_facets_idx[][3] = {{0, 1, 2}, {3, 4, 5}, {6, 5, 0}, {3, 5, 6}, {6, 2, 7}, {6, 0, 2}, {8, 9, 10}, {11, 12, 13}, {10, 11, 14}, {14, 11, 13}, {15, 8, 14},
{8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8},
{0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11}};
for (int i=0;i<16;++i)
points.push_back(Pointf3(out_points_idx[i][0] / (100.f/min_width), out_points_idx[i][1] + depth, out_points_idx[i][2]));
for (int i=0;i<28;++i)
facets.push_back(Point3(out_facets_idx[i][0], out_facets_idx[i][1], out_facets_idx[i][2]));
TriangleMesh tooth_mesh(points, facets);
// We have the mesh ready. It has one tooth and width of min_width. We will now append several of these together until we are close to
// the required width of the block. Than we can scale it precisely.
size_t n = std::max(1, int(width/min_width)); // How many shall be merged?
for (size_t i=0;i<n;++i) {
mesh.merge(tooth_mesh);
tooth_mesh.translate(min_width, 0.f, 0.f);
}
mesh.scale(Pointf3(width/(n*min_width), 1.f, height)); // Scaling to proper width
}
else
mesh = make_cube(width, depth, height);
// We'll make another mesh to show the brim (fixed layer height):
TriangleMesh brim_mesh = make_cube(width+2.f*brim_width, depth+2.f*brim_width, 0.2f);
brim_mesh.translate(-brim_width, -brim_width, 0.f);
mesh.merge(brim_mesh);
mesh.rotate(rotation_angle, &origin_of_rotation); // rotates the box according to the config rotation setting
this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back();
if (use_VBOs)
v.indexed_vertex_array.load_mesh_full_shading(mesh);

View file

@ -401,7 +401,7 @@ public:
bool use_VBOs);
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs);
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);
// Render the volumes by OpenGL.
void render_VBOs() const;

View file

@ -9,6 +9,8 @@
#include "Model.hpp"
#include "boost/nowide/iostream.hpp"
#include <algorithm>
namespace Slic3r {
namespace GUI {
@ -146,21 +148,18 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
// okay, it's a rectangle
// find origin
// the || 0 hack prevents "-0" which might confuse the user
int x_min, x_max, y_min, y_max;
x_max = x_min = points->values[0].x;
coordf_t x_min, x_max, y_min, y_max;
x_max = x_min = points->values[0].x;
y_max = y_min = points->values[0].y;
for (auto pt : points->values){
if (x_min > pt.x) x_min = pt.x;
if (x_max < pt.x) x_max = pt.x;
if (y_min > pt.y) y_min = pt.y;
if (y_max < pt.y) y_max = pt.y;
}
if (x_min < 0) x_min = 0;
if (x_max < 0) x_max = 0;
if (y_min < 0) y_min = 0;
if (y_max < 0) y_max = 0;
auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) };
for (auto pt : points->values)
{
x_min = std::min(x_min, pt.x);
x_max = std::max(x_max, pt.x);
y_min = std::min(y_min, pt.y);
y_max = std::max(y_max, pt.y);
}
auto origin = new ConfigOptionPoints{ Pointf(-x_min, -y_min) };
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];

View file

@ -95,9 +95,10 @@ namespace Slic3r { namespace GUI {
wxString tooltip_text("");
wxString tooltip = _(m_opt.tooltip);
if (tooltip.length() > 0)
tooltip_text = tooltip + "(" + _(L("default")) + ": " +
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") +
default_string + ")";
tooltip_text = tooltip + "\n " + _(L("default value")) + "\t: " +
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string +
(boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") + "\n " +
_(L("variable name")) + "\t: " + m_opt_id;
return tooltip_text;
}

View file

@ -1,12 +1,14 @@
#include <numeric>
#include <algorithm>
#include <thread>
#include <condition_variable>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <boost/optional.hpp>
#include "libslic3r/Utils.hpp"
#include "avrdude/avrdude-slic3r.hpp"
@ -32,11 +34,13 @@
#include <wx/gauge.h>
#include <wx/collpane.h>
#include <wx/msgdlg.h>
#include <wx/filefn.h>
namespace fs = boost::filesystem;
namespace asio = boost::asio;
using boost::system::error_code;
using boost::optional;
namespace Slic3r {
@ -46,19 +50,31 @@ using Utils::SerialPortInfo;
using Utils::Serial;
// USB IDs used to perform device lookup
enum {
USB_VID_PRUSA = 0x2c99,
USB_PID_MK2 = 1,
USB_PID_MK3 = 2,
USB_PID_MMU_BOOT = 3,
USB_PID_MMU_APP = 4,
};
// This enum discriminates the kind of information in EVT_AVRDUDE,
// it's stored in the ExtraLong field of wxCommandEvent.
enum AvrdudeEvent
{
AE_MESSAGE,
AE_PROGRESS,
AE_STATUS,
AE_EXIT,
AE_ERROR,
};
wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent);
wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent);
wxDECLARE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
wxDEFINE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
// Private
@ -66,15 +82,17 @@ struct FirmwareDialog::priv
{
enum AvrDudeComplete
{
AC_NONE,
AC_SUCCESS,
AC_FAILURE,
AC_CANCEL,
AC_USER_CANCELLED,
};
FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer")
// GUI elements
wxComboBox *port_picker;
std::vector<SerialPortInfo> ports;
wxStaticText *port_autodetect;
wxFilePickerCtrl *hex_picker;
wxStaticText *txt_status;
wxGauge *progressbar;
@ -85,43 +103,66 @@ struct FirmwareDialog::priv
wxButton *btn_flash;
wxString btn_flash_label_ready;
wxString btn_flash_label_flashing;
wxString label_status_flashing;
wxTimer timer_pulse;
// Async modal dialog during flashing
std::mutex mutex;
int modal_response;
std::condition_variable response_cv;
// Data
std::vector<SerialPortInfo> ports;
optional<SerialPortInfo> port;
HexFile hex_file;
// This is a shared pointer holding the background AvrDude task
// also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset).
AvrDude::Ptr avrdude;
std::string avrdude_config;
unsigned progress_tasks_done;
unsigned progress_tasks_bar;
bool cancelled;
bool user_cancelled;
const bool extra_verbose; // For debugging
priv(FirmwareDialog *q) :
q(q),
btn_flash_label_ready(_(L("Flash!"))),
btn_flash_label_flashing(_(L("Cancel"))),
label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))),
timer_pulse(q),
avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()),
progress_tasks_done(0),
progress_tasks_bar(0),
cancelled(false),
user_cancelled(false),
extra_verbose(false)
{}
void find_serial_ports();
void fit_no_shrink();
void set_txt_status(const wxString &label);
void flashing_start(unsigned tasks);
void flashing_done(AvrDudeComplete complete);
void check_model_id(const HexFile &metadata, const SerialPortInfo &port);
void enable_port_picker(bool enable);
void load_hex_file(const wxString &path);
void queue_status(wxString message);
void queue_error(const wxString &message);
void prepare_common(AvrDude &, const SerialPortInfo &port, const std::string &filename);
void prepare_mk2(AvrDude &, const SerialPortInfo &port, const std::string &filename);
void prepare_mk3(AvrDude &, const SerialPortInfo &port, const std::string &filename);
void prepare_mm_control(AvrDude &, const SerialPortInfo &port, const std::string &filename);
bool ask_model_id_mismatch(const std::string &printer_model);
bool check_model_id();
void wait_for_mmu_bootloader(unsigned retries);
void mmu_reboot(const SerialPortInfo &port);
void lookup_port_mmu();
void prepare_common();
void prepare_mk2();
void prepare_mk3();
void prepare_mm_control();
void perform_upload();
void cancel();
void user_cancel();
void on_avrdude(const wxCommandEvent &evt);
void on_async_dialog(const wxCommandEvent &evt);
void ensure_joined();
};
@ -146,10 +187,32 @@ void FirmwareDialog::priv::find_serial_ports()
}
}
void FirmwareDialog::priv::fit_no_shrink()
{
// Ensure content fits into window and window is not shrinked
const auto old_size = q->GetSize();
q->Layout();
q->Fit();
const auto new_size = q->GetSize();
const auto new_width = std::max(old_size.GetWidth(), new_size.GetWidth());
const auto new_height = std::max(old_size.GetHeight(), new_size.GetHeight());
q->SetSize(new_width, new_height);
}
void FirmwareDialog::priv::set_txt_status(const wxString &label)
{
const auto width = txt_status->GetSize().GetWidth();
txt_status->SetLabel(label);
txt_status->Wrap(width);
fit_no_shrink();
}
void FirmwareDialog::priv::flashing_start(unsigned tasks)
{
modal_response = wxID_NONE;
txt_stdout->Clear();
txt_status->SetLabel(_(L("Flashing in progress. Please do not disconnect the printer!")));
set_txt_status(label_status_flashing);
txt_status->SetForegroundColour(GUI::get_label_clr_modified());
port_picker->Disable();
btn_rescan->Disable();
@ -160,7 +223,7 @@ void FirmwareDialog::priv::flashing_start(unsigned tasks)
progressbar->SetValue(0);
progress_tasks_done = 0;
progress_tasks_bar = 0;
cancelled = false;
user_cancelled = false;
timer_pulse.Start(50);
}
@ -177,54 +240,190 @@ void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
progressbar->SetValue(progressbar->GetRange());
switch (complete) {
case AC_SUCCESS: txt_status->SetLabel(_(L("Flashing succeeded!"))); break;
case AC_FAILURE: txt_status->SetLabel(_(L("Flashing failed. Please see the avrdude log below."))); break;
case AC_CANCEL: txt_status->SetLabel(_(L("Flashing cancelled."))); break;
case AC_SUCCESS: set_txt_status(_(L("Flashing succeeded!"))); break;
case AC_FAILURE: set_txt_status(_(L("Flashing failed. Please see the avrdude log below."))); break;
case AC_USER_CANCELLED: set_txt_status(_(L("Flashing cancelled."))); break;
default: break;
}
}
void FirmwareDialog::priv::check_model_id(const HexFile &metadata, const SerialPortInfo &port)
void FirmwareDialog::priv::enable_port_picker(bool enable)
{
if (metadata.model_id.empty()) {
// No data to check against
return;
port_picker->Show(enable);
btn_rescan->Show(enable);
port_autodetect->Show(! enable);
q->Layout();
fit_no_shrink();
}
void FirmwareDialog::priv::load_hex_file(const wxString &path)
{
hex_file = HexFile(path.wx_str());
enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL);
}
void FirmwareDialog::priv::queue_status(wxString message)
{
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_STATUS);
evt->SetString(std::move(message));
wxQueueEvent(this->q, evt);
}
void FirmwareDialog::priv::queue_error(const wxString &message)
{
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_STATUS);
evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message));
wxQueueEvent(this->q, evt); avrdude->cancel();
}
bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model)
{
// model_id in the hex file doesn't match what the printer repoted.
// Ask the user if it should be flashed anyway.
std::unique_lock<std::mutex> lock(mutex);
auto evt = new wxCommandEvent(EVT_ASYNC_DIALOG, this->q->GetId());
evt->SetString(wxString::Format(_(L(
"This firmware hex file does not match the printer model.\n"
"The hex file is intended for: %s\n"
"Printer reported: %s\n\n"
"Do you want to continue and flash this hex file anyway?\n"
"Please only continue if you are sure this is the right thing to do.")),
hex_file.model_id, printer_model
));
wxQueueEvent(this->q, evt);
response_cv.wait(lock, [this]() { return this->modal_response != wxID_NONE; });
if (modal_response == wxID_YES) {
return true;
} else {
user_cancel();
return false;
}
}
asio::io_service io;
Serial serial(io, port.port, 115200);
serial.printer_setup();
bool FirmwareDialog::priv::check_model_id()
{
// XXX: The implementation in Serial doesn't currently work reliably enough to be used.
// Therefore, regretably, so far the check cannot be used and we just return true here.
// TODO: Rewrite Serial using more platform-native code.
return true;
// if (hex_file.model_id.empty()) {
// // No data to check against, assume it's ok
// return true;
// }
// asio::io_service io;
// Serial serial(io, port->port, 115200);
// serial.printer_setup();
// enum {
// TIMEOUT = 2000,
// RETREIES = 5,
// };
// if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) {
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
// return false;
// }
// std::string line;
// error_code ec;
// serial.printer_write_line("PRUSA Rev");
// while (serial.read_line(TIMEOUT, line, ec)) {
// if (ec) {
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
// return false;
// }
// if (line == "ok") { continue; }
// if (line == hex_file.model_id) {
// return true;
// } else {
// return ask_model_id_mismatch(line);
// }
// line.clear();
// }
// return false;
}
void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries)
{
enum {
TIMEOUT = 1000,
RETREIES = 3,
SLEEP_MS = 500,
};
if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) {
throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port);
}
for (unsigned i = 0; i < retries && !user_cancelled; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_MS));
std::string line;
error_code ec;
serial.printer_write_line("PRUSA Rev");
while (serial.read_line(TIMEOUT, line, ec)) {
if (ec) { throw wxString::Format(_(L("Could not connect to the printer at %s")), port.port); }
if (line == "ok") { continue; }
auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
return port.id_vendor != USB_VID_PRUSA && port.id_product != USB_PID_MMU_BOOT;
}), ports.end());
if (line == metadata.model_id) {
if (ports.size() == 1) {
port = ports[0];
return;
} else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
return;
} else {
throw wxString::Format(_(L(
"The firmware hex file does not match the printer model.\n"
"The hex file is intended for:\n %s\n"
"Printer reports:\n %s"
)), metadata.model_id, line);
}
line.clear();
}
}
void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename)
void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port)
{
asio::io_service io;
Serial serial(io, port.port, 1200);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
void FirmwareDialog::priv::lookup_port_mmu()
{
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
auto ports = Utils::scan_serial_ports_extended();
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
return port.id_vendor != USB_VID_PRUSA &&
port.id_product != USB_PID_MMU_BOOT &&
port.id_product != USB_PID_MMU_APP;
}), ports.end());
if (ports.size() == 0) {
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(
"The Multi Material Control device was not found.\n"
"If the device is connected, please press the Reset button next to the USB connector ..."
)));
wait_for_mmu_bootloader(30);
} else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
} else {
if (ports[0].id_product == USB_PID_MMU_APP) {
// The device needs to be rebooted into the bootloader mode
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
mmu_reboot(ports[0]);
wait_for_mmu_bootloader(10);
} else {
port = ports[0];
}
}
}
void FirmwareDialog::priv::prepare_common()
{
std::vector<std::string> args {{
extra_verbose ? "-vvvvv" : "-v",
@ -233,10 +432,10 @@ void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo
// The Prusa's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip
// is flashed with a buggy firmware.
"-c", "wiring",
"-P", port.port,
"-b", "115200", // TODO: Allow other rates? Ditto below.
"-P", port->port,
"-b", "115200", // TODO: Allow other rates? Ditto elsewhere.
"-D",
"-U", (boost::format("flash:w:0:%1%:i") % filename).str(),
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
}};
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
@ -244,97 +443,75 @@ void FirmwareDialog::priv::prepare_common(AvrDude &avrdude, const SerialPortInfo
return a + ' ' + b;
});
avrdude.push_args(std::move(args));
avrdude->push_args(std::move(args));
}
void FirmwareDialog::priv::prepare_mk2(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename)
void FirmwareDialog::priv::prepare_mk2()
{
prepare_common(avrdude, port, filename);
if (! port) { return; }
if (! check_model_id()) {
avrdude->cancel();
return;
}
prepare_common();
}
void FirmwareDialog::priv::prepare_mk3(AvrDude &avrdude, const SerialPortInfo &port, const std::string &filename)
void FirmwareDialog::priv::prepare_mk3()
{
prepare_common(avrdude, port, filename);
if (! port) { return; }
if (! check_model_id()) {
avrdude->cancel();
return;
}
prepare_common();
// The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy)
// This is done via another avrdude invocation, here we build arg list for that:
std::vector<std::string> args_l10n {{
std::vector<std::string> args {{
extra_verbose ? "-vvvvv" : "-v",
"-p", "atmega2560",
// Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2).
// The Prusa's avrdude is patched again to never send semicolons inside the data packets.
"-c", "arduino",
"-P", port.port,
"-P", port->port,
"-b", "115200",
"-D",
"-u", // disable safe mode
"-U", (boost::format("flash:w:1:%1%:i") % filename).str(),
"-U", (boost::format("flash:w:1:%1%:i") % hex_file.path.string()).str(),
}};
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude for external flash flashing, arguments: "
<< std::accumulate(std::next(args_l10n.begin()), args_l10n.end(), args_l10n[0], [](std::string a, const std::string &b) {
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
return a + ' ' + b;
});
avrdude.push_args(std::move(args_l10n));
avrdude->push_args(std::move(args));
}
void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPortInfo &port_in, const std::string &filename)
void FirmwareDialog::priv::prepare_mm_control()
{
// Check if the port has the PID/VID of 0x2c99/3
// If not, check if it is the MMU (0x2c99/4) and reboot the by opening @ 1200 bauds
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
SerialPortInfo port = port_in;
if (! port.id_match(0x2c99, 3)) {
if (! port.id_match(0x2c99, 4)) {
// This is not a Prusa MMU 2.0 device
BOOST_LOG_TRIVIAL(error) << boost::format("Not a Prusa MMU 2.0 device: `%1%`") % port.port;
throw wxString::Format(_(L("The device at `%s` is not am Original Prusa i3 MMU 2.0 device")), port.port);
}
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % port.port;
{
asio::io_service io;
Serial serial(io, port.port, 1200);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
// Wait for the bootloader to show up
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
// Look for the rebooted device
BOOST_LOG_TRIVIAL(info) << "... looking for VID/PID 0x2c99/3 ...";
auto new_ports = Utils::scan_serial_ports_extended();
unsigned hits = 0;
for (auto &&new_port : new_ports) {
if (new_port.id_match(0x2c99, 3)) {
hits++;
port = std::move(new_port);
}
}
if (hits == 0) {
BOOST_LOG_TRIVIAL(error) << "No VID/PID 0x2c99/3 device found after rebooting the MMU 2.0";
throw wxString::Format(_(L("Failed to reboot the device at `%s` for programming")), port.port);
} else if (hits > 1) {
// We found multiple 0x2c99/3 devices, this is bad, because there's no way to find out
// which one is the one user wants to flash.
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found after rebooting the MMU 2.0";
throw wxString::Format(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")), port.port);
}
port = boost::none;
lookup_port_mmu();
if (! port) {
queue_error(_(L("The device could not have been found")));
return;
}
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port.port;
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port->port;
queue_status(label_status_flashing);
std::vector<std::string> args {{
extra_verbose ? "-vvvvv" : "-v",
"-p", "atmega32u4",
"-c", "avr109",
"-P", port.port,
"-P", port->port,
"-b", "57600",
"-D",
"-U", (boost::format("flash:w:0:%1%:i") % filename).str(),
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
}};
BOOST_LOG_TRIVIAL(info) << "Invoking avrdude, arguments: "
@ -342,7 +519,7 @@ void FirmwareDialog::priv::prepare_mm_control(AvrDude &avrdude, const SerialPort
return a + ' ' + b;
});
avrdude.push_args(std::move(args));
avrdude->push_args(std::move(args));
}
@ -351,20 +528,21 @@ void FirmwareDialog::priv::perform_upload()
auto filename = hex_picker->GetPath();
if (filename.IsEmpty()) { return; }
int selection = port_picker->GetSelection();
if (selection == wxNOT_FOUND) { return; }
load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh
const SerialPortInfo &port = this->ports[selection];
// Verify whether the combo box list selection equals to the combo box edit value.
if (wxString::FromUTF8(this->ports[selection].friendly_name.data()) != port_picker->GetValue()) {
return;
int selection = port_picker->GetSelection();
if (selection != wxNOT_FOUND) {
port = this->ports[selection];
// Verify whether the combo box list selection equals to the combo box edit value.
if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) {
return;
}
}
const bool extra_verbose = false; // For debugging
HexFile metadata(filename.wx_str());
const std::string filename_utf8(filename.utf8_str().data());
flashing_start(metadata.device == HexFile::DEV_MK3 ? 2 : 1);
flashing_start(hex_file.device == HexFile::DEV_MK3 ? 2 : 1);
// Init the avrdude object
AvrDude avrdude(avrdude_config);
@ -374,36 +552,23 @@ void FirmwareDialog::priv::perform_upload()
auto q = this->q;
this->avrdude = avrdude
.on_run([=](AvrDude &avrdude) {
auto queue_error = [&](wxString message) {
avrdude.cancel();
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_ERROR);
evt->SetString(std::move(message));
wxQueueEvent(this->q, evt);
};
.on_run([this]() {
try {
switch (metadata.device) {
switch (this->hex_file.device) {
case HexFile::DEV_MK3:
this->check_model_id(metadata, port);
this->prepare_mk3(avrdude, port, filename_utf8);
this->prepare_mk3();
break;
case HexFile::DEV_MM_CONTROL:
this->check_model_id(metadata, port);
this->prepare_mm_control(avrdude, port, filename_utf8);
this->prepare_mm_control();
break;
default:
this->prepare_mk2(avrdude, port, filename_utf8);
this->prepare_mk2();
break;
}
} catch (const wxString &message) {
queue_error(message);
} catch (const std::exception &ex) {
queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port.port, ex.what()));
queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what()));
}
})
.on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) {
@ -423,20 +588,19 @@ void FirmwareDialog::priv::perform_upload()
evt->SetInt(progress);
wxQueueEvent(q, evt);
}))
.on_complete(std::move([q](int status, size_t /* args_id */) {
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
.on_complete(std::move([this]() {
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_EXIT);
evt->SetInt(status);
wxQueueEvent(q, evt);
evt->SetInt(this->avrdude->exit_code());
wxQueueEvent(this->q, evt);
}))
.run();
}
void FirmwareDialog::priv::cancel()
void FirmwareDialog::priv::user_cancel()
{
if (avrdude) {
cancelled = true;
txt_status->SetLabel(_(L("Cancelling...")));
user_cancelled = true;
avrdude->cancel();
}
}
@ -474,19 +638,17 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
case AE_EXIT:
BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt();
complete_kind = cancelled ? AC_CANCEL : (evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE);
// Figure out the exit state
if (user_cancelled) { complete_kind = AC_USER_CANCELLED; }
else if (avrdude->cancelled()) { complete_kind = AC_NONE; } // Ie. cancelled programatically
else { complete_kind = evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE; }
flashing_done(complete_kind);
ensure_joined();
break;
case AE_ERROR:
txt_stdout->AppendText(evt.GetString());
flashing_done(AC_FAILURE);
ensure_joined();
{
GUI::ErrorDialog dlg(this->q, evt.GetString());
dlg.ShowModal();
}
case AE_STATUS:
set_txt_status(evt.GetString());
break;
default:
@ -494,6 +656,16 @@ void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
}
}
void FirmwareDialog::priv::on_async_dialog(const wxCommandEvent &evt)
{
wxMessageDialog dlg(this->q, evt.GetString(), wxMessageBoxCaptionStr, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
{
std::lock_guard<std::mutex> lock(mutex);
modal_response = dlg.ShowModal();
}
response_cv.notify_all();
}
void FirmwareDialog::priv::ensure_joined()
{
// Make sure the background thread is collected and the AvrDude object reset
@ -521,44 +693,50 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
mono_font.MakeSmaller();
// Create GUI components and layout
auto *panel = new wxPanel(this);
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(vsizer);
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY);
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY);
p->port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
port_sizer->Add(p->btn_rescan, 0);
port_sizer->Add(p->port_autodetect, 1, wxEXPAND);
p->enable_port_picker(true);
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY);
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:")));
p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready")));
p->txt_status->SetFont(status_font);
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
auto *grid = new wxFlexGridSizer(2, SPACING, SPACING);
grid->AddGrowableCol(1);
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(port_sizer, 0, wxEXPAND);
grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->hex_picker, 0, wxEXPAND);
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->txt_status, 0, wxEXPAND);
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(port_sizer, 0, wxEXPAND);
grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
grid->Add(p->txt_status, 0, wxEXPAND);
vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING);
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")));
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE);
auto *spoiler_pane = p->spoiler->GetPane();
auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL);
p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
@ -571,6 +749,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
p->btn_close = new wxButton(panel, wxID_CLOSE);
p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready);
p->btn_flash->Disable();
auto *bsizer = new wxBoxSizer(wxHORIZONTAL);
bsizer->Add(p->btn_close);
bsizer->AddStretchSpacer();
@ -585,16 +764,26 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
SetSize(std::max(size.GetWidth(), static_cast<int>(MIN_WIDTH)), std::max(size.GetHeight(), static_cast<int>(MIN_HEIGHT)));
Layout();
// Bind events
p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
if (wxFileExists(evt.GetPath())) {
this->p->load_hex_file(evt.GetPath());
this->p->btn_flash->Enable();
}
});
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) {
// Dialog size gets screwed up by wxCollapsiblePane, we need to fix it here
if (evt.GetCollapsed()) {
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight();
this->SetSize(this->GetSize().GetWidth(), new_height);
} else {
this->SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT_EXPANDED));
}
this->Fit();
this->Layout();
this->p->fit_no_shrink();
});
p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); });
@ -608,7 +797,8 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
_(L("Confirmation")),
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
if (dlg.ShowModal() == wxID_YES) {
this->p->cancel();
this->p->set_txt_status(_(L("Cancelling...")));
this->p->user_cancel();
}
} else {
// Start a flashing task
@ -619,6 +809,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); });
Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); });
Bind(EVT_ASYNC_DIALOG, [this](wxCommandEvent &evt) { this->p->on_async_dialog(evt); });
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
if (this->p->avrdude) {

View file

@ -1941,12 +1941,23 @@ void GLCanvas3D::set_model(Model* model)
void GLCanvas3D::set_bed_shape(const Pointfs& shape)
{
m_bed.set_shape(shape);
bool new_shape = (shape != m_bed.get_shape());
if (new_shape)
m_bed.set_shape(shape);
// Set the origin and size for painting of the coordinate system axes.
m_axes.origin = Pointf3(0.0, 0.0, (coordf_t)GROUND_Z);
set_axes_length(0.3f * (float)m_bed.get_bounding_box().max_size());
if (new_shape)
{
// forces the selection of the proper camera target
if (m_volumes.volumes.empty())
zoom_to_bed();
else
zoom_to_volumes();
}
m_dirty = true;
}
@ -2303,7 +2314,12 @@ void GLCanvas3D::reload_scene(bool force)
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_width"))->value;
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
m_volumes.load_wipe_tower_preview(1000, x, y, w, 15.0f * (float)(extruders_count - 1), (float)height, a, m_use_VBOs && m_initialized);
float depth = m_print->get_wipe_tower_depth();
if (!m_print->state.is_done(psWipeTower))
depth = (900.f/w) * (float)(extruders_count - 1) ;
m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->state.is_done(psWipeTower),
m_print->config.nozzle_diameter.values[0] * 1.25f * 4.5f);
}
}
@ -3372,7 +3388,7 @@ void GLCanvas3D::_camera_tranform() const
::glMatrixMode(GL_MODELVIEW);
::glLoadIdentity();
::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch
::glRotatef(-m_camera.get_theta(), 1.0f, 0.0f, 0.0f); // pitch
::glRotatef(m_camera.phi, 0.0f, 0.0f, 1.0f); // yaw
Pointf3 neg_target = m_camera.target.negative();
@ -4052,6 +4068,8 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
{
const Print *print;
const std::vector<float> *tool_colors;
WipeTower::xy wipe_tower_pos;
float wipe_tower_angle;
// Number of vertices (each vertex is 6x4=24 bytes long)
static const size_t alloc_size_max() { return 131072; } // 3.15MB
@ -4079,11 +4097,14 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
ctxt.print = m_print;
ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
if (m_print->m_wipe_tower_priming)
if (m_print->m_wipe_tower_priming && m_print->config.single_extruder_multi_material_priming)
ctxt.priming.emplace_back(*m_print->m_wipe_tower_priming.get());
if (m_print->m_wipe_tower_final_purge)
ctxt.final.emplace_back(*m_print->m_wipe_tower_final_purge.get());
ctxt.wipe_tower_angle = ctxt.print->config.wipe_tower_rotation_angle.value/180.f * M_PI;
ctxt.wipe_tower_pos = WipeTower::xy(ctxt.print->config.wipe_tower_x.value, ctxt.print->config.wipe_tower_y.value);
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start";
//FIXME Improve the heuristics for a grain size.
@ -4141,12 +4162,25 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
lines.reserve(n_lines);
widths.reserve(n_lines);
heights.assign(n_lines, extrusions.layer_height);
WipeTower::Extrusion e_prev = extrusions.extrusions[i-1];
if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation
e_prev.pos.rotate(ctxt.wipe_tower_angle);
e_prev.pos.translate(ctxt.wipe_tower_pos);
}
for (; i < j; ++i) {
const WipeTower::Extrusion &e = extrusions.extrusions[i];
WipeTower::Extrusion e = extrusions.extrusions[i];
assert(e.width > 0.f);
const WipeTower::Extrusion &e_prev = *(&e - 1);
if (!extrusions.priming) {
e.pos.rotate(ctxt.wipe_tower_angle);
e.pos.translate(ctxt.wipe_tower_pos);
}
lines.emplace_back(Point::new_scale(e_prev.pos.x, e_prev.pos.y), Point::new_scale(e.pos.x, e.pos.y));
widths.emplace_back(e.width);
e_prev = e;
}
_3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z,
*vols[ctxt.volume_idx(e.tool, 0)]);
@ -4705,8 +4739,11 @@ void GLCanvas3D::_load_shells()
const PrintConfig& config = m_print->config;
unsigned int extruders_count = config.nozzle_diameter.size();
if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) {
const float width_per_extruder = 15.0f; // a simple workaround after wipe_tower_per_color_wipe got obsolete
m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, width_per_extruder * (extruders_count - 1), max_z, config.wipe_tower_rotation_angle, m_use_VBOs && m_initialized);
float depth = m_print->get_wipe_tower_depth();
if (!m_print->state.is_done(psWipeTower))
depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ;
m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle,
m_use_VBOs && m_initialized, !m_print->state.is_done(psWipeTower), m_print->config.nozzle_diameter.values[0] * 1.25f * 4.5f);
}
}
@ -4812,7 +4849,7 @@ void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs)
if (m_model == nullptr)
return;
std::set<std::string> done; // prevent moving instances twice
std::set<std::string> done; // prevent moving instances twice
bool object_moved = false;
Pointf3 wipe_tower_origin(0.0, 0.0, 0.0);
for (int volume_idx : volume_idxs)
@ -4821,7 +4858,7 @@ void GLCanvas3D::_on_move(const std::vector<int>& volume_idxs)
int obj_idx = volume->object_idx();
int instance_idx = volume->instance_idx();
// prevent moving instances twice
// prevent moving instances twice
char done_id[64];
::sprintf(done_id, "%d_%d", obj_idx, instance_idx);
if (done.find(done_id) != done.end())

View file

@ -903,6 +903,7 @@ void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFl
std::vector<float> extruders = dlg.get_extruders();
(config.option<ConfigOptionFloats>("wiping_volumes_matrix"))->values = std::vector<double>(matrix.begin(),matrix.end());
(config.option<ConfigOptionFloats>("wiping_volumes_extruders"))->values = std::vector<double>(extruders.begin(),extruders.end());
g_on_request_update_callback.call();
}
}));
return sizer;
@ -919,6 +920,10 @@ ConfigOptionsGroup* get_optgroup()
return m_optgroup.get();
}
void register_on_request_update_callback(void* callback) {
if (callback != nullptr)
g_on_request_update_callback.register_callback(callback);
}
wxButton* get_wiping_dialog_button()
{

View file

@ -4,6 +4,7 @@
#include <string>
#include <vector>
#include "Config.hpp"
#include "../../libslic3r/Utils.hpp"
#include <wx/intl.h>
#include <wx/string.h>
@ -171,6 +172,9 @@ wxString from_u8(const std::string &str);
void add_frequently_changed_parameters(wxWindow* parent, wxBoxSizer* sizer, wxFlexGridSizer* preset_sizer);
static PerlCallback g_on_request_update_callback;
void register_on_request_update_callback(void* callback);
ConfigOptionsGroup* get_optgroup();
wxButton* get_wiping_dialog_button();

View file

@ -303,8 +303,8 @@ const std::vector<std::string>& Preset::print_options()
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "compatible_printers",
"compatible_printers_condition","inherits"
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming",
"compatible_printers", "compatible_printers_condition","inherits"
};
return s_opts;
}
@ -314,11 +314,12 @@ const std::vector<std::string>& Preset::filament_options()
static std::vector<std::string> s_opts {
"filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed",
"extrusion_multiplier", "filament_density", "filament_cost",
"filament_loading_speed", "filament_load_time", "filament_unloading_speed", "filament_unload_time", "filament_toolchange_delay",
"filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "temperature",
"first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed",
"bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
"start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition", "inherits"
"filament_loading_speed", "filament_load_time", "filament_unloading_speed", "filament_unload_time", "filament_toolchange_delay",
"filament_cooling_moves", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters",
"filament_minimal_purge_on_wipe_tower", "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature",
"fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time",
"slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode","compatible_printers", "compatible_printers_condition",
"inherits"
};
return s_opts;
}

View file

@ -919,6 +919,7 @@ void TabPrint::build()
optgroup->append_single_option_line("wipe_tower_width");
optgroup->append_single_option_line("wipe_tower_rotation_angle");
optgroup->append_single_option_line("wipe_tower_bridging");
optgroup->append_single_option_line("single_extruder_multi_material_priming");
optgroup = page->new_optgroup(_(L("Advanced")));
optgroup->append_single_option_line("interface_shells");
@ -1297,6 +1298,7 @@ void TabFilament::build()
optgroup->append_single_option_line("filament_cooling_moves");
optgroup->append_single_option_line("filament_cooling_initial_speed");
optgroup->append_single_option_line("filament_cooling_final_speed");
optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower");
line = { _(L("Ramming")), "" };
line.widget = [this](wxWindow* parent){

View file

@ -46,8 +46,7 @@ static size_t hex_num_sections(fs::ifstream &file)
}
HexFile::HexFile(fs::path path) :
path(std::move(path)),
device(DEV_GENERIC)
path(std::move(path))
{
fs::ifstream file(this->path);
if (! file.good()) {

View file

@ -19,9 +19,10 @@ struct HexFile
};
boost::filesystem::path path;
DeviceKind device;
DeviceKind device = DEV_GENERIC;
std::string model_id;
HexFile() {}
HexFile(boost::filesystem::path path);
};

View file

@ -373,7 +373,7 @@ void Serial::set_DTR(bool on)
void Serial::reset_line_num()
{
// See https://github.com/MarlinFirmware/Marlin/wiki/M110
printer_write_line("M110 N0", 0);
write_string("M110 N0\n");
m_line_num = 0;
}
@ -390,9 +390,9 @@ bool Serial::read_line(unsigned timeout, std::string &line, error_code &ec)
asio::async_read(*this, boost::asio::buffer(&c, 1), [&](const error_code &read_ec, size_t size) {
if (ec || size == 0) {
fail = true;
ec = read_ec;
ec = read_ec; // FIXME: only if operation not aborted
}
timer.cancel();
timer.cancel(); // FIXME: ditto
});
if (timeout > 0) {
@ -444,6 +444,7 @@ bool Serial::printer_ready_wait(unsigned retries, unsigned timeout)
}
line.clear();
}
line.clear();
}
@ -469,7 +470,7 @@ void Serial::printer_reset()
this->set_DTR(true);
std::this_thread::sleep_for(std::chrono::milliseconds(200));
this->set_DTR(false);
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
std::string Serial::printer_format_line(const std::string &line, unsigned line_num)

View file

@ -51,12 +51,13 @@ public:
// Reads a line or times out, the timeout is in milliseconds
bool read_line(unsigned timeout, std::string &line, boost::system::error_code &ec);
// Perform setup for communicating with a printer
// Perform an initial setup for communicating with a printer
void printer_setup();
// Write data from a string
size_t write_string(const std::string &str);
// Attempts to reset the line numer and waits until the printer says "ok"
bool printer_ready_wait(unsigned retries, unsigned timeout);
// Write Marlin-formatted line, with a line number and a checksum

View file

@ -104,3 +104,12 @@ void fix_model_by_win10_sdk_gui(ModelObject *model_object_src, Print *print, Mod
void set_3DScene(SV *scene)
%code%{ Slic3r::GUI::set_3DScene((_3DScene *)wxPli_sv_2_object(aTHX_ scene, "Slic3r::Model::3DScene") ); %};
%package{Slic3r::_GUI};
%{
void
register_on_request_update_callback(callback)
SV *callback;
CODE:
Slic3r::GUI::register_on_request_update_callback((void*)callback);
%}

View file

@ -89,7 +89,7 @@
std::vector<int> load_object(ModelObject *object, int obj_idx, std::vector<int> instance_idxs, std::string color_by, std::string select_by, std::string drag_by, bool use_VBOs);
int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs);
int load_wipe_tower_preview(int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);
void erase()
%code{% THIS->clear(); %};