diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index ce26887f2..e7f7ed3e6 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -276,8 +276,7 @@ using EigenVec = Eigen::Matrix; // Semantics are the following: // Upscaling (scaled()): only from floating point types (or Vec) to either // floating point or integer 'scaled coord' coordinates. -// Downscaling (unscaled()): from arithmetic types (or Vec) to either -// floating point only +// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only // Conversion definition from unscaled to floating point scaled template> inline SLIC3R_CONSTEXPR Tout scaled(const Tin &v) SLIC3R_NOEXCEPT { - return static_cast(v / static_cast(SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); } // Conversion definition from unscaled to integer 'scaled coord'. -// TODO: is the rounding necessary ? Here it is to show that it can be different -// but it does not have to be. Using std::round means loosing noexcept and -// constexpr modifiers +// TODO: is the rounding necessary? Here it is commented out to show that +// it can be different for integers but it does not have to be. Using +// std::round means loosing noexcept and constexpr modifiers template> inline SLIC3R_CONSTEXPR ScaledCoordOnly scaled(const Tin &v) SLIC3R_NOEXCEPT { //return static_cast(std::round(v / SCALING_FACTOR)); - return static_cast(v / static_cast(SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); } // Conversion for Eigen vectors (N dimensional points) template> inline EigenVec, N> scaled(const EigenVec &v) { - return v.template cast() / SCALING_FACTOR; + return v.template cast() /*/ SCALING_FACTOR*/; } // Conversion from arithmetic scaled type to floating point unscaled @@ -314,7 +313,7 @@ template> inline SLIC3R_CONSTEXPR Tout unscaled(const Tin &v) SLIC3R_NOEXCEPT { - return static_cast(v * static_cast(SCALING_FACTOR)); + return Tout(v * Tout(SCALING_FACTOR)); } // Unscaling for Eigen vectors. Input base type can be arithmetic, output base diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 9d68f7141..6b88987df 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,5 +1,6 @@ #include "Model.hpp" #include "Geometry.hpp" +#include "MTUtils.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -1800,6 +1801,35 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } +Polygon ModelInstance::get_arrange_polygon() const +{ + static const double SIMPLIFY_TOLERANCE_MM = 0.1; + + assert(m_inst); + + Vec3d rotation = get_rotation(); + rotation.z() = 0.; + Transform3d trafo_instance = Geometry:: + assemble_transform(Vec3d::Zero(), + rotation, + get_scaling_factor(), + get_mirror()); + + Polygon p = get_object()->convex_hull_2d(trafo_instance); + + assert(!p.points.empty()); + + // this may happen for malformed models, see: + // https://github.com/prusa3d/PrusaSlicer/issues/2209 + if (p.points.empty()) return {}; + + Polygons pp{p}; + pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); + if (!pp.empty()) p = pp.front(); + + return p; +} + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 0fd1140f0..85a94b0fd 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -7,6 +7,7 @@ #include "Point.hpp" #include "TriangleMesh.hpp" #include "Slicing.hpp" +#include "ModelArrange.hpp" #include #include @@ -490,7 +491,7 @@ private: // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance : public ModelBase +class ModelInstance : public ModelBase, public arr::Arrangeable { public: enum EPrintVolumeState : unsigned char @@ -552,6 +553,16 @@ public: const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } bool is_printable() const { return print_volume_state == PVS_Inside; } + + virtual void set_arrange_result(Vec2d offs, double rot_rads) final + { + // write the transformation data into the model instance + set_rotation(Z, get_rotation(Z) + rot_rads); + set_offset(X, get_offset(X) + offs(X)); + set_offset(Y, get_offset(Y) + offs(Y)); + } + + virtual Polygon get_arrange_polygon() const final; protected: friend class Print; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index d50e03bf2..1fe7552b0 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -42,6 +42,8 @@ namespace arr { using namespace libnest2d; +using Shape = ClipperLib::Polygon; + // Only for debugging. Prints the model object vertices on stdout. //std::string toString(const Model& model, bool holes = true) { // std::stringstream ss; @@ -129,9 +131,7 @@ namespace bgi = boost::geometry::index; using SpatElement = std::pair; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>; -template -using TPacker = typename placers::_NofitPolyPlacer; +using ItemGroup = std::vector>>; const double BIG_ITEM_TRESHOLD = 0.02; @@ -156,10 +156,10 @@ Box boundingBox(const Box& pilebb, const Box& ibb ) { // at the same time, it has to provide reasonable results. std::tuple objfunc(const PointImpl& bincenter, - const TMultiShape& merged_pile, + const TMultiShape& merged_pile, const Box& pilebb, const ItemGroup& items, - const Item &item, + const _Item &item, double bin_area, double norm, // A norming factor for physical dimensions // a spatial index to quickly get neighbors of the candidate item @@ -225,7 +225,7 @@ objfunc(const PointImpl& bincenter, mp.emplace_back(item.transformedShape()); auto chull = sl::convexHull(mp); - placers::EdgeCache ec(chull); + placers::EdgeCache ec(chull); double circ = ec.circumference() / norm; double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; @@ -256,7 +256,7 @@ objfunc(const PointImpl& bincenter, for(auto& e : result) { // now get the score for the best alignment auto idx = e.second; - Item& p = items[idx]; + _Item& p = items[idx]; auto parea = p.area(); if(std::abs(1.0 - parea/item.area()) < 1e-6) { auto bb = boundingBox(p.boundingBox(), ibb); @@ -322,12 +322,12 @@ class _ArrBase { public: // Useful type shortcuts... - using Placer = TPacker; - using Selector = FirstFitSelection; + using Placer = typename placers::_NofitPolyPlacer; + using Selector = selections::_FirstFitSelection; using Packer = Nester; using PConfig = typename Packer::PlacementConfig; using Distance = TCoord; - using Pile = TMultiShape; + using Pile = TMultiShape; protected: @@ -373,7 +373,7 @@ public: }; for(unsigned idx = 0; idx < items.size(); ++idx) { - Item& itm = items[idx]; + _Item& itm = items[idx]; if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); m_smallsrtree.insert({itm.boundingBox(), idx}); } @@ -382,13 +382,13 @@ public: m_pck.progressIndicator(progressind); m_pck.stopCondition(stopcond); } - - template inline IndexedPackGroup operator()(Args&&...args) { + + template inline _PackGroup operator()(Args&&...args) { m_rtree.clear(); - return m_pck.executeIndexed(std::forward(args)...); + return m_pck.execute(std::forward(args)...); } - - inline void preload(const PackGroup& pg) { + + inline void preload(const _PackGroup& pg) { m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; m_pconf.object_function = nullptr; // drop the special objectfunction m_pck.preload(pg); @@ -396,14 +396,14 @@ public: // Build the rtree for queries to work for(const ItemGroup& grp : pg) for(unsigned idx = 0; idx < grp.size(); ++idx) { - Item& itm = grp[idx]; + _Item& itm = grp[idx]; m_rtree.insert({itm.boundingBox(), idx}); } m_pck.configure(m_pconf); } - bool is_colliding(const Item& item) { + bool is_colliding(const _Item& item) { if(m_rtree.empty()) return false; std::vector result; m_rtree.query(bgi::intersects(item.boundingBox()), @@ -425,7 +425,7 @@ public: // Here we set up the actual object function that calls the common // object function for all bin shapes than does an additional inside // check for the arranged pile. - m_pconf.object_function = [this, bin] (const Item &item) { + m_pconf.object_function = [this, bin] (const _Item &item) { auto result = objfunc(bin.center(), m_merged_pile, @@ -468,7 +468,7 @@ public: _ArrBase(bin, dist, progressind, stopcond) { // As with the box, only the inside check is different. - m_pconf.object_function = [this, &bin] (const Item &item) { + m_pconf.object_function = [this, &bin] (const _Item &item) { auto result = objfunc(bin.center(), m_merged_pile, @@ -483,7 +483,7 @@ public: double score = std::get<0>(result); - auto isBig = [this](const Item& itm) { + auto isBig = [this](const _Item& itm) { return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; }; @@ -512,7 +512,7 @@ public: std::function stopcond = [](){return false;}): _ArrBase(bin, dist, progressind, stopcond) { - m_pconf.object_function = [this, &bin] (const Item &item) { + m_pconf.object_function = [this, &bin] (const _Item &item) { auto binbb = sl::boundingBox(bin); auto result = objfunc(binbb.center(), @@ -540,11 +540,11 @@ public: template<> class AutoArranger: public _ArrBase { public: - AutoArranger(Distance dist, std::function progressind, + AutoArranger(bool, Distance dist, std::function progressind, std::function stopcond): _ArrBase(Box(0, 0), dist, progressind, stopcond) { - this->m_pconf.object_function = [this] (const Item &item) { + this->m_pconf.object_function = [this] (const _Item &item) { auto result = objfunc({0, 0}, m_merged_pile, @@ -782,18 +782,18 @@ BedShapeHint bedShape(const Polyline &bed) { return ret; } -static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; +//static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; -//template -//IndexedPackGroup _arrange(std::vector> &shapes, -// const BinT & bin, -// coord_t minobjd, -// std::function prind, -// std::function stopfn) -//{ -// AutoArranger arranger{bin, minobjd, prind, stopfn}; -// return arranger(shapes.begin(), shapes.end()); -//} +template +_PackGroup _arrange(std::vector &shapes, + const BinT & bin, + coord_t minobjd, + std::function prind, + std::function stopfn) +{ + AutoArranger arranger{bin, minobjd, prind, stopfn}; + return arranger(shapes.begin(), shapes.end()); +} //template //IndexedPackGroup _arrange(std::vector> &shapes, @@ -845,11 +845,99 @@ static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; // return arranger(shapes.begin(), shapes.end()); //} -inline SLIC3R_CONSTEXPR libnest2d::Coord stride_padding(Coord w) +inline SLIC3R_CONSTEXPR coord_t stride_padding(coord_t w) { return w + w / 5; } +bool arrange(ArrangeableRefs & arrangables, + coord_t min_obj_distance, + BedShapeHint bedhint, + std::function progressind, + std::function stopcondition) +{ + bool ret = true; + + std::vector shapes; + shapes.reserve(arrangables.size()); + size_t id = 0; + for (Arrangeable &iref : arrangables) { + Polygon p = iref.get_arrange_polygon(); + + p.reverse(); + assert(!p.is_counter_clockwise()); + + Shape clpath(/*id++,*/ Slic3rMultiPoint_to_ClipperPath(p)); + + auto firstp = clpath.Contour.front(); clpath.Contour.emplace_back(firstp); + shapes.emplace_back(std::move(clpath)); + } + + _PackGroup result; + + auto& cfn = stopcondition; + + // Integer ceiling the min distance from the bed perimeters + coord_t md = min_obj_distance - SCALED_EPSILON; + md = (md % 2) ? md / 2 + 1 : md / 2; + coord_t binwidth = 0; + + switch (bedhint.type) { + case BedShapeType::BOX: { + // Create the arranger for the box shaped bed + BoundingBox bbb = bedhint.shape.box; + + auto binbb = Box({ClipperLib::cInt{bbb.min(0)} - md, + ClipperLib::cInt{bbb.min(1)} - md}, + {ClipperLib::cInt{bbb.max(0)} + md, + ClipperLib::cInt{bbb.max(1)} + md}); + + result = _arrange(shapes, binbb, min_obj_distance, progressind, cfn); + binwidth = coord_t(binbb.width()); + break; + } + case BedShapeType::CIRCLE: { + auto c = bedhint.shape.circ; + auto cc = to_lnCircle(c); + result = _arrange(shapes, cc, min_obj_distance, progressind, cfn); + binwidth = scaled(c.radius()); + break; + } + case BedShapeType::IRREGULAR: { + auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.shape.polygon); + ClipperLib::Polygon irrbed = sl::create(std::move(ctour)); + result = _arrange(shapes, irrbed, min_obj_distance, progressind, cfn); + BoundingBox polybb(bedhint.shape.polygon); + binwidth = (polybb.max(X) - polybb.min(X)); + break; + } + case BedShapeType::WHO_KNOWS: { + result = _arrange(shapes, false, min_obj_distance, progressind, cfn); + break; + } + }; + + if(result.empty() || stopcondition()) return false; + + ClipperLib::cInt stride = stride_padding(binwidth); + ClipperLib::cInt batch_offset = 0; + + for (const auto &group : result) { + for (_Item &itm : group) { + ClipperLib::IntPoint offs = itm.translation(); +// arrangables[itm.id()].get().set_arrange_result({offs.X, offs.Y}, +// itm.rotation()); + } + + // 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; + } + + return ret; +} + //// The final client function to arrange the Model. A progress indicator and //// a stop predicate can be also be passed to control the process. //bool arrange(Model &model, // The model with the geometries diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index cccf1bd57..77b357f4e 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -32,8 +32,8 @@ enum class BedShapeType { }; struct BedShapeHint { - BedShapeType type; - /*union*/ struct { // I know but who cares... + BedShapeType type = BedShapeType::WHO_KNOWS; + /*union*/ struct { // I know but who cares... TODO: use variant from cpp17? Circle circ; BoundingBox box; Polyline polygon; @@ -42,24 +42,17 @@ struct BedShapeHint { BedShapeHint bedShape(const Polyline& bed); -class ArrangeItem { +class Arrangeable { public: - virtual ~ArrangeItem() = default; + virtual ~Arrangeable() = default; - virtual void transform(Vec2d offset, double rotation_rads) = 0; + virtual void set_arrange_result(Vec2d offset, double rotation_rads) = 0; - virtual Polygon silhouette() const = 0; + virtual Polygon get_arrange_polygon() const = 0; }; -using ArrangeItems = std::vector>; - -//struct WipeTowerInfo { -// bool is_wipe_tower = false; -// Vec2d pos; -// Vec2d bb_size; -// double rotation; -//}; +using ArrangeableRefs = std::vector>; /** * \brief Arranges the model objects on the screen. @@ -96,7 +89,7 @@ using ArrangeItems = std::vector>; // std::function progressind, // std::function stopcondition); -bool arrange(ArrangeItems &items, +bool arrange(ArrangeableRefs &items, coord_t min_obj_distance, BedShapeHint bedhint, std::function progressind, @@ -109,8 +102,8 @@ bool arrange(ArrangeItems &items, // coord_t min_obj_distance, // const Slic3r::Polyline& bed, // WipeTowerInfo& wti); -void find_new_position(ArrangeItems &items, - const ArrangeItems &instances_to_add, +void find_new_position(ArrangeableRefs &items, + const ArrangeableRefs &instances_to_add, coord_t min_obj_distance, BedShapeHint bedhint); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index bed3b754b..bf892fe68 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3329,36 +3329,24 @@ void GLCanvas3D::update_ui_from_settings() -arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const +GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const { - arr::WipeTowerInfo wti; + WipeTowerInfo wti; + for (const GLVolume* vol : m_volumes.volumes) { if (vol->is_wipe_tower) { - wti.is_wipe_tower = true; - wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), + wti.m_pos = Vec2d(m_config->opt_float("wipe_tower_x"), m_config->opt_float("wipe_tower_y")); - wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); + wti.m_rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); const BoundingBoxf3& bb = vol->bounding_box; - wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); + wti.m_bb_size = Vec2d(bb.size().x(), bb.size().y()); break; } } + return wti; } - -void GLCanvas3D::arrange_wipe_tower(const arr::WipeTowerInfo& wti) const -{ - if (wti.is_wipe_tower) { - DynamicPrintConfig cfg; - cfg.opt("wipe_tower_x", true)->value = wti.pos(0); - cfg.opt("wipe_tower_y", true)->value = wti.pos(1); - cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * wti.rotation; - wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); - } -} - - Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos) { float z0 = 0.0f; @@ -5751,5 +5739,16 @@ const SLAPrint* GLCanvas3D::sla_print() const return (m_process == nullptr) ? nullptr : m_process->sla_print(); } +void GLCanvas3D::WipeTowerInfo::set_arrange_result(Vec2d offset, double rotation_rads) +{ + m_pos += offset; + m_rotation += rotation_rads; + DynamicPrintConfig cfg; + cfg.opt("wipe_tower_x", true)->value = m_pos(X); + cfg.opt("wipe_tower_y", true)->value = m_pos(Y); + cfg.opt("wipe_tower_rotation_angle", true)->value = (180./M_PI) * m_rotation; + wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 5a4287903..1872d2f37 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -611,9 +611,38 @@ public: int get_move_volume_id() const { return m_mouse.drag.move_volume_idx; } int get_first_hover_volume_idx() const { return m_hover_volume_idxs.empty() ? -1 : m_hover_volume_idxs.front(); } + + class WipeTowerInfo: public arr::Arrangeable { + Vec2d m_pos = {std::nan(""), std::nan("")}; + Vec2d m_bb_size; + double m_rotation; + friend class GLCanvas3D; + public: + + inline operator bool() const + { + return std::isnan(m_pos.x()) || std::isnan(m_pos.y()); + } - arr::WipeTowerInfo get_wipe_tower_info() const; - void arrange_wipe_tower(const arr::WipeTowerInfo& wti) const; + virtual void set_arrange_result(Vec2d offset, double rotation_rads) final; + + virtual Polygon get_arrange_polygon() const final + { + Polygon p({ + {coord_t(0), coord_t(0)}, + {scaled(m_bb_size(X)), coord_t(0)}, + {scaled(m_bb_size)}, + {coord_t(0), scaled(m_bb_size(Y))}, + {coord_t(0), coord_t(0)}, + }); + + p.rotate(m_rotation); + p.translate(scaled(m_pos)); + return p; + } + }; + + WipeTowerInfo get_wipe_tower_info() const; // Returns the view ray line, in world coordinate, at the given mouse position. Linef3 mouse_ray(const Point& mouse_pos); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b4cfa3368..b4afdfe67 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1424,22 +1424,25 @@ struct Plater::priv priv * m_plater; class ArrangeJob : public Job - { - int count = 0; - + { + int m_count = 0; + GLCanvas3D::WipeTowerInfo m_wti; protected: + void prepare() override { - count = 0; + m_wti = plater().view3D->get_canvas3d()->get_wipe_tower_info(); + m_count = bool(m_wti); + for (auto obj : plater().model.objects) - count += int(obj->instances.size()); + m_count += int(obj->instances.size()); } public: //using Job::Job; ArrangeJob(priv * pltr): Job(pltr) {} - int status_range() const override { return count; } - void set_count(int c) { count = c; } + int status_range() const override { return m_count; } + void set_count(int c) { m_count = c; } void process() override; } arrange_job/*{m_plater}*/; @@ -1525,6 +1528,7 @@ struct Plater::priv std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; + arr::BedShapeHint get_bed_shape_hint() const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -2171,9 +2175,9 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode auto& bedpoints = bed_shape_opt->values; Polyline bed; bed.points.reserve(bedpoints.size()); for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); - - arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info(); - + + std::pair wti = view3D->get_canvas3d()->get_wipe_tower_info(); + arr::find_new_position(model, new_instances, min_obj_distance, bed, wti); // it remains to move the wipe tower: @@ -2400,61 +2404,60 @@ void Plater::priv::sla_optimize_rotation() { m_ui_jobs.start(Jobs::Rotoptimize); } -void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { - static const SLIC3R_CONSTEXPR double SIMPLIFY_TOLERANCE_MM = 0.1; +arr::BedShapeHint Plater::priv::get_bed_shape_hint() const { + arr::BedShapeHint bedshape; - class ArrItemModelInstance: public arr::ArrangeItem { - ModelInstance *m_inst = nullptr; - public: - - ArrItemModelInstance() = default; - ArrItemModelInstance(ModelInstance *inst) : m_inst(inst) {} - - virtual void transform(Vec2d offs, double rot_rads) override { - assert(m_inst); - - // write the transformation data into the model instance - m_inst->set_rotation(Z, rot_rads); - m_inst->set_offset(offs); - } - - virtual Polygon silhouette() const override { - assert(m_inst); - - Vec3d rotation = m_inst->get_rotation(); - rotation.z() = 0.; - Transform3d trafo_instance = Geometry::assemble_transform( - Vec3d::Zero(), - rotation, - m_inst->get_scaling_factor(), - m_inst->get_mirror()); - - Polygon p = m_inst->get_object()->convex_hull_2d(trafo_instance); + const auto *bed_shape_opt = config->opt("bed_shape"); + assert(bed_shape_opt); + + if (bed_shape_opt) { + auto &bedpoints = bed_shape_opt->values; + Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); + for (auto &v : bedpoints) bedpoly.append(scaled(v)); + bedshape = arr::bedShape(bedpoly); + } + + return bedshape; +} - assert(!p.points.empty()); +void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() { + static const auto arrangestr = _(L("Arranging")); + + arr::ArrangeableRefs arrangeinput; arrangeinput.reserve(m_count); + for(ModelObject *mo : plater().model.objects) + for(ModelInstance *minst : mo->instances) + arrangeinput.emplace_back(std::ref(*minst)); + + // FIXME: I don't know how to obtain the minimum distance, it depends + // on printer technology. I guess the following should work but it crashes. + double dist = 6; // PrintConfig::min_object_distance(config); + if (plater().printer_technology == ptFFF) { + dist = PrintConfig::min_object_distance(plater().config); + } + + coord_t min_obj_distance = scaled(dist); + + arr::BedShapeHint bedshape = plater().get_bed_shape_hint(); + + try { + arr::arrange(arrangeinput, + min_obj_distance, + bedshape, + [this](unsigned st) { + if (st > 0) + update_status(m_count - int(st), arrangestr); + }, + [this]() { return was_canceled(); }); + } catch (std::exception & /*e*/) { + GUI::show_error(plater().q, + _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); + } + + update_status(m_count, + was_canceled() ? _(L("Arranging canceled.")) + : _(L("Arranging done."))); - // this may happen for malformed models, see: - // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (p.points.empty()) return {}; - - Polygons pp { p }; - pp = p.simplify(scaled(SIMPLIFY_TOLERANCE_MM)); - if (!pp.empty()) p = pp.front(); - - return p; - } - }; - - // Count all the items on the bin (all the object's instances) - auto count = std::accumulate(plater().model.objects.begin(), - plater().model.objects.end(), - size_t(0), [](size_t s, ModelObject* o) - { - return s + o->instances.size(); - }); - -// std::vector items(size_t); - // TODO: we should decide whether to allow arrange when the search is // running we should probably disable explicit slicing and background // processing