#ifndef FILLBEDTASKIMPL_HPP #define FILLBEDTASKIMPL_HPP #include "FillBedTask.hpp" #include "Arrange/Core/NFP/NFPArrangeItemTraits.hpp" #include namespace Slic3r { namespace arr2 { template int calculate_items_needed_to_fill_bed(const ExtendedBed &bed, const ArrItem &prototype_item, size_t prototype_count, const std::vector &fixed) { double poly_area = fixed_area(prototype_item); auto area_sum_fn = [](double s, const auto &itm) { return s + (get_bed_index(itm) == 0) * fixed_area(itm); }; double unsel_area = std::accumulate(fixed.begin(), fixed.end(), 0., area_sum_fn); double fixed_area = unsel_area + prototype_count * poly_area; double bed_area = 0.; visit_bed([&bed_area] (auto &realbed) { bed_area = area(realbed); }, bed); // This is the maximum number of items, // the real number will always be close but less. auto needed_items = static_cast( std::ceil((bed_area - fixed_area) / poly_area)); return needed_items; } template void extract(FillBedTask &task, const Scene &scene, const ArrangeableToItemConverter &itm_conv) { task.prototype_item = {}; auto selected_ids = scene.selected_ids(); if (selected_ids.empty()) return; std::set selected_objects = selected_geometry_ids(scene); if (selected_objects.size() != 1) return; ObjectID prototype_geometry_id = *(selected_objects.begin()); auto set_prototype_item = [&task, &itm_conv](const Arrangeable &arrbl) { if (arrbl.is_printable()) task.prototype_item = itm_conv.convert(arrbl); }; scene.model().visit_arrangeable(selected_ids.front(), set_prototype_item); if (!task.prototype_item) return; // Workaround for missing items when arranging the same geometry only: // Injecting a number of items but with slightly shrinked shape, so that // they can fill the emerging holes. Priority is set to lowest so that // these filler items will only be inserted as the last ones. ArrItem prototype_item_shrinked; scene.model().visit_arrangeable(selected_ids.front(), [&prototype_item_shrinked, &itm_conv](const Arrangeable &arrbl) { if (arrbl.is_printable()) prototype_item_shrinked = itm_conv.convert(arrbl, -SCALED_EPSILON); }); set_bed_index(*task.prototype_item, Unarranged); auto collect_task_items = [&prototype_geometry_id, &task, &itm_conv](const Arrangeable &arrbl) { try { if (arrbl.geometry_id() == prototype_geometry_id) { if (arrbl.is_printable()) { auto itm = itm_conv.convert(arrbl); raise_priority(itm); task.selected.emplace_back(std::move(itm)); } } else { auto itm = itm_conv.convert(arrbl, -SCALED_EPSILON); task.unselected.emplace_back(std::move(itm)); } } catch (const EmptyItemOutlineError &ex) { BOOST_LOG_TRIVIAL(error) << "ObjectID " << std::to_string(arrbl.id().id) << ": " << ex.what(); } }; // Set the lowest priority to the shrinked prototype (hole filler) item set_priority(prototype_item_shrinked, lowest_priority(range(task.selected)) - 1); scene.model().for_each_arrangeable(collect_task_items); int needed_items = calculate_items_needed_to_fill_bed(task.bed, *task.prototype_item, task.selected.size(), task.unselected); task.selected_existing_count = task.selected.size(); task.selected.reserve(task.selected.size() + needed_items); std::fill_n(std::back_inserter(task.selected), needed_items, *task.prototype_item); // Add as many filler items as there are needed items. Most of them will // be discarded anyways. std::fill_n(std::back_inserter(task.selected), needed_items, prototype_item_shrinked); } template std::unique_ptr> FillBedTask::create( const Scene &sc, const ArrangeableToItemConverter &converter) { auto task = std::make_unique>(); task->settings.set_from(sc.settings()); task->bed = get_corrected_bed(sc.bed(), converter); extract(*task, sc, converter); return task; } template std::unique_ptr FillBedTask::process_native( Ctl &ctl) { auto result = std::make_unique(); if (!prototype_item) return result; result->prototype_id = retrieve_id(*prototype_item).value_or(ObjectID{}); class FillBedCtl: public ArrangerCtl { ArrangeTaskCtl &parent; FillBedTask &self; bool do_stop = false; public: FillBedCtl(ArrangeTaskCtl &p, FillBedTask &slf) : parent{p}, self{slf} {} void update_status(int remaining) override { parent.update_status(remaining); } bool was_canceled() const override { return parent.was_canceled() || do_stop; } void on_packed(ArrItem &itm) override { // Stop at the first filler that is not on the physical bed do_stop = get_bed_index(itm) > PhysicalBedId && get_priority(itm) < 0; } } subctl(ctl, *this); auto arranger = Arranger::create(settings); arranger->arrange(selected, unselected, bed, subctl); auto arranged_range = Range{selected.begin(), selected.begin() + selected_existing_count}; result->add_arranged_items(arranged_range); auto to_add_range = Range{selected.begin() + selected_existing_count, selected.end()}; for (auto &itm : to_add_range) if (get_bed_index(itm) == PhysicalBedId) result->add_new_item(itm); return result; } } // namespace arr2 } // namespace Slic3r #endif // FILLBEDTASKIMPL_HPP