FIX: low-level NFP bug with auto-arranging

1. FIX low-level NFP bug with auto-arranging due to correctNfpPosition's
  bug.
    jira: STUDIO-5756, STUDIO-4874
2. FIX large items may not be able to be packed due to a too large inflation
    jira: STUDIO-5566, STUDIO-5716
    github: #3194

Change-Id: Id63fb3a5f5fe7ce3a5675c86a3d1440f46866ca3
(cherry picked from commit 9a789262cee4e14f10b673a4f00126d27bcf071b)
This commit is contained in:
Arthur 2024-01-13 19:28:13 +08:00 committed by Lane.Wei
parent 23fae23b06
commit 5145f39f57
7 changed files with 112 additions and 49 deletions

View File

@ -19,6 +19,14 @@ inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
return y1 == y2 ? x1 < x2 : y1 < y2;
}
template<class RawShape, class Unit = TCompute<RawShape>>
inline bool _vsort_max_x(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
{
Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
return y1 == y2 ? x1 > x2 : y1 < y2;
}
template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>>
inline void buildPolygon(const EdgeList& edgelist,
RawShape& rpoly,
@ -166,6 +174,22 @@ TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;
}
/**
* Get the vertex of the polygon that is at the lowest values (bottom) in the Y
* axis and if there are more than one vertices on the same Y coordinate then
* the result will be the leftmost (with the lowest X coordinate).
*/
template<class RawShape>
TPoint<RawShape> leftmostBottomVertex(const RawShape& sh)
{
// find min x and min y vertex
auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh),
__nfp::_vsort<RawShape>);
return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;
}
/**
* A method to get a vertex from a polygon that always maintains a relative
* position to the coordinate system: It is always the rightmost top vertex.
@ -227,7 +251,7 @@ template<class RawBox, class RawShape, class Ratio = double> inline NfpResult<Ra
*
* \tparam RawShape the Polygon data type.
* \param sh The stationary polygon
* \param cother The orbiting polygon
* \param other The orbiting polygon
* \return Returns a pair of the NFP and its reference vertex of the two input
* polygons which have to be strictly convex. The resulting NFP is proven to be
* convex as well in this case.

View File

@ -53,6 +53,9 @@ struct NfpPConfig {
TPoint<RawShape> best_object_pos;
// scaled bed shrink in x and y direction
TPoint<RawShape> bed_shrink;
/**
* @brief A function object representing the fitting function in the
* placement optimization process. (Optional)
@ -168,9 +171,9 @@ template<class RawShape> class EdgeCache {
void createCache(const RawShape& sh) {
{ // For the contour
auto first = shapelike::cbegin(sh);
auto next = std::next(first);
auto endit = shapelike::cend(sh);
auto first = sl::cbegin(sh);
auto endit = sl::cend(sh);
auto next = first == endit ? endit : std::next(first);
contour_.distances.reserve(shapelike::contourVertexCount(sh));
@ -182,12 +185,12 @@ template<class RawShape> class EdgeCache {
}
for(auto& h : shapelike::holes(sh)) { // For the holes
auto first = h.begin();
auto next = std::next(first);
auto endit = h.end();
auto first = sl::cbegin(h);
auto endit = sl::cend(h);
auto next = first == endit ? endit :std::next(first);
ContourCache hc;
hc.distances.reserve(endit - first);
hc.distances.reserve(sl::contourVertexCount(h));
while(next != endit) {
hc.emap.emplace_back(*(first++), *(next++));
@ -217,7 +220,6 @@ template<class RawShape> class EdgeCache {
contour_.corners.reserve(N / S + 1);
contour_.corners.emplace_back(0.0);
auto N_1 = N-1;
contour_.corners.emplace_back(0.0);
for(size_t i = 0; i < N_1; i += S) {
contour_.corners.emplace_back(
contour_.distances.at(i) / contour_.full_distance);
@ -349,12 +351,18 @@ inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
// rightmost upper vertex of the nfp. No proof provided other than Jonas
// Lindmark's reasoning about the reference vertex of nfp in his thesis
// ("No fit polygon problem" - section 2.1.9)
#if 0
auto touch_sh = stationary.rightmostTopVertex();
auto touch_other = orbiter.leftmostBottomVertex();
auto dtouch = touch_sh - touch_other;
auto top_other = orbiter.rightmostTopVertex() + dtouch;
auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point
#else
// move the nfp so that its leftmost bottom vertex touches that of the stationary
auto touch_sh = stationary.leftmostBottomVertex();
auto touch_nfp = nfp::leftmostBottomVertex(nfp.first);
auto dnfp = touch_sh - touch_nfp;
#endif
shapelike::translate(nfp.first, dnfp);
}
@ -363,11 +371,17 @@ inline void correctNfpPosition(nfp::NfpResult<RawShape>& nfp,
const RawShape& stationary,
const _Item<RawShape>& orbiter)
{
#if 0
auto touch_sh = nfp::rightmostUpVertex(stationary);
auto touch_other = orbiter.leftmostBottomVertex();
auto dtouch = touch_sh - touch_other;
auto top_other = orbiter.rightmostTopVertex() + dtouch;
auto dnfp = top_other - nfp.second;
#else
auto touch_sh = nfp::leftmostBottomVertex(stationary);
auto touch_nfp = nfp::leftmostBottomVertex(nfp.first);
auto dnfp = touch_sh - touch_nfp;
#endif
shapelike::translate(nfp.first, dnfp);
}
@ -609,7 +623,8 @@ private:
});
RawShape innerNfp = nfpInnerRectBed(bed, trsh.transformedShape()).first;
return nfp::subtract({innerNfp}, nfps);
Shapes finalNFP = nfp::subtract({ innerNfp }, nfps);
return finalNFP;
}
Shapes calcnfp(const RawShape &sliding, const Shapes &stationarys, const Box &bed, Lvl<nfp::NfpLevel::CONVEX_ONLY>)
@ -731,6 +746,7 @@ private:
bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; });
// 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>());
@ -747,19 +763,28 @@ private:
auto best_rot = item.rotation();
best_overfit = overfit(item.transformedShape(), bin_) + overlapWithVirtObject();
for(auto rot : config_.rotations) {
item.translation(initial_tr);
item.rotation(initial_rot + rot);
setInitialPosition(item);
double of = 0.;
if ((of = overfit(item.transformedShape(), bin_)) + overlapWithVirtObject() < best_overfit) {
best_overfit = of;
best_tr = item.translation();
best_rot = item.rotation();
// 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())};
for (size_t i = 0; i < 2; i++) {
item.inflation(inflations[i]);
for (auto rot : config_.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();
}
}
can_pack = best_overfit <= 0;
if(can_pack) break;
}
can_pack = best_overfit <= 0;
item.inflation(inflation_back);
if (can_pack)
global_score = 0.2;
item.rotation(best_rot);
@ -967,14 +992,20 @@ private:
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++)
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.writeItem(item, "none", "red", 2);
svgwriter.add_comment("current item");
svgwriter.writeItem(item, "red", "red", 2);
std::stringstream ss;
ss.setf(std::ios::fixed | std::ios::showpoint);
@ -987,7 +1018,7 @@ private:
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("SVG")/ ("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg"));
svgwriter.save(boost::filesystem::path("C:/Users/arthur.tang/AppData/Roaming/BambuStudioInternal/SVG")/ ("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg"));
#endif
if(can_pack) {
@ -1115,27 +1146,32 @@ private:
// BBS 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()); }
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 (const Item &item : items_) {
if (item.isFixed()) continue;
objs.push_back(item.transformedShape());
for (Item &item : items_) {
if (item.isFixed()) {
excludes.push_back(item.transformedShape());
}
else {
// better center a single large object without any inflation
if (n_objs == 1)
item.inflation(0);
objs.push_back(item.transformedShape());
}
}
if (objs.empty())
return;
{ // find a best position inside NFP of fixed items (excluded regions), so the center of pile is cloest to bed center
RawShape objs_convex_hull = sl::convexHull(objs);
for (const Item &item : items_) {
if (item.isFixed()) {
excludes.push_back(item.transformedShape());
}
}
auto nfps = calcnfp(objs_convex_hull, excludes, bbin, Lvl<MaxNfpLevel::value>());
if (nfps.empty()) {
return;

View File

@ -182,7 +182,7 @@ public:
if(!was_packed){
if (this->unfitindicator_ && !placers.empty())
this->unfitindicator_(it->get().name + ", height=" +std::to_string(it->get().height)
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)

View File

@ -153,6 +153,12 @@ public:
};
}
void add_comment(const std::string comment)
{
if (svg_layers_.empty()) addLayer();
currentLayer() += "<!-- " + comment + " -->\n";
}
private:
std::string& currentLayer() { return svg_layers_.back(); }

View File

@ -119,13 +119,6 @@ void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPri
// 3. otherwise, use each object's own brim width
ap.inflation = params.min_obj_distance != 0 ? params.min_obj_distance / 2 :
plate_has_tree_support ? scaled(brim_max / 2) : scaled(ap.brim_width);
BoundingBox apbb = ap.poly.contour.bounding_box();
auto diffx = bedbb.size().x() - apbb.size().x() - 5;
auto diffy = bedbb.size().y() - apbb.size().y() - 5;
if (diffx > 0 && diffy > 0) {
auto min_diff = std::min(diffx, diffy);
ap.inflation = std::min(min_diff / 2, ap.inflation);
}
});
}
@ -296,6 +289,8 @@ void fill_config(PConf& pcfg, const ArrangeParams &params) {
else
pcfg.rotations = {0.};
pcfg.bed_shrink = { scale_(params.bed_shrink_x), scale_(params.bed_shrink_y) };
// The accuracy of optimization.
// Goes from 0.0 to 1.0 and scales performance as well
pcfg.accuracy = params.accuracy;

View File

@ -126,8 +126,8 @@ struct ArrangeParams {
bool avoid_extrusion_cali_region = true;
bool is_seq_print = false;
bool align_to_y_axis = false;
float bed_shrink_x = 1;
float bed_shrink_y = 1;
float bed_shrink_x = 0.1;
float bed_shrink_y = 0.1;
float brim_skirt_distance = 0;
float clearance_height_to_rod = 0;
float clearance_height_to_lid = 0;

View File

@ -710,6 +710,12 @@ void ArrangeJob::finalize() {
}
m_plater->get_notification_manager()->close_notification_of_type(NotificationType::ArrangeOngoing);
// unlock the plates we just locked
for (int i : m_uncompatible_plates) {
PartPlate* plate = plate_list.get_plate(i);
if (plate) plate->lock(false);
}
//BBS: reload all objects due to arrange
if (only_on_partplate) {
plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true, current_plate_index);
@ -718,10 +724,6 @@ void ArrangeJob::finalize() {
plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true);
}
// unlock the plates we just locked
for (int i : m_uncompatible_plates)
plate_list.get_plate(i)->lock(false);
// BBS: update slice context and gcode result.
m_plater->update_slicing_context_to_current_partplate();