Merge branch 'master' into materials

This commit is contained in:
Vojtech Kral 2019-09-30 16:12:48 +02:00
commit eb93d2a32d
92 changed files with 1925 additions and 1073 deletions

View file

@ -89,33 +89,34 @@ struct stl_neighbors {
}; };
struct stl_stats { struct stl_stats {
stl_stats() { this->reset(); } stl_stats() { memset(&header, 0, 81); }
void reset() { memset(this, 0, sizeof(stl_stats)); this->volume = -1.0; }
char header[81]; char header[81];
stl_type type; stl_type type = (stl_type)0;
uint32_t number_of_facets; uint32_t number_of_facets = 0;
stl_vertex max; stl_vertex max = stl_vertex::Zero();
stl_vertex min; stl_vertex min = stl_vertex::Zero();
stl_vertex size; stl_vertex size = stl_vertex::Zero();
float bounding_diameter; float bounding_diameter = 0.f;
float shortest_edge; float shortest_edge = 0.f;
float volume; float volume = -1.f;
int connected_edges; int connected_edges = 0;
int connected_facets_1_edge; int connected_facets_1_edge = 0;
int connected_facets_2_edge; int connected_facets_2_edge = 0;
int connected_facets_3_edge; int connected_facets_3_edge = 0;
int facets_w_1_bad_edge; int facets_w_1_bad_edge = 0;
int facets_w_2_bad_edge; int facets_w_2_bad_edge = 0;
int facets_w_3_bad_edge; int facets_w_3_bad_edge = 0;
int original_num_facets; int original_num_facets = 0;
int edges_fixed; int edges_fixed = 0;
int degenerate_facets; int degenerate_facets = 0;
int facets_removed; int facets_removed = 0;
int facets_added; int facets_added = 0;
int facets_reversed; int facets_reversed = 0;
int backwards_edges; int backwards_edges = 0;
int normals_fixed; int normals_fixed = 0;
int number_of_parts; int number_of_parts = 0;
void clear() { *this = stl_stats(); }
}; };
struct stl_file { struct stl_file {
@ -124,7 +125,7 @@ struct stl_file {
void clear() { void clear() {
this->facet_start.clear(); this->facet_start.clear();
this->neighbors_start.clear(); this->neighbors_start.clear();
this->stats.reset(); this->stats.clear();
} }
size_t memsize() const { size_t memsize() const {

View file

@ -36,6 +36,10 @@
#error "SEEK_SET not defined" #error "SEEK_SET not defined"
#endif #endif
#ifndef BOOST_LITTLE_ENDIAN
extern void stl_internal_reverse_quads(char *buf, size_t cnt);
#endif /* BOOST_LITTLE_ENDIAN */
static FILE* stl_open_count_facets(stl_file *stl, const char *file) static FILE* stl_open_count_facets(stl_file *stl, const char *file)
{ {
// Open the file in binary mode first. // Open the file in binary mode first.
@ -238,10 +242,6 @@ bool stl_open(stl_file *stl, const char *file)
return result; return result;
} }
#ifndef BOOST_LITTLE_ENDIAN
extern void stl_internal_reverse_quads(char *buf, size_t cnt);
#endif /* BOOST_LITTLE_ENDIAN */
void stl_allocate(stl_file *stl) void stl_allocate(stl_file *stl)
{ {
// Allocate memory for the entire .STL file. // Allocate memory for the entire .STL file.

View file

@ -1,3 +1,12 @@
2018-01-17 Joerg Wunsch <j.gnu@uriah.heep.sax.de>
(cherry-picked)
Submitted by Reinhard Max
patch #8311: Add IPv6 support to the -Pnet:host:port option
* ser_posix.c (net_open): Rewrite to use getaddrinfo()
rather than gethostbyname()
* avrdude.1: Document IPv6 feature
* doc/avrdude.texi: (Dito)
2016-05-10 Joerg Wunsch <j.gnu@uriah.heep.sax.de> 2016-05-10 Joerg Wunsch <j.gnu@uriah.heep.sax.de>
Submitted by Hannes Jochriem: Submitted by Hannes Jochriem:

View file

@ -505,12 +505,19 @@ network connection to (TCP)
on on
.Ar host .Ar host
is established. is established.
Square brackets may be placed around
.Ar host
to improve readability, for numeric IPv6 addresses (e.g.
.Li net:[2001:db8::42]:1337 ) .
The remote endpoint is assumed to be a terminal or console server The remote endpoint is assumed to be a terminal or console server
that connects the network stream to a local serial port where the that connects the network stream to a local serial port where the
actual programmer has been attached to. actual programmer has been attached to.
The port is assumed to be properly configured, for example using a The port is assumed to be properly configured, for example using a
transparent 8-bit data connection without parity at 115200 Baud transparent 8-bit data connection without parity at 115200 Baud
for a STK500. for a STK500.
.Pp
Note: The ability to handle IPv6 hostnames and addresses is limited to
Posix systems (by now).
.It Fl q .It Fl q
Disable (or quell) output of the progress bar while reading or writing Disable (or quell) output of the progress bar while reading or writing
to the device. Specify it a second time for even quieter operation. to the device. Specify it a second time for even quieter operation.

View file

@ -214,7 +214,7 @@ AC_HEADER_TIME
AC_CHECK_LIB([ws2_32], [puts]) AC_CHECK_LIB([ws2_32], [puts])
# Checks for library functions. # Checks for library functions.
AC_CHECK_FUNCS([memset select strcasecmp strdup strerror strncasecmp strtol strtoul gettimeofday usleep]) AC_CHECK_FUNCS([memset select strcasecmp strdup strerror strncasecmp strtol strtoul gettimeofday usleep getaddrinfo])
AC_MSG_CHECKING([for a Win32 HID libray]) AC_MSG_CHECKING([for a Win32 HID libray])
SAVED_LIBS="${LIBS}" SAVED_LIBS="${LIBS}"

View file

@ -557,6 +557,9 @@ higher level protocol (as opposed to bit-bang style programmers),
In this case, instead of trying to open a local device, a TCP In this case, instead of trying to open a local device, a TCP
network connection to (TCP) @var{port} on @var{host} network connection to (TCP) @var{port} on @var{host}
is established. is established.
Square brackets may be placed around @var{host} to improve
readability for numeric IPv6 addresses (e.g.
@code{net:[2001:db8::42]:1337}).
The remote endpoint is assumed to be a terminal or console server The remote endpoint is assumed to be a terminal or console server
that connects the network stream to a local serial port where the that connects the network stream to a local serial port where the
actual programmer has been attached to. actual programmer has been attached to.
@ -564,6 +567,8 @@ The port is assumed to be properly configured, for example using a
transparent 8-bit data connection without parity at 115200 Baud transparent 8-bit data connection without parity at 115200 Baud
for a STK500. for a STK500.
Note: The ability to handle IPv6 hostnames and addresses is limited to
Posix systems (by now).
@item -q @item -q
Disable (or quell) output of the progress bar while reading or writing Disable (or quell) output of the progress bar while reading or writing

View file

@ -150,6 +150,7 @@ static int ser_setspeed(union filedescriptor *fd, long baud)
return 0; return 0;
} }
#include "ac_cfg.h"
// Timeout read & write variants // Timeout read & write variants
// Additionally to the regular -1 on I/O error, they return -2 on timeout // Additionally to the regular -1 on I/O error, they return -2 on timeout
@ -221,23 +222,35 @@ ssize_t write_timeout(int fd, const void *buf, size_t count, long timeout)
static int static int
net_open(const char *port, union filedescriptor *fdp) net_open(const char *port, union filedescriptor *fdp)
{ {
char *hstr, *pstr, *end; #ifdef HAVE_GETADDRINFO
unsigned int pnum; char *hp, *hstr, *pstr;
int fd; int s, fd, ret = -1;
struct sockaddr_in sockaddr; struct addrinfo hints;
struct hostent *hp; struct addrinfo *result, *rp;
if ((hstr = strdup(port)) == NULL) { if ((hstr = hp = strdup(port)) == NULL) {
avrdude_message(MSG_INFO, "%s: net_open(): Out of memory!\n", avrdude_message(MSG_INFO, "%s: net_open(): Out of memory!\n",
progname); progname);
return -1; return -1;
} }
if (((pstr = strchr(hstr, ':')) == NULL) || (pstr == hstr)) { /*
* As numeric IPv6 addresses use colons as separators, we need to
* look for the last colon here, which separates the port number or
* service name from the host or IP address.
*/
if (((pstr = strrchr(hstr, ':')) == NULL) || (pstr == hstr)) {
avrdude_message(MSG_INFO, "%s: net_open(): Mangled host:port string \"%s\"\n", avrdude_message(MSG_INFO, "%s: net_open(): Mangled host:port string \"%s\"\n",
progname, hstr); progname, hstr);
free(hstr); goto error;
return -1; }
/*
* Remove brackets from the host part, if present.
*/
if (*hstr == '[' && *(pstr-1) == ']') {
hstr++;
*(pstr-1) = '\0';
} }
/* /*
@ -245,43 +258,49 @@ net_open(const char *port, union filedescriptor *fdp)
*/ */
*pstr++ = '\0'; *pstr++ = '\0';
pnum = strtoul(pstr, &end, 10); memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
s = getaddrinfo(hstr, pstr, &hints, &result);
if ((*pstr == '\0') || (*end != '\0') || (pnum == 0) || (pnum > 65535)) { if (s != 0) {
avrdude_message(MSG_INFO, "%s: net_open(): Bad port number \"%s\"\n", avrdude_message(MSG_INFO,
progname, pstr); "%s: net_open(): Cannot resolve "
free(hstr); "host=\"%s\", port=\"%s\": %s\n",
return -1; progname, hstr, pstr, gai_strerror(s));
goto error;
} }
for (rp = result; rp != NULL; rp = rp->ai_next) {
if ((hp = gethostbyname(hstr)) == NULL) { fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
avrdude_message(MSG_INFO, "%s: net_open(): unknown host \"%s\"\n", if (fd == -1) {
progname, hstr); /* This one failed, loop over */
free(hstr); continue;
return -1;
} }
if (connect(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
free(hstr); /* Success, we are connected */
break;
if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { }
avrdude_message(MSG_INFO, "%s: net_open(): Cannot open socket: %s\n", close(fd);
}
if (rp == NULL) {
avrdude_message(MSG_INFO, "%s: net_open(): Cannot connect: %s\n",
progname, strerror(errno)); progname, strerror(errno));
return -1;
} }
else {
memset(&sockaddr, 0, sizeof(struct sockaddr_in));
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(pnum);
memcpy(&(sockaddr.sin_addr.s_addr), hp->h_addr, sizeof(struct in_addr));
if (connect(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr))) {
avrdude_message(MSG_INFO, "%s: net_open(): Connect failed: %s\n",
progname, strerror(errno));
return -1;
}
fdp->ifd = fd; fdp->ifd = fd;
return 0; ret = 0;
}
freeaddrinfo(result);
error:
free(hp);
return ret;
#else
avrdude_message(MSG_INFO,
"%s: Networking is not supported on your platform.\n"
"If you need it, please open a bug report.\n", progname);
return -1;
#endif /* HAVE_GETADDRINFO */
} }

View file

@ -15,4 +15,4 @@
#undef clipper_hpp #undef clipper_hpp
#undef use_xyz #undef use_xyz
#endif clipper_z_hpp #endif // clipper_z_hpp

View file

@ -81,17 +81,16 @@ inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance, const PolygonTag
using ClipperLib::etClosedPolygon; using ClipperLib::etClosedPolygon;
using ClipperLib::Paths; using ClipperLib::Paths;
// If the input is not at least a triangle, we can not do this algorithm
if(sh.Contour.size() <= 3 ||
std::any_of(sh.Holes.begin(), sh.Holes.end(),
[](const PathImpl& p) { return p.size() <= 3; })
) throw GeometryException(GeomErr::OFFSET);
ClipperOffset offs;
Paths result; Paths result;
try {
ClipperOffset offs;
offs.AddPath(sh.Contour, jtMiter, etClosedPolygon); offs.AddPath(sh.Contour, jtMiter, etClosedPolygon);
offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon); offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon);
offs.Execute(result, static_cast<double>(distance)); offs.Execute(result, static_cast<double>(distance));
} catch (ClipperLib::clipperException &) {
throw GeometryException(GeomErr::OFFSET);
}
// Offsetting reverts the orientation and also removes the last vertex // Offsetting reverts the orientation and also removes the last vertex
// so boost will not have a closed polygon. // so boost will not have a closed polygon.

View file

@ -1144,7 +1144,7 @@ inline bool isInside(const TBGuest& ibb, const TBHost& box,
auto minY = getY(box.minCorner()); auto minY = getY(box.minCorner());
auto maxY = getY(box.maxCorner()); auto maxY = getY(box.maxCorner());
return iminX > minX && imaxX < maxX && iminY > minY && imaxY < maxY; return iminX >= minX && imaxX <= maxX && iminY >= minY && imaxY <= maxY;
} }
template<class S, class TB> template<class S, class TB>

View file

@ -3,9 +3,6 @@
#include <cassert> #include <cassert>
// For caching nfps
#include <unordered_map>
// For parallel for // For parallel for
#include <functional> #include <functional>
#include <iterator> #include <iterator>
@ -76,55 +73,6 @@ inline void enumerate(
} }
namespace __itemhash {
using Key = size_t;
template<class S>
Key hash(const _Item<S>& item) {
using Point = TPoint<S>;
using Segment = _Segment<Point>;
static const int N = 26;
static const int M = N*N - 1;
std::string ret;
auto& rhs = item.rawShape();
auto& ctr = sl::contour(rhs);
auto it = ctr.begin();
auto nx = std::next(it);
double circ = 0;
while(nx != ctr.end()) {
Segment seg(*it++, *nx++);
Radians a = seg.angleToXaxis();
double deg = Degrees(a);
int ms = 'A', ls = 'A';
while(deg > N) { ms++; deg -= N; }
ls += int(deg);
ret.push_back(char(ms)); ret.push_back(char(ls));
circ += std::sqrt(seg.template sqlength<double>());
}
it = ctr.begin(); nx = std::next(it);
while(nx != ctr.end()) {
Segment seg(*it++, *nx++);
auto l = int(M * std::sqrt(seg.template sqlength<double>()) / circ);
int ms = 'A', ls = 'A';
while(l > N) { ms++; l -= N; }
ls += l;
ret.push_back(char(ms)); ret.push_back(char(ls));
}
return std::hash<std::string>()(ret);
}
template<class S>
using Hash = std::unordered_map<Key, nfp::NfpResult<S>>;
}
namespace placers { namespace placers {
template<class RawShape> template<class RawShape>
@ -529,17 +477,9 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer<RawShape, TBin
using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>; using MaxNfpLevel = nfp::MaxNfpLevel<RawShape>;
using ItemKeys = std::vector<__itemhash::Key>;
// Norming factor for the optimization function // Norming factor for the optimization function
const double norm_; const double norm_;
// Caching calculated nfps
__itemhash::Hash<RawShape> nfpcache_;
// Storing item hash keys
ItemKeys item_keys_;
public: public:
using Pile = nfp::Shapes<RawShape>; using Pile = nfp::Shapes<RawShape>;
@ -636,15 +576,12 @@ public:
private: private:
using Shapes = TMultiShape<RawShape>; using Shapes = TMultiShape<RawShape>;
using ItemRef = std::reference_wrapper<Item>;
using ItemWithHash = const std::pair<ItemRef, __itemhash::Key>;
Shapes calcnfp(const ItemWithHash itsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>) Shapes calcnfp(const Item &trsh, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
{ {
using namespace nfp; using namespace nfp;
Shapes nfps(items_.size()); Shapes nfps(items_.size());
const Item& trsh = itsh.first;
// ///////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////
// TODO: this is a workaround and should be solved in Item with mutexes // TODO: this is a workaround and should be solved in Item with mutexes
@ -678,12 +615,11 @@ private:
template<class Level> template<class Level>
Shapes calcnfp( const ItemWithHash itsh, Level) Shapes calcnfp(const Item &trsh, Level)
{ // Function for arbitrary level of nfp implementation { // Function for arbitrary level of nfp implementation
using namespace nfp; using namespace nfp;
Shapes nfps; Shapes nfps;
const Item& trsh = itsh.first;
auto& orb = trsh.transformedShape(); auto& orb = trsh.transformedShape();
bool orbconvex = trsh.isContourConvex(); bool orbconvex = trsh.isContourConvex();
@ -849,8 +785,6 @@ private:
remlist.insert(remlist.end(), remaining.from, remaining.to); remlist.insert(remlist.end(), remaining.from, remaining.to);
} }
size_t itemhash = __itemhash::hash(item);
if(items_.empty()) { if(items_.empty()) {
setInitialPosition(item); setInitialPosition(item);
best_overfit = overfit(item.transformedShape(), bin_); best_overfit = overfit(item.transformedShape(), bin_);
@ -875,7 +809,7 @@ private:
// it is disjunct from the current merged pile // it is disjunct from the current merged pile
placeOutsideOfBin(item); placeOutsideOfBin(item);
nfps = calcnfp({item, itemhash}, Lvl<MaxNfpLevel::value>()); nfps = calcnfp(item, Lvl<MaxNfpLevel::value>());
auto iv = item.referenceVertex(); auto iv = item.referenceVertex();
@ -1112,7 +1046,6 @@ private:
if(can_pack) { if(can_pack) {
ret = PackResult(item); ret = PackResult(item);
item_keys_.emplace_back(itemhash);
} else { } else {
ret = PackResult(best_overfit); ret = PackResult(best_overfit);
} }

View file

@ -43,7 +43,7 @@ protected:
Placer p{bin}; Placer p{bin};
p.configure(pcfg); p.configure(pcfg);
if (!p.pack(cpy)) it = c.erase(it); if (itm.area() <= 0 || !p.pack(cpy)) it = c.erase(it);
else it++; else it++;
} }
} }

View file

@ -40,7 +40,7 @@ struct NfpImpl<S, NfpLevel::CONVEX_ONLY>
} }
} }
std::vector<libnest2d::Item>& prusaParts() { static std::vector<libnest2d::Item>& prusaParts() {
static std::vector<libnest2d::Item> ret; static std::vector<libnest2d::Item> ret;
if(ret.empty()) { if(ret.empty()) {
@ -51,7 +51,7 @@ std::vector<libnest2d::Item>& prusaParts() {
return ret; return ret;
} }
TEST(BasicFunctionality, Angles) TEST(GeometryAlgorithms, Angles)
{ {
using namespace libnest2d; using namespace libnest2d;
@ -109,7 +109,7 @@ TEST(BasicFunctionality, Angles)
} }
// Simple test, does not use gmock // Simple test, does not use gmock
TEST(BasicFunctionality, creationAndDestruction) TEST(Nesting, ItemCreationAndDestruction)
{ {
using namespace libnest2d; using namespace libnest2d;
@ -572,26 +572,74 @@ TEST(GeometryAlgorithms, convexHull) {
} }
TEST(GeometryAlgorithms, NestTest) { TEST(Nesting, NestPrusaPartsShouldFitIntoTwoBins) {
std::vector<Item> input = prusaParts();
libnest2d::nest(input, Box(250000000, 210000000), [](unsigned cnt) { // Get the input items and define the bin.
std::cout << "parts left: " << cnt << std::endl; std::vector<Item> input = prusaParts();
auto bin = Box(250000000, 210000000);
// Do the nesting. Check in each step if the remaining items are less than
// in the previous step. (Some algorithms can place more items in one step)
size_t pcount = input.size();
libnest2d::nest(input, bin, [&pcount](unsigned cnt) {
ASSERT_TRUE(cnt < pcount);
pcount = cnt;
}); });
// Get the number of logical bins: search for the max binId...
auto max_binid_it = std::max_element(input.begin(), input.end(), auto max_binid_it = std::max_element(input.begin(), input.end(),
[](const Item &i1, const Item &i2) { [](const Item &i1, const Item &i2) {
return i1.binId() < i2.binId(); return i1.binId() < i2.binId();
}); });
size_t bins = max_binid_it == input.end() ? 0 : max_binid_it->binId() + 1; auto bins = size_t(max_binid_it == input.end() ? 0 :
max_binid_it->binId() + 1);
ASSERT_EQ(bins, 2u); // For prusa parts, 2 bins should be enough...
ASSERT_LE(bins, 2u);
// All parts should be processed by the algorithm
ASSERT_TRUE( ASSERT_TRUE(
std::all_of(input.begin(), input.end(), [](const Item &itm) { std::all_of(input.begin(), input.end(), [](const Item &itm) {
return itm.binId() != BIN_ID_UNSET; return itm.binId() != BIN_ID_UNSET;
})); }));
// Gather the items into piles of arranged polygons...
using Pile = TMultiShape<ClipperLib::Polygon>;
std::vector<Pile> piles(bins);
for (auto &itm : input)
piles[size_t(itm.binId())].emplace_back(itm.transformedShape());
// Now check all the piles, the bounding box of each pile should be inside
// the defined bin.
for (auto &pile : piles) {
auto bb = sl::boundingBox(pile);
ASSERT_TRUE(sl::isInside(bb, bin));
}
}
TEST(Nesting, NestEmptyItemShouldBeUntouched) {
auto bin = Box(250000000, 210000000); // dummy bin
std::vector<Item> items;
items.emplace_back(Item{}); // Emplace empty item
items.emplace_back(Item{0, 200, 0}); // Emplace zero area item
libnest2d::nest(items, bin);
for (auto &itm : items) ASSERT_EQ(itm.binId(), BIN_ID_UNSET);
}
TEST(Nesting, NestLargeItemShouldBeUntouched) {
auto bin = Box(250000000, 210000000); // dummy bin
std::vector<Item> items;
items.emplace_back(Rectangle{250000001, 210000001}); // Emplace large item
libnest2d::nest(items, bin);
ASSERT_EQ(items.front().binId(), BIN_ID_UNSET);
} }
namespace { namespace {
@ -966,26 +1014,20 @@ using Ratio = boost::rational<boost::multiprecision::int128_t>;
} }
TEST(RotatingCalipers, MinAreaBBCClk) { //TEST(GeometryAlgorithms, MinAreaBBCClk) {
auto u = [](ClipperLib::cInt n) { return n*1000000; }; // auto u = [](ClipperLib::cInt n) { return n*1000000; };
PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}}); // PolygonImpl poly({ {u(0), u(0)}, {u(4), u(1)}, {u(2), u(4)}});
long double arearef = refMinAreaBox(poly); // long double arearef = refMinAreaBox(poly);
long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area(); // long double area = minAreaBoundingBox<PolygonImpl, Unit, Ratio>(poly).area();
ASSERT_LE(std::abs(area - arearef), 500e6 ); // ASSERT_LE(std::abs(area - arearef), 500e6 );
} //}
TEST(RotatingCalipers, AllPrusaMinBB) { TEST(GeometryAlgorithms, MinAreaBBWithRotatingCalipers) {
// /size_t idx = 0;
long double err_epsilon = 500e6l; long double err_epsilon = 500e6l;
for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) { for(ClipperLib::Path rinput : PRINTER_PART_POLYGONS) {
// ClipperLib::Path rinput = PRINTER_PART_POLYGONS[idx];
// rinput.pop_back();
// std::reverse(rinput.begin(), rinput.end());
// PolygonImpl poly(removeCollinearPoints<PathImpl, PointImpl, Unit>(rinput, 1000000));
PolygonImpl poly(rinput); PolygonImpl poly(rinput);
long double arearef = refMinAreaBox(poly); long double arearef = refMinAreaBox(poly);
@ -993,8 +1035,6 @@ TEST(RotatingCalipers, AllPrusaMinBB) {
long double area = cast<long double>(bb.area()); long double area = cast<long double>(bb.area());
bool succ = std::abs(arearef - area) < err_epsilon; bool succ = std::abs(arearef - area) < err_epsilon;
// std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
// << arearef << " actual: " << area << std::endl;
ASSERT_TRUE(succ); ASSERT_TRUE(succ);
} }
@ -1011,8 +1051,6 @@ TEST(RotatingCalipers, AllPrusaMinBB) {
bool succ = std::abs(arearef - area) < err_epsilon; bool succ = std::abs(arearef - area) < err_epsilon;
// std::cout << idx++ << " " << (succ? "ok" : "failed") << " ref: "
// << arearef << " actual: " << area << std::endl;
ASSERT_TRUE(succ); ASSERT_TRUE(succ);
} }

View file

@ -618,19 +618,21 @@ void arrange(ArrangePolygons & arrangables,
items.reserve(arrangables.size()); items.reserve(arrangables.size());
// Create Item from Arrangeable // Create Item from Arrangeable
auto process_arrangeable = auto process_arrangeable = [](const ArrangePolygon &arrpoly,
[](const ArrangePolygon &arrpoly, std::vector<Item> &outp) std::vector<Item> & outp)
{ {
Polygon p = arrpoly.poly.contour; Polygon p = arrpoly.poly.contour;
const Vec2crd & offs = arrpoly.translation; const Vec2crd &offs = arrpoly.translation;
double rotation = arrpoly.rotation; double rotation = arrpoly.rotation;
if (p.is_counter_clockwise()) p.reverse(); if (p.is_counter_clockwise()) p.reverse();
clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p));
if (!clpath.Contour.empty()) {
auto firstp = clpath.Contour.front(); auto firstp = clpath.Contour.front();
clpath.Contour.emplace_back(firstp); clpath.Contour.emplace_back(firstp);
}
outp.emplace_back(std::move(clpath)); outp.emplace_back(std::move(clpath));
outp.back().rotation(rotation); outp.back().rotation(rotation);

View file

@ -100,7 +100,7 @@ add_library(libslic3r STATIC
Geometry.cpp Geometry.cpp
Geometry.hpp Geometry.hpp
Int128.hpp Int128.hpp
# KdTree.hpp KDTreeIndirect.hpp
Layer.cpp Layer.cpp
Layer.hpp Layer.hpp
LayerRegion.cpp LayerRegion.cpp
@ -131,8 +131,6 @@ add_library(libslic3r STATIC
PolygonTrimmer.hpp PolygonTrimmer.hpp
Polyline.cpp Polyline.cpp
Polyline.hpp Polyline.hpp
PolylineCollection.cpp
PolylineCollection.hpp
Print.cpp Print.cpp
Print.hpp Print.hpp
PrintBase.cpp PrintBase.cpp
@ -142,6 +140,8 @@ add_library(libslic3r STATIC
PrintObject.cpp PrintObject.cpp
PrintRegion.cpp PrintRegion.cpp
Semver.cpp Semver.cpp
ShortestPath.cpp
ShortestPath.hpp
SLAPrint.cpp SLAPrint.cpp
SLAPrint.hpp SLAPrint.hpp
SLA/SLAAutoSupports.hpp SLA/SLAAutoSupports.hpp

View file

@ -1,5 +1,6 @@
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "ShortestPath.hpp"
// #define CLIPPER_UTILS_DEBUG // #define CLIPPER_UTILS_DEBUG
@ -671,21 +672,19 @@ void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval)
// collect ordering points // collect ordering points
Points ordering_points; Points ordering_points;
ordering_points.reserve(nodes.size()); ordering_points.reserve(nodes.size());
for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { for (ClipperLib::PolyNode *pn : nodes)
Point p((*it)->Contour.front().X, (*it)->Contour.front().Y); ordering_points.emplace_back(Point(pn->Contour.front().X, pn->Contour.front().Y));
ordering_points.emplace_back(p);
}
// perform the ordering // perform the ordering
ClipperLib::PolyNodes ordered_nodes; ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes);
Slic3r::Geometry::chained_path_items(ordering_points, nodes, ordered_nodes);
// push results recursively // push results recursively
for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) { for (ClipperLib::PolyNode *pn : ordered_nodes) {
// traverse the next depth // traverse the next depth
traverse_pt((*it)->Childs, retval); traverse_pt(pn->Childs, retval);
retval->emplace_back(ClipperPath_to_Slic3rPolygon((*it)->Contour)); retval->emplace_back(ClipperPath_to_Slic3rPolygon(pn->Contour));
if ((*it)->IsHole()) retval->back().reverse(); // ccw if (pn->IsHole())
retval->back().reverse(); // ccw
} }
} }

View file

@ -5,6 +5,8 @@
#include "Polygon.hpp" #include "Polygon.hpp"
#include "Polyline.hpp" #include "Polyline.hpp"
#include <assert.h>
namespace Slic3r { namespace Slic3r {
class ExPolygonCollection; class ExPolygonCollection;
@ -79,8 +81,8 @@ public:
virtual ExtrusionEntity* clone_move() = 0; virtual ExtrusionEntity* clone_move() = 0;
virtual ~ExtrusionEntity() {} virtual ~ExtrusionEntity() {}
virtual void reverse() = 0; virtual void reverse() = 0;
virtual Point first_point() const = 0; virtual const Point& first_point() const = 0;
virtual Point last_point() const = 0; virtual const Point& last_point() const = 0;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0; virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
@ -121,24 +123,26 @@ public:
unsigned int extruder_id; unsigned int extruder_id;
// Id of the color, used for visualization purposes in the color printing case. // Id of the color, used for visualization purposes in the color printing case.
unsigned int cp_color_id; unsigned int cp_color_id;
// Fan speed for the extrusion, used for visualization purposes.
float fan_speed;
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {} ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), fan_speed(0.0f), m_role(role) {};
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {} ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), fan_speed(0.0f), m_role(role) {};
ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} ExtrusionPath(const ExtrusionPath& rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} ExtrusionPath(ExtrusionPath&& rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {} ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), fan_speed(rhs.fan_speed), m_role(rhs.m_role) {}
// ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {}; // ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {};
ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = rhs.polyline; return *this; } ExtrusionPath& operator=(const ExtrusionPath& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->fan_speed = rhs.fan_speed, this->polyline = rhs.polyline; return *this; }
ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = std::move(rhs.polyline); return *this; } ExtrusionPath& operator=(ExtrusionPath&& rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate, this->extruder_id = rhs.extruder_id, this->cp_color_id = rhs.cp_color_id, this->fan_speed = rhs.fan_speed, this->polyline = std::move(rhs.polyline); return *this; }
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); } ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
// Create a new object, initialize it with this object using the move semantics. // Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); } ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
void reverse() override { this->polyline.reverse(); } void reverse() override { this->polyline.reverse(); }
Point first_point() const override { return this->polyline.points.front(); } const Point& first_point() const override { return this->polyline.points.front(); }
Point last_point() const override { return this->polyline.points.back(); } const Point& last_point() const override { return this->polyline.points.back(); }
size_t size() const { return this->polyline.size(); } size_t size() const { return this->polyline.size(); }
bool empty() const { return this->polyline.empty(); } bool empty() const { return this->polyline.empty(); }
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); } bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
@ -198,8 +202,8 @@ public:
// Create a new object, initialize it with this object using the move semantics. // Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); } ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
void reverse() override; void reverse() override;
Point first_point() const override { return this->paths.front().polyline.points.front(); } const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { return this->paths.back().polyline.points.back(); } const Point& last_point() const override { return this->paths.back().polyline.points.back(); }
double length() const override; double length() const override;
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); } ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
@ -241,8 +245,8 @@ public:
bool make_clockwise(); bool make_clockwise();
bool make_counter_clockwise(); bool make_counter_clockwise();
void reverse() override; void reverse() override;
Point first_point() const override { return this->paths.front().polyline.points.front(); } const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); } const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
Polygon polygon() const; Polygon polygon() const;
double length() const override; double length() const override;
bool split_at_vertex(const Point &point); bool split_at_vertex(const Point &point);

View file

@ -1,4 +1,5 @@
#include "ExtrusionEntityCollection.hpp" #include "ExtrusionEntityCollection.hpp"
#include "ShortestPath.hpp"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <map> #include <map>
@ -16,7 +17,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE
this->entities = other.entities; this->entities = other.entities;
for (size_t i = 0; i < this->entities.size(); ++i) for (size_t i = 0; i < this->entities.size(); ++i)
this->entities[i] = this->entities[i]->clone(); this->entities[i] = this->entities[i]->clone();
this->orig_indices = other.orig_indices;
this->no_sort = other.no_sort; this->no_sort = other.no_sort;
return *this; return *this;
} }
@ -24,7 +24,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE
void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c) void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
{ {
std::swap(this->entities, c.entities); std::swap(this->entities, c.entities);
std::swap(this->orig_indices, c.orig_indices);
std::swap(this->no_sort, c.no_sort); std::swap(this->no_sort, c.no_sort);
} }
@ -75,79 +74,31 @@ void ExtrusionEntityCollection::remove(size_t i)
this->entities.erase(this->entities.begin() + i); this->entities.erase(this->entities.begin() + i);
} }
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const Point &start_near, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path(&coll, no_reverse, role);
return coll;
}
void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
{
if (this->entities.empty()) return;
this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(Point start_near, bool no_reverse, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path_from(start_near, &coll, no_reverse, role);
return coll;
}
void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
{ {
ExtrusionEntityCollection out;
if (this->no_sort) { if (this->no_sort) {
*retval = *this; out = *this;
return; } else {
} if (role == erMixed)
out = *this;
retval->entities.reserve(this->entities.size()); else {
retval->orig_indices.reserve(this->entities.size()); for (const ExtrusionEntity *ee : this->entities) {
// if we're asked to return the original indices, build a map
std::map<ExtrusionEntity*,size_t> indices_map;
ExtrusionEntitiesPtr my_paths;
for (ExtrusionEntity * const &entity_src : this->entities) {
if (role != erMixed) { if (role != erMixed) {
// The caller wants only paths with a specific extrusion role. // The caller wants only paths with a specific extrusion role.
auto role2 = entity_src->role(); auto role2 = ee->role();
if (role != role2) { if (role != role2) {
// This extrusion entity does not match the role asked. // This extrusion entity does not match the role asked.
assert(role2 != erMixed); assert(role2 != erMixed);
continue; continue;
} }
} }
out.entities.emplace_back(ee->clone());
ExtrusionEntity *entity = entity_src->clone();
my_paths.push_back(entity);
if (orig_indices != nullptr)
indices_map[entity] = &entity_src - &this->entities.front();
} }
Points endpoints;
for (const ExtrusionEntity *entity : my_paths) {
endpoints.push_back(entity->first_point());
endpoints.push_back((no_reverse || ! entity->can_reverse()) ?
entity->first_point() : entity->last_point());
} }
chain_and_reorder_extrusion_entities(out.entities, &start_near);
while (! my_paths.empty()) {
// find nearest point
int start_index = start_near.nearest_point_index(endpoints);
int path_index = start_index/2;
ExtrusionEntity* entity = my_paths.at(path_index);
// never reverse loops, since it's pointless for chained path and callers might depend on orientation
if (start_index % 2 && !no_reverse && entity->can_reverse())
entity->reverse();
retval->entities.push_back(my_paths.at(path_index));
if (orig_indices != nullptr)
orig_indices->push_back(indices_map[entity]);
my_paths.erase(my_paths.begin() + path_index);
endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
start_near = retval->entities.back()->last_point();
} }
return out;
} }
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const

View file

@ -14,15 +14,14 @@ public:
ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); } ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); }
ExtrusionEntitiesPtr entities; // we own these entities ExtrusionEntitiesPtr entities; // we own these entities
std::vector<size_t> orig_indices; // handy for XS
bool no_sort; bool no_sort;
ExtrusionEntityCollection(): no_sort(false) {}; ExtrusionEntityCollection(): no_sort(false) {};
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : orig_indices(other.orig_indices), no_sort(other.no_sort) { this->append(other.entities); } ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort) { this->append(other.entities); }
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), orig_indices(std::move(other.orig_indices)), no_sort(other.no_sort) {} ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort) {}
explicit ExtrusionEntityCollection(const ExtrusionPaths &paths); explicit ExtrusionEntityCollection(const ExtrusionPaths &paths);
ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other); ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other);
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other) ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
{ this->entities = std::move(other.entities); this->orig_indices = std::move(other.orig_indices); this->no_sort = other.no_sort; return *this; } { this->entities = std::move(other.entities); this->no_sort = other.no_sort; return *this; }
~ExtrusionEntityCollection() { clear(); } ~ExtrusionEntityCollection() { clear(); }
explicit operator ExtrusionPaths() const; explicit operator ExtrusionPaths() const;
@ -66,13 +65,10 @@ public:
} }
void replace(size_t i, const ExtrusionEntity &entity); void replace(size_t i, const ExtrusionEntity &entity);
void remove(size_t i); void remove(size_t i);
ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const; ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const;
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector<size_t>* orig_indices = nullptr) const;
ExtrusionEntityCollection chained_path_from(Point start_near, bool no_reverse = false, ExtrusionRole role = erMixed) const;
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector<size_t>* orig_indices = nullptr) const;
void reverse(); void reverse();
Point first_point() const { return this->entities.front()->first_point(); } const Point& first_point() const { return this->entities.front()->first_point(); }
Point last_point() const { return this->entities.back()->last_point(); } const Point& last_point() const { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;

View file

@ -15,40 +15,39 @@ namespace Slic3r {
struct SurfaceFillParams struct SurfaceFillParams
{ {
SurfaceFillParams() : flow(0.f, 0.f, 0.f, false) { memset(this, 0, sizeof(*this)); }
// Zero based extruder ID. // Zero based extruder ID.
unsigned int extruder; unsigned int extruder = 0;
// Infill pattern, adjusted for the density etc. // Infill pattern, adjusted for the density etc.
InfillPattern pattern; InfillPattern pattern = InfillPattern(0);
// FillBase // FillBase
// in unscaled coordinates // in unscaled coordinates
coordf_t spacing; coordf_t spacing = 0.;
// infill / perimeter overlap, in unscaled coordinates // infill / perimeter overlap, in unscaled coordinates
coordf_t overlap; coordf_t overlap = 0.;
// Angle as provided by the region config, in radians. // Angle as provided by the region config, in radians.
float angle; float angle = 0.f;
// Non-negative for a bridge. // Non-negative for a bridge.
float bridge_angle; float bridge_angle = 0.f;
// FillParams // FillParams
float density; float density = 0.f;
// Don't connect the fill lines around the inner perimeter. // Don't connect the fill lines around the inner perimeter.
bool dont_connect; bool dont_connect = false;
// Don't adjust spacing to fill the space evenly. // Don't adjust spacing to fill the space evenly.
bool dont_adjust; bool dont_adjust = false;
// width, height of extrusion, nozzle diameter, is bridge // width, height of extrusion, nozzle diameter, is bridge
// For the output, for fill generator. // For the output, for fill generator.
Flow flow; Flow flow = Flow(0.f, 0.f, 0.f, false);
// For the output // For the output
ExtrusionRole extrusion_role; ExtrusionRole extrusion_role = ExtrusionRole(0);
// Various print settings? // Various print settings?
// Index of this entry in a linear vector. // Index of this entry in a linear vector.
size_t idx; size_t idx = 0;
bool operator<(const SurfaceFillParams &rhs) const { bool operator<(const SurfaceFillParams &rhs) const {

View file

@ -1,5 +1,5 @@
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp" #include "../ShortestPath.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include "Fill3DHoneycomb.hpp" #include "Fill3DHoneycomb.hpp"
@ -175,27 +175,24 @@ void Fill3DHoneycomb::_fill_surface_single(
std::swap(expolygon_off, expolygons_off.front()); std::swap(expolygon_off, expolygons_off.front());
} }
} }
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true; bool first = true;
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { for (Polyline &polyline : chain_polylines(std::move(polylines))) {
if (! first) { if (! first) {
// Try to connect the lines. // Try to connect the lines.
Points &pts_end = polylines_out.back().points; Points &pts_end = polylines_out.back().points;
const Point &first_point = it_polyline->points.front(); const Point &first_point = polyline.points.front();
const Point &last_point = pts_end.back(); const Point &last_point = pts_end.back();
// TODO: we should also check that both points are on a fill_boundary to avoid // TODO: we should also check that both points are on a fill_boundary to avoid
// connecting paths on the boundaries of internal regions // connecting paths on the boundaries of internal regions
if ((last_point - first_point).cast<double>().norm() <= 1.5 * distance && if ((last_point - first_point).cast<double>().norm() <= 1.5 * distance &&
expolygon_off.contains(Line(last_point, first_point))) { expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline. // Append the polyline.
pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue; continue;
} }
} }
// The lines cannot be connected. // The lines cannot be connected.
polylines_out.emplace_back(std::move(*it_polyline)); polylines_out.emplace_back(std::move(polyline));
first = false; first = false;
} }
} }

View file

@ -1,5 +1,5 @@
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp" #include "../ShortestPath.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
@ -166,11 +166,8 @@ void FillGyroid::_fill_surface_single(
std::swap(expolygon_off, expolygons_off.front()); std::swap(expolygon_off, expolygons_off.front());
} }
} }
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true; bool first = true;
for (Polyline &polyline : chained) { for (Polyline &polyline : chain_polylines(std::move(polylines))) {
if (! first) { if (! first) {
// Try to connect the lines. // Try to connect the lines.
Points &pts_end = polylines_out.back().points; Points &pts_end = polylines_out.back().points;

View file

@ -1,5 +1,5 @@
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp" #include "../ShortestPath.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include "FillHoneycomb.hpp" #include "FillHoneycomb.hpp"
@ -93,22 +93,20 @@ void FillHoneycomb::_fill_surface_single(
// connect paths // connect paths
if (! paths.empty()) { // prevent calling leftmost_point() on empty collections if (! paths.empty()) { // prevent calling leftmost_point() on empty collections
Polylines chained = PolylineCollection::chained_path_from( Polylines chained = chain_polylines(std::move(paths));
std::move(paths),
PolylineCollection::leftmost_point(paths), false);
assert(paths.empty()); assert(paths.empty());
paths.clear(); paths.clear();
for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) { for (Polyline &path : chained) {
if (! paths.empty()) { if (! paths.empty()) {
// distance between first point of this path and last point of last path // distance between first point of this path and last point of last path
double distance = (it_path->first_point() - paths.back().last_point()).cast<double>().norm(); double distance = (path.first_point() - paths.back().last_point()).cast<double>().norm();
if (distance <= m.hex_width) { if (distance <= m.hex_width) {
paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end()); paths.back().points.insert(paths.back().points.end(), path.points.begin(), path.points.end());
continue; continue;
} }
} }
// Don't connect the paths. // Don't connect the paths.
paths.push_back(*it_path); paths.push_back(std::move(path));
} }
} }

View file

@ -1,5 +1,4 @@
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include "FillPlanePath.hpp" #include "FillPlanePath.hpp"

View file

@ -1,6 +1,6 @@
#include "../ClipperUtils.hpp" #include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp" #include "../ExPolygon.hpp"
#include "../PolylineCollection.hpp" #include "../ShortestPath.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include "FillRectilinear.hpp" #include "FillRectilinear.hpp"
@ -92,15 +92,12 @@ void FillRectilinear::_fill_surface_single(
std::swap(expolygon_off, expolygons_off.front()); std::swap(expolygon_off, expolygons_off.front());
} }
} }
Polylines chained = PolylineCollection::chained_path_from(
std::move(polylines),
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
bool first = true; bool first = true;
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { for (Polyline &polyline : chain_polylines(std::move(polylines))) {
if (! first) { if (! first) {
// Try to connect the lines. // Try to connect the lines.
Points &pts_end = polylines_out.back().points; Points &pts_end = polylines_out.back().points;
const Point &first_point = it_polyline->points.front(); const Point &first_point = polyline.points.front();
const Point &last_point = pts_end.back(); const Point &last_point = pts_end.back();
// Distance in X, Y. // Distance in X, Y.
const Vector distance = last_point - first_point; const Vector distance = last_point - first_point;
@ -109,12 +106,12 @@ void FillRectilinear::_fill_surface_single(
if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) && if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) &&
expolygon_off.contains(Line(last_point, first_point))) { expolygon_off.contains(Line(last_point, first_point))) {
// Append the polyline. // Append the polyline.
pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end()); pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
continue; continue;
} }
} }
// The lines cannot be connected. // The lines cannot be connected.
polylines_out.emplace_back(std::move(*it_polyline)); polylines_out.emplace_back(std::move(polyline));
first = false; first = false;
} }
} }

View file

@ -246,7 +246,7 @@ static void extract_model_from_archive(
sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
// Normal was mangled. Maybe denormals or "not a number" were stored? // Normal was mangled. Maybe denormals or "not a number" were stored?
// Just reset the normal and silently ignore it. // Just reset the normal and silently ignore it.
memset(&facet.normal, 0, sizeof(facet.normal)); facet.normal = stl_normal::Zero();
} }
facets.emplace_back(facet); facets.emplace_back(facet);
} }
@ -278,7 +278,7 @@ static void extract_model_from_archive(
instance->set_rotation(instance_rotation); instance->set_rotation(instance_rotation);
instance->set_scaling_factor(instance_scaling_factor); instance->set_scaling_factor(instance_scaling_factor);
instance->set_offset(instance_offset); instance->set_offset(instance_offset);
if (group_id != (size_t)-1) if (group_id != (unsigned int)(-1))
group_to_model_object[group_id] = model_object; group_to_model_object[group_id] = model_object;
} else { } else {
// This is not the 1st mesh of a group. Add it to the ModelObject. // This is not the 1st mesh of a group. Add it to the ModelObject.

View file

@ -6,6 +6,7 @@
#include "Geometry.hpp" #include "Geometry.hpp"
#include "GCode/PrintExtents.hpp" #include "GCode/PrintExtents.hpp"
#include "GCode/WipeTower.hpp" #include "GCode/WipeTower.hpp"
#include "ShortestPath.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include <algorithm> #include <algorithm>
@ -1160,7 +1161,7 @@ void GCode::_do_export(Print &print, FILE *file)
for (const LayerToPrint &ltp : layers_to_print) { for (const LayerToPrint &ltp : layers_to_print) {
std::vector<LayerToPrint> lrs; std::vector<LayerToPrint> lrs;
lrs.emplace_back(std::move(ltp)); lrs.emplace_back(std::move(ltp));
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), &copy - object.copies().data()); this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), nullptr, &copy - object.copies().data());
print.throw_if_canceled(); print.throw_if_canceled();
} }
#ifdef HAS_PRESSURE_EQUALIZER #ifdef HAS_PRESSURE_EQUALIZER
@ -1174,12 +1175,8 @@ void GCode::_do_export(Print &print, FILE *file)
} }
} }
} else { } else {
// Order objects using a nearest neighbor search. // Order object instances using a nearest neighbor search.
std::vector<size_t> object_indices; std::vector<std::pair<size_t, size_t>> print_object_instances_ordering = chain_print_object_instances(print);
Points object_reference_points;
for (PrintObject *object : print.objects())
object_reference_points.push_back(object->copies().front());
Slic3r::Geometry::chained_path(object_reference_points, object_indices);
// Sort layers by Z. // Sort layers by Z.
// All extrusion moves with the same top layer height are extruded uninterrupted. // All extrusion moves with the same top layer height are extruded uninterrupted.
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print); std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
@ -1218,7 +1215,7 @@ void GCode::_do_export(Print &print, FILE *file)
const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first);
if (m_wipe_tower && layer_tools.has_wipe_tower) if (m_wipe_tower && layer_tools.has_wipe_tower)
m_wipe_tower->next_layer(); m_wipe_tower->next_layer();
this->process_layer(file, print, layer.second, layer_tools, size_t(-1)); this->process_layer(file, print, layer.second, layer_tools, &print_object_instances_ordering, size_t(-1));
print.throw_if_canceled(); print.throw_if_canceled();
} }
#ifdef HAS_PRESSURE_EQUALIZER #ifdef HAS_PRESSURE_EQUALIZER
@ -1529,8 +1526,54 @@ inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
return islands; return islands;
} }
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
const std::vector<LayerToPrint> &layers,
// Ordering must be defined for normal (non-sequential print).
const std::vector<std::pair<size_t, size_t>> *ordering,
// For sequential print, the instance of the object to be printing has to be defined.
const size_t single_object_instance_idx)
{
std::vector<InstanceToPrint> out;
if (ordering == nullptr) {
// Sequential print, single object is being printed.
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
const PrintObject *print_object = layers[layer_id].object();
if (print_object)
out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
}
} else {
// Create mapping from PrintObject* to ObjectByExtruder*.
std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
sorted.reserve(objects_by_extruder.size());
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
const PrintObject *print_object = layers[layer_id].object();
if (print_object)
sorted.emplace_back(print_object, &object_by_extruder);
}
std::sort(sorted.begin(), sorted.end());
if (! sorted.empty()) {
const Print &print = *sorted.front().first->print();
out.reserve(sorted.size());
for (const std::pair<size_t, size_t> &instance_id : *ordering) {
const PrintObject &print_object = *print.objects()[instance_id.first];
std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
if (it != sorted.end() && it->first == &print_object)
// ObjectByExtruder for this PrintObject was found.
out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance_id.second);
}
}
}
return out;
}
// In sequential mode, process_layer is called once per each object and its copy, // In sequential mode, process_layer is called once per each object and its copy,
// therefore layers will contain a single entry and single_object_idx will point to the copy of the object. // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
// and performing the extruder specific extrusions together. // and performing the extruder specific extrusions together.
@ -1541,14 +1584,16 @@ void GCode::process_layer(
// Set of object & print layers of the same PrintObject and with the same print_z. // Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers, const std::vector<LayerToPrint> &layers,
const LayerTools &layer_tools, const LayerTools &layer_tools,
// Pairs of PrintObject index and its instance index.
const std::vector<std::pair<size_t, size_t>> *ordering,
// If set to size_t(-1), then print all copies of all objects. // If set to size_t(-1), then print all copies of all objects.
// Otherwise print a single copy of a single object. // Otherwise print a single copy of a single object.
const size_t single_object_idx) const size_t single_object_instance_idx)
{ {
assert(! layers.empty()); assert(! layers.empty());
// assert(! layer_tools.extruders.empty()); // assert(! layer_tools.extruders.empty());
// Either printing all copies of all objects, or just a single copy of a single object. // Either printing all copies of all objects, or just a single copy of a single object.
assert(single_object_idx == size_t(-1) || layers.size() == 1); assert(single_object_instance_idx == size_t(-1) || layers.size() == 1);
if (layer_tools.extruders.empty()) if (layer_tools.extruders.empty())
// Nothing to extrude. // Nothing to extrude.
@ -1762,6 +1807,17 @@ void GCode::process_layer(
layer_surface_bboxes.reserve(n_slices); layer_surface_bboxes.reserve(n_slices);
for (const ExPolygon &expoly : layer.slices.expolygons) for (const ExPolygon &expoly : layer.slices.expolygons)
layer_surface_bboxes.push_back(get_extents(expoly.contour)); layer_surface_bboxes.push_back(get_extents(expoly.contour));
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
std::vector<size_t> slices_test_order;
slices_test_order.reserve(n_slices);
for (size_t i = 0; i < n_slices; ++ i)
slices_test_order.emplace_back(i);
std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](int i, int j) {
const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
return s1.x() * s1.y() < s2.x() * s2.y();
});
auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
const BoundingBox &bbox = layer_surface_bboxes[i]; const BoundingBox &bbox = layer_surface_bboxes[i];
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
@ -1809,20 +1865,23 @@ void GCode::process_layer(
extruder, extruder,
&layer_to_print - layers.data(), &layer_to_print - layers.data(),
layers.size(), n_slices+1); layers.size(), n_slices+1);
for (size_t i = 0; i <= n_slices; ++i) for (size_t i = 0; i <= n_slices; ++ i) {
bool last = i == n_slices;
size_t island_idx = last ? n_slices : slices_test_order[i];
if (// fill->first_point does not fit inside any slice if (// fill->first_point does not fit inside any slice
i == n_slices || last ||
// fill->first_point fits inside ith slice // fill->first_point fits inside ith slice
point_inside_surface(i, fill->first_point())) { point_inside_surface(island_idx, fill->first_point())) {
if (islands[i].by_region.empty()) if (islands[island_idx].by_region.empty())
islands[i].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size()); islands[island_idx].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size());
break; break;
} }
} }
} }
} }
} }
}
} // for regions } // for regions
} }
} // for objects } // for objects
@ -1883,62 +1942,49 @@ void GCode::process_layer(
if (objects_by_extruder_it == by_extruder.end()) if (objects_by_extruder_it == by_extruder.end())
continue; continue;
std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden(); bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
if (is_anything_overridden && print_wipe_extrusions == 0) if (is_anything_overridden && print_wipe_extrusions == 0)
gcode+="; PURGING FINISHED\n"; gcode+="; PURGING FINISHED\n";
for (ObjectByExtruder &object_by_extruder : objects_by_extruder_it->second) { for (InstanceToPrint &instance_to_print : instances_to_print) {
const size_t layer_id = &object_by_extruder - objects_by_extruder_it->second.data(); m_config.apply(instance_to_print.print_object.config(), true);
const PrintObject *print_object = layers[layer_id].object(); m_layer = layers[instance_to_print.layer_id].layer();
if (print_object == nullptr)
// This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z.
continue;
m_config.apply(print_object->config(), true);
m_layer = layers[layer_id].layer();
if (m_config.avoid_crossing_perimeters) if (m_config.avoid_crossing_perimeters)
m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true)); m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true));
Points copies;
if (single_object_idx == size_t(-1))
copies = print_object->copies();
else
copies.push_back(print_object->copies()[single_object_idx]);
// Sort the copies by the closest point starting with the current print position.
unsigned int copy_id = 0;
for (const Point &copy : copies) {
if (this->config().gcode_label_objects) if (this->config().gcode_label_objects)
gcode += std::string("; printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n"; gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
// When starting a new object, use the external motion planner for the first travel move. // When starting a new object, use the external motion planner for the first travel move.
std::pair<const PrintObject*, Point> this_object_copy(print_object, copy); const Point &offset = instance_to_print.print_object.copies()[instance_to_print.instance_id];
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
if (m_last_obj_copy != this_object_copy) if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once = true; m_avoid_crossing_perimeters.use_external_mp_once = true;
m_last_obj_copy = this_object_copy; m_last_obj_copy = this_object_copy;
this->set_origin(unscale(copy)); this->set_origin(unscale(offset));
if (object_by_extruder.support != nullptr && !print_wipe_extrusions) { if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
m_layer = layers[layer_id].support_layer; m_layer = layers[instance_to_print.layer_id].support_layer;
gcode += this->extrude_support( gcode += this->extrude_support(
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths. // support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
object_by_extruder.support->chained_path_from(m_last_pos, false, object_by_extruder.support_extrusion_role)); instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role));
m_layer = layers[layer_id].layer(); m_layer = layers[instance_to_print.layer_id].layer();
} }
for (ObjectByExtruder::Island &island : object_by_extruder.islands) { for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(copy_id, extruder_id, print_wipe_extrusions) : island.by_region; const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region;
if (print.config().infill_first) { if (print.config().infill_first) {
gcode += this->extrude_infill(print, by_region_specific); gcode += this->extrude_infill(print, by_region_specific);
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]); gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
} else { } else {
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]); gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
gcode += this->extrude_infill(print,by_region_specific); gcode += this->extrude_infill(print,by_region_specific);
} }
} }
if (this->config().gcode_label_objects) if (this->config().gcode_label_objects)
gcode += std::string("; stop printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n"; gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
++ copy_id;
}
} }
} }
} }
@ -2542,12 +2588,10 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
std::string gcode; std::string gcode;
for (const ObjectByExtruder::Island::Region &region : by_region) { for (const ObjectByExtruder::Island::Region &region : by_region) {
m_config.apply(print.regions()[&region - &by_region.front()]->config()); m_config.apply(print.regions()[&region - &by_region.front()]->config());
ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false); for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos).entities) {
for (ExtrusionEntity *fill : chained.entities) {
auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill); auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
if (eec) { if (eec) {
ExtrusionEntityCollection chained2 = eec->chained_path_from(m_last_pos, false); for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
for (ExtrusionEntity *ee : chained2.entities)
gcode += this->extrude_entity(*ee, "infill"); gcode += this->extrude_entity(*ee, "infill");
} else } else
gcode += this->extrude_entity(*fill, "infill"); gcode += this->extrude_entity(*fill, "infill");

View file

@ -202,7 +202,7 @@ protected:
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
}; };
static std::vector<GCode::LayerToPrint> collect_layers_to_print(const PrintObject &object); static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print); static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
void process_layer( void process_layer(
// Write into the output file. // Write into the output file.
@ -211,6 +211,8 @@ protected:
// Set of object & print layers of the same PrintObject and with the same print_z. // Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers, const std::vector<LayerToPrint> &layers,
const LayerTools &layer_tools, const LayerTools &layer_tools,
// Pairs of PrintObject index and its instance index.
const std::vector<std::pair<size_t, size_t>> *ordering,
// If set to size_t(-1), then print all copies of all objects. // If set to size_t(-1), then print all copies of all objects.
// Otherwise print a single copy of a single object. // Otherwise print a single copy of a single object.
const size_t single_object_idx = size_t(-1)); const size_t single_object_idx = size_t(-1));
@ -258,6 +260,25 @@ protected:
std::vector<Island> islands; std::vector<Island> islands;
}; };
struct InstanceToPrint
{
InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) :
object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {}
ObjectByExtruder &object_by_extruder;
const size_t layer_id;
const PrintObject &print_object;
// Instance idx of the copy of a print object.
const size_t instance_id;
};
std::vector<InstanceToPrint> sort_print_object_instances(
std::vector<ObjectByExtruder> &objects_by_extruder,
const std::vector<LayerToPrint> &layers,
// Ordering must be defined for normal (non-sequential print).
const std::vector<std::pair<size_t, size_t>> *ordering,
// For sequential print, the instance of the object to be printing has to be defined.
const size_t single_object_instance_idx);
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid); std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid);
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region); std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);

View file

@ -20,6 +20,7 @@ static const unsigned int DEFAULT_EXTRUDER_ID = 0;
static const unsigned int DEFAULT_COLOR_PRINT_ID = 0; static const unsigned int DEFAULT_COLOR_PRINT_ID = 0;
static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f); static const Slic3r::Vec3d DEFAULT_START_POSITION = Slic3r::Vec3d(0.0f, 0.0f, 0.0f);
static const float DEFAULT_START_EXTRUSION = 0.0f; static const float DEFAULT_START_EXTRUSION = 0.0f;
static const float DEFAULT_FAN_SPEED = 0.0f;
namespace Slic3r { namespace Slic3r {
@ -36,21 +37,23 @@ const float GCodeAnalyzer::Default_Height = 0.0f;
GCodeAnalyzer::Metadata::Metadata() GCodeAnalyzer::Metadata::Metadata()
: extrusion_role(erNone) : extrusion_role(erNone)
, extruder_id(DEFAULT_EXTRUDER_ID) , extruder_id(DEFAULT_EXTRUDER_ID)
, cp_color_id(DEFAULT_COLOR_PRINT_ID)
, mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm) , mm3_per_mm(GCodeAnalyzer::Default_mm3_per_mm)
, width(GCodeAnalyzer::Default_Width) , width(GCodeAnalyzer::Default_Width)
, height(GCodeAnalyzer::Default_Height) , height(GCodeAnalyzer::Default_Height)
, feedrate(DEFAULT_FEEDRATE) , feedrate(DEFAULT_FEEDRATE)
, fan_speed(DEFAULT_FAN_SPEED)
, cp_color_id(DEFAULT_COLOR_PRINT_ID)
{ {
} }
GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id/* = 0*/) GCodeAnalyzer::Metadata::Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id/* = 0*/)
: extrusion_role(extrusion_role) : extrusion_role(extrusion_role)
, extruder_id(extruder_id) , extruder_id(extruder_id)
, mm3_per_mm(mm3_per_mm) , mm3_per_mm(mm3_per_mm)
, width(width) , width(width)
, height(height) , height(height)
, feedrate(feedrate) , feedrate(feedrate)
, fan_speed(fan_speed)
, cp_color_id(cp_color_id) , cp_color_id(cp_color_id)
{ {
} }
@ -75,15 +78,18 @@ bool GCodeAnalyzer::Metadata::operator != (const GCodeAnalyzer::Metadata& other)
if (feedrate != other.feedrate) if (feedrate != other.feedrate)
return true; return true;
if (fan_speed != other.fan_speed)
return true;
if (cp_color_id != other.cp_color_id) if (cp_color_id != other.cp_color_id)
return true; return true;
return false; return false;
} }
GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id/* = 0*/) GCodeAnalyzer::GCodeMove::GCodeMove(GCodeMove::EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id/* = 0*/)
: type(type) : type(type)
, data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, cp_color_id) , data(extrusion_role, extruder_id, mm3_per_mm, width, height, feedrate, fan_speed, cp_color_id)
, start_position(start_position) , start_position(start_position)
, end_position(end_position) , end_position(end_position)
, delta_extruder(delta_extruder) , delta_extruder(delta_extruder)
@ -133,6 +139,7 @@ void GCodeAnalyzer::reset()
_set_feedrate(DEFAULT_FEEDRATE); _set_feedrate(DEFAULT_FEEDRATE);
_set_start_position(DEFAULT_START_POSITION); _set_start_position(DEFAULT_START_POSITION);
_set_start_extrusion(DEFAULT_START_EXTRUSION); _set_start_extrusion(DEFAULT_START_EXTRUSION);
_set_fan_speed(DEFAULT_FAN_SPEED);
_reset_axes_position(); _reset_axes_position();
_reset_cached_position(); _reset_cached_position();
@ -259,6 +266,16 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi
_processM83(line); _processM83(line);
break; break;
} }
case 106: // Set fan speed
{
_processM106(line);
break;
}
case 107: // Disable fan
{
_processM107(line);
break;
}
case 108: case 108:
case 135: case 135:
{ {
@ -448,6 +465,24 @@ void GCodeAnalyzer::_processM83(const GCodeReader::GCodeLine& line)
_set_e_local_positioning_type(Relative); _set_e_local_positioning_type(Relative);
} }
void GCodeAnalyzer::_processM106(const GCodeReader::GCodeLine& line)
{
if (!line.has('P'))
{
// The absence of P means the print cooling fan, so ignore anything else.
float new_fan_speed;
if (line.has_value('S', new_fan_speed))
_set_fan_speed((100.0f / 256.0f) * new_fan_speed);
else
_set_fan_speed(100.0f);
}
}
void GCodeAnalyzer::_processM107(const GCodeReader::GCodeLine& line)
{
_set_fan_speed(0.0f);
}
void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line)
{ {
// These M-codes are used by MakerWare and Sailfish to change active tool. // These M-codes are used by MakerWare and Sailfish to change active tool.
@ -726,6 +761,16 @@ float GCodeAnalyzer::_get_feedrate() const
return m_state.data.feedrate; return m_state.data.feedrate;
} }
void GCodeAnalyzer::_set_fan_speed(float fan_speed_percentage)
{
m_state.data.fan_speed = fan_speed_percentage;
}
float GCodeAnalyzer::_get_fan_speed() const
{
return m_state.data.fan_speed;
}
void GCodeAnalyzer::_set_axis_position(EAxis axis, float position) void GCodeAnalyzer::_set_axis_position(EAxis axis, float position)
{ {
m_state.position[axis] = position; m_state.position[axis] = position;
@ -798,7 +843,7 @@ void GCodeAnalyzer::_store_move(GCodeAnalyzer::GCodeMove::EType type)
Vec3d start_position = _get_start_position() + extruder_offset; Vec3d start_position = _get_start_position() + extruder_offset;
Vec3d end_position = _get_end_position() + extruder_offset; Vec3d end_position = _get_end_position() + extruder_offset;
it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_cp_color_id()); it->second.emplace_back(type, _get_extrusion_role(), extruder_id, _get_mm3_per_mm(), _get_width(), _get_height(), _get_feedrate(), start_position, end_position, _get_delta_extrusion(), _get_fan_speed(), _get_cp_color_id());
} }
bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const
@ -834,6 +879,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
path.polyline = polyline; path.polyline = polyline;
path.feedrate = data.feedrate; path.feedrate = data.feedrate;
path.extruder_id = data.extruder_id; path.extruder_id = data.extruder_id;
path.fan_speed = data.fan_speed;
path.cp_color_id = data.cp_color_id; path.cp_color_id = data.cp_color_id;
get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path); get_layer_at_z(preview_data.extrusion.layers, z).paths.push_back(path);
@ -854,6 +900,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
GCodePreviewData::Range width_range; GCodePreviewData::Range width_range;
GCodePreviewData::Range feedrate_range; GCodePreviewData::Range feedrate_range;
GCodePreviewData::Range volumetric_rate_range; GCodePreviewData::Range volumetric_rate_range;
GCodePreviewData::Range fan_speed_range;
// to avoid to call the callback too often // to avoid to call the callback too often
unsigned int cancel_callback_threshold = (unsigned int)std::max((int)extrude_moves->second.size() / 25, 1); unsigned int cancel_callback_threshold = (unsigned int)std::max((int)extrude_moves->second.size() / 25, 1);
@ -888,6 +935,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
width_range.update_from(move.data.width); width_range.update_from(move.data.width);
feedrate_range.update_from(move.data.feedrate); feedrate_range.update_from(move.data.feedrate);
volumetric_rate_range.update_from(volumetric_rate); volumetric_rate_range.update_from(volumetric_rate);
fan_speed_range.update_from(move.data.fan_speed);
} }
else else
// append end vertex of the move to current polyline // append end vertex of the move to current polyline
@ -906,6 +954,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ
preview_data.ranges.width.update_from(width_range); preview_data.ranges.width.update_from(width_range);
preview_data.ranges.feedrate.update_from(feedrate_range); preview_data.ranges.feedrate.update_from(feedrate_range);
preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range); preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range);
preview_data.ranges.fan_speed.update_from(fan_speed_range);
// we need to sort the layers by their z as they can be shuffled in case of sequential prints // we need to sort the layers by their z as they can be shuffled in case of sequential prints
std::sort(preview_data.extrusion.layers.begin(), preview_data.extrusion.layers.end(), [](const GCodePreviewData::Extrusion::Layer& l1, const GCodePreviewData::Extrusion::Layer& l2)->bool { return l1.z < l2.z; }); std::sort(preview_data.extrusion.layers.begin(), preview_data.extrusion.layers.end(), [](const GCodePreviewData::Extrusion::Layer& l1, const GCodePreviewData::Extrusion::Layer& l2)->bool { return l1.z < l2.z; });

View file

@ -54,10 +54,11 @@ public:
float width; // mm float width; // mm
float height; // mm float height; // mm
float feedrate; // mm/s float feedrate; // mm/s
float fan_speed; // percentage
unsigned int cp_color_id; unsigned int cp_color_id;
Metadata(); Metadata();
Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, unsigned int cp_color_id = 0); Metadata(ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, float fan_speed, unsigned int cp_color_id = 0);
bool operator != (const Metadata& other) const; bool operator != (const Metadata& other) const;
}; };
@ -81,7 +82,7 @@ public:
Vec3d end_position; Vec3d end_position;
float delta_extruder; float delta_extruder;
GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, unsigned int cp_color_id = 0); GCodeMove(EType type, ExtrusionRole extrusion_role, unsigned int extruder_id, double mm3_per_mm, float width, float height, float feedrate, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder, float fan_speed, unsigned int cp_color_id = 0);
GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder); GCodeMove(EType type, const Metadata& data, const Vec3d& start_position, const Vec3d& end_position, float delta_extruder);
}; };
@ -171,6 +172,12 @@ private:
// Set extruder to relative mode // Set extruder to relative mode
void _processM83(const GCodeReader::GCodeLine& line); void _processM83(const GCodeReader::GCodeLine& line);
// Set fan speed
void _processM106(const GCodeReader::GCodeLine& line);
// Disable fan
void _processM107(const GCodeReader::GCodeLine& line);
// Set tool (MakerWare and Sailfish flavor) // Set tool (MakerWare and Sailfish flavor)
void _processM108orM135(const GCodeReader::GCodeLine& line); void _processM108orM135(const GCodeReader::GCodeLine& line);
@ -233,6 +240,9 @@ private:
void _set_feedrate(float feedrate_mm_sec); void _set_feedrate(float feedrate_mm_sec);
float _get_feedrate() const; float _get_feedrate() const;
void _set_fan_speed(float fan_speed_percentage);
float _get_fan_speed() const;
void _set_axis_position(EAxis axis, float position); void _set_axis_position(EAxis axis, float position);
float _get_axis_position(EAxis axis) const; float _get_axis_position(EAxis axis) const;

View file

@ -241,6 +241,7 @@ void GCodePreviewData::set_default()
::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.fan_speed.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); ::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color));
extrusion.set_default(); extrusion.set_default();
@ -287,6 +288,11 @@ GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) con
return ranges.feedrate.get_color_at(feedrate); return ranges.feedrate.get_color_at(feedrate);
} }
GCodePreviewData::Color GCodePreviewData::get_fan_speed_color(float fan_speed) const
{
return ranges.fan_speed.get_color_at(fan_speed);
}
GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const
{ {
return ranges.volumetric_rate.get_color_at(rate); return ranges.volumetric_rate.get_color_at(rate);
@ -358,8 +364,10 @@ std::string GCodePreviewData::get_legend_title() const
return L("Width (mm)"); return L("Width (mm)");
case Extrusion::Feedrate: case Extrusion::Feedrate:
return L("Speed (mm/s)"); return L("Speed (mm/s)");
case Extrusion::FanSpeed:
return L("Fan Speed (%)");
case Extrusion::VolumetricRate: case Extrusion::VolumetricRate:
return L("Volumetric flow rate (mm3/s)"); return L("Volumetric flow rate (mm³/s)");
case Extrusion::Tool: case Extrusion::Tool:
return L("Tool"); return L("Tool");
case Extrusion::ColorPrint: case Extrusion::ColorPrint:
@ -421,6 +429,11 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std::
Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f); Helper::FillListFromRange(items, ranges.feedrate, 1, 1.0f);
break; break;
} }
case Extrusion::FanSpeed:
{
Helper::FillListFromRange(items, ranges.fan_speed, 0, 1.0f);
break;
}
case Extrusion::VolumetricRate: case Extrusion::VolumetricRate:
{ {
Helper::FillListFromRange(items, ranges.volumetric_rate, 3, 1.0f); Helper::FillListFromRange(items, ranges.volumetric_rate, 3, 1.0f);

View file

@ -52,6 +52,8 @@ public:
Range width; Range width;
// Color mapping by feedrate. // Color mapping by feedrate.
Range feedrate; Range feedrate;
// Color mapping by fan speed.
Range fan_speed;
// Color mapping by volumetric extrusion rate. // Color mapping by volumetric extrusion rate.
Range volumetric_rate; Range volumetric_rate;
}; };
@ -74,6 +76,7 @@ public:
Height, Height,
Width, Width,
Feedrate, Feedrate,
FanSpeed,
VolumetricRate, VolumetricRate,
Tool, Tool,
ColorPrint, ColorPrint,
@ -205,6 +208,7 @@ public:
Color get_height_color(float height) const; Color get_height_color(float height) const;
Color get_width_color(float width) const; Color get_width_color(float width) const;
Color get_feedrate_color(float feedrate) const; Color get_feedrate_color(float feedrate) const;
Color get_fan_speed_color(float fan_speed) const;
Color get_volumetric_rate_color(float rate) const; Color get_volumetric_rate_color(float rate) const;
void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha); void set_extrusion_role_color(const std::string& role_name, float red, float green, float blue, float alpha);

View file

@ -3,7 +3,6 @@
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "Line.hpp" #include "Line.hpp"
#include "PolylineCollection.hpp"
#include "clipper.hpp" #include "clipper.hpp"
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
@ -309,49 +308,7 @@ convex_hull(const Polygons &polygons)
return convex_hull(std::move(pp)); return convex_hull(std::move(pp));
} }
/* accepts an arrayref of points and returns a list of indices bool directions_parallel(double angle1, double angle2, double max_diff)
according to a nearest-neighbor walk */
void
chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near)
{
PointConstPtrs my_points;
std::map<const Point*,Points::size_type> indices;
my_points.reserve(points.size());
for (Points::const_iterator it = points.begin(); it != points.end(); ++it) {
my_points.push_back(&*it);
indices[&*it] = it - points.begin();
}
retval.reserve(points.size());
while (!my_points.empty()) {
Points::size_type idx = start_near.nearest_point_index(my_points);
start_near = *my_points[idx];
retval.push_back(indices[ my_points[idx] ]);
my_points.erase(my_points.begin() + idx);
}
}
void
chained_path(const Points &points, std::vector<Points::size_type> &retval)
{
if (points.empty()) return; // can't call front() on empty vector
chained_path(points, retval, points.front());
}
/* retval and items must be different containers */
template<class T>
void
chained_path_items(Points &points, T &items, T &retval)
{
std::vector<Points::size_type> indices;
chained_path(points, indices);
for (std::vector<Points::size_type>::const_iterator it = indices.begin(); it != indices.end(); ++it)
retval.push_back(items[*it]);
}
template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval);
bool
directions_parallel(double angle1, double angle2, double max_diff)
{ {
double diff = fabs(angle1 - angle2); double diff = fabs(angle1 - angle2);
max_diff += EPSILON; max_diff += EPSILON;
@ -359,8 +316,7 @@ directions_parallel(double angle1, double angle2, double max_diff)
} }
template<class T> template<class T>
bool bool contains(const std::vector<T> &vector, const Point &point)
contains(const std::vector<T> &vector, const Point &point)
{ {
for (typename std::vector<T>::const_iterator it = vector.begin(); it != vector.end(); ++it) { for (typename std::vector<T>::const_iterator it = vector.begin(); it != vector.end(); ++it) {
if (it->contains(point)) return true; if (it->contains(point)) return true;
@ -369,16 +325,14 @@ contains(const std::vector<T> &vector, const Point &point)
} }
template bool contains(const ExPolygons &vector, const Point &point); template bool contains(const ExPolygons &vector, const Point &point);
double double rad2deg_dir(double angle)
rad2deg_dir(double angle)
{ {
angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0); angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0);
if (angle < 0) angle += PI; if (angle < 0) angle += PI;
return rad2deg(angle); return rad2deg(angle);
} }
void void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
{ {
Polygons pp; Polygons pp;
for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) { for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) {
@ -391,8 +345,7 @@ simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
*retval = Slic3r::simplify_polygons(pp); *retval = Slic3r::simplify_polygons(pp);
} }
double double linint(double value, double oldmin, double oldmax, double newmin, double newmax)
linint(double value, double oldmin, double oldmax, double newmin, double newmax)
{ {
return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin; return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
} }

View file

@ -138,9 +138,6 @@ Pointf3s convex_hull(Pointf3s points);
Polygon convex_hull(Points points); Polygon convex_hull(Points points);
Polygon convex_hull(const Polygons &polygons); Polygon convex_hull(const Polygons &polygons);
void chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near);
void chained_path(const Points &points, std::vector<Points::size_type> &retval);
template<class T> void chained_path_items(Points &points, T &items, T &retval);
bool directions_parallel(double angle1, double angle2, double max_diff = 0); bool directions_parallel(double angle1, double angle2, double max_diff = 0);
template<class T> bool contains(const std::vector<T> &vector, const Point &point); template<class T> bool contains(const std::vector<T> &vector, const Point &point);
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); } template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }

View file

@ -0,0 +1,233 @@
// KD tree built upon external data set, referencing the external data by integer indices.
#ifndef slic3r_KDTreeIndirect_hpp_
#define slic3r_KDTreeIndirect_hpp_
#include <algorithm>
#include <limits>
#include <vector>
#include "Utils.hpp" // for next_highest_power_of_2()
namespace Slic3r {
// KD tree for N-dimensional closest point search.
template<size_t ANumDimensions, typename ACoordType, typename ACoordinateFn>
class KDTreeIndirect
{
public:
static constexpr size_t NumDimensions = ANumDimensions;
using CoordinateFn = ACoordinateFn;
using CoordType = ACoordType;
// Following could be static constexpr size_t, but that would not link in C++11
enum : size_t {
npos = size_t(-1)
};
KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {}
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> indices) : coordinate(coordinate) { this->build(std::move(indices)); }
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> &&indices) : coordinate(coordinate) { this->build(std::move(indices)); }
KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); }
KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {}
KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; }
void clear() { m_nodes.clear(); }
void build(size_t num_indices)
{
std::vector<size_t> indices;
indices.reserve(num_indices);
for (size_t i = 0; i < num_indices; ++ i)
indices.emplace_back(i);
this->build(std::move(indices));
}
void build(std::vector<size_t> &&indices)
{
if (indices.empty())
clear();
else {
// Allocate a next highest power of 2 nodes, because the incomplete binary tree will not have the leaves filled strictly from the left.
m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos);
build_recursive(indices, 0, 0, 0, (int)(indices.size() - 1));
}
indices.clear();
}
enum class VisitorReturnMask : unsigned int
{
CONTINUE_LEFT = 1,
CONTINUE_RIGHT = 2,
STOP = 4,
};
template<typename CoordType>
unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const
{
CoordType dist = point_coord - this->coordinate(idx, dimension);
return (dist * dist < search_radius + CoordType(EPSILON)) ?
// The plane intersects a hypersphere centered at point_coord of search_radius.
((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) :
// The plane does not intersect the hypersphere.
(dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT);
}
// Visitor is supposed to return a bit mask of VisitorReturnMask.
template<typename Visitor>
void visit(Visitor &visitor) const
{
visit_recursive(0, 0, visitor);
}
CoordinateFn coordinate;
private:
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
void build_recursive(std::vector<size_t> &input, size_t node, int dimension, int left, int right)
{
if (left > right)
return;
assert(node < m_nodes.size());
if (left == right) {
// Insert a node into the balanced tree.
m_nodes[node] = input[left];
return;
}
// Partition the input sequence to two equal halves.
int center = (left + right) >> 1;
partition_input(input, dimension, left, right, center);
// Insert a node into the tree.
m_nodes[node] = input[center];
// Partition the left and right subtrees.
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
build_recursive(input, (node << 1) + 1, next_dimension, left, center - 1);
build_recursive(input, (node << 1) + 2, next_dimension, center + 1, right);
}
// Partition the input m_nodes <left, right> at k using QuickSelect method.
// https://en.wikipedia.org/wiki/Quickselect
void partition_input(std::vector<size_t> &input, int dimension, int left, int right, int k) const
{
while (left < right) {
// Guess the k'th element.
// Pick the pivot as a median of first, center and last value.
// Sort first, center and last values.
int center = (left + right) >> 1;
auto left_value = this->coordinate(input[left], dimension);
auto center_value = this->coordinate(input[center], dimension);
auto right_value = this->coordinate(input[right], dimension);
if (center_value < left_value) {
std::swap(input[left], input[center]);
std::swap(left_value, center_value);
}
if (right_value < left_value) {
std::swap(input[left], input[right]);
std::swap(left_value, right_value);
}
if (right_value < center_value) {
std::swap(input[center], input[right]);
// No need to do that, result is not used.
// std::swap(center_value, right_value);
}
// Only two or three values are left and those are sorted already.
if (left + 3 > right)
break;
// left and right items are already at their correct positions.
// input[left].point[dimension] <= input[center].point[dimension] <= input[right].point[dimension]
// Move the pivot to the (right - 1) position.
std::swap(input[center], input[right - 1]);
// Pivot value.
double pivot = this->coordinate(input[right - 1], dimension);
// Partition the set based on the pivot.
int i = left;
int j = right - 1;
for (;;) {
// Skip left points that are already at correct positions.
// Search will certainly stop at position (right - 1), which stores the pivot.
while (this->coordinate(input[++ i], dimension) < pivot) ;
// Skip right points that are already at correct positions.
while (this->coordinate(input[-- j], dimension) > pivot && i < j) ;
if (i >= j)
break;
std::swap(input[i], input[j]);
}
// Restore pivot to the center of the sequence.
std::swap(input[i], input[right]);
// Which side the kth element is in?
if (k < i)
right = i - 1;
else if (k == i)
// Sequence is partitioned, kth element is at its place.
break;
else
left = i + 1;
}
}
template<typename Visitor>
void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const
{
assert(! m_nodes.empty());
if (node >= m_nodes.size() || m_nodes[node] == npos)
return;
// Left / right child node index.
size_t left = (node << 1) + 1;
size_t right = left + 1;
unsigned int mask = visitor(m_nodes[node], dimension);
if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) {
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT)
visit_recursive(left, next_dimension, visitor);
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT)
visit_recursive(right, next_dimension, visitor);
}
}
std::vector<size_t> m_nodes;
};
// Find a closest point using Euclidian metrics.
// Returns npos if not found.
template<typename KDTreeIndirectType, typename PointType, typename FilterFn>
size_t find_closest_point(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter)
{
struct Visitor {
using CoordType = typename KDTreeIndirectType::CoordType;
const KDTreeIndirectType &kdtree;
const PointType &point;
const FilterFn filter;
size_t min_idx = KDTreeIndirectType::npos;
CoordType min_dist = std::numeric_limits<CoordType>::max();
Visitor(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) : kdtree(kdtree), point(point), filter(filter) {}
unsigned int operator()(size_t idx, size_t dimension) {
if (this->filter(idx)) {
auto dist = CoordType(0);
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++ i) {
CoordType d = point[i] - kdtree.coordinate(idx, i);
dist += d * d;
}
if (dist < min_dist) {
min_dist = dist;
min_idx = idx;
}
}
return kdtree.descent_mask(point[dimension], min_dist, idx, dimension);
}
} visitor(kdtree, point, filter);
kdtree.visit(visitor);
return visitor.min_idx;
}
template<typename KDTreeIndirectType, typename PointType>
size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& point)
{
return find_closest_point(kdtree, point, [](size_t) { return true; });
}
} // namespace Slic3r
#endif /* slic3r_KDTreeIndirect_hpp_ */

View file

@ -1,8 +1,8 @@
#include "Layer.hpp" #include "Layer.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "Print.hpp" #include "Print.hpp"
#include "Fill/Fill.hpp" #include "Fill/Fill.hpp"
#include "ShortestPath.hpp"
#include "SVG.hpp" #include "SVG.hpp"
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
@ -57,8 +57,7 @@ void Layer::make_slices()
ordering_points.push_back(ex.contour.first_point()); ordering_points.push_back(ex.contour.first_point());
// sort slices // sort slices
std::vector<Points::size_type> order; std::vector<Points::size_type> order = chain_points(ordering_points);
Slic3r::Geometry::chained_path(ordering_points, order);
// populate slices vector // populate slices vector
for (size_t i : order) for (size_t i : order)

View file

@ -6,8 +6,6 @@
#include "SurfaceCollection.hpp" #include "SurfaceCollection.hpp"
#include "ExtrusionEntityCollection.hpp" #include "ExtrusionEntityCollection.hpp"
#include "ExPolygonCollection.hpp" #include "ExPolygonCollection.hpp"
#include "PolylineCollection.hpp"
namespace Slic3r { namespace Slic3r {
@ -48,7 +46,7 @@ public:
Polygons bridged; Polygons bridged;
// collection of polylines representing the unsupported bridge edges // collection of polylines representing the unsupported bridge edges
PolylineCollection unsupported_bridge_edges; Polylines unsupported_bridge_edges;
// ordered collection of extrusion paths/loops to build all perimeters // ordered collection of extrusion paths/loops to build all perimeters
// (this collection contains only ExtrusionEntityCollection objects) // (this collection contains only ExtrusionEntityCollection objects)

View file

@ -272,7 +272,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
bridges[idx_last].bridge_angle = bd.angle; bridges[idx_last].bridge_angle = bd.angle;
if (this->layer()->object()->config().support_material) { if (this->layer()->object()->config().support_material) {
polygons_append(this->bridged, bd.coverage()); polygons_append(this->bridged, bd.coverage());
this->unsupported_bridge_edges.append(bd.unsupported_edges()); append(this->unsupported_bridge_edges, bd.unsupported_edges());
} }
} else if (custom_angle > 0) { } else if (custom_angle > 0) {
// Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in

View file

@ -1462,7 +1462,7 @@ stl_stats ModelObject::get_object_stl_stats() const
return this->volumes[0]->mesh().stl.stats; return this->volumes[0]->mesh().stl.stats;
stl_stats full_stats; stl_stats full_stats;
memset(&full_stats, 0, sizeof(stl_stats)); full_stats.volume = 0.f;
// fill full_stats from all objet's meshes // fill full_stats from all objet's meshes
for (ModelVolume* volume : this->volumes) for (ModelVolume* volume : this->volumes)

View file

@ -3,11 +3,6 @@
namespace Slic3r { namespace Slic3r {
MultiPoint::operator Points() const
{
return this->points;
}
void MultiPoint::scale(double factor) void MultiPoint::scale(double factor)
{ {
for (Point &pt : points) for (Point &pt : points)
@ -57,18 +52,7 @@ void MultiPoint::rotate(double angle, const Point &center)
} }
} }
void MultiPoint::reverse() double MultiPoint::length() const
{
std::reverse(this->points.begin(), this->points.end());
}
Point MultiPoint::first_point() const
{
return this->points.front();
}
double
MultiPoint::length() const
{ {
Lines lines = this->lines(); Lines lines = this->lines();
double len = 0; double len = 0;
@ -78,8 +62,7 @@ MultiPoint::length() const
return len; return len;
} }
int int MultiPoint::find_point(const Point &point) const
MultiPoint::find_point(const Point &point) const
{ {
for (const Point &pt : this->points) for (const Point &pt : this->points)
if (pt == point) if (pt == point)
@ -87,21 +70,18 @@ MultiPoint::find_point(const Point &point) const
return -1; // not found return -1; // not found
} }
bool bool MultiPoint::has_boundary_point(const Point &point) const
MultiPoint::has_boundary_point(const Point &point) const
{ {
double dist = (point.projection_onto(*this) - point).cast<double>().norm(); double dist = (point.projection_onto(*this) - point).cast<double>().norm();
return dist < SCALED_EPSILON; return dist < SCALED_EPSILON;
} }
BoundingBox BoundingBox MultiPoint::bounding_box() const
MultiPoint::bounding_box() const
{ {
return BoundingBox(this->points); return BoundingBox(this->points);
} }
bool bool MultiPoint::has_duplicate_points() const
MultiPoint::has_duplicate_points() const
{ {
for (size_t i = 1; i < points.size(); ++i) for (size_t i = 1; i < points.size(); ++i)
if (points[i-1] == points[i]) if (points[i-1] == points[i])
@ -109,8 +89,7 @@ MultiPoint::has_duplicate_points() const
return false; return false;
} }
bool bool MultiPoint::remove_duplicate_points()
MultiPoint::remove_duplicate_points()
{ {
size_t j = 0; size_t j = 0;
for (size_t i = 1; i < points.size(); ++i) { for (size_t i = 1; i < points.size(); ++i) {
@ -129,8 +108,7 @@ MultiPoint::remove_duplicate_points()
return false; return false;
} }
bool bool MultiPoint::intersection(const Line& line, Point* intersection) const
MultiPoint::intersection(const Line& line, Point* intersection) const
{ {
Lines lines = this->lines(); Lines lines = this->lines();
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {

View file

@ -17,7 +17,8 @@ class MultiPoint
public: public:
Points points; Points points;
operator Points() const; operator Points() const { return this->points; }
MultiPoint() {} MultiPoint() {}
MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(const MultiPoint &other) : points(other.points) {}
MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {}
@ -32,9 +33,10 @@ public:
void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double angle) { this->rotate(cos(angle), sin(angle)); }
void rotate(double cos_angle, double sin_angle); void rotate(double cos_angle, double sin_angle);
void rotate(double angle, const Point &center); void rotate(double angle, const Point &center);
void reverse(); void reverse() { std::reverse(this->points.begin(), this->points.end()); }
Point first_point() const;
virtual Point last_point() const = 0; const Point& first_point() const { return this->points.front(); }
virtual const Point& last_point() const = 0;
virtual Lines lines() const = 0; virtual Lines lines() const = 0;
size_t size() const { return points.size(); } size_t size() const { return points.size(); }
bool empty() const { return points.empty(); } bool empty() const { return points.empty(); }

View file

@ -13,21 +13,28 @@ public:
{} {}
~MutablePriorityQueue() { clear(); } ~MutablePriorityQueue() { clear(); }
inline void clear() { m_heap.clear(); } void clear();
inline void reserve(size_t cnt) { m_heap.reserve(cnt); } void reserve(size_t cnt) { m_heap.reserve(cnt); }
inline void push(const T &item); void push(const T &item);
inline void push(T &&item); void push(T &&item);
inline void pop(); void pop();
inline T& top() { return m_heap.front(); } T& top() { return m_heap.front(); }
inline void remove(size_t idx); void remove(size_t idx);
inline void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); } void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); }
inline size_t size() const { return m_heap.size(); } size_t size() const { return m_heap.size(); }
inline bool empty() const { return m_heap.empty(); } bool empty() const { return m_heap.empty(); }
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
iterator begin() { return m_heap.begin(); }
iterator end() { return m_heap.end(); }
const_iterator cbegin() const { return m_heap.cbegin(); }
const_iterator cend() const { return m_heap.cend(); }
protected: protected:
inline void update_heap_up(size_t top, size_t bottom); void update_heap_up(size_t top, size_t bottom);
inline void update_heap_down(size_t top, size_t bottom); void update_heap_down(size_t top, size_t bottom);
private: private:
std::vector<T> m_heap; std::vector<T> m_heap;
@ -42,6 +49,17 @@ MutablePriorityQueue<T, IndexSetter, LessPredicate> make_mutable_priority_queue(
std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate)); std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate));
} }
template<class T, class LessPredicate, class IndexSetter>
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::clear()
{
#ifndef NDEBUG
for (size_t idx = 0; idx < m_heap.size(); ++ idx)
// Mark as removed from the queue.
m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
#endif /* NDEBUG */
m_heap.clear();
}
template<class T, class LessPredicate, class IndexSetter> template<class T, class LessPredicate, class IndexSetter>
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item) inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item)
{ {
@ -64,6 +82,10 @@ template<class T, class LessPredicate, class IndexSetter>
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop() inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
{ {
assert(! m_heap.empty()); assert(! m_heap.empty());
#ifndef NDEBUG
// Mark as removed from the queue.
m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
#endif /* NDEBUG */
if (m_heap.size() > 1) { if (m_heap.size() > 1) {
m_heap.front() = m_heap.back(); m_heap.front() = m_heap.back();
m_heap.pop_back(); m_heap.pop_back();
@ -77,6 +99,10 @@ template<class T, class LessPredicate, class IndexSetter>
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t idx) inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t idx)
{ {
assert(idx < m_heap.size()); assert(idx < m_heap.size());
#ifndef NDEBUG
// Mark as removed from the queue.
m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
#endif /* NDEBUG */
if (idx + 1 == m_heap.size()) { if (idx + 1 == m_heap.size()) {
m_heap.pop_back(); m_heap.pop_back();
return; return;

View file

@ -1,6 +1,8 @@
#include "PerimeterGenerator.hpp" #include "PerimeterGenerator.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "ExtrusionEntityCollection.hpp" #include "ExtrusionEntityCollection.hpp"
#include "ShortestPath.hpp"
#include <cmath> #include <cmath>
#include <cassert> #include <cassert>
@ -86,24 +88,24 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
return paths; return paths;
} }
static ExtrusionEntityCollection variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow) static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector<ExtrusionEntity*> &out)
{ {
// This value determines granularity of adaptive width, as G-code does not allow // This value determines granularity of adaptive width, as G-code does not allow
// variable extrusion within a single move; this value shall only affect the amount // variable extrusion within a single move; this value shall only affect the amount
// of segments, and any pruning shall be performed before we apply this tolerance. // of segments, and any pruning shall be performed before we apply this tolerance.
ExtrusionEntityCollection coll;
const float tolerance = float(scale_(0.05)); const float tolerance = float(scale_(0.05));
for (const ThickPolyline &p : polylines) { for (const ThickPolyline &p : polylines) {
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance); ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
// Append paths to collection. // Append paths to collection.
if (! paths.empty()) { if (! paths.empty()) {
if (paths.front().first_point() == paths.back().last_point()) if (paths.front().first_point() == paths.back().last_point())
coll.append(ExtrusionLoop(std::move(paths))); out.emplace_back(new ExtrusionLoop(std::move(paths)));
else else {
coll.append(std::move(paths)); for (ExtrusionPath &path : paths)
out.emplace_back(new ExtrusionPath(std::move(path)));
}
} }
} }
return coll;
} }
// Hierarchy of perimeters. // Hierarchy of perimeters.
@ -173,10 +175,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
perimeter_generator.overhang_flow.width, perimeter_generator.overhang_flow.width,
perimeter_generator.overhang_flow.height); perimeter_generator.overhang_flow.height);
// reapply the nearest point search for starting point // Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
// reversed polylines during clipping. chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path();
} else { } else {
ExtrusionPath path(role); ExtrusionPath path(role);
path.polyline = loop.polygon.split_at_first_point(); path.polyline = loop.polygon.split_at_first_point();
@ -186,43 +187,47 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
paths.push_back(path); paths.push_back(path);
} }
coll.append(ExtrusionLoop(paths, loop_role)); coll.append(ExtrusionLoop(std::move(paths), loop_role));
} }
// Append thin walls to the nearest-neighbor search (only for first iteration) // Append thin walls to the nearest-neighbor search (only for first iteration)
if (! thin_walls.empty()) { if (! thin_walls.empty()) {
ExtrusionEntityCollection tw = variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow); variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities);
coll.append(tw.entities);
thin_walls.clear(); thin_walls.clear();
} }
// Sort entities into a new collection using a nearest-neighbor search, // Traverse children and build the final collection.
// preserving the original indices which are useful for detecting thin walls. Point zero_point(0, 0);
ExtrusionEntityCollection sorted_coll; std::vector<std::pair<size_t, bool>> chain = chain_extrusion_entities(coll.entities, &zero_point);
coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices); ExtrusionEntityCollection out;
for (const std::pair<size_t, bool> &idx : chain) {
// traverse children and build the final collection assert(coll.entities[idx.first] != nullptr);
ExtrusionEntityCollection entities; if (idx.first >= loops.size()) {
for (const size_t &idx : sorted_coll.orig_indices) { // This is a thin wall.
if (idx >= loops.size()) { out.entities.reserve(out.entities.size() + 1);
// This is a thin wall. Let's get it from the sorted collection as it might have been reversed. out.entities.emplace_back(coll.entities[idx.first]);
entities.append(std::move(*sorted_coll.entities[&idx - &sorted_coll.orig_indices.front()])); coll.entities[idx.first] = nullptr;
if (idx.second)
out.entities.back()->reverse();
} else { } else {
const PerimeterGeneratorLoop &loop = loops[idx]; const PerimeterGeneratorLoop &loop = loops[idx.first];
ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[idx]); assert(thin_walls.empty());
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls); ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls);
out.entities.reserve(out.entities.size() + children.entities.size() + 1);
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
coll.entities[idx.first] = nullptr;
if (loop.is_contour) { if (loop.is_contour) {
eloop.make_counter_clockwise(); eloop->make_counter_clockwise();
entities.append(std::move(children.entities)); out.append(std::move(children.entities));
entities.append(std::move(eloop)); out.entities.emplace_back(eloop);
} else { } else {
eloop.make_clockwise(); eloop->make_clockwise();
entities.append(std::move(eloop)); out.entities.emplace_back(eloop);
entities.append(std::move(children.entities)); out.append(std::move(children.entities));
} }
} }
} }
return entities; return out;
} }
void PerimeterGenerator::process() void PerimeterGenerator::process()
@ -445,8 +450,8 @@ void PerimeterGenerator::process()
for (const ExPolygon &ex : gaps_ex) for (const ExPolygon &ex : gaps_ex)
ex.medial_axis(max, min, &polylines); ex.medial_axis(max, min, &polylines);
if (! polylines.empty()) { if (! polylines.empty()) {
ExtrusionEntityCollection gap_fill = variable_width(polylines, erGapFill, this->solid_infill_flow); ExtrusionEntityCollection gap_fill;
this->gap_fill->append(gap_fill.entities); variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities);
/* Make sure we don't infill narrow parts that are already gap-filled /* Make sure we don't infill narrow parts that are already gap-filled
(we only consider this surface's gaps to reduce the diff() complexity). (we only consider this surface's gaps to reduce the diff() complexity).
Growing actual extrusions ensures that gaps not filled by medial axis Growing actual extrusions ensures that gaps not filled by medial axis
@ -456,6 +461,7 @@ void PerimeterGenerator::process()
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing, //FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
// therefore it may cover the area, but no the volume. // therefore it may cover the area, but no the volume.
last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f)); last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f));
this->gap_fill->append(std::move(gap_fill.entities));
} }
} }

View file

@ -5,43 +5,12 @@
namespace Slic3r { namespace Slic3r {
Polygon::operator Polygons() const
{
Polygons pp;
pp.push_back(*this);
return pp;
}
Polygon::operator Polyline() const
{
return this->split_at_first_point();
}
Point&
Polygon::operator[](Points::size_type idx)
{
return this->points[idx];
}
const Point&
Polygon::operator[](Points::size_type idx) const
{
return this->points[idx];
}
Point
Polygon::last_point() const
{
return this->points.front(); // last point == first point for polygons
}
Lines Polygon::lines() const Lines Polygon::lines() const
{ {
return to_lines(*this); return to_lines(*this);
} }
Polyline Polyline Polygon::split_at_vertex(const Point &point) const
Polygon::split_at_vertex(const Point &point) const
{ {
// find index of point // find index of point
for (const Point &pt : this->points) for (const Point &pt : this->points)
@ -52,8 +21,7 @@ Polygon::split_at_vertex(const Point &point) const
} }
// Split a closed polygon into an open polyline, with the split point duplicated at both ends. // Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline Polyline Polygon::split_at_index(int index) const
Polygon::split_at_index(int index) const
{ {
Polyline polyline; Polyline polyline;
polyline.points.reserve(this->points.size() + 1); polyline.points.reserve(this->points.size() + 1);
@ -64,19 +32,6 @@ Polygon::split_at_index(int index) const
return polyline; return polyline;
} }
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline
Polygon::split_at_first_point() const
{
return this->split_at_index(0);
}
Points
Polygon::equally_spaced_points(double distance) const
{
return this->split_at_first_point().equally_spaced_points(distance);
}
/* /*
int64_t Polygon::area2x() const int64_t Polygon::area2x() const
{ {
@ -107,20 +62,17 @@ double Polygon::area() const
return 0.5 * a; return 0.5 * a;
} }
bool bool Polygon::is_counter_clockwise() const
Polygon::is_counter_clockwise() const
{ {
return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this));
} }
bool bool Polygon::is_clockwise() const
Polygon::is_clockwise() const
{ {
return !this->is_counter_clockwise(); return !this->is_counter_clockwise();
} }
bool bool Polygon::make_counter_clockwise()
Polygon::make_counter_clockwise()
{ {
if (!this->is_counter_clockwise()) { if (!this->is_counter_clockwise()) {
this->reverse(); this->reverse();
@ -129,8 +81,7 @@ Polygon::make_counter_clockwise()
return false; return false;
} }
bool bool Polygon::make_clockwise()
Polygon::make_clockwise()
{ {
if (this->is_counter_clockwise()) { if (this->is_counter_clockwise()) {
this->reverse(); this->reverse();
@ -139,16 +90,9 @@ Polygon::make_clockwise()
return false; return false;
} }
bool
Polygon::is_valid() const
{
return this->points.size() >= 3;
}
// Does an unoriented polygon contain a point? // Does an unoriented polygon contain a point?
// Tested by counting intersections along a horizontal line. // Tested by counting intersections along a horizontal line.
bool bool Polygon::contains(const Point &point) const
Polygon::contains(const Point &point) const
{ {
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
bool result = false; bool result = false;
@ -174,8 +118,7 @@ Polygon::contains(const Point &point) const
} }
// this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons() // this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons()
Polygons Polygons Polygon::simplify(double tolerance) const
Polygon::simplify(double tolerance) const
{ {
// repeat first point at the end in order to apply Douglas-Peucker // repeat first point at the end in order to apply Douglas-Peucker
// on the whole polygon // on the whole polygon
@ -189,8 +132,7 @@ Polygon::simplify(double tolerance) const
return simplify_polygons(pp); return simplify_polygons(pp);
} }
void void Polygon::simplify(double tolerance, Polygons &polygons) const
Polygon::simplify(double tolerance, Polygons &polygons) const
{ {
Polygons pp = this->simplify(tolerance); Polygons pp = this->simplify(tolerance);
polygons.reserve(polygons.size() + pp.size()); polygons.reserve(polygons.size() + pp.size());
@ -198,8 +140,7 @@ Polygon::simplify(double tolerance, Polygons &polygons) const
} }
// Only call this on convex polygons or it will return invalid results // Only call this on convex polygons or it will return invalid results
void void Polygon::triangulate_convex(Polygons* polygons) const
Polygon::triangulate_convex(Polygons* polygons) const
{ {
for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) { for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) {
Polygon p; Polygon p;
@ -214,8 +155,7 @@ Polygon::triangulate_convex(Polygons* polygons) const
} }
// center of mass // center of mass
Point Point Polygon::centroid() const
Polygon::centroid() const
{ {
double area_temp = this->area(); double area_temp = this->area();
double x_temp = 0; double x_temp = 0;
@ -232,8 +172,7 @@ Polygon::centroid() const
// find all concave vertices (i.e. having an internal angle greater than the supplied angle) // find all concave vertices (i.e. having an internal angle greater than the supplied angle)
// (external = right side, thus we consider ccw orientation) // (external = right side, thus we consider ccw orientation)
Points Points Polygon::concave_points(double angle) const
Polygon::concave_points(double angle) const
{ {
Points points; Points points;
angle = 2*PI - angle; angle = 2*PI - angle;
@ -256,8 +195,7 @@ Polygon::concave_points(double angle) const
// find all convex vertices (i.e. having an internal angle smaller than the supplied angle) // find all convex vertices (i.e. having an internal angle smaller than the supplied angle)
// (external = right side, thus we consider ccw orientation) // (external = right side, thus we consider ccw orientation)
Points Points Polygon::convex_points(double angle) const
Polygon::convex_points(double angle) const
{ {
Points points; Points points;
angle = 2*PI - angle; angle = 2*PI - angle;

View file

@ -13,12 +13,13 @@ namespace Slic3r {
class Polygon; class Polygon;
typedef std::vector<Polygon> Polygons; typedef std::vector<Polygon> Polygons;
class Polygon : public MultiPoint { class Polygon : public MultiPoint
{
public: public:
operator Polygons() const; operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; }
operator Polyline() const; operator Polyline() const { return this->split_at_first_point(); }
Point& operator[](Points::size_type idx); Point& operator[](Points::size_type idx) { return this->points[idx]; }
const Point& operator[](Points::size_type idx) const; const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
Polygon() {} Polygon() {}
explicit Polygon(const Points &points): MultiPoint(points) {} explicit Polygon(const Points &points): MultiPoint(points) {}
@ -34,20 +35,24 @@ public:
Polygon& operator=(const Polygon &other) { points = other.points; return *this; } Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; } Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
Point last_point() const; // last point == first point for polygons
const Point& last_point() const override { return this->points.front(); }
virtual Lines lines() const; virtual Lines lines() const;
Polyline split_at_vertex(const Point &point) const; Polyline split_at_vertex(const Point &point) const;
// Split a closed polygon into an open polyline, with the split point duplicated at both ends. // Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_index(int index) const; Polyline split_at_index(int index) const;
// Split a closed polygon into an open polyline, with the split point duplicated at both ends. // Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_first_point() const; Polyline split_at_first_point() const { return this->split_at_index(0); }
Points equally_spaced_points(double distance) const; Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); }
double area() const; double area() const;
bool is_counter_clockwise() const; bool is_counter_clockwise() const;
bool is_clockwise() const; bool is_clockwise() const;
bool make_counter_clockwise(); bool make_counter_clockwise();
bool make_clockwise(); bool make_clockwise();
bool is_valid() const; bool is_valid() const { return this->points.size() >= 3; }
// Does an unoriented polygon contain a point? // Does an unoriented polygon contain a point?
// Tested by counting intersections along a horizontal line. // Tested by counting intersections along a horizontal line.
bool contains(const Point &point) const; bool contains(const Point &point) const;

View file

@ -23,24 +23,17 @@ Polyline::operator Line() const
return Line(this->points.front(), this->points.back()); return Line(this->points.front(), this->points.back());
} }
Point const Point& Polyline::leftmost_point() const
Polyline::last_point() const
{ {
return this->points.back(); const Point *p = &this->points.front();
} for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++ it) {
if (it->x() < p->x())
Point p = &(*it);
Polyline::leftmost_point() const
{
Point p = this->points.front();
for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) {
if ((*it)(0) < p(0)) p = *it;
} }
return p; return *p;
} }
Lines Lines Polyline::lines() const
Polyline::lines() const
{ {
Lines lines; Lines lines;
if (this->points.size() >= 2) { if (this->points.size() >= 2) {
@ -211,6 +204,20 @@ BoundingBox get_extents(const Polylines &polylines)
return bb; return bb;
} }
const Point& leftmost_point(const Polylines &polylines)
{
if (polylines.empty())
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
Polylines::const_iterator it = polylines.begin();
const Point *p = &it->leftmost_point();
for (++ it; it != polylines.end(); ++it) {
const Point *p2 = &it->leftmost_point();
if (p2->x() < p->x())
p = p2;
}
return *p;
}
bool remove_degenerate(Polylines &polylines) bool remove_degenerate(Polylines &polylines)
{ {
bool modified = false; bool modified = false;

View file

@ -62,8 +62,9 @@ public:
operator Polylines() const; operator Polylines() const;
operator Line() const; operator Line() const;
Point last_point() const; const Point& last_point() const override { return this->points.back(); }
Point leftmost_point() const;
const Point& leftmost_point() const;
virtual Lines lines() const; virtual Lines lines() const;
void clip_end(double distance); void clip_end(double distance);
void clip_start(double distance); void clip_start(double distance);
@ -76,6 +77,15 @@ public:
bool is_straight() const; bool is_straight() const;
}; };
// Don't use this class in production code, it is used exclusively by the Perl binding for unit tests!
#ifdef PERL_UCHAR_MIN
class PolylineCollection
{
public:
Polylines polylines;
};
#endif /* PERL_UCHAR_MIN */
extern BoundingBox get_extents(const Polyline &polyline); extern BoundingBox get_extents(const Polyline &polyline);
extern BoundingBox get_extents(const Polylines &polylines); extern BoundingBox get_extents(const Polylines &polylines);
@ -128,6 +138,8 @@ inline void polylines_append(Polylines &dst, Polylines &&src)
} }
} }
const Point& leftmost_point(const Polylines &polylines);
bool remove_degenerate(Polylines &polylines); bool remove_degenerate(Polylines &polylines);
class ThickPolyline : public Polyline { class ThickPolyline : public Polyline {

View file

@ -1,92 +0,0 @@
#include "PolylineCollection.hpp"
namespace Slic3r {
struct Chaining
{
Point first;
Point last;
size_t idx;
};
template<typename T>
inline int nearest_point_index(const std::vector<Chaining> &pairs, const Point &start_near, bool no_reverse)
{
T dmin = std::numeric_limits<T>::max();
int idx = 0;
for (std::vector<Chaining>::const_iterator it = pairs.begin(); it != pairs.end(); ++it) {
T d = sqr(T(start_near(0) - it->first(0)));
if (d <= dmin) {
d += sqr(T(start_near(1) - it->first(1)));
if (d < dmin) {
idx = (it - pairs.begin()) * 2;
dmin = d;
if (dmin < EPSILON)
break;
}
}
if (! no_reverse) {
d = sqr(T(start_near(0) - it->last(0)));
if (d <= dmin) {
d += sqr(T(start_near(1) - it->last(1)));
if (d < dmin) {
idx = (it - pairs.begin()) * 2 + 1;
dmin = d;
if (dmin < EPSILON)
break;
}
}
}
}
return idx;
}
Polylines PolylineCollection::_chained_path_from(
const Polylines &src,
Point start_near,
bool no_reverse,
bool move_from_src)
{
std::vector<Chaining> endpoints;
endpoints.reserve(src.size());
for (size_t i = 0; i < src.size(); ++ i) {
Chaining c;
c.first = src[i].first_point();
if (! no_reverse)
c.last = src[i].last_point();
c.idx = i;
endpoints.push_back(c);
}
Polylines retval;
while (! endpoints.empty()) {
// find nearest point
int endpoint_index = nearest_point_index<double>(endpoints, start_near, no_reverse);
assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2);
if (move_from_src) {
retval.push_back(std::move(src[endpoints[endpoint_index/2].idx]));
} else {
retval.push_back(src[endpoints[endpoint_index/2].idx]);
}
if (endpoint_index & 1)
retval.back().reverse();
endpoints.erase(endpoints.begin() + endpoint_index/2);
start_near = retval.back().last_point();
}
return retval;
}
Point PolylineCollection::leftmost_point(const Polylines &polylines)
{
if (polylines.empty())
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
Polylines::const_iterator it = polylines.begin();
Point p = it->leftmost_point();
for (++ it; it != polylines.end(); ++it) {
Point p2 = it->leftmost_point();
if (p2(0) < p(0))
p = p2;
}
return p;
}
} // namespace Slic3r

View file

@ -1,47 +0,0 @@
#ifndef slic3r_PolylineCollection_hpp_
#define slic3r_PolylineCollection_hpp_
#include "libslic3r.h"
#include "Polyline.hpp"
namespace Slic3r {
class PolylineCollection
{
static Polylines _chained_path_from(
const Polylines &src,
Point start_near,
bool no_reverse,
bool move_from_src);
public:
Polylines polylines;
void chained_path(PolylineCollection* retval, bool no_reverse = false) const
{ retval->polylines = chained_path(this->polylines, no_reverse); }
void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const
{ retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); }
Point leftmost_point() const
{ return leftmost_point(polylines); }
void append(const Polylines &polylines)
{ this->polylines.insert(this->polylines.end(), polylines.begin(), polylines.end()); }
static Point leftmost_point(const Polylines &polylines);
static Polylines chained_path(Polylines &&src, bool no_reverse = false) {
return (src.empty() || src.front().points.empty()) ?
Polylines() :
_chained_path_from(src, src.front().first_point(), no_reverse, true);
}
static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false)
{ return _chained_path_from(src, start_near, no_reverse, true); }
static Polylines chained_path(const Polylines &src, bool no_reverse = false) {
return (src.empty() || src.front().points.empty()) ?
Polylines() :
_chained_path_from(src, src.front().first_point(), no_reverse, false);
}
static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false)
{ return _chained_path_from(src, start_near, no_reverse, false); }
};
}
#endif

View file

@ -7,6 +7,7 @@
#include "Flow.hpp" #include "Flow.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "ShortestPath.hpp"
#include "SupportMaterial.hpp" #include "SupportMaterial.hpp"
#include "GCode.hpp" #include "GCode.hpp"
#include "GCode/WipeTower.hpp" #include "GCode/WipeTower.hpp"
@ -1236,7 +1237,8 @@ std::string Print::validate() const
// The comparison of the profiles is not just about element-wise equality, some layers may not be // The comparison of the profiles is not just about element-wise equality, some layers may not be
// explicitely included. Always remember z and height of last reference layer that in the vector // explicitely included. Always remember z and height of last reference layer that in the vector
// and compare to that. // and compare to that. In case some layers are in the vectors multiple times, only the last entry is
// taken into account and compared.
size_t i = 0; // index into tested profile size_t i = 0; // index into tested profile
size_t j = 0; // index into reference profile size_t j = 0; // index into reference profile
coordf_t ref_z = -1.; coordf_t ref_z = -1.;
@ -1244,8 +1246,12 @@ std::string Print::validate() const
coordf_t ref_height = -1.; coordf_t ref_height = -1.;
while (i < layer_height_profile.size()) { while (i < layer_height_profile.size()) {
coordf_t this_z = layer_height_profile[i]; coordf_t this_z = layer_height_profile[i];
// find the last entry with this z
while (i+2 < layer_height_profile.size() && layer_height_profile[i+2] == this_z)
i += 2;
coordf_t this_height = layer_height_profile[i+1]; coordf_t this_height = layer_height_profile[i+1];
if (next_ref_z < this_z + EPSILON) { if (ref_height < -1. || next_ref_z < this_z + EPSILON) {
ref_z = next_ref_z; ref_z = next_ref_z;
do { // one layer can be in the vector several times do { // one layer can be in the vector several times
ref_height = layer_height_profile_tallest[j+1]; ref_height = layer_height_profile_tallest[j+1];
@ -1819,8 +1825,8 @@ void Print::_make_brim()
[](const std::pair<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &r) { [](const std::pair<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &r) {
return l.second < r.second; return l.second < r.second;
}); });
Vec3f last_pt(0.f, 0.f, 0.f);
Point last_pt(0, 0);
for (size_t i = 0; i < loops_trimmed_order.size();) { for (size_t i = 0; i < loops_trimmed_order.size();) {
// Find all pieces that the initial loop was split into. // Find all pieces that the initial loop was split into.
size_t j = i + 1; size_t j = i + 1;
@ -1836,16 +1842,23 @@ void Print::_make_brim()
points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
i = j; i = j;
} else { } else {
//FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance. //FIXME The path chaining here may not be optimal.
ExtrusionEntityCollection this_loop_trimmed;
this_loop_trimmed.entities.reserve(j - i);
for (; i < j; ++ i) { for (; i < j; ++ i) {
m_brim.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()))); this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first; const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = static_cast<ExtrusionPath*>(m_brim.entities.back())->polyline.points; Points &points = static_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
points.reserve(path.size()); points.reserve(path.size());
for (const ClipperLib_Z::IntPoint &pt : path) for (const ClipperLib_Z::IntPoint &pt : path)
points.emplace_back(coord_t(pt.X), coord_t(pt.Y)); points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
} }
chain_and_reorder_extrusion_entities(this_loop_trimmed.entities, &last_pt);
m_brim.entities.reserve(m_brim.entities.size() + this_loop_trimmed.entities.size());
append(m_brim.entities, std::move(this_loop_trimmed.entities));
this_loop_trimmed.entities.clear();
} }
last_pt = m_brim.last_point();
} }
} }
} else { } else {

View file

@ -96,6 +96,7 @@ public:
const SupportLayerPtrs& support_layers() const { return m_support_layers; } const SupportLayerPtrs& support_layers() const { return m_support_layers; }
const Transform3d& trafo() const { return m_trafo; } const Transform3d& trafo() const { return m_trafo; }
const Points& copies() const { return m_copies; } const Points& copies() const { return m_copies; }
const Point copy_center(size_t idx) const { return m_copies[idx] + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); }
// since the object is aligned to origin, bounding box coincides with size // since the object is aligned to origin, bounding box coincides with size
BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); } BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }

View file

@ -268,8 +268,7 @@ public:
std::string text; std::string text;
// Bitmap of flags. // Bitmap of flags.
enum FlagBits { enum FlagBits {
DEFAULT, DEFAULT = 0,
NO_RELOAD_SCENE = 0,
RELOAD_SCENE = 1 << 1, RELOAD_SCENE = 1 << 1,
RELOAD_SLA_SUPPORT_POINTS = 1 << 2, RELOAD_SLA_SUPPORT_POINTS = 1 << 2,
RELOAD_SLA_PREVIEW = 1 << 3, RELOAD_SLA_PREVIEW = 1 << 3,

View file

@ -12,7 +12,6 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <float.h> #include <float.h>
#include <tbb/task_scheduler_init.h>
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
#include <tbb/atomic.h> #include <tbb/atomic.h>
@ -75,13 +74,9 @@ PrintBase::ApplyStatus PrintObject::set_copies(const Points &points)
{ {
// Order copies with a nearest-neighbor search. // Order copies with a nearest-neighbor search.
std::vector<Point> copies; std::vector<Point> copies;
{ copies.reserve(points.size());
std::vector<Points::size_type> ordered_copies; for (const Point &pt : points)
Slic3r::Geometry::chained_path(points, ordered_copies); copies.emplace_back(pt + m_copies_shift);
copies.reserve(ordered_copies.size());
for (size_t point_idx : ordered_copies)
copies.emplace_back(points[point_idx] + m_copies_shift);
}
// Invalidate and set copies. // Invalidate and set copies.
PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED; PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED;
if (copies != m_copies) { if (copies != m_copies) {

View file

@ -0,0 +1,514 @@
#if 0
#pragma optimize("", off)
#undef NDEBUG
#undef assert
#endif
#include "clipper.hpp"
#include "ShortestPath.hpp"
#include "KDTreeIndirect.hpp"
#include "MutablePriorityQueue.hpp"
#include "Print.hpp"
#include <cmath>
#include <cassert>
namespace Slic3r {
// Naive implementation of the Traveling Salesman Problem, it works by always taking the next closest neighbor.
// This implementation will always produce valid result even if some segments cannot reverse.
template<typename EndPointType, typename KDTreeType, typename CouldReverseFunc>
std::vector<std::pair<size_t, bool>> chain_segments_closest_point(std::vector<EndPointType> &end_points, KDTreeType &kdtree, CouldReverseFunc &could_reverse_func, EndPointType &first_point)
{
assert((end_points.size() & 1) == 0);
size_t num_segments = end_points.size() / 2;
assert(num_segments >= 2);
for (EndPointType &ep : end_points)
ep.chain_id = 0;
std::vector<std::pair<size_t, bool>> out;
out.reserve(num_segments);
size_t first_point_idx = &first_point - end_points.data();
out.emplace_back(first_point_idx / 2, (first_point_idx & 1) != 0);
first_point.chain_id = 1;
size_t this_idx = first_point_idx ^ 1;
for (int iter = (int)num_segments - 2; iter >= 0; -- iter) {
EndPointType &this_point = end_points[this_idx];
this_point.chain_id = 1;
// Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda).
// Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it.
size_t next_idx = find_closest_point(kdtree, this_point.pos,
[this_idx, &end_points, &could_reverse_func](size_t idx) {
return (idx ^ this_idx) > 1 && end_points[idx].chain_id == 0 && ((idx ^ 1) == 0 || could_reverse_func(idx >> 1));
});
assert(next_idx < end_points.size());
EndPointType &end_point = end_points[next_idx];
end_point.chain_id = 1;
this_idx = next_idx ^ 1;
}
#ifndef NDEBUG
assert(end_points[this_idx].chain_id == 0);
for (EndPointType &ep : end_points)
assert(&ep == &end_points[this_idx] || ep.chain_id == 1);
#endif /* NDEBUG */
return out;
}
// Chain perimeters (always closed) and thin fills (closed or open) using a greedy algorithm.
// Solving a Traveling Salesman Problem (TSP) with the modification, that the sites are not always points, but points and segments.
// Solving using a greedy algorithm, where a shortest edge is added to the solution if it does not produce a bifurcation or a cycle.
// Return index and "reversed" flag.
// https://en.wikipedia.org/wiki/Multi-fragment_algorithm
// The algorithm builds a tour for the traveling salesman one edge at a time and thus maintains multiple tour fragments, each of which
// is a simple path in the complete graph of cities. At each stage, the algorithm selects the edge of minimal cost that either creates
// a new fragment, extends one of the existing paths or creates a cycle of length equal to the number of cities.
template<typename PointType, typename SegmentEndPointFunc, bool REVERSE_COULD_FAIL, typename CouldReverseFunc>
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals_(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
{
std::vector<std::pair<size_t, bool>> out;
if (num_segments == 0) {
// Nothing to do.
}
else if (num_segments == 1)
{
// Just sort the end points so that the first point visited is closest to start_near.
out.emplace_back(0, start_near != nullptr &&
(end_point_func(0, true) - *start_near).template cast<double>().squaredNorm() < (end_point_func(0, false) - *start_near).template cast<double>().squaredNorm());
}
else
{
// End points of segments for the KD tree closest point search.
// A single end point is inserted into the search structure for loops, two end points are entered for open paths.
struct EndPoint {
EndPoint(const Vec2d &pos) : pos(pos) {}
Vec2d pos;
// Identifier of the chain, to which this end point belongs. Zero means unassigned.
size_t chain_id = 0;
// Link to the closest currently valid end point.
EndPoint *edge_out = nullptr;
// Distance to the next end point following the link.
// Zero value -> start of the final path.
double distance_out = std::numeric_limits<double>::max();
size_t heap_idx = std::numeric_limits<size_t>::max();
};
std::vector<EndPoint> end_points;
end_points.reserve(num_segments * 2);
for (size_t i = 0; i < num_segments; ++ i) {
end_points.emplace_back(end_point_func(i, true ).template cast<double>());
end_points.emplace_back(end_point_func(i, false).template cast<double>());
}
// Construct the closest point KD tree over end points of segments.
auto coordinate_fn = [&end_points](size_t idx, size_t dimension) -> double { return end_points[idx].pos[dimension]; };
KDTreeIndirect<2, double, decltype(coordinate_fn)> kdtree(coordinate_fn, end_points.size());
// Helper to detect loops in already connected paths.
// Unique chain IDs are assigned to paths. If paths are connected, end points will not have their chain IDs updated, but the chain IDs
// will remember an "equivalent" chain ID, which is the lowest ID of all the IDs in the path, and the lowest ID is equivalent to itself.
class EquivalentChains {
public:
// Zero'th chain ID is invalid.
EquivalentChains(size_t reserve) { m_equivalent_with.reserve(reserve); m_equivalent_with.emplace_back(0); }
// Generate next equivalence class.
size_t next() {
m_equivalent_with.emplace_back(++ m_last_chain_id);
return m_last_chain_id;
}
// Get equivalence class for chain ID.
size_t operator()(size_t chain_id) {
if (chain_id != 0) {
for (size_t last = chain_id;;) {
size_t lower = m_equivalent_with[last];
if (lower == last) {
m_equivalent_with[chain_id] = lower;
chain_id = lower;
break;
}
last = lower;
}
}
return chain_id;
}
size_t merge(size_t chain_id1, size_t chain_id2) {
size_t chain_id = std::min((*this)(chain_id1), (*this)(chain_id2));
m_equivalent_with[chain_id1] = chain_id;
m_equivalent_with[chain_id2] = chain_id;
return chain_id;
}
#ifndef NDEBUG
bool validate()
{
assert(m_last_chain_id >= 0);
assert(m_last_chain_id + 1 == m_equivalent_with.size());
for (size_t i = 0; i < m_equivalent_with.size(); ++ i) {
for (size_t last = i;;) {
size_t lower = m_equivalent_with[last];
assert(lower <= last);
if (lower == last)
break;
last = lower;
}
}
return true;
}
#endif /* NDEBUG */
private:
// Unique chain ID assigned to chains of end points of segments.
size_t m_last_chain_id = 0;
std::vector<size_t> m_equivalent_with;
} equivalent_chain(num_segments);
// Find the first end point closest to start_near.
EndPoint *first_point = nullptr;
size_t first_point_idx = std::numeric_limits<size_t>::max();
if (start_near != nullptr) {
size_t idx = find_closest_point(kdtree, start_near->template cast<double>());
assert(idx < end_points.size());
first_point = &end_points[idx];
first_point->distance_out = 0.;
first_point->chain_id = equivalent_chain.next();
first_point_idx = idx;
}
EndPoint *initial_point = first_point;
EndPoint *last_point = nullptr;
// Assign the closest point and distance to the end points.
for (EndPoint &end_point : end_points) {
assert(end_point.edge_out == nullptr);
if (&end_point != first_point) {
size_t this_idx = &end_point - &end_points.front();
// Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda).
// Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it.
size_t next_idx = find_closest_point(kdtree, end_point.pos,
[this_idx, first_point_idx](size_t idx){ return idx != first_point_idx && (idx ^ this_idx) > 1; });
assert(next_idx < end_points.size());
EndPoint &end_point2 = end_points[next_idx];
end_point.edge_out = &end_point2;
end_point.distance_out = (end_point2.pos - end_point.pos).squaredNorm();
}
}
// Initialize a heap of end points sorted by the lowest distance to the next valid point of a path.
auto queue = make_mutable_priority_queue<EndPoint*>(
[](EndPoint *ep, size_t idx){ ep->heap_idx = idx; },
[](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; });
queue.reserve(end_points.size() * 2 - 1);
for (EndPoint &ep : end_points)
if (first_point != &ep)
queue.push(&ep);
#ifndef NDEBUG
auto validate_graph_and_queue = [&equivalent_chain, &end_points, &queue, first_point]() -> bool {
assert(equivalent_chain.validate());
for (EndPoint &ep : end_points) {
if (ep.heap_idx < queue.size()) {
// End point is on the heap.
assert(*(queue.cbegin() + ep.heap_idx) == &ep);
assert(ep.chain_id == 0);
} else {
// End point is NOT on the heap, therefore it is part of the output path.
assert(ep.heap_idx == std::numeric_limits<size_t>::max());
assert(ep.chain_id != 0);
if (&ep == first_point) {
assert(ep.edge_out == nullptr);
} else {
assert(ep.edge_out != nullptr);
// Detect loops.
for (EndPoint *pt = &ep; pt != nullptr;) {
// Out of queue. It is a final point.
assert(pt->heap_idx == std::numeric_limits<size_t>::max());
EndPoint *pt_other = &end_points[(pt - &end_points.front()) ^ 1];
if (pt_other->heap_idx < queue.size())
// The other side of this segment is undecided yet.
break;
pt = pt_other->edge_out;
}
}
}
}
for (EndPoint *ep : queue)
// Points in the queue are not connected yet.
assert(ep->chain_id == 0);
return true;
};
#endif /* NDEBUG */
// Chain the end points: find (num_segments - 1) shortest links not forming bifurcations or loops.
assert(num_segments >= 2);
for (int iter = int(num_segments) - 2;; -- iter) {
assert(validate_graph_and_queue());
// Take the first end point, for which the link points to the currently closest valid neighbor.
EndPoint &end_point1 = *queue.top();
assert(end_point1.edge_out != nullptr);
// No point on the queue may be connected yet.
assert(end_point1.chain_id == 0);
// Take the closest end point to the first end point,
EndPoint &end_point2 = *end_point1.edge_out;
bool valid = true;
size_t end_point1_other_chain_id = 0;
size_t end_point2_other_chain_id = 0;
if (end_point2.chain_id > 0) {
// The other side is part of the output path. Don't connect to end_point2, update end_point1 and try another one.
valid = false;
} else {
// End points of the opposite ends of the segments.
end_point1_other_chain_id = equivalent_chain(end_points[(&end_point1 - &end_points.front()) ^ 1].chain_id);
end_point2_other_chain_id = equivalent_chain(end_points[(&end_point2 - &end_points.front()) ^ 1].chain_id);
if (end_point1_other_chain_id == end_point2_other_chain_id && end_point1_other_chain_id != 0)
// This edge forms a loop. Update end_point1 and try another one.
valid = false;
}
if (valid) {
// Remove the first and second point from the queue.
queue.pop();
queue.remove(end_point2.heap_idx);
assert(end_point1.edge_out = &end_point2);
end_point2.edge_out = &end_point1;
end_point2.distance_out = end_point1.distance_out;
// Assign chain IDs to the newly connected end points, set equivalent_chain if two chains were merged.
size_t chain_id =
(end_point1_other_chain_id == 0) ?
((end_point2_other_chain_id == 0) ? equivalent_chain.next() : end_point2_other_chain_id) :
((end_point2_other_chain_id == 0) ? end_point1_other_chain_id :
(end_point1_other_chain_id == end_point2_other_chain_id) ?
end_point1_other_chain_id :
equivalent_chain.merge(end_point1_other_chain_id, end_point2_other_chain_id));
end_point1.chain_id = chain_id;
end_point2.chain_id = chain_id;
assert(validate_graph_and_queue());
if (iter == 0) {
// Last iteration. There shall be exactly one or two end points waiting to be connected.
assert(queue.size() == ((first_point == nullptr) ? 2 : 1));
if (first_point == nullptr) {
first_point = queue.top();
queue.pop();
first_point->edge_out = nullptr;
}
last_point = queue.top();
last_point->edge_out = nullptr;
queue.pop();
assert(queue.empty());
break;
}
} else {
// This edge forms a loop. Update end_point1 and try another one.
++ iter;
end_point1.edge_out = nullptr;
// Update edge_out and distance.
size_t this_idx = &end_point1 - &end_points.front();
// Find the closest point to this end_point, which lies on a different extrusion path (filtered by the filter lambda).
size_t next_idx = find_closest_point(kdtree, end_point1.pos, [&end_points, &equivalent_chain, this_idx](size_t idx) {
assert(end_points[this_idx].edge_out == nullptr);
assert(end_points[this_idx].chain_id == 0);
if ((idx ^ this_idx) <= 1 || end_points[idx].chain_id != 0)
// Points of the same segment shall not be connected,
// cannot connect to an already connected point (ideally those would be removed from the KD tree, but the update is difficult).
return false;
size_t chain1 = equivalent_chain(end_points[this_idx ^ 1].chain_id);
size_t chain2 = equivalent_chain(end_points[idx ^ 1].chain_id);
return chain1 != chain2 || chain1 == 0;
});
assert(next_idx < end_points.size());
end_point1.edge_out = &end_points[next_idx];
end_point1.distance_out = (end_points[next_idx].pos - end_point1.pos).squaredNorm();
// Update position of this end point in the queue based on the distance calculated at the line above.
queue.update(end_point1.heap_idx);
//FIXME Remove the other end point from the KD tree.
// As the KD tree update is expensive, do it only after some larger number of points is removed from the queue.
assert(validate_graph_and_queue());
}
}
assert(queue.empty());
// Now interconnect pairs of segments into a chain.
assert(first_point != nullptr);
out.reserve(num_segments);
bool failed = false;
do {
assert(out.size() < num_segments);
size_t first_point_id = first_point - &end_points.front();
size_t segment_id = first_point_id >> 1;
bool reverse = (first_point_id & 1) != 0;
EndPoint *second_point = &end_points[first_point_id ^ 1];
if (REVERSE_COULD_FAIL) {
if (reverse && ! could_reverse_func(segment_id)) {
failed = true;
break;
}
} else {
assert(! reverse || could_reverse_func(segment_id));
}
out.emplace_back(segment_id, reverse);
first_point = second_point->edge_out;
} while (first_point != nullptr);
if (REVERSE_COULD_FAIL) {
if (failed) {
if (start_near == nullptr) {
// We may try the reverse order.
out.clear();
first_point = last_point;
failed = false;
do {
assert(out.size() < num_segments);
size_t first_point_id = first_point - &end_points.front();
size_t segment_id = first_point_id >> 1;
bool reverse = (first_point_id & 1) != 0;
EndPoint *second_point = &end_points[first_point_id ^ 1];
if (reverse && ! could_reverse_func(segment_id)) {
failed = true;
break;
}
out.emplace_back(segment_id, reverse);
first_point = second_point->edge_out;
} while (first_point != nullptr);
}
}
if (failed)
// As a last resort, try a dumb algorithm, which is not sensitive to edge reversal constraints.
out = chain_segments_closest_point<EndPoint, decltype(kdtree), CouldReverseFunc>(end_points, kdtree, could_reverse_func, (initial_point != nullptr) ? *initial_point : end_points.front());
} else {
assert(! failed);
}
}
assert(out.size() == num_segments);
return out;
}
template<typename PointType, typename SegmentEndPointFunc, typename CouldReverseFunc>
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
{
return chain_segments_greedy_constrained_reversals_<PointType, SegmentEndPointFunc, true, CouldReverseFunc>(end_point_func, could_reverse_func, num_segments, start_near);
}
template<typename PointType, typename SegmentEndPointFunc>
std::vector<std::pair<size_t, bool>> chain_segments_greedy(SegmentEndPointFunc end_point_func, size_t num_segments, const PointType *start_near)
{
auto could_reverse_func = [](size_t /* idx */) -> bool { return true; };
return chain_segments_greedy_constrained_reversals_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
}
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
{
auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(segment_end_point, could_reverse, entities.size(), start_near);
for (size_t i = 0; i < entities.size(); ++ i) {
ExtrusionEntity *ee = entities[i];
if (ee->is_loop())
// Ignore reversals for loops, as the start point equals the end point.
out[i].second = false;
// Is can_reverse() respected by the reversals?
assert(entities[i]->can_reverse() || ! out[i].second);
}
return out;
}
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain)
{
assert(entities.size() == chain.size());
std::vector<ExtrusionEntity*> out;
out.reserve(entities.size());
for (const std::pair<size_t, bool> &idx : chain) {
assert(entities[idx.first] != nullptr);
out.emplace_back(entities[idx.first]);
if (idx.second)
out.back()->reverse();
}
entities.swap(out);
}
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
{
reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near));
}
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near)
{
auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); };
return chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, extrusion_paths.size(), start_near);
}
void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const std::vector<std::pair<size_t, bool>> &chain)
{
assert(extrusion_paths.size() == chain.size());
std::vector<ExtrusionPath> out;
out.reserve(extrusion_paths.size());
for (const std::pair<size_t, bool> &idx : chain) {
out.emplace_back(std::move(extrusion_paths[idx.first]));
if (idx.second)
out.back().reverse();
}
extrusion_paths.swap(out);
}
void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near)
{
reorder_extrusion_paths(extrusion_paths, chain_extrusion_paths(extrusion_paths, start_near));
}
std::vector<size_t> chain_points(const Points &points, Point *start_near)
{
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
std::vector<size_t> out;
out.reserve(ordered.size());
for (auto &segment_and_reversal : ordered)
out.emplace_back(segment_and_reversal.first);
return out;
}
Polylines chain_polylines(Polylines &&polylines, const Point *start_near)
{
auto segment_end_point = [&polylines](size_t idx, bool first_point) -> const Point& { return first_point ? polylines[idx].first_point() : polylines[idx].last_point(); };
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), start_near);
Polylines out;
out.reserve(polylines.size());
for (auto &segment_and_reversal : ordered) {
out.emplace_back(std::move(polylines[segment_and_reversal.first]));
if (segment_and_reversal.second)
out.back().reverse();
}
return out;
}
template<class T> static inline T chain_path_items(const Points &points, const T &items)
{
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), nullptr);
T out;
out.reserve(items.size());
for (auto &segment_and_reversal : ordered)
out.emplace_back(items[segment_and_reversal.first]);
return out;
}
ClipperLib::PolyNodes chain_clipper_polynodes(const Points &points, const ClipperLib::PolyNodes &items)
{
return chain_path_items(points, items);
}
std::vector<std::pair<size_t, size_t>> chain_print_object_instances(const Print &print)
{
// Order objects using a nearest neighbor search.
Points object_reference_points;
std::vector<std::pair<size_t, size_t>> instances;
for (size_t i = 0; i < print.objects().size(); ++ i) {
const PrintObject &object = *print.objects()[i];
for (size_t j = 0; j < object.copies().size(); ++ j) {
object_reference_points.emplace_back(object.copy_center(j));
instances.emplace_back(i, j);
}
}
auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; };
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), nullptr);
std::vector<std::pair<size_t, size_t>> out;
out.reserve(instances.size());
for (auto &segment_and_reversal : ordered)
out.emplace_back(instances[segment_and_reversal.first]);
return out;
}
} // namespace Slic3r

View file

@ -0,0 +1,38 @@
#ifndef slic3r_ShortestPath_hpp_
#define slic3r_ShortestPath_hpp_
#include "libslic3r.h"
#include "ExtrusionEntity.hpp"
#include "Point.hpp"
#include <utility>
#include <vector>
namespace ClipperLib { class PolyNode; }
namespace Slic3r {
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, std::vector<std::pair<size_t, bool>> &chain);
void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
Polylines chain_polylines(Polylines &&src, const Point *start_near = nullptr);
inline Polylines chain_polylines(const Polylines& src, const Point* start_near = nullptr) { Polylines tmp(src); return chain_polylines(std::move(tmp), start_near); }
std::vector<ClipperLib::PolyNode*> chain_clipper_polynodes(const Points &points, const std::vector<ClipperLib::PolyNode*> &items);
// Chain instances of print objects by an approximate shortest path.
// Returns pairs of PrintObject idx and instance of that PrintObject.
class Print;
std::vector<std::pair<size_t, size_t>> chain_print_object_instances(const Print &print);
} // namespace Slic3r
#endif /* slic3r_ShortestPath_hpp_ */

View file

@ -923,7 +923,7 @@ namespace SupportMaterialInternal {
//FIXME add supports at regular intervals to support long bridges! //FIXME add supports at regular intervals to support long bridges!
bridges = diff(bridges, bridges = diff(bridges,
// Offset unsupported edges into polygons. // Offset unsupported edges into polygons.
offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS));
// Remove bridged areas from the supported areas. // Remove bridged areas from the supported areas.
contact_polygons = diff(contact_polygons, bridges, true); contact_polygons = diff(contact_polygons, bridges, true);
} }
@ -2125,7 +2125,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
} }
// $layer->slices contains the full shape of layer, thus including // $layer->slices contains the full shape of layer, thus including
// perimeter's width. $support contains the full shape of support // perimeter's width. $support contains the full shape of support
// material, thus including the width of its foremost extrusion. // material, thus including the width of its foremost extrusion.
// We leave a gap equal to a full extrusion width. // We leave a gap equal to a full extrusion width.
support_layer.polygons = diff(support_layer.polygons, polygons_trimming); support_layer.polygons = diff(support_layer.polygons, polygons_trimming);
} }
@ -2934,20 +2934,13 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Prepare fillers. // Prepare fillers.
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
bool with_sheath = m_object_config->support_material_with_sheath; bool with_sheath = m_object_config->support_material_with_sheath;
InfillPattern infill_pattern; InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipRectilinear);
std::vector<float> angles; std::vector<float> angles;
angles.push_back(base_angle); angles.push_back(base_angle);
switch (support_pattern) {
case smpRectilinearGrid: if (support_pattern == smpRectilinearGrid)
angles.push_back(interface_angle); angles.push_back(interface_angle);
// fall through
case smpRectilinear:
infill_pattern = ipRectilinear;
break;
case smpHoneycomb:
infill_pattern = ipHoneycomb;
break;
}
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
// const coordf_t link_max_length_factor = 3.; // const coordf_t link_max_length_factor = 3.;
@ -3217,7 +3210,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
density = 0.5f; density = 0.5f;
flow = m_first_layer_flow; flow = m_first_layer_flow;
// use the proper spacing for first layer as we don't need to align // use the proper spacing for first layer as we don't need to align
// its pattern to the other layers // its pattern to the other layers
//FIXME When paralellizing, each thread shall have its own copy of the fillers. //FIXME When paralellizing, each thread shall have its own copy of the fillers.
filler->spacing = flow.spacing(); filler->spacing = flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));

View file

@ -42,7 +42,7 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve
text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")"; text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")";
text += "</b></font><br>"; text += "</b></font><br>";
// End of row header. // End of row header.
text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>"; text += _(L("PrusaSlicer version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
text += _(L("print")) + ": " + snapshot.print + "<br>"; text += _(L("print")) + ": " + snapshot.print + "<br>";
text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>"; text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
text += _(L("printer")) + ": " + snapshot.printer + "<br>"; text += _(L("printer")) + ": " + snapshot.printer + "<br>";
@ -50,9 +50,9 @@ static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_eve
bool compatible = true; bool compatible = true;
for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) { for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) {
text += _(L("vendor")) + ": " + vc.name +", " + _(L("version")) + ": " + vc.version.config_version.to_string() + text += _(L("vendor")) + ": " + vc.name +", " + _(L("version")) + ": " + vc.version.config_version.to_string() +
", " + _(L("min slic3r version")) + ": " + vc.version.min_slic3r_version.to_string(); ", " + _(L("min PrusaSlicer version")) + ": " + vc.version.min_slic3r_version.to_string();
if (vc.version.max_slic3r_version != Semver::inf()) if (vc.version.max_slic3r_version != Semver::inf())
text += ", " + _(L("max slic3r version")) + ": " + vc.version.max_slic3r_version.to_string(); text += ", " + _(L("max PrusaSlicer version")) + ": " + vc.version.max_slic3r_version.to_string();
text += "<br>"; text += "<br>";
for (const std::pair<std::string, std::set<std::string>> &model : vc.models_variants_installed) { for (const std::pair<std::string, std::set<std::string>> &model : vc.models_variants_installed) {
text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": "; text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": ";

View file

@ -1792,13 +1792,16 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page)
const wxString& ConfigWizard::name(const bool from_menu/* = false*/) const wxString& ConfigWizard::name(const bool from_menu/* = false*/)
{ {
// A different naming convention is used for the Wizard on Windows vs. OSX & GTK. // A different naming convention is used for the Wizard on Windows & GTK vs. OSX.
// Note: Don't call _() macro here.
// This function just return the current name according to the OS.
// Translation is implemented inside GUI_App::add_config_menu()
#if __APPLE__ #if __APPLE__
static const wxString config_wizard_name = _(L("Configuration Assistant")); static const wxString config_wizard_name = L("Configuration Assistant");
static const wxString config_wizard_name_menu = _(L("Configuration &Assistant")); static const wxString config_wizard_name_menu = L("Configuration &Assistant");
#else #else
static const wxString config_wizard_name = _(L("Configuration Wizard")); static const wxString config_wizard_name = L("Configuration Wizard");
static const wxString config_wizard_name_menu = _(L("Configuration &Wizard")); static const wxString config_wizard_name_menu = L("Configuration &Wizard");
#endif #endif
return from_menu ? config_wizard_name_menu : config_wizard_name; return from_menu ? config_wizard_name_menu : config_wizard_name;
} }

View file

@ -1094,7 +1094,7 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const
wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
@ -3012,15 +3012,16 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
wxGetApp().obj_manipul()->set_dirty(); wxGetApp().obj_manipul()->set_dirty();
// forces a frame render to update the view before the context menu is shown // forces a frame render to update the view before the context menu is shown
render(); render();
}
}
Vec2d logical_pos = pos.cast<double>(); Vec2d logical_pos = pos.cast<double>();
#if ENABLE_RETINA_GL #if ENABLE_RETINA_GL
const float factor = m_retina_helper->get_scale_factor(); const float factor = m_retina_helper->get_scale_factor();
logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor)); logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor));
#endif // ENABLE_RETINA_GL #endif // ENABLE_RETINA_GL
post_event(Vec2dEvent(EVT_GLCANVAS_RIGHT_CLICK, logical_pos)); if (!m_mouse.dragging)
} // do not post the event if the user is panning the scene
} post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() }));
} }
mouse_up_cleanup(); mouse_up_cleanup();
@ -3372,7 +3373,7 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type)
void GLCanvas3D::set_camera_zoom(double zoom) void GLCanvas3D::set_camera_zoom(double zoom)
{ {
const Size& cnv_size = get_canvas_size(); const Size& cnv_size = get_canvas_size();
m_camera.set_zoom(zoom, _max_bounding_box(false, false), cnv_size.get_width(), cnv_size.get_height()); m_camera.set_zoom(zoom, _max_bounding_box(false, true), cnv_size.get_width(), cnv_size.get_height());
m_dirty = true; m_dirty = true;
} }
@ -3388,10 +3389,9 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc
m_sidebar_field = focus_on ? opt_key : ""; m_sidebar_field = focus_on ? opt_key : "";
if (!m_sidebar_field.empty()) if (!m_sidebar_field.empty())
{
m_gizmos.reset_all_states(); m_gizmos.reset_all_states();
m_dirty = true; m_dirty = true;
}
} }
void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type) void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type)
@ -5000,6 +5000,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
return path.width; return path.width;
case GCodePreviewData::Extrusion::Feedrate: case GCodePreviewData::Extrusion::Feedrate:
return path.feedrate; return path.feedrate;
case GCodePreviewData::Extrusion::FanSpeed:
return path.fan_speed;
case GCodePreviewData::Extrusion::VolumetricRate: case GCodePreviewData::Extrusion::VolumetricRate:
return path.feedrate * (float)path.mm3_per_mm; return path.feedrate * (float)path.mm3_per_mm;
case GCodePreviewData::Extrusion::Tool: case GCodePreviewData::Extrusion::Tool:
@ -5025,6 +5027,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
return data.get_width_color(value); return data.get_width_color(value);
case GCodePreviewData::Extrusion::Feedrate: case GCodePreviewData::Extrusion::Feedrate:
return data.get_feedrate_color(value); return data.get_feedrate_color(value);
case GCodePreviewData::Extrusion::FanSpeed:
return data.get_fan_speed_color(value);
case GCodePreviewData::Extrusion::VolumetricRate: case GCodePreviewData::Extrusion::VolumetricRate:
return data.get_volumetric_rate_color(value); return data.get_volumetric_rate_color(value);
case GCodePreviewData::Extrusion::Tool: case GCodePreviewData::Extrusion::Tool:

View file

@ -71,6 +71,8 @@ public:
wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
using Vec2dEvent = Event<Vec2d>; using Vec2dEvent = Event<Vec2d>;
// _bool_ value is used as a indicator of selection in the 3DScene
using RBtnEvent = Event<std::pair<Vec2d, bool>>;
template <size_t N> using Vec2dsEvent = ArrayEvent<Vec2d, N>; template <size_t N> using Vec2dsEvent = ArrayEvent<Vec2d, N>;
using Vec3dEvent = Event<Vec3d>; using Vec3dEvent = Event<Vec3d>;
@ -78,7 +80,7 @@ template <size_t N> using Vec3dsEvent = ArrayEvent<Vec3d, N>;
wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);

View file

@ -285,6 +285,9 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
{ {
this->SetFont(wxGetApp().normal_font()); this->SetFont(wxGetApp().normal_font());
// Reset m_enter_pressed flag to _false_, when value is editing
this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId());
this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&) this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&)
{ {
m_enter_pressed = true; m_enter_pressed = true;
@ -307,7 +310,7 @@ LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
if (!m_enter_pressed) { if (!m_enter_pressed) {
#ifndef __WXGTK__ #ifndef __WXGTK__
/* Update data for next editor selection. /* Update data for next editor selection.
* But under GTK it lucks like there is no information about selected control at e.GetWindow(), * But under GTK it looks like there is no information about selected control at e.GetWindow(),
* so we'll take it from wxEVT_LEFT_DOWN event * so we'll take it from wxEVT_LEFT_DOWN event
* */ * */
LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow()); LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow());

View file

@ -131,7 +131,7 @@ ObjectList::ObjectList(wxWindow* parent) :
{ {
wxDataViewItemArray sels; wxDataViewItemArray sels;
GetSelections(sels); GetSelections(sels);
if (sels.front() == m_last_selected_item) if (! sels.empty() && sels.front() == m_last_selected_item)
m_last_selected_item = sels.back(); m_last_selected_item = sels.back();
else else
m_last_selected_item = event.GetItem(); m_last_selected_item = event.GetItem();
@ -255,21 +255,32 @@ void ObjectList::create_objects_ctrl()
EnableDropTarget(wxDF_UNICODETEXT); EnableDropTarget(wxDF_UNICODETEXT);
#endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE #endif // wxUSE_DRAG_AND_DROP && wxUSE_UNICODE
const int em = wxGetApp().em_unit();
// column ItemName(Icon+Text) of the view control: // column ItemName(Icon+Text) of the view control:
// And Icon can be consisting of several bitmaps // And Icon can be consisting of several bitmaps
AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(), AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(),
colName, 20*wxGetApp().em_unit()/*200*/, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE));
// column PrintableProperty (Icon) of the view control: // column PrintableProperty (Icon) of the view control:
AppendBitmapColumn(" ", colPrint, wxDATAVIEW_CELL_INERT, int(2 * wxGetApp().em_unit()), AppendBitmapColumn(" ", colPrint, wxDATAVIEW_CELL_INERT, 3*em,
wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
// column Extruder of the view control: // column Extruder of the view control:
AppendColumn(create_objects_list_extruder_column(4)); AppendColumn(create_objects_list_extruder_column(4));
// column ItemEditing of the view control: // column ItemEditing of the view control:
AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, int(2.5 * wxGetApp().em_unit())/*25*/, AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em,
wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE);
// For some reason under OSX on 4K(5K) monitors in wxDataViewColumn constructor doesn't set width of column.
// Therefore, force set column width.
if (wxOSX)
{
GetColumn(colName)->SetWidth(20*em);
GetColumn(colPrint)->SetWidth(3*em);
GetColumn(colExtruder)->SetWidth(8*em);
}
} }
void ObjectList::create_popup_menus() void ObjectList::create_popup_menus()
@ -279,6 +290,7 @@ void ObjectList::create_popup_menus()
create_part_popupmenu(&m_menu_part); create_part_popupmenu(&m_menu_part);
create_sla_object_popupmenu(&m_menu_sla_object); create_sla_object_popupmenu(&m_menu_sla_object);
create_instance_popupmenu(&m_menu_instance); create_instance_popupmenu(&m_menu_instance);
create_default_popupmenu(&m_menu_default);
} }
void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/) void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& input_item/* = wxDataViewItem(nullptr)*/)
@ -741,9 +753,9 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol
} }
select_items(items); select_items(items);
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME //#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
selection_changed(); selection_changed();
#endif //no __WXOSX__ //__WXMSW__ //#endif //no __WXOSX__ //__WXMSW__
} }
void ObjectList::paste_objects_into_list(const std::vector<size_t>& object_idxs) void ObjectList::paste_objects_into_list(const std::vector<size_t>& object_idxs)
@ -761,9 +773,9 @@ void ObjectList::paste_objects_into_list(const std::vector<size_t>& object_idxs)
wxGetApp().plater()->changed_objects(object_idxs); wxGetApp().plater()->changed_objects(object_idxs);
select_items(items); select_items(items);
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME //#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
selection_changed(); selection_changed();
#endif //no __WXOSX__ //__WXMSW__ //#endif //no __WXOSX__ //__WXMSW__
} }
#ifdef __WXOSX__ #ifdef __WXOSX__
@ -783,42 +795,71 @@ void ObjectList::OnChar(wxKeyEvent& event)
void ObjectList::OnContextMenu(wxDataViewEvent&) void ObjectList::OnContextMenu(wxDataViewEvent&)
{ {
list_manipulation(); list_manipulation(true);
} }
void ObjectList::list_manipulation() void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
{ {
wxDataViewItem item; wxDataViewItem item;
wxDataViewColumn* col = nullptr; wxDataViewColumn* col = nullptr;
const wxPoint pt = get_mouse_position_in_control(); const wxPoint pt = get_mouse_position_in_control();
HitTest(pt, item, col); HitTest(pt, item, col);
if (!item || col == nullptr) { /* Note: Under OSX right click doesn't send "selection changed" event.
* It means that Selection() will be return still previously selected item.
* Thus under OSX we should force UnselectAll(), when item and col are nullptr,
* and select new item otherwise.
*/
if (!item) {
if (col == nullptr) {
if (wxOSX)
UnselectAll();
else if (!evt_context_menu)
// Case, when last item was deleted and under GTK was called wxEVT_DATAVIEW_SELECTION_CHANGED,
// which invoked next list_manipulation(false)
return; return;
} }
if (evt_context_menu) {
show_context_menu(evt_context_menu);
return;
}
}
if (wxOSX && item && col) {
UnselectAll();
Select(item);
}
const wxString title = col->GetTitle(); const wxString title = col->GetTitle();
if (title == " ") if (title == " ")
toggle_printable_state(item); toggle_printable_state(item);
else if (title == _("Editing")) else if (title == _("Editing"))
show_context_menu(); show_context_menu(evt_context_menu);
else if (title == _("Name")) else if (title == _("Name"))
{
if (wxOSX)
show_context_menu(evt_context_menu); // return context menu under OSX (related to #2909)
if (is_windows10())
{ {
int obj_idx, vol_idx; int obj_idx, vol_idx;
get_selected_item_indexes(obj_idx, vol_idx, item); get_selected_item_indexes(obj_idx, vol_idx, item);
if (is_windows10() && get_mesh_errors_count(obj_idx, vol_idx) > 0 && if (get_mesh_errors_count(obj_idx, vol_idx) > 0 &&
pt.x > 2*wxGetApp().em_unit() && pt.x < 4*wxGetApp().em_unit() ) pt.x > 2*wxGetApp().em_unit() && pt.x < 4*wxGetApp().em_unit() )
fix_through_netfabb(); fix_through_netfabb();
} }
}
#ifndef __WXMSW__ #ifndef __WXMSW__
GetMainWindow()->SetToolTip(""); // hide tooltip GetMainWindow()->SetToolTip(""); // hide tooltip
#endif //__WXMSW__ #endif //__WXMSW__
} }
void ObjectList::show_context_menu() void ObjectList::show_context_menu(const bool evt_context_menu)
{ {
if (multiple_selection()) if (multiple_selection())
{ {
@ -831,22 +872,26 @@ void ObjectList::show_context_menu()
} }
const auto item = GetSelection(); const auto item = GetSelection();
wxMenu* menu {nullptr};
if (item) if (item)
{ {
const ItemType type = m_objects_model->GetItemType(item); const ItemType type = m_objects_model->GetItemType(item);
if (!(type & (itObject | itVolume | itLayer | itInstance))) if (!(type & (itObject | itVolume | itLayer | itInstance)))
return; return;
wxMenu* menu = type & itInstance ? &m_menu_instance : menu = type & itInstance ? &m_menu_instance :
type & itLayer ? &m_menu_layer : type & itLayer ? &m_menu_layer :
m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part : m_objects_model->GetParent(item) != wxDataViewItem(nullptr) ? &m_menu_part :
printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object;
if (!(type & itInstance)) if (!(type & itInstance))
append_menu_item_settings(menu); append_menu_item_settings(menu);
wxGetApp().plater()->PopupMenu(menu);
} }
else if (evt_context_menu)
menu = &m_menu_default;
if (menu)
wxGetApp().plater()->PopupMenu(menu);
} }
void ObjectList::copy() void ObjectList::copy()
@ -1286,13 +1331,16 @@ void ObjectList::show_settings(const wxDataViewItem settings_item)
wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) { wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeType type) {
auto sub_menu = new wxMenu; auto sub_menu = new wxMenu;
if (wxGetApp().get_mode() == comExpert) { if (wxGetApp().get_mode() == comExpert && type != ModelVolumeType::INVALID) {
append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "", append_menu_item(sub_menu, wxID_ANY, _(L("Load")) + " " + dots, "",
[this, type](wxCommandEvent&) { load_subobject(type); }, "", menu); [this, type](wxCommandEvent&) { load_subobject(type); }, "", menu);
sub_menu->AppendSeparator(); sub_menu->AppendSeparator();
} }
for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") }) { for (auto& item : { L("Box"), L("Cylinder"), L("Sphere"), L("Slab") })
{
if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0)
continue;
append_menu_item(sub_menu, wxID_ANY, _(item), "", append_menu_item(sub_menu, wxID_ANY, _(item), "",
[this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu); [this, type, item](wxCommandEvent&) { load_generic_subobject(item, type); }, "", menu);
} }
@ -1579,6 +1627,12 @@ void ObjectList::create_instance_popupmenu(wxMenu*menu)
}, m_menu_item_split_instances->GetId()); }, m_menu_item_split_instances->GetId());
} }
void ObjectList::create_default_popupmenu(wxMenu*menu)
{
wxMenu* sub_menu = append_submenu_add_generic(menu, ModelVolumeType::INVALID);
append_submenu(menu, sub_menu, wxID_ANY, _(L("Add Shape")), "", "add_part");
}
wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
{ {
wxMenu *menu = new wxMenu; wxMenu *menu = new wxMenu;
@ -1668,9 +1722,9 @@ void ObjectList::load_subobject(ModelVolumeType type)
if (sel_item) if (sel_item)
select_item(sel_item); select_item(sel_item);
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME //#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
selection_changed(); selection_changed();
#endif //no __WXOSX__ //__WXMSW__ //#endif //no __WXOSX__ //__WXMSW__
} }
void ObjectList::load_part( ModelObject* model_object, void ObjectList::load_part( ModelObject* model_object,
@ -1717,8 +1771,38 @@ void ObjectList::load_part( ModelObject* model_object,
} }
static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf3& bb)
{
TriangleMesh mesh;
const double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1);
if (type_name == "Box")
// Sitting on the print bed, left front front corner at (0, 0).
mesh = make_cube(side, side, side);
else if (type_name == "Cylinder")
// Centered around 0, sitting on the print bed.
// The cylinder has the same volume as the box above.
mesh = make_cylinder(0.564 * side, side);
else if (type_name == "Sphere")
// Centered around 0, half the sphere below the print bed, half above.
// The sphere has the same volume as the box above.
mesh = make_sphere(0.62 * side, PI / 18);
else if (type_name == "Slab")
// Sitting on the print bed, left front front corner at (0, 0).
mesh = make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5);
mesh.repair();
return mesh;
}
void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type) void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type)
{ {
if (type == ModelVolumeType::INVALID) {
load_shape_object(type_name);
return;
}
const int obj_idx = get_selected_obj_idx(); const int obj_idx = get_selected_obj_idx();
if (obj_idx < 0) if (obj_idx < 0)
return; return;
@ -1741,26 +1825,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
// Bounding box of the selected instance in world coordinate system including the translation, without modifiers. // Bounding box of the selected instance in world coordinate system including the translation, without modifiers.
BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx); BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx);
const wxString name = _(L("Generic")) + "-" + _(type_name); TriangleMesh mesh = create_mesh(type_name, instance_bb);
TriangleMesh mesh;
double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1);
if (type_name == "Box")
// Sitting on the print bed, left front front corner at (0, 0).
mesh = make_cube(side, side, side);
else if (type_name == "Cylinder")
// Centered around 0, sitting on the print bed.
// The cylinder has the same volume as the box above.
mesh = make_cylinder(0.564 * side, side);
else if (type_name == "Sphere")
// Centered around 0, half the sphere below the print bed, half above.
// The sphere has the same volume as the box above.
mesh = make_sphere(0.62 * side, PI / 18);
else if (type_name == "Slab")
// Sitting on the print bed, left front front corner at (0, 0).
mesh = make_cube(instance_bb.size().x()*1.5, instance_bb.size().y()*1.5, instance_bb.size().z()*0.5);
mesh.repair();
// Mesh will be centered when loading. // Mesh will be centered when loading.
ModelVolume *new_volume = model_object.add_volume(std::move(mesh)); ModelVolume *new_volume = model_object.add_volume(std::move(mesh));
@ -1782,6 +1847,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset); new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
} }
const wxString name = _(L("Generic")) + "-" + _(type_name);
new_volume->name = into_u8(name); new_volume->name = into_u8(name);
// set a default extruder value, since user can't add it manually // set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
@ -1794,9 +1860,63 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
const auto object_item = m_objects_model->GetTopParent(GetSelection()); const auto object_item = m_objects_model->GetTopParent(GetSelection());
select_item(m_objects_model->AddVolumeChild(object_item, name, type, select_item(m_objects_model->AddVolumeChild(object_item, name, type,
new_volume->get_mesh_errors_count()>0)); new_volume->get_mesh_errors_count()>0));
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME //#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
selection_changed(); selection_changed();
#endif //no __WXOSX__ //__WXMSW__ //#endif //no __WXOSX__ //__WXMSW__
}
void ObjectList::load_shape_object(const std::string& type_name)
{
const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
assert(selection.get_object_idx() == -1); // Add nothing is something is selected on 3DScene
if (selection.get_object_idx() != -1)
return;
const int obj_idx = m_objects->size();
if (obj_idx < 0)
return;
take_snapshot(_(L("Add Shape")));
// Create mesh
BoundingBoxf3 bb;
TriangleMesh mesh = create_mesh(type_name, bb);
// Add mesh to model as a new object
Model& model = wxGetApp().plater()->model();
const wxString name = _(L("Shape")) + "-" + _(type_name);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
std::vector<size_t> object_idxs;
ModelObject* new_object = model.add_object();
new_object->name = into_u8(name);
new_object->add_instance(); // each object should have at list one instance
ModelVolume* new_volume = new_object->add_volume(mesh);
new_volume->name = into_u8(name);
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
new_object->invalidate_bounding_box();
new_object->center_around_origin();
new_object->ensure_on_bed();
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
object_idxs.push_back(model.objects.size() - 1);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
paste_objects_into_list(object_idxs);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
} }
void ObjectList::del_object(const int obj_idx) void ObjectList::del_object(const int obj_idx)
@ -3569,10 +3689,10 @@ void ObjectList::msw_rescale()
// update min size !!! A width of control shouldn't be a wxDefaultCoord // update min size !!! A width of control shouldn't be a wxDefaultCoord
SetMinSize(wxSize(1, 15 * em)); SetMinSize(wxSize(1, 15 * em));
GetColumn(colName)->SetWidth(19 * em); GetColumn(colName )->SetWidth(20 * em);
GetColumn(colPrint)->SetWidth( 2 * em); GetColumn(colPrint )->SetWidth( 3 * em);
GetColumn(colExtruder)->SetWidth( 8 * em); GetColumn(colExtruder)->SetWidth( 8 * em);
GetColumn(colEditing)->SetWidth( 2 * em); GetColumn(colEditing )->SetWidth( 3 * em);
// rescale all icons, used by ObjectList // rescale all icons, used by ObjectList
msw_rescale_icons(); msw_rescale_icons();
@ -3585,7 +3705,8 @@ void ObjectList::msw_rescale()
&m_menu_part, &m_menu_part,
&m_menu_sla_object, &m_menu_sla_object,
&m_menu_instance, &m_menu_instance,
&m_menu_layer }) &m_menu_layer,
&m_menu_default})
msw_rescale_menu(menu); msw_rescale_menu(menu);
Layout(); Layout();

View file

@ -132,6 +132,7 @@ private:
MenuWithSeparators m_menu_sla_object; MenuWithSeparators m_menu_sla_object;
MenuWithSeparators m_menu_instance; MenuWithSeparators m_menu_instance;
MenuWithSeparators m_menu_layer; MenuWithSeparators m_menu_layer;
MenuWithSeparators m_menu_default;
wxMenuItem* m_menu_item_settings { nullptr }; wxMenuItem* m_menu_item_settings { nullptr };
wxMenuItem* m_menu_item_split_instances { nullptr }; wxMenuItem* m_menu_item_split_instances { nullptr };
@ -208,7 +209,7 @@ public:
void set_tooltip_for_item(const wxPoint& pt); void set_tooltip_for_item(const wxPoint& pt);
void selection_changed(); void selection_changed();
void show_context_menu(); void show_context_menu(const bool evt_context_menu);
#ifndef __WXOSX__ #ifndef __WXOSX__
void key_event(wxKeyEvent& event); void key_event(wxKeyEvent& event);
#endif /* __WXOSX__ */ #endif /* __WXOSX__ */
@ -240,6 +241,7 @@ public:
void create_sla_object_popupmenu(wxMenu*menu); void create_sla_object_popupmenu(wxMenu*menu);
void create_part_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu);
void create_instance_popupmenu(wxMenu*menu); void create_instance_popupmenu(wxMenu*menu);
void create_default_popupmenu(wxMenu *menu);
wxMenu* create_settings_popupmenu(wxMenu *parent_menu); wxMenu* create_settings_popupmenu(wxMenu *parent_menu);
void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true); void create_freq_settings_popupmenu(wxMenu *parent_menu, const bool is_object_settings = true);
@ -248,6 +250,7 @@ public:
void load_subobject(ModelVolumeType type); void load_subobject(ModelVolumeType type);
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type); void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
void load_shape_object(const std::string &type_name);
void del_object(const int obj_idx); void del_object(const int obj_idx);
void del_subobject_item(wxDataViewItem& item); void del_subobject_item(wxDataViewItem& item);
void del_settings_from_config(const wxDataViewItem& parent_item); void del_settings_from_config(const wxDataViewItem& parent_item);
@ -362,7 +365,7 @@ private:
// void OnChar(wxKeyEvent& event); // void OnChar(wxKeyEvent& event);
#endif /* __WXOSX__ */ #endif /* __WXOSX__ */
void OnContextMenu(wxDataViewEvent &event); void OnContextMenu(wxDataViewEvent &event);
void list_manipulation(); void list_manipulation(bool evt_context_menu = false);
void OnBeginDrag(wxDataViewEvent &event); void OnBeginDrag(wxDataViewEvent &event);
void OnDropPossible(wxDataViewEvent &event); void OnDropPossible(wxDataViewEvent &event);

View file

@ -221,6 +221,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view
m_choice_view_type->Append(_(L("Height"))); m_choice_view_type->Append(_(L("Height")));
m_choice_view_type->Append(_(L("Width"))); m_choice_view_type->Append(_(L("Width")));
m_choice_view_type->Append(_(L("Speed"))); m_choice_view_type->Append(_(L("Speed")));
m_choice_view_type->Append(_(L("Fan speed")));
m_choice_view_type->Append(_(L("Volumetric flow rate"))); m_choice_view_type->Append(_(L("Volumetric flow rate")));
m_choice_view_type->Append(_(L("Tool"))); m_choice_view_type->Append(_(L("Tool")));
m_choice_view_type->Append(_(L("Color Print"))); m_choice_view_type->Append(_(L("Color Print")));

View file

@ -252,7 +252,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i]; const sla::SupportPoint& support_point = m_editing_mode ? m_editing_cache[i].support_point : m_normal_cache[i];
const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false; const bool& point_selected = m_editing_mode ? m_editing_cache[i].selected : false;
if (is_point_clipped(support_point.pos.cast<double>())) if (is_mesh_point_clipped(support_point.pos.cast<double>()))
continue; continue;
// First decide about the color of the point. // First decide about the color of the point.
@ -335,14 +335,14 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
{ {
if (m_clipping_plane_distance == 0.f) if (m_clipping_plane_distance == 0.f)
return false; return false;
Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point; Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point;
transformed_point(2) += m_z_shift; transformed_point(2) += m_z_shift;
return m_clipping_plane->distance(transformed_point) < 0.; return m_clipping_plane->is_point_clipped(transformed_point);
} }
@ -391,27 +391,15 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift)); trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
// The raycaster query // The raycaster query
std::vector<Vec3f> hits; Vec3f hit;
std::vector<Vec3f> normals; Vec3f normal;
m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, &hits, &normals); if (m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) {
// Return both the point and the facet normal.
// We must also take care of the clipping plane (if active) pos_and_normal = std::make_pair(hit, normal);
unsigned i = 0;
if (m_clipping_plane_distance != 0.f) {
for (i=0; i<hits.size(); ++i)
if (! is_point_clipped(hits[i].cast<double>()))
break;
}
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
// All hits are either clipped, or there is an odd number of unclipped
// hits - meaning the nearest must be from inside the mesh.
return false;
}
// Calculate and return both the point and the facet normal.
pos_and_normal = std::make_pair(hits[i], normals[i]);
return true; return true;
}
else
return false;
} }
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
@ -481,20 +469,16 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
std::vector<Vec3f> points_inside; std::vector<Vec3f> points_inside;
std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points); std::vector<unsigned int> points_idxs = m_selection_rectangle.stop_dragging(m_parent, points);
for (size_t idx : points_idxs) for (size_t idx : points_idxs)
points_inside.push_back((trafo.get_matrix() * points[idx]).cast<float>()); points_inside.push_back(points[idx].cast<float>());
// Only select/deselect points that are actually visible // Only select/deselect points that are actually visible
for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_clipping_plane.get()))
[this](const Vec3f& pt) { return is_point_clipped(pt.cast<double>()); }))
{ {
const sla::SupportPoint &support_point = m_editing_cache[points_idxs[idx]].support_point;
if (! is_point_clipped(support_point.pos.cast<double>())) {
if (rectangle_status == GLSelectionRectangle::Deselect) if (rectangle_status == GLSelectionRectangle::Deselect)
unselect_point(points_idxs[idx]); unselect_point(points_idxs[idx]);
else else
select_point(points_idxs[idx]); select_point(points_idxs[idx]);
} }
}
return true; return true;
} }

View file

@ -125,7 +125,7 @@ private:
mutable std::unique_ptr<MeshClipper> m_supports_clipper; mutable std::unique_ptr<MeshClipper> m_supports_clipper;
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const; std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_point_clipped(const Vec3d& point) const; bool is_mesh_point_clipped(const Vec3d& point) const;
//void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const; //void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
// Methods that do the model_object and editing cache synchronization, // Methods that do the model_object and editing cache synchronization,

View file

@ -152,8 +152,8 @@ Vec3f MeshRaycaster::AABBWrapper::get_hit_normal(const igl::Hit& hit) const
} }
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
const Camera& camera, std::vector<Vec3f>* positions, std::vector<Vec3f>* normals) const Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const
{ {
const std::array<int, 4>& viewport = camera.get_viewport(); const std::array<int, 4>& viewport = camera.get_viewport();
const Transform3d& model_mat = camera.get_view_matrix(); const Transform3d& model_mat = camera.get_view_matrix();
@ -179,25 +179,30 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
unsigned i = 0;
// Remove points that are obscured or cut by the clipping plane
if (clipping_plane) {
for (i=0; i<hits.size(); ++i)
if (! clipping_plane->is_point_clipped(trafo * m_AABB_wrapper->get_hit_pos(hits[i]).cast<double>()))
break;
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
// All hits are either clipped, or there is an odd number of unclipped
// hits - meaning the nearest must be from inside the mesh.
return false;
}
}
// Now stuff the points in the provided vector and calculate normals if asked about them: // Now stuff the points in the provided vector and calculate normals if asked about them:
if (positions != nullptr) { position = m_AABB_wrapper->get_hit_pos(hits[i]);
positions->clear(); normal = m_AABB_wrapper->get_hit_normal(hits[i]);
if (normals != nullptr)
normals->clear();
for (const igl::Hit& hit : hits) {
positions->push_back(m_AABB_wrapper->get_hit_pos(hit));
if (normals != nullptr)
normals->push_back(m_AABB_wrapper->get_hit_normal(hit));
}
}
return true; return true;
} }
std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points, std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector<Vec3f>& points,
std::function<bool(const Vec3f&)> fn_ignore_hit) const const ClippingPlane* clipping_plane) const
{ {
std::vector<unsigned> out; std::vector<unsigned> out;
@ -206,19 +211,24 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval(); Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval();
Vec3f scaling = trafo.get_scaling_factor().cast<float>(); Vec3f scaling = trafo.get_scaling_factor().cast<float>();
direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2)); direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2));
const Transform3f inverse_trafo = trafo.get_matrix().inverse().cast<float>();
for (size_t i=0; i<points.size(); ++i) { for (size_t i=0; i<points.size(); ++i) {
const Vec3f& pt = points[i]; const Vec3f& pt = points[i];
if (clipping_plane && clipping_plane->is_point_clipped(pt.cast<double>()))
continue;
bool is_obscured = false; bool is_obscured = false;
// Cast a ray in the direction of the camera and look for intersection with the mesh: // Cast a ray in the direction of the camera and look for intersection with the mesh:
std::vector<igl::Hit> hits; std::vector<igl::Hit> hits;
// Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies. // Offset the start of the ray by EPSILON to account for numerical inaccuracies.
if (m_AABB_wrapper->m_AABB.intersect_ray( if (m_AABB_wrapper->m_AABB.intersect_ray(
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3), AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3), AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) { inverse_trafo * pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) {
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; }); std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
// If the closest hit facet normal points in the same direction as the ray, // If the closest hit facet normal points in the same direction as the ray,
// we are looking through the mesh and should therefore discard the point: // we are looking through the mesh and should therefore discard the point:
if (m_AABB_wrapper->get_hit_normal(hits.front()).dot(direction_to_camera_mesh) > 0.f) if (m_AABB_wrapper->get_hit_normal(hits.front()).dot(direction_to_camera_mesh) > 0.f)
@ -227,11 +237,12 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
// Eradicate all hits that the caller wants to ignore // Eradicate all hits that the caller wants to ignore
for (unsigned j=0; j<hits.size(); ++j) { for (unsigned j=0; j<hits.size(); ++j) {
const igl::Hit& hit = hits[j]; const igl::Hit& hit = hits[j];
if (fn_ignore_hit(m_AABB_wrapper->get_hit_pos(hit))) { if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * m_AABB_wrapper->get_hit_pos(hit).cast<double>())) {
hits.erase(hits.begin()+j); hits.erase(hits.begin()+j);
--j; --j;
} }
} }
// FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction. // FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
// Also, the threshold is in mesh coordinates, not in actual dimensions. // Also, the threshold is in mesh coordinates, not in actual dimensions.
if (! hits.empty()) if (! hits.empty())

View file

@ -50,6 +50,7 @@ public:
return (-get_normal().dot(pt) + m_data[3]); return (-get_normal().dot(pt) + m_data[3]);
} }
bool is_point_clipped(const Vec3d& point) const { return distance(point) < 0.; }
void set_normal(const Vec3d& normal) { for (size_t i=0; i<3; ++i) m_data[i] = normal(i); } void set_normal(const Vec3d& normal) { for (size_t i=0; i<3; ++i) m_data[i] = normal(i); }
void set_offset(double offset) { m_data[3] = offset; } void set_offset(double offset) { m_data[3] = offset; }
Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); } Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); }
@ -98,10 +99,10 @@ public:
void set_camera(const Camera& camera); void set_camera(const Camera& camera);
bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, bool unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
std::vector<Vec3f>* positions = nullptr, std::vector<Vec3f>* normals = nullptr) const; Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane = nullptr) const;
std::vector<unsigned> get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, std::vector<unsigned> get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera,
const std::vector<Vec3f>& points, std::function<bool(const Vec3f&)> fn_ignore_hit) const; const std::vector<Vec3f>& points, const ClippingPlane* clipping_plane = nullptr) const;
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const; Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;

View file

@ -1347,6 +1347,8 @@ struct Plater::priv
MenuWithSeparators part_menu; MenuWithSeparators part_menu;
// SLA-Object popup menu // SLA-Object popup menu
MenuWithSeparators sla_object_menu; MenuWithSeparators sla_object_menu;
// Default popup menu (when nothing is selected on 3DScene)
MenuWithSeparators default_menu;
// Removed/Prepended Items according to the view mode // Removed/Prepended Items according to the view mode
std::vector<wxMenuItem*> items_increase; std::vector<wxMenuItem*> items_increase;
@ -1886,7 +1888,7 @@ struct Plater::priv
void on_action_layersediting(SimpleEvent&); void on_action_layersediting(SimpleEvent&);
void on_object_select(SimpleEvent&); void on_object_select(SimpleEvent&);
void on_right_click(Vec2dEvent&); void on_right_click(RBtnEvent&);
void on_wipetower_moved(Vec3dEvent&); void on_wipetower_moved(Vec3dEvent&);
void on_wipetower_rotated(Vec3dEvent&); void on_wipetower_rotated(Vec3dEvent&);
void on_update_geometry(Vec3dsEvent<2>&); void on_update_geometry(Vec3dsEvent<2>&);
@ -2525,6 +2527,10 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type)
if (output_file.empty()) if (output_file.empty())
// Find the file name of the first printable object. // Find the file name of the first printable object.
output_file = this->model.propose_export_file_name_and_path(); output_file = this->model.propose_export_file_name_and_path();
if (output_file.empty() && !model.objects.empty())
// Find the file name of the first object.
output_file = this->model.objects[0]->get_export_filename();
} }
wxString dlg_title; wxString dlg_title;
@ -3323,7 +3329,7 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
this->statusbar()->set_progress(evt.status.percent); this->statusbar()->set_progress(evt.status.percent);
this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("")); this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8(""));
} }
if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE || PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) {
switch (this->printer_technology) { switch (this->printer_technology) {
case ptFFF: case ptFFF:
this->update_fff_scene(); this->update_fff_scene();
@ -3449,13 +3455,21 @@ void Plater::priv::on_object_select(SimpleEvent& evt)
selection_changed(); selection_changed();
} }
void Plater::priv::on_right_click(Vec2dEvent& evt) void Plater::priv::on_right_click(RBtnEvent& evt)
{ {
int obj_idx = get_selected_object_idx(); int obj_idx = get_selected_object_idx();
wxMenu* menu = nullptr;
if (obj_idx == -1) if (obj_idx == -1)
menu = &default_menu;
else
{
// If in 3DScene is(are) selected volume(s), but right button was clicked on empty space
if (evt.data.second)
return; return;
wxMenu* menu = printer_technology == ptSLA ? &sla_object_menu : menu = printer_technology == ptSLA ? &sla_object_menu :
get_selection().is_single_full_instance() ? // show "Object menu" for each FullInstance instead of FullObject get_selection().is_single_full_instance() ? // show "Object menu" for each FullInstance instead of FullObject
&object_menu : &part_menu; &object_menu : &part_menu;
@ -3492,14 +3506,15 @@ void Plater::priv::on_right_click(Vec2dEvent& evt)
} }
} }
} }
}
if (q != nullptr) { if (q != nullptr && menu) {
#ifdef __linux__ #ifdef __linux__
// For some reason on Linux the menu isn't displayed if position is specified // For some reason on Linux the menu isn't displayed if position is specified
// (even though the position is sane). // (even though the position is sane).
q->PopupMenu(menu); q->PopupMenu(menu);
#else #else
q->PopupMenu(menu, (int)evt.data.x(), (int)evt.data.y()); q->PopupMenu(menu, (int)evt.data.first.x(), (int)evt.data.first.y());
#endif #endif
} }
} }
@ -3551,12 +3566,14 @@ bool Plater::priv::init_object_menu()
init_common_menu(&part_menu, true); init_common_menu(&part_menu, true);
complit_init_part_menu(); complit_init_part_menu();
sidebar->obj_list()->create_default_popupmenu(&default_menu);
return true; return true;
} }
void Plater::priv::msw_rescale_object_menu() void Plater::priv::msw_rescale_object_menu()
{ {
for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu }) for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu })
msw_rescale_menu(dynamic_cast<wxMenu*>(menu)); msw_rescale_menu(dynamic_cast<wxMenu*>(menu));
} }
@ -4278,11 +4295,10 @@ void Plater::increase_instances(size_t num)
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num); sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
if (p->get_config("autocenter") == "1") { if (p->get_config("autocenter") == "1")
p->arrange(); p->arrange();
} else {
p->update(); p->update();
}
p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1); p->get_selection().add_instance(obj_idx, (int)model_object->instances.size() - 1);
@ -4326,14 +4342,14 @@ void Plater::set_number_of_copies(/*size_t num*/)
ModelObject* model_object = p->model.objects[obj_idx]; ModelObject* model_object = p->model.objects[obj_idx];
const auto num = wxGetNumberFromUser( " ", _("Enter the number of copies:"), const int num = wxGetNumberFromUser( " ", _("Enter the number of copies:"),
_("Copies of the selected object"), model_object->instances.size(), 0, 1000, this ); _("Copies of the selected object"), model_object->instances.size(), 0, 1000, this );
if (num < 0) if (num < 0)
return; return;
Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num)); Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num));
int diff = (int)num - (int)model_object->instances.size(); int diff = num - (int)model_object->instances.size();
if (diff > 0) if (diff > 0)
increase_instances(diff); increase_instances(diff);
else if (diff < 0) else if (diff < 0)
@ -4833,6 +4849,34 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
this->p->schedule_background_process(); this->p->schedule_background_process();
} }
void Plater::force_filament_colors_update()
{
bool update_scheduled = false;
DynamicPrintConfig* config = p->config;
const std::vector<std::string> filament_presets = wxGetApp().preset_bundle->filament_presets;
if (filament_presets.size() > 1 &&
p->config->option<ConfigOptionStrings>("filament_colour")->values.size() == filament_presets.size())
{
const PresetCollection& filaments = wxGetApp().preset_bundle->filaments;
std::vector<std::string> filament_colors;
filament_colors.reserve(filament_presets.size());
for (const std::string& filament_preset : filament_presets)
filament_colors.push_back(filaments.find_preset(filament_preset, true)->config.opt_string("filament_colour", (unsigned)0));
if (config->option<ConfigOptionStrings>("filament_colour")->values != filament_colors) {
config->option<ConfigOptionStrings>("filament_colour")->values = filament_colors;
update_scheduled = true;
}
}
if (update_scheduled)
update();
if (p->main_frame->is_loaded())
this->p->schedule_background_process();
}
void Plater::on_activate() void Plater::on_activate()
{ {
#ifdef __linux__ #ifdef __linux__
@ -4884,6 +4928,11 @@ GLCanvas3D* Plater::canvas3D()
return p->view3D->get_canvas3d(); return p->view3D->get_canvas3d();
} }
BoundingBoxf Plater::bed_shape_bb() const
{
return p->bed_shape_bb();
}
PrinterTechnology Plater::printer_technology() const PrinterTechnology Plater::printer_technology() const
{ {
return p->printer_technology; return p->printer_technology;

View file

@ -215,6 +215,7 @@ public:
void on_extruders_change(size_t extruders_count); void on_extruders_change(size_t extruders_count);
void on_config_change(const DynamicPrintConfig &config); void on_config_change(const DynamicPrintConfig &config);
void force_filament_colors_update();
// On activating the parent window. // On activating the parent window.
void on_activate(); void on_activate();
const DynamicPrintConfig* get_plater_config() const; const DynamicPrintConfig* get_plater_config() const;
@ -229,6 +230,7 @@ public:
int get_selected_object_idx(); int get_selected_object_idx();
bool is_single_full_object_selection() const; bool is_single_full_object_selection() const;
GLCanvas3D* canvas3D(); GLCanvas3D* canvas3D();
BoundingBoxf bed_shape_bb() const;
PrinterTechnology printer_technology() const; PrinterTechnology printer_technology() const;
void set_printer_technology(PrinterTechnology printer_technology); void set_printer_technology(PrinterTechnology printer_technology);

View file

@ -3032,6 +3032,12 @@ void Tab::save_preset(std::string name /*= ""*/)
if (m_type == Preset::TYPE_PRINTER) if (m_type == Preset::TYPE_PRINTER)
static_cast<TabPrinter*>(this)->m_initial_extruders_count = static_cast<TabPrinter*>(this)->m_extruders_count; static_cast<TabPrinter*>(this)->m_initial_extruders_count = static_cast<TabPrinter*>(this)->m_extruders_count;
update_changed_ui(); update_changed_ui();
/* If filament preset is saved for multi-material printer preset,
* there are cases when filament comboboxs are updated for old (non-modified) colors,
* but in full_config a filament_colors option aren't.*/
if (m_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
wxGetApp().plater()->force_filament_colors_update();
} }
// Called for a currently selected preset. // Called for a currently selected preset.

View file

@ -14,13 +14,17 @@
void clear(); void clear();
ExtrusionEntityCollection* chained_path(bool no_reverse, ExtrusionRole role = erMixed) ExtrusionEntityCollection* chained_path(bool no_reverse, ExtrusionRole role = erMixed)
%code{% %code{%
if (no_reverse)
croak("no_reverse must be false");
RETVAL = new ExtrusionEntityCollection(); RETVAL = new ExtrusionEntityCollection();
THIS->chained_path(RETVAL, no_reverse, role); *RETVAL = THIS->chained_path_from(THIS->entities.front()->first_point());
%}; %};
ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse, ExtrusionRole role = erMixed) ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse, ExtrusionRole role = erMixed)
%code{% %code{%
if (no_reverse)
croak("no_reverse must be false");
RETVAL = new ExtrusionEntityCollection(); RETVAL = new ExtrusionEntityCollection();
THIS->chained_path_from(*start_near, RETVAL, no_reverse, role); *RETVAL = THIS->chained_path_from(*start_near, role);
%}; %};
Clone<Point> first_point(); Clone<Point> first_point();
Clone<Point> last_point(); Clone<Point> last_point();
@ -31,13 +35,11 @@
ExtrusionEntityCollection* flatten() ExtrusionEntityCollection* flatten()
%code{% %code{%
RETVAL = new ExtrusionEntityCollection(); RETVAL = new ExtrusionEntityCollection();
THIS->flatten(RETVAL); *RETVAL = THIS->flatten();
%}; %};
double min_mm3_per_mm(); double min_mm3_per_mm();
bool empty() bool empty()
%code{% RETVAL = THIS->entities.empty(); %}; %code{% RETVAL = THIS->entities.empty(); %};
std::vector<size_t> orig_indices()
%code{% RETVAL = THIS->orig_indices; %};
Polygons polygons_covered_by_width(); Polygons polygons_covered_by_width();
Polygons polygons_covered_by_spacing(); Polygons polygons_covered_by_spacing();
%{ %{

View file

@ -3,7 +3,6 @@
%{ %{
#include <xsinit.h> #include <xsinit.h>
#include "libslic3r/Fill/Fill.hpp" #include "libslic3r/Fill/Fill.hpp"
#include "libslic3r/PolylineCollection.hpp"
#include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/ExtrusionEntityCollection.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp"
%} %}

View file

@ -3,6 +3,7 @@
%{ %{
#include <xsinit.h> #include <xsinit.h>
#include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry.hpp"
#include "libslic3r/ShortestPath.hpp"
%} %}
@ -49,7 +50,7 @@ std::vector<Points::size_type>
chained_path(points) chained_path(points)
Points points Points points
CODE: CODE:
Slic3r::Geometry::chained_path(points, RETVAL); RETVAL = chain_points(points);
OUTPUT: OUTPUT:
RETVAL RETVAL
@ -58,7 +59,7 @@ chained_path_from(points, start_from)
Points points Points points
Point* start_from Point* start_from
CODE: CODE:
Slic3r::Geometry::chained_path(points, RETVAL, *start_from); RETVAL = chain_points(points, start_from);
OUTPUT: OUTPUT:
RETVAL RETVAL

View file

@ -19,8 +19,6 @@
%code%{ RETVAL = &THIS->fill_surfaces; %}; %code%{ RETVAL = &THIS->fill_surfaces; %};
Polygons bridged() Polygons bridged()
%code%{ RETVAL = THIS->bridged; %}; %code%{ RETVAL = THIS->bridged; %};
Ref<PolylineCollection> unsupported_bridge_edges()
%code%{ RETVAL = &THIS->unsupported_bridge_edges; %};
Ref<ExtrusionEntityCollection> perimeters() Ref<ExtrusionEntityCollection> perimeters()
%code%{ RETVAL = &THIS->perimeters; %}; %code%{ RETVAL = &THIS->perimeters; %};
Ref<ExtrusionEntityCollection> fills() Ref<ExtrusionEntityCollection> fills()

View file

@ -2,7 +2,11 @@
%{ %{
#include <xsinit.h> #include <xsinit.h>
#include "libslic3r/PolylineCollection.hpp"
#include "libslic3r.h"
#include "Polyline.hpp"
#include "ShortestPath.hpp"
%} %}
%name{Slic3r::Polyline::Collection} class PolylineCollection { %name{Slic3r::Polyline::Collection} class PolylineCollection {
@ -14,16 +18,15 @@
PolylineCollection* chained_path(bool no_reverse) PolylineCollection* chained_path(bool no_reverse)
%code{% %code{%
RETVAL = new PolylineCollection(); RETVAL = new PolylineCollection();
THIS->chained_path(RETVAL, no_reverse); RETVAL->polylines = chain_polylines(THIS->polylines, &THIS->polylines.front().first_point());
%}; %};
PolylineCollection* chained_path_from(Point* start_near, bool no_reverse) PolylineCollection* chained_path_from(Point* start_near, bool no_reverse)
%code{% %code{%
RETVAL = new PolylineCollection(); RETVAL = new PolylineCollection();
THIS->chained_path_from(*start_near, RETVAL, no_reverse); RETVAL->polylines = chain_polylines(THIS->polylines, start_near);
%}; %};
int count() int count()
%code{% RETVAL = THIS->polylines.size(); %}; %code{% RETVAL = THIS->polylines.size(); %};
Clone<Point> leftmost_point();
%{ %{
PolylineCollection* PolylineCollection*