#include "ArrangeJob.hpp" #include "libslic3r/MTUtils.hpp" #include "libslic3r/Model.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI.hpp" namespace Slic3r { namespace GUI { // Cache the wti info class WipeTower: public GLCanvas3D::WipeTowerInfo { using ArrangePolygon = arrangement::ArrangePolygon; public: explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti) : GLCanvas3D::WipeTowerInfo(wti) {} explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti) : GLCanvas3D::WipeTowerInfo(std::move(wti)) {} void apply_arrange_result(const Vec2d& tr, double rotation) { m_pos = unscaled(tr); m_rotation = rotation; apply_wipe_tower(); } ArrangePolygon get_arrange_polygon() const { Polygon ap({ {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)}, }); ArrangePolygon ret; ret.poly.contour = std::move(ap); ret.translation = scaled(m_pos); ret.rotation = m_rotation; ret.priority++; return ret; } }; static WipeTower get_wipe_tower(const Plater &plater) { return WipeTower{plater.canvas3D()->get_wipe_tower_info()}; } void ArrangeJob::clear_input() { const Model &model = m_plater->model(); size_t count = 0, cunprint = 0; // To know how much space to reserve for (auto obj : model.objects) for (auto mi : obj->instances) mi->printable ? count++ : cunprint++; m_selected.clear(); m_unselected.clear(); m_unprintable.clear(); m_selected.reserve(count + 1 /* for optional wti */); m_unselected.reserve(count + 1 /* for optional wti */); m_unprintable.reserve(cunprint /* for optional wti */); } void ArrangeJob::prepare_all() { clear_input(); for (ModelObject *obj: m_plater->model().objects) for (ModelInstance *mi : obj->instances) { ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; cont.emplace_back(get_arrange_poly(mi, m_plater)); } if (auto wti = get_wipe_tower(*m_plater)) m_selected.emplace_back(wti.get_arrange_polygon()); } void ArrangeJob::prepare_selected() { clear_input(); Model &model = m_plater->model(); double stride = bed_stride(m_plater); std::vector<const Selection::InstanceIdxsList *> obj_sel(model.objects.size(), nullptr); for (auto &s : m_plater->get_selection().get_content()) if (s.first < int(obj_sel.size())) obj_sel[size_t(s.first)] = &s.second; // Go through the objects and check if inside the selection for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; ModelObject *mo = model.objects[oidx]; std::vector<bool> inst_sel(mo->instances.size(), false); if (instlist) for (auto inst_id : *instlist) inst_sel[size_t(inst_id)] = true; for (size_t i = 0; i < inst_sel.size(); ++i) { ArrangePolygon &&ap = get_arrange_poly(mo->instances[i], m_plater); ArrangePolygons &cont = mo->instances[i]->printable ? (inst_sel[i] ? m_selected : m_unselected) : m_unprintable; cont.emplace_back(std::move(ap)); } } if (auto wti = get_wipe_tower(*m_plater)) { ArrangePolygon &&ap = get_arrange_poly(&wti, m_plater); m_plater->get_selection().is_wipe_tower() ? m_selected.emplace_back(std::move(ap)) : m_unselected.emplace_back(std::move(ap)); } // If the selection was empty arrange everything if (m_selected.empty()) m_selected.swap(m_unselected); // The strides have to be removed from the fixed items. For the // arrangeable (selected) items bed_idx is ignored and the // translation is irrelevant. for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; } void ArrangeJob::prepare() { wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); } void ArrangeJob::process() { static const auto arrangestr = _(L("Arranging")); GLCanvas3D::ArrangeSettings settings = m_plater->canvas3D()->get_arrange_settings(); arrangement::ArrangeParams params; params.min_obj_distance = scaled(settings.distance); params.allow_rotations = settings.enable_rotation; auto count = unsigned(m_selected.size() + m_unprintable.size()); Points bedpts = get_bed_shape(*m_plater->config()); params.stopcondition = [this]() { return was_canceled(); }; try { params.progressind = [this, count](unsigned st) { st += m_unprintable.size(); if (st > 0) update_status(int(count - st), arrangestr); }; arrangement::arrange(m_selected, m_unselected, bedpts, params); params.progressind = [this, count](unsigned st) { if (st > 0) update_status(int(count - st), arrangestr); }; arrangement::arrange(m_unprintable, {}, bedpts, params); } catch (std::exception & /*e*/) { GUI::show_error(m_plater, _(L("Could not arrange model objects! " "Some geometries may be invalid."))); } // finalize just here. update_status(int(count), was_canceled() ? _(L("Arranging canceled.")) : _(L("Arranging done."))); } void ArrangeJob::finalize() { // Ignore the arrange result if aborted. if (was_canceled()) return; // Unprintable items go to the last virtual bed int beds = 0; // Apply the arrange result to all selected objects for (ArrangePolygon &ap : m_selected) { beds = std::max(ap.bed_idx, beds); ap.apply(); } // Get the virtual beds from the unselected items for (ArrangePolygon &ap : m_unselected) beds = std::max(ap.bed_idx, beds); // Move the unprintable items to the last virtual bed. for (ArrangePolygon &ap : m_unprintable) { ap.bed_idx += beds + 1; ap.apply(); } m_plater->update(); Job::finalize(); } std::optional<arrangement::ArrangePolygon> get_wipe_tower_arrangepoly(const Plater &plater) { if (auto wti = get_wipe_tower(plater)) return wti.get_arrange_polygon(); return {}; } void apply_wipe_tower_arrangepoly(Plater & plater, const arrangement::ArrangePolygon &ap) { WipeTower{plater.canvas3D()->get_wipe_tower_info()} .apply_arrange_result(ap.translation.cast<double>(), ap.rotation); } double bed_stride(const Plater *plater) { double bedwidth = plater->bed_shape_bb().size().x(); return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth); } }} // namespace Slic3r::GUI