update src

This commit is contained in:
QIDI TECH
2025-05-05 15:13:42 +08:00
parent 3fe258372a
commit eae8e18c3a
35 changed files with 11268 additions and 6351 deletions

View File

@@ -656,7 +656,7 @@ inline std::string toString(const S& /*sh*/)
}
template<Formats, class S>
inline std::string serialize(const S& /*sh*/, double /*scale*/=1, std::string fill = "none", std::string stroke = "black", float stroke_width = 1)
inline std::string serialize(const S & /*sh*/, const std::string &name = "", double /*scale*/ = 1, std::string fill = "none", std::string stroke = "black", float stroke_width = 1)
{
static_assert(always_false<S>::value,
"shapelike::serialize() unimplemented!");

View File

@@ -10,6 +10,7 @@
#include <libnest2d/geometry_traits.hpp>
#define LARGE_COST_TO_REJECT 1e7
#define COST_OF_NEW_PLATE 0.1
namespace libnest2d {
@@ -75,8 +76,8 @@ class _Item {
public:
int itemid_{ 0 };
std::vector<int> extrude_ids;
int filament_temp_type = -1; // -1 means unset. otherwise should be {0,1,2}
std::map<int, std::string> extrude_id_filament_types; // extrude id to filament type
int filament_temp_type = -1; // -1 means unset. otherwise should be one of FilamentTempType ie {0,1,2}
double height{ 0 };
double print_temp{ 0 };
double bed_temp{ 0 };
@@ -85,7 +86,9 @@ public:
//QDS: virtual object to mark unprintable region on heatbed
bool is_virt_object{ false };
bool is_wipe_tower{ false };
bool has_tried_with_excluded{ false };
bool is_extrusion_cali_object{ false };
bool has_tried_without_extrusion_cali_obj{ false };
std::vector<double> allowed_rotations{0.};
/// The type of the shape which was handed over as the template argument.
using ShapeType = RawShape;

View File

@@ -18,8 +18,8 @@
#include "placer_boilerplate.hpp"
// temporary
//#include "../tools/svgtools.hpp"
//#include <iomanip> // setprecision
#include "../tools/svgtools.hpp"
#include <iomanip> // setprecision
#include <libnest2d/parallel.hpp>
@@ -130,6 +130,9 @@ struct NfpPConfig {
//QDS: sort function for selector
std::function<bool(_Item<RawShape>& i1, _Item<RawShape>& i2)> sortfunc;
std::function<void(const std::string &)> progressFunc = {};
//QDS: excluded region for V4 bed
std::vector<_Item<RawShape> > m_excluded_regions;
_ItemGroup<RawShape> m_excluded_items;
@@ -557,10 +560,12 @@ public:
template<class Range = ConstItemRange<typename Base::DefaultIter>>
PackResult trypack(Item& item,
const Range& remaining = Range()) {
auto result = _trypack(item, remaining);
if (item.is_wipe_tower) {
PackResult result1 = _trypack_with_original_pos(item);
if (result1.score() >= 0 && result1.score() < LARGE_COST_TO_REJECT) return result1;
}
// Experimental
// if(!result) repack(item, result);
auto result = _trypack(item, remaining);
return result;
}
@@ -685,11 +690,20 @@ private:
};
using Edges = EdgeCache<RawShape>;
// item won't overlap with virtual objects if it's inside or touches NFP
// @return 1 if current item overlaps with virtual objects, 0 otherwise
bool overlapWithVirtObject(const Item& item, const Box& binbb){
if (items_.empty()) return 0;
Shapes nfps = calcnfp(item, binbb, Lvl<MaxNfpLevel::value>());
auto v = item.referenceVertex();
for (const RawShape &nfp : nfps) {
if (sl::isInside(v, nfp) || sl::touches(v, nfp)) { return false; }
}
return true;
};
template<class Range = ConstItemRange<typename Base::DefaultIter>>
PackResult _trypack(
Item& item,
const Range& remaining = Range()) {
PackResult _trypack(Item& item, const Range& remaining = Range()) {
PackResult ret;
@@ -755,41 +769,41 @@ private:
};
}
bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; });
for (auto &it : items_) {
config_.progressFunc((boost::format("_trypack: plate: %4%, existing object: %1%, pos: (%2%, %3%)") % it.get().name % unscale_(it.get().translation()[0]) %
unscale_(it.get().translation()[1]) % plateID())
.str());
}
// item won't overlap with virtual objects if it's inside or touches NFP
// @return 1 if current item overlaps with virtual objects, 0 otherwise
auto overlapWithVirtObject = [&]() -> double {
if (items_.empty()) return 0;
nfps = calcnfp(item, binbb, Lvl<MaxNfpLevel::value>());
auto v = item.referenceVertex();
for (const RawShape &nfp : nfps) {
if (sl::isInside(v, nfp) || sl::touches(v, nfp)) { return 0; }
}
return 1;
};
bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; });
if (first_object) {
setInitialPosition(item);
auto best_tr = item.translation();
auto best_rot = item.rotation();
best_overfit = overfit(item.transformedShape(), bin_) + overlapWithVirtObject();
best_overfit = std::numeric_limits<double>::max();
// try normal inflation first, then 0 inflation if not fit. See STUDIO-5566.
// Note for by-object printing, bed is expanded by -config_.bed_shrink.x().
Coord inflation_back = item.inflation();
Coord inflations[2]={inflation_back, std::abs(config_.bed_shrink.x())};
Coord inflations[2]={inflation_back, 0};
for (size_t i = 0; i < 2; i++) {
item.inflation(inflations[i]);
for (auto rot : config_.rotations) {
for (auto rot : item.allowed_rotations) {
item.translation(initial_tr);
item.rotation(initial_rot + rot);
setInitialPosition(item);
double of = overfit(item.transformedShape(), bin_);
if (of + overlapWithVirtObject() < best_overfit) {
best_overfit = of;
best_tr = item.translation();
best_rot = item.rotation();
if (!overlapWithVirtObject(item, binbb)) {
double of = overfit(item.transformedShape(), bin_);
if (of < best_overfit) {
best_overfit = of;
best_tr = item.translation();
best_rot = item.rotation();
if (best_overfit <= 0) {
config_.progressFunc("First object " + item.name + " can fit with rot=" + std::to_string(rot));
break;
}
}
}
}
can_pack = best_overfit <= 0;
@@ -806,7 +820,7 @@ private:
Pile merged_pile = merged_pile_;
for(auto rot : config_.rotations) {
for(auto rot : item.allowed_rotations) {
item.translation(initial_tr);
item.rotation(initial_rot + rot);
@@ -991,6 +1005,7 @@ private:
final_rot = initial_rot + rot;
can_pack = true;
global_score = best_score;
break;
}
}
@@ -998,41 +1013,7 @@ private:
item.rotation(final_rot);
}
#ifdef SVGTOOLS_HPP
if (config_.save_svg) {
svg::SVGWriter<RawShape> svgwriter;
Box binbb2(binbb.width() * 2, binbb.height() * 2, binbb.center()); // expand bbox to allow object be drawed outside
svgwriter.setSize(binbb2);
svgwriter.conf_.x0 = binbb.width();
svgwriter.conf_.y0 = -binbb.height() / 2; // origin is top left corner
svgwriter.add_comment("bed");
svgwriter.writeShape(box2RawShape(binbb), "none", "black");
svgwriter.add_comment("nfps");
for (int i = 0; i < nfps.size(); i++) svgwriter.writeShape(nfps[i], "none", "blue");
for (int i = 0; i < items_.size(); i++) {
svgwriter.add_comment(items_[i].get().name);
svgwriter.writeItem(items_[i], "none", "black");
}
svgwriter.add_comment("merged_pile_");
for (int i = 0; i < merged_pile_.size(); i++) svgwriter.writeShape(merged_pile_[i], "none", "yellow");
svgwriter.add_comment("current item");
svgwriter.writeItem(item, "red", "red", 2);
std::stringstream ss;
ss.setf(std::ios::fixed | std::ios::showpoint);
ss.precision(1);
ss << "t=" << round(item.translation().x() / 1e6) << ","
<< round(item.translation().y() / 1e6)
//<< "-rot=" << round(item.rotation().toDegrees())
<< "-sco=" << round(global_score);
svgwriter.draw_text(20, 20, ss.str(), "blue", 20);
ss.str("");
ss << "items.size=" << items_.size() << "-merged_pile.size=" << merged_pile_.size();
svgwriter.draw_text(20, 40, ss.str(), "blue", 20);
svgwriter.save(boost::filesystem::path("C:/Users/arthur.tang/AppData/Roaming/QIDIStudioInternal/SVG") /
("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg"));
}
#endif
if (config_.save_svg) saveSVG(binbb, nfps, item, global_score, can_pack);
if(can_pack) {
ret = PackResult(item);
@@ -1045,6 +1026,87 @@ private:
return ret;
}
PackResult _trypack_with_original_pos(Item &item)
{
PackResult ret;
bool can_pack = false;
double best_overfit = std::numeric_limits<double>::max();
double global_score = std::numeric_limits<double>::max();
auto initial_tr = item.translation();
auto initial_rot = item.rotation();
Vertex final_tr = initial_tr;
Radians final_rot = initial_rot;
Shapes nfps;
auto binbb = sl::boundingBox(bin_);
for (auto &it : items_) {
config_.progressFunc((boost::format("_trypack_with_original_pos: plate: %4%, existing object: %1%, pos: (%2%, %3%)") % it.get().name % unscale_(it.get().translation()[0]) %
unscale_(it.get().translation()[1]) % plateID())
.str());
}
{
for (auto rot : item.allowed_rotations) {
item.translation(initial_tr);
item.rotation(initial_rot + rot);
if (!overlapWithVirtObject(item, binbb)) {
can_pack = true;
final_tr = initial_tr;
final_rot = initial_rot + rot;
global_score = 0.3;
break;
}
}
item.translation(final_tr);
item.rotation(final_rot);
}
if (config_.save_svg) saveSVG(binbb, nfps, item, global_score, can_pack);
if (can_pack) {
ret = PackResult(item);
ret.score_ = global_score;
// merged_pile_ = nfp::merge(merged_pile_, item.transformedShape());
config_.progressFunc((boost::format("_trypack_with_original_pos: item %1% can pack") % item.name).str());
} else {
ret = PackResult(best_overfit);
}
return ret;
}
void saveSVG(Box &binbb, Shapes &nfps, Item &item, double global_score, bool can_pack)
{
svg::SVGWriter<RawShape> svgwriter;
Box binbb2(binbb.width() * 2, binbb.height() * 2, binbb.center()); // expand bbox to allow object be drawed outside
svgwriter.setSize(binbb2);
svgwriter.conf_.x0 = binbb.width();
svgwriter.conf_.y0 = -binbb.height() / 2; // origin is top left corner
svgwriter.writeShape(box2RawShape(binbb), "bed", "none", "black");
for (int i = 0; i < nfps.size(); i++) svgwriter.writeShape(nfps[i], "nfp_" + std::to_string(i), "none", "blue", 0.2);
for (int i = 0; i < items_.size(); i++) { svgwriter.writeItem(items_[i], items_[i].get().name, "none", "black", 0.2); }
for (int i = 0; i < merged_pile_.size(); i++) svgwriter.writeShape(merged_pile_[i], "merged_pile_" + std::to_string(i), "none", "yellow", 0.2);
svgwriter.writeItem(item, item.name, "red", "red", 0.3);
std::stringstream ss;
ss.setf(std::ios::fixed | std::ios::showpoint);
ss.precision(1);
ss << "t=" << round(item.translation().x() / 1e6) << ","
<< round(item.translation().y() / 1e6)
//<< "-rot=" << round(item.rotation().toDegrees())
<< "-sco=" << round(global_score);
svgwriter.draw_text(20, 20, ss.str(), "blue", 10);
ss.str("");
ss << "items.size=" << items_.size() << "-merged_pile.size=" << merged_pile_.size();
svgwriter.draw_text(20, 40, ss.str(), "blue", 10);
svgwriter.save(boost::filesystem::path("SVG") / ("plate_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + "_canPack=" + std::to_string(can_pack) + ".svg"));
}
RawShape box2RawShape(Box& bbin)
{
RawShape binrsh;
@@ -1158,16 +1220,7 @@ private:
auto d = cb - ci;
// QDS make sure the item won't clash with excluded regions
// do we have wipe tower after arranging?
size_t n_objs = 0;
std::set<int> extruders;
for (const Item& item : items_) {
if (!item.is_virt_object) {
extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end());
n_objs++;
}
}
bool need_wipe_tower = extruders.size() > 1;
std::vector<RawShape> objs,excludes;
for (Item &item : items_) {

View File

@@ -41,7 +41,7 @@ public:
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()) {
@@ -67,7 +67,7 @@ public:
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;
@@ -88,7 +88,23 @@ public:
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();
<< ", area=" << it->get().area() << ", allowed_rotations=";
for(auto r: it->get().allowed_rotations) ss << r << ", ";
if (this->unfitindicator_)
this->unfitindicator_(ss.str());
}
// debug: write down fixed items
for (size_t i = 0; i < fixed_bins.size(); i++) {
if (fixed_bins[i].empty())
continue;
std::stringstream ss;
ss << "fixed bin " << i << ": ";
for (auto it = fixed_bins[i].begin(); it != fixed_bins[i].end(); ++it) {
ss << it->get().name << ", p=" << it->get().priority() << ", bed_temp=" << it->get().bed_temp << ", height=" << it->get().height
<< ", area=" << it->get().area() << ", allowed_rotations=";
for(auto r: it->get().allowed_rotations) ss << r << ", ";
ss << "; ";
}
if (this->unfitindicator_)
this->unfitindicator_(ss.str());
}
@@ -101,8 +117,8 @@ public:
};
auto& cancelled = this->stopcond_;
this->template remove_unpackable_items<Placer>(store_, bin, pconfig);
//this->template remove_unpackable_items<Placer>(store_, bin, pconfig);
for (auto it = store_.begin(); it != store_.end() && !cancelled(); ++it) {
// skip unpackable item
@@ -115,13 +131,21 @@ public:
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++) {
while (!was_packed && !cancelled() && placers.size() <= MAX_NUM_PLATES) {
for(; j < placers.size() && !was_packed && !cancelled() && j<MAX_NUM_PLATES; j++) {
if (it->get().is_wipe_tower && it->get().binId() != placers[j].plateID()) {
if (this->unfitindicator_)
this->unfitindicator_(it->get().name + " cant be placed in plate_id=" + std::to_string(j) + "/" + std::to_string(placers.size()) + ", continue to next plate");
continue;
}
result = placers[j].pack(*it, rem(it, store_));
score = result.score();
score_all_plates = score;
score_all_plates = score + COST_OF_NEW_PLATE * j; // add a larger cost to larger plate id to encourace to use less plates
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 (this->unfitindicator_)
this->unfitindicator_((boost::format("item %1% bed_id=%2%, score=%3%, score_all_plates=%4%, pos=(%5%, %6%)") % it->get().name % j % score %
score_all_plates % unscale_(it->get().translation()[0]) % unscale_(it->get().translation()[1]))
.str());
if(score >= 0 && score < LARGE_COST_TO_REJECT) {
if (bed_id_firstfit == -1) {
@@ -161,42 +185,42 @@ public:
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 the object can't be packed, try to pack it without extrusion calibration object
bool placer_not_packed = !was_packed && j > 0 && 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);
if (it->get().has_tried_without_extrusion_cali_obj == false) {
it->get().has_tried_without_extrusion_cali_obj = true;
placers[j - 1].clearItems([](const Item &itm) { return itm.is_extrusion_cali_object; });
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 + " not fit! 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;
this->unfitindicator_(it->get().name + " not fit! plate_id=" + std::to_string(placers.back().plateID()) + ", score=" + std::to_string(score) +
", best_bed_id=" + std::to_string(best_bed_id) + ", score_all_plates=" + std::to_string(score_all_plates) +
", item.bed_id=" + std::to_string(it->get().binId()));
if (!placers.empty() && placers.back().getItems().empty()) {
it->get().binId(BIN_ID_UNFIT);
if (this->unfitindicator_) this->unfitindicator_(it->get().name + " can't fit into a new bin. Can't fit!");
// remove the last empty placer to force next item to be fit in existing plates first
if (placers.size() > 1) placers.pop_back();
break;
}
if (placers.size() < MAX_NUM_PLATES) {
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;
} else {
it->get().binId(BIN_ID_UNFIT);
if (this->unfitindicator_) this->unfitindicator_(it->get().name + " can't fit into any bin. Can't fit!");
break;
}
}
}
}

View File

@@ -443,7 +443,7 @@ inline void offset(PolygonImpl& sh, bp2d::Coord distance)
#ifndef DISABLE_BOOST_SERIALIZE
template<> inline std::string serialize<libnest2d::Formats::SVG>(
const PolygonImpl& sh, double scale, std::string fill, std::string stroke, float stroke_width)
const PolygonImpl& sh, const std::string& name, double scale, std::string fill, std::string stroke, float stroke_width)
{
std::stringstream ss;
std::string style = "fill: "+fill+"; stroke: "+stroke+"; stroke-width: "+std::to_string(stroke_width)+"px; ";
@@ -478,7 +478,14 @@ template<> inline std::string serialize<libnest2d::Formats::SVG>(
ss << svg_data << std::endl;
return ss.str();
std::string svg_content = ss.str();
if (!name.empty()) {
size_t pos = svg_content.find_first_of("<g ");
if (pos != std::string::npos) { svg_content.insert(pos + 3, "id=\"" + name + "\" ");
}
}
return svg_content;
}
#endif

View File

@@ -38,6 +38,8 @@ public:
inline Radians angleToX() const {
double ret = std::atan2(getY(axis_), getX(axis_));
// horizontal or vertical up-right rectangles are not distinguished, but we need to get the horizontal one
if (abs(ret) < EPSILON && abs(bottom_) < abs(right_)) ret += Pi / 2;
auto s = std::signbit(ret);
if(s) ret += Pi_2;
return -ret;

View File

@@ -31,7 +31,7 @@ public:
double x0, y0;
Config():
origo_location(BOTTOMLEFT), mm_in_coord_units(1000000),
width(500), height(500),x0(100) {}
width(500), height(500),x0(100),y0(0) {}
};
@@ -47,14 +47,15 @@ public:
void setSize(const Box &box) {
conf_.x0 = box.width() / 5;
conf_.x0 = box.height() / 5;
conf_.y0 = box.height() / 5;
conf_.height = static_cast<double>(box.height() + conf_.y0*2) /
conf_.mm_in_coord_units;
conf_.width = static_cast<double>(box.width() + conf_.x0*2) /
conf_.mm_in_coord_units;
}
void writeShape(RawShape tsh, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) {
void writeShape(RawShape tsh, const std::string &name = "", std::string fill = "none", std::string stroke = "black", float stroke_width = 1)
{
if(svg_layers_.empty()) addLayer();
if(conf_.origo_location == BOTTOMLEFT) {
auto d = static_cast<Coord>(
@@ -74,13 +75,14 @@ public:
}
}
currentLayer() +=
shapelike::serialize<Formats::SVG>(tsh,
shapelike::serialize<Formats::SVG>(tsh, name,
1.0 / conf_.mm_in_coord_units, fill, stroke, stroke_width) +
"\n";
}
void writeItem(const Item& item, std::string fill = "none", std::string stroke = "black", float stroke_width = 1) {
writeShape(item.transformedShape(), fill, stroke, stroke_width);
void writeItem(const Item &item, const std::string &name = "", std::string fill = "none", std::string stroke = "black", float stroke_width = 1)
{
writeShape(item.transformedShape(), name, fill, stroke, stroke_width);
}
void writePackGroup(const PackGroup& result) {
@@ -97,7 +99,7 @@ public:
auto it = from;
PackGroup pg;
while(it != to) {
if(it->binId() == BIN_ID_UNSET) continue;
if (it->binId() == BIN_ID_UNFIT) continue;
while(pg.size() <= size_t(it->binId())) pg.emplace_back();
pg[it->binId()].emplace_back(*it);
++it;
@@ -159,6 +161,8 @@ public:
currentLayer() += "<!-- " + comment + " -->\n";
}
void clear() { svg_layers_.clear(); }
private:
std::string& currentLayer() { return svg_layers_.back(); }