Files
OrcaSlicer-bambulab/deps_src/libnest2d/include/libnest2d/selections/firstfit.hpp
Louis Rossmann c661ddc2eb Initial release
2026-05-11 07:39:33 -05:00

211 lines
8.7 KiB
C++

#ifndef FIRSTFIT_HPP
#define FIRSTFIT_HPP
#include "selection_boilerplate.hpp"
// for writing SVG
//#include "../tools/svgtools.hpp"
namespace libnest2d { namespace selections {
template<class RawShape>
class _FirstFitSelection: public SelectionBoilerplate<RawShape> {
using Base = SelectionBoilerplate<RawShape>;
public:
using typename Base::Item;
using Config = int; //dummy
private:
using Base::packed_bins_;
using typename Base::ItemGroup;
using Container = ItemGroup;//typename std::vector<_Item<RawShape>>;
Container store_;
public:
void configure(const Config& /*config*/) { }
template<class TPlacer, class TIterator,
class TBin = typename PlacementStrategyLike<TPlacer>::BinType,
class PConfig = typename PlacementStrategyLike<TPlacer>::Config>
void packItems(TIterator first,
TIterator last,
TBin&& bin,
PConfig&& pconfig = PConfig())
{
using Placer = PlacementStrategyLike<TPlacer>;
store_.clear();
store_.reserve(last-first);
std::vector<Placer> placers;
placers.reserve(last-first);
typename Base::PackGroup fixed_bins;
std::for_each(first, last, [this, &fixed_bins](Item& itm) {
if (itm.isFixed()) {
if (itm.binId() < 0) itm.binId(0);
auto binidx = size_t(itm.binId());
while (fixed_bins.size() <= binidx)
fixed_bins.emplace_back();
fixed_bins[binidx].emplace_back(itm);
}
else {
store_.emplace_back(itm);
}
});
std::for_each(pconfig.m_excluded_regions.begin(), pconfig.m_excluded_regions.end(), [this, &pconfig](Item& itm) {
pconfig.m_excluded_items.emplace_back(itm);
});
#ifdef SVGTOOLS_HPP
svg::SVGWriter<RawShape> svgwriter;
std::for_each(first, last, [this,&svgwriter](Item &itm) { svgwriter.writeShape(itm, "none", "blue"); });
svgwriter.save(boost::filesystem::path("SVG") / "all_items.svg");
#endif
std::function<bool(Item& i1, Item& i2)> sortfunc;
if (pconfig.sortfunc)
sortfunc = pconfig.sortfunc;
else {
sortfunc = [](Item& i1, Item& i2) {
int p1 = i1.priority(), p2 = i2.priority();
if (p1 != p2)
return p1 > p2;
return i1.bed_temp != i2.bed_temp ? (i1.bed_temp > i2.bed_temp) :
(i1.height != i2.height ? (i1.height < i2.height) : (i1.area() > i2.area()));
};
}
std::sort(store_.begin(), store_.end(), sortfunc);
// debug: write down intitial order
for (auto it = store_.begin(); it != store_.end(); ++it) {
std::stringstream ss;
ss << "initial order: " << it->get().name << ", p=" << it->get().priority() << ", bed_temp=" << it->get().bed_temp << ", height=" << it->get().height
<< ", area=" << it->get().area();
if (this->unfitindicator_)
this->unfitindicator_(ss.str());
}
int item_id = 0;
auto makeProgress = [this, &item_id](Placer &placer, size_t bin_idx) {
packed_bins_[bin_idx] = placer.getItems();
this->last_packed_bin_id_ = int(bin_idx);
this->progress_(static_cast<unsigned>(item_id));
};
auto& cancelled = this->stopcond_;
this->template remove_unpackable_items<Placer>(store_, bin, pconfig);
for (auto it = store_.begin(); it != store_.end() && !cancelled(); ++it) {
// skip unpackable item
if (it->get().binId() == BIN_ID_UNFIT)
continue;
bool was_packed = false;
int best_bed_id = -1;
int bed_id_firstfit = -1;
double score = LARGE_COST_TO_REJECT+1, best_score = LARGE_COST_TO_REJECT+1;
double score_all_plates = 0, score_all_plates_best = std::numeric_limits<double>::max();
typename Placer::PackResult result, result_best, result_firstfit;
int j = 0;
while(!was_packed && !cancelled()) {
for(; j < placers.size() && !was_packed && !cancelled(); j++) {
result = placers[j].pack(*it, rem(it, store_));
score = result.score();
score_all_plates = score;
for (int i = 0; i < placers.size(); i++) { score_all_plates += placers[i].score();}
if (this->unfitindicator_) this->unfitindicator_(it->get().name + " bed_id="+std::to_string(j) + ",score=" + std::to_string(score)+", score_all_plates="+std::to_string(score_all_plates));
if(score >= 0 && score < LARGE_COST_TO_REJECT) {
if (bed_id_firstfit == -1) {
bed_id_firstfit = j;
result_firstfit = result;
}
if (score_all_plates < score_all_plates_best) {
best_score = score;
score_all_plates_best = score_all_plates;
best_bed_id = j;
result_best = result;
}
}
}
if (best_bed_id == MAX_NUM_PLATES) {
// item is not fit because we have tried all possible plates to find a good enough fit
if (bed_id_firstfit == MAX_NUM_PLATES) {
it->get().binId(BIN_ID_UNFIT);
if (this->unfitindicator_)
this->unfitindicator_(it->get().name + " bed_id_firstfit == MAX_NUM_PLATES" + ",best_score=" + std::to_string(best_score));
break;
}
else {
// best bed is invalid, but firstfit bed is OK, use the latter
best_bed_id = bed_id_firstfit;
result_best = result_firstfit;
}
}
if(best_bed_id>=0)
{
was_packed = true;
j = best_bed_id;
it->get().binId(int(j));
it->get().itemId(item_id++);
placers[j].accept(result_best);
makeProgress(placers[j], j);
}
if (was_packed && it->get().has_tried_with_excluded) {
placers[j].clearItems([](const Item &itm) { return itm.isFixed() && !itm.is_wipe_tower; });
if (fixed_bins.size() >= placers.size())
placers[j].preload(fixed_bins[placers.size() - 1]);
}
bool placer_not_packed = !was_packed && !placers.empty() && j == placers.size() && placers[j - 1].getPackedSize() == 0; // large item is not placed into the bin
if (placer_not_packed) {
if (it->get().has_tried_with_excluded == false) {
it->get().has_tried_with_excluded = true;
placers[j - 1].clearItems([](const Item &itm) { return itm.isFixed()&&!itm.is_wipe_tower; });
placers[j - 1].preload(pconfig.m_excluded_items);
j = j - 1;
continue;
} else {
placers[j - 1].clearItems([](const Item &itm) { return itm.isFixed() && !itm.is_wipe_tower; });
placers[j - 1].preload(fixed_bins[placers.size() - 1]);
}
}
if(!was_packed){
if (this->unfitindicator_ && !placers.empty())
this->unfitindicator_(it->get().name + ", height=" +std::to_string(it->get().height)
+ " ,plate_id=" + std::to_string(j-1)
+ ", score=" + std::to_string(score)
+ ", best_bed_id=" + std::to_string(best_bed_id)
+ ", score_all_plates=" + std::to_string(score_all_plates)
+", overfit=" + std::to_string(result.overfit()));
placers.emplace_back(bin);
placers.back().plateID(placers.size() - 1);
placers.back().configure(pconfig);
if (fixed_bins.size() >= placers.size())
placers.back().preload(fixed_bins[placers.size() - 1]);
//placers.back().preload(pconfig.m_excluded_items);
packed_bins_.emplace_back();
j = placers.size() - 1;
}
}
}
}
};
}
}
#endif // FIRSTFIT_HPP