From 3acd89e877db034e9755a77c3c11f1538402a0c9 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 7 Aug 2023 23:42:27 +0800 Subject: [PATCH] Merge some BS1.7 changes: bring back fill_bed feature, original implemention from PrusaSlicer. BS added exclusion logic. --- src/libslic3r/Arrange.cpp | 45 ++--- src/libslic3r/Arrange.hpp | 4 +- src/libslic3r/PerimeterGenerator.cpp | 2 +- src/libslic3r/Preset.cpp | 8 +- src/libslic3r/Preset.hpp | 2 +- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 6 +- src/slic3r/GUI/GUI_Factories.cpp | 12 +- src/slic3r/GUI/GUI_Factories.hpp | 3 +- src/slic3r/GUI/GUI_ObjectList.cpp | 5 +- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 206 +++++++++++--------- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 10 + src/slic3r/GUI/Jobs/FillBedJob.cpp | 85 ++++---- src/slic3r/GUI/Jobs/FillBedJob.hpp | 2 + src/slic3r/GUI/MainFrame.cpp | 8 +- src/slic3r/GUI/PlateSettingsDialog.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 6 +- src/slic3r/GUI/PresetComboBoxes.cpp | 4 +- src/slic3r/GUI/Tab.cpp | 8 +- 20 files changed, 242 insertions(+), 180 deletions(-) diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index eeb385676..9b4f493ef 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -86,18 +86,21 @@ template void fill_config(PConf& pcfg, const ArrangeParams ¶ms) { if (params.is_seq_print) { - // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; // Start placing the items from the center of the print bed pcfg.starting_point = PConf::Alignment::BOTTOM_LEFT; } else { - // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; // Start placing the items from the center of the print bed pcfg.starting_point = PConf::Alignment::TOP_RIGHT; } + if (params.do_final_align) { + // Align the arranged pile into the center of the bin + pcfg.alignment = PConf::Alignment::CENTER; + }else + pcfg.alignment = PConf::Alignment::DONT_ALIGN; + + // Try 4 angles (45 degree step) and find the one with min cost if (params.allow_rotations) pcfg.rotations = {0., PI / 4., PI/2, 3. * PI / 4. }; @@ -452,27 +455,25 @@ protected: } std::set extruder_ids; - int non_virt_cnt = 0; - std::set first_object_extruder_ids; for (int i = 0; i < m_items.size(); i++) { Item& p = m_items[i]; if (p.is_virt_object) continue; extruder_ids.insert(p.extrude_ids.begin(),p.extrude_ids.end()); - non_virt_cnt++; - if (non_virt_cnt == 1) { first_object_extruder_ids.insert(p.extrude_ids.begin(), p.extrude_ids.end()); } } - extruder_ids.insert(item.extrude_ids.begin(),item.extrude_ids.end()); // add a large cost if not multi materials on same plate is not allowed if (!params.allow_multi_materials_on_same_plate) { - bool first_object = non_virt_cnt == 0; - bool same_color_with_first_object = std::all_of(item.extrude_ids.begin(), item.extrude_ids.end(), - [&](int color) { return first_object_extruder_ids.find(color) != first_object_extruder_ids.end(); }); - // non_virt_cnt==0 means it's the first object, which can be multi-color - if (!(first_object || same_color_with_first_object)) score += LARGE_COST_TO_REJECT * 1.3; + // it's the first object, which can be multi-color + bool first_object = extruder_ids.empty(); + // the two objects (previously packed items and the current item) are considered having same color if either one's colors are a subset of the other + std::set item_extruder_ids(item.extrude_ids.begin(), item.extrude_ids.end()); + bool same_color_with_previous_items = std::includes(item_extruder_ids.begin(), item_extruder_ids.end(), extruder_ids.begin(), extruder_ids.end()) + || std::includes(extruder_ids.begin(), extruder_ids.end(), item_extruder_ids.begin(), item_extruder_ids.end()); + if (!(first_object || same_color_with_previous_items)) score += LARGE_COST_TO_REJECT * 1.3; } // for layered printing, we want extruder change as few as possible // this has very weak effect, CAN NOT use a large weight + extruder_ids.insert(item.extrude_ids.begin(), item.extrude_ids.end()); if (!params.is_seq_print) { score += 1 * std::max(0, ((int) extruder_ids.size() - 1)); } @@ -544,12 +545,6 @@ public: if (items.empty()) return; auto binbb = sl::boundingBox(m_bin); - // BBS: excluded region (virtual object but not wipe tower) should not affect final alignment - //bool all_is_excluded_region = std::all_of(items.begin(), items.end(), [](Item &itm) { return itm.is_virt_object && !itm.is_wipe_tower; }); - //if (!all_is_excluded_region) - // cfg.alignment = PConfig::Alignment::DONT_ALIGN; - //else - // cfg.alignment = PConfig::Alignment::CENTER; auto starting_point = cfg.starting_point == PConfig::Alignment::BOTTOM_LEFT ? binbb.minCorner() : binbb.center(); // if we have wipe tower, items should be arranged around wipe tower @@ -561,15 +556,7 @@ public: } cfg.object_function = [this, binbb, starting_point](const Item &item, const ItemGroup &packed_items) { - // 在我们的摆盘中,没有天然的固定对象。固定对象只有:屏蔽区域、挤出补偿区域、料塔。 - // 对于屏蔽区域,摆入的对象仍然是可以向右上滑动的; - // 对挤出料塔,摆入的对象不能滑动(必须围绕料塔) - bool pack_around_wipe_tower = std::any_of(packed_items.begin(), packed_items.end(), [](Item& itm) { return itm.is_wipe_tower; }); - //if(pack_around_wipe_tower) - return fixed_overfit(objfunc(item, starting_point), binbb); - //else { - // return fixed_overfit_topright_sliding(objfunc(item, starting_point), binbb, m_excluded_and_extruCali_regions); - //} + return fixed_overfit(objfunc(item, starting_point), binbb); }; }; diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index d1f905b89..a9b7cc484 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -57,7 +57,7 @@ struct ArrangePolygon { //BBS: add row/col for sudoku-style layout int row{0}; int col{0}; - std::vector extrude_ids{1}; ///extruder_id for least extruder switch + std::vector extrude_ids{}; /// extruder_id for least extruder switch int bed_temp{0}; ///bed temperature for different material judge int print_temp{0}; ///print temperature for different material judge int first_bed_temp{ 0 }; ///first layer bed temperature for different material judge @@ -114,6 +114,8 @@ struct ArrangeParams { bool allow_rotations = false; + bool do_final_align = true; + //BBS: add specific arrange params bool allow_multi_materials_on_same_plate = true; bool avoid_extrusion_cali_region = true; diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 59d9cb8ab..dab6641e5 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -807,7 +807,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP offset_top_surface = 0; // don't takes into account too thin areas // skip if the exposed area is smaller than 2x perimeter width - double min_width_top_surface = std::max(double(ext_perimeter_spacing / 2 + 10), 2.0 * (double(perimeter_width))); + double min_width_top_surface = std::max(double(ext_perimeter_spacing / 2 + 10), 2.5 * (double(perimeter_width))); Polygons grown_upper_slices = offset(*this->upper_slices, min_width_top_surface); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 76af2566f..6bddf074a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -685,9 +685,9 @@ bool Preset::is_custom_defined() return false; } -bool Preset::is_bbl_vendor_preset(PresetBundle *preset_bundle) +bool Preset::has_lidar(PresetBundle *preset_bundle) { - bool is_bbl_vendor_preset = false; + bool has_lidar = false; if (preset_bundle) { auto config = &preset_bundle->printers.get_edited_preset().config; std::string vendor_name; @@ -700,9 +700,9 @@ bool Preset::is_bbl_vendor_preset(PresetBundle *preset_bundle) } } if (!vendor_name.empty()) - is_bbl_vendor_preset = vendor_name.compare("BBL") == 0 ? true : false; + has_lidar = vendor_name.compare("BBL") == 0 ? true : false; } - return is_bbl_vendor_preset; + return has_lidar; } static std::vector s_Preset_print_options { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index e319f60cb..51af082bc 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -302,7 +302,7 @@ public: std::string get_current_printer_type(PresetBundle *preset_bundle); // get current preset type bool is_custom_defined(); - bool is_bbl_vendor_preset(PresetBundle *preset_bundle); + bool has_lidar(PresetBundle *preset_bundle); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index ecd528b54..f771b4d78 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -187,7 +187,7 @@ void BackgroundSlicingProcess::process_fff() { assert(m_print == m_fff_print); PresetBundle &preset_bundle = *wxGetApp().preset_bundle; - m_fff_print->is_BBL_printer() = preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle); + m_fff_print->is_BBL_printer() = preset_bundle.printers.get_edited_preset().has_lidar(&preset_bundle); //BBS: add the logic to process from an existed gcode file if (m_print->finished()) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" %1%: skip slicing, to process previous gcode file")%__LINE__; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index d322fe3d4..486d39002 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5399,11 +5399,13 @@ bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, flo // only show this option if the printer has micro Lidar and can do first layer scan DynamicPrintConfig ¤t_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + const bool has_lidar = preset_bundle->printers.get_edited_preset().has_lidar(preset_bundle); auto op = current_config.option("scan_first_layer"); - if (op && op->getBool()) { + if (has_lidar && op && op->getBool()) { if (imgui->bbl_checkbox(_L("Avoid extrusion calibration region"), settings.avoid_extrusion_cali_region)) { settings_out.avoid_extrusion_cali_region = settings.avoid_extrusion_cali_region; - appcfg->set("arrange", avoid_extrusion_key.c_str(), settings_out.avoid_extrusion_cali_region); + appcfg->set("arrange", avoid_extrusion_key.c_str(), settings_out.avoid_extrusion_cali_region ? "1" : "0"); settings_changed = true; } } else { diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 4c3d21cac..41167376e 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -656,6 +656,13 @@ wxMenuItem* MenuFactory::append_menu_item_instance_to_object(wxMenu* menu) return menu_item; } +void MenuFactory::append_menu_item_fill_bed(wxMenu *menu) +{ + append_menu_item( + menu, wxID_ANY, _L("Fill bed with copies"), _L("Fill the remaining area of bed with copies of the selected object"), + [](wxCommandEvent &) { plater()->fill_bed_with_instances(); }, "", nullptr, []() { return plater()->can_increase_instances(); }, m_parent); +} + wxMenuItem* MenuFactory::append_menu_item_printable(wxMenu* menu) { // BBS: to be checked @@ -1040,8 +1047,9 @@ void MenuFactory::create_object_menu() // "Add (volumes)" popupmenu will be added later in append_menu_items_add_volume() } -void MenuFactory::create_bbl_object_menu() +void MenuFactory::create_extra_object_menu() { + append_menu_item_fill_bed(&m_object_menu); // Object Clone append_menu_item_clone(&m_object_menu); // Object Repair @@ -1262,7 +1270,7 @@ void MenuFactory::init(wxWindow* parent) create_sla_object_menu(); //create_part_menu(); - create_bbl_object_menu(); + create_extra_object_menu(); create_bbl_part_menu(); create_bbl_assemble_object_menu(); create_bbl_assemble_part_menu(); diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 3d99a072b..2076e630e 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -108,7 +108,7 @@ private: //BBS: add part plate related logic void create_plate_menu(); //BBS: add bbl object menu - void create_bbl_object_menu(); + void create_extra_object_menu(); void create_bbl_part_menu(); void create_bbl_assemble_object_menu(); void create_bbl_assemble_part_menu(); @@ -150,6 +150,7 @@ private: void append_menu_item_change_filament(wxMenu* menu); void append_menu_item_set_printable(wxMenu* menu); void append_menu_item_locked(wxMenu* menu); + void append_menu_item_fill_bed(wxMenu *menu); }; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 1b676ab67..f55b3ccfc 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -3547,7 +3547,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio -void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed, bool notify_partplate) +void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed, bool notify_partplate, bool do_info_update) { auto model_object = (*m_objects)[obj_idx]; //BBS start add obj_idx for debug @@ -3564,6 +3564,9 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed, const auto item = m_objects_model->AddObject(model_object, warning_bitmap, model_object->is_cut()); Expand(m_objects_model->GetParent(item)); + if (!do_info_update) + return; + update_info_items(obj_idx, nullptr, call_selection_changed); add_volumes_to_object_in_list(obj_idx); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index da1302ed5..5793b59d3 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -325,7 +325,7 @@ public: void part_selection_changed(); // Add object to the list - void add_object_to_list(size_t obj_idx, bool call_selection_changed = true, bool notify_partplate = true); + void add_object_to_list(size_t obj_idx, bool call_selection_changed = true, bool notify_partplate = true, bool do_info_update = true); // Add object's volumes to the list // Return selected items, if add_to_selection is defined wxDataViewItemArray add_volumes_to_object_in_list(size_t obj_idx, std::function add_to_selection = nullptr); diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 75bf5875f..2b5fc8075 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -353,7 +353,7 @@ void ArrangeJob::prepare_partplate() { ModelObject* mo = model.objects[oidx]; for (size_t inst_idx = 0; inst_idx < mo->instances.size(); ++inst_idx) { - bool in_plate = plate->contain_instance(oidx, inst_idx); + bool in_plate = plate->contain_instance(oidx, inst_idx) || plate->intersect_instance(oidx, inst_idx); ArrangePolygon&& ap = prepare_arrange_polygon(mo->instances[inst_idx]); ArrangePolygons& cont = mo->instances[inst_idx]->printable ? @@ -392,20 +392,7 @@ void ArrangeJob::prepare() NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Arranging...")); m_plater->get_notification_manager()->bbl_close_plateinfo_notification(); - { - const GLCanvas3D::ArrangeSettings &settings = static_cast(m_plater->canvas3D())->get_arrange_settings(); - auto & print = wxGetApp().plater()->get_partplate_list().get_current_fff_print(); - - params.clearance_height_to_rod = print.config().extruder_clearance_height_to_rod.value; - params.clearance_height_to_lid = print.config().extruder_clearance_height_to_lid.value; - params.cleareance_radius = print.config().extruder_clearance_radius.value; - params.printable_height = print.config().printable_height.value; - params.allow_rotations = settings.enable_rotation; - params.allow_multi_materials_on_same_plate = settings.allow_multi_materials_on_same_plate; - params.avoid_extrusion_cali_region = settings.avoid_extrusion_cali_region; - params.is_seq_print = settings.is_seq_print; - params.min_obj_distance = scaled(settings.distance); - } + params = init_arrange_params(m_plater); //BBS update extruder params and speed table before arranging Plater::setExtruderParams(Model::extruderParamsMap); @@ -510,85 +497,27 @@ void ArrangeJob::process() auto & partplate_list = m_plater->get_partplate_list(); auto& print = wxGetApp().plater()->get_partplate_list().get_current_fff_print(); - if (params.is_seq_print) - params.min_obj_distance = std::max(params.min_obj_distance, scaled(params.cleareance_radius + 0.001)); // +0.001mm to avoid clearance check fail due to rounding error - - if (params.avoid_extrusion_cali_region && print.full_print_config().opt_bool("scan_first_layer")) + const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config(); + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + const bool has_lidar = preset_bundle->printers.get_edited_preset().has_lidar(preset_bundle); + if (has_lidar && params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer")) partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES); - double skirt_distance = print.has_skirt() ? print.config().skirt_distance.value : 0; - double brim_max = 0; - std::for_each(m_selected.begin(), m_selected.end(), [&](ArrangePolygon ap) { brim_max = std::max(brim_max, ap.brim_width); }); + update_arrange_params(params, *m_plater, m_selected); + update_selected_items_inflation(m_selected, *m_plater, params); + update_unselected_items_inflation(m_unselected, *m_plater, params); - // Note: skirt_distance is now defined between outermost brim and skirt, not the object and skirt. - // So we can't do max but do adding instead. - params.brim_skirt_distance = skirt_distance + brim_max; - params.bed_shrink_x = settings.bed_shrink_x + params.brim_skirt_distance; - params.bed_shrink_y = settings.bed_shrink_y + params.brim_skirt_distance; - // for sequential print, we need to inflate the bed because cleareance_radius is so large - if (params.is_seq_print) { - float shift_dist = params.cleareance_radius / 2 - 5; - params.bed_shrink_x -= shift_dist; - params.bed_shrink_y -= shift_dist; - // dont forget to move the excluded region - for (auto& region : m_unselected) { - if (region.is_virt_object) - region.poly.translate(-scaled(shift_dist), -scaled(shift_dist)); - } - } - - if (print.full_print_config().opt_bool("enable_support")) { - params.bed_shrink_x = std::max(5.f, params.bed_shrink_x); - params.bed_shrink_y = std::max(5.f, params.bed_shrink_y); - params.min_obj_distance = std::max(scaled(10.0), params.min_obj_distance); - } - - // do not inflate brim_width. Objects are allowed to have overlapped brim. - Points bedpts = get_bed_shape(*m_plater->config()); - BoundingBox bedbb = Polygon(bedpts).bounding_box(); - std::for_each(m_selected.begin(), m_selected.end(), [&](ArrangePolygon &ap) { - ap.inflation = params.min_obj_distance / 2; - 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); - } - }); - // For occulusion regions, inflation should be larger to prevent genrating brim on them. - // However, extrusion cali regions are exceptional, since we can allow brim overlaps them. - // 屏蔽区域只需要膨胀brim宽度,防止brim长过去;挤出标定区域不需要膨胀,brim可以长过去。 - // 以前我们认为还需要膨胀clearance_radius/2,这其实是不需要的,因为这些区域并不会真的摆放物体, - // 其他物体的膨胀轮廓是可以跟它们重叠的。 + Points bedpts = get_shrink_bedpts(*m_plater,params); double scaled_exclusion_gap = scale_(1); - std::for_each(m_unselected.begin(), m_unselected.end(), [&](auto &ap) { - ap.inflation = !ap.is_virt_object ? - params.min_obj_distance / 2 : - (ap.is_extrusion_cali_object ? 0 : scaled_exclusion_gap); - }); - - partplate_list.preprocess_exclude_areas(params.excluded_regions, 1, scaled_exclusion_gap); - // shrink bed by moving to center by dist - auto shrinkFun = [](Points& bedpts, double dist, int direction) { -#define SGN(x) ((x)>=0?1:-1) - Point center = Polygon(bedpts).bounding_box().center(); - for (auto& pt : bedpts) - pt[direction] += dist * SGN(center[direction] - pt[direction]); - }; - shrinkFun(bedpts, scaled(params.bed_shrink_x), 0); - shrinkFun(bedpts, scaled(params.bed_shrink_y), 1); - BOOST_LOG_TRIVIAL(debug) << "arrange bed_shrink_x=" << params.bed_shrink_x - << ", brim_max= "< get_wipe_tower_arrangepoly(const Plater &plater) { - // BBS FIXME: use actual plate_idx - if (auto wti = get_wipe_tower(plater, 0)) + int id = plater.canvas3D()->fff_print()->get_plate_index(); + if (auto wti = get_wipe_tower(plater, id)) return get_wipetower_arrange_poly(&wti); return {}; @@ -775,12 +704,12 @@ get_wipe_tower_arrangepoly(const Plater &plater) //BBS: add sudoku-style stride double bed_stride_x(const Plater* plater) { double bedwidth = plater->build_volume().bounding_box().size().x(); - return scaled((1. + LOGICAL_BED_GAP) * bedwidth); + return (1. + LOGICAL_BED_GAP) * bedwidth; } double bed_stride_y(const Plater* plater) { double beddepth = plater->build_volume().bounding_box().size().y(); - return scaled((1. + LOGICAL_BED_GAP) * beddepth); + return (1. + LOGICAL_BED_GAP) * beddepth; } @@ -800,4 +729,107 @@ arrangement::ArrangeParams get_arrange_params(Plater *p) return params; } +// call before get selected and unselected +arrangement::ArrangeParams init_arrange_params(Plater *p) +{ + arrangement::ArrangeParams params; + const GLCanvas3D::ArrangeSettings &settings = static_cast(p->canvas3D())->get_arrange_settings(); + auto & print = wxGetApp().plater()->get_partplate_list().get_current_fff_print(); + + params.clearance_height_to_rod = print.config().extruder_clearance_height_to_rod.value; + params.clearance_height_to_lid = print.config().extruder_clearance_height_to_lid.value; + params.cleareance_radius = print.config().extruder_clearance_radius.value; + params.printable_height = print.config().printable_height.value; + params.allow_rotations = settings.enable_rotation; + params.allow_multi_materials_on_same_plate = settings.allow_multi_materials_on_same_plate; + params.avoid_extrusion_cali_region = settings.avoid_extrusion_cali_region; + params.is_seq_print = settings.is_seq_print; + params.min_obj_distance = scaled(settings.distance); + params.bed_shrink_x = settings.bed_shrink_x; + params.bed_shrink_y = settings.bed_shrink_y; + + int state = p->get_prepare_state(); + if (state == Job::JobPrepareState::PREPARE_STATE_MENU) { + PartPlateList &plate_list = p->get_partplate_list(); + PartPlate * plate = plate_list.get_curr_plate(); + params.is_seq_print = plate->get_real_print_seq() == PrintSequence::ByObject; + } + + if (params.is_seq_print) + params.min_obj_distance = std::max(params.min_obj_distance, scaled(params.cleareance_radius + 0.001)); // +0.001mm to avoid clearance check fail due to rounding error + return params; +} + +//after get selected.call this to update bed_shrink +void update_arrange_params(arrangement::ArrangeParams ¶ms, const Plater &p, const arrangement::ArrangePolygons &selected) +{ + const GLCanvas3D::ArrangeSettings &settings = static_cast(p.canvas3D())->get_arrange_settings(); + auto & print = wxGetApp().plater()->get_partplate_list().get_current_fff_print(); + double skirt_distance = print.has_skirt() ? print.config().skirt_distance.value : 0; + double brim_max = 0; + std::for_each(selected.begin(), selected.end(), [&](const ArrangePolygon &ap) { brim_max = std::max(brim_max, ap.brim_width); }); + // Note: skirt_distance is now defined between outermost brim and skirt, not the object and skirt. + // So we can't do max but do adding instead. + params.brim_skirt_distance = skirt_distance + brim_max; + params.bed_shrink_x = settings.bed_shrink_x + params.brim_skirt_distance; + params.bed_shrink_y = settings.bed_shrink_y + params.brim_skirt_distance; + // for sequential print, we need to inflate the bed because cleareance_radius is so large + if (params.is_seq_print) { + float shift_dist = params.cleareance_radius / 2 - 5; + params.bed_shrink_x -= shift_dist; + params.bed_shrink_y -= shift_dist; + } +} + +//it will bed accurate after call update_params +Points get_shrink_bedpts(const Plater &plater, const arrangement::ArrangeParams ¶ms) +{ + Points bedpts = get_bed_shape(*plater.config()); + // shrink bed by moving to center by dist + auto shrinkFun = [](Points &bedpts, double dist, int direction) { +#define SGN(x) ((x) >= 0 ? 1 : -1) + Point center = Polygon(bedpts).bounding_box().center(); + for (auto &pt : bedpts) pt[direction] += dist * SGN(center[direction] - pt[direction]); + }; + shrinkFun(bedpts, scaled(params.bed_shrink_x), 0); + shrinkFun(bedpts, scaled(params.bed_shrink_y), 1); + return bedpts; +} + +void update_selected_items_inflation(arrangement::ArrangePolygons &selected, const Plater &p, const arrangement::ArrangeParams ¶ms) { + // do not inflate brim_width. Objects are allowed to have overlapped brim. + Points bedpts = get_shrink_bedpts(p, params); + BoundingBox bedbb = Polygon(bedpts).bounding_box(); + std::for_each(selected.begin(), selected.end(), [&](ArrangePolygon &ap) { + ap.inflation = std::max(scaled(ap.brim_width), params.min_obj_distance / 2); + 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); + } + }); +} + +void update_unselected_items_inflation(arrangement::ArrangePolygons &unselected, const Plater &p, const arrangement::ArrangeParams ¶ms) +{ + if (params.is_seq_print) { + float shift_dist = params.cleareance_radius / 2 - 5; + // dont forget to move the excluded region + for (auto ®ion : unselected) { + if (region.is_virt_object) region.poly.translate(-scaled(shift_dist), -scaled(shift_dist)); + } + } + // For occulusion regions, inflation should be larger to prevent genrating brim on them. + // However, extrusion cali regions are exceptional, since we can allow brim overlaps them. + // 屏蔽区域只需要膨胀brim宽度,防止brim长过去;挤出标定区域不需要膨胀,brim可以长过去。 + // 以前我们认为还需要膨胀clearance_radius/2,这其实是不需要的,因为这些区域并不会真的摆放物体, + // 其他物体的膨胀轮廓是可以跟它们重叠的。 + double scaled_exclusion_gap = scale_(1); + std::for_each(unselected.begin(), unselected.end(), + [&](auto &ap) { ap.inflation = !ap.is_virt_object ? std::max(scaled(ap.brim_width), params.min_obj_distance / 2) + : (ap.is_extrusion_cali_object ? 0 : scaled_exclusion_gap); }); +} + }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 6e8ebe028..e4ec09b5e 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -78,6 +78,16 @@ double bed_stride_y(const Plater* plater); arrangement::ArrangeParams get_arrange_params(Plater *p); +arrangement::ArrangeParams init_arrange_params(Plater *p); + +Points get_shrink_bedpts(const Plater& plater,const arrangement::ArrangeParams& params); + +void update_arrange_params(arrangement::ArrangeParams ¶ms, const Plater &p, const arrangement::ArrangePolygons &selected); + +void update_selected_items_inflation(arrangement::ArrangePolygons &selected, const Plater &p, const arrangement::ArrangeParams ¶ms); + +void update_unselected_items_inflation(arrangement::ArrangePolygons &unselected, const Plater &p, const arrangement::ArrangeParams ¶ms); + }} // namespace Slic3r::GUI #endif // ARRANGEJOB_HPP diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index e30affafd..379885bc0 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "libnest2d/common.hpp" #include @@ -22,6 +23,8 @@ void FillBedJob::prepare() m_unselected.clear(); m_bedpts.clear(); + params = init_arrange_params(m_plater); + m_object_idx = m_plater->get_selected_object_idx(); if (m_object_idx == -1) return; @@ -42,6 +45,7 @@ void FillBedJob::prepare() ModelObject *model_object = m_plater->model().objects[m_object_idx]; if (model_object->instances.empty()) return; + const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config(); m_selected.reserve(model_object->instances.size()); for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { @@ -50,7 +54,7 @@ void FillBedJob::prepare() { bool selected = (oidx == m_object_idx); - ArrangePolygon ap = get_arrange_poly(mo->instances[inst_idx]); + ArrangePolygon ap = get_instance_arrange_poly(mo->instances[inst_idx], global_config); BoundingBox ap_bb = ap.transformed_poly().contour.bounding_box(); ap.height = 1; ap.name = mo->name; @@ -117,6 +121,8 @@ void FillBedJob::prepare() if (m_selected.empty()) return; //add the virtual object into unselect list if has + double scaled_exclusion_gap = scale_(1); + plate_list.preprocess_exclude_areas(params.excluded_regions, 1, scaled_exclusion_gap); plate_list.preprocess_exclude_areas(m_unselected); m_bedpts = get_bed_shape(*m_plater->config()); @@ -134,14 +140,15 @@ void FillBedJob::prepare() ap.bed_idx = arrangement::UNARRANGED; m_unselected.emplace_back(ap); - } - + }*/ if (auto wt = get_wipe_tower_arrangepoly(*m_plater)) - m_unselected.emplace_back(std::move(*wt));*/ + m_unselected.emplace_back(std::move(*wt)); double sc = scaled(1.) * scaled(1.); - ExPolygon poly = m_selected.front().poly; + const GLCanvas3D::ArrangeSettings& settings = static_cast(m_plater->canvas3D())->get_arrange_settings(); + auto polys = offset_ex(m_selected.front().poly, scaled(settings.distance) / 2); + ExPolygon poly = polys.empty() ? m_selected.front().poly : polys.front(); double poly_area = poly.area() / sc; double unsel_area = std::accumulate(m_unselected.begin(), m_unselected.end(), 0., @@ -161,7 +168,7 @@ void FillBedJob::prepare() // if the selection is not a single instance, choose the first as template //sel_id = std::max(sel_id, 0); ModelInstance *mi = model_object->instances[sel_id]; - ArrangePolygon template_ap = get_arrange_poly(mi); + ArrangePolygon template_ap = get_instance_arrange_poly(mi, global_config); for (int i = 0; i < needed_items; ++i) { ArrangePolygon ap = template_ap; @@ -171,8 +178,10 @@ void FillBedJob::prepare() ap.itemid = -1; ap.setter = [this, mi](const ArrangePolygon &p) { ModelObject *mo = m_plater->model().objects[m_object_idx]; - ModelInstance *inst = mo->add_instance(*mi); - inst->apply_arrange_result(p.translation.cast(), p.rotation); + ModelObject* newObj = m_plater->model().add_object(*mo); + newObj->name = mo->name +" "+ std::to_string(p.itemid); + for (ModelInstance *newInst : newObj->instances) { newInst->apply_arrange_result(p.translation.cast(), p.rotation); } + //m_plater->sidebar().obj_list()->paste_objects_into_list({m_plater->model().objects.size()-1}); }; m_selected.emplace_back(ap); } @@ -196,9 +205,19 @@ void FillBedJob::process() const GLCanvas3D::ArrangeSettings &settings = static_cast(m_plater->canvas3D())->get_arrange_settings(); - arrangement::ArrangeParams params; - params.allow_rotations = settings.enable_rotation; - params.min_obj_distance = scaled(settings.distance); + update_arrange_params(params, *m_plater, m_selected); + m_bedpts = get_shrink_bedpts(*m_plater, params); + + auto &partplate_list = m_plater->get_partplate_list(); + auto &print = wxGetApp().plater()->get_partplate_list().get_current_fff_print(); + const Slic3r::DynamicPrintConfig& global_config = wxGetApp().preset_bundle->full_config(); + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + const bool has_lidar = preset_bundle->printers.get_edited_preset().has_lidar(preset_bundle); + if (has_lidar && params.avoid_extrusion_cali_region && global_config.opt_bool("scan_first_layer")) + partplate_list.preprocess_nonprefered_areas(m_unselected, MAX_NUM_PLATES); + + update_selected_items_inflation(m_selected, *m_plater, params); + update_unselected_items_inflation(m_unselected, *m_plater, params); bool do_stop = false; params.stopcondition = [this, &do_stop]() { @@ -206,20 +225,22 @@ void FillBedJob::process() }; params.progressind = [this](unsigned st,std::string str="") { - // if (st > 0) - // update_status(int(m_status_range - st), _L("Filling bed " + str)); + if (st > 0) + update_status(st, _L("Filling bed " + str)); }; params.on_packed = [&do_stop] (const ArrangePolygon &ap) { do_stop = ap.bed_idx > 0 && ap.priority == 0; }; + // final align用的是凸包,在有fixed item的情况下可能找到的参考点位置是错的,这里就不做了。见STUDIO-3265 + params.do_final_align = !has_lidar; arrangement::arrange(m_selected, m_unselected, m_bedpts, params); // finalize just here. - // update_status(m_status_range, was_canceled() ? - // _(L("Bed filling canceled.")) : - // _(L("Bed filling done."))); + update_status(m_status_range, was_canceled() ? + _(L("Bed filling canceled.")) : + _(L("Bed filling done."))); } void FillBedJob::finalize() @@ -243,11 +264,14 @@ void FillBedJob::finalize() return s + int(ap.priority == 0 && ap.bed_idx == 0); }); + int oldSize = m_plater->model().objects.size(); + if (added_cnt > 0) { //BBS: adjust the selected instances for (ArrangePolygon& ap : m_selected) { if (ap.bed_idx != 0) { - if (ap.itemid == -1) + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":skipped: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)); + /*if (ap.itemid == -1)*/ continue; ap.bed_idx = plate_list.get_plate_count(); } @@ -263,33 +287,24 @@ void FillBedJob::finalize() BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(":selected: bed_id %1%, trans {%2%,%3%}") % ap.bed_idx % unscale(ap.translation(X)) % unscale(ap.translation(Y)); } - for (size_t inst_idx = 0; inst_idx < model_object->instances.size(); ++inst_idx) - { - plate_list.notify_instance_update(m_object_idx, inst_idx); + + int newSize = m_plater->model().objects.size(); + auto obj_list = m_plater->sidebar().obj_list(); + for (size_t i = oldSize; i < newSize; i++) { + obj_list->add_object_to_list(i, true, true, false); } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": paste_objects_into_list"; + /*for (ArrangePolygon& ap : m_selected) { if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0)) ap.apply(); }*/ - model_object->ensure_on_bed(); + //model_object->ensure_on_bed(); + //BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << ": model_object->ensure_on_bed()"; m_plater->update(); - - //BBS: add partplate related logic - int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, - [cur_plate](int s, auto& ap) { - return s + int(ap.priority == 0 && ap.bed_idx == cur_plate); - //return s + int(ap.priority == 0 && ap.bed_idx == 0); - }); - - // FIXME: somebody explain why this is needed for increase_object_instances - if (inst_cnt == 1) added_cnt++; - - if (added_cnt > 0) - m_plater->sidebar() - .obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt)); } Job::finalize(); diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index cd0c32c1b..f78b1f9f2 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -21,6 +21,8 @@ class FillBedJob : public PlaterJob Points m_bedpts; + arrangement::ArrangeParams params; + int m_status_range = 0; protected: diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 63ec229cf..f26e6b9aa 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -549,7 +549,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_ m_print_btn->Enable(m_print_enable); if (m_print_enable) { PresetBundle &preset_bundle = *wxGetApp().preset_bundle; - if (preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle)) + if (preset_bundle.printers.get_edited_preset().has_lidar(&preset_bundle)) wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_PRINT_PLATE)); else wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SEND_GCODE)); @@ -907,7 +907,7 @@ void MainFrame::init_tabpanel() { int old_sel = e.GetOldSelection(); int new_sel = e.GetSelection(); if (wxGetApp().preset_bundle && - wxGetApp().preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(wxGetApp().preset_bundle) && + wxGetApp().preset_bundle->printers.get_edited_preset().has_lidar(wxGetApp().preset_bundle) && new_sel == tpMonitor) { if (!wxGetApp().getAgent()) { e.Veto(); @@ -1505,7 +1505,7 @@ wxBoxSizer* MainFrame::create_side_tools() SidePopup* p = new SidePopup(this); if (wxGetApp().preset_bundle - && !wxGetApp().preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(wxGetApp().preset_bundle)) { + && !wxGetApp().preset_bundle->printers.get_edited_preset().has_lidar(wxGetApp().preset_bundle)) { // ThirdParty Buttons SideButton* export_gcode_btn = new SideButton(p, _L("Export G-code file"), ""); export_gcode_btn->SetCornerRadius(0); @@ -3410,7 +3410,7 @@ void MainFrame::load_printer_url(wxString url) void MainFrame::load_printer_url() { PresetBundle &preset_bundle = *wxGetApp().preset_bundle; - if (preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle)) + if (preset_bundle.printers.get_edited_preset().has_lidar(&preset_bundle)) return; auto cfg = preset_bundle.printers.get_edited_preset().config; diff --git a/src/slic3r/GUI/PlateSettingsDialog.cpp b/src/slic3r/GUI/PlateSettingsDialog.cpp index eb44b19ac..c23d3bd35 100644 --- a/src/slic3r/GUI/PlateSettingsDialog.cpp +++ b/src/slic3r/GUI/PlateSettingsDialog.cpp @@ -23,7 +23,7 @@ PlateSettingsDialog::PlateSettingsDialog(wxWindow* parent, wxWindowID id, const top_sizer->SetFlexibleDirection(wxBOTH); top_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED); - bool is_bbl = wxGetApp().preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(wxGetApp().preset_bundle); + bool is_bbl = wxGetApp().preset_bundle->printers.get_edited_preset().has_lidar(wxGetApp().preset_bundle); if (is_bbl) { m_bed_type_choice = new ComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(FromDIP(240), -1), 0, NULL, wxCB_READONLY); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6221c0092..3dcdb87cd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1024,7 +1024,7 @@ void Sidebar::update_all_preset_comboboxes() PresetBundle &preset_bundle = *wxGetApp().preset_bundle; const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology(); - bool is_bbl_preset = preset_bundle.printers.get_edited_preset().is_bbl_vendor_preset(&preset_bundle); + bool is_bbl_preset = preset_bundle.printers.get_edited_preset().has_lidar(&preset_bundle); auto p_mainframe = wxGetApp().mainframe; @@ -8183,7 +8183,7 @@ void Plater::_calib_pa_pattern(const Calib_Params& params) const DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(); PresetBundle* preset_bundle = wxGetApp().preset_bundle; - const bool is_bbl_machine = preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(preset_bundle); + const bool is_bbl_machine = preset_bundle->printers.get_edited_preset().has_lidar(preset_bundle); const Vec3d plate_origin = get_partplate_list().get_current_plate_origin(); CalibPressureAdvancePattern pa_pattern( params, @@ -10403,7 +10403,7 @@ void Plater::reslice() model().calib_pa_pattern->generate_custom_gcodes( wxGetApp().preset_bundle->full_config(), - preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(preset_bundle), + preset_bundle->printers.get_edited_preset().has_lidar(preset_bundle), model(), get_partplate_list().get_current_plate_origin() ); diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 813569231..170aed416 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -1039,7 +1039,7 @@ void PlaterPresetComboBox::update() //if (i + 1 == m_collection->num_default_presets()) // set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); } - if (m_type == Preset::TYPE_FILAMENT && m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle)) + if (m_type == Preset::TYPE_FILAMENT && m_preset_bundle->printers.get_edited_preset().has_lidar(m_preset_bundle)) add_ams_filaments(into_u8(selected_user_preset), true); //BBS: add project embedded preset logic @@ -1267,7 +1267,7 @@ void TabPresetComboBox::update() // set_label_marker(Append(separator(L("System presets")), wxNullBitmap)); } - if (m_type == Preset::TYPE_FILAMENT && m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle)) + if (m_type == Preset::TYPE_FILAMENT && m_preset_bundle->printers.get_edited_preset().has_lidar(m_preset_bundle)) add_ams_filaments(into_u8(selected)); //BBS: add project embedded preset logic diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 76afc9f92..540c59434 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1644,9 +1644,9 @@ void Tab::on_presets_changed() // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets wxGetApp().plater()->sidebar().update_presets(m_type); - bool is_bbl_vendor_preset = wxGetApp().preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(wxGetApp().preset_bundle); + bool has_lidar = wxGetApp().preset_bundle->printers.get_edited_preset().has_lidar(wxGetApp().preset_bundle); auto& printer_cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config; - if (is_bbl_vendor_preset) + if (has_lidar) wxGetApp().plater()->get_partplate_list().set_render_option( !printer_cfg.option("bbl_calib_mark_logo")->value, true); else @@ -2873,7 +2873,7 @@ void TabFilament::toggle_options() bool is_BBL_printer = false; if (m_preset_bundle) { is_BBL_printer = - m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset( + m_preset_bundle->printers.get_edited_preset().has_lidar( m_preset_bundle); } @@ -3669,7 +3669,7 @@ void TabPrinter::toggle_options() //BBS: whether the preset is Bambu Lab printer bool is_BBL_printer = false; if (m_preset_bundle) { - is_BBL_printer = m_preset_bundle->printers.get_edited_preset().is_bbl_vendor_preset(m_preset_bundle); + is_BBL_printer = m_preset_bundle->printers.get_edited_preset().has_lidar(m_preset_bundle); } bool have_multiple_extruders = true;