This commit is contained in:
bubnikv 2019-09-27 09:52:57 +02:00
commit 4b35ebe6e5
49 changed files with 360 additions and 347 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)0;
stl_type type; uint32_t number_of_facets = 0;
uint32_t number_of_facets; stl_vertex max = stl_vertex::Zero();
stl_vertex max; stl_vertex min = stl_vertex::Zero();
stl_vertex min; stl_vertex size = stl_vertex::Zero();
stl_vertex size; float bounding_diameter = 0.f;
float bounding_diameter; float shortest_edge = 0.f;
float shortest_edge; float volume = -1.f;
float volume; int connected_edges = 0;
int connected_edges; int connected_facets_1_edge = 0;
int connected_facets_1_edge; int connected_facets_2_edge = 0;
int connected_facets_2_edge; int connected_facets_3_edge = 0;
int connected_facets_3_edge; int facets_w_1_bad_edge = 0;
int facets_w_1_bad_edge; int facets_w_2_bad_edge = 0;
int facets_w_2_bad_edge; int facets_w_3_bad_edge = 0;
int facets_w_3_bad_edge; int original_num_facets = 0;
int original_num_facets; int edges_fixed = 0;
int edges_fixed; int degenerate_facets = 0;
int degenerate_facets; int facets_removed = 0;
int facets_removed; int facets_added = 0;
int facets_added; int facets_reversed = 0;
int facets_reversed; int backwards_edges = 0;
int backwards_edges; int normals_fixed = 0;
int normals_fixed; int number_of_parts = 0;
int number_of_parts;
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,8 +618,8 @@ 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;
@ -629,8 +629,10 @@ void arrange(ArrangePolygons & arrangables,
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

@ -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

@ -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

@ -359,7 +359,7 @@ std::string GCodePreviewData::get_legend_title() const
case Extrusion::Feedrate: case Extrusion::Feedrate:
return L("Speed (mm/s)"); return L("Speed (mm/s)");
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:

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

@ -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

@ -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

@ -1225,13 +1225,16 @@ bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater
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

@ -3389,11 +3389,10 @@ 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)
{ {

View file

@ -273,6 +273,8 @@ void ObjectList::create_objects_ctrl()
AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em, 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) if (wxOSX)
{ {
GetColumn(colName)->SetWidth(20*em); GetColumn(colName)->SetWidth(20*em);
@ -751,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)
@ -771,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__
@ -810,8 +812,15 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
*/ */
if (!item) { if (!item) {
if (wxOSX && col == nullptr) if (col == nullptr) {
if (wxOSX)
UnselectAll(); 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;
}
if (evt_context_menu) { if (evt_context_menu) {
show_context_menu(evt_context_menu); show_context_menu(evt_context_menu);
return; return;
@ -1330,7 +1339,7 @@ wxMenu* ObjectList::append_submenu_add_generic(wxMenu* menu, const ModelVolumeTy
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 && item == "Slab") if (type == ModelVolumeType::INVALID && strncmp(item, "Slab", 4) == 0)
continue; 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);
@ -1713,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,
@ -1851,9 +1860,9 @@ 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) void ObjectList::load_shape_object(const std::string& type_name)
@ -1892,6 +1901,9 @@ void ObjectList::load_shape_object(const std::string& type_name)
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
new_object->invalidate_bounding_box(); new_object->invalidate_bounding_box();
new_object->center_around_origin();
new_object->ensure_on_bed();
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb(); 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))); new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
@ -3677,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();

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,28 +391,16 @@ 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.
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is // The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
@ -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

@ -3322,7 +3322,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();
@ -4288,11 +4288,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);