Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/xs/src
diff options
context:
space:
mode:
authortamasmeszaros <meszaros.q@gmail.com>2018-07-30 17:41:35 +0300
committertamasmeszaros <meszaros.q@gmail.com>2018-07-30 17:41:35 +0300
commit6cdec7ac9a43a3416180d3b61801387a554c3ae1 (patch)
treec1eda5f67d2f84101a25caa4d66aaebda9f7cca6 /xs/src
parentd136d61edded36ad36f17ee14b4b954a55d69db5 (diff)
parentbf4871d7f8b5090ec9ce47a9ba168c6d7c09b8ee (diff)
Prepare integration for arbitrary shaped print beds.
Diffstat (limited to 'xs/src')
-rw-r--r--xs/src/libnest2d/examples/main.cpp226
-rw-r--r--xs/src/libnest2d/libnest2d.h2
-rw-r--r--xs/src/libnest2d/libnest2d/libnest2d.hpp10
-rw-r--r--xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp2
-rw-r--r--xs/src/libslic3r/Model.cpp453
-rw-r--r--xs/src/libslic3r/Model.hpp3
-rw-r--r--xs/src/libslic3r/ModelArrange.hpp405
-rw-r--r--xs/src/libslic3r/libslic3r.h2
-rw-r--r--xs/src/slic3r/AppController.cpp4
-rw-r--r--xs/src/slic3r/GUI/GLCanvas3D.cpp31
10 files changed, 571 insertions, 567 deletions
diff --git a/xs/src/libnest2d/examples/main.cpp b/xs/src/libnest2d/examples/main.cpp
index 883d12610..d6b2ccc34 100644
--- a/xs/src/libnest2d/examples/main.cpp
+++ b/xs/src/libnest2d/examples/main.cpp
@@ -544,25 +544,25 @@ void arrangeRectangles() {
// input.insert(input.end(), proba.begin(), proba.end());
// input.insert(input.end(), crasher.begin(), crasher.end());
-// Box bin(250*SCALE, 210*SCALE);
- PolygonImpl bin = {
- {
- {25*SCALE, 0},
- {0, 25*SCALE},
- {0, 225*SCALE},
- {25*SCALE, 250*SCALE},
- {225*SCALE, 250*SCALE},
- {250*SCALE, 225*SCALE},
- {250*SCALE, 25*SCALE},
- {225*SCALE, 0},
- {25*SCALE, 0}
- },
- {}
- };
+ Box bin(250*SCALE, 210*SCALE);
+// PolygonImpl bin = {
+// {
+// {25*SCALE, 0},
+// {0, 25*SCALE},
+// {0, 225*SCALE},
+// {25*SCALE, 250*SCALE},
+// {225*SCALE, 250*SCALE},
+// {250*SCALE, 225*SCALE},
+// {250*SCALE, 25*SCALE},
+// {225*SCALE, 0},
+// {25*SCALE, 0}
+// },
+// {}
+// };
auto min_obj_distance = static_cast<Coord>(0*SCALE);
- using Placer = strategies::_NofitPolyPlacer<PolygonImpl, PolygonImpl>;
+ using Placer = strategies::_NofitPolyPlacer<PolygonImpl, Box>;
using Packer = Arranger<Placer, FirstFitSelection>;
Packer arrange(bin, min_obj_distance);
@@ -571,102 +571,102 @@ void arrangeRectangles() {
pconf.alignment = Placer::Config::Alignment::CENTER;
pconf.starting_point = Placer::Config::Alignment::CENTER;
pconf.rotations = {0.0/*, Pi/2.0, Pi, 3*Pi/2*/};
- pconf.accuracy = 1.0;
-
- auto bincenter = ShapeLike::boundingBox(bin).center();
- pconf.object_function = [&bin, bincenter](
- Placer::Pile pile, const Item& item,
- double /*area*/, double norm, double penality) {
-
- using pl = PointLike;
-
- static const double BIG_ITEM_TRESHOLD = 0.2;
- static const double GRAVITY_RATIO = 0.5;
- static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
-
- // We will treat big items (compared to the print bed) differently
- NfpPlacer::Pile bigs;
- bigs.reserve(pile.size());
- for(auto& p : pile) {
- auto pbb = ShapeLike::boundingBox(p);
- auto na = std::sqrt(pbb.width()*pbb.height())/norm;
- if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
- }
-
- // Candidate item bounding box
- auto ibb = item.boundingBox();
-
- // Calculate the full bounding box of the pile with the candidate item
- pile.emplace_back(item.transformedShape());
- auto fullbb = ShapeLike::boundingBox(pile);
- pile.pop_back();
-
- // The bounding box of the big items (they will accumulate in the center
- // of the pile
- auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
-
- // The size indicator of the candidate item. This is not the area,
- // but almost...
- auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
-
- // Will hold the resulting score
- double score = 0;
-
- if(itemnormarea > BIG_ITEM_TRESHOLD) {
- // This branch is for the bigger items..
- // Here we will use the closest point of the item bounding box to
- // the already arranged pile. So not the bb center nor the a choosen
- // corner but whichever is the closest to the center. This will
- // prevent unwanted strange arrangements.
-
- auto minc = ibb.minCorner(); // bottom left corner
- auto maxc = ibb.maxCorner(); // top right corner
-
- // top left and bottom right corners
- auto top_left = PointImpl{getX(minc), getY(maxc)};
- auto bottom_right = PointImpl{getX(maxc), getY(minc)};
-
- auto cc = fullbb.center(); // The gravity center
-
- // Now the distnce of the gravity center will be calculated to the
- // five anchor points and the smallest will be chosen.
- std::array<double, 5> dists;
- dists[0] = pl::distance(minc, cc);
- dists[1] = pl::distance(maxc, cc);
- dists[2] = pl::distance(ibb.center(), cc);
- dists[3] = pl::distance(top_left, cc);
- dists[4] = pl::distance(bottom_right, cc);
-
- auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
-
- // Density is the pack density: how big is the arranged pile
- auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
-
- // The score is a weighted sum of the distance from pile center
- // and the pile size
- score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
-
- } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
- // If there are no big items, only small, we should consider the
- // density here as well to not get silly results
- auto bindist = pl::distance(ibb.center(), bincenter) / norm;
- auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
- score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
- } else {
- // Here there are the small items that should be placed around the
- // already processed bigger items.
- // No need to play around with the anchor points, the center will be
- // just fine for small items
- score = pl::distance(ibb.center(), bigbb.center()) / norm;
- }
-
- // If it does not fit into the print bed we will beat it
- // with a large penality. If we would not do this, there would be only
- // one big pile that doesn't care whether it fits onto the print bed.
- if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
-
- return score;
- };
+ pconf.accuracy = 0.5f;
+
+// auto bincenter = ShapeLike::boundingBox(bin).center();
+// pconf.object_function = [&bin, bincenter](
+// Placer::Pile pile, const Item& item,
+// double /*area*/, double norm, double penality) {
+
+// using pl = PointLike;
+
+// static const double BIG_ITEM_TRESHOLD = 0.2;
+// static const double GRAVITY_RATIO = 0.5;
+// static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
+
+// // We will treat big items (compared to the print bed) differently
+// NfpPlacer::Pile bigs;
+// bigs.reserve(pile.size());
+// for(auto& p : pile) {
+// auto pbb = ShapeLike::boundingBox(p);
+// auto na = std::sqrt(pbb.width()*pbb.height())/norm;
+// if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
+// }
+
+// // Candidate item bounding box
+// auto ibb = item.boundingBox();
+
+// // Calculate the full bounding box of the pile with the candidate item
+// pile.emplace_back(item.transformedShape());
+// auto fullbb = ShapeLike::boundingBox(pile);
+// pile.pop_back();
+
+// // The bounding box of the big items (they will accumulate in the center
+// // of the pile
+// auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
+
+// // The size indicator of the candidate item. This is not the area,
+// // but almost...
+// auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
+
+// // Will hold the resulting score
+// double score = 0;
+
+// if(itemnormarea > BIG_ITEM_TRESHOLD) {
+// // This branch is for the bigger items..
+// // Here we will use the closest point of the item bounding box to
+// // the already arranged pile. So not the bb center nor the a choosen
+// // corner but whichever is the closest to the center. This will
+// // prevent unwanted strange arrangements.
+
+// auto minc = ibb.minCorner(); // bottom left corner
+// auto maxc = ibb.maxCorner(); // top right corner
+
+// // top left and bottom right corners
+// auto top_left = PointImpl{getX(minc), getY(maxc)};
+// auto bottom_right = PointImpl{getX(maxc), getY(minc)};
+
+// auto cc = fullbb.center(); // The gravity center
+
+// // Now the distnce of the gravity center will be calculated to the
+// // five anchor points and the smallest will be chosen.
+// std::array<double, 5> dists;
+// dists[0] = pl::distance(minc, cc);
+// dists[1] = pl::distance(maxc, cc);
+// dists[2] = pl::distance(ibb.center(), cc);
+// dists[3] = pl::distance(top_left, cc);
+// dists[4] = pl::distance(bottom_right, cc);
+
+// auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
+
+// // Density is the pack density: how big is the arranged pile
+// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
+
+// // The score is a weighted sum of the distance from pile center
+// // and the pile size
+// score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
+
+// } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
+// // If there are no big items, only small, we should consider the
+// // density here as well to not get silly results
+// auto bindist = pl::distance(ibb.center(), bincenter) / norm;
+// auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
+// score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
+// } else {
+// // Here there are the small items that should be placed around the
+// // already processed bigger items.
+// // No need to play around with the anchor points, the center will be
+// // just fine for small items
+// score = pl::distance(ibb.center(), bigbb.center()) / norm;
+// }
+
+// // If it does not fit into the print bed we will beat it
+// // with a large penality. If we would not do this, there would be only
+// // one big pile that doesn't care whether it fits onto the print bed.
+// if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
+
+// return score;
+// };
Packer::SelectionConfig sconf;
// sconf.allow_parallel = false;
@@ -707,7 +707,7 @@ void arrangeRectangles() {
std::vector<double> eff;
eff.reserve(result.size());
- auto bin_area = ShapeLike::area(bin);
+ auto bin_area = ShapeLike::area<PolygonImpl>(bin);
for(auto& r : result) {
double a = 0;
std::for_each(r.begin(), r.end(), [&a] (Item& e ){ a += e.area(); });
diff --git a/xs/src/libnest2d/libnest2d.h b/xs/src/libnest2d/libnest2d.h
index e0ad05c41..c9e21ecfb 100644
--- a/xs/src/libnest2d/libnest2d.h
+++ b/xs/src/libnest2d/libnest2d.h
@@ -6,7 +6,7 @@
#include <libnest2d/clipper_backend/clipper_backend.hpp>
// We include the stock optimizers for local and global optimization
-#include <libnest2d/optimizers/simplex.hpp> // Local simplex for NfpPlacer
+#include <libnest2d/optimizers/subplex.hpp> // Local subplex for NfpPlacer
#include <libnest2d/optimizers/genetic.hpp> // Genetic for min. bounding box
#include <libnest2d/libnest2d.hpp>
diff --git a/xs/src/libnest2d/libnest2d/libnest2d.hpp b/xs/src/libnest2d/libnest2d/libnest2d.hpp
index 1aa672447..fad38b9a3 100644
--- a/xs/src/libnest2d/libnest2d/libnest2d.hpp
+++ b/xs/src/libnest2d/libnest2d/libnest2d.hpp
@@ -53,8 +53,8 @@ class _Item {
enum class Convexity: char {
UNCHECKED,
- TRUE,
- FALSE
+ C_TRUE,
+ C_FALSE
};
mutable Convexity convexity_ = Convexity::UNCHECKED;
@@ -213,10 +213,10 @@ public:
switch(convexity_) {
case Convexity::UNCHECKED:
ret = sl::isConvex<RawShape>(sl::getContour(transformedShape()));
- convexity_ = ret? Convexity::TRUE : Convexity::FALSE;
+ convexity_ = ret? Convexity::C_TRUE : Convexity::C_FALSE;
break;
- case Convexity::TRUE: ret = true; break;
- case Convexity::FALSE:;
+ case Convexity::C_TRUE: ret = true; break;
+ case Convexity::C_FALSE:;
}
return ret;
diff --git a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp
index 6ae71bb48..06163b00a 100644
--- a/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp
+++ b/xs/src/libnest2d/libnest2d/placers/nfpplacer.hpp
@@ -625,7 +625,7 @@ public:
opt::StopCriteria stopcr;
stopcr.max_iterations = 1000;
stopcr.absolute_score_difference = 1e-20*norm_;
- opt::TOptimizer<opt::Method::L_SIMPLEX> solver(stopcr);
+ opt::TOptimizer<opt::Method::L_SUBPLEX> solver(stopcr);
Optimum optimum(0, 0);
double best_score = penality_;
diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp
index 5f9c7a7d4..24e34d75b 100644
--- a/xs/src/libslic3r/Model.cpp
+++ b/xs/src/libslic3r/Model.cpp
@@ -7,11 +7,6 @@
#include "Format/STL.hpp"
#include "Format/3mf.hpp"
-#include <numeric>
-#include <libnest2d.h>
-#include <ClipperUtils.hpp>
-#include "slic3r/GUI/GUI.hpp"
-
#include <float.h>
#include <boost/algorithm/string/predicate.hpp>
@@ -304,438 +299,36 @@ static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb
return result;
}
-namespace arr {
-
-using namespace libnest2d;
-
-std::string toString(const Model& model, bool holes = true) {
- std::stringstream ss;
-
- ss << "{\n";
-
- for(auto objptr : model.objects) {
- if(!objptr) continue;
-
- auto rmesh = objptr->raw_mesh();
-
- for(auto objinst : objptr->instances) {
- if(!objinst) continue;
-
- Slic3r::TriangleMesh tmpmesh = rmesh;
- tmpmesh.scale(objinst->scaling_factor);
- objinst->transform_mesh(&tmpmesh);
- ExPolygons expolys = tmpmesh.horizontal_projection();
- for(auto& expoly_complex : expolys) {
-
- auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
- if(tmp.empty()) continue;
- auto expoly = tmp.front();
- expoly.contour.make_clockwise();
- for(auto& h : expoly.holes) h.make_counter_clockwise();
-
- ss << "\t{\n";
- ss << "\t\t{\n";
-
- for(auto v : expoly.contour.points) ss << "\t\t\t{"
- << v.x << ", "
- << v.y << "},\n";
- {
- auto v = expoly.contour.points.front();
- ss << "\t\t\t{" << v.x << ", " << v.y << "},\n";
- }
- ss << "\t\t},\n";
-
- // Holes:
- ss << "\t\t{\n";
- if(holes) for(auto h : expoly.holes) {
- ss << "\t\t\t{\n";
- for(auto v : h.points) ss << "\t\t\t\t{"
- << v.x << ", "
- << v.y << "},\n";
- {
- auto v = h.points.front();
- ss << "\t\t\t\t{" << v.x << ", " << v.y << "},\n";
- }
- ss << "\t\t\t},\n";
- }
- ss << "\t\t},\n";
-
- ss << "\t},\n";
- }
- }
- }
-
- ss << "}\n";
-
- return ss.str();
-}
-
-void toSVG(SVG& svg, const Model& model) {
- for(auto objptr : model.objects) {
- if(!objptr) continue;
-
- auto rmesh = objptr->raw_mesh();
-
- for(auto objinst : objptr->instances) {
- if(!objinst) continue;
-
- Slic3r::TriangleMesh tmpmesh = rmesh;
- tmpmesh.scale(objinst->scaling_factor);
- objinst->transform_mesh(&tmpmesh);
- ExPolygons expolys = tmpmesh.horizontal_projection();
- svg.draw(expolys);
- }
- }
-}
-
-// A container which stores a pointer to the 3D object and its projected
-// 2D shape from top view.
-using ShapeData2D =
- std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
-
-ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
- ShapeData2D ret;
-
- auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
- [](size_t s, ModelObject* o){
- return s + o->instances.size();
- });
-
- ret.reserve(s);
-
- for(auto objptr : model.objects) {
- if(objptr) {
-
- auto rmesh = objptr->raw_mesh();
-
- for(auto objinst : objptr->instances) {
- if(objinst) {
- Slic3r::TriangleMesh tmpmesh = rmesh;
- ClipperLib::PolygonImpl pn;
-
- tmpmesh.scale(objinst->scaling_factor);
-
- // TODO export the exact 2D projection
- auto p = tmpmesh.convex_hull();
-
- p.make_clockwise();
- p.append(p.first_point());
- pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
-
- // Efficient conversion to item.
- Item item(std::move(pn));
-
- // Invalid geometries would throw exceptions when arranging
- if(item.vertexCount() > 3) {
- item.rotation(objinst->rotation);
- item.translation( {
- ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR),
- ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR)
- });
- ret.emplace_back(objinst, item);
- }
- }
- }
- }
- }
-
- return ret;
-}
-
-/**
- * \brief Arranges the model objects on the screen.
- *
- * The arrangement considers multiple bins (aka. print beds) for placing all
- * the items provided in the model argument. If the items don't fit on one
- * print bed, the remaining will be placed onto newly created print beds.
- * The first_bin_only parameter, if set to true, disables this behaviour and
- * makes sure that only one print bed is filled and the remaining items will be
- * untouched. When set to false, the items which could not fit onto the
- * print bed will be placed next to the print bed so the user should see a
- * pile of items on the print bed and some other piles outside the print
- * area that can be dragged later onto the print bed as a group.
- *
- * \param model The model object with the 3D content.
- * \param dist The minimum distance which is allowed for any pair of items
- * on the print bed in any direction.
- * \param bb The bounding box of the print bed. It corresponds to the 'bin'
- * for bin packing.
- * \param first_bin_only This parameter controls whether to place the
- * remaining items which do not fit onto the print area next to the print
- * bed or leave them untouched (let the user arrange them by hand or remove
- * them).
- */
-bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
- bool first_bin_only,
- std::function<void(unsigned)> progressind)
-{
- using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
-
- bool ret = true;
-
- // Create the arranger config
- auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
-
- // Get the 2D projected shapes with their 3D model instance pointers
- auto shapemap = arr::projectModelFromTop(model);
-
- bool hasbin = bb != nullptr && bb->defined;
- double area_max = 0;
-
- // Copy the references for the shapes only as the arranger expects a
- // sequence of objects convertible to Item or ClipperPolygon
- std::vector<std::reference_wrapper<Item>> shapes;
- shapes.reserve(shapemap.size());
- std::for_each(shapemap.begin(), shapemap.end(),
- [&shapes, min_obj_distance, &area_max, hasbin]
- (ShapeData2D::value_type& it)
- {
- shapes.push_back(std::ref(it.second));
- });
-
- Box bin;
-
- if(hasbin) {
- // Scale up the bounding box to clipper scale.
- BoundingBoxf bbb = *bb;
- bbb.scale(1.0/SCALING_FACTOR);
-
- bin = Box({
- static_cast<libnest2d::Coord>(bbb.min.x),
- static_cast<libnest2d::Coord>(bbb.min.y)
- },
- {
- static_cast<libnest2d::Coord>(bbb.max.x),
- static_cast<libnest2d::Coord>(bbb.max.y)
- });
- }
-
- // Will use the DJD selection heuristic with the BottomLeft placement
- // strategy
- using Arranger = Arranger<NfpPlacer, FirstFitSelection>;
- using PConf = Arranger::PlacementConfig;
- using SConf = Arranger::SelectionConfig;
-
- PConf pcfg; // Placement configuration
- SConf scfg; // Selection configuration
-
- // Align the arranged pile into the center of the bin
- pcfg.alignment = PConf::Alignment::CENTER;
-
- // Start placing the items from the center of the print bed
- pcfg.starting_point = PConf::Alignment::CENTER;
-
- // TODO cannot use rotations until multiple objects of same geometry can
- // handle different rotations
- // arranger.useMinimumBoundigBoxRotation();
- pcfg.rotations = { 0.0 };
-
- // The accuracy of optimization. Goes from 0.0 to 1.0 and scales performance
- pcfg.accuracy = 0.8;
-
- // Magic: we will specify what is the goal of arrangement... In this case
- // we override the default object function to make the larger items go into
- // the center of the pile and smaller items orbit it so the resulting pile
- // has a circle-like shape. This is good for the print bed's heat profile.
- // We alse sacrafice a bit of pack efficiency for this to work. As a side
- // effect, the arrange procedure is a lot faster (we do not need to
- // calculate the convex hulls)
- pcfg.object_function = [bin, hasbin](
- NfpPlacer::Pile& pile, // The currently arranged pile
- const Item &item,
- double /*area*/, // Sum area of items (not needed)
- double norm, // A norming factor for physical dimensions
- double penality) // Min penality in case of bad arrangement
- {
- using pl = PointLike;
-
- static const double BIG_ITEM_TRESHOLD = 0.2;
- static const double GRAVITY_RATIO = 0.5;
- static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
-
- // We will treat big items (compared to the print bed) differently
- NfpPlacer::Pile bigs;
- bigs.reserve(pile.size());
- for(auto& p : pile) {
- auto pbb = ShapeLike::boundingBox(p);
- auto na = std::sqrt(pbb.width()*pbb.height())/norm;
- if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
- }
-
- // Candidate item bounding box
- auto ibb = item.boundingBox();
-
- // Calculate the full bounding box of the pile with the candidate item
- pile.emplace_back(item.transformedShape());
- auto fullbb = ShapeLike::boundingBox(pile);
- pile.pop_back();
-
- // The bounding box of the big items (they will accumulate in the center
- // of the pile
- auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
-
- // The size indicator of the candidate item. This is not the area,
- // but almost...
- auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
-
- // Will hold the resulting score
- double score = 0;
-
- if(itemnormarea > BIG_ITEM_TRESHOLD) {
- // This branch is for the bigger items..
- // Here we will use the closest point of the item bounding box to
- // the already arranged pile. So not the bb center nor the a choosen
- // corner but whichever is the closest to the center. This will
- // prevent unwanted strange arrangements.
-
- auto minc = ibb.minCorner(); // bottom left corner
- auto maxc = ibb.maxCorner(); // top right corner
-
- // top left and bottom right corners
- auto top_left = PointImpl{getX(minc), getY(maxc)};
- auto bottom_right = PointImpl{getX(maxc), getY(minc)};
-
- auto cc = fullbb.center(); // The gravity center
-
- // Now the distnce of the gravity center will be calculated to the
- // five anchor points and the smallest will be chosen.
- std::array<double, 5> dists;
- dists[0] = pl::distance(minc, cc);
- dists[1] = pl::distance(maxc, cc);
- dists[2] = pl::distance(ibb.center(), cc);
- dists[3] = pl::distance(top_left, cc);
- dists[4] = pl::distance(bottom_right, cc);
-
- auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
-
- // Density is the pack density: how big is the arranged pile
- auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
-
- // The score is a weighted sum of the distance from pile center
- // and the pile size
- score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
-
- } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
- // If there are no big items, only small, we should consider the
- // density here as well to not get silly results
- auto bindist = pl::distance(ibb.center(), bin.center()) / norm;
- auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
- score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
- } else {
- // Here there are the small items that should be placed around the
- // already processed bigger items.
- // No need to play around with the anchor points, the center will be
- // just fine for small items
- score = pl::distance(ibb.center(), bigbb.center()) / norm;
- }
-
- // If it does not fit into the print bed we will beat it
- // with a large penality. If we would not do this, there would be only
- // one big pile that doesn't care whether it fits onto the print bed.
- if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
-
- return score;
- };
-
- // Create the arranger object
- Arranger arranger(bin, min_obj_distance, pcfg, scfg);
-
- // Set the progress indicator for the arranger.
- arranger.progressIndicator(progressind);
-
- // Arrange and return the items with their respective indices within the
- // input sequence.
- auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end());
-
- auto applyResult = [&shapemap](ArrangeResult::value_type& group,
- Coord batch_offset)
- {
- for(auto& r : group) {
- auto idx = r.first; // get the original item index
- Item& item = r.second; // get the item itself
-
- // Get the model instance from the shapemap using the index
- ModelInstance *inst_ptr = shapemap[idx].first;
-
- // Get the tranformation data from the item object and scale it
- // appropriately
- auto off = item.translation();
- Radians rot = item.rotation();
- Pointf foff(off.X*SCALING_FACTOR + batch_offset,
- off.Y*SCALING_FACTOR);
-
- // write the tranformation data into the model instance
- inst_ptr->rotation = rot;
- inst_ptr->offset = foff;
- }
- };
-
- if(first_bin_only) {
- applyResult(result.front(), 0);
- } else {
-
- const auto STRIDE_PADDING = 1.2;
-
- Coord stride = static_cast<Coord>(STRIDE_PADDING*
- bin.width()*SCALING_FACTOR);
- Coord batch_offset = 0;
-
- for(auto& group : result) {
- applyResult(group, batch_offset);
-
- // Only the first pack group can be placed onto the print bed. The
- // other objects which could not fit will be placed next to the
- // print bed
- batch_offset += stride;
- }
- }
-
- for(auto objptr : model.objects) objptr->invalidate_bounding_box();
-
- return ret && result.size() == 1;
-}
-}
-
/* arrange objects preserving their instance count
but altering their instance positions */
-bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb,
- std::function<void(unsigned)> progressind)
-{
- bool ret = false;
- if(bb != nullptr && bb->defined) {
- // Despite the new arrange is able to run without a specified bin,
- // the perl testsuit still fails for this case. For now the safest
- // thing to do is to use the new arrange only when a proper bin is
- // specified.
- ret = arr::arrange(*this, dist, bb, false, progressind);
- } else {
- // get the (transformed) size of each instance so that we take
- // into account their different transformations when packing
- Pointfs instance_sizes;
- Pointfs instance_centers;
- for (const ModelObject *o : this->objects)
- for (size_t i = 0; i < o->instances.size(); ++ i) {
- // an accurate snug bounding box around the transformed mesh.
- BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
- instance_sizes.push_back(bbox.size());
- instance_centers.push_back(bbox.center());
- }
+bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb)
+{
+ // get the (transformed) size of each instance so that we take
+ // into account their different transformations when packing
+ Pointfs instance_sizes;
+ Pointfs instance_centers;
+ for (const ModelObject *o : this->objects)
+ for (size_t i = 0; i < o->instances.size(); ++ i) {
+ // an accurate snug bounding box around the transformed mesh.
+ BoundingBoxf3 bbox(o->instance_bounding_box(i, true));
+ instance_sizes.push_back(bbox.size());
+ instance_centers.push_back(bbox.center());
+ }
- Pointfs positions;
- if (! _arrange(instance_sizes, dist, bb, positions))
- return false;
+ Pointfs positions;
+ if (! _arrange(instance_sizes, dist, bb, positions))
+ return false;
- size_t idx = 0;
- for (ModelObject *o : this->objects) {
- for (ModelInstance *i : o->instances) {
- i->offset = positions[idx] - instance_centers[idx];
- ++ idx;
- }
- o->invalidate_bounding_box();
+ size_t idx = 0;
+ for (ModelObject *o : this->objects) {
+ for (ModelInstance *i : o->instances) {
+ i->offset = positions[idx] - instance_centers[idx];
+ ++ idx;
}
+ o->invalidate_bounding_box();
}
- return ret;
+ return true;
}
// Duplicate the entire model preserving instance relative positions.
diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp
index f5e97fb6a..4c650f0de 100644
--- a/xs/src/libslic3r/Model.hpp
+++ b/xs/src/libslic3r/Model.hpp
@@ -290,8 +290,7 @@ public:
void center_instances_around_point(const Pointf &point);
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
TriangleMesh mesh() const;
- bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL,
- std::function<void(unsigned)> progressind = [](unsigned){});
+ bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL);
// Croaks if the duplicated objects do not fit the print bed.
void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL);
diff --git a/xs/src/libslic3r/ModelArrange.hpp b/xs/src/libslic3r/ModelArrange.hpp
new file mode 100644
index 000000000..af4bfcf70
--- /dev/null
+++ b/xs/src/libslic3r/ModelArrange.hpp
@@ -0,0 +1,405 @@
+#ifndef MODELARRANGE_HPP
+#define MODELARRANGE_HPP
+
+#include "Model.hpp"
+#include "SVG.hpp"
+#include <libnest2d.h>
+
+#include <numeric>
+#include <ClipperUtils.hpp>
+
+namespace Slic3r {
+namespace arr {
+
+using namespace libnest2d;
+
+std::string toString(const Model& model, bool holes = true) {
+ std::stringstream ss;
+
+ ss << "{\n";
+
+ for(auto objptr : model.objects) {
+ if(!objptr) continue;
+
+ auto rmesh = objptr->raw_mesh();
+
+ for(auto objinst : objptr->instances) {
+ if(!objinst) continue;
+
+ Slic3r::TriangleMesh tmpmesh = rmesh;
+ tmpmesh.scale(objinst->scaling_factor);
+ objinst->transform_mesh(&tmpmesh);
+ ExPolygons expolys = tmpmesh.horizontal_projection();
+ for(auto& expoly_complex : expolys) {
+
+ auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR);
+ if(tmp.empty()) continue;
+ auto expoly = tmp.front();
+ expoly.contour.make_clockwise();
+ for(auto& h : expoly.holes) h.make_counter_clockwise();
+
+ ss << "\t{\n";
+ ss << "\t\t{\n";
+
+ for(auto v : expoly.contour.points) ss << "\t\t\t{"
+ << v.x << ", "
+ << v.y << "},\n";
+ {
+ auto v = expoly.contour.points.front();
+ ss << "\t\t\t{" << v.x << ", " << v.y << "},\n";
+ }
+ ss << "\t\t},\n";
+
+ // Holes:
+ ss << "\t\t{\n";
+ if(holes) for(auto h : expoly.holes) {
+ ss << "\t\t\t{\n";
+ for(auto v : h.points) ss << "\t\t\t\t{"
+ << v.x << ", "
+ << v.y << "},\n";
+ {
+ auto v = h.points.front();
+ ss << "\t\t\t\t{" << v.x << ", " << v.y << "},\n";
+ }
+ ss << "\t\t\t},\n";
+ }
+ ss << "\t\t},\n";
+
+ ss << "\t},\n";
+ }
+ }
+ }
+
+ ss << "}\n";
+
+ return ss.str();
+}
+
+void toSVG(SVG& svg, const Model& model) {
+ for(auto objptr : model.objects) {
+ if(!objptr) continue;
+
+ auto rmesh = objptr->raw_mesh();
+
+ for(auto objinst : objptr->instances) {
+ if(!objinst) continue;
+
+ Slic3r::TriangleMesh tmpmesh = rmesh;
+ tmpmesh.scale(objinst->scaling_factor);
+ objinst->transform_mesh(&tmpmesh);
+ ExPolygons expolys = tmpmesh.horizontal_projection();
+ svg.draw(expolys);
+ }
+ }
+}
+
+// A container which stores a pointer to the 3D object and its projected
+// 2D shape from top view.
+using ShapeData2D =
+ std::vector<std::pair<Slic3r::ModelInstance*, Item>>;
+
+ShapeData2D projectModelFromTop(const Slic3r::Model &model) {
+ ShapeData2D ret;
+
+ auto s = std::accumulate(model.objects.begin(), model.objects.end(), 0,
+ [](size_t s, ModelObject* o){
+ return s + o->instances.size();
+ });
+
+ ret.reserve(s);
+
+ for(auto objptr : model.objects) {
+ if(objptr) {
+
+ auto rmesh = objptr->raw_mesh();
+
+ for(auto objinst : objptr->instances) {
+ if(objinst) {
+ Slic3r::TriangleMesh tmpmesh = rmesh;
+ ClipperLib::PolygonImpl pn;
+
+ tmpmesh.scale(objinst->scaling_factor);
+
+ // TODO export the exact 2D projection
+ auto p = tmpmesh.convex_hull();
+
+ p.make_clockwise();
+ p.append(p.first_point());
+ pn.Contour = Slic3rMultiPoint_to_ClipperPath( p );
+
+ // Efficient conversion to item.
+ Item item(std::move(pn));
+
+ // Invalid geometries would throw exceptions when arranging
+ if(item.vertexCount() > 3) {
+ item.rotation(objinst->rotation);
+ item.translation( {
+ ClipperLib::cInt(objinst->offset.x/SCALING_FACTOR),
+ ClipperLib::cInt(objinst->offset.y/SCALING_FACTOR)
+ });
+ ret.emplace_back(objinst, item);
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * \brief Arranges the model objects on the screen.
+ *
+ * The arrangement considers multiple bins (aka. print beds) for placing all
+ * the items provided in the model argument. If the items don't fit on one
+ * print bed, the remaining will be placed onto newly created print beds.
+ * The first_bin_only parameter, if set to true, disables this behaviour and
+ * makes sure that only one print bed is filled and the remaining items will be
+ * untouched. When set to false, the items which could not fit onto the
+ * print bed will be placed next to the print bed so the user should see a
+ * pile of items on the print bed and some other piles outside the print
+ * area that can be dragged later onto the print bed as a group.
+ *
+ * \param model The model object with the 3D content.
+ * \param dist The minimum distance which is allowed for any pair of items
+ * on the print bed in any direction.
+ * \param bb The bounding box of the print bed. It corresponds to the 'bin'
+ * for bin packing.
+ * \param first_bin_only This parameter controls whether to place the
+ * remaining items which do not fit onto the print area next to the print
+ * bed or leave them untouched (let the user arrange them by hand or remove
+ * them).
+ */
+bool arrange(Model &model, coordf_t dist, const Slic3r::BoundingBoxf* bb,
+ bool first_bin_only,
+ std::function<void(unsigned)> progressind)
+{
+ using ArrangeResult = _IndexedPackGroup<PolygonImpl>;
+
+ bool ret = true;
+
+ // Create the arranger config
+ auto min_obj_distance = static_cast<Coord>(dist/SCALING_FACTOR);
+
+ // Get the 2D projected shapes with their 3D model instance pointers
+ auto shapemap = arr::projectModelFromTop(model);
+
+ bool hasbin = bb != nullptr && bb->defined;
+ double area_max = 0;
+
+ // Copy the references for the shapes only as the arranger expects a
+ // sequence of objects convertible to Item or ClipperPolygon
+ std::vector<std::reference_wrapper<Item>> shapes;
+ shapes.reserve(shapemap.size());
+ std::for_each(shapemap.begin(), shapemap.end(),
+ [&shapes, min_obj_distance, &area_max, hasbin]
+ (ShapeData2D::value_type& it)
+ {
+ shapes.push_back(std::ref(it.second));
+ });
+
+ Box bin;
+
+ if(hasbin) {
+ // Scale up the bounding box to clipper scale.
+ BoundingBoxf bbb = *bb;
+ bbb.scale(1.0/SCALING_FACTOR);
+
+ bin = Box({
+ static_cast<libnest2d::Coord>(bbb.min.x),
+ static_cast<libnest2d::Coord>(bbb.min.y)
+ },
+ {
+ static_cast<libnest2d::Coord>(bbb.max.x),
+ static_cast<libnest2d::Coord>(bbb.max.y)
+ });
+ }
+
+ // Will use the DJD selection heuristic with the BottomLeft placement
+ // strategy
+ using Arranger = Arranger<NfpPlacer, FirstFitSelection>;
+ using PConf = Arranger::PlacementConfig;
+ using SConf = Arranger::SelectionConfig;
+
+ PConf pcfg; // Placement configuration
+ SConf scfg; // Selection configuration
+
+ // Align the arranged pile into the center of the bin
+ pcfg.alignment = PConf::Alignment::CENTER;
+
+ // Start placing the items from the center of the print bed
+ pcfg.starting_point = PConf::Alignment::CENTER;
+
+ // TODO cannot use rotations until multiple objects of same geometry can
+ // handle different rotations
+ // arranger.useMinimumBoundigBoxRotation();
+ pcfg.rotations = { 0.0 };
+
+ // The accuracy of optimization. Goes from 0.0 to 1.0 and scales performance
+ pcfg.accuracy = 0.4f;
+
+ // Magic: we will specify what is the goal of arrangement... In this case
+ // we override the default object function to make the larger items go into
+ // the center of the pile and smaller items orbit it so the resulting pile
+ // has a circle-like shape. This is good for the print bed's heat profile.
+ // We alse sacrafice a bit of pack efficiency for this to work. As a side
+ // effect, the arrange procedure is a lot faster (we do not need to
+ // calculate the convex hulls)
+ pcfg.object_function = [bin, hasbin](
+ NfpPlacer::Pile& pile, // The currently arranged pile
+ const Item &item,
+ double /*area*/, // Sum area of items (not needed)
+ double norm, // A norming factor for physical dimensions
+ double penality) // Min penality in case of bad arrangement
+ {
+ using pl = PointLike;
+
+ static const double BIG_ITEM_TRESHOLD = 0.2;
+ static const double GRAVITY_RATIO = 0.5;
+ static const double DENSITY_RATIO = 1.0 - GRAVITY_RATIO;
+
+ // We will treat big items (compared to the print bed) differently
+ NfpPlacer::Pile bigs;
+ bigs.reserve(pile.size());
+ for(auto& p : pile) {
+ auto pbb = ShapeLike::boundingBox(p);
+ auto na = std::sqrt(pbb.width()*pbb.height())/norm;
+ if(na > BIG_ITEM_TRESHOLD) bigs.emplace_back(p);
+ }
+
+ // Candidate item bounding box
+ auto ibb = item.boundingBox();
+
+ // Calculate the full bounding box of the pile with the candidate item
+ pile.emplace_back(item.transformedShape());
+ auto fullbb = ShapeLike::boundingBox(pile);
+ pile.pop_back();
+
+ // The bounding box of the big items (they will accumulate in the center
+ // of the pile
+ auto bigbb = bigs.empty()? fullbb : ShapeLike::boundingBox(bigs);
+
+ // The size indicator of the candidate item. This is not the area,
+ // but almost...
+ auto itemnormarea = std::sqrt(ibb.width()*ibb.height())/norm;
+
+ // Will hold the resulting score
+ double score = 0;
+
+ if(itemnormarea > BIG_ITEM_TRESHOLD) {
+ // This branch is for the bigger items..
+ // Here we will use the closest point of the item bounding box to
+ // the already arranged pile. So not the bb center nor the a choosen
+ // corner but whichever is the closest to the center. This will
+ // prevent unwanted strange arrangements.
+
+ auto minc = ibb.minCorner(); // bottom left corner
+ auto maxc = ibb.maxCorner(); // top right corner
+
+ // top left and bottom right corners
+ auto top_left = PointImpl{getX(minc), getY(maxc)};
+ auto bottom_right = PointImpl{getX(maxc), getY(minc)};
+
+ auto cc = fullbb.center(); // The gravity center
+
+ // Now the distnce of the gravity center will be calculated to the
+ // five anchor points and the smallest will be chosen.
+ std::array<double, 5> dists;
+ dists[0] = pl::distance(minc, cc);
+ dists[1] = pl::distance(maxc, cc);
+ dists[2] = pl::distance(ibb.center(), cc);
+ dists[3] = pl::distance(top_left, cc);
+ dists[4] = pl::distance(bottom_right, cc);
+
+ auto dist = *(std::min_element(dists.begin(), dists.end())) / norm;
+
+ // Density is the pack density: how big is the arranged pile
+ auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
+
+ // The score is a weighted sum of the distance from pile center
+ // and the pile size
+ score = GRAVITY_RATIO * dist + DENSITY_RATIO * density;
+
+ } else if(itemnormarea < BIG_ITEM_TRESHOLD && bigs.empty()) {
+ // If there are no big items, only small, we should consider the
+ // density here as well to not get silly results
+ auto bindist = pl::distance(ibb.center(), bin.center()) / norm;
+ auto density = std::sqrt(fullbb.width()*fullbb.height()) / norm;
+ score = GRAVITY_RATIO * bindist + DENSITY_RATIO * density;
+ } else {
+ // Here there are the small items that should be placed around the
+ // already processed bigger items.
+ // No need to play around with the anchor points, the center will be
+ // just fine for small items
+ score = pl::distance(ibb.center(), bigbb.center()) / norm;
+ }
+
+ // If it does not fit into the print bed we will beat it
+ // with a large penality. If we would not do this, there would be only
+ // one big pile that doesn't care whether it fits onto the print bed.
+ if(!NfpPlacer::wouldFit(fullbb, bin)) score = 2*penality - score;
+
+ return score;
+ };
+
+ // Create the arranger object
+ Arranger arranger(bin, min_obj_distance, pcfg, scfg);
+
+ // Set the progress indicator for the arranger.
+ arranger.progressIndicator(progressind);
+
+ // Arrange and return the items with their respective indices within the
+ // input sequence.
+ auto result = arranger.arrangeIndexed(shapes.begin(), shapes.end());
+
+ auto applyResult = [&shapemap](ArrangeResult::value_type& group,
+ Coord batch_offset)
+ {
+ for(auto& r : group) {
+ auto idx = r.first; // get the original item index
+ Item& item = r.second; // get the item itself
+
+ // Get the model instance from the shapemap using the index
+ ModelInstance *inst_ptr = shapemap[idx].first;
+
+ // Get the tranformation data from the item object and scale it
+ // appropriately
+ auto off = item.translation();
+ Radians rot = item.rotation();
+ Pointf foff(off.X*SCALING_FACTOR + batch_offset,
+ off.Y*SCALING_FACTOR);
+
+ // write the tranformation data into the model instance
+ inst_ptr->rotation = rot;
+ inst_ptr->offset = foff;
+ }
+ };
+
+ if(first_bin_only) {
+ applyResult(result.front(), 0);
+ } else {
+
+ const auto STRIDE_PADDING = 1.2;
+
+ Coord stride = static_cast<Coord>(STRIDE_PADDING*
+ bin.width()*SCALING_FACTOR);
+ Coord batch_offset = 0;
+
+ for(auto& group : result) {
+ applyResult(group, batch_offset);
+
+ // Only the first pack group can be placed onto the print bed. The
+ // other objects which could not fit will be placed next to the
+ // print bed
+ batch_offset += stride;
+ }
+ }
+
+ for(auto objptr : model.objects) objptr->invalidate_bounding_box();
+
+ return ret && result.size() == 1;
+}
+
+}
+}
+#endif // MODELARRANGE_HPP
diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h
index 77006cebe..2f07ca51a 100644
--- a/xs/src/libslic3r/libslic3r.h
+++ b/xs/src/libslic3r/libslic3r.h
@@ -14,7 +14,7 @@
#include <boost/thread.hpp>
#define SLIC3R_FORK_NAME "Slic3r Prusa Edition"
-#define SLIC3R_VERSION "1.41.0-alpha2"
+#define SLIC3R_VERSION "1.41.0-alpha3"
#define SLIC3R_BUILD "UNKNOWN"
typedef int32_t coord_t;
diff --git a/xs/src/slic3r/AppController.cpp b/xs/src/slic3r/AppController.cpp
index 151b7f880..1d4b7d545 100644
--- a/xs/src/slic3r/AppController.cpp
+++ b/xs/src/slic3r/AppController.cpp
@@ -8,6 +8,7 @@
#include <unordered_map>
#include <slic3r/GUI/GUI.hpp>
+#include <ModelArrange.hpp>
#include <slic3r/GUI/PresetBundle.hpp>
#include <Geometry.hpp>
@@ -310,12 +311,13 @@ void AppController::arrange_model()
auto dist = print_ctl()->config().min_object_distance();
+
BoundingBoxf bb(print_ctl()->config().bed_shape.values);
if(pind) pind->update(0, _(L("Arranging objects...")));
try {
- model_->arrange_objects(dist, &bb, [pind, count](unsigned rem){
+ arr::arrange(*model_, dist, &bb, false, [pind, count](unsigned rem){
if(pind) pind->update(count - rem, _(L("Arranging objects...")));
});
} catch(std::exception& e) {
diff --git a/xs/src/slic3r/GUI/GLCanvas3D.cpp b/xs/src/slic3r/GUI/GLCanvas3D.cpp
index 6396233e8..46bcf4f44 100644
--- a/xs/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/xs/src/slic3r/GUI/GLCanvas3D.cpp
@@ -2697,9 +2697,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
}
else if (evt.Leaving())
{
- // to remove hover when mouse goes out of this canvas
- m_mouse.position = Pointf((coordf_t)pos.x, (coordf_t)pos.y);
- render();
+ // to remove hover on objects when the mouse goes out of this canvas
+ m_mouse.position = Pointf(-1.0, -1.0);
+ m_dirty = true;
}
else if (evt.LeftDClick() && (m_hover_volume_id != -1))
m_on_double_click_callback.call();
@@ -3403,20 +3403,22 @@ void GLCanvas3D::_picking_pass() const
if (m_multisample_allowed)
::glEnable(GL_MULTISAMPLE);
- const Size& cnv_size = get_canvas_size();
-
- GLubyte color[4];
- ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color);
- int volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256;
-
- m_hover_volume_id = -1;
-
+ int volume_id = -1;
for (GLVolume* vol : m_volumes.volumes)
{
vol->hover = false;
}
- if (volume_id < (int)m_volumes.volumes.size())
+ GLubyte color[4] = { 0, 0, 0, 0 };
+ const Size& cnv_size = get_canvas_size();
+ bool inside = (0 <= pos.x) && (pos.x < cnv_size.get_width()) && (0 <= pos.y) && (pos.y < cnv_size.get_height());
+ if (inside)
+ {
+ ::glReadPixels(pos.x, cnv_size.get_height() - pos.y - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color);
+ volume_id = color[0] + color[1] * 256 + color[2] * 256 * 256;
+ }
+
+ if ((0 <= volume_id) && (volume_id < (int)m_volumes.volumes.size()))
{
m_hover_volume_id = volume_id;
m_volumes.volumes[volume_id]->hover = true;
@@ -3432,7 +3434,10 @@ void GLCanvas3D::_picking_pass() const
m_gizmos.set_hover_id(-1);
}
else
- m_gizmos.set_hover_id(254 - (int)color[2]);
+ {
+ m_hover_volume_id = -1;
+ m_gizmos.set_hover_id(inside ? (254 - (int)color[2]) : -1);
+ }
// updates gizmos overlay
if (_get_first_selected_object_id() != -1)