diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index e200b7edb..bed1e5536 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -85,7 +85,13 @@ template void fill_config(PConf& pcfg, const ArrangeParams ¶ms) { // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; + switch (params.alignment) { + case Pivots::Center: pcfg.alignment = PConf::Alignment::CENTER; break; + case Pivots::BottomLeft: pcfg.alignment = PConf::Alignment::BOTTOM_LEFT; break; + case Pivots::BottomRight: pcfg.alignment = PConf::Alignment::BOTTOM_RIGHT; break; + case Pivots::TopLeft: pcfg.alignment = PConf::Alignment::TOP_LEFT; break; + case Pivots::TopRight: pcfg.alignment = PConf::Alignment::TOP_RIGHT; break; + } // Start placing the items from the center of the print bed pcfg.starting_point = PConf::Alignment::CENTER; @@ -593,23 +599,27 @@ template auto call_with_bed(const Points &bed, Fn &&fn) auto parea = poly_area(bed); if ((1.0 - parea / area(bb)) < 1e-3) - return fn(bb); + return fn(RectangleBed{bb}); else if (!std::isnan(circ.radius())) return fn(circ); else - return fn(Polygon(bed)); + return fn(IrregularBed{ExPolygon(bed)}); } } +bool is_box(const Points &bed) +{ + return !bed.empty() && + ((1.0 - poly_area(bed) / area(BoundingBox(bed))) < 1e-3); +} + template<> void arrange(ArrangePolygons & items, const ArrangePolygons &excludes, const Points & bed, const ArrangeParams & params) { - call_with_bed(bed, [&](const auto &bin) { - arrange(items, excludes, bin, params); - }); + arrange(items, excludes, to_arrange_bed(bed), params); } template @@ -649,5 +659,85 @@ template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, c template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms); template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms); +ArrangeBed to_arrange_bed(const Points &bedpts) +{ + ArrangeBed ret; + + call_with_bed(bedpts, [&](const auto &bed) { + ret = bed; + }); + + return ret; +} + +void arrange(ArrangePolygons &items, + const ArrangePolygons &excludes, + const SegmentedRectangleBed &bed, + const ArrangeParams ¶ms) +{ + arrange(items, excludes, bed.bb, params); + + if (! excludes.empty()) + return; + + auto it = std::max_element(items.begin(), items.end(), + [](auto &i1, auto &i2) { + return i1.bed_idx < i2.bed_idx; + }); + + size_t beds = 0; + if (it != items.end()) + beds = it->bed_idx + 1; + + std::vector pilebb(beds); + + for (auto &itm : items) { + if (itm.bed_idx >= 0) + pilebb[itm.bed_idx].merge(get_extents(itm.transformed_poly())); + } + + auto piecesz = unscaled(bed.bb).size(); + piecesz.x() /= bed.segments.x(); + piecesz.y() /= bed.segments.y(); + + for (size_t bedidx = 0; bedidx < beds; ++bedidx) { + BoundingBox bb; + auto pilesz = unscaled(pilebb[bedidx]).size(); + bb.max.x() = scaled(std::ceil(pilesz.x() / piecesz.x()) * piecesz.x()); + bb.max.y() = scaled(std::ceil(pilesz.y() / piecesz.y()) * piecesz.y()); + coord_t offs = params.min_bed_distance; + switch (params.alignment) { + case Pivots::BottomLeft: + bb.translate((bed.bb.min - bb.min) + Point{offs, offs}); + break; + case Pivots::TopRight: + bb.translate((bed.bb.max - bb.max) - Point{offs, offs}); + break; + case Pivots::BottomRight: { + Point bedref{bed.bb.max.x(), bed.bb.min.y()}; + Point bbref {bb.max.x(), bb.min.y()}; + bb.translate((bedref - bbref) + Point{-offs, offs}); + break; + } + case Pivots::TopLeft: { + Point bedref{bed.bb.min.x(), bed.bb.max.y()}; + Point bbref {bb.min.x(), bb.max.y()}; + bb.translate((bedref - bbref) + Point{offs, -offs}); + break; + } + case Pivots::Center: { + bb.translate(bed.bb.center() - bb.center()); + break; + } + } + + Vec2crd d = bb.center() - pilebb[bedidx].center(); + + for (auto &itm : items) + if (itm.bed_idx == bedidx) + itm.translation += d; + } +} + } // namespace arr } // namespace Slic3r diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 02d05e3e4..6d4001a50 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -3,12 +3,25 @@ #include "ExPolygon.hpp" +#include +#include + namespace Slic3r { class BoundingBox; namespace arrangement { +/// Representing an unbounded bed. +struct InfiniteBed { + Point center; + explicit InfiniteBed(const Point &p = {0, 0}): center{p} {} +}; + +struct RectangleBed { + BoundingBox bb; +}; + /// A geometry abstraction for a circular print bed. Similarly to BoundingBox. class CircleBed { Point center_; @@ -22,12 +35,28 @@ public: inline const Point& center() const { return center_; } }; -/// Representing an unbounded bed. -struct InfiniteBed { - Point center; - explicit InfiniteBed(const Point &p = {0, 0}): center{p} {} +struct SegmentedRectangleBed { + Vec<2, size_t> segments; + BoundingBox bb; + + SegmentedRectangleBed (const BoundingBox &bb, + size_t segments_x, + size_t segments_y) + : segments{segments_x, segments_y} + , bb{bb} + {} }; +struct IrregularBed { + ExPolygon poly; +}; + +//enum BedType { Infinite, Rectangle, Circle, SegmentedRectangle, Irregular }; + +using ArrangeBed = boost::variant; + +ArrangeBed to_arrange_bed(const Points &bedpts); + /// A logical bed representing an object not being arranged. Either the arrange /// has not yet successfully run on this ArrangePolygon or it could not fit the /// object due to overly large size or invalid geometry. @@ -75,6 +104,10 @@ struct ArrangePolygon { using ArrangePolygons = std::vector; +enum class Pivots { + Center, TopLeft, BottomLeft, BottomRight, TopRight +}; + struct ArrangeParams { /// The minimum distance which is allowed for any @@ -93,6 +126,12 @@ struct ArrangeParams { bool allow_rotations = false; + /// Final alignment of the merged pile after arrangement + Pivots alignment = Pivots::Center; + + /// Starting position hint for the arrangement + Pivots starting_point = Pivots::Center; + /// Progress indicator callback called when an object gets packed. /// The unsigned argument is the number of items remaining to pack. std::function progressind; @@ -127,12 +166,32 @@ extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excl extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms); extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms); +inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const RectangleBed &bed, const ArrangeParams ¶ms) +{ + arrange(items, excludes, bed.bb, params); +} + +inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const IrregularBed &bed, const ArrangeParams ¶ms) +{ + arrange(items, excludes, bed.poly.contour, params); +} + +void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const SegmentedRectangleBed &bed, const ArrangeParams ¶ms); + +inline void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const ArrangeBed &bed, const ArrangeParams ¶ms) +{ + auto call_arrange = [&](const auto &realbed) { arrange(items, excludes, realbed, params); }; + boost::apply_visitor(call_arrange, bed); +} + inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +bool is_box(const Points &bed); + }} // namespace Slic3r::arrangement #endif // MODELARRANGE_HPP diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 3c124fe2a..d741be36c 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -235,9 +235,18 @@ inline bool empty(const BoundingBox3Base &bb) } inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; } -inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; } -inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } -inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } + +template +BoundingBoxBase> scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; } + +template +BoundingBox3Base> scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; } + +template +BoundingBoxBase> unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } + +template +BoundingBox3Base> unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } template auto cast(const BoundingBoxBase &b) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 30206a6c6..072597162 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4822,6 +4822,15 @@ Points get_bed_shape(const DynamicPrintConfig &config) return to_points(bed_shape_opt->values); } +void get_bed_shape(const DynamicPrintConfig &cfg, arrangement::ArrangeBed &out) +{ + if (is_XL_printer(cfg)) { + out = arrangement::SegmentedRectangleBed{get_extents(get_bed_shape(cfg)), 4, 4}; + } else { + out = arrangement::to_arrange_bed(get_bed_shape(cfg)); + } +} + Points get_bed_shape(const PrintConfig &cfg) { return to_points(cfg.bed_shape.values); @@ -4846,6 +4855,20 @@ std::string get_sla_suptree_prefix(const DynamicPrintConfig &config) return slatree; } +bool is_XL_printer(const DynamicPrintConfig &cfg) +{ + static constexpr const char *ALIGN_ONLY_FOR = "XL"; + + bool ret = false; + + auto *printer_model = cfg.opt("printer_model"); + + if (printer_model) + ret = boost::algorithm::contains(printer_model->value, ALIGN_ONLY_FOR); + + return ret; +} + } // namespace Slic3r #include diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 9e89fcefd..a2ccb546b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -19,6 +19,7 @@ #include "libslic3r.h" #include "Config.hpp" #include "SLA/SupportTreeStrategies.hpp" +#include "libslic3r/Arrange.hpp" #include #include @@ -1185,10 +1186,14 @@ private: static PrintAndCLIConfigDef s_def; }; +bool is_XL_printer(const DynamicPrintConfig &cfg); + Points get_bed_shape(const DynamicPrintConfig &cfg); Points get_bed_shape(const PrintConfig &cfg); Points get_bed_shape(const SLAPrinterConfig &cfg); +void get_bed_shape(const DynamicPrintConfig &cfg, arrangement::ArrangeBed &out); + std::string get_sla_suptree_prefix(const DynamicPrintConfig &config); // ModelConfig is a wrapper around DynamicPrintConfig with an addition of a timestamp. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d0967d3db..2f31a9171 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -999,6 +999,19 @@ void GLCanvas3D::load_arrange_settings() std::string en_rot_sla_str = wxGetApp().app_config->get("arrange", "enable_rotation_sla"); +// std::string alignment_fff_str = +// wxGetApp().app_config->get("arrange", "alignment_fff"); + +// std::string alignment_fff_seqp_str = +// wxGetApp().app_config->get("arrange", "alignment_fff_seq_pring"); + +// std::string alignment_sla_str = +// wxGetApp().app_config->get("arrange", "alignment_sla"); + + // Override default alignment and save save/load it to a temporary slot "alignment_xl" + std::string alignment_xl_str = + wxGetApp().app_config->get("arrange", "alignment_xl"); + if (!dist_fff_str.empty()) m_arrange_settings_fff.distance = string_to_float_decimal_point(dist_fff_str); @@ -1025,6 +1038,24 @@ void GLCanvas3D::load_arrange_settings() if (!en_rot_sla_str.empty()) m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); + +// if (!alignment_sla_str.empty()) +// m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str); + +// if (!alignment_fff_str.empty()) +// m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str); + +// if (!alignment_fff_seqp_str.empty()) +// m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str); + + // Override default alignment and save save/load it to a temporary slot "alignment_xl" + int arr_alignment = static_cast(arrangement::Pivots::BottomLeft); + if (!alignment_xl_str.empty()) + arr_alignment = std::stoi(alignment_xl_str); + + m_arrange_settings_sla.alignment = arr_alignment ; + m_arrange_settings_fff.alignment = arr_alignment ; + m_arrange_settings_fff_seq_print.alignment = arr_alignment ; } PrinterTechnology GLCanvas3D::current_printer_technology() const @@ -1032,6 +1063,11 @@ PrinterTechnology GLCanvas3D::current_printer_technology() const return m_process->current_printer_technology(); } +bool GLCanvas3D::is_arrange_alignment_enabled() const +{ + return m_config ? is_XL_printer(*m_config) : false; +} + GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed) : m_canvas(canvas) , m_context(nullptr) @@ -4160,7 +4196,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); ArrangeSettings settings = get_arrange_settings(); - ArrangeSettings &settings_out = get_arrange_settings(); + ArrangeSettings &settings_out = get_arrange_settings_ref(this); auto &appcfg = wxGetApp().app_config; PrinterTechnology ptech = current_printer_technology(); @@ -4171,6 +4207,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) std::string dist_key = "min_object_distance"; std::string dist_bed_key = "min_bed_distance"; std::string rot_key = "enable_rotation"; + std::string align_key = "alignment"; std::string postfix; if (ptech == ptSLA) { @@ -4189,6 +4226,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) dist_key += postfix; dist_bed_key += postfix; rot_key += postfix; + align_key += postfix; imgui->text(GUI::format_wxstr(_L("Press %1%left mouse button to enter the exact value"), shortkey_ctrl_prefix())); @@ -4212,11 +4250,28 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) settings_changed = true; } + Points bed = m_config ? get_bed_shape(*m_config) : Points{}; + + if (arrangement::is_box(bed) && settings.alignment >= 0 && + imgui->combo(_L("Alignment"), {_u8L("Center"), _u8L("Rear left"), _u8L("Front left"), _u8L("Front right"), _u8L("Rear right"), _u8L("Random") }, settings.alignment)) { + settings_out.alignment = settings.alignment; + appcfg->set("arrange", align_key.c_str(), std::to_string(settings_out.alignment)); + settings_changed = true; + } + ImGui::Separator(); if (imgui->button(_L("Reset"))) { + auto alignment = settings_out.alignment; settings_out = ArrangeSettings{}; settings_out.distance = std::max(dist_min, settings_out.distance); + + // Default alignment for XL printers set explicitly: + if (is_arrange_alignment_enabled()) + settings_out.alignment = static_cast(arrangement::Pivots::BottomLeft); + else + settings_out.alignment = alignment; + appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance)); appcfg->set("arrange", dist_bed_key.c_str(), float_to_string_decimal_point(settings_out.distance_from_bed)); appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0"); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index f8f5a0efc..8545cdf51 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -463,6 +463,7 @@ public: // float distance_sla = 6.; float accuracy = 0.65f; // Unused currently bool enable_rotation = false; + int alignment = 0; }; private: @@ -549,8 +550,10 @@ private: PrinterTechnology current_printer_technology() const; + bool is_arrange_alignment_enabled() const; + template - static auto & get_arrange_settings(Self *self) { + static auto & get_arrange_settings_ref(Self *self) { PrinterTechnology ptech = self->current_printer_technology(); auto *ptr = &self->m_arrange_settings_fff; @@ -568,8 +571,22 @@ private: return *ptr; } - ArrangeSettings &get_arrange_settings() { return get_arrange_settings(this); } +public: + ArrangeSettings get_arrange_settings() const { + const ArrangeSettings &settings = get_arrange_settings_ref(this); + ArrangeSettings ret = settings; + if (&settings == &m_arrange_settings_fff_seq_print) { + ret.distance = std::max(ret.distance, + float(min_object_distance(*m_config))); + } + if (!is_arrange_alignment_enabled()) + ret.alignment = -1; + + return ret; + } + +private: void load_arrange_settings(); class SequentialPrintClearance @@ -901,17 +918,6 @@ public: void highlight_toolbar_item(const std::string& item_name); void highlight_gizmo(const std::string& gizmo_name); - ArrangeSettings get_arrange_settings() const { - const ArrangeSettings &settings = get_arrange_settings(this); - ArrangeSettings ret = settings; - if (&settings == &m_arrange_settings_fff_seq_print) { - ret.distance = std::max(ret.distance, - float(min_object_distance(*m_config))); - } - - return ret; - } - // Timestamp for FPS calculation and notification fade-outs. static int64_t timestamp_now() { #ifdef _WIN32 diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 7873af758..4595ae254 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -14,6 +14,7 @@ #include "libnest2d/common.hpp" #include +#include namespace Slic3r { namespace GUI { @@ -168,11 +169,11 @@ void ArrangeJob::process(Ctl &ctl) static const auto arrangestr = _u8L("Arranging"); arrangement::ArrangeParams params; - Points bedpts; - ctl.call_on_main_thread([this, ¶ms, &bedpts]{ + arrangement::ArrangeBed bed; + ctl.call_on_main_thread([this, ¶ms, &bed]{ prepare(); params = get_arrange_params(m_plater); - bedpts = get_bed_shape(*m_plater->config()); + get_bed_shape(*m_plater->config(), bed); }).wait(); auto count = unsigned(m_selected.size() + m_unprintable.size()); @@ -191,13 +192,13 @@ void ArrangeJob::process(Ctl &ctl) ctl.update_status(0, arrangestr); - arrangement::arrange(m_selected, m_unselected, bedpts, params); + arrangement::arrange(m_selected, m_unselected, bed, params); params.progressind = [this, count, &ctl](unsigned st) { if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr); }; - arrangement::arrange(m_unprintable, {}, bedpts, params); + arrangement::arrange(m_unprintable, {}, bed, params); // finalize just here. ctl.update_status(int(count) * 100 / status_range(), ctl.was_canceled() ? @@ -291,13 +292,30 @@ arrangement::ArrangePolygon get_arrange_poly(ModelInstance *inst, arrangement::ArrangeParams get_arrange_params(Plater *p) { const GLCanvas3D::ArrangeSettings &settings = - static_cast(p->canvas3D())->get_arrange_settings(); + p->canvas3D()->get_arrange_settings(); arrangement::ArrangeParams params; params.allow_rotations = settings.enable_rotation; params.min_obj_distance = scaled(settings.distance); params.min_bed_distance = scaled(settings.distance_from_bed); + arrangement::Pivots pivot = arrangement::Pivots::Center; + + int pivot_max = static_cast(arrangement::Pivots::TopRight); + if (settings.alignment < 0) { + pivot = arrangement::Pivots::Center; + } else if (settings.alignment > pivot_max) { + // means it should be random + std::random_device rd{}; + std::mt19937 rng(rd()); + std::uniform_int_distribution dist(0, pivot_max); + pivot = static_cast(dist(rng)); + } else { + pivot = static_cast(settings.alignment); + } + + params.alignment = pivot; + return params; } diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 5eefc14b6..6a173683f 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -106,18 +106,15 @@ void FillBedJob::prepare() void FillBedJob::process(Ctl &ctl) { auto statustxt = _u8L("Filling bed"); - ctl.call_on_main_thread([this] { prepare(); }).wait(); + arrangement::ArrangeParams params; + ctl.call_on_main_thread([this, ¶ms] { + prepare(); + params = get_arrange_params(m_plater); + }).wait(); ctl.update_status(0, statustxt); - if (m_object_idx == -1 || m_selected.empty()) return; - - const GLCanvas3D::ArrangeSettings &settings = - static_cast(m_plater->canvas3D())->get_arrange_settings(); - - arrangement::ArrangeParams params; - params.allow_rotations = settings.enable_rotation; - params.min_obj_distance = scaled(settings.distance); - params.min_bed_distance = scaled(settings.distance_from_bed); + if (m_object_idx == -1 || m_selected.empty()) + return; bool do_stop = false; params.stopcondition = [&ctl, &do_stop]() {