diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 088a9bb33..bf333909e 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -559,10 +559,12 @@ public: template> 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; } @@ -1050,6 +1052,101 @@ private: return ret; } + PackResult _trypack_with_original_pos(Item &item) + { + PackResult ret; + + bool can_pack = false; + double best_overfit = std::numeric_limits::max(); + double global_score = std::numeric_limits::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("existing object: " + it.get().name); } + + // 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()); + auto v = item.referenceVertex(); + for (const RawShape &nfp : nfps) { + if (sl::isInside(v, nfp) || sl::touches(v, nfp)) { return 0; } + } + return 1; + }; + + { + for (auto rot : item.allowed_rotations) { + item.translation(initial_tr); + item.rotation(initial_rot + rot); + + if (0==overlapWithVirtObject()) { + 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); + } + +#ifdef SVGTOOLS_HPP + if (config_.save_svg) { + svg::SVGWriter 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/BambuStudioInternal/SVG") / + ("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg")); + } +#endif + + if (can_pack) { + ret = PackResult(item); + ret.score_ = global_score; + // merged_pile_ = nfp::merge(merged_pile_, item.transformedShape()); + } else { + ret = PackResult(best_overfit); + } + + return ret; + } + RawShape box2RawShape(Box& bbin) { RawShape binrsh; diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index 6d00d94c8..532419e07 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -118,6 +118,11 @@ public: int j = 0; while(!was_packed && !cancelled()) { for(; j < placers.size() && !was_packed && !cancelled(); 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; @@ -184,11 +189,11 @@ public: 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) + + " ,plate_id=" + std::to_string(j) + ", 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())); + +", item.bed_id=" + std::to_string(it->get().binId())); placers.emplace_back(bin); placers.back().plateID(placers.size() - 1); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 052307bda..9c8da3b0e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1033,6 +1033,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); //BBS: add arrange and orient event wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE_PARTPLATE, SimpleEvent); +wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE_OUTPLATE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ORIENT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ORIENT_PARTPLATE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL, SimpleEvent); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index bfa700c98..efd4af616 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -162,6 +162,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent); //BBS: add arrange and orient event wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE_PARTPLATE, SimpleEvent); +wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE_OUTPLATE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ORIENT, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ORIENT_PARTPLATE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL, SimpleEvent); diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index c9b7f4633..2598d5bd7 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -30,9 +30,10 @@ public: : GLCanvas3D::WipeTowerInfo(std::move(wti)) {} - void apply_arrange_result(const Vec2d& tr, double rotation, int item_id) + void apply_arrange_result(const Vec2d& tr, double rotation, int item_id, int bed_id) { m_pos = unscaled(tr); m_rotation = rotation; + m_plate_idx = bed_id; apply_wipe_tower(); } @@ -71,7 +72,13 @@ arrangement::ArrangePolygon get_wipetower_arrange_poly(WipeTower* tower) { ArrangePolygon ap = tower->get_arrange_polygon(); ap.bed_idx = 0; - ap.setter = NULL; // do not move wipe tower + //ap.setter = NULL; // do not move wipe tower + ap.setter = [tower](const ArrangePolygon &p) { + if (p.is_arranged()) { + Vec2d t = p.translation.cast(); + tower->apply_arrange_result(t, p.rotation, p.itemid, p.bed_idx); + } + }; return ap; } @@ -98,7 +105,7 @@ void ArrangeJob::clear_input() current_plate_index = 0; } -ArrangePolygon ArrangeJob::prepare_arrange_polygon(void* model_instance) +ArrangePolygon ArrangeJob::prepare_arrange_polygon(void *model_instance) { ModelInstance* instance = (ModelInstance*)model_instance; auto preset_bundle = wxGetApp().preset_bundle; @@ -301,7 +308,7 @@ arrangement::ArrangePolygon estimate_wipe_tower_info(int plate_index, std::setname; + //ARRANGE_LOG(debug) << __FUNCTION__ << boost::format(": skip locked instance, obj_id %1%, name %2%") % oidx % mo->name; } } } @@ -450,9 +461,9 @@ void ArrangeJob::prepare_partplate() { void ArrangeJob::prepare_outside_plate() { clear_input(); - - std::set> all_inside_objects; - std::set> all_outside_objects; + typedef std::tuple obj_inst_plate_t; + std::map,int> all_inside_objects; + std::map, int> all_outside_objects; Model &model = m_plater->model(); PartPlateList &plate_list = m_plater->get_partplate_list(); @@ -468,9 +479,8 @@ void ArrangeJob::prepare_outside_plate() { continue; } - all_inside_objects.insert(plate_objects.begin(), plate_objects.end()); - if (!plate_outside_objects.empty()) - all_outside_objects.insert(plate_outside_objects.begin(), plate_outside_objects.end()); + for (auto &obj_inst : plate_objects) { all_inside_objects[obj_inst] = plate_idx; } + for (auto &obj_inst : plate_outside_objects) { all_outside_objects[obj_inst] = plate_idx; } if (plate->is_locked()) { ARRANGE_LOG(info) << __FUNCTION__ << format(": skip locked plate %d!", plate_idx); @@ -478,24 +488,22 @@ void ArrangeJob::prepare_outside_plate() { } // if there are objects inside the plate, lock the plate and don't put new objects in it - if (plate_objects.size() > plate_outside_objects.size()) { - plate->lock(true); - m_uncompatible_plates.push_back(plate_idx); - ARRANGE_LOG(info) << __FUNCTION__ << format(": lock plate %d because there are objects inside!", plate_idx); - } + //if (plate_objects.size() > plate_outside_objects.size()) { + // plate->lock(true); + // m_uncompatible_plates.push_back(plate_idx); + // ARRANGE_LOG(info) << __FUNCTION__ << format(": lock plate %d because there are objects inside!", plate_idx); + //} } for (int obj_idx = 0; obj_idx < model.objects.size(); obj_idx++) { ModelObject *object = model.objects[obj_idx]; for (size_t inst_idx = 0; inst_idx < object->instances.size(); ++inst_idx) { ModelInstance * instance = object->instances[inst_idx]; - std::set>::iterator iter1, iter2; - iter1 = all_inside_objects.find(std::pair(obj_idx, inst_idx)); - iter2 = all_outside_objects.find(std::pair(obj_idx, inst_idx)); + auto iter1 = all_inside_objects.find(std::pair(obj_idx, inst_idx)); + auto iter2 = all_outside_objects.find(std::pair(obj_idx, inst_idx)); bool outside_plate = false; if (iter1 == all_inside_objects.end()) { - //skip continue; } if (iter2 != all_outside_objects.end()) { @@ -505,13 +513,15 @@ void ArrangeJob::prepare_outside_plate() { ArrangePolygons &cont = instance->printable ? (outside_plate ? m_selected : m_locked) : m_unprintable; ap.itemid = cont.size(); if (!outside_plate) { - plate_list.preprocess_arrange_polygon(obj_idx, inst_idx, ap, true); + plate_list.preprocess_arrange_polygon(obj_idx, inst_idx, ap, false); + ap.bed_idx = iter1->second; + ap.locked_plate = iter1->second; } cont.emplace_back(std::move(ap)); } } - prepare_wipe_tower(); + prepare_wipe_tower(true); // add the virtual object into unselect list if has plate_list.preprocess_exclude_areas(m_unselected, current_plate_index + 1); @@ -544,6 +554,7 @@ void ArrangeJob::prepare() prepare_outside_plate(); } + ARRANGE_LOG(info) << "prepare state: " << state << ", items selected : " << m_selected.size(); #if SAVE_ARRANGE_POLY if (1) @@ -586,6 +597,12 @@ void ArrangeJob::prepare() #endif check_unprintable(); + + if (!m_selected.empty()) { + m_plater->get_notification_manager()->push_notification(NotificationType::ArrangeOngoing, NotificationManager::NotificationLevel::RegularNotificationLevel, + _u8L("Arranging...")); + m_plater->get_notification_manager()->bbl_close_plateinfo_notification(); + } } void ArrangeJob::check_unprintable() @@ -640,7 +657,7 @@ void ArrangeJob::process() partplate_list.preprocess_exclude_areas(params.excluded_regions, 1, scale_(1)); - BOOST_LOG_TRIVIAL(debug) << "arrange bedpts:" << bedpts[0].transpose() << ", " << bedpts[1].transpose() << ", " << bedpts[2].transpose() << ", " << bedpts[3].transpose(); + ARRANGE_LOG(debug) << "bedpts:" << bedpts[0].transpose() << ", " << bedpts[1].transpose() << ", " << bedpts[2].transpose() << ", " << bedpts[3].transpose(); params.stopcondition = [this]() { return was_canceled(); }; @@ -648,35 +665,36 @@ void ArrangeJob::process() update_status(num_finished, _L("Arranging") + " "+ wxString::FromUTF8(str)); }; + ArrangePolygons unselected_and_locked = m_unselected; + append(unselected_and_locked, m_locked); { - BOOST_LOG_TRIVIAL(warning)<< "Arrange full params: "<< params.to_json(); - BOOST_LOG_TRIVIAL(info) << boost::format("arrange: items selected before arranging: %1%") % m_selected.size(); + ARRANGE_LOG(warning)<< "full params: "<< params.to_json(); + ARRANGE_LOG(info) << boost::format("items selected before arranging: %1%") % m_selected.size(); for (auto selected : m_selected) { - BOOST_LOG_TRIVIAL(debug) << selected.name << ", extruder: " << VectorFormatter(selected.extrude_ids) + ARRANGE_LOG(debug) << selected.name << ", extruder: " << VectorFormatter(selected.extrude_ids) << ", filament types: " << VectorFormatter(selected.filament_types) << ", bed: " << selected.bed_idx << ", filemant_type:" << selected.filament_temp_type << ", trans: " << unscale(selected.translation(X)) << "," << unscale(selected.translation(Y)) << ", rotation: " << selected.rotation; } - BOOST_LOG_TRIVIAL(debug) << "arrange: items unselected before arrange: " << m_unselected.size(); - for (auto item : m_unselected) - BOOST_LOG_TRIVIAL(debug) << item.name << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose() - <<", bbox:"<(item.translation(X)) << "," << unscale(item.translation(Y)); } - arrangement::arrange(m_selected, m_unselected, bedpts, params); + arrangement::arrange(m_selected, unselected_and_locked, bedpts, params); // sort by item id std::sort(m_selected.begin(), m_selected.end(), [](auto a, auto b) {return a.itemid < b.itemid; }); { - BOOST_LOG_TRIVIAL(info) << boost::format("arrange: items selected after arranging: %1%") % m_selected.size(); + ARRANGE_LOG(info) << boost::format("items selected after arranging: %1%") % m_selected.size(); for (auto selected : m_selected) - BOOST_LOG_TRIVIAL(debug) << selected.name << ", extruder: " << VectorFormatter(selected.extrude_ids) << ", bed: " << selected.bed_idx + ARRANGE_LOG(debug) << selected.name << ", extruder: " << VectorFormatter(selected.extrude_ids) << ", bed: " << selected.bed_idx << ", bed_temp: " << selected.first_bed_temp << ", print_temp: " << selected.print_temp << ", trans: " << unscale(selected.translation(X)) << "," << unscale(selected.translation(Y)) << ", rotation: " << selected.rotation; - BOOST_LOG_TRIVIAL(debug) << "arrange: items unselected after arrange: "<< m_unselected.size(); - for (auto item : m_unselected) - BOOST_LOG_TRIVIAL(debug) << item.name << ", bed: " << item.bed_idx << ", trans: " << item.translation.transpose() << ", rotation: " << item.rotation; + ARRANGE_LOG(debug) << "items unselected after arrange: " << unselected_and_locked.size(); + for (auto item : unselected_and_locked) + ARRANGE_LOG(debug) << item.name << ", bed: " << item.bed_idx << ", trans: " << unscale(item.translation(X)) << "," << unscale(item.translation(Y)); } // put unpackable items to m_unprintable so they goes outside @@ -732,7 +750,7 @@ void ArrangeJob::finalize() beds = std::max(ap.bed_idx, beds); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": arrange selected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; + ARRANGE_LOG(debug) << __FUNCTION__ << boost::format(": selected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; } //BBS: adjust the bed_index, create new plates, get the max bed_index @@ -745,7 +763,7 @@ void ArrangeJob::finalize() plate_list.postprocess_bed_index_for_unselected(ap); beds = std::max(ap.bed_idx, beds); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange unselected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; + ARRANGE_LOG(debug) << __FUNCTION__ << boost::format(": unselected %4%: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; } for (ArrangePolygon& ap : m_locked) { @@ -782,7 +800,7 @@ void ArrangeJob::finalize() plate_list.postprocess_arrange_polygon(ap, true); ap.apply(); - BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":arrange m_unprintable: name: %4%, bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; + ARRANGE_LOG(debug) << __FUNCTION__ << boost::format(": m_unprintable: name: %4%, bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)) % ap.name; } m_plater->update(); diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 4dbfc77f1..49fa0cef7 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -42,9 +42,7 @@ class ArrangeJob : public PlaterJob // prepare the items which are selected and not on the current partplate void prepare_outside_plate(); - void prepare_wipe_tower(); - - ArrangePolygon prepare_arrange_polygon(void* instance); + void prepare_wipe_tower(bool select = false); protected: @@ -68,6 +66,8 @@ public: } void finalize() override; + + static ArrangePolygon prepare_arrange_polygon(void *instance); }; std::optional get_wipe_tower_arrangepoly(const Plater &); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index e39825ed9..c8aa5abb9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4375,7 +4375,14 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE_PARTPLATE, [this](SimpleEvent& evt) { //BBS arrage from EVT set default state. this->q->set_prepare_state(Job::PREPARE_STATE_MENU); - this->q->arrange(); }); + this->q->arrange(); + }); + view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE_OUTPLATE, [this](SimpleEvent &evt) { + if (this->q->last_arrange_job_is_finished()) { + this->q->set_prepare_state(Job::PREPARE_STATE_OUTSIDE_BED); + this->q->arrange(); + } + }); view3D_canvas->Bind(EVT_GLCANVAS_ORIENT, [this](SimpleEvent& evt) { //BBS oriant from EVT set default state. this->q->set_prepare_state(Job::PREPARE_STATE_DEFAULT); @@ -9437,6 +9444,7 @@ void Plater::priv::update_objects_position_when_select_preset(const std::functio bool cur_plate_is_smaller = cur_plate_size.x() + 1.0 < old_plate_size.x() || cur_plate_size.y() + 1.0 < old_plate_size.y(); BOOST_LOG_TRIVIAL(info) << format("change bed pos from (%.0f,%.0f) to (%.0f,%.0f)", old_plate_pos.x(), old_plate_pos.y(), cur_plate_pos.x(), cur_plate_pos.y()); + bool plate_not_empty = std::any_of(plate_object.begin(), plate_object.end(), [](const std::vector &obj_idxs) { return !obj_idxs.empty(); }); if (old_plate_pos.x() != cur_plate_pos.x() || old_plate_pos.y() != cur_plate_pos.y() || cur_plate_is_smaller) { for (int i = 0; i < plate_object.size(); ++i) { view3D->select_object_from_idx(plate_object[i]); @@ -9445,7 +9453,7 @@ void Plater::priv::update_objects_position_when_select_preset(const std::functio } BOOST_LOG_TRIVIAL(info) << format("change bed size from (%.0f,%.0f) to (%.0f,%.0f)", old_plate_size.x(), old_plate_size.y(), cur_plate_size.x(), cur_plate_size.y()); - if (cur_plate_is_smaller && std::any_of(plate_object.begin(), plate_object.end(), [](const std::vector& obj_idxs) { return !obj_idxs.empty(); })) { + if (cur_plate_is_smaller && plate_not_empty) { take_snapshot("Arrange after bed size changes"); //collect all the objects on the current plates std::set new_all_plate_object; @@ -9465,8 +9473,6 @@ void Plater::priv::update_objects_position_when_select_preset(const std::functio obj_out_set.emplace(std::pair{i, 0}); } } - q->set_prepare_state(Job::PREPARE_STATE_OUTSIDE_BED); - q->arrange(); } #if 0 const BoundingBoxf3 &cur_platelist_bbox = cur_plate_list.get_bounding_box(); @@ -9504,6 +9510,8 @@ void Plater::priv::update_objects_position_when_select_preset(const std::functio #endif view3D->deselect_all(); } + + wxQueueEvent(view3D->get_wxglcanvas(), new SimpleEvent(EVT_GLCANVAS_ARRANGE_OUTPLATE)); } void Plater::orient()